Skip to content

Commit e0fac58

Browse files
MaskedHunterclaude
authored andcommitted
feat: add 5 new FoodShot hero concepts (11 total)
New concepts designed around the strongest food photos: - The Spotlight: cinematic single-hero carousel with auto-advance - Split Screen: diagonal before/after wall with parallax - The Lens: magic magnifying glass revealing enhanced versions - The Menu: restaurant menu layout with engagement stats - The Stack: scroll-driven card deck dealing animation Photo tier ranking: Culinary Dropout, CAPO PIZZA, Tokyo Hana (top 3 featured prominently across all new concepts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e90b684 commit e0fac58

6 files changed

Lines changed: 902 additions & 8 deletions

File tree

src/app/demos/foodshot/page.tsx

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,31 @@ const TheWall = dynamic(
3737
{ ssr: false, loading: () => <SectionLoader label="The Wall" /> }
3838
);
3939

40+
const TheSpotlight = dynamic(
41+
() => import("@/components/foodshot/spotlight-carousel").then((m) => m.SpotlightCarousel),
42+
{ ssr: false, loading: () => <SectionLoader label="The Spotlight" /> }
43+
);
44+
45+
const TheSplitScreen = dynamic(
46+
() => import("@/components/foodshot/split-screen").then((m) => m.SplitScreen),
47+
{ ssr: false, loading: () => <SectionLoader label="Split Screen" /> }
48+
);
49+
50+
const TheLens = dynamic(
51+
() => import("@/components/foodshot/magic-lens").then((m) => m.MagicLens),
52+
{ ssr: false, loading: () => <SectionLoader label="The Lens" /> }
53+
);
54+
55+
const TheMenu = dynamic(
56+
() => import("@/components/foodshot/restaurant-menu").then((m) => m.RestaurantMenu),
57+
{ ssr: false, loading: () => <SectionLoader label="The Menu" /> }
58+
);
59+
60+
const TheStack = dynamic(
61+
() => import("@/components/foodshot/card-stack").then((m) => m.CardStack),
62+
{ ssr: false, loading: () => <SectionLoader label="The Stack" /> }
63+
);
64+
4065
function SectionLoader({ label }: { label: string }) {
4166
return (
4267
<div className="w-full min-h-screen flex items-center justify-center bg-[#0a0a0a]">
@@ -55,6 +80,11 @@ const concepts = [
5580
{ id: "the-process", label: "The Process", desc: "Scroll Pipeline" },
5681
{ id: "the-pitch", label: "The Pitch", desc: "Homepage Hero" },
5782
{ id: "the-wall", label: "The Wall", desc: "Portfolio Grid" },
83+
{ id: "the-spotlight", label: "The Spotlight", desc: "Cinematic Carousel" },
84+
{ id: "split-screen", label: "Split Screen", desc: "Before vs After" },
85+
{ id: "the-lens", label: "The Lens", desc: "Magic Reveal" },
86+
{ id: "the-menu", label: "The Menu", desc: "Restaurant Style" },
87+
{ id: "the-stack", label: "The Stack", desc: "Card Deck" },
5888
];
5989

6090
export default function FoodShotShowcase() {
@@ -70,12 +100,12 @@ export default function FoodShotShowcase() {
70100
FoodShot
71101
</GradientText>
72102
</Link>
73-
<div className="hidden md:flex items-center gap-1.5">
103+
<div className="hidden lg:flex items-center gap-1">
74104
{concepts.map((c) => (
75105
<a
76106
key={c.id}
77107
href={`#${c.id}`}
78-
className="px-3 py-1.5 text-[10px] font-medium rounded-full border border-zinc-800 text-zinc-500 hover:text-amber-400 hover:border-amber-500/30 transition-colors"
108+
className="px-2.5 py-1.5 text-[9px] font-medium rounded-full border border-zinc-800 text-zinc-500 hover:text-amber-400 hover:border-amber-500/30 transition-colors"
79109
>
80110
{c.label}
81111
</a>
@@ -101,7 +131,7 @@ export default function FoodShotShowcase() {
101131
className="relative z-10 text-center max-w-3xl"
102132
>
103133
<span className="inline-block px-4 py-1.5 text-xs font-mono uppercase tracking-wider rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-400 mb-8">
104-
6 Hero Concepts for FoodShot
134+
11 Hero Concepts for FoodShot
105135
</span>
106136
<h1 className="text-5xl md:text-8xl font-bold mb-6 leading-[1.05] tracking-tight">
107137
<span className="bg-gradient-to-r from-amber-400 via-orange-400 to-red-400 bg-clip-text text-transparent">
@@ -113,19 +143,19 @@ export default function FoodShotShowcase() {
113143
</span>
114144
</h1>
115145
<p className="text-zinc-500 text-lg max-w-xl mx-auto mb-10">
116-
6 distinct visual experiences, each selling FoodShot from a unique angle.
146+
11 distinct visual experiences, each selling FoodShot from a unique angle.
117147
Real restaurant photos. No stock imagery.
118148
</p>
119149

120150
{/* Concept pills */}
121-
<div className="flex flex-wrap justify-center gap-2 max-w-xl mx-auto">
151+
<div className="flex flex-wrap justify-center gap-2 max-w-2xl mx-auto">
122152
{concepts.map((c, i) => (
123153
<motion.a
124154
key={c.id}
125155
href={`#${c.id}`}
126156
initial={{ opacity: 0, y: 8 }}
127157
animate={{ opacity: 1, y: 0 }}
128-
transition={{ delay: 0.4 + i * 0.08 }}
158+
transition={{ delay: 0.4 + i * 0.06 }}
129159
className="px-4 py-2.5 rounded-xl bg-zinc-900 border border-zinc-800 hover:border-amber-500/30 transition-colors group"
130160
>
131161
<span className="text-white text-sm font-medium">{c.label}</span>
@@ -146,8 +176,9 @@ export default function FoodShotShowcase() {
146176
</motion.div>
147177
</section>
148178

149-
{/* Concepts */}
179+
{/* All 11 Concepts */}
150180
<div className="space-y-0">
181+
{/* === ORIGINAL 6 === */}
151182
<div id="tasting-menu">
152183
<TastingMenu />
153184
</div>
@@ -181,13 +212,51 @@ export default function FoodShotShowcase() {
181212
<div id="the-wall">
182213
<TheWall />
183214
</div>
215+
216+
{/* === NEW 5 CONCEPTS === */}
217+
<div className="relative py-20 px-6">
218+
<div className="max-w-3xl mx-auto text-center">
219+
<div className="h-px bg-gradient-to-r from-transparent via-amber-500/30 to-transparent mb-12" />
220+
<span className="inline-block px-4 py-1.5 text-xs font-mono uppercase tracking-wider rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-400">
221+
5 New Concepts
222+
</span>
223+
</div>
224+
</div>
225+
226+
<div id="the-spotlight">
227+
<TheSpotlight />
228+
</div>
229+
230+
<div className="h-px bg-gradient-to-r from-transparent via-amber-500/10 to-transparent" />
231+
232+
<div id="split-screen">
233+
<TheSplitScreen />
234+
</div>
235+
236+
<div className="h-px bg-gradient-to-r from-transparent via-amber-500/10 to-transparent" />
237+
238+
<div id="the-lens">
239+
<TheLens />
240+
</div>
241+
242+
<div className="h-px bg-gradient-to-r from-transparent via-amber-500/10 to-transparent" />
243+
244+
<div id="the-menu">
245+
<TheMenu />
246+
</div>
247+
248+
<div className="h-px bg-gradient-to-r from-transparent via-amber-500/10 to-transparent" />
249+
250+
<div id="the-stack">
251+
<TheStack />
252+
</div>
184253
</div>
185254

186255
{/* Footer */}
187256
<footer className="relative z-10 py-16 px-6 border-t border-zinc-900">
188257
<div className="max-w-3xl mx-auto text-center">
189258
<p className="text-zinc-600 text-sm mb-6">
190-
6 concepts • Real restaurant photography • Contained images at proper resolution
259+
11 concepts • Real restaurant photography • Contained images at proper resolution
191260
</p>
192261
<div className="flex gap-4 justify-center">
193262
<Link
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"use client";
2+
3+
import { motion, useScroll, useTransform, MotionValue } from "framer-motion";
4+
import { useRef } from "react";
5+
import Image from "next/image";
6+
import { foodPhotos } from "./photo-data";
7+
8+
// Order by visual impact
9+
const stackOrder = [
10+
{ photo: foodPhotos[0], tagline: "Studio-quality steak & fries", stat: "+142% clicks" },
11+
{ photo: foodPhotos[3], tagline: "Seafood that sells itself", stat: "+89% orders" },
12+
{ photo: foodPhotos[1], tagline: "Sashimi that stops the scroll", stat: "+215% saves" },
13+
{ photo: foodPhotos[2], tagline: "Comfort food, elevated", stat: "+167% visits" },
14+
{ photo: foodPhotos[5], tagline: "Fine dining made irresistible", stat: "+94% shares" },
15+
];
16+
17+
function StackCard({
18+
item,
19+
index,
20+
total,
21+
scrollYProgress,
22+
}: {
23+
item: typeof stackOrder[0];
24+
index: number;
25+
total: number;
26+
scrollYProgress: MotionValue<number>;
27+
}) {
28+
// Each card occupies a segment of the scroll range
29+
const segmentSize = 1 / total;
30+
const start = index * segmentSize;
31+
const end = start + segmentSize;
32+
33+
// Card scale: starts slightly smaller, grows to full, then stays
34+
const scale = useTransform(
35+
scrollYProgress,
36+
[start, start + segmentSize * 0.3, end],
37+
[0.88, 1, 1]
38+
);
39+
40+
// Card opacity: fades in, stays, then fades slightly for next
41+
const opacity = useTransform(
42+
scrollYProgress,
43+
index < total - 1
44+
? [start, start + segmentSize * 0.2, end - segmentSize * 0.2, end]
45+
: [start, start + segmentSize * 0.2, 1, 1],
46+
index < total - 1 ? [0, 1, 1, 0.3] : [0, 1, 1, 1]
47+
);
48+
49+
// Y position: slides up from below
50+
const y = useTransform(
51+
scrollYProgress,
52+
[start, start + segmentSize * 0.3],
53+
[80, 0]
54+
);
55+
56+
// Z-index based on which card should be on top
57+
const zIndex = useTransform(
58+
scrollYProgress,
59+
[start, end],
60+
[total - index, total - index]
61+
);
62+
63+
// Rotation for a natural card-dealing feel
64+
const rotate = useTransform(
65+
scrollYProgress,
66+
[start, start + segmentSize * 0.3],
67+
[index % 2 === 0 ? 3 : -3, 0]
68+
);
69+
70+
return (
71+
<motion.div
72+
style={{
73+
scale,
74+
opacity,
75+
y,
76+
zIndex,
77+
rotate,
78+
}}
79+
className="absolute inset-0 flex items-center justify-center"
80+
>
81+
<div className="w-full max-w-md mx-auto">
82+
<div className="relative rounded-3xl overflow-hidden border border-amber-500/10 bg-zinc-900 shadow-2xl shadow-black/60" style={{ aspectRatio: "3 / 4" }}>
83+
<Image
84+
src={item.photo.after}
85+
alt={`${item.photo.restaurant} — Enhanced`}
86+
fill
87+
className="object-cover"
88+
sizes="(max-width: 768px) 90vw, 448px"
89+
priority={index < 2}
90+
/>
91+
92+
{/* Bottom gradient */}
93+
<div className="absolute bottom-0 inset-x-0 h-2/5 bg-gradient-to-t from-black/90 via-black/50 to-transparent z-10" />
94+
95+
{/* Card content */}
96+
<div className="absolute bottom-0 inset-x-0 p-7 z-20">
97+
<p className="text-amber-400/60 text-[10px] font-mono uppercase tracking-[0.2em] mb-2">
98+
{item.photo.restaurant}
99+
</p>
100+
<h3 className="text-white text-xl md:text-2xl font-bold tracking-tight mb-2">
101+
{item.tagline}
102+
</h3>
103+
<div className="flex items-center gap-3">
104+
<span className="text-amber-400 text-lg font-bold">{item.stat}</span>
105+
<span className="text-zinc-600 text-[10px] font-mono">after FoodShot</span>
106+
</div>
107+
</div>
108+
109+
{/* Card number chip */}
110+
<div className="absolute top-4 right-4 z-20">
111+
<span className="w-8 h-8 rounded-full bg-black/50 backdrop-blur-sm border border-amber-500/20 flex items-center justify-center text-amber-400 text-xs font-bold">
112+
{index + 1}
113+
</span>
114+
</div>
115+
</div>
116+
</div>
117+
</motion.div>
118+
);
119+
}
120+
121+
export function CardStack() {
122+
const containerRef = useRef<HTMLDivElement>(null);
123+
const { scrollYProgress } = useScroll({
124+
target: containerRef,
125+
offset: ["start start", "end end"],
126+
});
127+
128+
// Progress indicator
129+
const progressWidth = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
130+
131+
return (
132+
<section
133+
ref={containerRef}
134+
className="relative bg-[#060606]"
135+
style={{ height: `${stackOrder.length * 100}vh` }}
136+
>
137+
<div className="sticky top-0 h-screen overflow-hidden">
138+
{/* Header */}
139+
<div className="absolute top-8 left-0 right-0 z-30 text-center px-6">
140+
<h2 className="text-3xl md:text-5xl font-bold text-white tracking-tight">
141+
The{" "}
142+
<span className="bg-gradient-to-r from-amber-400 to-orange-400 bg-clip-text text-transparent">
143+
collection
144+
</span>
145+
</h2>
146+
<p className="text-zinc-600 text-sm mt-2">
147+
Scroll to deal the deck
148+
</p>
149+
</div>
150+
151+
{/* Card area */}
152+
<div className="absolute inset-0 flex items-center justify-center px-6 pt-16">
153+
{stackOrder.map((item, i) => (
154+
<StackCard
155+
key={i}
156+
item={item}
157+
index={i}
158+
total={stackOrder.length}
159+
scrollYProgress={scrollYProgress}
160+
/>
161+
))}
162+
</div>
163+
164+
{/* Progress bar at bottom */}
165+
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 w-48 z-30">
166+
<div className="h-1 bg-zinc-800 rounded-full overflow-hidden">
167+
<motion.div
168+
style={{ width: progressWidth }}
169+
className="h-full bg-gradient-to-r from-amber-500 to-orange-400 rounded-full"
170+
/>
171+
</div>
172+
<div className="flex justify-between mt-2">
173+
<span className="text-zinc-700 text-[8px] font-mono">1</span>
174+
<span className="text-zinc-700 text-[8px] font-mono">{stackOrder.length}</span>
175+
</div>
176+
</div>
177+
</div>
178+
</section>
179+
);
180+
}

0 commit comments

Comments
 (0)