diff --git a/README.md b/README.md index e215bc4..1416f19 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,318 @@ -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). +# Innovision 2026 · The Celestial Odyssey -## Getting Started +The official website of **Innovision 2026**, the technical fest of **NIT Rourkela** (13–15 November 2026). -First, run the development server: +The site takes visitors through four "worlds" of events, with a scroll-driven home page, an event explorer, a 3D +photo gallery, a schedule, a merch store and Google sign-in for fest registration. Staff manage it from an admin +dashboard built into the site. + +Built by **OpenCode NIT Rourkela**. + +--- + +## Contents + +- [Tech stack](#tech-stack) +- [Getting started](#getting-started) +- [Environment variables](#environment-variables) +- [Database setup](#database-setup) +- [Project structure](#project-structure) +- [How the site works](#how-the-site-works) +- [Common tasks](#common-tasks) +- [Admin dashboard and roles](#admin-dashboard-and-roles) +- [API routes](#api-routes) +- [Security](#security) +- [Scripts, CI and deployment](#scripts-ci-and-deployment) +- [Contributing](#contributing) +- [Contact](#contact) + +--- + +## Tech stack + +| Area | What we use | +| ------------- | ------------------------------------------------------------------------------------------- | +| Framework | [Next.js 16](https://nextjs.org) (App Router, Turbopack) · React 19 · TypeScript | +| Animation | [GSAP 3](https://gsap.com) with ScrollTrigger and ScrambleText | +| Auth and data | [Supabase](https://supabase.com): Google OAuth, Postgres with Row Level Security, Storage | +| Images | [ImageKit](https://imagekit.io): event posters and gallery photos, resized on the CDN | +| Hosting | [Vercel](https://vercel.com) | +| Tooling | ESLint (`eslint-config-next`), Husky and lint-staged pre-commit hook, GitHub Actions CI | + +> **Heads-up for contributors:** Next.js 16 has breaking changes compared to older versions. For example, middleware +> is now `src/proxy.ts`. Before relying on an API you remember, read the guides bundled in `node_modules/next/dist/docs/` +> (see [`AGENTS.md`](AGENTS.md)). + +--- + +## Getting started + +**Requirements:** Node.js 22 (the version CI uses) and npm. + +```bash +# 1. Clone and install +git clone https://github.com/OpencodeNIT-R/project-spacex.git +cd project-spacex +npm install # also installs the Husky pre-commit hook + +# 2. Configure the environment +cp .env.example .env # then fill in the values (see below) + +# 3. Run the dev server +npm run dev # http://localhost:3000 +``` + +The site renders without Supabase or ImageKit keys, but sign-in, registration, the admin dashboard, and events or +gallery photos from the database won't work until they are set. + +--- + +## Environment variables + +[`.env.example`](.env.example) is the documented template: copy it to `.env` (or `.env.local`). Real env files are +git-ignored, so never commit secrets. Anything prefixed `NEXT_PUBLIC_` is bundled into the browser and is public. + +| Variable | Required | Purpose | +| -------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------- | +| `IMAGEKIT_URL_ENDPOINT` | yes | `https://ik.imagekit.io/`. Also allowed in the Content-Security-Policy `img-src`. | +| `IMAGEKIT_PUBLIC_KEY` | yes | Server-side ImageKit SDK. | +| `IMAGEKIT_PRIVATE_KEY` | yes (secret) | Uploads and deletes posters and gallery images. Server only. | +| `NEXT_PUBLIC_SUPABASE_URL` | yes | Supabase project URL. | +| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | yes | Publishable (anon) key. Safe in the browser because RLS is enforced. | +| `SUPABASE_SERVICE_ROLE_KEY` | prod (secret) | Backend API routes. Bypasses RLS and signs staff links to payment proofs. Server only. | +| `NEXT_PUBLIC_SITE_URL` | optional | Canonical origin for SEO, sitemap and the sign-in return URL. Defaults to the Vercel URL. | +| `GOOGLE_SITE_VERIFICATION` | optional | Google Search Console verification token. | +| `DATABASE_URL` | local tools only | Direct Postgres connection for running SQL from your machine. Never add it to Vercel. | + +--- + +## Database setup + +The whole schema (tables, Row Level Security policies, triggers, helper functions and the private storage bucket) +is in [`supabase/schema.sql`](supabase/schema.sql). It is idempotent, so it is safe to run again after updates. ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +psql "$DATABASE_URL" -f supabase/schema.sql +# or paste it into Supabase Dashboard → SQL Editor ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +| Table | Holds | +| --------------- | --------------------------------------------------------------------------------------------- | +| `profiles` | One row per signed-in user (created by a trigger on Google sign-up) and their `role`. | +| `registrations` | Fest registrations with payment status. Created only through `/api/register`. | +| `events` | Events added from the admin dashboard, shown in the event worlds. | +| `gallery` | Photos uploaded from the admin dashboard. | +| `user_sessions` | Server-side session records. | + +Payment screenshots go to the **private** Supabase Storage bucket `payment-proofs`. Staff see them only through +short-lived signed links. + +Also configure, in the Supabase dashboard: + +- **Authentication → Providers → Google**: enable Google sign-in. +- **Authentication → URL Configuration**: add `http://localhost:3000` and the production domain. + +Scripts in `supabase/manual/` are one-off migrations. They are kept out of any automatic migration path on purpose, +so read each file's header before running it. + +--- + +## Project structure + +```text +src/ +├── app/ # Next.js App Router +│ ├── page.tsx # The whole interactive site (renders ) +│ ├── events/ # Server-rendered event pages, one per world (for SEO and shareable links) +│ ├── register/ merch/ # "Opening soon" pages while those flows are switched off +│ ├── api/ # Route handlers (see "API routes") +│ ├── layout.tsx # Fonts, metadata, JSON-LD +│ ├── sitemap.ts robots.ts # Generated from src/config/site.ts +│ └── opengraph-image.tsx … # Generated social share cards +├── components/ +│ ├── innovision/ # The main experience +│ │ ├── Innovision.tsx # The app shell: hash router, GSAP engine, auth, data fetching +│ │ ├── data.ts # Content: worlds, schedule, products, sponsors, gallery, contacts +│ │ ├── gallery.json # Static gallery photo list (ImageKit URLs) +│ │ ├── HomeView.tsx # Landing page +│ │ ├── WorldsView.tsx DetailView.tsx # Event worlds and their detail scenes +│ │ ├── GalleryView.tsx # 3D "fly through" gallery +│ │ ├── ScheduleView.tsx MerchView.tsx +│ │ ├── SiteFooter.tsx Hud.tsx MenuOverlay.tsx … +│ │ ├── AdminDashboard.tsx admin/ # Staff dashboard +│ │ └── AuthOverlay.tsx ProfileOverlay.tsx +│ ├── coming-soon/ # Registration and merch teaser pages +│ └── events/ # Chrome for the /events pages +├── config/ +│ ├── launch.ts # Launch switches (registrations, merch store) +│ └── site.ts # Canonical URL and site name +├── lib/ # Server and shared helpers: supabase, imagekit, auth, validation, security, seo +└── proxy.ts # Request proxy (Next 16's middleware): CSRF, rate limits, auth gate +public/assets/ # Illustrations, planets, sounds (WebP and SVG) +supabase/ # schema.sql and manual migrations +``` + +--- + +## How the site works + +The home page is a single-page experience. [`Innovision.tsx`](src/components/innovision/Innovision.tsx) owns the state +and a small **hash router**, and swaps between views with a curtain transition: + +| Route | View | +| ---------------------- | ------------------------------------- | +| `#/` | Home | +| `#/worlds/` | Event worlds slider | +| `#/world/` | A world's detail scene and its events | +| `#/gallery` | 3D gallery | +| `#/schedule` | Schedule | +| `#/merch` | Merch store (when open) | + +The four worlds and their slugs are `flagship-events`, `standout-events`, `main-events` and `dts-and-fun-events`. +They also have real server-rendered pages at `/events/`, which search engines index and people can share. + +Views load lazily on first visit. Heavy animation loops pause while a view is hidden, and low-powered devices +(or *prefers-reduced-motion*) get a lighter mode. + +--- + +## Common tasks + +### Open registrations or the merch store + +Flip the switches in [`src/config/launch.ts`](src/config/launch.ts): + +```ts +export const REGISTRATIONS_OPEN = true; +export const MERCH_STORE_OPEN = true; +``` + +While a switch is `false`, its buttons and links lead to a "coming soon" page, and the real flow stays untouched. + +### Add gallery photos + +Gallery photos are a **static list** in +[`src/components/innovision/gallery.json`](src/components/innovision/gallery.json), so the home page needs no +database query to show them. + +1. Convert the photo to WebP (around 2000px on the long side and quality 80 is a good default). +2. Upload it to ImageKit in the `/innovision/gallery/archive` folder. +3. Add an entry to `gallery.json`: + + ```json + { "id": "innovision-27", "title": "Robo race", "url": "https://ik.imagekit.io//innovision/gallery/archive/innovision-27.webp", "width": 2000, "height": 1333 } + ``` + +The **first six** entries fly through the home page's gallery section. The gallery page (`#/gallery`) shows all of +them. Pages request resized copies from ImageKit (`?tr=w-…`), and ImageKit picks WebP or AVIF for each browser. + +Photos uploaded from the admin dashboard's Gallery tab are stored in the database and appear on the gallery page +after the static list. + +### Edit content + +Most copy and content lives in [`src/components/innovision/data.ts`](src/components/innovision/data.ts): the worlds, +schedule, products, sponsors (`TITLE_SPONSOR`, `SPONSOR_TIERS`), navigation links and the footer's `CONTACTS`. +Events added in the admin dashboard are merged into the worlds at runtime. + +### Change the domain + +Set `NEXT_PUBLIC_SITE_URL` in Vercel, add the domain under Supabase → Authentication → URL Configuration, then +redeploy. Canonical URLs, Open Graph tags, `robots.txt`, `sitemap.xml` and JSON-LD all follow it. + +--- + +## Admin dashboard and roles + +Staff open the dashboard from their profile on the site. Access depends on `profiles.role`: + +| Role | Can do | +| ------------------- | ------------------------------------------------------------------------ | +| `user` | Register for the fest and view their pass. | +| `registration-team` | Review registrations and payment proofs, approve or reject them. | +| `it-team` | Manage events and gallery photos. | +| `admin` | Everything above, plus managing users' roles. | + +Admins can switch users between `user`, `it-team` and `registration-team`. The `admin` role itself can only be granted +directly in the database: + +```sql +update public.profiles set role = 'admin' where email = 'someone@example.com'; +``` + +--- + +## API routes + +| Route | Methods | Access | +| ------------------------------------------- | -------------------------- | ------------------------------ | +| `/api/events` | GET | Public (cached) | +| `/api/gallery` | GET | Public (cached) | +| `/api/auth/session` | GET, POST | Session cookie handling | +| `/api/auth/profile` | POST, PATCH | Signed-in user | +| `/api/auth/logout` | POST | Signed-in user | +| `/api/register` | GET, POST | Signed-in user | +| `/api/upload` | POST | Signed-in user (payment proof) | +| `/api/admin/registrations` | GET, PATCH | Admin, registration team | +| `/api/admin/registrations/payment-proof` | GET | Admin, registration team | +| `/api/admin/events` | GET, POST, PATCH, DELETE | Admin, IT team | +| `/api/admin/gallery` | GET, POST, PATCH, DELETE | Admin, IT team | +| `/api/admin/users` | GET, PATCH | Admin | + +--- + +## Security + +- **Row Level Security** on every table. Privileged columns (`role`, `email`, and others) are protected by triggers, + and registrations can only be created by the server. +- **[`src/proxy.ts`](src/proxy.ts)** rejects cross-site writes to `/api` (CSRF), caps request body size, and rate-limits + writes and auth endpoints per IP. +- **Every route handler** checks the Supabase session and the caller's role itself. The proxy's auth gate is only an + early filter. +- **Strict security headers** and a Content-Security-Policy are set in [`next.config.ts`](next.config.ts). +- **Secrets stay on the server.** Never put a secret in a `NEXT_PUBLIC_*` variable. + +--- + +## Scripts, CI and deployment + +```bash +npm run dev # development server (Turbopack) +npm run build # production build +npm run start # serve the production build +npm run lint # ESLint +``` + +- **Pre-commit:** Husky runs `eslint --fix` on staged JS/TS files. +- **CI** ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)): on pushes and PRs to `main`, it runs `npm ci`, + `npm run lint` and `npm run build` on Node 22. +- **Deployment:** Vercel builds production from the `development` branch of + [OpencodeNIT-R/project-spacex](https://github.com/OpencodeNIT-R/project-spacex). Set the environment variables above in + the Vercel project settings. + +--- -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +## Contributing -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. +1. Fork the repo and create a branch from `development`. +2. Keep to the existing code style: content in `data.ts`, views as plain components that render from the `v` values + computed in `Innovision.tsx`, and animation in the GSAP engine. +3. Run `npm run lint` and `npm run build` before opening a PR. +4. Open a pull request against `development` on + [OpencodeNIT-R/project-spacex](https://github.com/OpencodeNIT-R/project-spacex). -## Learn More +--- -To learn more about Next.js, take a look at the following resources: +## Contact -- [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. +For queries about the fest: -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +- **Rishav Bansal**: [+91 79788 88164](tel:+917978888164) +- **Manjeet Srichandan**: [+91 77355 10636](tel:+917735510636) -## Deploy on Vercel +Follow Innovision on [Instagram](https://www.instagram.com/inno.nitr_official) and +[LinkedIn](https://www.linkedin.com/company/innovision-nitrkl/). -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. +--- -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +

Crafted with ♥ by OpenCode NIT Rourkela

diff --git a/src/app/globals.css b/src/app/globals.css index 280cd01..4fdca86 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -178,9 +178,11 @@ body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;o .hero-cta:active{transform:translateY(1px)} /* Stacked screens (portrait, or narrower than 720px): the kicker, title and subtitle fit inside the disc, the buttons sit on the paper below it, the astronaut drifts off the disc's upper right and the storm planet tucks into the - bottom-left corner. The disc + buttons group is centred between the top HUD bar (72px) and a 24px bottom margin. */ + bottom-left corner. The fest dates head the column, centred just above the disc: in the top-right corner they met + the astronaut whenever the screen was short for its width. The dates band (--dh) + disc + buttons group is centred + between the top HUD bar (72px) and a 24px bottom margin. */ @media (max-aspect-ratio:1/1),(max-width:719px) and (min-height:520px){ - .hero{--cta-h:48px;--ctas:var(--cta-h);--d:min(80vw,100vh - 220px,640px);--cy:calc(50% + 12px - var(--ctas) / 2)} + .hero{--cta-h:48px;--ctas:var(--cta-h);--dh:102px;--d:min(80vw,100vh - 220px - var(--dh),640px);--cy:calc(50% + 12px - var(--ctas) / 2 + var(--dh) / 2)} .hero-copy{top:calc(var(--cy) - var(--d) / 2);bottom:auto;justify-content:flex-start;padding:0 16px} .hero-copy-disc{display:flex;flex-direction:column;align-items:center;justify-content:center;flex:none;height:var(--d)} /* .hero-copy starts at the disc's top edge here. */ @@ -198,19 +200,22 @@ body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;o .hero-cta-secondary.hv-gold:hover{color:#8a6a2a !important} .hero-astro{top:calc(var(--cy) - var(--d) * .56);bottom:auto;right:max(4px,50% - var(--d) * .62);width:calc(var(--d) * .36);height:calc(var(--d) * .36)} .hero-planet{--p:min(60vw,38vh);left:calc(var(--p) * -.32);bottom:calc(var(--p) * -.62);width:var(--p);height:var(--p)} - .hero-date{top:90px;right:clamp(18px,5vw,36px);min-width:0;padding:10px 0 8px 12px} + /* Bottom edge 12px above the disc's top, centred over it; --dh leaves room for it under the HUD. */ + .hero-date{top:auto;bottom:calc(100% - var(--cy) + var(--d) / 2 + 12px);right:auto;left:50%;translate:-50% 0;align-items:center;min-width:0;padding:10px 14px 8px;text-align:center} .hero-date-range{font-size:34px} .hero-coords{display:none} } /* Phones: the buttons stack full width, so the disc gives up the extra height. */ @media (max-width:459px) and (min-height:520px){ - .hero{--cta-h:46px;--ctas:calc(var(--cta-h) * 2 + 10px);--d:min(86vw,100vh - 270px)} + .hero{--cta-h:46px;--ctas:calc(var(--cta-h) * 2 + 10px);--dh:90px;--d:min(86vw,100vh - 270px - var(--dh))} + /* The centred dates reach over the astronaut's side of the disc here, so it starts just inside the disc's top edge: + even at the top of its 22px bob it stays clear of the dates (whose bottom is 12px above the disc). */ + .hero-astro{top:calc(var(--cy) - var(--d) / 2 + 12px)} .hero-ctas{flex-direction:column;align-items:stretch;width:min(100%,300px)} .hero-cta{width:100%} .hero-sub-rule{display:none} } @media (max-width:459px){ - .hero-date{top:82px;right:18px} .hero-date-label,.hero-date-foot{font-size:8px;letter-spacing:.16em} .hero-date-range{font-size:29px;gap:7px} .hero-date-dash{width:15px} @@ -297,6 +302,22 @@ body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;o .td-tagline{top:44%} } +/* ---------- Flagship Events (takeoff) scene (DetailView.tsx) ---------- */ +/* Wide: the ringed planet centred behind the title, the rocket off to its right. */ +.to-planet{--s:max(47vw,60vh);position:absolute;left:calc(50% - var(--s) / 2);top:calc(77% - var(--s) * 1.329);width:var(--s);pointer-events:none} +.to-rocket{position:absolute;right:max(9%,40px);top:17%;height:min(30vh,290px);pointer-events:none} +/* Portrait phones and tablets (the compact layout, with the tagline over the scene): one column has no room for the + wide layout, where the planet overflows the screen and the rocket sits on its rings over the title. The planet + shrinks to the upper left, the rocket gets the open sky on the right, and the tagline takes the open floor under the + lander instead of the middle of the screen, where the lander ends its climb (see Innovision#setupDetailScroll). */ +@media (orientation:portrait) and (max-width:1099px){ + /* --s: the planet's size. Its art (110% wide, 4% down) ends 1.14 --s below its top: just above the title at 43%. */ + .to-planet,.to-rocket{--s:min(70vw,40vh)} + .to-planet{left:-8vw;top:calc(43% - var(--s) * 1.14)} + .to-rocket{right:5vw;top:calc(43% - var(--s) * .6 - min(9.5vh,85px));height:min(19vh,170px)} + .to-tagline{top:74%} +} + /* ---------- worlds slider decoration (WorldsView.tsx) ---------- */ @media (max-width:719px){.wv-deco-wide{display:none}} diff --git a/src/components/innovision/DetailView.tsx b/src/components/innovision/DetailView.tsx index 4f6bc30..f6b06e0 100644 --- a/src/components/innovision/DetailView.tsx +++ b/src/components/innovision/DetailView.tsx @@ -229,12 +229,12 @@ export default function DetailView({ v }: { v: V }) {
-
+
-