chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,30 @@
'use client'
import { ChipLink } from '@sim/emcn'
import { GithubOutlineIcon } from '@/components/icons'
/**
* GitHub repository link - icon + star count, as on the old landing.
*
* Client leaf only so the icon component can be passed as a prop; the
* star count itself is fetched server-side and arrives as a string.
*/
interface GitHubChipProps {
/** Formatted star count (e.g. "28.8k"). */
stars: string
}
export function GitHubChip({ stars }: GitHubChipProps) {
return (
<ChipLink
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
leftIcon={GithubOutlineIcon}
aria-label={`GitHub repository, ${stars} stars`}
>
{stars}
</ChipLink>
)
}
@@ -0,0 +1 @@
export { GitHubChip } from './github-chip'
@@ -0,0 +1,13 @@
export { GitHubChip } from './github-chip'
export { LogoMark } from './logo-mark'
export { MobileNav } from './mobile-nav'
export type { NavMenu, NavMenuItemData } from './nav-menu-chip'
export {
NAV_MENUS,
NavMenuChip,
PLATFORM_MENU,
RESOURCES_MENU,
SOLUTIONS_MENU,
} from './nav-menu-chip'
export { NavbarShell } from './navbar-shell'
export { SimWordmark } from './sim-wordmark'
@@ -0,0 +1 @@
export { LogoMark } from './logo-mark'
@@ -0,0 +1,67 @@
'use client'
import { type CSSProperties, type ReactNode, useState } from 'react'
import { cn } from '@sim/emcn'
import { ThinkingLoader } from '@/components/ui'
interface LogoMarkProps {
/** Server-rendered Sim wordmark, shown by default. */
children: ReactNode
}
/**
* Loader ink that matches the wordmark - keeps the loader's default radial gloss
* (center darker, edge lifted ~24% toward white, the same relative step as the
* stock `#2c2c2c → #5f5f5f`) but recentres it on `var(--text-body)`, the
* navbar's text color, so each blob's center matches the static "sim" mark it
* dissolves from. Glow off so nothing over-lightens the silhouette.
*/
const LOADER_INK = {
'--tl-grad-inner': 'var(--text-body)',
'--tl-grad-outer': 'color-mix(in srgb, var(--text-body) 76%, #fff)',
'--tl-glow': 'transparent',
} as CSSProperties
/**
* Navbar logo with a hover easter egg: the static "sim" wordmark dissolves into
* the cycling thinking loader, inked to match the wordmark's solid
* `--text-body` fill - so the mark appears to come alive in the same
* material. The wordmark stays server-rendered (passed as children) and
* crawlable; only this hover shell is client. The loader mounts on hover (no
* idle timers) and sits behind the wordmark, revealed as the wordmark fades.
*/
export function LogoMark({ children }: LogoMarkProps) {
const [hovered, setHovered] = useState(false)
return (
<span
className='relative inline-flex items-center'
// Transform + its transition are inline on purpose: Tailwind's
// `transition-transform` utility (its var-based transform composition)
// prevents the scale from applying on this element, so the transition is
// declared directly. This is the sanctioned dynamic, state-driven-value
// exception - do not move it back to a `transition-transform`/`scale-*`
// class.
style={{
transition: 'transform 150ms cubic-bezier(0.23, 1, 0.32, 1)',
transform: hovered ? 'scale(1.08)' : undefined,
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<span
className={cn(
'relative z-10 transition-opacity duration-100 ease-[cubic-bezier(0.23,1,0.32,1)]',
hovered && 'opacity-0'
)}
>
{children}
</span>
{hovered ? (
<span aria-hidden className='absolute inset-0 z-0 flex items-center justify-center'>
<ThinkingLoader size={28} startVariant='corners' style={LOADER_INK} />
</span>
) : null}
</span>
)
}
@@ -0,0 +1 @@
export { MobileNav } from './mobile-nav'
@@ -0,0 +1,183 @@
'use client'
import { useEffect, useState } from 'react'
import { ChipLink, cn } from '@sim/emcn'
import { Menu, X } from 'lucide-react'
import Link from 'next/link'
import { GithubOutlineIcon } from '@/components/icons'
import { NAV_MENUS } from '@/app/(landing)/components/navbar/components/nav-menu-chip'
import {
NAVBAR_GLASS_SURFACE,
useNavbarFrost,
} from '@/app/(landing)/components/navbar/components/navbar-shell'
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
/**
* Mobile navigation - the `< lg` counterpart to the desktop nav clusters.
*
* Renders an always-visible "Sign up" chip plus a hamburger that toggles a
* full-width slide-down sheet anchored under the bar (`top-full`). The sheet
* expands the desktop mega-menus into grouped link sections plus the auth CTAs,
* stacked on the shared {@link NAVBAR_GLASS_SURFACE} so it reads as one frosted
* panel with the bar (which frosts in sync via {@link useNavbarFrost}). Only this
* leaf hydrates; the desktop nav stays server-rendered.
*
* The sheet locks body scroll while open and closes on route change intent
* (any link tap) and on `Escape`. Motion is a short token-driven
* transform/opacity that collapses under `prefers-reduced-motion`.
*/
interface MobileNavProps {
/** Formatted GitHub star count (e.g. "28.8k"). */
stars: string
}
/**
* Standalone top-level routes shown in the sheet alongside the expanded mega-menu
* sections. Pricing is a standalone link on the desktop nav (not a mega-menu), so
* it stays a single row here too. Restoring `PLATFORM_MENU`/`SOLUTIONS_MENU` to
* {@link NAV_MENUS} automatically expands them here as grouped sections too - the
* sheet mirrors the desktop nav's information architecture with no extra edit.
*/
const STANDALONE_LINKS = [
{ label: 'Enterprise', href: '/enterprise' },
{ label: 'Pricing', href: '/pricing' },
] as const
/** Shared row chrome for every tappable text link in the sheet. */
const SHEET_ROW =
'rounded-lg px-3 py-2.5 text-[15px] text-[var(--text-body)] transition-colors hover:bg-[var(--surface-hover)]'
export function MobileNav({ stars }: MobileNavProps) {
const frost = useNavbarFrost()
const [open, setOpen] = useState(false)
useEffect(() => {
if (!open) return
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && setOpen(false)
document.body.style.overflow = 'hidden'
document.addEventListener('keydown', onKey)
return () => {
document.body.style.overflow = ''
document.removeEventListener('keydown', onKey)
}
}, [open])
// Report open state to the shell so the sticky bar frosts in sync with the
// sheet - the two then read as one continuous glass panel.
useEffect(() => {
frost?.setMenuOpen(open)
}, [open, frost])
return (
<div className='ml-auto flex items-center gap-2 lg:hidden'>
<ChipLink variant='primary' href={SIGNUP_HREF} prefetch={false}>
Sign up
</ChipLink>
<button
type='button'
aria-label={open ? 'Close menu' : 'Open menu'}
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
className='flex size-[30px] items-center justify-center rounded-lg border border-[var(--border-1)] text-[var(--text-icon)] transition-colors hover:bg-[var(--surface-hover)]'
>
{open ? <X className='size-[18px]' /> : <Menu className='size-[18px]' />}
</button>
{open ? (
<button
type='button'
aria-hidden='true'
tabIndex={-1}
onClick={() => setOpen(false)}
className='fixed inset-0 top-full z-40 cursor-default bg-[color-mix(in_srgb,var(--text-primary)_8%,transparent)]'
/>
) : null}
<div
className={cn(
'absolute top-full right-0 left-0 z-50 origin-top border-[var(--border)] border-b transition-[opacity,transform] duration-200 motion-reduce:transition-none',
NAVBAR_GLASS_SURFACE,
open
? 'pointer-events-auto translate-y-0 opacity-100'
: '-translate-y-2 pointer-events-none opacity-0'
)}
>
<div className='mx-auto flex w-full max-w-[1460px] flex-col gap-1 px-5 pt-2 pb-5'>
{NAV_MENUS.map((menu) => (
<div key={menu.label} className='flex flex-col'>
<span className='px-3 pt-2.5 pb-1 text-[13px] text-[var(--text-muted)]'>
{menu.label}
</span>
{menu.items.map((item) =>
item.external ? (
<a
key={item.title}
href={item.href}
target='_blank'
rel='noopener noreferrer'
onClick={() => setOpen(false)}
className={SHEET_ROW}
>
{item.title}
</a>
) : (
<Link
key={item.title}
href={item.href}
onClick={() => setOpen(false)}
className={SHEET_ROW}
>
{item.title}
</Link>
)
)}
</div>
))}
{STANDALONE_LINKS.map(({ label, href }) => (
<Link key={label} href={href} onClick={() => setOpen(false)} className={SHEET_ROW}>
{label}
</Link>
))}
<a
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
onClick={() => setOpen(false)}
className={cn('flex items-center gap-2', SHEET_ROW)}
>
<GithubOutlineIcon className='size-[16px] text-[var(--text-icon)]' />
<span>GitHub</span>
<span className='text-[var(--text-muted)]'>{stars}</span>
</a>
<div className='mt-3 flex flex-col gap-2'>
<ChipLink
variant='border'
href='/login'
fullWidth
flush
prefetch={false}
className='h-[40px] justify-center [&>span]:flex-none'
onClick={() => setOpen(false)}
>
Log in
</ChipLink>
<ChipLink
variant='primary'
href={DEMO_HREF}
fullWidth
flush
className='h-[40px] justify-center [&>span]:flex-none'
onClick={() => setOpen(false)}
>
Contact sales
</ChipLink>
</div>
</div>
</div>
</div>
)
}
@@ -0,0 +1 @@
export { NavMenuItem } from './nav-menu-item'
@@ -0,0 +1,62 @@
import { ArrowRight } from '@sim/emcn/icons'
import Link from 'next/link'
import type { NavMenuItemData } from '@/app/(landing)/components/navbar/components/nav-menu-chip/types'
/**
* One row inside a navbar mega-menu panel - a title, a one-line description, and
* a right-arrow that slides in on hover.
*
* The row is its own `group/item`, so the arrow reveal (`opacity-0
* -translate-x-1` → visible on `group-hover/item` and `group-focus-visible/item`)
* is pure CSS, scoped to the hovered or keyboard-focused row and never to the
* whole panel. `onSelect` fires on click so the parent menu can close itself.
*
* Internal routes render a crawlable Next {@link Link}; `external` items render
* a new-tab `<a>` with `rel='noopener noreferrer'`.
*/
interface NavMenuItemProps {
item: NavMenuItemData
/** Called when the row is activated, so the parent menu can close. */
onSelect?: () => void
}
const ROW_CLASS =
'group/item flex items-center gap-2.5 rounded-lg p-2 text-left transition-colors hover-hover:bg-[var(--surface-active)] focus-visible:bg-[var(--surface-active)] focus-visible:outline-none'
const TITLE_CLASS = 'truncate text-[14px] text-[var(--text-body)]'
const DESC_CLASS = 'text-[12px] text-[var(--text-muted)] leading-snug'
const ARROW_CLASS =
'size-4 flex-shrink-0 -translate-x-1 text-[var(--text-icon)] opacity-0 transition-all group-hover/item:translate-x-0 group-hover/item:opacity-100 group-focus-visible/item:translate-x-0 group-focus-visible/item:opacity-100 motion-reduce:transition-none'
export function NavMenuItem({ item, onSelect }: NavMenuItemProps) {
const { title, description, href, external } = item
const content = (
<>
<span className='flex min-w-0 flex-1 flex-col'>
<span className={TITLE_CLASS}>{title}</span>
<span className={DESC_CLASS}>{description}</span>
</span>
<ArrowRight className={ARROW_CLASS} aria-hidden='true' />
</>
)
if (external) {
return (
<a
href={href}
target='_blank'
rel='noopener noreferrer'
onClick={onSelect}
className={ROW_CLASS}
>
{content}
</a>
)
}
return (
<Link href={href} onClick={onSelect} className={ROW_CLASS}>
{content}
</Link>
)
}
@@ -0,0 +1,133 @@
import type { NavMenu } from '@/app/(landing)/components/navbar/components/nav-menu-chip/types'
/**
* The Platform menu - Sim's modules. Six items in a three-column grid. Each
* description names the outcome the module unlocks for your agents.
*/
export const PLATFORM_MENU: NavMenu = {
label: 'Platform',
items: [
{
title: 'Mothership',
description: 'Build and command agents in natural language',
href: '/',
},
{
title: 'Workflows',
description: 'Design agent logic visually',
href: '/workflows',
},
{
title: 'Knowledge Base',
description: 'Give agents memory of your data',
href: '/knowledge',
},
{
title: 'Tables',
description: 'Power agents with structured data',
href: '/tables',
},
{
title: 'Files',
description: 'One file store for team and agents',
href: '/files',
},
{
title: 'Logs',
description: 'Trace every agent decision',
href: '/logs',
},
],
}
/**
* The Resources menu - learning and reference surfaces. Six items in a
* three-column grid. Docs is the one off-site link.
*/
export const RESOURCES_MENU: NavMenu = {
label: 'Resources',
items: [
{
title: 'Docs',
description: 'Guides and API reference',
href: 'https://docs.sim.ai',
external: true,
},
{
title: 'Blog',
description: 'Ideas, news, and deep dives',
href: '/blog',
},
{
title: 'Library',
description: 'Comparisons, how-tos, and roundups',
href: '/library',
},
{
title: 'Changelog',
description: 'Everything we just shipped',
href: '/changelog',
},
{
title: 'Models',
description: 'Run on every major LLM',
href: '/models',
},
{
title: 'Integrations',
description: 'Connect 1,000+ apps and tools',
href: '/integrations',
},
],
}
/**
* The Solutions menu - agent use cases by team. Six items in a three-column grid.
*/
export const SOLUTIONS_MENU: NavMenu = {
label: 'Solutions',
items: [
{
title: 'Enterprise',
description: 'Govern AI agents at enterprise scale',
href: '/enterprise',
},
{
title: 'Engineering',
description: 'Let agents handle the busywork',
href: '/solutions/engineering',
},
{
title: 'IT',
description: 'Resolve tickets and access requests',
href: '/solutions/it',
},
{
title: 'Compliance',
description: 'Stay audit-ready around the clock',
href: '/solutions/compliance',
},
{
title: 'Finance',
description: 'Close the books faster',
href: '/solutions/finance',
},
{
title: 'HR',
description: 'Onboard and support every employee',
href: '/solutions/hr',
},
],
}
/**
* Navbar mega-menus that are currently rendered, in trigger order - shared by
* desktop and mobile nav (the desktop bar maps this list; mobile nav derives
* its visible sections from these labels).
*
* `PLATFORM_MENU` and `SOLUTIONS_MENU` are intentionally omitted for now to hide
* those sections from navigation while their pages stay live and reachable by
* direct URL. The menu definitions above are kept intact - to restore a section,
* add its constant back to this list.
*/
export const NAV_MENUS = [RESOURCES_MENU] as const
@@ -0,0 +1,3 @@
export { NAV_MENUS, PLATFORM_MENU, RESOURCES_MENU, SOLUTIONS_MENU } from './constants'
export { NavMenuChip } from './nav-menu-chip'
export type { NavMenu, NavMenuItemData } from './types'
@@ -0,0 +1,87 @@
'use client'
import { useState } from 'react'
import { ChipChevronDown, chipContentLabelClass, chipGeometryClass, cn } from '@sim/emcn'
import { NavMenuItem } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-item'
import type { NavMenu } from '@/app/(landing)/components/navbar/components/nav-menu-chip/types'
/**
* Navbar mega-menu - a chip trigger and the framed panel it reveals.
*
* Open is CSS-first. The trigger and panel share a `group/navmenu` wrapper; the
* panel sits behind `invisible opacity-0` and is revealed on
* `group-hover/navmenu` (pointer) and `group-focus-within/navmenu` (keyboard and
* touch-focus). The reveal classes ship in the initial HTML, so the menu opens
* with zero JS even before hydration.
*
* The one thing CSS can't do is force the panel shut while the pointer still
* rests on a just-clicked link (the navbar persists across client navigations,
* so `:hover`/`:focus-within` keep matching on the new page). A single `closed`
* flag handles that: selecting a row drops the reveal classes (closing the panel
* regardless of hover/focus) and blurs the link so focus-within clears;
* `onMouseLeave` and the trigger's `onFocus` re-arm it for the next open.
*
* Accessibility: `visibility: hidden` keeps the panel's links out of the tab
* order while closed, so Tab order is trigger → (panel opens on focus) → links →
* next nav item. The 8px `pt-2` bridge keeps the trigger and the framed card in
* one contiguous hover area so the pointer never crosses a dead gap. Motion
* collapses under `prefers-reduced-motion`.
*
* The panel chrome replicates the `ChipModal` framed-card look exactly: an outer
* `--surface-4` ring (`p-[3px]`, overlay shadow) wrapping an inner `--bg`
* surface, with the item grid padded inside.
*/
interface NavMenuChipProps {
/** The menu to render - trigger label and item grid. */
menu: NavMenu
}
const PANEL_BASE =
'pointer-events-none invisible absolute top-full left-0 z-50 translate-y-1 pt-2 opacity-0 transition-[opacity,transform] duration-150 motion-reduce:transition-none'
/** Reveal-on-open classes, omitted while `closed` so a selected menu stays shut. */
const PANEL_REVEAL =
'group-hover/navmenu:pointer-events-auto group-hover/navmenu:visible group-hover/navmenu:translate-y-0 group-hover/navmenu:opacity-100 group-focus-within/navmenu:pointer-events-auto group-focus-within/navmenu:visible group-focus-within/navmenu:translate-y-0 group-focus-within/navmenu:opacity-100'
export function NavMenuChip({ menu }: NavMenuChipProps) {
const { label, items } = menu
const [closed, setClosed] = useState(false)
const reArm = () => setClosed(false)
const handleSelect = () => {
setClosed(true)
if (document.activeElement instanceof HTMLElement) document.activeElement.blur()
}
return (
<div className='group/navmenu relative' onMouseLeave={reArm}>
<button
type='button'
aria-label={`${label} menu`}
onFocus={reArm}
className={cn(
chipGeometryClass,
'mx-0.5 inline-flex cursor-pointer transition-colors hover-hover:bg-[var(--surface-active)]',
'group-focus-within/navmenu:bg-[var(--surface-active)] group-hover/navmenu:bg-[var(--surface-active)]'
)}
>
<span className={chipContentLabelClass}>{label}</span>
<ChipChevronDown />
</button>
<div className={cn(PANEL_BASE, !closed && PANEL_REVEAL)}>
<div className='w-[840px] rounded-xl border border-[var(--border-muted)] bg-[var(--surface-4)] p-[3px] shadow-[var(--shadow-overlay)]'>
<div className='rounded-lg border border-[var(--border-1)] bg-[var(--bg)] p-2'>
<div className='grid grid-cols-3 gap-1' role='group' aria-label={label}>
{items.map((item) => (
<NavMenuItem key={item.title} item={item} onSelect={handleSelect} />
))}
</div>
</div>
</div>
</div>
</div>
)
}
@@ -0,0 +1,27 @@
/**
* One destination inside a navbar mega-menu - a title, a one-line description,
* and a crawlable href. {@link external} routes render as a plain
* `<a target='_blank' rel='noopener noreferrer'>`; internal routes use Next
* `<Link>`.
*/
export interface NavMenuItemData {
/** Item heading (e.g. "Mothership"). */
title: string
/** One-line description shown under the title. */
description: string
/** Destination - an internal route (`/workflows`) or an absolute URL. */
href: string
/** When true, the row is an off-site link opened in a new tab. */
external?: boolean
}
/**
* A single navbar dropdown: its trigger label and the flat three-column grid of
* items it reveals.
*/
export interface NavMenu {
/** Trigger label and accessible name of the panel (e.g. "Platform"). */
label: string
/** The destinations rendered in the panel, in display order. */
items: readonly NavMenuItemData[]
}
@@ -0,0 +1 @@
export { NAVBAR_GLASS_SURFACE, NavbarShell, useNavbarFrost } from './navbar-shell'
@@ -0,0 +1,109 @@
'use client'
import type { ReactNode } from 'react'
import { createContext, use, useEffect, useMemo, useRef, useState } from 'react'
import { cn } from '@sim/emcn'
/**
* Frosted near-white surface for the scrolled bar - `--bg` at 92% + a strong 40px
* blur, edge to edge. Exported as the single source of truth so the mobile
* dropdown sheet ({@link MobileNav}) wears the exact same glass as the bar and the
* two can never drift.
*/
export const NAVBAR_GLASS_SURFACE =
'bg-[color-mix(in_srgb,var(--bg)_92%,transparent)] backdrop-blur-2xl'
interface NavbarFrostContextValue {
/**
* Reported by the mobile sheet as it opens/closes so the bar frosts to glass
* while the menu is open - the bar and the dropdown then read as one continuous
* frosted panel instead of a transparent bar over a separate sheet.
*/
setMenuOpen: (open: boolean) => void
}
const NavbarFrostContext = createContext<NavbarFrostContextValue | null>(null)
/** Lets the mobile nav report its open state up to the shell so the bar can frost in sync. */
export function useNavbarFrost(): NavbarFrostContextValue | null {
return use(NavbarFrostContext)
}
interface NavbarShellProps {
children: ReactNode
}
/**
* Sticky navbar chrome that frosts to glass once the page scrolls (or, on mobile,
* while the dropdown menu is open).
*
* At the very top the bar uses the same solid canvas token as the hero, so it is
* visually seamless while still preventing route content from painting through
* the sticky header. A 1px sentinel at the top of the landing shell's internal
* scroll port is watched by an {@link IntersectionObserver} - no scroll listener
* and no per-frame work. Past that point the bar gains the shared
* {@link NAVBAR_GLASS_SURFACE} (`--bg` at 92% via `color-mix` plus a strong 40px
* backdrop blur) - a white/glass surface built entirely from the platform's
* light tokens, not invented colors.
*
* Only `background-color` is transitioned, NOT `backdrop-filter`: animating the
* blur re-runs every time the threshold is re-crossed, which on mobile reads as a
* vertical wobble of the bar's text as you scroll near the top. The blur snaps in
* while the fill still fades, so the frost appears smoothly without the jitter.
*
* The mobile sheet reports its open state through {@link NavbarFrostContext}, so
* opening the menu also frosts the bar - the bar and the dropdown then form one
* continuous glass panel with no transparent seam between them.
*
* The frost lives on a separate sibling layer (`absolute inset-0 -z-10`) behind
* the nav content rather than on the `<header>` element itself. This is
* deliberate: a `backdrop-filter` ancestor establishes a backdrop root that
* starves any descendant's own `backdrop-filter`, so a header that carried the
* blur would render the mobile dropdown's identical glass at a fraction of the
* strength (the dropdown is nested inside the header). With the blur on a sibling
* layer instead, the `<header>` has no backdrop-filter, so the dropdown samples
* the page directly and frosts at the exact same strength as the bar.
*
* The sentinel's height is cancelled by `-mb-px` so it contributes nothing to
* layout flow: the sticky header sits at `y=0` from the start and never creeps
* the 1px between its flowing and stuck positions as you begin scrolling.
*
* Only this shell hydrates; the nav content is server-rendered and passed through
* as {@link children}, so the wordmark and links stay zero-hydration and crawlable.
*/
export function NavbarShell({ children }: NavbarShellProps) {
const sentinelRef = useRef<HTMLDivElement>(null)
const [scrolled, setScrolled] = useState(false)
const [menuOpen, setMenuOpen] = useState(false)
useEffect(() => {
const sentinel = sentinelRef.current
if (!sentinel) return
const scrollPort = sentinel.parentElement
if (!scrollPort) return
const observer = new IntersectionObserver(([entry]) => setScrolled(!entry.isIntersecting), {
root: scrollPort,
})
observer.observe(sentinel)
return () => observer.disconnect()
}, [])
const frost = useMemo<NavbarFrostContextValue>(() => ({ setMenuOpen }), [])
return (
<NavbarFrostContext value={frost}>
<div ref={sentinelRef} aria-hidden='true' className='-mb-px h-px' />
<header className='sticky top-0 z-50'>
<div
aria-hidden='true'
className={cn(
'-z-10 pointer-events-none absolute inset-0 transition-[background-color] duration-200 motion-reduce:transition-none',
scrolled || menuOpen ? NAVBAR_GLASS_SURFACE : 'bg-[var(--bg)]'
)}
/>
{children}
</header>
</NavbarFrostContext>
)
}
@@ -0,0 +1 @@
export { SimWordmark } from './sim-wordmark'
@@ -0,0 +1,32 @@
/**
* Inline "sim" brand logotype (wordmark, no separate icon mark) - the paths
* from the v1.0 brand guide's `simLogotype--dark.svg`, inlined so the logo
* ships as zero-request server-rendered HTML.
*
* Filled with a single solid `var(--text-body)` - the navbar's own text color
* (the same token its nav-link chips use) - so the wordmark reads as one solid
* ink that matches the surrounding nav text, with no gradient or glow.
*
* Drawn at 18px tall - the chip's 14px label plus 2px above/below; the parent
* slot centers it in a chip-height (30px) box. Nudged up 1.5px: the glyph mass
* sits below the i-dot's headroom, so true geometric centering reads low.
*/
export function SimWordmark() {
return (
<svg
viewBox='0 0 441 212'
width={37}
height={18}
fill='none'
aria-hidden='true'
className='-translate-y-[1.5px] h-[18px] w-auto'
>
<g fill='var(--text-body)'>
<path d='M0 160.9H29.51C29.51 169.08 32.46 175.61 38.37 180.48C44.27 185.12 52.25 187.44 62.31 187.44C73.24 187.44 81.65 185.34 87.56 181.14C93.46 176.71 96.41 170.85 96.41 163.55C96.41 158.24 94.77 153.82 91.49 150.28C88.43 146.74 82.75 143.86 74.44 141.65L46.24 135.01C32.03 131.47 21.42 126.05 14.43 118.75C7.65 111.45 4.26 101.83 4.26 89.88C4.26 79.93 6.78 71.3 11.81 64C17.05 56.7 24.16 51.06 33.12 47.08C42.3 43.09 52.8 41.1 64.6 41.1C76.41 41.1 86.57 43.2 95.1 47.41C103.84 51.61 110.62 57.47 115.43 64.99C120.46 72.52 123.08 81.48 123.3 91.87H93.79C93.57 83.47 90.84 76.94 85.59 72.3C80.34 67.65 73.02 65.33 63.62 65.33C54 65.33 46.57 67.43 41.32 71.63C36.07 75.83 33.45 81.59 33.45 88.89C33.45 99.73 41.32 107.14 57.06 111.12L85.26 118.09C98.81 121.19 108.98 126.28 115.76 133.35C122.53 140.21 125.92 149.61 125.92 161.56C125.92 171.74 123.19 180.7 117.73 188.44C112.26 195.96 104.72 201.82 95.1 206.03C85.7 210.01 74.55 212 61.65 212C42.85 212 27.87 207.35 16.72 198.06C5.57 188.77 0 176.38 0 160.9Z' />
<path d='M232.8 212H202.13L202.13 49.76H229.54V77.39C232.8 68.34 239.11 60.66 247.81 54.7C256.73 48.52 267.5 45.43 280.12 45.43C294.26 45.43 306.01 49.29 315.36 57.02C324.72 64.75 330.81 75.01 333.64 87.82H328.09C330.27 75.01 336.25 64.75 346.04 57.02C355.83 49.29 367.9 45.43 382.26 45.43C400.54 45.43 414.89 50.84 425.34 61.66C435.78 72.47 441 87.26 441 106.03V212H410.98V113.65C410.98 100.84 407.71 91.02 401.19 84.17C394.88 77.11 386.29 73.58 375.41 73.58C367.79 73.58 361.05 75.34 355.17 78.88C349.52 82.19 345.06 87.04 341.8 93.45C338.53 99.85 336.9 107.36 336.9 115.97V212H306.55V113.32C306.55 100.51 303.4 90.8 297.09 84.17C290.78 77.33 282.19 73.91 271.31 73.91C263.69 73.91 256.95 75.67 251.08 79.21C245.42 82.52 240.96 87.38 237.7 93.78C234.43 99.96 232.8 107.36 232.8 115.97V212Z' />
<path d='M184.83 20.55C184.83 31.9 175.64 41.1 164.29 41.1C152.95 41.1 143.76 31.9 143.76 20.55C143.76 9.2 152.95 0 164.29 0C175.64 0 184.83 9.2 184.83 20.55Z' />
<path d='M179.43 212H149.16V49.76C153.76 51.91 158.88 53.12 164.29 53.12C169.7 53.12 174.83 51.91 179.43 49.76V212Z' />
</g>
</svg>
)
}
@@ -0,0 +1 @@
export { Navbar } from './navbar'
@@ -0,0 +1,105 @@
import { ChipLink } from '@sim/emcn'
import Link from 'next/link'
import {
GitHubChip,
LogoMark,
MobileNav,
NAV_MENUS,
NavbarShell,
NavMenuChip,
SimWordmark,
} from '@/app/(landing)/components/navbar/components'
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
/**
* Landing navbar.
*
* Sticky `<header><nav>` landmark with `SiteNavigationElement` schema.org
* markup. Server Component - the dropdown triggers, GitHub chip, and the
* {@link NavbarShell} (which frosts the bar to glass on scroll) are isolated
* client leaves, so the wordmark and links stay zero-hydration, crawlable HTML.
*
* Every item is a bare emcn chip. Both clusters use `gap-1`, which with
* the chips' own `mx-0.5` margins yields 8px between pills; the nav's
* `gap-3.5` (14px) plus the first chip's 2px margin puts exactly 16px -
* twice the inter-chip gap - between the wordmark and the first menu chip.
* Horizontal padding (`px-20`, 48px) matches every section's edge gutter,
* and the bar content is capped and centered at the shared
* `max-w-[1460px]` (1300px content + the two 80px gutters) so the wordmark
* aligns with the contained section content on wide screens - the frosted
* `<header>` shell stays full-bleed. Slightly taller vertical padding. Text
* weight is the platform default (400).
*
* Layout (left → right): Sim wordmark (18px glyph centered in a
* chip-height slot, chip-text color) → the {@link NAV_MENUS} mega-menus
* (pure-CSS hover/focus dropdowns) → Pricing → GitHub stars. Right side: Log in
* (default chip), Contact sales (outline chip), Sign up (filled chip).
*/
interface NavbarProps {
/**
* Formatted GitHub star count (e.g. "28.8k"), fetched server-side at
* build/revalidate time. Omitted by non-marketing shells that reuse this
* navbar without a stars fetch (the GitHub chip is hidden when absent).
*/
stars?: string
/**
* Render only the Sim wordmark - no nav menus, GitHub chip, auth chips, or
* mobile sheet. Used by non-marketing shells (resume, public-file auth) that
* want the brand header without the full marketing navigation.
*/
logoOnly?: boolean
}
export function Navbar({ stars, logoOnly = false }: NavbarProps) {
return (
<NavbarShell>
<nav
aria-label='Primary navigation'
itemScope
itemType='https://schema.org/SiteNavigationElement'
className='relative mx-auto flex w-full max-w-[1460px] items-center gap-3.5 px-20 py-4 max-sm:px-5 max-lg:px-8'
>
<Link href='/' aria-label='Sim home' itemProp='url' className='flex h-[30px] items-center'>
<span itemProp='name' className='sr-only'>
Sim
</span>
<LogoMark>
<SimWordmark />
</LogoMark>
</Link>
{!logoOnly && (
<>
<div className='hidden items-center gap-1 lg:flex'>
{NAV_MENUS.map((menu) => (
<NavMenuChip key={menu.label} menu={menu} />
))}
<ChipLink href='/enterprise' itemProp='url'>
Enterprise
</ChipLink>
<ChipLink href='/pricing' itemProp='url'>
Pricing
</ChipLink>
{stars !== undefined && <GitHubChip stars={stars} />}
</div>
<div className='ml-auto hidden items-center gap-1 lg:flex'>
<ChipLink href='/login' prefetch={false}>
Log in
</ChipLink>
<ChipLink variant='border' href={DEMO_HREF}>
Contact sales
</ChipLink>
<ChipLink variant='primary' href={SIGNUP_HREF} prefetch={false}>
Sign up
</ChipLink>
</div>
<MobileNav stars={stars ?? '0'} />
</>
)}
</nav>
</NavbarShell>
)
}