Skip to content

Commit 91cfab3

Browse files
authored
Merge pull request #30 from Krish-2118/main
Add: sponsors
2 parents f068f93 + 95b312f commit 91cfab3

21 files changed

Lines changed: 430 additions & 346 deletions

‎src/app/api/register/route.ts‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@ import { getSupabaseAdmin } from '@/lib/supabase';
44
import { getAuthenticatedUser } from '@/lib/auth-server';
55
import { paymentProofExists } from '@/lib/payment-proofs';
66
import { registrationsClosedResponse, registrationsOpen } from '@/lib/registration-gate';
7-
import {
8-
isDuplicatePhoneError,
9-
isIterSoaCollege,
10-
isIterSoaEmail,
11-
ITER_SOA_ERROR_MESSAGE,
12-
PHONE_IN_USE_ERROR_MESSAGE,
13-
} from '@/lib/validation';
7+
import { isDuplicatePhoneError, PHONE_IN_USE_ERROR_MESSAGE } from '@/lib/validation';
148
import {
159
classifyStudentType,
1610
createRateLimiter,
@@ -153,14 +147,6 @@ export async function POST(req: NextRequest) {
153147
const input = parsed.data;
154148
const college = studentType === 'internal' ? NIT_ROURKELA_COLLEGE : input.college;
155149

156-
// Exclusion rule: Students from ITER - SOA are not allowed to register
157-
if (isIterSoaCollege(college) || isIterSoaEmail(verifiedEmail)) {
158-
return NextResponse.json(
159-
{ error: ITER_SOA_ERROR_MESSAGE, field: 'college' },
160-
{ status: 403 }
161-
);
162-
}
163-
164150
const adminClient = getSupabaseAdmin(token);
165151

166152
// 3. Prevent duplicate registrations for the same authenticated user

‎src/app/globals.css‎

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,18 @@ body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;o
128128
@media (max-width:719px){.wh-card{gap:12px !important;padding:10px 12px 10px 14px !important}.wh-glyph{display:none !important}.wh-main{font-size:14px !important}.wh-sub{font-size:12px !important}}
129129

130130
/* ---------- sponsors ---------- */
131-
/* Tickers: each strip holds its logos twice and slides by one copy, forever; it pauses while hovered, pressed
132-
or holding keyboard focus, and the edges fade so logos drift in and out of view. */
133-
@keyframes iv-sp{to{transform:translateX(-50%)}}
134-
.sp-row{overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);padding:4px 0}
135-
.sp-strip{display:flex;width:max-content;animation:iv-sp var(--sp-dur,30s) linear infinite}
136-
.sp-strip-rev{animation-direction:reverse}
137-
.sp-row:hover .sp-strip,.sp-row:active .sp-strip,.sp-row:focus-within .sp-strip{animation-play-state:paused}
138-
.sp-card{transition:border-color .3s,background-color .3s}
139-
.sp-card:hover{border-color:rgba(20,19,18,.5) !important;background:rgba(20,19,18,.06) !important}
140-
.sp-logo{width:100%;height:100%;object-fit:contain;filter:grayscale(1);opacity:.78;transition:filter .4s,opacity .4s}
141-
.sp-card:hover .sp-logo,.sp-card:focus-visible .sp-logo{filter:none;opacity:1}
142-
/* Without motion the rows stand still and scroll by hand instead, so every logo stays reachable. */
143-
@media (prefers-reduced-motion:reduce){.sp-row{overflow-x:auto;scrollbar-width:none;-webkit-mask-image:none;mask-image:none}.sp-row::-webkit-scrollbar{display:none}.sp-strip{animation:none}}
144-
@media (max-width:719px){.sp-title{grid-template-columns:minmax(0,1fr) !important}.sp-title-art{display:none}}
131+
/* Past sponsors: a wall of logo cards, five across on wide screens and two on phones. A card lifts while hovered or
132+
holding keyboard focus. */
133+
.ps-grid{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px}
134+
.ps-card{position:relative;aspect-ratio:3 / 2;border:1px solid rgba(20,19,18,.14);transition:border-color .3s,transform .4s cubic-bezier(.25,1,.1,1),box-shadow .4s}
135+
.ps-card:hover,.ps-card:focus-within{border-color:rgba(20,19,18,.5);transform:translateY(-3px);box-shadow:0 14px 26px -18px rgba(20,19,18,.5)}
136+
.ps-in{position:absolute;inset:0;display:grid;place-items:center}
137+
.ps-in:focus-visible{outline:2px solid #141312;outline-offset:3px}
138+
.ps-logo{height:auto;max-height:64%;object-fit:contain}
139+
@media (max-width:959px){.ps-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
140+
@media (max-width:719px){.ps-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}}
141+
@media (max-width:479px){.ps-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
142+
@media (prefers-reduced-motion:reduce){.ps-card:hover,.ps-card:focus-within{transform:none}}
145143

146144
/* ---------- home hero ---------- */
147145
/* --d is the disc's diameter and --cy its centre. Rings, disc, orbiters, copy and astronaut all hang off the two,
@@ -316,6 +314,8 @@ body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;o
316314
.to-planet{left:-8vw;top:calc(43% - var(--s) * 1.14)}
317315
.to-rocket{right:5vw;top:calc(43% - var(--s) * .6 - min(9.5vh,85px));height:min(19vh,170px)}
318316
.to-tagline{top:74%}
317+
/* The closing fade into the mission list stays under the tagline, so the ink text never lands on the dark. */
318+
.to-fade{height:17% !important}
319319
}
320320

321321
/* ---------- worlds slider decoration (WorldsView.tsx) ---------- */
@@ -391,6 +391,53 @@ body::after{content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;o
391391
/* Every other spark and gallery dust mote, and two of the five curtain cloud layers, sit out. */
392392
[data-lowpower] [data-lp-skip]{display:none}
393393

394+
/* ---------- home gallery tunnel (HomeView.tsx) ---------- */
395+
.t-scrim{display:none}
396+
/* Phones: photos are capped by height too (--ar is the photo's width/height; --tx/--ty its offset from the centre), so a tall one can't outgrow the room
397+
between the heading and the counter; the tunnel's centre and vanishing point drop into that room; and dark fades sit
398+
behind the heading and the counter, so a photo passing beneath never covers the text. */
399+
@media (max-width:719px){
400+
.t-space{perspective-origin:50% 56% !important}
401+
.t-frame{width:min(54vw,calc(28vh * var(--ar))) !important;top:calc(56% + var(--ty) * .5) !important}
402+
.t-scrim{display:block;position:absolute;left:0;right:0;pointer-events:none}
403+
.t-scrim-top{top:0;height:calc(4vh + 290px);background:linear-gradient(180deg,#141312 0,rgba(20,19,18,.94) 62%,rgba(20,19,18,0))}
404+
.t-scrim-bot{bottom:0;height:200px;background:linear-gradient(0deg,#141312 0,rgba(20,19,18,.9) 45%,rgba(20,19,18,0))}
405+
}
406+
/* Landscape phones: too short to stack the photos between the heading and the counter, so they fly through the open
407+
right-hand side instead, sized off the height. */
408+
@media (max-height:499px) and (orientation:landscape){
409+
.t-space{perspective-origin:76% 42% !important}
410+
.t-frame{width:calc(38vh * var(--ar)) !important;left:calc(76% + var(--tx) * .3) !important;top:calc(42% + var(--ty) * .3) !important}
411+
}
412+
413+
/* ---------- gallery (GalleryView.tsx) ---------- */
414+
/* Phones: a frame sized off the width alone came out about 90-125px wide in portrait, and ran off the bottom of a
415+
landscape screen. These fill most of the screen once the photo in focus is scaled up by the perspective (1.25x),
416+
with Innovision#galleryTick keeping the spiral tight. Landscape phones size off the height instead. */
417+
@media (max-width:719px){
418+
.g-fr-l{width:min(64vw,340px) !important;height:min(42.2vw,224px) !important}
419+
.g-fr-p{width:min(48vw,240px) !important;height:min(64vw,320px) !important}
420+
}
421+
@media (max-height:499px) and (orientation:landscape){
422+
.g-fr-l{width:70vh !important;height:46vh !important}
423+
.g-fr-p{width:42vh !important;height:56vh !important}
424+
}
425+
426+
/* ---------- touch devices ---------- */
427+
/* Phones and tablets: drop the effects that cost the most per frame while a page scrolls. Scene art's drop shadows go
428+
(each is an extra blur pass over a big layer, every frame it moves): inline filters write theirs as
429+
var(--no-ds, drop-shadow(...)), and opacity(1) is a no-op in its place. Low-power mode drops them everywhere. The film
430+
grain, a full-screen layer blended over the whole page, goes too. The HUD bar and the world switcher, always on screen,
431+
trade the frosted glass (a blur of whatever scrolls beneath, redone every frame) for a near-solid fill. */
432+
[data-lowpower]{--no-ds:opacity(1)}
433+
@media (pointer:coarse){
434+
:root{--no-ds:opacity(1)}
435+
body::after{display:none}
436+
.hud-frost,.wn-pill{-webkit-backdrop-filter:none !important;backdrop-filter:none !important}
437+
.hud-frost{background:rgba(236,232,223,.97) !important}
438+
.wn-pill{background:rgba(236,232,223,.96) !important}
439+
}
440+
394441
/* ---------- schedule ---------- */
395442
/* --gut lines the rows up with the 1240px column while letting each carousel run to the screen edge. */
396443
.sc-hero,.sc-body{--gut:max(clamp(16px,4vw,64px),(100% - 1240px) / 2)}

‎src/components/coming-soon/ComingSoon.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import styles from './ComingSoon.module.css';
55

66
type ComingSoonProps = {
77
title: ReactNode;
8-
/** Second call to action, next to BACK TO INNOVISION. */
9-
secondary: { label: string; href: string };
8+
/** Optional second call to action, next to BACK TO INNOVISION. */
9+
secondary?: { label: string; href: string };
1010
};
1111

1212
/**
1313
* Shared full-page teaser for flows that are built but not yet live (see src/config/launch.ts): a centred
14-
* headline and two buttons. Used by RegistrationsOpeningSoon (/register) and MerchDroppingSoon (/merch).
14+
* headline, a BACK TO INNOVISION button and an optional second one. Used by RegistrationsOpeningSoon (/register) and MerchDroppingSoon (/merch).
1515
*/
1616
export default function ComingSoon({ title, secondary }: ComingSoonProps) {
1717
return (
@@ -20,7 +20,7 @@ export default function ComingSoon({ title, secondary }: ComingSoonProps) {
2020
<h1 className={styles.title}>{title}</h1>
2121
<div className={styles.actions}>
2222
<Link href="/" className={`${styles.cta} ${styles.ctaPrimary}`}>BACK TO INNOVISION</Link>
23-
<Link href={secondary.href} className={`${styles.cta} ${styles.ctaGhost}`}>{secondary.label}</Link>
23+
{secondary && <Link href={secondary.href} className={`${styles.cta} ${styles.ctaGhost}`}>{secondary.label}</Link>}
2424
</div>
2525
</main>
2626
);

‎src/components/coming-soon/MerchDroppingSoon.tsx‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import ComingSoon from './ComingSoon';
33
/** /merch while MERCH_STORE_OPEN is false (src/config/launch.ts). */
44
export default function MerchDroppingSoon() {
55
return (
6-
<ComingSoon
7-
title={<>Exclusive merch<br /><em>dropping soon.</em></>}
8-
secondary={{ label: 'VIEW THE SCHEDULE', href: '/#/schedule' }}
9-
/>
6+
<ComingSoon title={<>Exclusive merch<br /><em>dropping soon.</em></>} />
107
);
118
}

‎src/components/innovision/AuthOverlay.tsx‎

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
import React, { useState } from 'react';
66
import type { V } from './types';
77
import { Logo } from './icons';
8-
import { isIterSoaCollege, isIterSoaEmail } from '@/lib/validation';
98

109
export default function AuthOverlay({ v }: { v: V }) {
1110
const [userCollege, setUserCollege] = useState<string | null>(null);
1211
const collegeValue = userCollege !== null ? userCollege : (v.regVals?.college || '');
13-
const isBlockedCollege = !v.isInternal && (isIterSoaCollege(collegeValue) || isIterSoaEmail(v.regVals?.email));
1412
return (
1513
<div data-auth-root="" aria-hidden={v.authHidden} style={{ position: "fixed", inset: "0", zIndex: "62", visibility: "hidden", pointerEvents: "none" }}>
1614
<div data-rift-veil="" style={{ position: "absolute", inset: "0", background: "#070605", opacity: "0" }}></div>
@@ -105,14 +103,7 @@ export default function AuthOverlay({ v }: { v: V }) {
105103
{v.err.gender ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.gender}</span>) : null}
106104
</fieldset>
107105
<label data-s-in="" style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
108-
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
109-
<span style={{ fontSize: "12px", fontWeight: "700", letterSpacing: ".16em", color: "rgba(236,232,223,.86)" }}>COLLEGE NAME</span>
110-
{isBlockedCollege && (
111-
<span style={{ fontSize: "11px", fontWeight: "700", color: "#f87171", letterSpacing: ".06em" }}>
112-
NOT ELIGIBLE
113-
</span>
114-
)}
115-
</div>
106+
<span style={{ fontSize: "12px", fontWeight: "700", letterSpacing: ".16em", color: "rgba(236,232,223,.86)" }}>COLLEGE NAME</span>
116107
<input
117108
name="college"
118109
value={v.isInternal ? v.regVals.college : collegeValue}
@@ -126,48 +117,23 @@ export default function AuthOverlay({ v }: { v: V }) {
126117
readOnly={v.isInternal}
127118
autoComplete="organization"
128119
placeholder="Full name of your institute"
129-
aria-invalid={isBlockedCollege || !!v.err.college}
120+
aria-invalid={!!v.err.college}
130121
style={{
131122
height: "54px",
132123
padding: "0 16px",
133124
borderRadius: "0",
134-
background: isBlockedCollege ? "rgba(239, 68, 68, 0.08)" : v.isInternal ? "rgba(236,232,223,.08)" : "rgba(236,232,223,.04)",
125+
background: v.isInternal ? "rgba(236,232,223,.08)" : "rgba(236,232,223,.04)",
135126
fontSize: "16px",
136127
color: "#ECE8DF",
137128
outline: "none",
138129
transition: "border-color .3s,background-color .3s",
139-
border: isBlockedCollege ? "1.5px solid #ef4444" : `1.5px solid ${v.bc.college}`,
130+
border: `1.5px solid ${v.bc.college}`,
140131
cursor: v.isInternal ? "not-allowed" : "text",
141132
}}
142133
style-focus="border-color:oklch(0.8 0.12 85);background:rgba(236,232,223,.08)"
143134
/>
144135
{v.isInternal && <span style={{ fontSize: "12px", color: "oklch(0.8 0.12 85)" }}>Verified NIT Rourkela student.</span>}
145-
{!v.isInternal && !isBlockedCollege && (
146-
<span style={{ fontSize: "12px", color: "rgba(236,232,223,0.5)" }}>
147-
Note: Students from ITER - SOA are not eligible to register.
148-
</span>
149-
)}
150-
{isBlockedCollege && (
151-
<div
152-
role="alert"
153-
style={{
154-
display: "flex",
155-
alignItems: "center",
156-
gap: "8px",
157-
padding: "10px 14px",
158-
background: "rgba(239, 68, 68, 0.14)",
159-
border: "1px solid #ef4444",
160-
borderRadius: "4px",
161-
color: "#fca5a5",
162-
fontSize: "13px",
163-
fontWeight: 600,
164-
lineHeight: "1.4",
165-
}}
166-
>
167-
<span>Registration is not allowed for students from ITER - SOA.</span>
168-
</div>
169-
)}
170-
{v.err.college && !isBlockedCollege ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.college}</span>) : null}
136+
{v.err.college ? (<span role="alert" style={{ fontSize: "14px", lineHeight: "1.4", color: "oklch(0.76 0.14 35)" }}>{v.err.college}</span>) : null}
171137
</label>
172138
<label data-s-in="" style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
173139
<span style={{ fontSize: "12px", fontWeight: "700", letterSpacing: ".16em", color: "rgba(236,232,223,.86)" }}>EMAIL</span>
@@ -326,7 +292,7 @@ export default function AuthOverlay({ v }: { v: V }) {
326292
) : null}
327293
<button
328294
type="submit"
329-
disabled={v.busy || (v.d.s0 !== 'none' && isBlockedCollege)}
295+
disabled={v.busy}
330296
onMouseEnter={v.beep}
331297
style={{
332298
flex: "1",
@@ -337,30 +303,24 @@ export default function AuthOverlay({ v }: { v: V }) {
337303
minHeight: "58px",
338304
padding: "0 28px",
339305
border: "0",
340-
cursor: (v.d.s0 !== 'none' && isBlockedCollege) ? "not-allowed" : "pointer",
306+
cursor: "pointer",
341307
fontWeight: "700",
342308
fontSize: "15px",
343309
letterSpacing: ".08em",
344310
whiteSpace: "nowrap",
345-
color: (v.d.s0 !== 'none' && isBlockedCollege) ? "#fca5a5" : "#141312",
346-
background: (v.d.s0 !== 'none' && isBlockedCollege) ? "rgba(239, 68, 68, 0.25)" : "#ECE8DF",
347-
opacity: (v.d.s0 !== 'none' && isBlockedCollege) ? 0.7 : v.busyO,
311+
color: "#141312",
312+
background: "#ECE8DF",
313+
opacity: v.busyO,
348314
clipPath: "polygon(12px 0,100% 0,100% calc(100% - 12px),calc(100% - 12px) 100%,0 100%,0 12px)",
349315
transition: "background-color .4s,opacity .3s,color .3s",
350316
}}
351-
style-hover={(v.d.s0 !== 'none' && isBlockedCollege) ? "" : "background:oklch(0.8 0.12 85)"}
317+
style-hover="background:oklch(0.8 0.12 85)"
352318
style-active="transform:scale(.98)"
353319
>
354-
<span>
355-
{v.d.s0 !== 'none' && isBlockedCollege
356-
? 'NOT ELIGIBLE (ITER - SOA)'
357-
: v.submitLbl}
358-
</span>
359-
{!(v.d.s0 !== 'none' && isBlockedCollege) && (
360-
<svg width="16" height="10" viewBox="0 0 16 10" aria-hidden="true">
361-
<path d="M11 1l4 4-4 4M15 5H0" fill="none" stroke="currentColor" strokeWidth="1.5"></path>
362-
</svg>
363-
)}
320+
<span>{v.submitLbl}</span>
321+
<svg width="16" height="10" viewBox="0 0 16 10" aria-hidden="true">
322+
<path d="M11 1l4 4-4 4M15 5H0" fill="none" stroke="currentColor" strokeWidth="1.5"></path>
323+
</svg>
364324
</button>
365325
</div>
366326
</form>

0 commit comments

Comments
 (0)