Skip to content
Open
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
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ The landing page looks like the product. Its visual language is the workspace UI
- **Never touch global styles.** No additions to `app/_styles/globals.css`. All styling is local Tailwind classes; `cn()` from `@/lib/core/utils/cn` for conditionals; no inline `style` attributes.
- **Responsive - desktop is the source of truth, scaled down via `max-*` overrides.** The page is fully responsive (iPad + phone). The desktop layout stays the unprefixed baseline; smaller screens are handled by *layering* `max-*` overrides on top, so desktop renders byte-identically. Tiers:
- `max-xl:` (≤1279) - the hero's two-panel split (absolute visual + logos) collapses to a stacked, in-flow column. The split needs ≥1280 to avoid the headline colliding with the visual panel; iPad-landscape (1024) therefore gets the stacked hero with the desktop nav.
- `max-lg:` (≤1023) - the desktop nav clusters hide (`hidden lg:flex`) and `MobileNav` (hamburger sheet) takes over; multi-column grids step down (mothership 4→2, footer 7→3); shared gutter `px-12 → max-lg:px-8`; section gaps tighten.
- `max-lg:` (≤1023) - the desktop nav clusters hide (`hidden lg:flex`) and `MobileNav` (hamburger sheet) takes over; multi-column grids step down (mothership 4→2, footer 7→3); shared gutter `px-20 → max-lg:px-8`; section gaps tighten.
- `max-md:` (≤767) - Features beats drop the floating callout (`max-md:hidden`) and show the un-masked backdrop preview full-width.
- `max-sm:` (≤639) - single-column grids, smallest type scale, `px-5` gutter, hero CTA row stacks.

When adding a new section, give it the same `px-12 max-lg:px-8 max-sm:px-5` gutter so the navbar wordmark stays aligned with section content at every width. Verify desktop is unchanged and there is zero horizontal overflow at 1280 / 1024 / 768 / 390 before shipping.
When adding a new section, give it the same `px-20 max-lg:px-8 max-sm:px-5` gutter so the navbar wordmark stays aligned with section content at every width. Verify desktop is unchanged and there is zero horizontal overflow at 1280 / 1024 / 768 / 390 before shipping.

## Performance - page speed is a feature

Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/(landing)/blog/[slug]/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Skeleton } from '@sim/emcn'
export default function BlogPostLoading() {
return (
<article className='w-full bg-[var(--bg)]'>
<div className='mx-auto w-full max-w-[1446px] px-12 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<div className='mx-auto w-full max-w-[1460px] px-20 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<div className='mb-6'>
<Skeleton className='h-[16px] w-[100px] rounded-[4px] bg-[var(--surface-hover)]' />
</div>
Expand Down Expand Up @@ -31,8 +31,8 @@ export default function BlogPostLoading() {

<div className='mt-8 h-px w-full bg-[var(--border)]' />

<div className='mx-auto w-full max-w-[1446px]'>
<div className='mx-12 border-[var(--border)] border-x max-sm:mx-5 max-lg:mx-8'>
<div className='mx-auto w-full max-w-[1460px]'>
<div className='mx-20 border-[var(--border)] border-x max-sm:mx-5 max-lg:mx-8'>
<div className='mx-auto max-w-[900px] px-6 py-16'>
<div className='space-y-4'>
<Skeleton className='h-[16px] w-full rounded-[4px] bg-[var(--surface-hover)]' />
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
return (
<article className='w-full bg-[var(--bg)]' itemScope itemType='https://schema.org/TechArticle'>
<JsonLd data={graphJsonLd} />
<header className='mx-auto w-full max-w-[1446px] px-12 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<header className='mx-auto w-full max-w-[1460px] px-20 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<div className='mb-6'>
<BackLink href='/blog' label='Back to Blog' />
</div>
Expand Down Expand Up @@ -118,7 +118,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string

<div className='mt-8 h-px w-full bg-[var(--border)]' />

<div className='mx-auto w-full max-w-[1446px] px-12 max-sm:px-5 max-lg:px-8'>
<div className='mx-auto w-full max-w-[1460px] px-20 max-sm:px-5 max-lg:px-8'>
<div className='border-[var(--border)] border-x'>
<div className='mx-auto max-w-[900px] px-6 py-16' itemProp='articleBody'>
<div className='prose prose-lg max-w-none prose-blockquote:border-[var(--border-1)] prose-hr:border-[var(--border)] prose-headings:font-season prose-a:text-[var(--text-primary)] prose-blockquote:text-[var(--text-muted)] prose-code:text-[var(--text-primary)] prose-headings:text-[var(--text-primary)] prose-li:text-[var(--text-body)] prose-p:text-[var(--text-body)] prose-strong:text-[var(--text-primary)] prose-headings:tracking-[-0.02em]'>
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/(landing)/blog/loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { Skeleton } from '@sim/emcn'
export default function BlogLoading() {
return (
<section className='bg-[var(--bg)]'>
<div className='mx-auto w-full max-w-[1446px]'>
<div className='mx-auto w-full max-w-[1460px]'>
{/* Header skeleton */}
<div className='px-12 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<div className='px-20 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<Skeleton className='mb-5 h-[20px] w-[60px] rounded-md bg-[var(--surface-hover)]' />
<div className='flex flex-col gap-4 md:flex-row md:items-end md:justify-between'>
<Skeleton className='h-[40px] w-[240px] rounded-[4px] bg-[var(--surface-hover)]' />
Expand All @@ -14,7 +14,7 @@ export default function BlogLoading() {
</div>

{/* Content area with vertical border rails */}
<div className='mx-12 mt-8 border-[var(--border)] border-x max-sm:mx-5 max-lg:mx-8'>
<div className='mx-20 mt-8 border-[var(--border)] border-x max-sm:mx-5 max-lg:mx-8'>
<div className='h-px w-full bg-[var(--border)]' />

{/* Featured skeleton */}
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default async function BlogIndex({
<JsonLd data={collectionJsonLd} />

{/* Section header */}
<div className='mx-auto w-full max-w-[1446px] px-12 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<div className='mx-auto w-full max-w-[1460px] px-20 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
<div className='flex flex-col gap-4 md:flex-row md:items-end md:justify-between'>
<h1 className='text-balance text-[28px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] lg:text-[40px]'>
Latest from Sim
Expand All @@ -112,7 +112,7 @@ export default async function BlogIndex({
<div className='mt-8 h-px w-full bg-[var(--border)]' />

{/* Content area with vertical border rails */}
<div className='mx-auto w-full max-w-[1446px] px-12 max-sm:px-5 max-lg:px-8'>
<div className='mx-auto w-full max-w-[1460px] px-20 max-sm:px-5 max-lg:px-8'>
<div className='border-[var(--border)] border-x'>
{/* Featured posts */}
{featured.length > 0 && (
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/app/(landing)/careers/careers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ interface CareersProps {
* crawlable HTML; the interactive {@link JobBoard} hydrates on top to add
* Team/Location filtering.
*
* Both sections share the landing gutter — capped and centered at `max-w-[1446px]`
* with the navbar-aligned `px-12 max-lg:px-8 max-sm:px-5` so the headline starts on
* Both sections share the landing gutter — capped and centered at `max-w-[1460px]`
* with the navbar-aligned `px-20 max-lg:px-8 max-sm:px-5` so the headline starts on
* the same vertical line as the wordmark. The hero carries the single `<h1>`
* (containing "Sim" and "AI workspace") plus an sr-only product summary for AI
* citation (landing CLAUDE.md → GEO); the roles section owns its own `<h2>`.
Expand All @@ -45,7 +45,7 @@ export default async function Careers({ searchParams }: CareersProps) {
<section
id='careers-hero'
aria-labelledby='careers-heading'
className='mx-auto flex w-full max-w-[1446px] flex-col gap-5 px-12 pt-20 pb-10 max-sm:px-5 max-sm:pt-16 max-lg:px-8'
className='mx-auto flex w-full max-w-[1460px] flex-col gap-5 px-20 pt-20 pb-10 max-sm:px-5 max-sm:pt-16 max-lg:px-8'
>
<p className='sr-only'>
Careers at Sim, the open-source AI workspace where teams build, deploy, and manage AI
Expand All @@ -70,7 +70,7 @@ export default async function Careers({ searchParams }: CareersProps) {
<section
id='open-roles'
aria-labelledby='open-roles-heading'
className='mx-auto flex w-full max-w-[1446px] flex-col gap-10 px-12 pt-6 pb-24 max-sm:px-5 max-sm:pb-16 max-lg:px-8'
className='mx-auto flex w-full max-w-[1460px] flex-col gap-10 px-20 pt-6 pb-24 max-sm:px-5 max-sm:pb-16 max-lg:px-8'
>
<h2
id='open-roles-heading'
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/(landing)/components/cta/cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import { ChipLink } from '@sim/emcn'
* `landing.tsx`) above it and the `Footer`'s top margin below it. The headline
* mirrors the hero `<h1>` exactly (48px / `leading-[1.1]` and the same responsive
* ramp), so the page opens and closes on the same display size. Horizontal
* padding (`px-12`) matches every section above, and the section is capped and
* centered at the shared `max-w-[1446px]`.
* padding (`px-20`) matches every section above, and the section is capped and
* centered at the shared `max-w-[1460px]`.
*/
export function Cta() {
return (
<section
id='cta'
aria-labelledby='cta-heading'
className='mx-auto flex w-full max-w-[1446px] flex-col items-center gap-[22px] px-12 text-center max-sm:px-5 max-lg:px-8'
className='mx-auto flex w-full max-w-[1460px] flex-col items-center gap-[22px] px-20 text-center max-sm:px-5 max-lg:px-8'
>
<h2
id='cta-heading'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ReactNode } from 'react'
import { cn } from '@sim/emcn'

const CALLOUT_FADE =
'[-webkit-mask-image:linear-gradient(to_bottom,#000_72%,transparent)] [mask-image:linear-gradient(to_bottom,#000_72%,transparent)]'

interface CalloutFrameProps {
/** Width/layout for the panel (e.g. `w-[340px]`). */
className?: string
/** Sizing for the inner body (e.g. `h-[300px]`). */
bodyClassName?: string
/** Dissolve the body's lower edge so the surface reads as continuing below. */
fade?: boolean
children: ReactNode
}

/**
* The shared chrome for a callout: an elevated panel that lifts a real Sim UI
* surface off the backdrop, wearing the hero platform window's exact chrome -
* 10px radius, `--surface-1` fill, and the hairline-ring + layered soft shadow
* - so every floating window on the page reads as one family. Optionally fades
* its body's foot so a long surface dissolves rather than ends on a hard edge.
*/
export function CalloutFrame({ className, bodyClassName, fade, children }: CalloutFrameProps) {
return (
<div
className={cn(
'overflow-hidden rounded-[10px] bg-[var(--surface-1)] shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_2px_6px_0_rgba(0,0,0,0.05),0_4px_42px_0_rgba(0,0,0,0.06)]',
className
)}
>
<div className={cn('relative overflow-hidden', fade && CALLOUT_FADE, bodyClassName)}>
{children}
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CalloutFrame } from './callout-frame'
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { ReactNode } from 'react'
import { ChipTag, cn } from '@sim/emcn'
import { ArrowRight } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'

interface FeatureCardProps {
/** Capability name shown as a chip tag pinned to the card's top-right corner. */
eyebrow: string
/** The beat's headline (`<h3>` - the section owns the single `<h2>`). */
title: string
/** Supporting copy beneath the headline. */
description: string
/** Optional trailing link (e.g. the feature's platform page). */
href?: string
/** Label for {@link href}. */
linkLabel?: string
/** Backdrop image under the floating callout (public path). */
backdropSrc: string
/**
* Which side the media stage sits on from `lg` up (cards alternate down the
* section, Cursor-style). Below `lg` the card always stacks media-first.
*/
mediaSide?: 'left' | 'right'
/**
* Square the card's bottom corners so its bottom edge merges with a
* full-bleed divider drawn at the same line (the section's last card).
*/
flushBottom?: boolean
/** The elevated real-UI callout floating over the backdrop. */
children: ReactNode
}

/**
* Cursor-style feature card - one large OUTLINED container (a light
* `--border` hairline on a transparent ground, no grey fill) holding a media
* stage (a painted backdrop with the beat's real-UI callout floating over it)
* and a vertically-centered copy column: `<h3>`, muted description, and an
* optional arrow link. `mediaSide` picks which side the media sits on so the
* cards can alternate down the section. The beat's name sits as a borderless
* grey-filled {@link ChipTag} pinned to the card's top corner on the COPY
* side (top-right when media is left, top-left when media is right), just
* inside the outline - never floating over the media image.
*
* Below `lg` the card stacks - media on top, copy beneath - and the media
* shortens so the card stays scannable in the compact grid.
*/
export function FeatureCard({
eyebrow,
title,
description,
href,
linkLabel,
backdropSrc,
mediaSide = 'left',
flushBottom = false,
children,
}: FeatureCardProps) {
const mediaRight = mediaSide === 'right'
return (
<article
className={cn(
'relative grid gap-10 rounded-[10px] border border-[var(--border)] p-4 max-lg:grid-cols-1 max-lg:gap-6',
mediaRight ? 'grid-cols-[386px_1fr]' : 'grid-cols-[1fr_386px]',
flushBottom && 'rounded-b-none'
)}
>
<ChipTag
variant='mono'
className={cn('absolute top-4 z-10', mediaRight ? 'left-4' : 'right-4')}
>
{eyebrow}
</ChipTag>
<div
aria-hidden='true'
className={cn(
'relative h-[650px] overflow-hidden rounded-[4px] max-sm:h-[280px] max-lg:order-1 max-lg:h-[360px]',
mediaRight && 'lg:order-2'
)}
>
<Image
src={backdropSrc}
alt=''
fill
sizes='(max-width: 1460px) 70vw, 900px'
className='object-cover'
/>
<div className='absolute inset-0 flex items-center justify-center p-4 [&>*]:max-w-full'>
{children}
</div>
</div>

<div
className={cn(
'flex flex-col justify-center max-lg:order-2 max-lg:pb-2',
mediaRight ? 'pl-4 max-lg:pl-0' : 'pr-4 max-lg:pr-0'
)}
>
<h3 className='text-balance font-medium text-[22px] text-[var(--text-primary)] leading-[1.3] max-sm:text-[20px]'>
{title}
</h3>
<p className='mt-3 text-pretty text-[15px] text-[var(--text-muted)] leading-[1.6]'>
{description}
</p>
{href && linkLabel && (
<Link
href={href}
className='mt-5 flex items-center gap-1.5 text-[15px] text-[var(--text-body)] transition-colors hover-hover:text-[var(--text-primary)]'
>
{linkLabel}
<ArrowRight className='size-[15px]' />
</Link>
)}
</div>
</article>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FeatureCard } from './feature-card'
Loading