@tanstack/cta-framework-react-cra 0.44.3 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{examples/tanchat/assets/src/components/example-AIAssistant.tsx → add-ons/ai/assets/src/components/demo-AIAssistant.tsx} +6 -8
- package/{examples/tanchat/assets/src/components/example-GuitarRecommendation.tsx → add-ons/ai/assets/src/components/demo-GuitarRecommendation.tsx} +2 -2
- package/{examples/tanchat/assets/src/lib/example.ai-hook.ts → add-ons/ai/assets/src/lib/demo-ai-hook.ts} +9 -9
- package/{examples/tanchat/assets/src/lib/example.guitar-tools.ts → add-ons/ai/assets/src/lib/demo-guitar-tools.ts} +1 -1
- package/{examples/tanchat/assets/src/routes/demo/tanchat.tsx → add-ons/ai/assets/src/routes/demo/ai-chat.tsx} +28 -148
- package/{examples/tanchat/assets/src/routes/demo/image.tsx → add-ons/ai/assets/src/routes/demo/ai-image.tsx} +2 -50
- package/add-ons/ai/assets/src/routes/demo/ai-structured.tsx +310 -0
- package/{examples/tanchat/assets/src/routes/demo/api.tanchat.ts → add-ons/ai/assets/src/routes/demo/api.ai.chat.ts} +16 -6
- package/{examples/tanchat/assets/src/routes/demo/api.image.ts → add-ons/ai/assets/src/routes/demo/api.ai.image.ts} +3 -5
- package/add-ons/ai/assets/src/routes/demo/api.ai.structured.ts +136 -0
- package/add-ons/ai/assets/src/routes/demo/api.ai.transcription.ts +89 -0
- package/{examples/tanchat/assets/src/routes/demo/api.tts.ts → add-ons/ai/assets/src/routes/demo/api.ai.tts.ts} +1 -1
- package/{examples/tanchat/assets/src/routes/example.guitars → add-ons/ai/assets/src/routes/demo/guitars}/$guitarId.tsx +3 -2
- package/{examples/tanchat/assets/src/routes/example.guitars → add-ons/ai/assets/src/routes/demo/guitars}/index.tsx +3 -2
- package/add-ons/ai/info.json +46 -0
- package/{examples/tanchat → add-ons/ai}/package.json +1 -1
- package/examples/events/README.md +110 -0
- package/examples/events/assets/content/speakers/andre-costa.md +22 -0
- package/examples/events/assets/content/speakers/hans-mueller.md +22 -0
- package/examples/events/assets/content/speakers/isabella-martinez.md +22 -0
- package/examples/events/assets/content/speakers/kenji-nakamura.md +22 -0
- package/examples/events/assets/content/speakers/marie-dubois.md +20 -0
- package/examples/events/assets/content/speakers/priya-sharma.md +22 -0
- package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
- package/examples/events/assets/content/talks/french-macaron-mastery.md +39 -0
- package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md +39 -0
- package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md +39 -0
- package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md +36 -0
- package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md +32 -0
- package/examples/events/assets/content/talks/the-science-of-sugar.md +39 -0
- package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md +39 -0
- package/examples/events/assets/content-collections.ts +56 -0
- package/examples/events/assets/public/background-1.jpg +0 -0
- package/examples/events/assets/public/background-2.jpg +0 -0
- package/examples/events/assets/public/background-3.jpg +0 -0
- package/examples/events/assets/public/background-4.jpg +0 -0
- package/examples/events/assets/public/conference-logo.png +0 -0
- package/examples/events/assets/public/favicon.ico +0 -0
- package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
- package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
- package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
- package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
- package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
- package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
- package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
- package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
- package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
- package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
- package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
- package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
- package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
- package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
- package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
- package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
- package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
- package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
- package/examples/events/assets/src/components/RemyButton.tsx +18 -0
- package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
- package/examples/events/assets/src/components/TalkCard.tsx +77 -0
- package/examples/events/assets/src/components/ui/card.tsx +92 -0
- package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
- package/examples/events/assets/src/lib/conference-tools.ts +210 -0
- package/examples/events/assets/src/lib/utils.ts +6 -0
- package/examples/events/assets/src/routes/api.remy-chat.ts +121 -0
- package/examples/events/assets/src/routes/index.tsx +192 -0
- package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
- package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
- package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
- package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
- package/examples/events/assets/src/routes/talks.index.tsx +40 -0
- package/examples/events/assets/src/styles.css +194 -0
- package/examples/events/info.json +63 -0
- package/examples/events/package.json +23 -0
- package/package.json +2 -2
- package/examples/tanchat/assets/src/lib/model-selection.ts +0 -78
- package/examples/tanchat/assets/src/lib/vendor-capabilities.ts +0 -55
- package/examples/tanchat/assets/src/routes/demo/api.available-providers.ts +0 -35
- package/examples/tanchat/assets/src/routes/demo/api.structured.ts +0 -168
- package/examples/tanchat/assets/src/routes/demo/api.transcription.ts +0 -89
- package/examples/tanchat/assets/src/routes/demo/structured.tsx +0 -460
- package/examples/tanchat/info.json +0 -46
- /package/{examples/tanchat → add-ons/ai}/README.md +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/_dot_env.local.append +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-flowers.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-motherboard.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-racing.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-steamer-trunk.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-superhero.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-traveling.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-video-games.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-ukelele-tanstack.jpg +0 -0
- /package/{examples/tanchat/assets/src/data/example-guitars.ts → add-ons/ai/assets/src/data/demo-guitars.ts} +0 -0
- /package/{examples/tanchat/assets/src/hooks/useAudioRecorder.ts → add-ons/ai/assets/src/hooks/demo-useAudioRecorder.ts} +0 -0
- /package/{examples/tanchat/assets/src/hooks/useTTS.ts → add-ons/ai/assets/src/hooks/demo-useTTS.ts} +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/src/lib/ai-devtools.tsx +0 -0
- /package/{examples/tanchat/assets/src/routes/demo/tanchat.css → add-ons/ai/assets/src/routes/demo/ai-chat.css} +0 -0
- /package/{examples/tanchat → add-ons/ai}/small-logo.svg +0 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
+
import { Clock, Calendar, MapPin, ChevronRight } from 'lucide-react'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { allTalks, allSpeakers } from 'content-collections'
|
|
6
|
+
|
|
7
|
+
import RemyAssistant from '@/components/RemyAssistant'
|
|
8
|
+
|
|
9
|
+
export const Route = createFileRoute('/schedule/')({
|
|
10
|
+
component: SchedulePage,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// Helper to get speaker data by name
|
|
14
|
+
function getSpeakerByName(name: string) {
|
|
15
|
+
return allSpeakers.find(
|
|
16
|
+
(s) => s.name.toLowerCase() === name.toLowerCase()
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Define the conference schedule with time slots
|
|
21
|
+
const scheduleData = [
|
|
22
|
+
{
|
|
23
|
+
day: 1,
|
|
24
|
+
date: 'March 15, 2026',
|
|
25
|
+
dayName: 'Day One',
|
|
26
|
+
theme: 'French Foundations',
|
|
27
|
+
sessions: [
|
|
28
|
+
{ time: '9:00 AM', talkSlug: 'french-macaron-mastery' },
|
|
29
|
+
{ time: '11:30 AM', talkSlug: 'croissant-lamination-secrets' },
|
|
30
|
+
{ time: '3:00 PM', talkSlug: 'the-science-of-sugar' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
day: 2,
|
|
35
|
+
date: 'March 16, 2026',
|
|
36
|
+
dayName: 'Day Two',
|
|
37
|
+
theme: 'Global Traditions',
|
|
38
|
+
sessions: [
|
|
39
|
+
{ time: '9:00 AM', talkSlug: 'sourdough-from-starter-to-masterpiece' },
|
|
40
|
+
{ time: '11:30 AM', talkSlug: 'umami-in-pastry-east-meets-west' },
|
|
41
|
+
{ time: '2:30 PM', talkSlug: 'savory-breads-of-the-mediterranean' },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
day: 3,
|
|
46
|
+
date: 'March 17, 2026',
|
|
47
|
+
dayName: 'Day Three',
|
|
48
|
+
theme: 'Artisan Mastery',
|
|
49
|
+
sessions: [
|
|
50
|
+
{ time: '9:00 AM', talkSlug: 'the-art-of-the-perfect-tart' },
|
|
51
|
+
{ time: '11:00 AM', talkSlug: 'neapolitan-pizza-tradition-meets-innovation' },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
function SchedulePage() {
|
|
57
|
+
const [selectedDay, setSelectedDay] = useState(1)
|
|
58
|
+
|
|
59
|
+
const currentDayData = scheduleData.find((d) => d.day === selectedDay)!
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<RemyAssistant />
|
|
64
|
+
<div className="min-h-screen">
|
|
65
|
+
{/* Hero section */}
|
|
66
|
+
<div className="relative py-16 px-6">
|
|
67
|
+
<div className="max-w-7xl mx-auto text-center">
|
|
68
|
+
<div className="inline-flex items-center gap-2 px-4 py-2 mb-6 rounded-full bg-copper/10 border border-copper/30 text-copper-light text-sm font-medium">
|
|
69
|
+
<Calendar className="w-4 h-4" />
|
|
70
|
+
<span>March 15-17, 2026</span>
|
|
71
|
+
<span className="mx-2 text-copper/40">•</span>
|
|
72
|
+
<MapPin className="w-4 h-4" />
|
|
73
|
+
<span>Paris, France</span>
|
|
74
|
+
</div>
|
|
75
|
+
<h1 className="font-display text-5xl md:text-6xl font-bold text-cream mb-4">
|
|
76
|
+
Conference <span className="text-gold italic">Schedule</span>
|
|
77
|
+
</h1>
|
|
78
|
+
<p className="text-xl text-cream/70 max-w-2xl mx-auto font-body">
|
|
79
|
+
Three days of masterclasses, demonstrations, and culinary inspiration from the world's finest pastry artisans.
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Day selector tabs */}
|
|
85
|
+
<div className="max-w-7xl mx-auto px-6 mb-12">
|
|
86
|
+
<div className="flex justify-center">
|
|
87
|
+
<div className="inline-flex bg-card/50 rounded-2xl p-2 border border-border/50">
|
|
88
|
+
{scheduleData.map((day) => (
|
|
89
|
+
<button
|
|
90
|
+
key={day.day}
|
|
91
|
+
onClick={() => setSelectedDay(day.day)}
|
|
92
|
+
className={`relative px-8 py-4 rounded-xl font-display font-semibold transition-all duration-300 ${
|
|
93
|
+
selectedDay === day.day
|
|
94
|
+
? 'bg-gradient-to-br from-copper to-copper-dark text-charcoal shadow-lg shadow-copper/20'
|
|
95
|
+
: 'text-cream/70 hover:text-cream hover:bg-card'
|
|
96
|
+
}`}
|
|
97
|
+
>
|
|
98
|
+
<span className="block text-xs uppercase tracking-wider opacity-75">
|
|
99
|
+
{day.dayName}
|
|
100
|
+
</span>
|
|
101
|
+
<span className="block text-lg">{day.date.split(',')[0].split(' ').slice(0, 2).join(' ')}</span>
|
|
102
|
+
</button>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Day theme header */}
|
|
109
|
+
<div className="max-w-7xl mx-auto px-6 mb-8">
|
|
110
|
+
<div className="text-center">
|
|
111
|
+
<h2 className="font-display text-3xl font-bold text-cream mb-2">
|
|
112
|
+
{currentDayData.dayName}: <span className="text-gold italic">{currentDayData.theme}</span>
|
|
113
|
+
</h2>
|
|
114
|
+
<p className="text-cream/50 font-body">{currentDayData.date}</p>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{/* Schedule timeline */}
|
|
119
|
+
<div className="max-w-5xl mx-auto px-6 pb-20">
|
|
120
|
+
<div className="relative">
|
|
121
|
+
{/* Timeline line */}
|
|
122
|
+
<div className="absolute left-8 md:left-12 top-0 bottom-0 w-px bg-gradient-to-b from-copper via-gold to-copper/30" />
|
|
123
|
+
|
|
124
|
+
{/* Sessions */}
|
|
125
|
+
<div className="space-y-8">
|
|
126
|
+
{currentDayData.sessions.map((session, index) => {
|
|
127
|
+
const talk = allTalks.find((t) => t.slug === session.talkSlug)
|
|
128
|
+
if (!talk) return null
|
|
129
|
+
|
|
130
|
+
const speaker = getSpeakerByName(talk.speaker)
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<Link
|
|
134
|
+
key={session.talkSlug}
|
|
135
|
+
to={`/talks/${talk.slug}`}
|
|
136
|
+
className="group block"
|
|
137
|
+
>
|
|
138
|
+
<div className="relative flex gap-6 md:gap-10">
|
|
139
|
+
{/* Time marker */}
|
|
140
|
+
<div className="flex-shrink-0 w-16 md:w-24 pt-6">
|
|
141
|
+
<div className="relative">
|
|
142
|
+
{/* Timeline dot */}
|
|
143
|
+
<div className="absolute -right-[13px] md:-right-[17px] top-0 w-6 h-6 rounded-full bg-charcoal border-2 border-gold flex items-center justify-center group-hover:border-copper group-hover:scale-110 transition-all">
|
|
144
|
+
<div className="w-2 h-2 rounded-full bg-gold group-hover:bg-copper transition-colors" />
|
|
145
|
+
</div>
|
|
146
|
+
<span className="block text-right text-sm md:text-base font-display font-semibold text-copper-light">
|
|
147
|
+
{session.time}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* Session card */}
|
|
153
|
+
<div
|
|
154
|
+
className="flex-1 relative overflow-hidden rounded-2xl bg-card border border-border/50
|
|
155
|
+
group-hover:border-gold/50 transition-all duration-300 group-hover:shadow-xl group-hover:shadow-gold/5
|
|
156
|
+
group-hover:-translate-y-1"
|
|
157
|
+
style={{
|
|
158
|
+
animationDelay: `${index * 100}ms`,
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
{/* Background image with overlay */}
|
|
162
|
+
<div className="absolute inset-0">
|
|
163
|
+
<img
|
|
164
|
+
src={`/${talk.image}`}
|
|
165
|
+
alt={talk.title}
|
|
166
|
+
className="w-full h-full object-cover opacity-30 group-hover:opacity-40 group-hover:scale-105 transition-all duration-500"
|
|
167
|
+
/>
|
|
168
|
+
<div className="absolute inset-0 bg-gradient-to-r from-charcoal via-charcoal/95 to-charcoal/80" />
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{/* Content */}
|
|
172
|
+
<div className="relative p-6 md:p-8 flex flex-col md:flex-row gap-6 items-start">
|
|
173
|
+
{/* Speaker image */}
|
|
174
|
+
{speaker && (
|
|
175
|
+
<div className="flex-shrink-0">
|
|
176
|
+
<div className="relative w-20 h-20 md:w-24 md:h-24 rounded-xl overflow-hidden border-2 border-gold/30 group-hover:border-gold/60 transition-colors shadow-lg">
|
|
177
|
+
<img
|
|
178
|
+
src={`/${speaker.headshot}`}
|
|
179
|
+
alt={speaker.name}
|
|
180
|
+
className="w-full h-full object-cover"
|
|
181
|
+
/>
|
|
182
|
+
<div className="absolute inset-0 bg-gradient-to-t from-charcoal/40 to-transparent" />
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
{/* Talk info */}
|
|
188
|
+
<div className="flex-1 min-w-0">
|
|
189
|
+
{/* Topics */}
|
|
190
|
+
<div className="flex flex-wrap gap-2 mb-3">
|
|
191
|
+
{talk.topics.slice(0, 3).map((topic) => (
|
|
192
|
+
<span
|
|
193
|
+
key={topic}
|
|
194
|
+
className="px-2.5 py-0.5 text-xs font-medium tracking-wide uppercase bg-gold/10 text-gold border border-gold/20 rounded-full"
|
|
195
|
+
>
|
|
196
|
+
{topic}
|
|
197
|
+
</span>
|
|
198
|
+
))}
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{/* Title */}
|
|
202
|
+
<h3 className="font-display text-xl md:text-2xl font-semibold text-cream group-hover:text-gold transition-colors mb-2 leading-tight">
|
|
203
|
+
{talk.title}
|
|
204
|
+
</h3>
|
|
205
|
+
|
|
206
|
+
{/* Speaker & Duration */}
|
|
207
|
+
<div className="flex flex-wrap items-center gap-4 text-cream/60 text-sm mb-3">
|
|
208
|
+
<span className="font-medium text-copper-light">
|
|
209
|
+
{talk.speaker}
|
|
210
|
+
</span>
|
|
211
|
+
<div className="flex items-center gap-1.5">
|
|
212
|
+
<Clock className="w-3.5 h-3.5" />
|
|
213
|
+
<span>{talk.duration}</span>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{/* Speaker title if available */}
|
|
218
|
+
{speaker && (
|
|
219
|
+
<p className="text-cream/50 text-sm font-body">
|
|
220
|
+
{speaker.title} at {speaker.restaurant}
|
|
221
|
+
</p>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* Arrow indicator */}
|
|
226
|
+
<div className="flex-shrink-0 self-center">
|
|
227
|
+
<div className="w-10 h-10 rounded-full bg-gold/10 border border-gold/20 flex items-center justify-center group-hover:bg-gold/20 group-hover:border-gold/40 transition-all">
|
|
228
|
+
<ChevronRight className="w-5 h-5 text-gold group-hover:translate-x-0.5 transition-transform" />
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</Link>
|
|
235
|
+
)
|
|
236
|
+
})}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Bottom CTA */}
|
|
242
|
+
<div className="max-w-4xl mx-auto px-6 pb-20">
|
|
243
|
+
<div className="relative p-8 md:p-12 rounded-3xl bg-gradient-to-br from-card to-charcoal border border-border/50 overflow-hidden text-center">
|
|
244
|
+
<div className="absolute top-0 right-0 w-64 h-64 bg-copper/5 rounded-full blur-3xl" />
|
|
245
|
+
<div className="absolute bottom-0 left-0 w-48 h-48 bg-gold/5 rounded-full blur-3xl" />
|
|
246
|
+
|
|
247
|
+
<div className="relative">
|
|
248
|
+
<h3 className="font-display text-2xl md:text-3xl font-bold text-cream mb-3">
|
|
249
|
+
Don't Miss a Single Session
|
|
250
|
+
</h3>
|
|
251
|
+
<p className="text-cream/60 font-body mb-6 max-w-xl mx-auto">
|
|
252
|
+
Each masterclass offers hands-on learning from the world's finest pastry artisans.
|
|
253
|
+
</p>
|
|
254
|
+
<div className="flex flex-wrap justify-center gap-4">
|
|
255
|
+
<Link
|
|
256
|
+
to="/talks"
|
|
257
|
+
className="inline-flex items-center gap-2 px-6 py-3 rounded-full bg-gradient-to-r from-copper to-copper-dark text-charcoal font-semibold transition-all hover:shadow-lg hover:shadow-copper/30 hover:scale-[1.02]"
|
|
258
|
+
>
|
|
259
|
+
Browse All Sessions
|
|
260
|
+
</Link>
|
|
261
|
+
<Link
|
|
262
|
+
to="/speakers"
|
|
263
|
+
className="inline-flex items-center gap-2 px-6 py-3 rounded-full border-2 border-gold/50 text-gold font-semibold transition-all hover:bg-gold/10 hover:border-gold"
|
|
264
|
+
>
|
|
265
|
+
Meet the Speakers
|
|
266
|
+
</Link>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
</>
|
|
273
|
+
)
|
|
274
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { marked } from 'marked'
|
|
3
|
+
import { MapPin, Award, ArrowLeft } from 'lucide-react'
|
|
4
|
+
import { Link } from '@tanstack/react-router'
|
|
5
|
+
|
|
6
|
+
import { allSpeakers, allTalks } from 'content-collections'
|
|
7
|
+
|
|
8
|
+
import RemyAssistant from '@/components/RemyAssistant'
|
|
9
|
+
import TalkCard from '@/components/TalkCard'
|
|
10
|
+
|
|
11
|
+
export const Route = createFileRoute('/speakers/$slug')({
|
|
12
|
+
loader: async ({ params }) => {
|
|
13
|
+
const speaker = allSpeakers.find((s) => s.slug === params.slug)
|
|
14
|
+
if (!speaker) {
|
|
15
|
+
throw new Error('Speaker not found')
|
|
16
|
+
}
|
|
17
|
+
const speakerTalks = allTalks.filter((t) => t.speaker === speaker.name)
|
|
18
|
+
return { speaker, speakerTalks }
|
|
19
|
+
},
|
|
20
|
+
component: SpeakerDetailPage,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
function SpeakerDetailPage() {
|
|
24
|
+
const { speaker, speakerTalks } = Route.useLoaderData()
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="min-h-screen">
|
|
28
|
+
<RemyAssistant />
|
|
29
|
+
|
|
30
|
+
{/* Back navigation */}
|
|
31
|
+
<div className="max-w-7xl mx-auto px-6 py-4">
|
|
32
|
+
<Link
|
|
33
|
+
to="/speakers"
|
|
34
|
+
className="inline-flex items-center gap-2 text-cream/60 hover:text-gold transition-colors"
|
|
35
|
+
>
|
|
36
|
+
<ArrowLeft className="w-4 h-4" />
|
|
37
|
+
<span>All Speakers</span>
|
|
38
|
+
</Link>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{/* Hero section */}
|
|
42
|
+
<div className="relative py-12 px-6">
|
|
43
|
+
<div className="max-w-7xl mx-auto">
|
|
44
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
|
|
45
|
+
{/* Photo */}
|
|
46
|
+
<div className="lg:col-span-1">
|
|
47
|
+
<div className="aspect-square rounded-2xl overflow-hidden border border-border/50">
|
|
48
|
+
<img
|
|
49
|
+
src={`/${speaker.headshot}`}
|
|
50
|
+
alt={speaker.name}
|
|
51
|
+
className="w-full h-full object-cover"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Info */}
|
|
57
|
+
<div className="lg:col-span-2 flex flex-col justify-center">
|
|
58
|
+
{/* Specialty tag */}
|
|
59
|
+
<span className="inline-block w-fit px-4 py-1.5 text-sm font-medium tracking-wider uppercase bg-copper/20 text-copper-light rounded-full border border-copper/30 mb-4">
|
|
60
|
+
{speaker.specialty}
|
|
61
|
+
</span>
|
|
62
|
+
|
|
63
|
+
<h1 className="font-display text-5xl md:text-6xl font-bold text-cream mb-3">
|
|
64
|
+
{speaker.name}
|
|
65
|
+
</h1>
|
|
66
|
+
|
|
67
|
+
<p className="text-2xl text-gold font-display italic mb-4">
|
|
68
|
+
{speaker.title}
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
<div className="flex items-center gap-2 text-cream/60 text-lg mb-8">
|
|
72
|
+
<MapPin className="w-5 h-5 text-copper" />
|
|
73
|
+
<span>{speaker.restaurant}, {speaker.location}</span>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{/* Awards */}
|
|
77
|
+
{speaker.awards && speaker.awards.length > 0 && (
|
|
78
|
+
<div className="space-y-3">
|
|
79
|
+
<h3 className="text-sm font-medium tracking-wider uppercase text-cream/50">Awards & Recognition</h3>
|
|
80
|
+
<div className="flex flex-wrap gap-2">
|
|
81
|
+
{speaker.awards.map((award) => (
|
|
82
|
+
<span
|
|
83
|
+
key={award}
|
|
84
|
+
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm bg-gold/10 text-gold/90 rounded-lg border border-gold/20"
|
|
85
|
+
>
|
|
86
|
+
<Award className="w-3.5 h-3.5" />
|
|
87
|
+
{award}
|
|
88
|
+
</span>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Bio section */}
|
|
99
|
+
<div className="max-w-4xl mx-auto px-6 py-12">
|
|
100
|
+
<div className="prose prose-lg max-w-none prose-invert prose-p:text-cream/80 prose-headings:text-cream prose-headings:font-display prose-strong:text-cream prose-a:text-gold font-body text-lg leading-relaxed">
|
|
101
|
+
<div
|
|
102
|
+
dangerouslySetInnerHTML={{ __html: marked(speaker.content) }}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Speaker's talks */}
|
|
108
|
+
{speakerTalks.length > 0 && (
|
|
109
|
+
<div className="max-w-7xl mx-auto px-6 py-12">
|
|
110
|
+
<h2 className="font-display text-3xl font-bold text-cream mb-8">
|
|
111
|
+
Sessions by {speaker.name}
|
|
112
|
+
</h2>
|
|
113
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
114
|
+
{speakerTalks.map((talk) => (
|
|
115
|
+
<TalkCard key={talk.slug} talk={talk} />
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import { allSpeakers } from 'content-collections'
|
|
4
|
+
|
|
5
|
+
import SpeakerCard from '@/components/SpeakerCard'
|
|
6
|
+
import RemyAssistant from '@/components/RemyAssistant'
|
|
7
|
+
|
|
8
|
+
export const Route = createFileRoute('/speakers/')({
|
|
9
|
+
component: SpeakersPage,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
function SpeakersPage() {
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<RemyAssistant />
|
|
16
|
+
<div className="min-h-screen">
|
|
17
|
+
{/* Hero section */}
|
|
18
|
+
<div className="relative py-16 px-6">
|
|
19
|
+
<div className="max-w-7xl mx-auto text-center">
|
|
20
|
+
<h1 className="font-display text-5xl md:text-6xl font-bold text-cream mb-4">
|
|
21
|
+
Our <span className="text-gold italic">Distinguished</span> Speakers
|
|
22
|
+
</h1>
|
|
23
|
+
<p className="text-xl text-cream/70 max-w-2xl mx-auto font-body">
|
|
24
|
+
Meet the world-renowned pastry chefs and master bakers who will share their expertise at Haute Pâtisserie 2026.
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
{/* Speakers grid */}
|
|
30
|
+
<div className="max-w-7xl mx-auto px-6 pb-20">
|
|
31
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
|
32
|
+
{allSpeakers.map((speaker) => (
|
|
33
|
+
<SpeakerCard key={speaker.slug} speaker={speaker} />
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
+
import { marked } from 'marked'
|
|
3
|
+
import { Clock, User, ArrowLeft, Tag } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
import { allTalks, allSpeakers } from 'content-collections'
|
|
6
|
+
|
|
7
|
+
import RemyAssistant from '@/components/RemyAssistant'
|
|
8
|
+
|
|
9
|
+
export const Route = createFileRoute('/talks/$slug')({
|
|
10
|
+
loader: async ({ params }) => {
|
|
11
|
+
const talk = allTalks.find((t) => t.slug === params.slug)
|
|
12
|
+
if (!talk) {
|
|
13
|
+
throw new Error('Talk not found')
|
|
14
|
+
}
|
|
15
|
+
const speaker = allSpeakers.find((s) => s.name === talk.speaker)
|
|
16
|
+
return { talk, speaker }
|
|
17
|
+
},
|
|
18
|
+
component: TalkDetailPage,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
function TalkDetailPage() {
|
|
22
|
+
const { talk, speaker } = Route.useLoaderData()
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="min-h-screen">
|
|
26
|
+
<RemyAssistant />
|
|
27
|
+
|
|
28
|
+
{/* Back navigation */}
|
|
29
|
+
<div className="max-w-7xl mx-auto px-6 py-4">
|
|
30
|
+
<Link
|
|
31
|
+
to="/talks"
|
|
32
|
+
className="inline-flex items-center gap-2 text-cream/60 hover:text-gold transition-colors"
|
|
33
|
+
>
|
|
34
|
+
<ArrowLeft className="w-4 h-4" />
|
|
35
|
+
<span>All Sessions</span>
|
|
36
|
+
</Link>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
{/* Hero image */}
|
|
40
|
+
<div className="relative h-[40vh] max-w-7xl mx-auto px-6 mb-8">
|
|
41
|
+
<div className="w-full h-full rounded-2xl overflow-hidden border border-border/50">
|
|
42
|
+
<img
|
|
43
|
+
src={`/${talk.image}`}
|
|
44
|
+
alt={talk.title}
|
|
45
|
+
className="w-full h-full object-cover"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="absolute inset-6 bg-gradient-to-t from-charcoal/60 to-transparent rounded-2xl pointer-events-none" />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
{/* Content */}
|
|
52
|
+
<div className="max-w-4xl mx-auto px-6">
|
|
53
|
+
{/* Topics */}
|
|
54
|
+
<div className="flex flex-wrap gap-2 mb-6">
|
|
55
|
+
{talk.topics.map((topic) => (
|
|
56
|
+
<span
|
|
57
|
+
key={topic}
|
|
58
|
+
className="inline-flex items-center gap-1.5 px-3 py-1 text-sm font-medium tracking-wide uppercase bg-gold/15 text-gold border border-gold/30 rounded-full"
|
|
59
|
+
>
|
|
60
|
+
<Tag className="w-3 h-3" />
|
|
61
|
+
{topic}
|
|
62
|
+
</span>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{/* Title */}
|
|
67
|
+
<h1 className="font-display text-4xl md:text-5xl font-bold text-cream mb-6 leading-tight">
|
|
68
|
+
{talk.title}
|
|
69
|
+
</h1>
|
|
70
|
+
|
|
71
|
+
{/* Meta info */}
|
|
72
|
+
<div className="flex flex-wrap items-center gap-6 mb-10 pb-10 border-b border-border/50">
|
|
73
|
+
{/* Speaker link */}
|
|
74
|
+
{speaker ? (
|
|
75
|
+
<Link
|
|
76
|
+
to={`/speakers/${speaker.slug}`}
|
|
77
|
+
className="flex items-center gap-3 group"
|
|
78
|
+
>
|
|
79
|
+
<div className="w-12 h-12 rounded-full overflow-hidden border border-border/50">
|
|
80
|
+
<img
|
|
81
|
+
src={`/${speaker.headshot}`}
|
|
82
|
+
alt={speaker.name}
|
|
83
|
+
className="w-full h-full object-cover"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
<div>
|
|
87
|
+
<p className="text-cream group-hover:text-gold transition-colors font-medium">
|
|
88
|
+
{talk.speaker}
|
|
89
|
+
</p>
|
|
90
|
+
<p className="text-cream/50 text-sm">{speaker.restaurant}</p>
|
|
91
|
+
</div>
|
|
92
|
+
</Link>
|
|
93
|
+
) : (
|
|
94
|
+
<div className="flex items-center gap-2 text-cream/70">
|
|
95
|
+
<User className="w-5 h-5 text-copper" />
|
|
96
|
+
<span>{talk.speaker}</span>
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{/* Duration */}
|
|
101
|
+
<div className="flex items-center gap-2 text-cream/60">
|
|
102
|
+
<Clock className="w-5 h-5 text-copper" />
|
|
103
|
+
<span className="text-lg">{talk.duration}</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Description content */}
|
|
108
|
+
<div className="prose prose-lg max-w-none prose-invert prose-p:text-cream/80 prose-headings:text-cream prose-headings:font-display prose-strong:text-cream prose-a:text-gold prose-li:text-cream/80 prose-ul:text-cream/80 font-body text-lg leading-relaxed pb-20">
|
|
109
|
+
<div
|
|
110
|
+
dangerouslySetInnerHTML={{ __html: marked(talk.content) }}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import { allTalks } from 'content-collections'
|
|
4
|
+
|
|
5
|
+
import TalkCard from '@/components/TalkCard'
|
|
6
|
+
import RemyAssistant from '@/components/RemyAssistant'
|
|
7
|
+
|
|
8
|
+
export const Route = createFileRoute('/talks/')({
|
|
9
|
+
component: TalksPage,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
function TalksPage() {
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<RemyAssistant />
|
|
16
|
+
<div className="min-h-screen">
|
|
17
|
+
{/* Hero section */}
|
|
18
|
+
<div className="relative py-16 px-6">
|
|
19
|
+
<div className="max-w-7xl mx-auto text-center">
|
|
20
|
+
<h1 className="font-display text-5xl md:text-6xl font-bold text-cream mb-4">
|
|
21
|
+
Conference <span className="text-gold italic">Sessions</span>
|
|
22
|
+
</h1>
|
|
23
|
+
<p className="text-xl text-cream/70 max-w-2xl mx-auto font-body">
|
|
24
|
+
Immerse yourself in masterclasses and demonstrations covering every aspect of artisan baking and pastry.
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
{/* Talks grid */}
|
|
30
|
+
<div className="max-w-7xl mx-auto px-6 pb-20">
|
|
31
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
32
|
+
{allTalks.map((talk) => (
|
|
33
|
+
<TalkCard key={talk.slug} talk={talk} />
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</>
|
|
39
|
+
)
|
|
40
|
+
}
|