Skip to content

Commit 66ea6e3

Browse files
fix(docsite): stop mobile hero page-jump and polish the theme collage (#3401)
The home hero's theme reel re-skins the page on each swap. On mobile the card collage sits in normal flow, so a theme with different fonts/spacing changed the collage's height and reflowed the whole page every ~4.5s while autoplaying. - Reserve a stable, theme-independent height for the collage so a theme swap can't reflow the page, and move its top gap to a non-themed wrapper (page.tsx) so --hero-gap/--spacing resolve in the docsite scale rather than the active theme's. - Drop the earlier --spacing-* force-cast on the collage so every theme renders in its own authentic spacing again; the fixed height + bounded rows + flexible image cells absorb the per-theme size differences. - Keep auto-advance desktop-only; mobile stays manual (swipe + dots). - 2-col: the product/reward cards grow to fill the row (side row is content-sized) so the collage never leaves dead space. - 3-col: size the row to fit the tallest theme's side column and distribute the side items with space-between so it fills without spilling past the cards; hoist the radio + badge pills into that distribution so their gap matches the rest. - Pin the collage layout gaps (16px) and inner padding (24px) to constants so the column widths and gaps are identical across the 2-col/3-col breakpoints and across themes. - Round the collage images with --radius-container (was --radius-element) so they match the card corners (most visible on Matcha: 18px vs 12px). - Shorten the overly descriptive reel product names so the card titles fit on one line in the narrow 2-col cards without truncating (e.g. "Iced matcha latte" -> "Matcha", "Dried sea holly" -> "Sea holly", "Holo flip phone" -> "Phone"). - Use one consistent label ("Points") for the reward/progress card across all themes (was "Setup progress", "Loyalty perks", "Sparkle points", etc.). - Apply the same shortened names in the /themes showcase preview so the hero and the theme gallery stay consistent. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c891bf8 commit 66ea6e3

5 files changed

Lines changed: 110 additions & 68 deletions

File tree

apps/docsite/src/app/(site)/_landing/hero/HeroFloatingCards.tsx

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ const styles = stylex.create({
5151
'@media (min-width: 1024px)': 'block',
5252
},
5353
},
54-
// Narrow-screen collage grid. <768px: 2 columns (product|reward, side below).
55-
// 768–1024px: 3 columns (product | reward | side group). In flow inside the
56-
// fixed hero content, so it's pinned and sits --hero-gap below the text.
54+
// Narrow-screen collage grid. <768px: 2 cols (product|reward, side below);
55+
// 768–1023px: 3 cols. Fixed-size box (see height) so a swap can't reflow the page.
5756
collage: {
5857
display: {
5958
default: 'grid',
@@ -68,7 +67,7 @@ const styles = stylex.create({
6867
},
6968
gridTemplateColumns: {
7069
default: '1fr 1fr',
71-
'@media (min-width: 768px)': '1fr 1fr minmax(0, 240px)',
70+
'@media (min-width: 768px)': '1fr 1fr 1fr',
7271
},
7372
gridTemplateAreas: {
7473
default: '"product reward" "side side"',
@@ -78,33 +77,46 @@ const styles = stylex.create({
7877
alignItems: 'stretch',
7978
justifyContent: 'center',
8079
textAlign: 'start',
81-
columnGap: 'var(--spacing-4)',
82-
rowGap: 'var(--spacing-4)',
80+
// Literal, not var(--spacing-4): the collage renders inside <Theme> where
81+
// Matcha/Y2K scale --spacing ~1.5×, so a token would differ per theme.
82+
columnGap: 16,
83+
rowGap: 16,
84+
// Fixed height so a swap can't reflow the page (image cells absorb per-theme
85+
// differences). 420px at 768-1023px fits the tallest theme's side column.
86+
height: {
87+
default: '665px',
88+
'@media (min-width: 768px)': '420px',
89+
'@media (min-width: 1024px)': 'auto',
90+
},
91+
// 2-col: side row is content-sized (`auto`), top cards take the rest (`1fr`);
92+
// the fixed height means `auto` can't grow the box. 3-col: one row.
93+
gridTemplateRows: {
94+
default: 'minmax(0, 1fr) auto',
95+
'@media (min-width: 768px)': 'minmax(0, 1fr)',
96+
'@media (min-width: 1024px)': 'none',
97+
},
8398
width: '100%',
8499
maxWidth: {
85100
default: 520,
86101
'@media (min-width: 768px)': 1200,
87102
},
88103
marginInline: 'auto',
89-
paddingInline: 'var(--spacing-6)',
104+
// Literal, like the gaps above — a themed value would make column widths
105+
// differ per theme.
106+
paddingInline: 24,
90107
boxSizing: 'border-box',
91108
zIndex: 0,
92109
pointerEvents: 'none',
93110
},
94-
// Breaks the collage out of the 800px hero text column to span the viewport so
95-
// the inner grid can center at the shared 1200px content width.
111+
// Full-bleed wrapper so the inner grid can center at 1200px. Its top gap is
112+
// set on a non-themed wrapper in page.tsx (heroCollageGap).
96113
collageBleed: {
97114
display: {
98115
default: 'block',
99116
'@media (min-width: 1024px)': 'none',
100117
},
101118
width: '100vw',
102119
marginInline: 'calc(50% - 50vw)',
103-
// --hero-gap (96px) is too cavernous on phones; full gap returns at ≥768px.
104-
marginBlockStart: {
105-
default: 'var(--spacing-10)',
106-
'@media (min-width: 768px)': 'var(--hero-gap)',
107-
},
108120
},
109121
gaProduct: {
110122
gridArea: 'product',
@@ -120,26 +132,40 @@ const styles = stylex.create({
120132
minHeight: 0,
121133
textAlign: 'start',
122134
},
123-
// Side group: flex column of the small items with a uniform gap. alignSelf
124-
// start so it keeps its natural height (it's the row-height ruler).
135+
// Side column (buy card, composer, pills). 3-col: stretch + space-between so it
136+
// fills the fixed-height row. 2-col: content-sized row, so it keeps the 16px gap.
125137
gaSide: {
126138
gridArea: 'side',
127139
display: 'flex',
128140
flexDirection: 'column',
129-
alignSelf: 'start',
130-
gap: 'var(--spacing-3)',
141+
alignSelf: 'stretch',
142+
justifyContent: 'space-between',
143+
gap: 16,
131144
minWidth: 0,
145+
minHeight: 0,
132146
textAlign: 'start',
133147
},
134-
// Radio + badge on one row, shrink to content.
148+
// Radio + badge pills. 3-col: display:contents hoists them into the side
149+
// column's space-between distribution so their gap matches the cards. 2-col:
150+
// one centered row.
135151
collagePillRow: {
136-
display: 'flex',
152+
display: {
153+
default: 'flex',
154+
'@media (min-width: 768px)': 'contents',
155+
},
137156
flexDirection: 'row',
138157
alignItems: 'center',
139-
justifyContent: 'flex-start',
158+
justifyContent: 'center',
140159
flexWrap: 'wrap',
141160
gap: 'var(--spacing-2)',
142161
},
162+
// Left-align the pills when hoisted in 3-col (see collagePillRow); centered in 2-col.
163+
pillSelf: {
164+
alignSelf: {
165+
default: 'center',
166+
'@media (min-width: 768px)': 'flex-start',
167+
},
168+
},
143169
// Per-card reset in the collage: drop the overlap positioning + scaled pose.
144170
stackCard: {
145171
position: 'static',
@@ -150,19 +176,15 @@ const styles = stylex.create({
150176
collageProductBody: {
151177
height: '100%',
152178
},
153-
// Image fills the card height without adding intrinsic height (flexBasis:0 +
154-
// absolutely-filled img), so the side group stays the row-height ruler. minH
155-
// gives a real height in 2-col (own row, nothing to stretch it); 0 in 3-col.
179+
// Image fills the card height (flexBasis:0 + absolutely-filled img, minHeight:0)
180+
// so it's the flexible element that absorbs each theme's text-height differences.
156181
collageImageFill: {
157182
position: 'relative',
158183
flexGrow: 1,
159184
flexBasis: 0,
160-
minHeight: {
161-
default: 180,
162-
'@media (min-width: 768px)': 0,
163-
},
185+
minHeight: 0,
164186
overflow: 'hidden',
165-
borderRadius: 'var(--radius-element)',
187+
borderRadius: 'var(--radius-container)',
166188
},
167189
collageImageAbs: {
168190
position: 'absolute',
@@ -181,10 +203,7 @@ const styles = stylex.create({
181203
position: 'relative',
182204
flexGrow: 1,
183205
flexBasis: 0,
184-
minHeight: {
185-
default: 180,
186-
'@media (min-width: 768px)': 0,
187-
},
206+
minHeight: 0,
188207
overflow: 'hidden',
189208
},
190209
// Shared base for the overlap-layout floating cards.
@@ -212,7 +231,7 @@ const styles = stylex.create({
212231
// Shared image helpers.
213232
imageFrame: {
214233
overflow: 'hidden',
215-
borderRadius: 'var(--radius-element)',
234+
borderRadius: 'var(--radius-container)',
216235
},
217236
image: {
218237
width: '100%',
@@ -287,6 +306,10 @@ const styles = stylex.create({
287306
borderWidth: 0,
288307
boxShadow: 'none',
289308
whiteSpace: 'nowrap',
309+
alignSelf: {
310+
default: 'center',
311+
'@media (min-width: 768px)': 'flex-start',
312+
},
290313
},
291314
buyCard: {
292315
left: '80%',
@@ -300,7 +323,7 @@ const styles = stylex.create({
300323
height: 'var(--spacing-12)',
301324
flexShrink: 0,
302325
overflow: 'hidden',
303-
borderRadius: 'var(--radius-element)',
326+
borderRadius: 'var(--radius-container)',
304327
},
305328
fullWidth: {
306329
width: '100%',
@@ -337,6 +360,7 @@ export function HeroFloatingCards({
337360
variant="green"
338361
label={content.pills.leading}
339362
icon={<Sparkles size={12} />}
363+
xstyle={styles.pillSelf}
340364
/>
341365
);
342366

apps/docsite/src/app/(site)/_landing/hero/HeroThemeReel.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ export function HeroReelProvider({children}: {children: ReactNode}) {
229229
const [index, setIndex] = useState(0);
230230
const [paused, setPaused] = useState(false);
231231
const reduceMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
232+
// Auto-advance is desktop-only; on mobile the reel stays manual (swipe + dots)
233+
// so the cards don't move on their own while the user reads/scrolls.
234+
const isNarrow = useMediaQuery('(max-width: 1023px)');
232235
const {themeMode: userMode} = useThemeMode();
233236

234237
const goTo = useCallback(
@@ -282,14 +285,20 @@ export function HeroReelProvider({children}: {children: ReactNode}) {
282285
);
283286

284287
useEffect(() => {
285-
if (!AUTOPLAY_ENABLED || reduceMotion || paused || slides.length <= 1) {
288+
if (
289+
!AUTOPLAY_ENABLED ||
290+
reduceMotion ||
291+
isNarrow ||
292+
paused ||
293+
slides.length <= 1
294+
) {
286295
return;
287296
}
288297
const id = window.setInterval(() => {
289298
setIndex(i => (i + 1) % slides.length);
290299
}, ADVANCE_INTERVAL_MS);
291300
return () => window.clearInterval(id);
292-
}, [reduceMotion, paused, slides.length]);
301+
}, [reduceMotion, isNarrow, paused, slides.length]);
293302

294303
useEffect(() => {
295304
const onVisibility = () => setPaused(document.hidden);

apps/docsite/src/app/(site)/_landing/hero/heroThemeContent.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ const CONTENT_BY_THEME: Record<string, HeroThemeContent> = {
124124
pills: {leading: 'Limited time', trailing: 'Free shipping'},
125125
chatPrompt: 'How can I help?',
126126
reward: {
127-
label: 'Setup progress',
127+
label: 'Points',
128128
value: 7,
129129
total: 8,
130130
member: 'Astryx team',
@@ -150,7 +150,7 @@ const CONTENT_BY_THEME: Record<string, HeroThemeContent> = {
150150
pills: {leading: 'Limited time', trailing: 'Free shipping'},
151151
chatPrompt: 'How can I help?',
152152
reward: {
153-
label: 'Member rewards',
153+
label: 'Points',
154154
value: 6,
155155
total: 10,
156156
member: 'Alex Rivera',
@@ -170,17 +170,17 @@ const CONTENT_BY_THEME: Record<string, HeroThemeContent> = {
170170
},
171171
mini: {
172172
image: `${IMAGE_CDN}/Butter-Pancake.png`,
173-
title: 'Buttermilk pancakes',
173+
title: 'Pancakes',
174174
description: 'Stacked tall with melting butter.',
175175
},
176176
pills: {leading: 'Limited time', trailing: 'Free shipping'},
177177
chatPrompt: 'How can I help?',
178-
reward: {label: 'Loyalty perks', value: 5, total: 9, member: 'Noa Bright'},
178+
reward: {label: 'Points', value: 5, total: 9, member: 'Noa Bright'},
179179
},
180180
'@astryxdesign/theme-matcha': {
181181
product: {
182182
image: `${IMAGE_CDN}/matcha-product-1.png`,
183-
title: 'Iced matcha latte',
183+
title: 'Matcha',
184184
description: 'Stone-ground ceremonial matcha over cold milk.',
185185
price: '$6',
186186
},
@@ -197,7 +197,7 @@ const CONTENT_BY_THEME: Record<string, HeroThemeContent> = {
197197
pills: {leading: 'Limited time', trailing: 'Free shipping'},
198198
chatPrompt: 'How can I help?',
199199
reward: {
200-
label: 'Reward progress',
200+
label: 'Points',
201201
value: 7,
202202
total: 8,
203203
member: 'Lottie Wang',
@@ -206,7 +206,7 @@ const CONTENT_BY_THEME: Record<string, HeroThemeContent> = {
206206
'@astryxdesign/theme-gothic': {
207207
product: {
208208
image: `${IMAGE_CDN}/Gothic-1.png`,
209-
title: 'Dried sea holly',
209+
title: 'Sea holly',
210210
description: 'A single preserved thistle stem with a steely bloom.',
211211
price: '$24',
212212
},
@@ -217,17 +217,17 @@ const CONTENT_BY_THEME: Record<string, HeroThemeContent> = {
217217
},
218218
mini: {
219219
image: `${IMAGE_CDN}/Gothic-3.png`,
220-
title: 'Lilac ranunculus',
220+
title: 'Ranunculus',
221221
description: 'Layered petals in a soft mauve.',
222222
},
223223
pills: {leading: 'Limited time', trailing: 'Free shipping'},
224224
chatPrompt: 'How can I help?',
225-
reward: {label: 'Member rewards', value: 7, total: 8, member: 'Mara Vale'},
225+
reward: {label: 'Points', value: 7, total: 8, member: 'Mara Vale'},
226226
},
227227
'@astryxdesign/theme-y2k': {
228228
product: {
229229
image: `${IMAGE_CDN}/Y2K-Phone.png`,
230-
title: 'Holo flip phone',
230+
title: 'Phone',
231231
description: 'Iridescent clamshell with a rainbow screen.',
232232
price: '$18',
233233
},
@@ -238,12 +238,12 @@ const CONTENT_BY_THEME: Record<string, HeroThemeContent> = {
238238
},
239239
mini: {
240240
image: `${IMAGE_CDN}/Y2K-Butterfly.png`,
241-
title: 'Glitter butterfly',
241+
title: 'Butterfly',
242242
description: 'Sparkly stick-on in pastel chrome.',
243243
},
244244
pills: {leading: 'Limited time', trailing: 'Free shipping'},
245245
chatPrompt: 'How can I help?',
246-
reward: {label: 'Sparkle points', value: 6, total: 8, member: 'Bella Cruz'},
246+
reward: {label: 'Points', value: 6, total: 8, member: 'Bella Cruz'},
247247
},
248248
};
249249

@@ -262,7 +262,7 @@ function fallbackContent(name: string): HeroThemeContent {
262262
mini: {image, title: 'Featured', description: 'In stock now.'},
263263
pills: {leading: 'Limited time', trailing: 'Free shipping'},
264264
chatPrompt: 'How can I help?',
265-
reward: {label: 'Member rewards', value: 6, total: 10, member: 'Sam Lee'},
265+
reward: {label: 'Points', value: 6, total: 10, member: 'Sam Lee'},
266266
};
267267
}
268268

apps/docsite/src/app/(site)/page.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ const styles = stylex.create({
9898
maxWidth: 420,
9999
marginInline: 'auto',
100100
},
101+
// Top gap for the narrow-screen collage, set outside the reel's <Theme> so
102+
// --hero-gap/--spacing resolve in the docsite scale (constant across swaps).
103+
heroCollageGap: {
104+
marginBlockStart: {
105+
default: spacingVars['--spacing-10'],
106+
'@media (min-width: 768px)': 'var(--hero-gap)',
107+
},
108+
},
101109
// On dark slides the hero text switches to a light ink (headline/links inherit).
102110
heroTextDark: {
103111
color: 'var(--hero-on-dark)',
@@ -238,11 +246,12 @@ function HeroContent({contentRef}: {contentRef: Ref<HTMLElement>}) {
238246
</Link>
239247
</Text>
240248
</VStack>
241-
{/* Narrow-screen collage — rendered inside the (fixed) hero content so it's
242-
pinned with the text and sits a consistent --hero-gap below it. The
243-
desktop overlap layer (HeroReelCards) hides below 1024px; this
244-
self-hides at ≥1024px. */}
245-
<HeroReelStack />
249+
{/* Narrow-screen collage. The wrapper's gap is non-themed so it stays
250+
constant across theme swaps. Overlap layer (HeroReelCards) takes over
251+
≥1024px; this self-hides there. */}
252+
<div {...stylex.props(styles.heroCollageGap)}>
253+
<HeroReelStack />
254+
</div>
246255
{/* DarkScope flips the dot ink to the active slide's light/dark mode. */}
247256
<DarkScope isDark={isDark}>
248257
<div data-home-page="true" {...stylex.props(styles.heroDots)}>

0 commit comments

Comments
 (0)