Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust

/**
* The film streams only while the slide is on screen in a visible tab:
* `play()` defeats `preload='metadata'`, so calling it at mount would pull
* `play()` defeats `preload='none'`, so calling it at mount would pull
* the whole file for a section well below the fold. Autoplay may still be
* blocked, in which case the poster remains the fallback.
*/
Expand Down Expand Up @@ -82,8 +82,8 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
<article
aria-hidden={!active}
className={cn(
'relative isolate size-full overflow-hidden',
isFilm ? 'bg-[var(--text-primary)]' : 'bg-[var(--surface-4)]',
'relative isolate size-full overflow-hidden [clip-path:border-box]',
isFilm ? 'bg-black' : 'bg-[var(--surface-4)]',
LANDING_STAGE_RADIUS
)}
>
Expand All @@ -103,7 +103,7 @@ export function FeaturedCustomerCard({ story, active, emphasized }: FeaturedCust
loop
muted
playsInline
preload='metadata'
preload='none'
src={story.media.src}
tabIndex={-1}
className='pointer-events-none absolute inset-0 size-full object-cover motion-reduce:hidden'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,17 @@ describe('FeaturedCustomer', () => {
expect(rivian.querySelector('video')?.getAttribute('src')).toBe(
'/landing/customer-stories/rivian-r2-loop.mp4'
)
expect(expRealty.querySelector('video')).toBeNull()
const expVideo = expRealty.querySelector('video') as HTMLVideoElement
expect(expVideo.getAttribute('src')).toBe('/landing/customer-stories/exp-house-color-loop.mp4')
expect(expVideo.getAttribute('preload')).toBe('none')
expect(expVideo.muted).toBe(true)
expect(vi.mocked(video.play).mock.contexts).not.toContain(expVideo)
expect(
[...expRealty.querySelectorAll('img')].map((image) => image.getAttribute('src'))
).toEqual(['/landing/logos/exp-realty.svg'])
).toEqual([
'/landing/customer-stories/exp-house-color-poster.jpg',
'/landing/logos/exp-realty.svg',
])

const controls = nextButton.parentElement as HTMLElement
expect(controls.className).toContain('justify-end')
Expand Down Expand Up @@ -122,6 +129,7 @@ describe('FeaturedCustomer', () => {
expect(rivian.querySelector('article')?.getAttribute('aria-hidden')).toBe('true')
expect(rivian.className).toContain('-translate-x-[calc(100%_+_1.5rem)]')
expect(expRealty.getAttribute('aria-current')).toBe('true')
expect(vi.mocked(video.play).mock.contexts).toContain(expVideo)
expect(expRealty.querySelector('article')?.getAttribute('aria-hidden')).toBe('false')
expect(expRealty.className).toContain('translate-x-0')
expect(expRealty.className).toContain('opacity-100')
Expand All @@ -140,10 +148,15 @@ describe('FeaturedCustomer', () => {
expect(expRealty.querySelector('img[alt="eXp Realty"]')).not.toBeNull()
expect(host.querySelector('video')).toBe(video)
expect(video.currentTime).toBe(12)
expect(video.pause).toHaveBeenCalledOnce()
expect(
vi.mocked(video.pause).mock.contexts.filter((context) => context === video)
).toHaveLength(1)
expect(
[...expRealty.querySelectorAll('img')].map((image) => image.getAttribute('src'))
).toEqual(['/landing/logos/exp-realty.svg'])
).toEqual([
'/landing/customer-stories/exp-house-color-poster.jpg',
'/landing/logos/exp-realty.svg',
])
expect(
[...host.querySelectorAll('[data-testid="customer-tooltip"]')].map((node) => node.textContent)
).toEqual(['View Rivian customer story'])
Expand All @@ -156,7 +169,9 @@ describe('FeaturedCustomer', () => {
expect(expRealty.getAttribute('aria-current')).toBeNull()
expect(rivian.querySelector('video')).toBe(video)
expect(video.currentTime).toBe(12)
expect(video.play).toHaveBeenCalledTimes(2)
expect(vi.mocked(video.play).mock.contexts.filter((context) => context === video)).toHaveLength(
2
)

act(() => {
root.unmount()
Expand All @@ -173,15 +188,19 @@ describe('FeaturedCustomer', () => {
act(() => root.render(<FeaturedCustomer />))
const video = host.querySelector('video') as HTMLVideoElement
expect(video.play).not.toHaveBeenCalled()
expect(video.pause).toHaveBeenCalledOnce()
expect(
vi.mocked(video.pause).mock.contexts.filter((context) => context === video)
).toHaveLength(1)

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

motionPreference.reduced = true
act(() => root.render(<FeaturedCustomer />))
expect(video.pause).toHaveBeenCalledTimes(2)
expect(
vi.mocked(video.pause).mock.contexts.filter((context) => context === video)
).toHaveLength(2)

act(() => root.unmount())
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ const CUSTOMER_STORIES: FeaturedCustomerStory[] = [
id: 'exp-realty',
company: 'eXp Realty',
caption: 'Bring teams, shared knowledge, and AI agents into one workspace with Sim.',
media: { kind: 'brand' },
media: {
kind: 'video',
poster: '/landing/customer-stories/exp-house-color-poster.jpg',
src: '/landing/customer-stories/exp-house-color-loop.mp4',
alt: 'An eXp Realty sign and the exterior of a modern home',
},
logo: { src: '/landing/logos/exp-realty.svg', alt: 'eXp Realty', aspect: 1.84, height: 32 },
},
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { type ComponentType, useState } from 'react'
import type { ComponentType } from 'react'
import {
Button,
cn,
Expand All @@ -21,6 +21,8 @@ import {
Workflow,
} from '@sim/emcn/icons'
import { HeroWorkflowStage } from '@/app/(landing)/components/hero/components/hero-platform-loop/hero-workflow-stage'
import type { LeadRecord } from '@/app/(landing)/tables/components/tables-records-preview/data'
import { TablesRecordsTable } from '@/app/(landing)/tables/components/tables-records-preview/tables-records-table'
import {
RESOURCE_HEADER_CLASSES,
RESOURCE_TAB_ICON_BUTTON_CLASS,
Expand All @@ -41,11 +43,11 @@ const HERO_RESOURCES: readonly HeroResourceDefinition[] = [
{ id: 'brief', title: 'lead-enrichment-brief.md', icon: FileText },
] as const

const TABLE_ROWS = [
{ id: 'northstar', lead: 'Maya Chen', company: 'Northstar', score: '94', status: 'Qualified' },
{ id: 'arcadia', lead: 'Jon Bell', company: 'Arcadia', score: '88', status: 'Qualified' },
{ id: 'hearth', lead: 'Priya Shah', company: 'Hearth', score: '79', status: 'Review' },
{ id: 'lattice', lead: 'Noah Kim', company: 'Lattice', score: '72', status: 'Review' },
const TABLE_ROWS: readonly LeadRecord[] = [
{ id: 'northstar', contact: 'Maya Chen', company: 'Northstar', score: 94, status: 'Qualified' },
{ id: 'arcadia', contact: 'Jon Bell', company: 'Arcadia', score: 88, status: 'Qualified' },
{ id: 'hearth', contact: 'Priya Shah', company: 'Hearth', score: 79, status: 'Review' },
{ id: 'lattice', contact: 'Noah Kim', company: 'Lattice', score: 72, status: 'Review' },
] as const

interface HeroResourcePanelProps {
Expand All @@ -60,52 +62,6 @@ interface HeroResourcePanelProps {
onRunWorkflow: () => void
}

function HeroTableResource() {
const [selectedId, setSelectedId] = useState<string | null>('northstar')

return (
<div className='flex h-full flex-col bg-[var(--bg)]'>
<div className='flex h-10 shrink-0 items-center border-[var(--border)] border-b px-4'>
<span className='text-[var(--text-body)] text-sm'>Qualified leads</span>
<span className='ml-auto text-[var(--text-muted)] text-caption'>4 rows</span>
</div>
<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'>
<span>Lead</span>
<span>Company</span>
<span>Score</span>
<span>Status</span>
</div>
<div className='min-h-0 flex-1 overflow-y-auto'>
{TABLE_ROWS.map((row) => (
<button
key={row.id}
type='button'
aria-pressed={selectedId === row.id}
onClick={() => setSelectedId(row.id)}
className={cn(
'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)]',
selectedId === row.id && 'bg-[var(--surface-active)]'
)}
>
<span className='truncate text-[var(--text-body)]'>{row.lead}</span>
<span className='truncate text-[var(--text-secondary)]'>{row.company}</span>
<span className='text-[var(--text-body)] tabular-nums'>{row.score}</span>
<span className='flex items-center gap-1.5 text-[var(--text-secondary)]'>
<span
className={cn(
'size-1.5 rounded-full',
row.status === 'Qualified' ? 'bg-[var(--brand-accent)]' : 'bg-[var(--text-muted)]'
)}
/>
{row.status}
</span>
</button>
))}
</div>
</div>
)
}

function HeroFileResource() {
return (
<div className='h-full overflow-y-auto bg-[var(--bg)]'>
Expand Down Expand Up @@ -237,7 +193,7 @@ export function HeroResourcePanel({
{activeId === 'workflow' ? (
<HeroWorkflowStage key={workflowKey} builtCount={builtCount} interactive />
) : activeId === 'table' ? (
<HeroTableResource />
<TablesRecordsTable initialRows={TABLE_ROWS} />
) : activeId === 'brief' ? (
<HeroFileResource />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Circle, Minus, Plus, Square, Unlock } from '@sim/emcn/icons'
import {
CanvasSentenceView,
InlineChip,
useCanvasColorMode,
WorkflowBlockView,
WorkflowEdgeView,
} from '@sim/workflow-renderer'
Expand Down Expand Up @@ -482,6 +483,7 @@ function ProductionWorkflowCanvas({
scripted,
viewportInset,
}: ProductionWorkflowStageProps) {
const colorMode = useCanvasColorMode()
const { getNode, getViewport, setCenter, setViewport } = useReactFlow()
const containerRef = useRef<HTMLDivElement>(null)
const focusFrameRef = useRef(0)
Expand Down Expand Up @@ -738,6 +740,7 @@ function ProductionWorkflowCanvas({
)}
>
<ReactFlow
colorMode={colorMode}
onInit={handleInit}
nodes={nodes}
edges={flowEdges}
Expand All @@ -762,7 +765,10 @@ function ProductionWorkflowCanvas({
onNodeClick={interactive ? handleNodeClick : undefined}
onNodeDragStart={interactive ? (_event, node) => setSelectedId(node.id) : undefined}
proOptions={{ hideAttribution: true }}
className={REACT_FLOW_STYLES}
className={cn(
REACT_FLOW_STYLES,
interactive ? '[--xy-background-color:var(--bg)]' : '[--xy-background-color:transparent]'
)}
/>
{interactive && <CanvasZoomControls />}
{interactive && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,6 @@ export function MobileNav({ stars }: MobileNavProps) {
<span className='px-3 pt-2.5 pb-1 text-[13px] text-[var(--text-muted)]'>
{menu.label}
</span>
{menu.index && (
<Link
href={menu.index.href}
prefetch={open ? null : false}
onClick={() => updateOpen(false)}
className={SHEET_ROW}
>
{menu.index.label}
</Link>
)}
{menu.sections.map((section) => (
<div key={section.label} className='flex flex-col'>
{section.items.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import 'server-only'

import { BrainCircuit, Search } from '@sim/emcn/icons'
import { AnthropicIcon, GeminiIcon, OpenAIIcon } from '@/components/icons'
import { MenuPreviewFrame } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-frame'
import {
MenuPreviewHeader,
MenuPreviewToolbar,
} from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/menu-preview-header/menu-preview-header'
import { MODEL_CATALOG_PROVIDERS } from '@/app/(landing)/models/utils'

const MODELS = [
{ name: 'GPT-6 Astra', provider: 'OpenAI', icon: OpenAIIcon },
{ name: 'Claude Sonnet 5', provider: 'Anthropic', icon: AnthropicIcon },
{ name: 'Gemini 3.6 Flash', provider: 'Google', icon: GeminiIcon },
] as const
const FEATURED_MODELS = MODEL_CATALOG_PROVIDERS.flatMap((provider) =>
provider.models
.filter((model) => model.featured)
.map((model) => ({
id: model.id,
name: model.displayName,
provider: provider.name,
icon: provider.icon ?? BrainCircuit,
}))
)

/** A quiet model directory extending past the preview's right and bottom edges. */
export function ModelsMenuPreview() {
Expand All @@ -25,8 +32,8 @@ export function ModelsMenuPreview() {
</span>
</MenuPreviewToolbar>
<div className='divide-y divide-[var(--border)]'>
{MODELS.map(({ name, provider, icon: Icon }) => (
<div key={name} className='flex h-[76px] items-center gap-3 px-4'>
{FEATURED_MODELS.map(({ id, name, provider, icon: Icon }) => (
<div key={id} className='flex h-[76px] items-center gap-3 px-4'>
<Icon className='size-7 shrink-0 text-[var(--text-primary)]' />
<div>
<p className='text-[var(--text-primary)] text-base'>{name}</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ReactNode } from 'react'
import { ArrowRight, BookOpen } from '@sim/emcn/icons'
import { BlogMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/blog-menu-preview'
import { ChangelogMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/changelog-menu-preview'
Expand All @@ -8,7 +9,6 @@ import { IntegrationsMenuPreview } from '@/app/(landing)/components/navbar/compo
import { KnowledgeMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/knowledge-menu-preview'
import { LibraryMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/library-menu-preview'
import { LogsMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/logs-menu-preview'
import { ModelsMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/models-menu-preview'
import { OverviewMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/overview-menu-preview'
import { TablesMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/tables-menu-preview'
import { WorkflowMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview'
Expand All @@ -19,14 +19,15 @@ import type {

interface NavMenuPreviewProps {
item: NavMenuItemData
modelsPreview: ReactNode
}

interface PreviewGraphicProps {
details: readonly [string, string, string]
}

interface ProductGraphicProps extends PreviewGraphicProps {
kind: NavMenuPreviewKind
kind: Exclude<NavMenuPreviewKind, 'models'>
}

const PREVIEW_ROW =
Expand Down Expand Up @@ -79,8 +80,6 @@ function PreviewGraphic({ kind, details }: ProductGraphicProps) {
return <IntegrationsMenuPreview />
case 'library':
return <LibraryMenuPreview />
case 'models':
return <ModelsMenuPreview />
case 'overview':
return <OverviewMenuPreview />
case 'workflows':
Expand All @@ -102,13 +101,17 @@ function PreviewGraphic({ kind, details }: ProductGraphicProps) {
}
}

export function NavMenuPreview({ item }: NavMenuPreviewProps) {
export function NavMenuPreview({ item, modelsPreview }: NavMenuPreviewProps) {
return (
<div
data-nav-menu-preview
className='relative min-h-[340px] w-full self-stretch overflow-hidden rounded-[10px] bg-[var(--surface-3)]'
>
<PreviewGraphic kind={item.preview.kind} details={item.preview.details} />
{item.preview.kind === 'models' ? (
modelsPreview
) : (
<PreviewGraphic kind={item.preview.kind} details={item.preview.details} />
)}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ export const RESOURCES_MENU: NavMenu = {
*/
export const CUSTOMERS_MENU: NavMenu = {
label: 'Customers',
index: { label: 'All customers', href: '/customers' },
eyebrow: 'Customers',
heading: 'Teams building with Sim',
description: 'How enterprise teams build, govern, and operate AI agents with Sim.',
Expand Down
Loading
Loading