diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..72f2855 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install + run: npm ci + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Test + run: npm test + + - name: Build + run: npm run build + + - name: Upload GitHub Pages artifact + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: ./out + + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: verify + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index e215bc4..c666f80 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,56 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Upabode -## Getting Started +Upabode is a small rental listing finder. It ships with 12 sample homes across Bengaluru, Pune, Hyderabad, and Kochi. Search and filters run in the browser, so the site needs no account, database, or API key. -First, run the development server: +**Live site:** [https://naveecode.github.io/upabode/](https://naveecode.github.io/upabode/) -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` +GitHub Pages has to be set to **GitHub Actions** before that URL serves this build. The deploy workflow in this repo publishes the static `out/` folder on every push to `main`. + +## Features -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +- Text search across title, neighborhood, city, description, and amenities +- Filters for city, home type, maximum rent, bedrooms, pets, and furnished +- Sort by rent or by recently posted +- A listing detail dialog with availability and amenities +- Sample data is bundled with the page -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +## Stack -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +- Next.js 16 (App Router) and React 19 +- TypeScript +- Static export (`output: 'export'`) with `basePath` `/upabode` for GitHub project pages +- Vitest for the search and filter functions +- GitHub Actions for lint, typecheck, unit tests, build, and Pages deploy -## Learn More +## Screenshots -To learn more about Next.js, take a look at the following resources: +![Browse page with search, filters, and listing cards](docs/screenshots/browse.png) -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +![Bengaluru filter narrowed to three homes](docs/screenshots/filters.png) -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +![Listing detail dialog](docs/screenshots/detail.png) + +## Run locally + +```bash +npm install +npm run dev +``` + +Open [http://localhost:3000/upabode](http://localhost:3000/upabode). The `/upabode` prefix matches the GitHub Pages base path. + +```bash +npm test +npm run lint +npm run typecheck +npm run build +``` -## Deploy on Vercel +`npm run build` writes the static site to `out/`. -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +## What I learned -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +- A static export can only include what is known at build time. Keeping the listings in the client, and the matching rules in plain functions, meant the page could be hosted as files. +- Those functions are easier to trust once the awkward cases are tests: blank queries, combined filters, every search word required, and sorts that do not reorder the original array. +- Project pages need `basePath: '/upabode'` so CSS, JavaScript, and fonts load from `/upabode/_next/...` instead of the domain root. +- This repository already contained a larger social app (Multigram) that depends on a database, cookies, and route handlers. That cannot be a static export, so the source is preserved under [`legacy/multigram`](legacy/multigram) rather than deleted. Credentials that had been committed in its Render config were replaced with placeholders; the old values are still in git history and should be rotated. diff --git a/docs/screenshots/browse.png b/docs/screenshots/browse.png new file mode 100644 index 0000000..8755d1b Binary files /dev/null and b/docs/screenshots/browse.png differ diff --git a/docs/screenshots/detail.png b/docs/screenshots/detail.png new file mode 100644 index 0000000..e0ffa58 Binary files /dev/null and b/docs/screenshots/detail.png differ diff --git a/docs/screenshots/filters.png b/docs/screenshots/filters.png new file mode 100644 index 0000000..72e0f86 Binary files /dev/null and b/docs/screenshots/filters.png differ diff --git a/eslint.config.mjs b/eslint.config.mjs index 05e726d..334b50a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,13 +5,13 @@ import nextTs from "eslint-config-next/typescript"; const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, - // Override default ignores of eslint-config-next. globalIgnores([ - // Default ignores of eslint-config-next: ".next/**", "out/**", "build/**", "next-env.d.ts", + "legacy/**", + "scratch/**", ]), ]); diff --git a/legacy/multigram/README.md b/legacy/multigram/README.md new file mode 100644 index 0000000..32719bf --- /dev/null +++ b/legacy/multigram/README.md @@ -0,0 +1,9 @@ +# Multigram (preserved snapshot) + +This folder is the previous app that lived at the root of this repository: a full-stack social product (posts, reels, chat, calls, Google sign-in) built with Next.js, Prisma, and a Node server. + +It is kept here so that work is not thrown away. It is **not** the site deployed to GitHub Pages. Static export cannot run it: the app uses server actions, route handlers, cookies, a database, and request-time APIs. + +To run this snapshot, check out the commit before the portfolio change and use that tree as the project root (it expects `DATABASE_URL` and the other service credentials). Do not commit those values. + +`render.yaml` in this folder no longer contains live credentials. If those values were ever pushed, rotate them in Pusher, UploadThing, and the database provider. They can still be recovered from older git history. diff --git a/legacy/multigram/eslint.config.mjs b/legacy/multigram/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/legacy/multigram/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/fix_audio.py b/legacy/multigram/fix_audio.py similarity index 100% rename from fix_audio.py rename to legacy/multigram/fix_audio.py diff --git a/fix_avatars.py b/legacy/multigram/fix_avatars.py similarity index 100% rename from fix_avatars.py rename to legacy/multigram/fix_avatars.py diff --git a/fix_callback.py b/legacy/multigram/fix_callback.py similarity index 100% rename from fix_callback.py rename to legacy/multigram/fix_callback.py diff --git a/fix_log.py b/legacy/multigram/fix_log.py similarity index 100% rename from fix_log.py rename to legacy/multigram/fix_log.py diff --git a/fix_mobilenav.py b/legacy/multigram/fix_mobilenav.py similarity index 100% rename from fix_mobilenav.py rename to legacy/multigram/fix_mobilenav.py diff --git a/fix_mobilenav_v3.py b/legacy/multigram/fix_mobilenav_v3.py similarity index 100% rename from fix_mobilenav_v3.py rename to legacy/multigram/fix_mobilenav_v3.py diff --git a/fix_post_autoplay.py b/legacy/multigram/fix_post_autoplay.py similarity index 100% rename from fix_post_autoplay.py rename to legacy/multigram/fix_post_autoplay.py diff --git a/fix_post_pause.py b/legacy/multigram/fix_post_pause.py similarity index 100% rename from fix_post_pause.py rename to legacy/multigram/fix_post_pause.py diff --git a/fix_post_video.py b/legacy/multigram/fix_post_video.py similarity index 100% rename from fix_post_video.py rename to legacy/multigram/fix_post_video.py diff --git a/fix_profile.py b/legacy/multigram/fix_profile.py similarity index 100% rename from fix_profile.py rename to legacy/multigram/fix_profile.py diff --git a/fix_reel_observer.py b/legacy/multigram/fix_reel_observer.py similarity index 100% rename from fix_reel_observer.py rename to legacy/multigram/fix_reel_observer.py diff --git a/fix_reel_touch.py b/legacy/multigram/fix_reel_touch.py similarity index 100% rename from fix_reel_touch.py rename to legacy/multigram/fix_reel_touch.py diff --git a/fix_reel_viewer.py b/legacy/multigram/fix_reel_viewer.py similarity index 100% rename from fix_reel_viewer.py rename to legacy/multigram/fix_reel_viewer.py diff --git a/fix_reels.py b/legacy/multigram/fix_reels.py similarity index 100% rename from fix_reels.py rename to legacy/multigram/fix_reels.py diff --git a/fix_route.py b/legacy/multigram/fix_route.py similarity index 100% rename from fix_route.py rename to legacy/multigram/fix_route.py diff --git a/fix_transform.py b/legacy/multigram/fix_transform.py similarity index 100% rename from fix_transform.py rename to legacy/multigram/fix_transform.py diff --git a/fix_transform_2.py b/legacy/multigram/fix_transform_2.py similarity index 100% rename from fix_transform_2.py rename to legacy/multigram/fix_transform_2.py diff --git a/legacy/multigram/next.config.ts b/legacy/multigram/next.config.ts new file mode 100644 index 0000000..8b05112 --- /dev/null +++ b/legacy/multigram/next.config.ts @@ -0,0 +1,42 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + typescript: { + // Skip typecheck during container build to save 300MB RAM (we verify locally) + ignoreBuildErrors: true, + }, + + experimental: { + // Restrict to single thread to stay well under 512MB RAM on free tier + cpus: 1, + workerThreads: false, + }, + + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'X-Frame-Options', + value: 'SAMEORIGIN', + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin', + }, + { + key: 'Permissions-Policy', + value: 'camera=(self), microphone=(self), geolocation=()', + }, + ], + }, + ]; + }, +}; + +export default nextConfig; diff --git a/legacy/multigram/package.json b/legacy/multigram/package.json new file mode 100644 index 0000000..aa83294 --- /dev/null +++ b/legacy/multigram/package.json @@ -0,0 +1,36 @@ +{ + "name": "multigram", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "npm install \u0026\u0026 npx prisma generate \u0026\u0026 npx prisma db push \u0026\u0026 next build", + "start": "next start", + "lint": "eslint", + "postinstall": "prisma generate" + }, + "dependencies": { + "@prisma/client": "^5.11.0", + "@uploadthing/react": "^7.3.3", + "jose": "^6.2.12", + "next": "16.3.5", + "peerjs": "^1.5.5", + "pusher": "^5.3.4", + "pusher-js": "^8.6.0", + "react": "19.2.8", + "react-dom": "19.2.8", + "uploadthing": "^7.7.4" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.3.5", + "prisma": "^5.11.0", + "tailwindcss": "^4", + "tsx": "^4.23.13", + "typescript": "^5" + } +} diff --git a/legacy/multigram/postcss.config.mjs b/legacy/multigram/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/legacy/multigram/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/prisma/schema.prisma b/legacy/multigram/prisma/schema.prisma similarity index 100% rename from prisma/schema.prisma rename to legacy/multigram/prisma/schema.prisma diff --git a/prisma/seed.ts b/legacy/multigram/prisma/seed.ts similarity index 100% rename from prisma/seed.ts rename to legacy/multigram/prisma/seed.ts diff --git a/public/.well-known/assetlinks.json b/legacy/multigram/public/.well-known/assetlinks.json similarity index 100% rename from public/.well-known/assetlinks.json rename to legacy/multigram/public/.well-known/assetlinks.json diff --git a/public/favicon.ico b/legacy/multigram/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to legacy/multigram/public/favicon.ico diff --git a/public/file.svg b/legacy/multigram/public/file.svg similarity index 100% rename from public/file.svg rename to legacy/multigram/public/file.svg diff --git a/public/globe.svg b/legacy/multigram/public/globe.svg similarity index 100% rename from public/globe.svg rename to legacy/multigram/public/globe.svg diff --git a/public/icon.png b/legacy/multigram/public/icon.png similarity index 100% rename from public/icon.png rename to legacy/multigram/public/icon.png diff --git a/public/manifest.json b/legacy/multigram/public/manifest.json similarity index 100% rename from public/manifest.json rename to legacy/multigram/public/manifest.json diff --git a/public/multigram-icon.png b/legacy/multigram/public/multigram-icon.png similarity index 100% rename from public/multigram-icon.png rename to legacy/multigram/public/multigram-icon.png diff --git a/public/multigram-minimal.png b/legacy/multigram/public/multigram-minimal.png similarity index 100% rename from public/multigram-minimal.png rename to legacy/multigram/public/multigram-minimal.png diff --git a/public/multigram-minimal.svg b/legacy/multigram/public/multigram-minimal.svg similarity index 100% rename from public/multigram-minimal.svg rename to legacy/multigram/public/multigram-minimal.svg diff --git a/public/multigram.apk b/legacy/multigram/public/multigram.apk similarity index 100% rename from public/multigram.apk rename to legacy/multigram/public/multigram.apk diff --git a/public/next.svg b/legacy/multigram/public/next.svg similarity index 100% rename from public/next.svg rename to legacy/multigram/public/next.svg diff --git a/public/vercel.svg b/legacy/multigram/public/vercel.svg similarity index 100% rename from public/vercel.svg rename to legacy/multigram/public/vercel.svg diff --git a/public/window.svg b/legacy/multigram/public/window.svg similarity index 100% rename from public/window.svg rename to legacy/multigram/public/window.svg diff --git a/render.yaml b/legacy/multigram/render.yaml similarity index 62% rename from render.yaml rename to legacy/multigram/render.yaml index d9bc00f..cfc3380 100644 --- a/render.yaml +++ b/legacy/multigram/render.yaml @@ -13,12 +13,12 @@ services: - key: SESSION_SECRET generateValue: true - key: PUSHER_APP_ID - value: "2194387" + sync: false - key: NEXT_PUBLIC_PUSHER_KEY - value: "a1d789b8b44c24f2dac8" + sync: false - key: PUSHER_SECRET - value: "4127fdaf450956e0782f" + sync: false - key: NEXT_PUBLIC_PUSHER_CLUSTER - value: "ap2" + sync: false - key: UPLOADTHING_TOKEN - value: "eyJhcGlLZXkiOiJza19saXZlXzAwN2NiNTc0NDg4YTRjOWM1NzE1YjIxNzgwNGQ5NGEwZGY0YzVjZDBkN2NkYWE3MTg5NGJiZGVmZjllMjAwZDEiLCJhcHBJZCI6InV1N3ZoYjczcjYiLCJyZWdpb25zIjpbInNlYTEiXX0=" + sync: false diff --git a/server/Dockerfile b/legacy/multigram/server/Dockerfile similarity index 100% rename from server/Dockerfile rename to legacy/multigram/server/Dockerfile diff --git a/server/package.json b/legacy/multigram/server/package.json similarity index 100% rename from server/package.json rename to legacy/multigram/server/package.json diff --git a/server/src/index.ts b/legacy/multigram/server/src/index.ts similarity index 100% rename from server/src/index.ts rename to legacy/multigram/server/src/index.ts diff --git a/server/tsconfig.json b/legacy/multigram/server/tsconfig.json similarity index 100% rename from server/tsconfig.json rename to legacy/multigram/server/tsconfig.json diff --git a/src/app/actions.ts b/legacy/multigram/src/app/actions.ts similarity index 100% rename from src/app/actions.ts rename to legacy/multigram/src/app/actions.ts diff --git a/src/app/api/auth/google/callback/route.ts b/legacy/multigram/src/app/api/auth/google/callback/route.ts similarity index 100% rename from src/app/api/auth/google/callback/route.ts rename to legacy/multigram/src/app/api/auth/google/callback/route.ts diff --git a/src/app/api/auth/google/route.ts b/legacy/multigram/src/app/api/auth/google/route.ts similarity index 100% rename from src/app/api/auth/google/route.ts rename to legacy/multigram/src/app/api/auth/google/route.ts diff --git a/src/app/api/auth/me/route.ts b/legacy/multigram/src/app/api/auth/me/route.ts similarity index 100% rename from src/app/api/auth/me/route.ts rename to legacy/multigram/src/app/api/auth/me/route.ts diff --git a/src/app/api/calls/decline/route.ts b/legacy/multigram/src/app/api/calls/decline/route.ts similarity index 100% rename from src/app/api/calls/decline/route.ts rename to legacy/multigram/src/app/api/calls/decline/route.ts diff --git a/src/app/api/pusher/auth/route.ts b/legacy/multigram/src/app/api/pusher/auth/route.ts similarity index 100% rename from src/app/api/pusher/auth/route.ts rename to legacy/multigram/src/app/api/pusher/auth/route.ts diff --git a/src/app/api/test-db/route.ts b/legacy/multigram/src/app/api/test-db/route.ts similarity index 100% rename from src/app/api/test-db/route.ts rename to legacy/multigram/src/app/api/test-db/route.ts diff --git a/src/app/api/uploadthing/core.ts b/legacy/multigram/src/app/api/uploadthing/core.ts similarity index 100% rename from src/app/api/uploadthing/core.ts rename to legacy/multigram/src/app/api/uploadthing/core.ts diff --git a/src/app/api/uploadthing/route.ts b/legacy/multigram/src/app/api/uploadthing/route.ts similarity index 100% rename from src/app/api/uploadthing/route.ts rename to legacy/multigram/src/app/api/uploadthing/route.ts diff --git a/src/app/apple-icon.png b/legacy/multigram/src/app/apple-icon.png similarity index 100% rename from src/app/apple-icon.png rename to legacy/multigram/src/app/apple-icon.png diff --git a/src/app/auth/login/page.tsx b/legacy/multigram/src/app/auth/login/page.tsx similarity index 100% rename from src/app/auth/login/page.tsx rename to legacy/multigram/src/app/auth/login/page.tsx diff --git a/src/app/auth/register/page.tsx b/legacy/multigram/src/app/auth/register/page.tsx similarity index 100% rename from src/app/auth/register/page.tsx rename to legacy/multigram/src/app/auth/register/page.tsx diff --git a/src/app/chat/[id]/error.tsx b/legacy/multigram/src/app/chat/[id]/error.tsx similarity index 100% rename from src/app/chat/[id]/error.tsx rename to legacy/multigram/src/app/chat/[id]/error.tsx diff --git a/src/app/chat/[id]/page.tsx b/legacy/multigram/src/app/chat/[id]/page.tsx similarity index 100% rename from src/app/chat/[id]/page.tsx rename to legacy/multigram/src/app/chat/[id]/page.tsx diff --git a/src/app/chat/loading.tsx b/legacy/multigram/src/app/chat/loading.tsx similarity index 100% rename from src/app/chat/loading.tsx rename to legacy/multigram/src/app/chat/loading.tsx diff --git a/src/app/chat/page.tsx b/legacy/multigram/src/app/chat/page.tsx similarity index 100% rename from src/app/chat/page.tsx rename to legacy/multigram/src/app/chat/page.tsx diff --git a/src/app/explore/loading.tsx b/legacy/multigram/src/app/explore/loading.tsx similarity index 100% rename from src/app/explore/loading.tsx rename to legacy/multigram/src/app/explore/loading.tsx diff --git a/src/app/explore/page.tsx b/legacy/multigram/src/app/explore/page.tsx similarity index 100% rename from src/app/explore/page.tsx rename to legacy/multigram/src/app/explore/page.tsx diff --git a/src/app/favicon.ico b/legacy/multigram/src/app/favicon.ico similarity index 100% rename from src/app/favicon.ico rename to legacy/multigram/src/app/favicon.ico diff --git a/legacy/multigram/src/app/globals.css b/legacy/multigram/src/app/globals.css new file mode 100644 index 0000000..ed24879 --- /dev/null +++ b/legacy/multigram/src/app/globals.css @@ -0,0 +1,2040 @@ +@import "tailwindcss"; + +:root { + --background: #FCFBF8; + --background-soft: #F3F0E9; + --panel: rgba(255, 255, 255, 0.7); + --panel-solid: #FFFFFF; + --text: #1C1914; + --muted: #8A8175; + --line: rgba(28, 25, 20, 0.08); + --earth: #C5A059; + --earth-dark: #A68444; + --mars: #B85C5C; + --moon: #E5DFD3; + --yellow: #D1A741; + --danger: #B85C5C; + --shadow: 0 12px 30px rgba(45, 38, 28, 0.06); + --ui-scale: 1; +} + +[data-ui-scale="compact"], +body.compact-mode { + --ui-scale: 0.88; + font-size: 14.2px; +} + +body:has([data-hide-chrome="true"]) .topbar, +body:has([data-hide-chrome="true"]) .mobile-tabs-wave, +body:has([data-hide-topbar="true"]) .topbar, +body:has([data-reel-viewer="true"]) .topbar, +body.chat-room-active .topbar, +body.chat-room-active .mobile-tabs-wave { + display: none !important; +} + +body.chat-room-active, +body:has([data-hide-chrome="true"]) { + overflow: hidden !important; +} + +body.chat-room-active .page-content, +body:has([data-hide-chrome="true"]) .page-content { + padding: 0 !important; + margin: 0 !important; + max-width: 100vw !important; + width: 100vw !important; + height: 100vh !important; + max-height: 100vh !important; + overflow: hidden !important; +} + +@keyframes chatShimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes orbitSpin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes orbitPulse { + 0%, 100% { + opacity: 0.6; + } + 50% { + opacity: 1; + } +} + +.chat-media-shimmer { + background: linear-gradient(90deg, rgba(28, 25, 20, 0.05) 25%, rgba(197, 160, 89, 0.15) 50%, rgba(28, 25, 20, 0.05) 75%); + background-size: 200% 100%; + animation: chatShimmer 1.8s infinite ease-in-out; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; + overflow-x: hidden; + max-width: 100vw; + width: 100%; + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +body { + min-height: 100vh; + color: var(--text); + background: var(--background); + overflow-x: hidden; + font-family: var(--font-body), sans-serif; + font-size: 15px; + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +body::before { + position: fixed; + inset: 0; + z-index: -1; + content: ""; + opacity: 0.4; + pointer-events: none; + background-image: + radial-gradient(circle, rgba(197, 160, 89, 0.15) 0 1px, transparent 1px); + background-position: 0 0, 80px 120px; + background-size: 170px 170px, 240px 240px; +} + +button { + border: 0; + color: inherit; + font: inherit; + cursor: pointer; +} + +.app-shell { + width: min(100%, 1500px); + min-height: 100vh; + margin: 0 auto; +} + +.topbar { + position: sticky; + top: 0; + z-index: 20; + display: flex; + align-items: center; + justify-content: space-between; + height: 58px; + padding: 0 18px; + border-bottom: 1px solid var(--line); + background: rgba(7, 17, 31, 0.88); + backdrop-filter: blur(24px); +} + +@media (min-width: 769px) { + .topbar { + height: 66px; + padding: 0 32px; + } +} + +.brand { + display: flex; + align-items: center; + gap: 12px; +} + +.brand-mark { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 38px; + height: 38px; + min-width: 38px; + min-height: 38px; + max-width: 38px; + max-height: 38px; + aspect-ratio: 1 / 1; + border-radius: 50%; + overflow: hidden; + flex-shrink: 0; + box-shadow: 0 0 20px rgba(197, 160, 89, 0.35); + border: 1.5px solid rgba(197, 160, 89, 0.4); + background: var(--panel-solid); +} + +.brand-mark img { + width: 100%; + height: 100%; + min-width: 100%; + min-height: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + border-radius: 50%; + display: block; +} + +.brand-name { + font-family: var(--font-space-grotesk), "Space Grotesk", sans-serif; + font-size: 1.25rem; + font-weight: 700; + letter-spacing: -0.04em; +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.icon-button { + display: grid; + width: 42px; + height: 42px; + place-items: center; + border: 1px solid var(--line); + border-radius: 50%; + background: rgba(255, 255, 255, 0.05); + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.icon-button:hover { + border-color: rgba(255, 255, 255, 0.25); + background: rgba(0,0,0,0.08); + transform: translateY(-2px); +} + +.avatar { + display: grid; + width: 38px; + height: 38px; + place-items: center; + border: 2px solid rgba(64, 201, 162, 0.65); + border-radius: 50%; + color: #08221c; + background: linear-gradient(145deg, #eaf8f4, #6ce0bd); + font-size: 0.82rem; + font-weight: 700; +} + +.content { + display: grid; + grid-template-columns: 250px minmax(0, 680px) 250px; + gap: 42px; + justify-content: center; + padding: 42px 28px 80px; +} + +.sidebar { + position: sticky; + top: 118px; + align-self: start; +} + +.sidebar-heading { + margin-bottom: 17px; + color: var(--muted); + font-family: var(--font-space-grotesk), "Space Grotesk", sans-serif; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.15em; + text-transform: uppercase; +} + +.planet-list { + display: grid; + gap: 10px; +} + +.planet-button { + display: flex; + align-items: center; + gap: 13px; + width: 100%; + padding: 11px; + border: 1px solid transparent; + border-radius: 15px; + color: var(--text); + background: transparent; + text-align: left; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.planet-button:hover, +.planet-button.active { + border-color: var(--line); + background: rgba(255, 255, 255, 0.06); +} + +.planet-icon { + display: grid; + width: 38px; + height: 38px; + flex: 0 0 auto; + place-items: center; + border-radius: 50%; + color: #fff; + font-size: 1.15rem; + box-shadow: inset -8px -8px 12px rgba(0, 0, 0, 0.22); +} + +.planet-icon.earth { + background: linear-gradient(145deg, #4ce0b7, #14787e); +} + +.planet-icon.mars { + background: linear-gradient(145deg, #ef9873, #a43e32); +} + +.planet-icon.moon { + color: #354253; + background: linear-gradient(145deg, #edf3f6, #8793a3); +} + +.planet-copy { + display: grid; + gap: 3px; +} + +.planet-name { + font-size: 0.92rem; + font-weight: 600; +} + +.planet-status { + color: var(--muted); + font-size: 0.72rem; +} + +.feed { + min-width: 0; +} + +.feed-header { + display: flex; + align-items: end; + justify-content: space-between; + margin-bottom: 25px; +} + +.eyebrow { + margin-bottom: 6px; + color: var(--earth); + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +h1 { + font-family: var(--font-space-grotesk), "Space Grotesk", sans-serif; + font-size: clamp(2rem, 5vw, 3.2rem); + letter-spacing: -0.07em; + line-height: 0.95; +} + +.feed-subtitle { + max-width: 330px; + margin-top: 12px; + color: var(--muted); + font-size: 0.92rem; + line-height: 1.6; +} + +.feed-count { + padding: 8px 11px; + border: 1px solid var(--line); + border-radius: 100px; + color: var(--muted); + font-size: 0.76rem; + white-space: nowrap; +} + +.stories { + display: flex; + gap: 16px; + margin-bottom: 28px; + overflow-x: auto; + scrollbar-width: none; +} + +.stories::-webkit-scrollbar { + display: none; +} + +.story { + display: grid; + min-width: 72px; + gap: 7px; + justify-items: center; + color: var(--muted); + font-size: 0.7rem; +} + +.story-ring { + display: grid; + width: 62px; + height: 62px; + padding: 3px; + place-items: center; + border-radius: 50%; + background: conic-gradient(var(--earth), #8de1ff, var(--yellow), var(--earth)); +} + +.story-inner { + display: grid; + width: 100%; + height: 100%; + place-items: center; + border: 3px solid var(--background); + border-radius: 50%; + background: var(--panel-solid); + font-size: 1.25rem; +} + +.post { + position: relative; + margin-bottom: 26px; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 25px; + background: var(--panel); + box-shadow: var(--shadow); + backdrop-filter: blur(18px); +} + +.post-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 17px 18px; +} + +.user { + display: flex; + align-items: center; + gap: 11px; +} + +.user-avatar { + width: 42px !important; + height: 42px !important; + min-width: 42px !important; + min-height: 42px !important; + max-width: 42px !important; + max-height: 42px !important; + aspect-ratio: 1 / 1 !important; + border-radius: 50% !important; + overflow: hidden !important; + position: relative !important; + flex-shrink: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.avatar { + width: 38px !important; + height: 38px !important; + min-width: 38px !important; + min-height: 38px !important; + max-width: 38px !important; + max-height: 38px !important; + aspect-ratio: 1 / 1 !important; + border-radius: 50% !important; + overflow: hidden !important; + position: relative !important; + flex-shrink: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.user-avatar img, .avatar img { + width: 100% !important; + height: 100% !important; + min-width: 100% !important; + min-height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + aspect-ratio: 1 / 1 !important; + object-fit: cover !important; + object-position: center !important; + border-radius: 50% !important; + display: block !important; +} + +.user-avatar.green { + background: linear-gradient(145deg, #a9f4d9, #338d82); +} + +.user-avatar.orange { + background: linear-gradient(145deg, #ffc59e, #bc5e42); +} + +.user-avatar.blue { + background: linear-gradient(145deg, #acd7fa, #4e7598); +} + +.user-details { + display: grid; + gap: 3px; +} + +.username { + font-size: 0.9rem; + font-weight: 700; +} + +.location { + color: var(--muted); + font-size: 0.74rem; +} + +.more-button { + color: var(--muted); + background: none; + font-size: 1.2rem; +} + +.post-media { + position: relative; + aspect-ratio: 1.15; + overflow: hidden; + touch-action: pan-y; + user-select: none; + cursor: grab; +} + +.post-media:active { + cursor: grabbing; +} + +.post-media::after { + position: absolute; + inset: 0; + content: ""; + pointer-events: none; + background: linear-gradient(180deg, transparent 62%, rgba(0, 0, 0, 0.48)); +} + +.post-media.aurora { + background: + radial-gradient(ellipse at 60% 80%, rgba(68, 255, 205, 0.9), transparent 28%), + radial-gradient(ellipse at 20% 100%, rgba(26, 126, 255, 0.7), transparent 36%), + linear-gradient(165deg, #061b32 0%, #063755 42%, #041625 100%); +} + +.post-media.aurora::before { + position: absolute; + inset: -25%; + content: ""; + opacity: 0.75; + transform: rotate(-12deg); + background: repeating-linear-gradient( + 110deg, + transparent 0 32px, + rgba(93, 255, 203, 0.35) 42px 56px, + transparent 68px 105px + ); + filter: blur(16px); +} + +.post-media.mars-landscape { + background: + radial-gradient(circle at 72% 22%, #ffcc91 0 5%, transparent 6%), + linear-gradient(175deg, transparent 0 48%, rgba(61, 20, 18, 0.3) 49%), + linear-gradient(155deg, #e5a16f, #873d37 62%, #311d26); +} + +.post-media.mars-landscape::before { + position: absolute; + right: 10%; + bottom: 20%; + width: 46%; + height: 25%; + border-radius: 50% 50% 0 0; + content: ""; + background: rgba(68, 26, 25, 0.6); + filter: blur(2px); +} + +.post-media.ocean { + background: + radial-gradient(circle at 43% 37%, rgba(255, 255, 255, 0.9) 0 2%, transparent 3%), + radial-gradient(circle at 80% 55%, rgba(255, 255, 255, 0.45) 0 1%, transparent 2%), + linear-gradient(145deg, #8bd5e7, #1a668b 56%, #082a48); +} + +.media-label { + position: absolute; + right: 18px; + bottom: 16px; + z-index: 2; + color: rgba(255, 255, 255, 0.8); + font-family: var(--font-space-grotesk), "Space Grotesk", sans-serif; + font-size: 0.7rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.gesture-feedback { + position: absolute; + top: 50%; + left: 50%; + z-index: 5; + display: grid; + width: 95px; + height: 95px; + place-items: center; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + color: white; + background: rgba(7, 17, 31, 0.72); + font-size: 2rem; + opacity: 0; + transform: translate(-50%, -50%) scale(0.7); + pointer-events: none; +} + +.gesture-feedback.show { + animation: feedback 0.75s ease forwards; +} + +@keyframes feedback { + 0% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.7); + } + 25% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + 100% { + opacity: 0; + transform: translate(-50%, -50%) scale(1.15); + } +} + +.post-actions { + display: flex; + align-items: center; + gap: 15px; + padding: 18px 18px 0; +} + +.action-button { + display: flex; + align-items: center; + gap: 7px; + color: var(--muted); + background: none; + font-size: 0.84rem; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.action-button:hover { + color: var(--text); + transform: translateY(-1px); +} + +.action-button.liked { + color: var(--danger); +} + +.action-button.following { + color: var(--earth); +} + +.post-content { + padding: 13px 18px 20px; +} + +.post-likes { + margin-bottom: 8px; + font-size: 0.84rem; + font-weight: 700; +} + +.caption { + color: #d9e3eb; + font-size: 0.86rem; + line-height: 1.55; +} + +.caption strong { + color: var(--text); +} + +.comments { + display: block; + margin-top: 12px; + color: var(--muted); + background: none; + font-size: 0.78rem; +} + +.right-panel { + display: grid; + align-content: start; + gap: 23px; +} + +.info-card { + padding: 20px; + border: 1px solid var(--line); + border-radius: 21px; + background: rgba(255, 255, 255, 0.045); +} + +.info-card h2 { + margin-bottom: 9px; + font-family: var(--font-space-grotesk), "Space Grotesk", sans-serif; + font-size: 1rem; +} + +.info-card p { + color: var(--muted); + font-size: 0.8rem; + line-height: 1.6; +} + +.instruction { + display: flex; + align-items: center; + gap: 11px; + margin-top: 15px; + color: #d7e4ee; + font-size: 0.78rem; +} + +.instruction-icon { + display: grid; + width: 30px; + height: 30px; + place-items: center; + border-radius: 10px; + background: rgba(64, 201, 162, 0.14); + color: var(--earth); +} + +.coming-soon-card { + position: relative; + min-height: 180px; + overflow: hidden; + background: + linear-gradient(135deg, rgba(237, 118, 86, 0.15), transparent), + rgba(255, 255, 255, 0.045); +} + +.coming-soon-card::before { + position: absolute; + right: -25px; + bottom: -50px; + width: 170px; + height: 170px; + border-radius: 50%; + content: ""; + background: radial-gradient(circle at 35% 30%, #f0a179, #a74438 55%, #481e29); + opacity: 0.55; + box-shadow: inset -22px -22px 35px rgba(0, 0, 0, 0.3); +} + +.coming-soon-card.moon-card::before { + background: radial-gradient(circle at 35% 30%, #e7edf0, #8d9bab 65%, #46515f); +} + +.coming-label { + position: relative; + z-index: 1; + display: inline-block; + margin-bottom: 14px; + padding: 5px 8px; + border-radius: 6px; + color: #18212d; + background: var(--yellow); + font-size: 0.64rem; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.mobile-tabs { + display: none; +} + +.toast { + position: fixed; + bottom: 24px; + left: 50%; + z-index: 30; + padding: 12px 17px; + border: 1px solid var(--line); + border-radius: 100px; + background: rgba(16, 34, 57, 0.92); + box-shadow: var(--shadow); + color: var(--text); + font-size: 0.8rem; + opacity: 0; + transform: translate(-50%, 20px); + transition: 0.25s ease; + pointer-events: none; +} + +.toast.visible { + opacity: 1; + transform: translate(-50%, 0); +} + +@media (max-width: 1100px) { + .content { + grid-template-columns: 190px minmax(0, 680px); + } + + .right-panel { + display: none; + } +} + +@media (max-width: 760px) { + .topbar { + height: 65px; + padding: 0 18px; + } + + .topbar-actions .icon-button:first-child { + display: none; + } + + .content { + display: block; + padding: 27px 15px 95px; + } + + .sidebar { + position: static; + margin-bottom: 27px; + } + + .sidebar-heading { + display: none; + } + + .planet-list { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; + width: 100%; + box-sizing: border-box; + overflow: hidden; + } + + .planet-button { + width: 100%; + min-width: 0; + padding: 8px 6px; + border-color: var(--line); + background: rgba(255, 255, 255, 0.04); + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 4px; + border-radius: 16px; + box-sizing: border-box; + } + + .planet-icon { + width: 28px; + height: 28px; + font-size: 0.85rem; + margin: 0 auto; + } + + .feed-header { + align-items: start; + } + + h1 { + font-size: 2.35rem; + } + + .feed-subtitle { + max-width: 255px; + font-size: 0.82rem; + } + + .feed-count { + font-size: 0.68rem; + } + + .post { + border-radius: 20px; + } + + .post-media { + aspect-ratio: 0.96; + } + + .mobile-tabs { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 15; + display: flex; + align-items: center; + justify-content: space-around; + height: 66px; + border-top: 1px solid var(--line); + background: rgba(7, 17, 31, 0.9); + backdrop-filter: blur(22px); + } + + .mobile-tab { + display: grid; + gap: 4px; + justify-items: center; + color: var(--muted); + background: none; + font-size: 0.66rem; + } + + .mobile-tab.active { + color: var(--earth); + } + + .mobile-tab span:first-child { + font-size: 1.15rem; + } +} + +/* ───────── Auth Pages ───────── */ +.auth-page { + display: grid; + place-items: center; + min-height: calc(100vh - 76px); + padding: 40px 20px; +} + +.auth-card { + width: 100%; + max-width: 420px; + padding: 40px 35px; + border: 1px solid var(--line); + border-radius: 25px; + background: var(--panel); + backdrop-filter: blur(18px); + box-shadow: var(--shadow); +} + +.auth-card .brand { + justify-content: center; + margin-bottom: 30px; +} + +.auth-card h1 { + font-size: 1.6rem; + text-align: center; + margin-bottom: 8px; +} + +.auth-card .subtitle { + text-align: center; + color: var(--muted); + font-size: 0.88rem; + margin-bottom: 30px; +} + +.form-group { + margin-bottom: 18px; +} + +.form-label { + display: block; + font-size: 0.78rem; + color: var(--muted); + margin-bottom: 8px; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.form-input { + width: 100%; + padding: 14px 16px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--line); + color: var(--text); + font-size: 0.92rem; + font-family: inherit; + outline: none; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.form-input:focus { + border-color: var(--earth); + background: rgba(255, 255, 255, 0.08); + box-shadow: 0 0 0 3px rgba(64, 201, 162, 0.15); +} + +.form-input::placeholder { + color: rgba(145, 165, 186, 0.5); +} + +.btn-primary { + width: 100%; + padding: 15px; + border: none; + border-radius: 14px; + background: linear-gradient(135deg, var(--earth), var(--earth-dark)); + color: #07111f; + font-weight: 700; + font-size: 0.92rem; + cursor: pointer; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); + margin-top: 8px; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(64, 201, 162, 0.3); +} + +.btn-primary:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +.auth-link { + display: block; + text-align: center; + margin-top: 22px; + color: var(--muted); + font-size: 0.84rem; +} + +.auth-link a { + color: var(--earth); + text-decoration: none; + font-weight: 600; +} + +.auth-link a:hover { + text-decoration: underline; +} + +.form-error { + padding: 12px; + border-radius: 12px; + background: rgba(255, 107, 122, 0.12); + border: 1px solid rgba(255, 107, 122, 0.3); + color: var(--danger); + font-size: 0.82rem; + margin-bottom: 18px; +} + +.color-picker { + display: flex; + gap: 12px; + margin-top: 8px; +} + +.color-option { + width: 42px; + height: 42px; + border-radius: 50%; + border: 3px solid transparent; + cursor: pointer; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.color-option:hover { + transform: scale(1.1); +} + +.color-option.selected { + border-color: var(--text); + box-shadow: 0 0 0 3px rgba(64, 201, 162, 0.4); +} + +.color-option.green { background: linear-gradient(145deg, #a9f4d9, #338d82); } +.color-option.orange { background: linear-gradient(145deg, #ffc59e, #bc5e42); } +.color-option.blue { background: linear-gradient(145deg, #acd7fa, #4e7598); } + +.avatar-preview { + width: 72px; + height: 72px; + border-radius: 50%; + display: grid; + place-items: center; + font-size: 1.5rem; + font-weight: 700; + color: white; + margin: 0 auto 20px; + border: 3px solid rgba(255, 255, 255, 0.2); +} + +/* ───────── Page Transitions ───────── */ +.page-content { + animation: pageFadeIn 0.12s cubic-bezier(0.16, 1, 0.3, 1); + will-change: opacity; +} + +@keyframes pageFadeIn { + from { + opacity: 0.85; + } + to { + opacity: 1; + } +} + +/* ───────── Chat Inbox ───────── */ +.chat-inbox { + max-width: 680px; + margin: 0 auto; + padding: 30px 20px 80px; +} + +.chat-inbox h1 { + font-size: 1.8rem; + margin-bottom: 25px; +} + +.chat-list { + display: grid; + gap: 8px; +} + +.chat-item { + display: flex; + align-items: center; + gap: 14px; + padding: 16px; + border: 1px solid transparent; + border-radius: 18px; + background: rgba(255, 255, 255, 0.03); + text-decoration: none; + color: inherit; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.chat-item:hover { + border-color: var(--line); + background: rgba(255, 255, 255, 0.06); + transform: translateX(4px); +} + +.chat-item-avatar { + width: 50px; + height: 50px; + border-radius: 50%; + display: grid; + place-items: center; + font-weight: 700; + font-size: 1rem; + flex-shrink: 0; + border: 2px solid rgba(255, 255, 255, 0.15); +} + +.chat-item-details { + flex: 1; + min-width: 0; +} + +.chat-item-name { + font-weight: 600; + font-size: 0.92rem; + margin-bottom: 4px; +} + +.chat-item-preview { + color: var(--muted); + font-size: 0.8rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.chat-item-time { + color: var(--muted); + font-size: 0.72rem; + flex-shrink: 0; +} + +/* ───────── Chat Room ───────── */ +.chat-room { + display: flex; + flex-direction: column; + height: calc(100vh - 76px); + max-width: 680px; + margin: 0 auto; +} + +.chat-header { + display: flex; + align-items: center; + gap: 14px; + padding: 16px 20px; + border-bottom: 1px solid var(--line); +} + +.chat-back { + display: grid; + width: 38px; + height: 38px; + place-items: center; + border-radius: 50%; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--line); + color: var(--text); + text-decoration: none; + font-size: 1.1rem; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.chat-back:hover { + background: rgba(0,0,0,0.08); +} + +.chat-header-info { + flex: 1; +} + +.chat-header-name { + font-weight: 700; + font-size: 0.95rem; +} + +.chat-header-status { + color: var(--earth); + font-size: 0.72rem; +} + +.chat-call-buttons { + display: flex; + gap: 8px; +} + +.call-btn { + width: 38px; + height: 38px; + border-radius: 50%; + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.05); + color: var(--text); + display: grid; + place-items: center; + cursor: pointer; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.call-btn:hover { + background: var(--earth); + color: #07111f; + border-color: var(--earth); +} + +.messages-container { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; + scrollbar-width: thin; + scrollbar-color: rgba(0,0,0,0.08) transparent; +} + +.message-bubble { + max-width: 75%; + animation: msgIn 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +@keyframes msgIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +.message-sent { + align-self: flex-end; +} + +.message-received { + align-self: flex-start; +} + +.message-sender { + font-size: 0.72rem; + color: var(--muted); + margin-bottom: 4px; +} + +.message-sent .message-sender { + text-align: right; +} + +.message-body { + padding: 12px 16px; + border-radius: 18px; + font-size: 0.88rem; + line-height: 1.5; + word-wrap: break-word; +} + +.message-sent .message-body { + background: linear-gradient(135deg, var(--earth), var(--earth-dark)); + color: #07111f; + border-bottom-right-radius: 4px; +} + +.message-received .message-body { + background: rgba(255, 255, 255, 0.08); + color: var(--text); + border-bottom-left-radius: 4px; +} + +.message-body img { + width: 100%; + max-width: 280px; + border-radius: 12px; + margin-bottom: 6px; + display: block; +} + +.message-body audio { + width: 100%; + max-width: 260px; + height: 36px; + margin-top: 4px; +} + +.message-time { + font-size: 0.65rem; + color: var(--muted); + margin-top: 4px; +} + +.message-sent .message-time { + text-align: right; +} + +.chat-input-area { + display: flex; + align-items: center; + gap: 10px; + padding: 16px 20px; + border-top: 1px solid var(--line); + background: rgba(7, 17, 31, 0.5); +} + +.chat-input { + flex: 1; + padding: 13px 18px; + border-radius: 25px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--line); + color: var(--text); + font-size: 0.88rem; + font-family: inherit; + outline: none; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.chat-input:focus { + border-color: var(--earth); + background: rgba(255, 255, 255, 0.08); +} + +.chat-send-btn { + width: 44px; + height: 44px; + border-radius: 50%; + border: none; + background: linear-gradient(135deg, var(--earth), var(--earth-dark)); + color: #07111f; + font-size: 1.1rem; + cursor: pointer; + display: grid; + place-items: center; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); + flex-shrink: 0; +} + +.chat-send-btn:hover { + transform: scale(1.08); + box-shadow: 0 4px 15px rgba(64, 201, 162, 0.4); +} + +.chat-action-btn { + width: 38px; + height: 38px; + border-radius: 50%; + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.05); + color: var(--muted); + cursor: pointer; + display: grid; + place-items: center; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); + flex-shrink: 0; +} + +.chat-action-btn:hover { + color: var(--text); + background: rgba(0,0,0,0.08); +} + +/* ───────── Emoji Picker ───────── */ +.emoji-picker { + position: absolute; + bottom: 100%; + left: 0; + margin-bottom: 8px; + padding: 12px; + background: var(--panel-solid); + border: 1px solid var(--line); + border-radius: 16px; + display: grid; + grid-template-columns: repeat(8, 1fr); + gap: 4px; + box-shadow: var(--shadow); + z-index: 10; + max-height: 200px; + overflow-y: auto; +} + +.emoji-btn { + width: 36px; + height: 36px; + border: none; + background: none; + border-radius: 8px; + font-size: 1.2rem; + cursor: pointer; + display: grid; + place-items: center; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.emoji-btn:hover { + background: rgba(0,0,0,0.08); + transform: scale(1.15); +} + +/* ───────── Video Call Overlay ───────── */ +.video-overlay { + position: fixed; + inset: 0; + z-index: 50; + background: rgba(0, 0, 0, 0.95); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; +} + +.video-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + width: 90%; + max-width: 800px; +} + +.video-grid video { + width: 100%; + border-radius: 16px; + background: #111; +} + +.video-label { + color: var(--muted); + font-size: 0.8rem; + text-align: center; + margin-top: 6px; +} + +.video-controls { + display: flex; + gap: 16px; +} + +.video-control-btn { + width: 56px; + height: 56px; + border-radius: 50%; + border: none; + cursor: pointer; + display: grid; + place-items: center; + font-size: 1.3rem; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.video-control-btn.end-call { + background: var(--danger); + color: white; +} + +.video-control-btn.mute-btn { + background: rgba(255, 255, 255, 0.15); + color: white; +} + +/* ───────── Reels ───────── */ +.reels-container { + height: calc(100vh - 76px); + overflow-y: auto; + scroll-snap-type: y mandatory; + scrollbar-width: none; +} + +.reels-container::-webkit-scrollbar { + display: none; +} + +.reel { + height: calc(100vh - 76px); + scroll-snap-align: start; + position: relative; + display: flex; + align-items: flex-end; + overflow: hidden; +} + +.reel-media { + position: absolute; + inset: 0; +} + +.reel-media::after { + position: absolute; + inset: 0; + content: ''; + background: linear-gradient(180deg, transparent 40%, rgba(0, 0, 0, 0.7)); + pointer-events: none; +} + +.reel-content { + position: relative; + z-index: 2; + padding: 30px 20px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: flex-end; +} + +.reel-info { + flex: 1; +} + +.reel-author { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; +} + +.reel-avatar { + width: 38px; + height: 38px; + border-radius: 50%; + display: grid; + place-items: center; + font-size: 0.9rem; + font-weight: 700; + border: 2px solid rgba(255, 255, 255, 0.3); +} + +.reel-handle { + font-weight: 700; + font-size: 0.9rem; +} + +.reel-caption { + font-size: 0.86rem; + line-height: 1.5; + max-width: 300px; + color: rgba(255, 255, 255, 0.9); +} + +.reel-actions { + display: flex; + flex-direction: column; + gap: 22px; + align-items: center; +} + +.reel-action { + display: grid; + gap: 4px; + justify-items: center; + color: white; + background: none; + border: none; + cursor: pointer; + font-size: 0.68rem; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.reel-action:hover { + transform: scale(1.1); +} + +.reel-action-icon { + width: 44px; + height: 44px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.15); + backdrop-filter: blur(8px); + display: grid; + place-items: center; + font-size: 1.2rem; +} + +/* ───────── Explore ───────── */ +.explore-page { + max-width: 900px; + margin: 0 auto; + padding: 30px 20px 80px; +} + +.explore-page h1 { + font-size: 1.8rem; + margin-bottom: 8px; +} + +.explore-subtitle { + color: var(--muted); + font-size: 0.88rem; + margin-bottom: 25px; +} + +.search-bar { + width: 100%; + padding: 14px 18px 14px 44px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--line); + color: var(--text); + font-size: 0.92rem; + font-family: inherit; + outline: none; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); + margin-bottom: 30px; +} + +.search-bar:focus { + border-color: var(--earth); + background: rgba(255, 255, 255, 0.08); +} + +.user-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 16px; +} + +.user-card { + padding: 24px 20px; + border: 1px solid var(--line); + border-radius: 20px; + background: rgba(255, 255, 255, 0.04); + text-align: center; + transition: 0.25s ease; + text-decoration: none; + color: inherit; + display: block; +} + +.user-card:hover { + border-color: rgba(64, 201, 162, 0.3); + background: rgba(255, 255, 255, 0.07); + transform: translateY(-4px); + box-shadow: 0 12px 35px rgba(0, 0, 0, 0.3); +} + +.user-card-avatar { + width: 64px; + height: 64px; + border-radius: 50%; + display: grid; + place-items: center; + font-size: 1.4rem; + font-weight: 700; + margin: 0 auto 14px; + border: 3px solid rgba(255, 255, 255, 0.15); +} + +.user-card-name { + font-weight: 700; + font-size: 0.95rem; + margin-bottom: 3px; +} + +.user-card-handle { + color: var(--earth); + font-size: 0.8rem; + margin-bottom: 6px; +} + +.user-card-location { + color: var(--muted); + font-size: 0.76rem; +} + +.user-card-stats { + display: flex; + justify-content: center; + gap: 20px; + margin-top: 16px; + padding-top: 14px; + border-top: 1px solid var(--line); +} + +.user-card-stat { + text-align: center; +} + +.user-card-stat-value { + font-weight: 700; + font-size: 0.92rem; +} + +.user-card-stat-label { + color: var(--muted); + font-size: 0.68rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* ───────── Profile ───────── */ +.profile-page { + max-width: 680px; + margin: 0 auto; + padding: 30px 20px 80px; +} + +.profile-header { + display: flex; + align-items: center; + gap: 24px; + margin-bottom: 30px; +} + +.profile-avatar { + width: 90px; + height: 90px; + border-radius: 50%; + display: grid; + place-items: center; + font-size: 2rem; + font-weight: 700; + flex-shrink: 0; + border: 3px solid rgba(255, 255, 255, 0.2); +} + +.profile-info h1 { + font-size: 1.5rem; + margin-bottom: 3px; +} + +.profile-handle { + color: var(--earth); + font-size: 0.88rem; + margin-bottom: 4px; +} + +.profile-location { + color: var(--muted); + font-size: 0.82rem; +} + +.profile-stats { + display: flex; + gap: 30px; + padding: 20px 0; + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); + margin-bottom: 30px; +} + +.profile-stat { + text-align: center; +} + +.profile-stat-value { + font-weight: 700; + font-size: 1.1rem; + display: block; +} + +.profile-stat-label { + color: var(--muted); + font-size: 0.76rem; +} + +.profile-posts-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px; +} + +.profile-post-thumb { + aspect-ratio: 1; + border-radius: 8px; + overflow: hidden; + cursor: pointer; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.profile-post-thumb:hover { + opacity: 0.8; +} + +.btn-outline { + padding: 10px 22px; + border: 1px solid var(--line); + border-radius: 12px; + background: rgba(255, 255, 255, 0.05); + color: var(--text); + font-size: 0.82rem; + font-weight: 600; + cursor: pointer; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.btn-outline:hover { + background: rgba(0,0,0,0.08); + border-color: rgba(255, 255, 255, 0.25); +} + +.btn-danger { + padding: 10px 22px; + border: 1px solid rgba(255, 107, 122, 0.3); + border-radius: 12px; + background: rgba(255, 107, 122, 0.1); + color: var(--danger); + font-size: 0.82rem; + font-weight: 600; + cursor: pointer; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.btn-danger:hover { + background: rgba(255, 107, 122, 0.2); +} + +/* ───────── New Chat Search ───────── */ +.new-chat-section { + margin-bottom: 25px; +} + +.search-results { + margin-top: 8px; + border: 1px solid var(--line); + border-radius: 16px; + background: var(--panel-solid); + overflow: hidden; +} + +.search-result-item { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; + border-bottom: 1px solid var(--line); + cursor: pointer; + transition: 0.25s cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.search-result-item:last-child { + border-bottom: none; +} + +.search-result-item:hover { + background: rgba(255, 255, 255, 0.06); +} + +.search-result-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + display: grid; + place-items: center; + font-weight: 700; + font-size: 0.85rem; + flex-shrink: 0; +} + +.search-result-info { + flex: 1; +} + +.search-result-name { + font-weight: 600; + font-size: 0.88rem; +} + +.search-result-handle { + color: var(--muted); + font-size: 0.76rem; +} + +/* ───────── Mobile Tabs active styling fix ───────── */ +.mobile-tab { + text-decoration: none; +} + +/* ───────── Responsive: hide sidebar on chat/reels ───────── */ +@media (max-width: 760px) { + .reels-container { + height: calc(100vh - 65px - 66px); + } + + .chat-room { + height: calc(100vh - 65px); + } +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translate(-50%, -20px); + } + to { + opacity: 1; + transform: translate(-50%, 0); + } +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.08); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.97); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(14px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Safe area insets for iOS devices */ +.mobile-tabs { + padding-bottom: env(safe-area-inset-bottom, 0px); +} + +/* ───────── Chat Grid & Responsive Polish ───────── */ +.chat-layout-grid { + display: grid; + grid-template-columns: 360px 1fr; + gap: 0; +} + +.chat-inbox-preview-desktop { + display: flex; +} + +.chat-room-page-wrapper { + max-width: 880px; + margin: 0 auto; + height: 100dvh; + width: 100%; + display: flex; + flex-direction: column; + padding: 0; + box-sizing: border-box; + overflow: hidden; + background: var(--background); +} + +@media (max-width: 768px) { + .chat-layout-grid { + grid-template-columns: 1fr; + } + + .chat-inbox-preview-desktop { + display: none !important; + } + + html:has(.chat-room-page-wrapper), + body:has(.chat-room-page-wrapper) { + overflow: hidden !important; + height: 100dvh !important; + position: fixed !important; + width: 100vw !important; + overscroll-behavior: none !important; + background: var(--background) !important; + } + + .chat-room-page-wrapper { + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + height: 100dvh !important; + width: 100vw !important; + max-width: 100vw !important; + padding: 0 !important; + margin: 0 !important; + overflow: hidden !important; + z-index: 1000; + background: var(--background) !important; + } + + .chat-room-container { + height: 100% !important; + border-radius: 0 !important; + border-left: none !important; + border-right: none !important; + border-bottom: none !important; + border-top: none !important; + } +} + + diff --git a/src/app/healthz/route.ts b/legacy/multigram/src/app/healthz/route.ts similarity index 100% rename from src/app/healthz/route.ts rename to legacy/multigram/src/app/healthz/route.ts diff --git a/src/app/icon.png b/legacy/multigram/src/app/icon.png similarity index 100% rename from src/app/icon.png rename to legacy/multigram/src/app/icon.png diff --git a/legacy/multigram/src/app/layout.tsx b/legacy/multigram/src/app/layout.tsx new file mode 100644 index 0000000..650a668 --- /dev/null +++ b/legacy/multigram/src/app/layout.tsx @@ -0,0 +1,104 @@ +import type { Metadata, Viewport } from 'next' +import { Montserrat, Cormorant_Garamond } from 'next/font/google' +import './globals.css' +import '@uploadthing/react/styles.css' + +import { getCurrentUser } from './actions' +import Link from 'next/link' +import MobileNav from '../components/MobileNav' +import SplashLoader from '../components/SplashLoader' +import HeaderActions from '../components/HeaderActions' +import AppHeader from '../components/AppHeader' +import OnboardingModal from '../components/OnboardingModal' +import GlobalCallManager from '../components/GlobalCallManager' + +const montserrat = Montserrat({ + subsets: ['latin'], + variable: '--font-body', +}) + +const cormorant = Cormorant_Garamond({ + subsets: ['latin'], + weight: ['400', '600', '700'], + variable: '--font-heading', +}) + +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, + minimumScale: 1, + maximumScale: 1, + userScalable: false, + viewportFit: 'cover', +} + +export const metadata: Metadata = { + title: 'Multigram - Premium Communication', + description: 'Luxurious social connections with Multigram', + icons: { + icon: [ + { url: '/icon.png', sizes: '512x512', type: 'image/png' }, + { url: '/favicon.ico', sizes: 'any' } + ], + shortcut: '/icon.png', + apple: '/icon.png', + }, + manifest: '/manifest.json', +} + +export const dynamic = 'force-dynamic' + +export default async function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + let user: any = null + try { + user = await getCurrentUser() + } catch (err) { + console.error('Failed to get user:', err) + } + + return ( + + + + + +