Skip to content

Commit e520af1

Browse files
committed
Refine landing proof sections
1 parent 4be4cde commit e520af1

2 files changed

Lines changed: 187 additions & 53 deletions

File tree

src/app/globals.css

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -871,14 +871,34 @@ select option {
871871
--font-editorial: var(--font-newsreader), ui-serif, Georgia;
872872
--font-ui: var(--font-hanken), system-ui, sans-serif;
873873
--font-code: var(--font-dm-mono), ui-monospace, monospace;
874+
background:
875+
linear-gradient(120deg, rgba(16, 20, 25, 0.94), rgba(21, 95, 71, 0.92)),
876+
var(--tray-ink);
874877
}
875878
.tl-built-with {
876879
--font-editorial: var(--font-unbounded), system-ui, sans-serif;
877880
--font-ui: var(--font-jakarta), system-ui, sans-serif;
878881
--font-code: var(--font-jetbrains), ui-monospace, monospace;
879882
background:
880-
radial-gradient(circle at 12% 20%, rgba(193, 72, 34, 0.08), transparent 26rem),
881-
linear-gradient(180deg, var(--tray-cream) 0%, var(--tray-bg) 100%);
883+
linear-gradient(180deg, rgba(251, 252, 248, 0.78) 0%, rgba(238, 242, 237, 0.96) 100%),
884+
repeating-linear-gradient(
885+
90deg,
886+
rgba(16, 20, 25, 0.025) 0,
887+
rgba(16, 20, 25, 0.025) 1px,
888+
transparent 1px,
889+
transparent 80px
890+
),
891+
var(--tray-bg);
892+
}
893+
@media (hover: none) {
894+
.tl-built-with article > div {
895+
transform: translateY(0);
896+
opacity: 1;
897+
filter: blur(0);
898+
}
899+
.tl-built-with article > span[aria-hidden] {
900+
opacity: 0;
901+
}
882902
}
883903

884904
.tl-closing {

src/components/landing/sections/BuiltWithSection.tsx

Lines changed: 165 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
"use client";
22

3-
import { useRef } from "react";
3+
import { useRef, useState } from "react";
44
import {
55
motion,
66
useReducedMotion,
77
useScroll,
88
useTransform,
9-
type MotionValue,
109
type Variants,
1110
} from "framer-motion";
1211
import { SectionReveal, tm } from "@/lib/motion/tray-framer";
@@ -29,6 +28,12 @@ const stack = [
2928
["Realtime queues", "live / websocket"],
3029
] as const;
3130

31+
const handoffRows = [
32+
["A-217", "Masala dosa", "Ready", "OTP 4821"],
33+
["B-044", "Veg thali", "Prep 04m", "Paid"],
34+
["C-103", "Filter coffee", "Called", "Counter 2"],
35+
] as const;
36+
3237
const lineReveal: Variants = {
3338
hidden: { y: "112%", opacity: 0, filter: "blur(8px)" },
3439
show: {
@@ -54,7 +59,7 @@ function KitchenProofSection() {
5459
return (
5560
<SectionReveal
5661
as="section"
57-
className="tl-kitchen-proof relative overflow-hidden bg-[var(--tray-ink)] px-5 py-[clamp(5rem,9vw,8.5rem)] text-[var(--tray-cream)] sm:px-8 lg:px-10"
62+
className="tl-kitchen-proof relative overflow-hidden px-5 py-[clamp(3rem,4.8vw,4.4rem)] text-[var(--tray-cream)] sm:px-8 lg:px-10"
5863
amount={0.35}
5964
>
6065
<motion.div
@@ -74,55 +79,140 @@ function KitchenProofSection() {
7479
backgroundSize: "96px 96px",
7580
}}
7681
/>
77-
<div className="relative mx-auto max-w-4xl">
78-
<motion.p
79-
variants={lineReveal}
80-
className="font-code text-[0.64rem] uppercase tracking-[0.28em] text-[rgba(250,248,243,0.42)]"
81-
>
82-
From the kitchen
83-
</motion.p>
82+
<div className="relative mx-auto grid max-w-6xl items-center gap-10 lg:grid-cols-[minmax(0,0.95fr)_minmax(21rem,0.7fr)]">
83+
<div>
84+
<motion.p
85+
variants={lineReveal}
86+
className="font-code text-[0.64rem] uppercase tracking-[0.28em] text-[rgba(250,248,243,0.42)]"
87+
>
88+
From the kitchen
89+
</motion.p>
8490

85-
<motion.blockquote
86-
className="mt-7 font-editorial text-[clamp(3rem,9vw,6.8rem)] font-black italic leading-[0.92] tracking-[-0.035em]"
87-
variants={{ hidden: {}, show: { transition: { staggerChildren: 0.09 } } }}
88-
>
89-
{quoteLines.map((line) => (
90-
<span key={line} className="block overflow-hidden pb-[0.09em]">
91+
<motion.blockquote
92+
className="mt-5 max-w-[14.5ch] font-editorial text-[clamp(2.25rem,5.2vw,4.25rem)] font-black italic leading-[0.96] tracking-[-0.02em]"
93+
variants={{ hidden: {}, show: { transition: { staggerChildren: 0.09 } } }}
94+
>
95+
{quoteLines.map((line) => (
96+
<span key={line} className="block overflow-hidden pb-[0.09em]">
97+
<motion.span variants={lineReveal} className="block">
98+
{line}
99+
</motion.span>
100+
</span>
101+
))}
102+
<span className="block overflow-hidden pb-[0.09em]">
91103
<motion.span variants={lineReveal} className="block">
92-
{line}
104+
<span className="text-[#ff3a22]">Lunch</span> ends on time.
93105
</motion.span>
94106
</span>
95-
))}
96-
<span className="block overflow-hidden pb-[0.09em]">
97-
<motion.span variants={lineReveal} className="block">
98-
<span className="text-[#ff1f12]">Lunch</span> ends on time.
99-
</motion.span>
100-
</span>
101-
</motion.blockquote>
107+
</motion.blockquote>
102108

103-
<motion.p
104-
variants={lineReveal}
105-
className="mt-5 font-code text-[0.58rem] uppercase tracking-[0.2em] text-[rgba(250,248,243,0.35)]"
106-
>
107-
Kitchen supervisor / campus canteen
108-
</motion.p>
109+
<motion.p
110+
variants={lineReveal}
111+
className="mt-5 font-code text-[0.58rem] uppercase tracking-[0.2em] text-[rgba(250,248,243,0.35)]"
112+
>
113+
Kitchen supervisor / campus canteen
114+
</motion.p>
115+
</div>
116+
117+
<KitchenHandoffBoard reduce={reduce} />
109118
</div>
110119
</SectionReveal>
111120
);
112121
}
113122

123+
function KitchenHandoffBoard({ reduce }: { reduce: boolean | null }) {
124+
return (
125+
<motion.aside
126+
variants={{
127+
hidden: { opacity: 0, x: 28, filter: "blur(8px)" },
128+
show: {
129+
opacity: 1,
130+
x: 0,
131+
filter: "blur(0px)",
132+
transition: { duration: 0.68, ease: tm.easeSharp, delay: 0.2 },
133+
},
134+
}}
135+
className="relative overflow-hidden rounded-[var(--tray-radius-md)] border border-[rgba(251,252,248,0.16)] bg-[rgba(251,252,248,0.055)] p-4 shadow-[0_26px_80px_rgba(0,0,0,0.22)] backdrop-blur"
136+
>
137+
{!reduce && (
138+
<motion.span
139+
aria-hidden
140+
className="absolute left-0 top-0 h-px w-full bg-[linear-gradient(90deg,transparent,rgba(255,58,34,0.95),transparent)]"
141+
animate={{ x: ["-100%", "100%"] }}
142+
transition={{ duration: 2.6, repeat: Infinity, ease: "linear" }}
143+
/>
144+
)}
145+
<div className="mb-4 flex items-center justify-between gap-4">
146+
<div>
147+
<p className="font-code text-[0.58rem] uppercase tracking-[0.2em] text-[rgba(250,248,243,0.42)]">
148+
Live handoff board
149+
</p>
150+
<p className="mt-1 font-ui text-sm font-semibold text-[var(--tray-cream)]">
151+
Kitchen queue / OTP pickup
152+
</p>
153+
</div>
154+
<span className="rounded-full border border-[rgba(63,230,163,0.28)] bg-[rgba(63,230,163,0.12)] px-2.5 py-1 font-code text-[0.58rem] uppercase tracking-[0.12em] text-[#72f0b9]">
155+
Live
156+
</span>
157+
</div>
158+
159+
<div className="space-y-2.5">
160+
{handoffRows.map(([code, item, state, meta], index) => (
161+
<motion.div
162+
key={code}
163+
variants={{
164+
hidden: { opacity: 0, y: 16 },
165+
show: {
166+
opacity: 1,
167+
y: 0,
168+
transition: { duration: 0.46, delay: 0.32 + index * 0.08, ease: tm.ease },
169+
},
170+
}}
171+
className="group grid grid-cols-[4.5rem_1fr_auto] items-center gap-3 rounded-[var(--tray-radius-sm)] border border-[rgba(251,252,248,0.1)] bg-[rgba(16,20,25,0.22)] px-3 py-3 transition-all duration-300 hover:-translate-y-0.5 hover:border-[rgba(255,58,34,0.42)] hover:bg-[rgba(251,252,248,0.09)]"
172+
>
173+
<span className="font-code text-[0.68rem] text-[rgba(250,248,243,0.52)]">{code}</span>
174+
<span className="min-w-0">
175+
<span className="block truncate font-ui text-sm font-semibold text-[var(--tray-cream)]">
176+
{item}
177+
</span>
178+
<span className="mt-1 block font-code text-[0.55rem] uppercase tracking-[0.14em] text-[rgba(250,248,243,0.35)]">
179+
{meta}
180+
</span>
181+
</span>
182+
<span className="rounded-[var(--tray-radius-sm)] bg-[rgba(255,58,34,0.12)] px-2 py-1 font-code text-[0.58rem] uppercase tracking-[0.1em] text-[#ff725f]">
183+
{state}
184+
</span>
185+
</motion.div>
186+
))}
187+
</div>
188+
189+
<motion.div
190+
variants={lineReveal}
191+
className="mt-4 border-t border-[rgba(251,252,248,0.1)] pt-4 font-code text-[0.6rem] uppercase tracking-[0.14em] text-[rgba(250,248,243,0.36)]"
192+
>
193+
Board calls order / student shows code / queue closes clean.
194+
</motion.div>
195+
</motion.aside>
196+
);
197+
}
198+
114199
function StackProofSection() {
115200
const ref = useRef<HTMLElement | null>(null);
201+
const [revealedCards, setRevealedCards] = useState<number[]>([]);
116202
const { scrollYProgress } = useScroll({
117203
target: ref,
118204
offset: ["start 70%", "end 55%"],
119205
});
120206

207+
function revealCard(index: number) {
208+
setRevealedCards((current) => (current.includes(index) ? current : [...current, index]));
209+
}
210+
121211
return (
122212
<section
123213
ref={ref}
124214
id="built-with"
125-
className="tl-built-with relative overflow-hidden px-5 py-[clamp(5rem,9vw,8rem)] sm:px-8 lg:px-10"
215+
className="tl-built-with relative overflow-hidden px-5 py-[clamp(3.75rem,6.5vw,5.75rem)] sm:px-8 lg:px-10"
126216
>
127217
<div className="mx-auto max-w-4xl">
128218
<motion.p
@@ -140,7 +230,7 @@ function StackProofSection() {
140230
opacity: useTransform(scrollYProgress, [0.03, 0.22], [0, 1]),
141231
y: useTransform(scrollYProgress, [0.03, 0.22], [28, 0]),
142232
}}
143-
className="mt-5 max-w-3xl font-editorial text-[clamp(3.3rem,8vw,6.2rem)] font-black leading-[0.9] tracking-[-0.05em] text-[var(--tray-ink)]"
233+
className="mt-5 max-w-3xl font-editorial text-[clamp(2.65rem,5.7vw,4.85rem)] font-black leading-[0.92] tracking-[-0.035em] text-[var(--tray-ink)]"
144234
>
145235
A boring stack,
146236
<br />
@@ -164,7 +254,8 @@ function StackProofSection() {
164254
name={name}
165255
detail={detail}
166256
index={index}
167-
progress={scrollYProgress}
257+
revealed={revealedCards.includes(index)}
258+
onReveal={() => revealCard(index)}
168259
/>
169260
))}
170261
</div>
@@ -177,32 +268,55 @@ function StackCard({
177268
name,
178269
detail,
179270
index,
180-
progress,
271+
revealed,
272+
onReveal,
181273
}: {
182274
name: string;
183275
detail: string;
184276
index: number;
185-
progress: MotionValue<number>;
277+
revealed: boolean;
278+
onReveal: () => void;
186279
}) {
187-
const start = 0.06 + index * 0.035;
188-
const end = start + 0.14;
189-
const opacity = useTransform(progress, [start, end], [0, 1]);
190-
const y = useTransform(progress, [start, end], [34, 0]);
191-
const scale = useTransform(progress, [start, end], [0.94, 1]);
192-
193280
return (
194281
<motion.article
195-
style={{ opacity, y, scale }}
196-
className="group min-h-[5.3rem] rounded-[var(--tray-radius-md)] border border-[var(--tray-border)] bg-[var(--tray-surface-strong)] px-4 py-4 shadow-[0_18px_42px_rgba(16,20,25,0.06)] transition-colors duration-300 hover:border-[var(--tray-ink)]"
282+
tabIndex={0}
283+
onPointerEnter={onReveal}
284+
onMouseOver={onReveal}
285+
onMouseMove={onReveal}
286+
onMouseEnter={onReveal}
287+
onFocus={onReveal}
288+
initial={{ opacity: 0, y: 18 }}
289+
whileInView={{ opacity: 1, y: 0 }}
290+
viewport={{ once: true, amount: 0.35 }}
291+
transition={{ delay: index * 0.035, duration: 0.42, ease: tm.easeSharp }}
292+
className={`group relative min-h-[5.45rem] overflow-hidden rounded-[var(--tray-radius-md)] border px-4 py-4 outline-none transition-all duration-300 hover:-translate-y-0.5 hover:border-solid hover:border-[var(--tray-ink)] hover:bg-[var(--tray-surface-strong)] hover:shadow-[0_18px_42px_rgba(16,20,25,0.08)] focus-visible:-translate-y-0.5 focus-visible:border-solid focus-visible:border-[var(--tray-ink)] focus-visible:bg-[var(--tray-surface-strong)] focus-visible:shadow-[0_18px_42px_rgba(16,20,25,0.08)] ${
293+
revealed
294+
? "border-solid border-[var(--tray-border-strong)] bg-[var(--tray-surface-strong)] shadow-[0_18px_42px_rgba(16,20,25,0.08)]"
295+
: "border-dashed border-[rgba(16,20,25,0.2)] bg-[rgba(251,252,248,0.26)]"
296+
}`}
197297
>
198-
<h3 className="flex flex-wrap gap-x-1.5 font-ui text-[0.92rem] font-black tracking-0 text-[var(--tray-ink)]">
199-
{name.split(" ").map((part) => (
200-
<span key={part}>{part}</span>
201-
))}
202-
</h3>
203-
<p className="mt-2 font-code text-[0.54rem] uppercase tracking-[0.18em] text-[var(--tray-muted)]">
204-
{detail}
205-
</p>
298+
<span
299+
aria-hidden
300+
className={`absolute right-3 top-3 font-code text-[0.58rem] uppercase tracking-[0.16em] text-[var(--tray-muted)] transition-opacity duration-300 group-hover:opacity-0 group-focus-visible:opacity-0 ${
301+
revealed ? "opacity-0" : "opacity-45"
302+
}`}
303+
>
304+
{String(index + 1).padStart(2, "0")}
305+
</span>
306+
<div
307+
className={`transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:translate-y-0 group-hover:opacity-100 group-hover:blur-0 group-focus-visible:translate-y-0 group-focus-visible:opacity-100 group-focus-visible:blur-0 ${
308+
revealed ? "translate-y-0 opacity-100 blur-0" : "translate-y-3 opacity-0 blur-[3px]"
309+
}`}
310+
>
311+
<h3 className="flex flex-wrap gap-x-1.5 font-ui text-[0.92rem] font-black tracking-0 text-[var(--tray-ink)]">
312+
{name.split(" ").map((part) => (
313+
<span key={part}>{part}</span>
314+
))}
315+
</h3>
316+
<p className="mt-2 font-code text-[0.54rem] uppercase tracking-[0.18em] text-[var(--tray-muted)]">
317+
{detail}
318+
</p>
319+
</div>
206320
</motion.article>
207321
);
208322
}

0 commit comments

Comments
 (0)