Skip to content

Commit 8e52ff4

Browse files
committed
improvement(landing): homepage redesign with live hero and real platform UI feature cards
1 parent 577a7a2 commit 8e52ff4

79 files changed

Lines changed: 2408 additions & 469 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/(landing)/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ The landing page looks like the product. Its visual language is the workspace UI
1818
- **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.
1919
- **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:
2020
- `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.
21-
- `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.
21+
- `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.
2222
- `max-md:` (≤767) - Features beats drop the floating callout (`max-md:hidden`) and show the un-masked backdrop preview full-width.
2323
- `max-sm:` (≤639) - single-column grids, smallest type scale, `px-5` gutter, hero CTA row stacks.
2424

25-
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.
25+
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.
2626

2727
## Performance - page speed is a feature
2828

apps/sim/app/(landing)/blog/[slug]/loading.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Skeleton } from '@sim/emcn'
33
export default function BlogPostLoading() {
44
return (
55
<article className='w-full bg-[var(--bg)]'>
6-
<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'>
6+
<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'>
77
<div className='mb-6'>
88
<Skeleton className='h-[16px] w-[100px] rounded-[4px] bg-[var(--surface-hover)]' />
99
</div>
@@ -31,8 +31,8 @@ export default function BlogPostLoading() {
3131

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

34-
<div className='mx-auto w-full max-w-[1446px]'>
35-
<div className='mx-12 border-[var(--border)] border-x max-sm:mx-5 max-lg:mx-8'>
34+
<div className='mx-auto w-full max-w-[1460px]'>
35+
<div className='mx-20 border-[var(--border)] border-x max-sm:mx-5 max-lg:mx-8'>
3636
<div className='mx-auto max-w-[900px] px-6 py-16'>
3737
<div className='space-y-4'>
3838
<Skeleton className='h-[16px] w-full rounded-[4px] bg-[var(--surface-hover)]' />

apps/sim/app/(landing)/blog/[slug]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
3939
return (
4040
<article className='w-full bg-[var(--bg)]' itemScope itemType='https://schema.org/TechArticle'>
4141
<JsonLd data={graphJsonLd} />
42-
<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'>
42+
<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'>
4343
<div className='mb-6'>
4444
<BackLink href='/blog' label='Back to Blog' />
4545
</div>
@@ -118,7 +118,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
118118

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

121-
<div className='mx-auto w-full max-w-[1446px] px-12 max-sm:px-5 max-lg:px-8'>
121+
<div className='mx-auto w-full max-w-[1460px] px-20 max-sm:px-5 max-lg:px-8'>
122122
<div className='border-[var(--border)] border-x'>
123123
<div className='mx-auto max-w-[900px] px-6 py-16' itemProp='articleBody'>
124124
<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]'>

apps/sim/app/(landing)/blog/loading.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { Skeleton } from '@sim/emcn'
33
export default function BlogLoading() {
44
return (
55
<section className='bg-[var(--bg)]'>
6-
<div className='mx-auto w-full max-w-[1446px]'>
6+
<div className='mx-auto w-full max-w-[1460px]'>
77
{/* Header skeleton */}
8-
<div className='px-12 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
8+
<div className='px-20 pt-[112px] max-sm:px-5 max-sm:pt-20 max-lg:px-8'>
99
<Skeleton className='mb-5 h-[20px] w-[60px] rounded-md bg-[var(--surface-hover)]' />
1010
<div className='flex flex-col gap-4 md:flex-row md:items-end md:justify-between'>
1111
<Skeleton className='h-[40px] w-[240px] rounded-[4px] bg-[var(--surface-hover)]' />
@@ -14,7 +14,7 @@ export default function BlogLoading() {
1414
</div>
1515

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

2020
{/* Featured skeleton */}

apps/sim/app/(landing)/blog/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export default async function BlogIndex({
9797
<JsonLd data={collectionJsonLd} />
9898

9999
{/* Section header */}
100-
<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'>
100+
<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'>
101101
<div className='flex flex-col gap-4 md:flex-row md:items-end md:justify-between'>
102102
<h1 className='text-balance text-[28px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] lg:text-[40px]'>
103103
Latest from Sim
@@ -112,7 +112,7 @@ export default async function BlogIndex({
112112
<div className='mt-8 h-px w-full bg-[var(--border)]' />
113113

114114
{/* Content area with vertical border rails */}
115-
<div className='mx-auto w-full max-w-[1446px] px-12 max-sm:px-5 max-lg:px-8'>
115+
<div className='mx-auto w-full max-w-[1460px] px-20 max-sm:px-5 max-lg:px-8'>
116116
<div className='border-[var(--border)] border-x'>
117117
{/* Featured posts */}
118118
{featured.length > 0 && (

apps/sim/app/(landing)/careers/careers.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ interface CareersProps {
2121
* crawlable HTML; the interactive {@link JobBoard} hydrates on top to add
2222
* Team/Location filtering.
2323
*
24-
* Both sections share the landing gutter — capped and centered at `max-w-[1446px]`
25-
* with the navbar-aligned `px-12 max-lg:px-8 max-sm:px-5` so the headline starts on
24+
* Both sections share the landing gutter — capped and centered at `max-w-[1460px]`
25+
* with the navbar-aligned `px-20 max-lg:px-8 max-sm:px-5` so the headline starts on
2626
* the same vertical line as the wordmark. The hero carries the single `<h1>`
2727
* (containing "Sim" and "AI workspace") plus an sr-only product summary for AI
2828
* citation (landing CLAUDE.md → GEO); the roles section owns its own `<h2>`.
@@ -45,7 +45,7 @@ export default async function Careers({ searchParams }: CareersProps) {
4545
<section
4646
id='careers-hero'
4747
aria-labelledby='careers-heading'
48-
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'
48+
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'
4949
>
5050
<p className='sr-only'>
5151
Careers at Sim, the open-source AI workspace where teams build, deploy, and manage AI
@@ -70,7 +70,7 @@ export default async function Careers({ searchParams }: CareersProps) {
7070
<section
7171
id='open-roles'
7272
aria-labelledby='open-roles-heading'
73-
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'
73+
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'
7474
>
7575
<h2
7676
id='open-roles-heading'

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import { ChipLink } from '@sim/emcn'
1111
* `landing.tsx`) above it and the `Footer`'s top margin below it. The headline
1212
* mirrors the hero `<h1>` exactly (48px / `leading-[1.1]` and the same responsive
1313
* ramp), so the page opens and closes on the same display size. Horizontal
14-
* padding (`px-12`) matches every section above, and the section is capped and
15-
* centered at the shared `max-w-[1446px]`.
14+
* padding (`px-20`) matches every section above, and the section is capped and
15+
* centered at the shared `max-w-[1460px]`.
1616
*/
1717
export function Cta() {
1818
return (
1919
<section
2020
id='cta'
2121
aria-labelledby='cta-heading'
22-
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'
22+
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'
2323
>
2424
<h2
2525
id='cta-heading'
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { ReactNode } from 'react'
2+
import { cn } from '@sim/emcn'
3+
4+
const CALLOUT_FADE =
5+
'[-webkit-mask-image:linear-gradient(to_bottom,#000_72%,transparent)] [mask-image:linear-gradient(to_bottom,#000_72%,transparent)]'
6+
7+
interface CalloutFrameProps {
8+
/** Width/layout for the panel (e.g. `w-[340px]`). */
9+
className?: string
10+
/** Sizing for the inner body (e.g. `h-[300px]`). */
11+
bodyClassName?: string
12+
/** Dissolve the body's lower edge so the surface reads as continuing below. */
13+
fade?: boolean
14+
children: ReactNode
15+
}
16+
17+
/**
18+
* The shared chrome for a callout: an elevated panel that lifts a real Sim UI
19+
* surface off the backdrop, wearing the hero platform window's exact chrome -
20+
* 10px radius, `--surface-1` fill, and the hairline-ring + layered soft shadow
21+
* - so every floating window on the page reads as one family. Optionally fades
22+
* its body's foot so a long surface dissolves rather than ends on a hard edge.
23+
*/
24+
export function CalloutFrame({ className, bodyClassName, fade, children }: CalloutFrameProps) {
25+
return (
26+
<div
27+
className={cn(
28+
'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)]',
29+
className
30+
)}
31+
>
32+
<div className={cn('relative overflow-hidden', fade && CALLOUT_FADE, bodyClassName)}>
33+
{children}
34+
</div>
35+
</div>
36+
)
37+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { CalloutFrame } from './callout-frame'
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import type { ReactNode } from 'react'
2+
import { ChipTag, cn } from '@sim/emcn'
3+
import { ArrowRight } from 'lucide-react'
4+
import Image from 'next/image'
5+
import Link from 'next/link'
6+
7+
interface FeatureCardProps {
8+
/** Capability name shown as a chip tag pinned to the card's top-right corner. */
9+
eyebrow: string
10+
/** The beat's headline (`<h3>` - the section owns the single `<h2>`). */
11+
title: string
12+
/** Supporting copy beneath the headline. */
13+
description: string
14+
/** Optional trailing link (e.g. the feature's platform page). */
15+
href?: string
16+
/** Label for {@link href}. */
17+
linkLabel?: string
18+
/** Backdrop image under the floating callout (public path). */
19+
backdropSrc: string
20+
/**
21+
* Which side the media stage sits on from `lg` up (cards alternate down the
22+
* section, Cursor-style). Below `lg` the card always stacks media-first.
23+
*/
24+
mediaSide?: 'left' | 'right'
25+
/**
26+
* Square the card's bottom corners so its bottom edge merges with a
27+
* full-bleed divider drawn at the same line (the section's last card).
28+
*/
29+
flushBottom?: boolean
30+
/** The elevated real-UI callout floating over the backdrop. */
31+
children: ReactNode
32+
}
33+
34+
/**
35+
* Cursor-style feature card - one large OUTLINED container (a light
36+
* `--border` hairline on a transparent ground, no grey fill) holding a media
37+
* stage (a painted backdrop with the beat's real-UI callout floating over it)
38+
* and a vertically-centered copy column: `<h3>`, muted description, and an
39+
* optional arrow link. `mediaSide` picks which side the media sits on so the
40+
* cards can alternate down the section. The beat's name sits as a borderless
41+
* grey-filled {@link ChipTag} pinned to the card's top corner on the COPY
42+
* side (top-right when media is left, top-left when media is right), just
43+
* inside the outline - never floating over the media image.
44+
*
45+
* Below `lg` the card stacks - media on top, copy beneath - and the media
46+
* shortens so the card stays scannable in the compact grid.
47+
*/
48+
export function FeatureCard({
49+
eyebrow,
50+
title,
51+
description,
52+
href,
53+
linkLabel,
54+
backdropSrc,
55+
mediaSide = 'left',
56+
flushBottom = false,
57+
children,
58+
}: FeatureCardProps) {
59+
const mediaRight = mediaSide === 'right'
60+
return (
61+
<article
62+
className={cn(
63+
'relative grid gap-10 rounded-[10px] border border-[var(--border)] p-4 max-lg:grid-cols-1 max-lg:gap-6',
64+
mediaRight ? 'grid-cols-[386px_1fr]' : 'grid-cols-[1fr_386px]',
65+
flushBottom && 'rounded-b-none'
66+
)}
67+
>
68+
<ChipTag
69+
variant='mono'
70+
className={cn('absolute top-4 z-10', mediaRight ? 'left-4' : 'right-4')}
71+
>
72+
{eyebrow}
73+
</ChipTag>
74+
<div
75+
aria-hidden='true'
76+
className={cn(
77+
'relative h-[650px] overflow-hidden rounded-[4px] max-sm:h-[280px] max-lg:order-1 max-lg:h-[360px]',
78+
mediaRight && 'lg:order-2'
79+
)}
80+
>
81+
<Image
82+
src={backdropSrc}
83+
alt=''
84+
fill
85+
sizes='(max-width: 1460px) 70vw, 900px'
86+
className='object-cover'
87+
/>
88+
<div className='absolute inset-0 flex items-center justify-center p-4 [&>*]:max-w-full'>
89+
{children}
90+
</div>
91+
</div>
92+
93+
<div
94+
className={cn(
95+
'flex flex-col justify-center max-lg:order-2 max-lg:pb-2',
96+
mediaRight ? 'pl-4 max-lg:pl-0' : 'pr-4 max-lg:pr-0'
97+
)}
98+
>
99+
<h3 className='text-balance font-medium text-[22px] text-[var(--text-primary)] leading-[1.3] max-sm:text-[20px]'>
100+
{title}
101+
</h3>
102+
<p className='mt-3 text-pretty text-[15px] text-[var(--text-muted)] leading-[1.6]'>
103+
{description}
104+
</p>
105+
{href && linkLabel && (
106+
<Link
107+
href={href}
108+
className='mt-5 flex items-center gap-1.5 text-[15px] text-[var(--text-body)] transition-colors hover-hover:text-[var(--text-primary)]'
109+
>
110+
{linkLabel}
111+
<ArrowRight className='size-[15px]' />
112+
</Link>
113+
)}
114+
</div>
115+
</article>
116+
)
117+
}

0 commit comments

Comments
 (0)