Skip to content

Commit 6322ac4

Browse files
fix(landing): refine product previews and customer media (#7608)
* fix(landing): refine product previews and customer media * fix(landing): preserve accessible controls and readable terminal previews --------- Co-authored-by: andresdjasso <andresdjasso@users.noreply.github.com> Co-authored-by: Waleed Latif <walif6@gmail.com>
1 parent 4e6ee86 commit 6322ac4

27 files changed

Lines changed: 1223 additions & 500 deletions

File tree

apps/sim/app/(landing)/components/featured-customer/featured-customer-card.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
8282
<article
8383
aria-hidden={!active}
8484
className={cn(
85-
'relative isolate size-full overflow-hidden [clip-path:border-box]',
85+
'relative isolate size-full overflow-hidden [clip-path:inset(0_round_12px)]',
8686
isFilm ? 'bg-black' : 'bg-[var(--surface-4)]',
8787
LANDING_STAGE_RADIUS
8888
)}
@@ -95,7 +95,7 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
9595
fill
9696
sizes='(max-width: 640px) 100vw, 1728px'
9797
quality={90}
98-
className='object-cover'
98+
className='rounded-[inherit] object-cover'
9999
/>
100100
<video
101101
ref={videoRef}
@@ -106,9 +106,9 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
106106
preload='none'
107107
src={story.media.src}
108108
tabIndex={-1}
109-
className='pointer-events-none absolute inset-0 size-full object-cover motion-reduce:hidden'
109+
className='pointer-events-none absolute inset-0 size-full rounded-[inherit] object-cover motion-reduce:hidden'
110110
/>
111-
<div className='absolute inset-0 bg-[linear-gradient(135deg,rgba(0,0,0,0.68)_0%,rgba(0,0,0,0.3)_36%,rgba(0,0,0,0.08)_70%)]' />
111+
<div className='pointer-events-none absolute inset-0 rounded-[inherit] bg-[linear-gradient(135deg,rgba(0,0,0,0.68)_0%,rgba(0,0,0,0.3)_36%,rgba(0,0,0,0.08)_70%)]' />
112112
</>
113113
)}
114114

apps/sim/app/(landing)/components/features/components/core-feature-card/core-feature-card.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface CoreFeatureCardProps {
88
description: string
99
href: string
1010
visual: ReactNode
11+
interactiveVisual?: boolean
1112
tone?: 'light' | 'mid' | 'dark'
1213
}
1314

@@ -33,35 +34,39 @@ const SCROLL_THROUGH = 'overscroll-x-auto [&_*]:overscroll-x-auto'
3334

3435
/**
3536
* One homepage product module: a tall product-UI stage followed by a compact,
36-
* independently quotable title and description. The whole module is a real
37-
* route link, while the illustration remains decorative and non-interactive.
37+
* independently quotable title and description. Interactive illustrations sit outside the route link;
38+
* decorative illustrations remain part of the linked module.
3839
*/
3940
export function CoreFeatureCard({
4041
title,
4142
description,
4243
href,
4344
visual,
45+
interactiveVisual = false,
4446
tone = 'light',
4547
}: CoreFeatureCardProps) {
48+
const graphic = (
49+
<div
50+
aria-hidden={interactiveVisual ? undefined : true}
51+
className={cn(
52+
'relative aspect-[5/6] overflow-hidden border border-[var(--border)] transition-colors duration-300 group-hover:border-[var(--border)] motion-reduce:transition-none',
53+
LANDING_STAGE_RADIUS,
54+
SCROLL_THROUGH,
55+
TONE_CLASSES[tone]
56+
)}
57+
>
58+
<div className='absolute inset-0'>{visual}</div>
59+
</div>
60+
)
4661
return (
4762
<article className='min-w-0'>
63+
{interactiveVisual ? graphic : null}
4864
<Link
4965
href={href}
5066
rel={href.startsWith('https://') ? 'noopener noreferrer' : undefined}
5167
className='group block outline-none focus-visible:outline focus-visible:outline-1 focus-visible:outline-[var(--border)] focus-visible:outline-offset-4'
5268
>
53-
<div
54-
aria-hidden='true'
55-
className={cn(
56-
'relative aspect-[5/6] overflow-hidden border border-[var(--border)] transition-colors duration-300 group-hover:border-[var(--border)] motion-reduce:transition-none',
57-
LANDING_STAGE_RADIUS,
58-
SCROLL_THROUGH,
59-
TONE_CLASSES[tone]
60-
)}
61-
>
62-
<div className='absolute inset-0'>{visual}</div>
63-
</div>
64-
69+
{interactiveVisual ? null : graphic}
6570
<h3 className='mt-5 text-[20px] text-[var(--text-primary)] leading-[1.25] tracking-[-0.01em]'>
6671
{title}
6772
</h3>

apps/sim/app/(landing)/components/features/components/features-rail/features-rail.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,39 @@ describe('FeaturesRail', () => {
145145
expect(mount(true).scrollLeft).toBe(SET)
146146
})
147147

148+
it('keeps interactive controls keyboard-accessible only in the home copy', () => {
149+
let activations = 0
150+
act(() =>
151+
root?.render(
152+
<FeaturesRail label='Interactive features'>
153+
<div>
154+
<button type='button' onClick={() => activations++}>
155+
Open folder
156+
</button>
157+
<input aria-label='Search files' />
158+
</div>
159+
</FeaturesRail>
160+
)
161+
)
162+
163+
const homeControls = host?.querySelectorAll<HTMLElement>(
164+
'[data-copy="home"] :is(button, input)'
165+
)
166+
expect(homeControls).toHaveLength(2)
167+
for (const control of homeControls ?? []) expect(control.tabIndex).toBe(0)
168+
169+
const cloneControls = host?.querySelectorAll<HTMLElement>(
170+
':is([data-copy="lead"], [data-copy="tail"]) :is(button, input)'
171+
)
172+
expect(cloneControls).toHaveLength(4)
173+
for (const control of cloneControls ?? []) expect(control.tabIndex).toBe(-1)
174+
175+
const cloneButton = host?.querySelector<HTMLButtonElement>('[data-copy="tail"] button')
176+
expect(cloneButton?.disabled).toBe(false)
177+
act(() => cloneButton?.click())
178+
expect(activations).toBe(1)
179+
})
180+
148181
it('drags with the mouse, scrolling by the pointer delta and swallowing the click', () => {
149182
const rail = mount()
150183
const link = rail.querySelector<HTMLAnchorElement>('[data-copy="home"] a')

apps/sim/app/(landing)/components/features/components/features-rail/features-rail.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,10 @@ export function FeaturesRail({ label, children }: FeaturesRailProps) {
142142
if (Math.abs(next - rail.scrollLeft) > FOLD_TOLERANCE) rail.scrollLeft = next
143143
}
144144

145-
for (const link of rail.querySelectorAll<HTMLElement>(
146-
'[data-copy="lead"] a, [data-copy="tail"] a'
145+
for (const control of rail.querySelectorAll<HTMLElement>(
146+
':is([data-copy="lead"], [data-copy="tail"]) :is(a, button, input, select, textarea, [tabindex])'
147147
)) {
148-
link.tabIndex = -1
148+
control.tabIndex = -1
149149
}
150150
measure()
151151
rail.scrollLeft = foldScrollLeft(rail.scrollLeft + setWidthRef.current, setWidthRef.current)

apps/sim/app/(landing)/components/features/features.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const CORE_FEATURES = [
5353
href: '/files',
5454
tone: 'mid',
5555
visual: <FileLibraryGraphic />,
56+
interactiveVisual: true,
5657
},
5758
{
5859
title: 'Logs',

apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useEffect, useRef, useState } from 'react'
3+
import { type ReactNode, useEffect, useRef, useState } from 'react'
44
import {
55
Button,
66
Check,
@@ -35,6 +35,8 @@ interface StageBlockCardProps {
3535
selected?: boolean
3636
/** Keeps the production selection toolbar visible in a noninteractive graphic. */
3737
decorative?: boolean
38+
/** Optional animated run glyph for a decorative, synchronized scene. */
39+
decorativeRunIcon?: ReactNode
3840
runStatus?: 'idle' | 'running' | 'complete'
3941
onSelect?: (blockId: string) => void
4042
onRunToggle?: (blockId: string) => void
@@ -201,6 +203,7 @@ export function StageBlockCard({
201203
orientation = 'vertical',
202204
selected = false,
203205
decorative = false,
206+
decorativeRunIcon,
204207
runStatus = 'idle',
205208
onSelect,
206209
onRunToggle,
@@ -291,7 +294,9 @@ export function StageBlockCard({
291294
onRunToggle?.(block.id)
292295
}}
293296
>
294-
{running ? (
297+
{decorative && decorativeRunIcon ? (
298+
decorativeRunIcon
299+
) : running ? (
295300
<RunningActionIcon />
296301
) : complete ? (
297302
<Check className='size-[14px]' />
@@ -316,17 +321,19 @@ export function StageBlockCard({
316321
variant='ghost'
317322
size={null}
318323
disabled={!decorative}
319-
aria-label={`${label} unavailable in preview`}
320-
className={cn(
321-
'pointer-events-none',
322-
getActionButtonClassName(label === 'Delete' ? 'last' : 'middle', selected)
324+
aria-label={decorative ? label : `${label} unavailable in preview`}
325+
className={getActionButtonClassName(
326+
label === 'Delete' ? 'last' : 'middle',
327+
selected
323328
)}
324329
>
325330
<Icon className='size-[14px]' />
326331
</Button>
327332
</span>
328333
</Tooltip.Trigger>
329-
<Tooltip.Content side='top'>{label} is unavailable in preview</Tooltip.Content>
334+
<Tooltip.Content side='top'>
335+
{decorative ? label : `${label} unavailable in preview`}
336+
</Tooltip.Content>
330337
</Tooltip.Root>
331338
))}
332339
</div>

apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/constants.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,14 @@ describe('navbar customers menu', () => {
1919
])
2020
expect(CUSTOMERS_MENU.sections[0].items.map((item) => item.card?.tone)).toEqual([
2121
'dark',
22-
'light',
22+
'dark',
2323
])
24-
expect(CUSTOMERS_MENU.sections[0].items[0].card?.background?.src).toMatch(
25-
/^\/landing\/customers\/.+\.jpg$/
26-
)
2724
expect(CUSTOMERS_MENU.sections[0].items.map((item) => item.href)).toEqual([
2825
'/customers/rivian',
2926
'/customers/exp-realty',
3027
])
3128
for (const item of CUSTOMERS_MENU.sections[0].items) {
29+
expect(item.card?.background?.src).toMatch(/^\/landing\/customers\/.+\.jpg$/)
3230
expect(item.card?.imageSrc).toMatch(/^\/landing\/logos\/.+\.svg$/)
3331
expect(item.card?.aspect).toBeGreaterThan(0)
3432
}

apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ export const CUSTOMERS_MENU: NavMenu = {
248248
imageAlt: 'eXp Realty',
249249
aspect: 1.84,
250250
height: 44,
251-
tone: 'light',
251+
tone: 'dark',
252+
background: { src: '/landing/customers/exp-beach-house.jpg' },
252253
},
253254
preview: {
254255
kind: 'resource',

apps/sim/app/(landing)/components/security/components/workspace-controls/workspace-controls.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ const CONTROL_QUIET = 'text-[var(--text-secondary)]'
3131
*/
3232
const CONTROL_COPY = 'text-[15px] leading-[1.45]'
3333

34-
/**
35-
* The rail and controls share a group in `Landing`. Its positive gap provides
36-
* breathing room above this inset rule; the padding balances the space below.
37-
*/
38-
const CONTROL_RULE = 'border-[var(--border)] border-t pt-16 max-sm:pt-12 max-lg:pt-14'
39-
4034
interface Control {
4135
title: string
4236
description: string
@@ -76,26 +70,22 @@ const CONTROLS: readonly Control[] = [
7670
},
7771
] as const
7872

79-
/**
80-
* Operational controls as a heading-less white spec grid on the page measure,
81-
* six items over two even rows. It reads as the plain-language index of the
82-
* feature rail it follows, so it carries no title of its own: the section is
83-
* named by `aria-label`, and the items stay `h3` under the rail's `h2` rather
84-
* than becoming six competing section headings. Certifications and the
85-
* governance intro live in `Security`, a separate beat further down the page.
86-
*/
73+
/** Six workspace capabilities grouped in a shared panel below the product rail. */
8774
export function WorkspaceControls() {
8875
return (
8976
<section
9077
id='controls'
9178
aria-label='Workspace controls'
9279
className={cn('flex w-full flex-col', LANDING_CONTENT_WIDTH, LANDING_GUTTER)}
9380
>
94-
<div className={cn(HOME_INSET, CONTROL_RULE)}>
95-
<ul className='grid grid-cols-3 gap-x-16 gap-y-20 max-sm:grid-cols-1 max-sm:gap-y-12 max-lg:grid-cols-2 max-lg:gap-y-16'>
81+
<div className={cn(HOME_INSET, 'pt-12 max-sm:pt-8')}>
82+
<ul className='grid grid-cols-3 gap-px overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--border)] max-sm:grid-cols-1 max-lg:grid-cols-2'>
9683
{CONTROLS.map(({ title, description, Mark }) => (
97-
<li key={title} className='flex flex-col items-start gap-3'>
98-
<Mark className={cn('size-[56px] max-sm:size-[48px]', CONTROL_QUIET)} />
84+
<li
85+
key={title}
86+
className='flex flex-col items-start gap-3 bg-[var(--surface-2)] p-7 max-sm:p-6'
87+
>
88+
<Mark className={cn('size-[56px]', CONTROL_QUIET)} />
9989
<div>
10090
<h3 className={cn('text-[var(--text-primary)]', HOME_TYPE.body)}>{title}</h3>
10191
<p className={cn('mt-1.5 max-w-[30ch] text-pretty', CONTROL_QUIET, CONTROL_COPY)}>

0 commit comments

Comments
 (0)