Skip to content

Commit 0ea9272

Browse files
authored
fix(landing): polish product previews and featured media (#7599)
* fix(landing): polish product previews and featured media * fix(landing): restore color media and correct dark previews
1 parent 0a63dda commit 0ea9272

31 files changed

Lines changed: 329 additions & 253 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
@@ -52,7 +52,7 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
5252

5353
/**
5454
* The film streams only while the slide is on screen in a visible tab:
55-
* `play()` defeats `preload='metadata'`, so calling it at mount would pull
55+
* `play()` defeats `preload='none'`, so calling it at mount would pull
5656
* the whole file for a section well below the fold. Autoplay may still be
5757
* blocked, in which case the poster remains the fallback.
5858
*/
@@ -82,8 +82,8 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
8282
<article
8383
aria-hidden={!active}
8484
className={cn(
85-
'relative isolate size-full overflow-hidden',
86-
isFilm ? 'bg-[var(--text-primary)]' : 'bg-[var(--surface-4)]',
85+
'relative isolate size-full overflow-hidden [clip-path:border-box]',
86+
isFilm ? 'bg-black' : 'bg-[var(--surface-4)]',
8787
LANDING_STAGE_RADIUS
8888
)}
8989
>
@@ -103,7 +103,7 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
103103
loop
104104
muted
105105
playsInline
106-
preload='metadata'
106+
preload='none'
107107
src={story.media.src}
108108
tabIndex={-1}
109109
className='pointer-events-none absolute inset-0 size-full object-cover motion-reduce:hidden'

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

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,17 @@ describe('FeaturedCustomer', () => {
8585
expect(rivian.querySelector('video')?.getAttribute('src')).toBe(
8686
'/landing/customer-stories/rivian-r2-loop.mp4'
8787
)
88-
expect(expRealty.querySelector('video')).toBeNull()
88+
const expVideo = expRealty.querySelector('video') as HTMLVideoElement
89+
expect(expVideo.getAttribute('src')).toBe('/landing/customer-stories/exp-house-color-loop.mp4')
90+
expect(expVideo.getAttribute('preload')).toBe('none')
91+
expect(expVideo.muted).toBe(true)
92+
expect(vi.mocked(video.play).mock.contexts).not.toContain(expVideo)
8993
expect(
9094
[...expRealty.querySelectorAll('img')].map((image) => image.getAttribute('src'))
91-
).toEqual(['/landing/logos/exp-realty.svg'])
95+
).toEqual([
96+
'/landing/customer-stories/exp-house-color-poster.jpg',
97+
'/landing/logos/exp-realty.svg',
98+
])
9299

93100
const controls = nextButton.parentElement as HTMLElement
94101
expect(controls.className).toContain('justify-end')
@@ -122,6 +129,7 @@ describe('FeaturedCustomer', () => {
122129
expect(rivian.querySelector('article')?.getAttribute('aria-hidden')).toBe('true')
123130
expect(rivian.className).toContain('-translate-x-[calc(100%_+_1.5rem)]')
124131
expect(expRealty.getAttribute('aria-current')).toBe('true')
132+
expect(vi.mocked(video.play).mock.contexts).toContain(expVideo)
125133
expect(expRealty.querySelector('article')?.getAttribute('aria-hidden')).toBe('false')
126134
expect(expRealty.className).toContain('translate-x-0')
127135
expect(expRealty.className).toContain('opacity-100')
@@ -140,10 +148,15 @@ describe('FeaturedCustomer', () => {
140148
expect(expRealty.querySelector('img[alt="eXp Realty"]')).not.toBeNull()
141149
expect(host.querySelector('video')).toBe(video)
142150
expect(video.currentTime).toBe(12)
143-
expect(video.pause).toHaveBeenCalledOnce()
151+
expect(
152+
vi.mocked(video.pause).mock.contexts.filter((context) => context === video)
153+
).toHaveLength(1)
144154
expect(
145155
[...expRealty.querySelectorAll('img')].map((image) => image.getAttribute('src'))
146-
).toEqual(['/landing/logos/exp-realty.svg'])
156+
).toEqual([
157+
'/landing/customer-stories/exp-house-color-poster.jpg',
158+
'/landing/logos/exp-realty.svg',
159+
])
147160
expect(
148161
[...host.querySelectorAll('[data-testid="customer-tooltip"]')].map((node) => node.textContent)
149162
).toEqual(['View Rivian customer story'])
@@ -156,7 +169,9 @@ describe('FeaturedCustomer', () => {
156169
expect(expRealty.getAttribute('aria-current')).toBeNull()
157170
expect(rivian.querySelector('video')).toBe(video)
158171
expect(video.currentTime).toBe(12)
159-
expect(video.play).toHaveBeenCalledTimes(2)
172+
expect(vi.mocked(video.play).mock.contexts.filter((context) => context === video)).toHaveLength(
173+
2
174+
)
160175

161176
act(() => {
162177
root.unmount()
@@ -173,15 +188,19 @@ describe('FeaturedCustomer', () => {
173188
act(() => root.render(<FeaturedCustomer />))
174189
const video = host.querySelector('video') as HTMLVideoElement
175190
expect(video.play).not.toHaveBeenCalled()
176-
expect(video.pause).toHaveBeenCalledOnce()
191+
expect(
192+
vi.mocked(video.pause).mock.contexts.filter((context) => context === video)
193+
).toHaveLength(1)
177194

178195
motionPreference.reduced = false
179196
act(() => root.render(<FeaturedCustomer />))
180197
expect(video.play).toHaveBeenCalledOnce()
181198

182199
motionPreference.reduced = true
183200
act(() => root.render(<FeaturedCustomer />))
184-
expect(video.pause).toHaveBeenCalledTimes(2)
201+
expect(
202+
vi.mocked(video.pause).mock.contexts.filter((context) => context === video)
203+
).toHaveLength(2)
185204

186205
act(() => root.unmount())
187206
})

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ const CUSTOMER_STORIES: FeaturedCustomerStory[] = [
3535
id: 'exp-realty',
3636
company: 'eXp Realty',
3737
caption: 'Bring teams, shared knowledge, and AI agents into one workspace with Sim.',
38-
media: { kind: 'brand' },
38+
media: {
39+
kind: 'video',
40+
poster: '/landing/customer-stories/exp-house-color-poster.jpg',
41+
src: '/landing/customer-stories/exp-house-color-loop.mp4',
42+
alt: 'An eXp Realty sign and the exterior of a modern home',
43+
},
3944
logo: { src: '/landing/logos/exp-realty.svg', alt: 'eXp Realty', aspect: 1.84, height: 32 },
4045
},
4146
]

apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-resource-panel.tsx

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

3-
import { type ComponentType, useState } from 'react'
3+
import type { ComponentType } from 'react'
44
import {
55
Button,
66
cn,
@@ -21,6 +21,8 @@ import {
2121
Workflow,
2222
} from '@sim/emcn/icons'
2323
import { HeroWorkflowStage } from '@/app/(landing)/components/hero/components/hero-platform-loop/hero-workflow-stage'
24+
import type { LeadRecord } from '@/app/(landing)/tables/components/tables-records-preview/data'
25+
import { TablesRecordsTable } from '@/app/(landing)/tables/components/tables-records-preview/tables-records-table'
2426
import {
2527
RESOURCE_HEADER_CLASSES,
2628
RESOURCE_TAB_ICON_BUTTON_CLASS,
@@ -41,11 +43,11 @@ const HERO_RESOURCES: readonly HeroResourceDefinition[] = [
4143
{ id: 'brief', title: 'lead-enrichment-brief.md', icon: FileText },
4244
] as const
4345

44-
const TABLE_ROWS = [
45-
{ id: 'northstar', lead: 'Maya Chen', company: 'Northstar', score: '94', status: 'Qualified' },
46-
{ id: 'arcadia', lead: 'Jon Bell', company: 'Arcadia', score: '88', status: 'Qualified' },
47-
{ id: 'hearth', lead: 'Priya Shah', company: 'Hearth', score: '79', status: 'Review' },
48-
{ id: 'lattice', lead: 'Noah Kim', company: 'Lattice', score: '72', status: 'Review' },
46+
const TABLE_ROWS: readonly LeadRecord[] = [
47+
{ id: 'northstar', contact: 'Maya Chen', company: 'Northstar', score: 94, status: 'Qualified' },
48+
{ id: 'arcadia', contact: 'Jon Bell', company: 'Arcadia', score: 88, status: 'Qualified' },
49+
{ id: 'hearth', contact: 'Priya Shah', company: 'Hearth', score: 79, status: 'Review' },
50+
{ id: 'lattice', contact: 'Noah Kim', company: 'Lattice', score: 72, status: 'Review' },
4951
] as const
5052

5153
interface HeroResourcePanelProps {
@@ -60,52 +62,6 @@ interface HeroResourcePanelProps {
6062
onRunWorkflow: () => void
6163
}
6264

63-
function HeroTableResource() {
64-
const [selectedId, setSelectedId] = useState<string | null>('northstar')
65-
66-
return (
67-
<div className='flex h-full flex-col bg-[var(--bg)]'>
68-
<div className='flex h-10 shrink-0 items-center border-[var(--border)] border-b px-4'>
69-
<span className='text-[var(--text-body)] text-sm'>Qualified leads</span>
70-
<span className='ml-auto text-[var(--text-muted)] text-caption'>4 rows</span>
71-
</div>
72-
<div className='grid grid-cols-[1.25fr_1fr_72px_96px] border-[var(--border)] border-b bg-[var(--surface-1)] px-4 py-2 text-[var(--text-muted)] text-caption'>
73-
<span>Lead</span>
74-
<span>Company</span>
75-
<span>Score</span>
76-
<span>Status</span>
77-
</div>
78-
<div className='min-h-0 flex-1 overflow-y-auto'>
79-
{TABLE_ROWS.map((row) => (
80-
<button
81-
key={row.id}
82-
type='button'
83-
aria-pressed={selectedId === row.id}
84-
onClick={() => setSelectedId(row.id)}
85-
className={cn(
86-
'grid w-full grid-cols-[1.25fr_1fr_72px_96px] items-center border-[var(--border)] border-b px-4 py-3 text-left text-sm transition-colors hover-hover:bg-[var(--surface-hover)]',
87-
selectedId === row.id && 'bg-[var(--surface-active)]'
88-
)}
89-
>
90-
<span className='truncate text-[var(--text-body)]'>{row.lead}</span>
91-
<span className='truncate text-[var(--text-secondary)]'>{row.company}</span>
92-
<span className='text-[var(--text-body)] tabular-nums'>{row.score}</span>
93-
<span className='flex items-center gap-1.5 text-[var(--text-secondary)]'>
94-
<span
95-
className={cn(
96-
'size-1.5 rounded-full',
97-
row.status === 'Qualified' ? 'bg-[var(--brand-accent)]' : 'bg-[var(--text-muted)]'
98-
)}
99-
/>
100-
{row.status}
101-
</span>
102-
</button>
103-
))}
104-
</div>
105-
</div>
106-
)
107-
}
108-
10965
function HeroFileResource() {
11066
return (
11167
<div className='h-full overflow-y-auto bg-[var(--bg)]'>
@@ -237,7 +193,7 @@ export function HeroResourcePanel({
237193
{activeId === 'workflow' ? (
238194
<HeroWorkflowStage key={workflowKey} builtCount={builtCount} interactive />
239195
) : activeId === 'table' ? (
240-
<HeroTableResource />
196+
<TablesRecordsTable initialRows={TABLE_ROWS} />
241197
) : activeId === 'brief' ? (
242198
<HeroFileResource />
243199
) : (

apps/sim/app/(landing)/components/hero/components/hero-platform-loop/production-workflow-stage.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Circle, Minus, Plus, Square, Unlock } from '@sim/emcn/icons'
1414
import {
1515
CanvasSentenceView,
1616
InlineChip,
17+
useCanvasColorMode,
1718
WorkflowBlockView,
1819
WorkflowEdgeView,
1920
} from '@sim/workflow-renderer'
@@ -482,6 +483,7 @@ function ProductionWorkflowCanvas({
482483
scripted,
483484
viewportInset,
484485
}: ProductionWorkflowStageProps) {
486+
const colorMode = useCanvasColorMode()
485487
const { getNode, getViewport, setCenter, setViewport } = useReactFlow()
486488
const containerRef = useRef<HTMLDivElement>(null)
487489
const focusFrameRef = useRef(0)
@@ -738,6 +740,7 @@ function ProductionWorkflowCanvas({
738740
)}
739741
>
740742
<ReactFlow
743+
colorMode={colorMode}
741744
onInit={handleInit}
742745
nodes={nodes}
743746
edges={flowEdges}
@@ -762,7 +765,10 @@ function ProductionWorkflowCanvas({
762765
onNodeClick={interactive ? handleNodeClick : undefined}
763766
onNodeDragStart={interactive ? (_event, node) => setSelectedId(node.id) : undefined}
764767
proOptions={{ hideAttribution: true }}
765-
className={REACT_FLOW_STYLES}
768+
className={cn(
769+
REACT_FLOW_STYLES,
770+
interactive ? '[--xy-background-color:var(--bg)]' : '[--xy-background-color:transparent]'
771+
)}
766772
/>
767773
{interactive && <CanvasZoomControls />}
768774
{interactive && (

apps/sim/app/(landing)/components/navbar/components/mobile-nav/mobile-nav.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,6 @@ export function MobileNav({ stars }: MobileNavProps) {
145145
<span className='px-3 pt-2.5 pb-1 text-[13px] text-[var(--text-muted)]'>
146146
{menu.label}
147147
</span>
148-
{menu.index && (
149-
<Link
150-
href={menu.index.href}
151-
prefetch={open ? null : false}
152-
onClick={() => updateOpen(false)}
153-
className={SHEET_ROW}
154-
>
155-
{menu.index.label}
156-
</Link>
157-
)}
158148
{menu.sections.map((section) => (
159149
<div key={section.label} className='flex flex-col'>
160150
{section.items.map((item) => {

apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/models-menu-preview/models-menu-preview.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
import 'server-only'
2+
13
import { BrainCircuit, Search } from '@sim/emcn/icons'
2-
import { AnthropicIcon, GeminiIcon, OpenAIIcon } from '@/components/icons'
34
import { MenuPreviewFrame } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-frame'
45
import {
56
MenuPreviewHeader,
67
MenuPreviewToolbar,
78
} from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-header/menu-preview-header'
9+
import { MODEL_CATALOG_PROVIDERS } from '@/app/(landing)/models/utils'
810

9-
const MODELS = [
10-
{ name: 'GPT-6 Astra', provider: 'OpenAI', icon: OpenAIIcon },
11-
{ name: 'Claude Sonnet 5', provider: 'Anthropic', icon: AnthropicIcon },
12-
{ name: 'Gemini 3.6 Flash', provider: 'Google', icon: GeminiIcon },
13-
] as const
11+
const FEATURED_MODELS = MODEL_CATALOG_PROVIDERS.flatMap((provider) =>
12+
provider.models
13+
.filter((model) => model.featured)
14+
.map((model) => ({
15+
id: model.id,
16+
name: model.displayName,
17+
provider: provider.name,
18+
icon: provider.icon ?? BrainCircuit,
19+
}))
20+
)
1421

1522
/** A quiet model directory extending past the preview's right and bottom edges. */
1623
export function ModelsMenuPreview() {
@@ -25,8 +32,8 @@ export function ModelsMenuPreview() {
2532
</span>
2633
</MenuPreviewToolbar>
2734
<div className='divide-y divide-[var(--border)]'>
28-
{MODELS.map(({ name, provider, icon: Icon }) => (
29-
<div key={name} className='flex h-[76px] items-center gap-3 px-4'>
35+
{FEATURED_MODELS.map(({ id, name, provider, icon: Icon }) => (
36+
<div key={id} className='flex h-[76px] items-center gap-3 px-4'>
3037
<Icon className='size-7 shrink-0 text-[var(--text-primary)]' />
3138
<div>
3239
<p className='text-[var(--text-primary)] text-base'>{name}</p>

apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/nav-menu-preview.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ReactNode } from 'react'
12
import { ArrowRight, BookOpen } from '@sim/emcn/icons'
23
import { BlogMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/blog-menu-preview'
34
import { ChangelogMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/changelog-menu-preview'
@@ -8,7 +9,6 @@ import { IntegrationsMenuPreview } from '@/app/(landing)/components/navbar/compo
89
import { KnowledgeMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/knowledge-menu-preview'
910
import { LibraryMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/library-menu-preview'
1011
import { LogsMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/logs-menu-preview'
11-
import { ModelsMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/models-menu-preview'
1212
import { OverviewMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/overview-menu-preview'
1313
import { TablesMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/tables-menu-preview'
1414
import { WorkflowMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview'
@@ -19,14 +19,15 @@ import type {
1919

2020
interface NavMenuPreviewProps {
2121
item: NavMenuItemData
22+
modelsPreview: ReactNode
2223
}
2324

2425
interface PreviewGraphicProps {
2526
details: readonly [string, string, string]
2627
}
2728

2829
interface ProductGraphicProps extends PreviewGraphicProps {
29-
kind: NavMenuPreviewKind
30+
kind: Exclude<NavMenuPreviewKind, 'models'>
3031
}
3132

3233
const PREVIEW_ROW =
@@ -79,8 +80,6 @@ function PreviewGraphic({ kind, details }: ProductGraphicProps) {
7980
return <IntegrationsMenuPreview />
8081
case 'library':
8182
return <LibraryMenuPreview />
82-
case 'models':
83-
return <ModelsMenuPreview />
8483
case 'overview':
8584
return <OverviewMenuPreview />
8685
case 'workflows':
@@ -102,13 +101,17 @@ function PreviewGraphic({ kind, details }: ProductGraphicProps) {
102101
}
103102
}
104103

105-
export function NavMenuPreview({ item }: NavMenuPreviewProps) {
104+
export function NavMenuPreview({ item, modelsPreview }: NavMenuPreviewProps) {
106105
return (
107106
<div
108107
data-nav-menu-preview
109108
className='relative min-h-[340px] w-full self-stretch overflow-hidden rounded-[10px] bg-[var(--surface-3)]'
110109
>
111-
<PreviewGraphic kind={item.preview.kind} details={item.preview.details} />
110+
{item.preview.kind === 'models' ? (
111+
modelsPreview
112+
) : (
113+
<PreviewGraphic kind={item.preview.kind} details={item.preview.details} />
114+
)}
112115
</div>
113116
)
114117
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ export const RESOURCES_MENU: NavMenu = {
209209
*/
210210
export const CUSTOMERS_MENU: NavMenu = {
211211
label: 'Customers',
212-
index: { label: 'All customers', href: '/customers' },
213212
eyebrow: 'Customers',
214213
heading: 'Teams building with Sim',
215214
description: 'How enterprise teams build, govern, and operate AI agents with Sim.',

0 commit comments

Comments
 (0)