What2Do · Redesign
What2Do · Before & After · iPhone 17 Pro

What2Do Blue

Light map world, one confident blue accent, white glass surfaces. Every screen below: before (current app, problems tagged in red) → after (redesign, components labeled beneath each screen). Expand Shape code under any component for its TSX + Tailwind (shadcn/ui, Framer Motion m., lucide-react) — matching the app codebase.

Send via Gmail Phones-only PDF ✏️ Double-click anywhere to drop a note (0 so far). Public link for anyone: w2d.147-182-168-121.sslip.io — or attach the HTML / phones PDF in Gmail.
Iteration history
Iteration 41Aug 11, 2026Every Before frame is now Arad’s real iPhone screenshot — home, explore, schedule, event, messages, profile, camera — replacing the coded replicas. Also repaired 20 dead S1 hover mappings (was 237/257, now 257/257). Previous build → You are here.
Iteration 7Jul 12, 2026 · 4:55 PMHome proves the product — grey MapLibre canvas with the spotlit pick, and the Live Window: real footage of the #1 moment, friends already there, one Step in › action; two-state event flow; chat matched to reference; five-step onboarding (welcome footage, number, code, photo, permissions).
Iteration 6Jul 12, 2026 · 2:40 PMPokémon GO street-world home — drawn avatar with backpack, event beacons, judge-panel polished — and the TikTok×Insta profile. Open →
Iteration 5Jul 11, 2026 · 8:20 PMBlue theme, first Pokémon GO-style home world, real social profile, glass calendar & map-canvas event. Open →
Iteration 4Jul 11, 2026 · 7:30 PMViolet theme — sandwich home, glass calendar, map-canvas event. Open →
Iteration 3Jul 11, 2026 · 5:05 PMPhoto-real light pass — real photography, fine-grain 3D map art, flatter minimal surfaces. Open →
Iteration 2Jul 11, 2026 · 3:55 PMFirst Periwinkle light pass — CSS mockups, duotone covers, numbered overlay dots. Open →
Iteration 1Jul 11, 2026 · 1:10 PMEmber Night — dark direction with full design-system spec. Open →
The palette — exactly by name

Colors

Azure — the one accent

5.1:1 AA
Azure 600
#1D6FF2
Host button, Request to Join, active tab, selected day, unread pill, the "2" in the logo. White text on it.
Azure 500
#3D8BFF
Map pins, small accents, calendar event dots.
Azure 100
#E8F1FF
Tints — active filter chip, Hosting tag, empty-state disc.

Ink & surfaces

Ink 900
#17161C
Titles, names, primary text.
5.3:1 AA
Ink 500
#6C6B76
Secondary text, previews (fixes the brown text bug).
Ink 300
#9997A3
Timestamps, placeholders, inactive tab icons.
Cloud
#FFFFFF
Cards, pills, tab bar, FABs, calendar.
Paper
#F6F5F2
App background behind cards.
Hairline
#E8E6E1
Borders, dividers, chip outlines.

Signals

Siren
#ED2F4E
LIVE dot + live pins ONLY. Never on buttons.
Mint
#22C55E
"Out now" dot, success states.
Amber
#F59E0B
Private Event pill, Request-Sent pending state.
Azure
#3B82F6
Your location dot on the map. Nothing else is blue.

Category colors (pins & tags)

Magenta · Music/Party
#EF4A9B
Teal · Sports
#14B8A6
Amber · Coffee/Food
#F59E0B
Iris · Default pin
#3D8BFF

MapLibre map colors (you own the style)

Sky / Water
#CFE5F4
Land
#F1EEE7
Roads
#FFFFFF
Buildings
#E3DFD6
3D tops #EFECE5
Parks
#D5EAC8
Labels
#6B675E
White halo 1.2px

Your stack — drop-in theme

All snippets below are TSX + Tailwind, matching the app (Next.js · TypeScript · shadcn/ui · Framer Motion via m. · lucide-react). Add these tokens so the class names resolve:

// tailwind.config.ts — theme.extend
colors: {
  azure: { 100: "#E8F1FF", 500: "#3D8BFF", 600: "#1D6FF2" },
  ink:  { 900: "#17161C", 500: "#6C6B76", 300: "#9997A3" },
  siren: "#ED2F4E",     // LIVE only — never on buttons
  paper: "#F6F5F2",
},
// wrap the app once for framer-motion:
// <LazyMotion features={domAnimation}> … use <m.div/> everywhere
O1

Org page — the page people judge you on

The headline org problem: today an org page is a dashboard pointed at its owner — a teal letter disc, "WELCOME BACK", and a "3 dropped" counter that means nothing to a visitor. Nothing on it says who this org is. The After turns it into a page a stranger can trust in four seconds: a cover photo the org picks, a real logo, two lines of plain English, the three numbers that prove it is real (members · events hosted · since), curated photo highlights, and a photo grid in the same shape the personal profile uses. No calendar sits on the page — just the one next moment the org is hosting, which opens the full Upcoming tab (O2) where every date already lives. Editing reuses the app's own Edit page screen rather than inventing a sheet, and a separate Arrange your page screen handles layout — what shows, and the order of the grid — so neither screen ever gets cluttered.
Before · real screenshot
What2Do org page today — real iPhone screenshot
After · what people see
9:41
Hopkins Outdoors
@hopkinsoutdoors · Student org · Homewood
Weekend trips, night hikes and campfires for anyone who wants out of the library.
128 members  ·  24 events  ·  since 2024
follow
share page
NEXT MOMENT
all dates ›
Night hike · Patapsco
Tonight · 8:30 PM · 14 going
i'm in
HIGHLIGHTS
trips
campfires
trail runs
socials
UPCOMING MOMENTS PEOPLE
Sync Cal
▶ 0:24
Home
Chats
Host
Explore
Page
After · edit in place
9:41
Cancel Editing your page Done
change cover
Hopkins Outdoors
Weekend trips, night hikes and campfires for anyone who wants out of the library.
128 members · 24 events · since 2024 — counted for you, nothing to fill in.
HIGHLIGHTS
+
YOUR POSTS · 18 · NO LIMIT
+ add
Tap anything to change it. Post as much as you like — your page never runs out of room.
After · add anything, no limit
9:41
Add to your page
Photos, videos, anything. No limit and no layout to fill in — new posts go to the front of your grid.
Camera
Library
Link
RECENT
Keep scrolling for everything else — we never cap how much you post.
add 3 to your page
1<CoverPhoto/>
Full-bleed 244px cover the org chooses — the single biggest trust signal on the page. Bleeds under the status bar so the page opens like a magazine, not a form.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-0 h-[244px] overflow-hidden">
  <Image src={org.coverUrl} alt="" fill className="object-cover" priority />
</div>
2<CoverScrim/>
Two-stop gradient — dark at the very top so white status-bar glyphs and the back chip stay legible, dark again at the base so the logo edge reads against any photo.
Shape code — TSX + Tailwind
<div className="pointer-events-none absolute inset-x-0 top-0 h-[244px]
  bg-[linear-gradient(180deg,rgba(23,22,28,.46),transparent_32%,transparent_58%,rgba(23,22,28,.34))]" />
3<BackChip/>
Frosted 34px circle — the back affordance Arad asked for, always present, never a bare arrow lost on a photo.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[62px] grid size-[34px] place-items-center
  rounded-full bg-ink-900/40 backdrop-blur-md">
  <ChevronLeft className="size-4 text-white" />
</button>
4<SharePageChip/>
Share the page itself — this is the link an org sends to bring people to What2Do. Mirrored by the full-width share button below.
Shape code — TSX + Tailwind
<button onClick={() => share(org.url)} className="absolute right-14 top-[62px] grid size-[34px]
  place-items-center rounded-full bg-ink-900/40 backdrop-blur-md">
  <Share2 className="size-[15px] text-white" />
</button>
5<MoreChip/>
Overflow — report, mute, copy link. Kept as a chip so the cover stays clean.
Shape code — TSX + Tailwind
<button className="absolute right-3.5 top-[62px] grid size-[34px] place-items-center
  rounded-full bg-ink-900/40 backdrop-blur-md">
  <MoreHorizontal className="size-[15px] text-white" />
</button>
6<OrgLogo/>
84px squircle with a 3px Cloud-white ring, overlapping the cover by 46px. A real photo, never a coloured letter tile — the teal "S" disc in the current app is the single most generic thing on the page.
Shape code — TSX + Tailwind
<div className="absolute left-5 top-[198px] size-[84px] overflow-hidden rounded-[26px]
  border-[3px] border-white shadow-[0_8px_22px_rgba(23,22,28,.2)]">
  <Image src={org.logoUrl} alt="" fill className="object-cover" />
</div>
7<OrgName/>
23px/800 at -0.02em. The org's real name leads — "WELCOME BACK" above it in the current app is dashboard language on a page strangers land on.
Shape code — TSX + Tailwind
<h1 className="absolute left-5 top-[296px] text-[23px] font-extrabold tracking-[-0.02em] text-ink-900">
  {org.name}
</h1>
8<VerifiedBadge/>
Azure 600 check disc, 17px, baseline-aligned to the name — verification is a trust cue, so it wears the trust colour, never a decorative one.
Shape code — TSX + Tailwind
{org.verified && (
  <span className="absolute left-[214px] top-[302px] grid size-[17px] place-items-center
    rounded-full bg-azure-600"><Check className="size-2.5 stroke-[3.4] text-white" /></span>
)}
9<HandleLine/>
Handle, org type and campus on one 12.5px line — what kind of thing this is, answered before anyone has to guess.
Shape code — TSX + Tailwind
<p className="absolute left-5 top-[326px] text-[12.5px] font-semibold text-ink-500">
  @{org.handle} · {org.kind} · {org.campus}
</p>
10<AboutLine/>
Two lines of plain English on who they are and who is welcome. The current org page has nowhere to say this at all — it is the "what are they about" gap Arad named.
Shape code — TSX + Tailwind
<p className="absolute inset-x-5 top-[350px] text-[13.5px] leading-relaxed text-ink-500">
  {org.about}
</p>
11<TrustRow/>
Members · events hosted · since — the three numbers a stranger uses to decide if an org is real. "Dropped" is gone: it is a personal-account metric and it confused orgs.
Shape code — TSX + Tailwind
<div className="absolute inset-x-5 top-[404px] flex items-baseline gap-2 text-[12.5px] text-ink-500">
  <b className="text-[14.5px] text-ink-900">{org.memberCount}</b> members
  <Sep /> <b className="text-[14.5px] text-ink-900">{org.eventCount}</b> events hosted
  <Sep /> since {org.foundedYear}
</div>
12<FollowCTA/>
The one primary action, flat Azure 500, lowercase, 150×42 capsule. Following an org is how its events reach a feed — so it outranks everything else on the page.
Shape code — TSX + Tailwind
<Button onClick={follow} className="absolute left-5 top-[438px] h-[42px] w-[150px]
  rounded-full bg-azure-500 text-sm font-bold lowercase text-white">follow</Button>
13<SharePageButton/>
Outline capsule beside the primary — this is the "link they can send" Arad asked for, replacing the "dropped" counter. Copies a what2do.app/@handle URL.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={() => share(org.url)}
  className="absolute left-[180px] top-[438px] h-[42px] w-[136px] rounded-full
  border-[1.5px] border-[#E8E6E1] bg-white text-sm font-bold text-ink-900">share page</Button>
14<SaveOrgButton/>
Quiet 42px outline circle — save an org for later without committing to follow.
Shape code — TSX + Tailwind
<Button variant="outline" size="icon" className="absolute left-[326px] top-[438px] size-[42px]
  rounded-full border-[1.5px] border-[#E8E6E1] bg-white"><Star className="size-4" /></Button>
15<NextMomentLabel/>
NEXT MOMENT — the same eyebrow the personal profile uses, so a page and a profile speak one language.
Shape code — TSX + Tailwind
<h2 className="text-[10px] font-extrabold tracking-[0.14em] text-ink-300">NEXT MOMENT</h2>
16<AllDatesLink/>
"all dates ›" — the only route to the calendar from this page. Everything the org has scheduled lives in the Upcoming tab that O2 already builds, so the page never carries a grid.
Shape code — TSX + Tailwind
<Link href={`/@${org.handle}/upcoming`}
  className="text-[10.5px] font-bold text-azure-600">all dates ›</Link>
17<NextMomentCard/>
One event, not a calendar. 58px dark footage card showing only the soonest thing the org is hosting — tapping it opens the full Upcoming tab (O2) with every date. A page answers "is anything on?"; the calendar answers "what else?".
Shape code — TSX + Tailwind
{next && (
  <Link href={`/@${org.handle}/upcoming`} className="relative block h-[58px] overflow-hidden rounded-[15px]">
    <Image src={next.coverUrl} alt="" fill className="object-cover" />
    <div className="absolute inset-0 bg-[linear-gradient(90deg,rgba(23,22,28,.88),rgba(23,22,28,.26))]" />
  </Link>
)}
18<NextMomentTitle/>
14px/800 Cloud white over the gradient, with time and headcount beneath — the two facts that decide whether someone turns up.
Shape code — TSX + Tailwind
<h3 className="absolute left-3 top-2.5 text-sm font-extrabold text-white">{next.title}</h3>
19<HighlightsLabel/>
10px/800 eyebrow at 0.14em — the same quiet section label used across the spec.
Shape code — TSX + Tailwind
<h2 className="absolute left-5 top-[498px] text-[10px] font-extrabold tracking-[0.14em] text-ink-300">
  HIGHLIGHTS
</h2>
20<HighlightBubble/>
76px rounded-24 photo albums the org curates and reorders — trips, campfires, trail runs, socials. This is the decorative surface that lets an org look like itself instead of a form.
Shape code — TSX + Tailwind
{org.highlights.map(h => (
  <button key={h.id} className="size-[76px] shrink-0 overflow-hidden rounded-[24px]">
    <Image src={h.coverUrl} alt="" width={76} height={76} className="size-full object-cover" />
  </button>
))}
21<HighlightCaption/>
10.5px centred caption under each album — lowercase, human, never a category enum.
Shape code — TSX + Tailwind
<span className="mt-1.5 block w-[76px] text-center text-[10.5px] font-semibold text-ink-500">
  {h.label}
</span>
22<PageTabs/>
UPCOMING → MOMENTS → PEOPLE, in Arad's order, as an underlined text row instead of the heavy grey segmented pill. Upcoming leads because an org page exists to get people to a next thing.
Shape code — TSX + Tailwind
<Tabs defaultValue="upcoming">
  <TabsList className="absolute inset-x-5 top-[628px] h-[34px] justify-start gap-[30px]
    rounded-none border-b border-[#E8E6E1] bg-transparent p-0">
    {['UPCOMING','MOMENTS','PEOPLE'].map(t => (
      <TabsTrigger key={t} value={t.toLowerCase()}
        className="rounded-none border-b-[2.5px] border-transparent px-0 text-[12.5px]
        font-bold tracking-[0.06em] text-ink-300 data-[state=active]:border-azure-500
        data-[state=active]:text-ink-900 data-[state=active]:shadow-none">{t}</TabsTrigger>
    ))}
  </TabsList>
</Tabs>
23<SyncCalButton/>
Azure capsule with a rotate glyph on the tab row — one tap opens the sync sheet in O5. Owner-only: a visitor never sees it. Putting it here rather than three levels down in settings is the difference between an org syncing once and never finding it.
Shape code — TSX + Tailwind
{isOwner && (
  <Button size="sm" onClick={openSyncSheet}
    className="absolute right-5 top-[546px] h-7 gap-1.5 rounded-full bg-azure-500 px-3.5
    text-[11.5px] font-bold text-white"><RefreshCw className="size-3.5" /> Sync Cal</Button>
)}
24<PostTile/>
112px rounded-12 tiles, three across — the same grid language as the personal profile. This is what replaced the calendar: the pictures an org chooses to be known by, in the space fourteen date cells used to take.
Shape code — TSX + Tailwind
<div className="grid grid-cols-3 gap-2">
  {org.posts.map(p => (
    <Link key={p.id} href={`/p/${p.id}`} className="relative aspect-square overflow-hidden rounded-xl">
      <Image src={p.thumbUrl} alt="" fill className="object-cover" />
    </Link>
  ))}
</div>
25<TileVideoChip/>
Dark glass duration chip on video tiles — matches the ▶ 0:24 treatment on the personal profile grid exactly.
Shape code — TSX + Tailwind
{p.type === 'video' && (
  <span className="absolute bottom-1.5 left-1.5 rounded-[5px] bg-ink-900/60 px-1.5 text-[9px]
    font-bold text-white">▶ {p.duration}</span>
)}
26<TabBar/>
Frosted Cloud-white tab bar with the leveled azure Host button. The fifth tab reads "Page" in org mode, not "Profile" — an org has a page, a person has a profile.
Shape code — TSX + Tailwind
<nav className="absolute inset-x-0 bottom-0 flex h-[74px] items-start justify-around
  border-t border-[#E8E6E1] bg-white/95 pt-3.5 backdrop-blur-xl">
  {tabs.map(t => <TabItem key={t.id} {...t} active={t.id === 'page'} />)}
</nav>
27<EditModeBar/>
Cancel · Editing your page · Done. The only chrome edit mode adds — there is no separate form screen, because the page itself is the form.
Shape code — TSX + Tailwind
<header className="sticky top-0 flex h-12 items-center justify-between border-b bg-[#F9F8F6] px-[18px]">
  <button className="text-sm font-semibold text-ink-500">Cancel</button>
  <span className="text-sm font-extrabold">Editing your page</span>
  <button className="text-sm font-extrabold text-azure-600">Done</button>
</header>
28<CoverEditTarget/>
The cover dims and grows one white change cover chip. You change the picture by tapping the picture, not by finding a row labelled "Cover photo".
Shape code — TSX + Tailwind
<button onClick={pickCover} className="absolute left-1/2 top-[60px] h-[30px] -translate-x-1/2
  rounded-full bg-white/95 px-3.5 text-xs font-bold">change cover</button>
29<LogoCameraBadge/>
26px azure camera badge on the logo corner — the universal "this image is replaceable" mark.
Shape code — TSX + Tailwind
<button onClick={pickLogo} className="absolute -bottom-1 -right-1 grid size-[26px] place-items-center
  rounded-full border-2 border-white bg-azure-500"><Camera className="size-3 text-white" /></button>
30<EditableText/>
Name and about sit on a dashed azure underline with a pencil at the end — live text you type into, in place, at the size it will actually appear. A labelled input can never show you that.
Shape code — TSX + Tailwind
<div className="border-b-[1.5px] border-dashed border-azure-500 pb-1.5">
  <ContentEditable html={org.name} onChange={setName}
    className="text-xl font-extrabold outline-none" />
</div>
31<AutoCountedLine/>
The stats stay grey and untouchable, with the reason attached — "counted for you, nothing to fill in". Fewer fields is the point.
Shape code — TSX + Tailwind
<p className="text-[11px] leading-snug text-ink-300">
  {members} members · {events} events · since {year} — counted for you, nothing to fill in.
</p>
32<AddHighlightBubble/>
A dashed + bubble on the end of the highlights row. Albums are added by tapping where the next one would go.
Shape code — TSX + Tailwind
<button onClick={newHighlight} className="size-[62px] shrink-0 rounded-[20px] border-[1.5px]
  border-dashed border-azure-500 text-2xl font-light text-azure-500">+</button>
33<PostCountLabel/>
YOUR POSTS · 18 · NO LIMIT — the ceiling question answered in the label itself.
Shape code — TSX + Tailwind
<h2 className="text-[10px] font-extrabold tracking-[0.14em] text-ink-300">
  YOUR POSTS · {posts.length} · NO LIMIT
</h2>
34<AddPostTile/>
A dashed azure + add tile permanently first in the grid. The grid is an append-only feed, not a set of slots to arrange — posting never runs out of somewhere to go.
Shape code — TSX + Tailwind
<button onClick={openAdd} className="aspect-square rounded-xl border-[1.5px] border-dashed
  border-azure-500 bg-[#F5F9FF] text-azure-600">
  <span className="block text-3xl font-light">+</span>
  <span className="text-[11px] font-bold">add</span>
</button>
35<PostTileEdit/>
Existing posts sit behind the add tile, newest first, scrolling on forever. No drag handles and no slot numbers — order is simply when you posted.
Shape code — TSX + Tailwind
{posts.map(p => (
  <button key={p.id} onClick={() => openPost(p)} className="relative aspect-square overflow-hidden rounded-xl">
    <Image src={p.thumbUrl} alt="" fill className="object-cover" />
  </button>
))}
36<EditModeNote/>
One line pinned above the safe area: tap anything to change it, post as much as you like. Two sentences replace an entire settings screen.
Shape code — TSX + Tailwind
<p className="sticky bottom-3 rounded-[10px] bg-white/95 py-[7px] text-center text-[11px]
  leading-snug text-ink-300">Tap anything to change it. Post as much as you like.</p>
37<AddSheet/>
596px sheet that opens from the + tile. One screen, three sources, and a picker — no steps, no wizard, nothing to configure.
Shape code — TSX + Tailwind
<Sheet open={adding}><SheetContent side="bottom" className="h-[596px] rounded-t-3xl">…</SheetContent></Sheet>
38<NoLimitLine/>
"No limit and no layout to fill in — new posts go to the front of your grid." States the model in one sentence so nobody goes looking for a layout step.
Shape code — TSX + Tailwind
<SheetDescription className="text-xs leading-snug text-ink-500">
  Photos, videos, anything. No limit and no layout to fill in.
</SheetDescription>
39<SourceTile/>
Camera · Library · Link as three equal 104×64 Paper tiles. Every way media gets in, on one row.
Shape code — TSX + Tailwind
<div className="grid grid-cols-3 gap-2.5">
  {SOURCES.map(s => (
    <button key={s.id} onClick={s.open} className="h-16 rounded-2xl bg-paper">
      <s.Icon className="mx-auto size-5" />
      <span className="mt-1 block text-[11px] font-bold">{s.label}</span>
    </button>
  ))}
</div>
40<RecentTile/>
The camera roll inline, selected tiles ringed 3px azure. Multi-select means an org posts a whole night in one go rather than one photo at a time.
Shape code — TSX + Tailwind
{recent.map(m => (
  <button key={m.id} onClick={() => toggle(m.id)}
    className={cn('relative aspect-square overflow-hidden rounded-xl',
      picked.has(m.id) && 'ring-[3px] ring-azure-500')}>
    <Image src={m.thumbUrl} alt="" fill className="object-cover" />
  </button>
))}
41<SelectTick/>
Azure disc with a white check when picked, translucent ink when not — the state is on the tile, never in a counter somewhere else.
Shape code — TSX + Tailwind
<span className={cn('absolute right-1.5 top-1.5 grid size-5 place-items-center rounded-full border-[1.5px] border-white',
  picked ? 'bg-azure-500' : 'bg-ink-900/35')}>{picked && <Check className="size-2.5 text-white" />}</span>
42<NoCapNote/>
"We never cap how much you post." Said once here and once on the page, because storage anxiety is what stops orgs posting.
Shape code — TSX + Tailwind
<p className="text-center text-[11px] text-ink-300">
  Keep scrolling for everything else — we never cap how much you post.
</p>
43<AddCTA/>
Azure capsule carrying the live count — "add 3 to your page". They land at the front of the grid immediately; there is no publish step after this.
Shape code — TSX + Tailwind
<Button onClick={commit} className="h-12 w-full rounded-full bg-azure-500 text-[14.5px]
  font-bold lowercase text-white">add {picked.size} to your page</Button>
O2

Upcoming — two weeks, so the day sheet fits

The month grid was eating the screen. A full August takes ~430px, which pushes the day detail off the bottom — so an org taps a date and cannot see what it posted. The After defaults to two weeks (124px) with a month option one tap away, and pins the day sheet underneath: tap a day, its line-up is already there. Empty days carry a dashed placeholder rather than nothing, and the empty state gives two ways out — post your first event, or sync a calendar you already run — with the honest note that imported events arrive as drafts the org chooses to publish.
Before · real screenshot
What2Do org upcoming tab today — real iPhone screenshot
After · two weeks + day sheet
9:41
Hopkins Outdoors
@hopkinsoutdoors
UPCOMINGMOMENTSPEOPLE
August
2 weeks ⌄
Today
SunMonTueWedThuFriSat
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Wednesday, August 13
2 events you're hosting · 41 going
LIVE
Campfire & s'mores
8:30 PM · Wyman Quad · 28 going
manage
Gear swap
2:00 PM · Levering Hall · 13 going
imported from Google Calendar
post an event on this day
share this day's line-up
After · nothing posted yet
9:41
Hopkins Outdoors
@hopkinsoutdoors
UPCOMINGMOMENTSPEOPLE
August
2 weeks ⌄
Today
SunMonTueWedThuFriSat
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Nothing on August 13 yet
An empty page is the one thing that loses people. Put one thing up, or bring across what you already run elsewhere.
post your first event
Sync a calendar you already run
Google, Apple or Outlook. Events land here as drafts — you choose which ones go public.
set up sync
Tap any empty day above to post straight onto it.
1<OrgHeader/>
Compact 34px identity strip — logo, name, handle, settings. Replaces the giant "WELCOME BACK" block so the calendar starts 300px higher up the screen.
Shape code — TSX + Tailwind
<header className="absolute inset-x-4 top-[58px] flex h-[34px] items-center gap-2.5">
  <Avatar className="size-[30px] rounded-[10px]"><AvatarImage src={org.logoUrl} /></Avatar>
  <div className="flex-1"><p className="text-[15.5px] font-extrabold">{org.name}</p>
  <p className="text-[10.5px] font-semibold text-ink-300">@{org.handle}</p></div>
  <Button size="icon" variant="ghost" className="size-7 rounded-full bg-paper"><Settings className="size-4" /></Button>
</header>
2<PageTabs/>
UPCOMING first — Arad's order. An org opens its page to check what is coming, not to browse its own member list.
Shape code — TSX + Tailwind
<TabsList className="absolute inset-x-4 top-[106px] h-8 justify-start gap-[52px] rounded-none
  border-b border-[#E8E6E1] bg-transparent p-0">…</TabsList>
3<MonthTitle/>
24px/800 month label — still tells you where you are even though only two weeks are on screen.
Shape code — TSX + Tailwind
<h2 className="absolute left-4 top-[150px] text-2xl font-extrabold tracking-[-0.02em]">
  {format(cursor, 'MMMM')}
</h2>
4<RangeToggle/>
The fix for the whole complaint: 2 weeks is the default, month is one tap away. A full month grid is 430px tall and buries the day sheet — two weeks is 124px and leaves room for it.
Shape code — TSX + Tailwind
<Select value={range} onValueChange={setRange}>
  <SelectTrigger className="absolute right-[78px] top-[154px] h-[26px] rounded-full bg-paper
    px-[11px] text-[11.5px] font-bold">2 weeks</SelectTrigger>
  <SelectContent><SelectItem value="2w">2 weeks</SelectItem><SelectItem value="m">Month</SelectItem></SelectContent>
</Select>
5<TodayPill/>
Outline capsule that snaps the range back to the week containing today.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={goToday} className="absolute right-4 top-[154px] h-[26px]
  rounded-full border-[1.5px] px-3 text-[11.5px] font-bold">Today</Button>
6<WeekdayRow/>
Sun–Sat in 10px/700 Ink 300 — quiet, because the numbers below are what people scan.
Shape code — TSX + Tailwind
<div className="absolute inset-x-4 top-[190px] grid grid-cols-7 text-center text-[10px]
  font-bold text-ink-300">{DOW.map(d => <span key={d}>{d}</span>)}</div>
7<DayNumber/>
14px numbers on a 51.5px column, 62px rows. Two rows only — the org sees this week and next, which is the horizon an event page actually operates on.
Shape code — TSX + Tailwind
<button onClick={() => select(day)} className="relative h-[62px] w-full">
  <span className="text-sm font-semibold">{day.getDate()}</span>
</button>
8<TodayDisc/>
Filled Azure 500 disc behind today's number — one solid mark on the grid, matching the shipped app.
Shape code — TSX + Tailwind
{isToday(day) && <span className="absolute inset-x-0 top-0 mx-auto size-[30px] rounded-full bg-azure-500" />}
9<SelectedRing/>
2px Azure 600 ring marking the day whose sheet is open — ring for selected, fill for today, so the two never read as the same state.
Shape code — TSX + Tailwind
{isSelected(day) && <span className="absolute inset-x-0 -top-0.5 mx-auto size-[34px] rounded-full
  border-2 border-azure-600" />}
10<DayEventThumb/>
36×22 cover thumbnail under any day the org has posted — the calendar shows its own work back to it at a glance, the way the shipped Schedule page already does.
Shape code — TSX + Tailwind
{day.events[0] && (
  <Image src={day.events[0].coverUrl} alt="" width={36} height={22}
    className="absolute inset-x-0 bottom-1.5 mx-auto rounded-md object-cover" />
)}
11<DaySheet/>
414px slide-up pinned under the two-week grid — the whole reason the month view had to go. Tap a day, its line-up is already on screen; no scrolling, no covered sheet.
Shape code — TSX + Tailwind
<Sheet.Content side="bottom" modal={false}
  className="absolute inset-x-0 bottom-0 h-[414px] rounded-t-3xl border-t bg-white
  shadow-[0_-14px_34px_rgba(16,15,18,.14)]">{children}</Sheet.Content>
12<SheetDate/>
Full weekday and date, 17px/800 — the sheet always says which day you are looking at.
Shape code — TSX + Tailwind
<Sheet.Title className="absolute left-5 top-[26px] text-[17px] font-extrabold">
  {format(selected, 'EEEE, MMMM d')}
</Sheet.Title>
13<SheetCount/>
Owner-facing line: how many you are hosting and how many people are coming — real numbers, never a badge.
Shape code — TSX + Tailwind
<p className="absolute left-5 top-[49px] text-[11.5px] font-semibold text-ink-500">
  {n} events you're hosting · {going} going
</p>
14<HostedEventCard/>
Dark footage card = happening. The org sees its own event exactly the way an attendee will, which is what makes a bad cover photo obvious.
Shape code — TSX + Tailwind
<article className="absolute inset-x-5 top-[78px] h-[104px] overflow-hidden rounded-2xl">
  <Image src={ev.coverUrl} alt="" fill className="object-cover" />
  <div className="absolute inset-0 bg-[linear-gradient(90deg,rgba(23,22,28,.86),rgba(23,22,28,.24))]" />
</article>
15<LiveBadge/>
Siren red, LIVE only — never applied to a draft or an imported event.
Shape code — TSX + Tailwind
{ev.isLive && <Badge className="bg-siren text-[9px] font-extrabold tracking-[0.08em]">LIVE</Badge>}
16<ManageChip/>
White-glass chip on the dark card — edit, invite, cancel. Owner actions live on the card itself, not behind a separate management screen.
Shape code — TSX + Tailwind
<Button size="sm" variant="secondary" className="absolute right-[34px] top-[146px] h-[26px]
  rounded-full bg-white/90 px-3 text-[11.5px] font-bold">manage</Button>
17<ImportedEventRow/>
Paper-grey row for a second event that day, tagged imported from Google Calendar in Azure 600 — the org can always tell what it typed from what a sync brought in.
Shape code — TSX + Tailwind
<li className="flex h-[74px] items-center gap-3 rounded-2xl bg-paper p-2.5">
  <Image src={ev.coverUrl} alt="" width={54} height={54} className="rounded-xl object-cover" />
  <div><p className="text-sm font-extrabold">{ev.title}</p>
  <p className="text-[11px] text-ink-500">{ev.time} · {ev.place} · {ev.going} going</p>
  {ev.source && <p className="text-[10.5px] font-bold text-azure-600">imported from {ev.source}</p>}</div>
</li>
18<PostOnThisDay/>
The primary action carries the selected date with it — tapping a day and then "post" never makes the org re-pick the date.
Shape code — TSX + Tailwind
<Button onClick={() => openComposer({ date: selected })}
  className="absolute inset-x-5 top-[284px] h-[46px] rounded-full bg-azure-500 text-sm
  font-bold lowercase text-white">post an event on this day</Button>
19<ShareLineUp/>
Outline capsule that produces a shareable link to that day's line-up — an org's most-sent message is "here is what we have on".
Shape code — TSX + Tailwind
<Button variant="outline" onClick={() => share(dayUrl)}
  className="absolute inset-x-5 top-[340px] h-[42px] rounded-full border-[1.5px] text-[13.5px]
  font-bold">share this day's line-up</Button>
20<EmptyDayCell/>
Dashed 36×22 placeholder on every empty day — the locked language of the spec is light dashed = potential. The grid itself asks to be filled instead of just being blank.
Shape code — TSX + Tailwind
{!day.events.length && (
  <span className="absolute inset-x-0 bottom-1.5 mx-auto h-[22px] w-9 rounded-md
    border border-dashed border-[#E8E6E1]" />
)}
21<EmptyDayTitle/>
"Nothing on August 13 yet" — names the actual day. The shipped empty state says "Nothing scheduled yet" with no date, so it reads as a dead page rather than a free slot.
Shape code — TSX + Tailwind
<Sheet.Title className="absolute left-5 top-[26px] text-[17px] font-extrabold">
  Nothing on {format(selected, 'MMMM d')} yet
</Sheet.Title>
22<EmptyReason/>
One honest sentence on why it matters, then two ways out. No illustration, no shrug.
Shape code — TSX + Tailwind
<Sheet.Description className="absolute inset-x-5 top-[50px] text-[12.5px] leading-relaxed
  text-ink-500">An empty page is the one thing that loses people.</Sheet.Description>
23<PostFirstEvent/>
Full-width Azure 500 capsule — posting is the primary path. Sync is the shortcut for orgs that already run a calendar elsewhere.
Shape code — TSX + Tailwind
<Button onClick={openComposer} className="absolute inset-x-5 top-[112px] h-12 rounded-full
  bg-azure-500 text-[14.5px] font-bold lowercase text-white">post your first event</Button>
24<SyncPromptCard/>
Dashed azure card = a real thing that does not exist yet. This is the "direct them to sync a calendar" call to action, sitting on the empty day rather than buried in settings.
Shape code — TSX + Tailwind
<Card className="absolute inset-x-5 top-[174px] h-[104px] rounded-2xl border-[1.5px]
  border-dashed border-azure-500 bg-white p-3.5">…</Card>
25<SyncExplainer/>
Answers the question before it is asked: imported events arrive as drafts, and the org chooses which go public. Nothing from a personal calendar is ever published automatically.
Shape code — TSX + Tailwind
<p className="text-[11.5px] leading-snug text-ink-500">
  Google, Apple or Outlook. Events land here as drafts — you choose which ones go public.
</p>
26<SetUpSyncButton/>
Small azure capsule opening the sync sheet in O5 — same wording as the shipped "Set up calendar sync" so the path stays familiar.
Shape code — TSX + Tailwind
<Button size="sm" onClick={openSync} className="h-6 rounded-full bg-azure-500 px-3
  text-[11.5px] font-bold lowercase text-white">set up sync</Button>
27<TapAnyDayHint/>
Teaches the gesture Arad asked for — tapping an empty day prompts a post, with the date already filled in.
Shape code — TSX + Tailwind
<p className="absolute inset-x-5 top-[296px] text-center text-[11.5px] leading-relaxed
  text-ink-300">Tap any empty day above to post straight onto it.</p>
O3

Moments — proof the org actually shows up

The shipped grid is three dark squares behind a HOSTED / JOINED radio pair, with no sense of which moment matters. The After gives the org a pinned hero it chooses — the one moment a stranger should see first — then a clean 3-up grid, a real count on each segment, and an honest note that moments expire in 30 days unless pinned. "Joined" becomes Tagged: an org does not join things, its people tag it.
Before · real screenshot
What2Do org moments tab today — real iPhone screenshot
After
9:41
Hopkins Outdoors
@hopkinsoutdoors
UPCOMINGMOMENTSPEOPLE
Hosted · 24
Tagged · 61
+ add
PINNED — WHAT YOU WANT PEOPLE TO SEE FIRST
Campfire & s'mores
last Friday · 28 there · 340 views
ALL MOMENTS
Moments stay up for 30 days unless you pin them.
1<HostedSegment/>
Ink-filled capsule carrying a real count — "Hosted · 24" beats a crowned radio button with no number.
Shape code — TSX + Tailwind
<ToggleGroupItem value="hosted" className="h-[30px] rounded-full bg-ink-900 px-3.5 text-[11.5px]
  font-bold text-white">Hosted · {hosted.length}</ToggleGroupItem>
2<TaggedSegment/>
Paper-grey inactive twin. "Tagged" replaces "Joined" — an org does not attend, its members tag it into their moments.
Shape code — TSX + Tailwind
<ToggleGroupItem value="tagged" className="h-[30px] rounded-full bg-paper px-3.5 text-[11.5px]
  font-bold text-ink-500">Tagged · {tagged.length}</ToggleGroupItem>
3<AddMomentChip/>
Azure capsule so posting a moment is reachable from the tab itself, not only from the global Host button.
Shape code — TSX + Tailwind
<Button size="sm" onClick={openCamera} className="h-[30px] rounded-full bg-azure-500 px-3.5
  text-[11.5px] font-bold lowercase text-white">+ add</Button>
4<PinnedLabel/>
Eyebrow that explains the row instead of naming it — "what you want people to see first" tells an org why to bother pinning.
Shape code — TSX + Tailwind
<h2 className="text-[10px] font-extrabold tracking-[0.14em] text-ink-300">
  PINNED — WHAT YOU WANT PEOPLE TO SEE FIRST
</h2>
5<PinnedHero/>
One 150px hero the org picks. This is the org's best night, given the space it deserves — the single strongest trust signal after the cover photo.
Shape code — TSX + Tailwind
<article className="relative h-[150px] overflow-hidden rounded-[18px]">
  <Image src={pinned.coverUrl} alt="" fill className="object-cover" />
  <div className="absolute inset-0 bg-[linear-gradient(180deg,transparent_44%,rgba(23,22,28,.72))]" />
</article>
6<MomentTile/>
113px rounded-14 tiles, 3 per row with 8px gutters — square photos, no crowns stamped on every one.
Shape code — TSX + Tailwind
<div className="grid grid-cols-3 gap-2">
  {moments.map(m => (
    <Image key={m.id} src={m.coverUrl} alt="" width={113} height={113}
      className="aspect-square rounded-[14px] object-cover" />
  ))}
</div>
7<ExpiryNote/>
States the rule plainly so an org is never surprised that a moment vanished — and gives it a reason to use pinning.
Shape code — TSX + Tailwind
<p className="text-center text-[11px] font-semibold text-ink-300">
  Moments stay up for 30 days unless you pin them.
</p>
O4

People — a link to share, not a "dropped" count

"Dropped" is gone. It is a personal-account metric and, as Arad put it, it only confuses an org. In its place the tab leads with the thing an org actually needs: its own link — what2do.app/@handle — with copy, a printable QR poster, and invite. Then real leadership rows, a member face-pile that says what membership does ("every event you post lands in their feed"), and one dashed card on growing past the contacts you already have. Two identical "Invite your people" empty states become one place to act.
Before · real screenshot
What2Do org people tab today — real iPhone screenshot
After
9:41
Hopkins Outdoors
@hopkinsoutdoors
UPCOMINGMOMENTSPEOPLE
Your page link
what2do.app/@hopkinsoutdoors
copy link
QR poster
invite
LEADERSHIP · 3
Maya R.
president
manage
Kofi A.
trips lead
manage
Dan S.
treasurer
manage
MEMBERS · 128
and 123 more — every event you post lands in their feed
Grow past your own contacts
Put the QR on a flyer or drop the link in your group chat — anyone who opens it lands on your page, not a sign-up wall.
1<PageLinkCard/>
Cream card at the very top — the org's shareable address is the first thing on the tab, because sending it is the single highest-value thing an org does here.
Shape code — TSX + Tailwind
<Card className="rounded-[18px] bg-[#FBF6EC] p-4">
  <p className="text-[13.5px] font-extrabold">Your page link</p>
  <p className="mt-1 text-sm font-bold text-azure-600">{org.publicUrl}</p>
</Card>
2<CopyLinkButton/>
Primary azure capsule — one tap puts the link on the clipboard, ready for a group chat.
Shape code — TSX + Tailwind
<Button size="sm" onClick={() => copy(org.publicUrl)} className="h-[26px] rounded-full
  bg-azure-500 px-3 text-[11.5px] font-bold lowercase text-white">copy link</Button>
3<QRPosterButton/>
Generates a printable poster with the org's QR — how a campus org actually recruits, on a corkboard.
Shape code — TSX + Tailwind
<Button size="sm" variant="outline" onClick={makePoster}
  className="h-[26px] rounded-full border-[1.5px] px-3 text-[11.5px] font-bold">QR poster</Button>
4<InviteButton/>
Direct invites by handle or contacts — the narrow path, kept beside the broad one rather than replacing it.
Shape code — TSX + Tailwind
<Button size="sm" variant="outline" onClick={openInvite}
  className="h-[26px] rounded-full border-[1.5px] px-3 text-[11.5px] font-bold">invite</Button>
5<SectionCount/>
Section labels carry their count — LEADERSHIP · 3, MEMBERS · 128. Real numbers, never a placeholder.
Shape code — TSX + Tailwind
<h2 className="text-[10px] font-extrabold tracking-[0.14em] text-ink-300">
  LEADERSHIP · {leaders.length}
</h2>
6<LeaderRow/>
Paper rows with a real photo, a name and the role they actually hold — president, trips lead, treasurer. The shipped page only offers an empty "promote a few" pitch.
Shape code — TSX + Tailwind
<li className="flex h-[54px] items-center gap-2.5 rounded-2xl bg-paper px-2.5">
  <Avatar className="size-9"><AvatarImage src={p.avatarUrl} /></Avatar>
  <div className="flex-1"><p className="text-[13.5px] font-bold">{p.name}</p>
  <p className="text-[11px] text-ink-500">{p.role}</p></div>
  <Button variant="ghost" size="sm" className="text-[11.5px] text-ink-300">manage</Button>
</li>
7<MemberFacePile/>
Five overlapping 38px faces with white rings, then plain English on what membership does — "every event you post lands in their feed". Members are people, not a number.
Shape code — TSX + Tailwind
<div className="flex items-center">
  {members.slice(0,5).map((m,i) => (
    <Avatar key={m.id} style={{ marginLeft: i ? -8 : 0 }}
      className="size-[38px] ring-[2.5px] ring-white"><AvatarImage src={m.avatarUrl} /></Avatar>
  ))}
  <p className="ml-3 text-[12.5px] text-ink-500">and {members.length - 5} more</p>
</div>
8<GrowCard/>
Dashed azure = potential, per the locked language. Tells an org how to reach people who are not already in its phone — and promises the link opens the page, not a sign-up wall.
Shape code — TSX + Tailwind
<Card className="rounded-2xl border-[1.5px] border-dashed border-azure-500 bg-white p-4">
  <p className="text-[13.5px] font-extrabold">Grow past your own contacts</p>
  <p className="mt-1 text-[11.5px] leading-snug text-ink-500">…lands on your page, not a sign-up wall.</p>
</Card>
O5

Calendar sync — say what it actually does

Arad's question, answered in the UI: "when I sync my Google calendar, does that sync all my events? does it make them public? right now it just loads." So the After states the contract before you connect — we read the calendar you pick and turn each event into a draft; nothing goes public until you say so — and the second frame is the state the shipped app never shows: a green connected banner with when it last checked and how many events it found, every imported event with its own publish toggle, and a plain-English note on where a published event shows up (your Upcoming tab, your followers' calendar icon, and the map). The feed URL is now the recommended path since it is the one that actually re-syncs.
Before · real screenshot
What2Do calendar sync sheet today — real iPhone screenshot
After · before you connect
9:41
Bring your calendar in
We read the calendar you pick and turn each event into a draft on your page. Nothing goes public until you say so — your personal events never appear anywhere.
RECOMMENDED
Paste a calendar link
The public .ics address from Google, Apple or Outlook. We re-check it every hour, so edits over there show up here.
paste link
Connect Google Calendar
Sign in once. Read-only — we never write to it.
Upload an .ics file
One-off import. Does not stay in sync.
WHAT PEOPLE WILL SEE
Only the drafts you publish. Titles, times and places of everything else stay private to you — busy blocks, lectures and personal entries are never imported.
connect a calendar
After · permission popup
9:41
What2Do wants to read your calendar
hopkinsoutdoors@gmail.com
See events on your calendars
so they can become drafts here
Create or change your events
never — we only read
Private and busy-only entries
never imported, never shown
Cancel
Allow
After · syncing
9:41
Syncing your calendar
Reading Google Calendar · 8 of 12 events
Campfire night
imported as draft
Gear swap
imported as draft
Trail clean-up
reading…
You can close this — we'll finish in the background and tell you when it's done.
run in the background
After · imported
9:41
12 events imported
All saved as drafts. Nothing is public yet — you decide what people see.
9 upcoming · 3 already passed
Past events were skipped. Busy blocks and private entries were never read.
review and publish
keep them all as drafts
After · connected
9:41
Calendar sync
Google Calendar connected
Last checked 6 minutes ago · 12 events found · next check in 54 min
sync now
IMPORTED — CHOOSE WHAT GOES PUBLIC
Campfire night
Wed 13 · 8:30 PM
Wyman Quad 3 photos added
Gear swap
Wed 13 · 2:00 PM
Levering Hall add photos or video
Trail clean-up
Sat 16 · 9:00 AM
add location add photos or video
DRAFT
Exec meeting
Mon 18 · 6:00 PM
add location add photos or video
DRAFT
Two are live. An imported event arrives with no place and no pictures — add them here and it stops looking like a diary entry.
WHERE THEY SHOW UP
Published events appear in your Upcoming tab, in the calendar icon on every follower's account, and on the map — which is why a location matters. Drafts appear only to you.
disconnect calendar
After · pick a location
9:41
Where is it?
Trail clean-up · Sat 16 · 9:00 AM
Patap
YOUR PLACES
Wyman Quad used 6 times
Levering Hall used 4 times
Patapsco Valley used 3 times MATCH
Homewood Field used twice
Tap one to reuse it, or keep typing to add somewhere new. Every place you type is saved here for next time.
use Patapsco Valley
1<SyncSheet/>
614px sheet — taller than the shipped one because the contract has to fit above the fold, not below three scrolling cards.
Shape code — TSX + Tailwind
<Sheet.Content side="bottom" className="h-[614px] rounded-t-3xl bg-white">{children}</Sheet.Content>
2<SheetTitle/>
"Bring your calendar in" — an outcome, not a feature name.
Shape code — TSX + Tailwind
<Sheet.Title className="text-[19px] font-extrabold tracking-[-0.01em]">Bring your calendar in</Sheet.Title>
3<SyncContract/>
The single most important paragraph on the screen: events arrive as drafts, nothing publishes itself, personal events never appear. Stated before the buttons, so nobody connects on a guess.
Shape code — TSX + Tailwind
<Sheet.Description className="text-[12.5px] leading-relaxed text-ink-500">
  We read the calendar you pick and turn each event into a <b>draft</b> on your page.
  Nothing goes public until you say so.
</Sheet.Description>
4<RecommendedCard/>
The .ics feed is promoted to RECOMMENDED because it is the only option that keeps re-syncing — the shipped sheet buries it under a Google button that admits import "is not live yet".
Shape code — TSX + Tailwind
<Card className="rounded-2xl bg-[#FBF6EC] p-3.5">
  <Badge className="bg-azure-500 text-[9px] font-extrabold">RECOMMENDED</Badge>
  <p className="mt-2 text-sm font-extrabold">Paste a calendar link</p>
</Card>
5<PasteLinkButton/>
Azure capsule opening a single paste field — the shortest path to a sync that keeps working.
Shape code — TSX + Tailwind
<Button size="sm" onClick={openPaste} className="h-[26px] rounded-full bg-azure-500 px-3
  text-[11.5px] font-bold lowercase text-white">paste link</Button>
6<GoogleOption/>
Secondary Paper row, with the honest limit inline — read-only, never writes back to their calendar.
Shape code — TSX + Tailwind
<button className="flex h-[62px] w-full items-center rounded-2xl bg-paper px-3.5">
  <div><p className="text-[13.5px] font-bold">Connect Google Calendar</p>
  <p className="text-[11px] text-ink-500">Read-only — we never write to it.</p></div>
  <ChevronRight className="ml-auto size-[15px] text-ink-300" />
</button>
7<FileOption/>
The fallback, labelled with its real drawback — a one-off import that will go stale.
Shape code — TSX + Tailwind
<button className="flex h-[62px] w-full items-center rounded-2xl bg-paper px-3.5">
  <div><p className="text-[13.5px] font-bold">Upload an .ics file</p>
  <p className="text-[11px] text-ink-500">One-off import. Does not stay in sync.</p></div>
</button>
8<PrivacyPanel/>
Outlined panel spelling out what stays private — busy blocks, lectures and personal entries are never imported. This is the half of the answer people are actually anxious about.
Shape code — TSX + Tailwind
<Card className="rounded-2xl border-[1.5px] bg-white p-3.5">
  <p className="text-xs font-extrabold tracking-[0.06em] text-ink-300">WHAT PEOPLE WILL SEE</p>
  <p className="mt-2 text-[11.5px] leading-snug text-ink-500">Only the drafts you publish.</p>
</Card>
9<ConnectCTA/>
One primary capsule at the base of the sheet.
Shape code — TSX + Tailwind
<Button className="absolute inset-x-[22px] bottom-6 h-[46px] rounded-full bg-azure-500
  text-sm font-bold lowercase text-white">connect a calendar</Button>
10<PermissionDialog/>
Centred 392px dialog over a 52% scrim — the one moment in the flow that is a popup rather than a slide-up, because it is a decision that blocks everything behind it.
Shape code — TSX + Tailwind
<Dialog open={step === 'permission'}>
  <DialogOverlay className="bg-[rgba(16,15,18,.52)]" />
  <DialogContent className="rounded-[22px] p-0 shadow-[0_20px_60px_rgba(16,15,18,.34)]">…</DialogContent>
</Dialog>
11<DialogTitle/>
"What2Do wants to read your calendar" — says read in the title, so the scariest question is answered before the list is read.
Shape code — TSX + Tailwind
<DialogTitle className="text-[17px] font-extrabold leading-snug tracking-[-0.01em]">
  What2Do wants to read your calendar
</DialogTitle>
12<AccountLine/>
Which Google account is about to be connected — orgs routinely have three, and picking the wrong one is the most common way this goes wrong.
Shape code — TSX + Tailwind
<p className="text-xs font-semibold text-ink-500">{googleAccount.email}</p>
13<PermissionRow/>
Three rows, and only the first has a Mint tick — the other two carry a grey dash and read "never". Showing what an app will not do is what makes the one thing it does do believable.
Shape code — TSX + Tailwind
{SCOPES.map(s => (
  <li key={s.id} className="flex gap-3">
    <span className={cn('grid size-5 place-items-center rounded-full', s.granted ? 'bg-mint' : 'bg-paper')}>
      {s.granted ? <Check className="size-3 text-white" /> : <Minus className="size-3 text-ink-300" />}
    </span>
    <div><p className="text-[13px] font-bold">{s.title}</p>
    <p className="text-[11px] text-ink-300">{s.note}</p></div>
  </li>
))}
14<AllowButton/>
Azure capsule, and the wider of the two — consenting is the expected path, cancelling is always one tap away beside it.
Shape code — TSX + Tailwind
<Button onClick={grant} className="h-11 flex-1 rounded-full bg-azure-500 text-[13.5px] font-bold
  text-white">Allow</Button>
15<CancelButton/>
Outline capsule, never red — backing out of a connection is not destructive.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={close}
  className="h-11 w-[126px] rounded-full border-[1.5px] text-[13.5px] font-bold">Cancel</Button>
16<SyncingSheet/>
400px slide-up that replaces the dialog the moment permission is granted. This is the screen the shipped app is missing — today the calendar simply spins with no sheet, no progress and no end.
Shape code — TSX + Tailwind
<Sheet open={step === 'syncing'}>
  <SheetContent side="bottom" className="h-[400px] rounded-t-3xl">…</SheetContent>
</Sheet>
17<SyncStepLine/>
Names the source and the count — "Reading Google Calendar · 8 of 12 events". A spinner says something is happening; this says what and how much is left.
Shape code — TSX + Tailwind
<p className="text-[12.5px] font-semibold text-ink-500">
  Reading {source.name} · {done} of {total} events
</p>
18<ProgressTrack/>
8px Paper track — determinate, because the event count is known as soon as the feed is fetched. An indeterminate bar here would be a lie.
Shape code — TSX + Tailwind
<Progress value={(done / total) * 100} className="h-2 bg-paper" />
19<ProgressFill/>
Azure 500 fill at the real percentage.
Shape code — TSX + Tailwind
<ProgressPrimitive.Indicator className="h-2 rounded-full bg-azure-500"
  style={{ width: `${(done / total) * 100}%` }} />
20<ImportFeedRow/>
Events land one at a time, each turning Mint with "imported as draft" as it arrives; the one still working stays Paper-grey. The org watches its own calendar come across, which is what makes the sync feel real.
Shape code — TSX + Tailwind
{feed.map(f => (
  <li key={f.id} className={cn('flex h-[52px] items-center gap-3 rounded-2xl px-3',
    f.done ? 'bg-[#F6FBF8]' : 'bg-paper')}>
    <StatusDot done={f.done} />
    <div><p className="text-[13px] font-bold">{f.title}</p>
    <p className={cn('text-[11px] font-semibold', f.done ? 'text-mint' : 'text-ink-300')}>{f.state}</p></div>
  </li>
))}
21<BackgroundNote/>
Promises the org is not trapped watching a bar — the import finishes server-side and notifies.
Shape code — TSX + Tailwind
<p className="text-center text-[11.5px] leading-relaxed text-ink-300">
  You can close this — we'll finish in the background and tell you when it's done.
</p>
22<RunInBackground/>
Outline capsule that dismisses the sheet without cancelling the job — the escape hatch the note just promised.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={dismiss}
  className="h-11 w-full rounded-full border-[1.5px] text-[13.5px] font-bold">run in the background</Button>
23<DoneSheet/>
404px completion slide-up. Sync ends with a result screen, never by silently closing — the org must know what just landed on its page.
Shape code — TSX + Tailwind
<Sheet open={step === 'done'}>
  <SheetContent side="bottom" className="h-[404px] rounded-t-3xl">…</SheetContent>
</Sheet>
24<SuccessTick/>
56px Mint disc — the only large success mark in the whole spec, earned because this is the moment an org has been waiting through a progress bar for.
Shape code — TSX + Tailwind
<span className="mx-auto grid size-14 place-items-center rounded-full bg-mint">
  <Check className="size-[30px] stroke-[3.2] text-white" />
</span>
25<ImportedCount/>
"12 events imported" at 19px/800 — the number first, because that is the question.
Shape code — TSX + Tailwind
<SheetTitle className="text-center text-[19px] font-extrabold">{n} events imported</SheetTitle>
26<DraftPromise/>
Repeats the contract at the exact moment it matters — all saved as drafts, nothing is public yet. Said before connecting and again after importing, so it can never be missed.
Shape code — TSX + Tailwind
<SheetDescription className="text-center text-[12.5px] leading-relaxed text-ink-500">
  All saved as <b>drafts</b>. Nothing is public yet — you decide what people see.
</SheetDescription>
27<ImportSummary/>
Cream card accounting for everything: 9 upcoming, 3 past ones skipped, and a restatement that busy blocks and private entries were never read.
Shape code — TSX + Tailwind
<Card className="rounded-2xl bg-[#FBF6EC] p-4">
  <p className="text-[13px] font-bold">{upcoming} upcoming · {past} already passed</p>
  <p className="mt-1 text-[11px] leading-snug text-ink-500">Past events were skipped.</p>
</Card>
28<ReviewAndPublish/>
Primary azure capsule into the per-event toggle list — the org is walked straight to the decision rather than left to find it.
Shape code — TSX + Tailwind
<Button onClick={goReview} className="h-12 w-full rounded-full bg-azure-500 text-[14.5px]
  font-bold lowercase text-white">review and publish</Button>
29<KeepAsDrafts/>
The do-nothing option, stated plainly. An org that is not ready to publish should be able to leave without feeling it failed a step.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={close}
  className="h-[42px] w-full rounded-full border-[1.5px] text-[13.5px] font-bold">keep them all as drafts</Button>
30<ConnectedBanner/>
Mint success banner — the state the shipped app never reaches, where the calendar "just loads" forever. Sync either says it worked or says what broke.
Shape code — TSX + Tailwind
<Alert className="rounded-2xl bg-[#EAF9EF]">
  <CheckCircle2 className="size-5 text-mint" />
  <AlertTitle className="text-[13.5px] font-extrabold">Google Calendar connected</AlertTitle>
</Alert>
31<LastCheckedLine/>
Last checked · events found · next check. Three facts that turn an invisible background job into something an org can trust.
Shape code — TSX + Tailwind
<AlertDescription className="text-[11.5px] text-ink-500">
  Last checked {ago(sync.lastRun)} · {sync.found} events found · next check in {mins} min
</AlertDescription>
32<ImportedRow/>
86px Paper rows — taller than a list row because each one carries work to finish, not just a switch to flip.
Shape code — TSX + Tailwind
<li className="flex min-h-[86px] items-center rounded-2xl bg-paper px-3.5">
  <div className="flex-1"><p className="text-[13.5px] font-bold">{e.title}</p>
  <p className="text-[11px] text-ink-500">{e.when}</p></div>
</li>
33<InlineFieldPlaceholder/>
Grey add location / add photos or video on a 1px hairline — tap it and you type straight onto the line. A dashed chip announces itself as a control; a hairline just waits, which is what a field that is often skipped should do.
Shape code — TSX + Tailwind
<button onClick={() => edit(e.id, 'place')}
  className="w-[148px] truncate border-b border-[#DCDAD4] pb-1 text-left text-[11.5px]
  font-medium text-ink-300">add location</button>
34<InlineFieldValue/>
The same line once filled — Ink 900 at 700 weight on a faint blue rule. Empty and filled share one shape and one position, so a row reads as done or not done without a single icon.
Shape code — TSX + Tailwind
<button onClick={() => edit(e.id, 'place')}
  className="w-[148px] truncate border-b border-[#C9D6EA] pb-1 text-left text-[11.5px]
  font-bold text-ink-900">{e.place}</button>
35<DraftTag/>
A quiet DRAFT word under any switch that is off — the row states its own status rather than making the org read a toggle position.
Shape code — TSX + Tailwind
{!e.published && <span className="text-[9.5px] font-extrabold tracking-[0.06em] text-ink-300">DRAFT</span>}
36<SyncNowChip/>
Azure capsule on the connected banner — forces a re-check instead of waiting out the hour. Pairs with the Sync Cal button on the org page itself, so the action is reachable from both ends.
Shape code — TSX + Tailwind
<Button size="sm" onClick={syncNow} className="h-6 rounded-full bg-azure-500 px-3 text-[10.5px]
  font-bold lowercase text-white">sync now</Button>
37<PublishToggle/>
Per-event publish switch, azure when on. This is the mechanism behind the promise on the previous frame — the org, not the sync, decides what the world sees.
Shape code — TSX + Tailwind
<Switch checked={e.published} onCheckedChange={v => publish(e.id, v)}
  className="data-[state=checked]:bg-azure-500" />
38<DraftExplainer/>
Reads the toggles back in words — two live, two drafts nobody can see and that do not show on the map.
Shape code — TSX + Tailwind
<p className="text-[11.5px] leading-relaxed text-ink-500">
  {live} are live on your page. The other {draft} stay as drafts.
</p>
39<WhereTheyShowCard/>
Answers "does it show up in the calendar icon on every account?" — yes, for followers, once published. Upcoming tab, followers' calendar, and the map.
Shape code — TSX + Tailwind
<Card className="rounded-2xl bg-[#FBF6EC] p-3.5">
  <p className="text-xs font-extrabold tracking-[0.06em] text-ink-300">WHERE THEY SHOW UP</p>
  <p className="mt-2 text-[11.5px] leading-relaxed text-ink-500">…and on the map at their location.</p>
</Card>
40<DisconnectButton/>
Quiet outline capsule — reversible, and never red, because disconnecting is not a destructive act.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={disconnect}
  className="h-[42px] w-full rounded-full border-[1.5px] text-[13.5px] font-bold">disconnect calendar</Button>
41<LocationSheet/>
Tapping the grey add location line opens a 488px sheet. Never a full page — a location is one short answer.
Shape code — TSX + Tailwind
<Sheet open={editing === 'place'}><SheetContent side="bottom" className="h-[488px] rounded-t-3xl">…</SheetContent></Sheet>
42<LocationInput/>
A single 16px line on an Azure 500 rule — the focused twin of the grey hairline on the row. No boxed input, no label above it.
Shape code — TSX + Tailwind
<Input autoFocus value={q} onChange={e => setQ(e.target.value)}
  className="h-[34px] rounded-none border-0 border-b-[1.5px] border-azure-500 px-0 text-base
  font-semibold focus-visible:ring-0" />
43<SavedPlaceRow/>
Every place the org has ever typed, with how often it used it — type once, tap forever. A campus org runs the same four venues all year, so the second time should never need typing.
Shape code — TSX + Tailwind
{places.map(p => (
  <button key={p.id} onClick={() => choose(p)} className="flex h-12 w-full items-center gap-3 border-b">
    <MapPin className="size-[15px] text-ink-300" />
    <span className="flex-1 text-left"><span className="block text-sm font-semibold">{p.name}</span>
    <span className="block text-[10.5px] text-ink-300">used {p.uses} times</span></span>
  </button>
))}
44<MatchTag/>
Azure MATCH chip on the row the typing is narrowing to — the list filters live, so reuse and typing are the same gesture rather than two modes.
Shape code — TSX + Tailwind
{p.matches(q) && <Badge className="h-5 rounded-md bg-azure-100 px-2 text-[9.5px] font-extrabold text-azure-600">MATCH</Badge>}
45<SaveLocation/>
The capsule names the place it will set. Anything newly typed is added to Your places automatically — the org never manages a venue list.
Shape code — TSX + Tailwind
<Button onClick={commit} className="h-[46px] w-full rounded-full bg-azure-500 text-sm font-bold
  lowercase text-white">use {chosen.name}</Button>
O6

Switch account — remembered, never a login

Switching must never show a sign-up page. Arad hit exactly that. The After says it out loud at the top — both stay signed in; switching never asks for a password — puts the current account in an Azure-tinted row with a check, gives the other account a one-tap switch capsule plus a reason to tap it ("2 unread"), and demotes account creation to a dashed row at the bottom where it belongs. "Add account" became "Create an organization", because that is the only second account this app makes.
Before · real screenshot
What2Do switch accounts sheet today — real iPhone screenshot
After
9:41
Switch account
Both stay signed in. Switching never asks for a password.
Hopkins Outdoors ORG
@hopkinsoutdoors · you're here
arad Sadaghiani
@arad · 2 unread
switch
Create an organization
Attached to this account — one page, one minute
Same phone number, same login — switch as often as you like.
1<SwitchSheet/>
352px sheet — short, because switching is a two-second job and should never feel like account management.
Shape code — TSX + Tailwind
<Sheet.Content side="bottom" className="h-[352px] rounded-t-3xl bg-white">{children}</Sheet.Content>
2<SheetTitle/>
"Switch account", singular — you are performing one switch, not administering a list.
Shape code — TSX + Tailwind
<Sheet.Title className="text-[19px] font-extrabold">Switch account</Sheet.Title>
3<NoPasswordPromise/>
The promise, stated before the rows: both stay signed in, switching never asks for a password. Directly answers the bug Arad hit.
Shape code — TSX + Tailwind
<Sheet.Description className="text-xs text-ink-500">
  Both stay signed in. Switching never asks for a password.
</Sheet.Description>
4<CurrentAccountRow/>
Azure-100 tinted row with an azure check — where you already are, unmistakably, with no button to press.
Shape code — TSX + Tailwind
<li className="flex h-[62px] items-center gap-3 rounded-2xl bg-azure-100 px-3">
  <Avatar className="size-10 rounded-[13px]"><AvatarImage src={org.logoUrl} /></Avatar>
  <div className="flex-1">…</div>
  <span className="grid size-5 place-items-center rounded-full bg-azure-500"><Check className="size-3 text-white" /></span>
</li>
5<OrgTag/>
Azure 600 ORG chip — the one place the two account kinds need telling apart, so it carries the trust colour rather than a neutral grey.
Shape code — TSX + Tailwind
<Badge className="h-4 rounded bg-azure-600 px-1.5 text-[8.5px] font-extrabold tracking-[0.06em]">ORG</Badge>
6<OtherAccountRow/>
The account you are not in, with a reason to go there — "2 unread". Switching is a decision, so give it information.
Shape code — TSX + Tailwind
<li className="flex h-[62px] items-center gap-3 rounded-2xl bg-paper px-3">
  <Avatar className="size-10"><AvatarImage src={me.avatarUrl} /></Avatar>
  <div className="flex-1"><p className="text-sm font-extrabold">{me.name}</p>
  <p className="text-[11.5px] text-ink-500">@{me.handle} · {me.unread} unread</p></div>
</li>
7<SwitchCapsule/>
One lowercase azure capsule. Tapping it swaps the session from a stored token and lands you on that account's home — no password, no re-verification, no page in between.
Shape code — TSX + Tailwind
<Button size="sm" onClick={() => switchTo(acct.id)} className="h-[26px] rounded-full
  bg-azure-500 px-3 text-[11.5px] font-bold lowercase text-white">switch</Button>
8<CreateOrgRow/>
Dashed = does not exist yet. Creation sits below the accounts that do exist, so the common action is never crowded by the rare one.
Shape code — TSX + Tailwind
<button className="h-[52px] w-full rounded-2xl border-[1.5px] border-dashed border-[#E8E6E1]
  bg-white px-3.5 text-left">
  <p className="text-[13.5px] font-bold">Create an organization</p>
  <p className="text-[11px] text-ink-500">Attached to this account — one page, one minute</p>
</button>
9<SameLoginFooter/>
Restates the settled account model: one phone-verified person, organizations attached to it. Nobody ever manages two logins.
Shape code — TSX + Tailwind
<p className="text-center text-[11px] text-ink-300">
  Same phone number, same login — switch as often as you like.
</p>
O7

Org settings — every row says its current value

The shipped settings sheet is a flat list of nouns — Support, Verify phone number, Switch to individual account, Calendar sync, Manage preferences, Manage users — with no indication of what any of them is set to. The After gives every row its current value underneath ("Google Calendar · checked 6 min ago", "leaders only", "128 members · 3 leaders"), so an org can audit its page without opening six screens. "Switch to individual account" becomes a plain back to my personal account button, and the footer states the account model that S12B settled.
Before · real screenshot
What2Do org settings sheet today — real iPhone screenshot
After
9:41
Settings
Hopkins Outdoors
org page · managed by @arad
Page & branding
cover, logo, about, highlights
Calendar sync
Google Calendar · checked 6 min ago
Members & roles
128 members · 3 leaders
Who can post
leaders only
Notifications
RSVPs and mentions
Verify phone number
for SMS login
back to my personal account
Your organization is attached to your personal account. Deleting the page never deletes your account.
1<WhoAmIRow/>
Which page these settings belong to, and who runs it — the shipped sheet gives no such anchor, so it is easy to change the wrong account's settings.
Shape code — TSX + Tailwind
<Card className="flex h-[62px] items-center gap-3 rounded-2xl bg-paper px-3">
  <Avatar className="size-10 rounded-[13px]"><AvatarImage src={org.logoUrl} /></Avatar>
  <div><p className="text-sm font-extrabold">{org.name}</p>
  <p className="text-[11.5px] text-ink-500">org page · managed by @{owner.handle}</p></div>
</Card>
2<SettingRow/>
Hairline-outlined 54px rows on white. Title on top, current value underneath — the single change that turns a menu into a status page.
Shape code — TSX + Tailwind
{rows.map(r => (
  <button key={r.id} className="flex h-[54px] w-full items-center rounded-[14px] border bg-white px-3.5">
    <div className="flex-1 text-left"><p className="text-[13.5px] font-bold">{r.title}</p>
    <p className="text-[11px] text-ink-500">{r.value}</p></div>
    <ChevronRight className="size-[15px] text-ink-300" />
  </button>
))}
3<BackToPersonal/>
Outline capsule in plain words. "Switch to individual account" sounds like a conversion that might cost you the page — this does not.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={() => switchTo('personal')}
  className="h-[46px] w-full rounded-full border-[1.5px] text-[13.5px] font-bold">
  back to my personal account
</Button>
4<AccountModelNote/>
The reassurance an org needs before touching anything here: the page is attached to a person, and deleting one never deletes the other.
Shape code — TSX + Tailwind
<p className="text-center text-[11px] leading-relaxed text-ink-300">
  Your organization is attached to your personal account.
  Deleting the page never deletes your account.
</p>
O8

Create an organization — one page, then done

No Before capture for this one — it is the JHU-ID sign-up flow Arad described but did not screenshot, so this frame is drawn from the brief rather than from a photo. Every ask is here: a back button, Google and Apple as first-class options, JHU ID demoted to an optional badge you can verify later instead of a wall, only two fields, and a single create that makes the account — no chain of pages afterwards. It is reached from the dashed row in O6, and it is never shown when someone is merely switching accounts.
After · the only sign-up page
9:41
Create your
organization
One page. Press create and it exists — you can add photos and details any time after.
NAME
Hopkins Outdoors
HANDLE
@hopkinsoutdoors
✓ available
OR CONTINUE WITH
Continue with Google
Continue with Apple
Have a JHU email? Verify it later for a campus badge.
create
That's the whole sign-up. You'll land straight on your new page.
1<BackButton/>
The back affordance Arad asked for, first thing on the screen — creating a second account must always be abandonable.
Shape code — TSX + Tailwind
<Button variant="ghost" size="icon" onClick={router.back}
  className="absolute left-3.5 top-[60px] size-[34px] rounded-full bg-paper">
  <ChevronLeft className="size-4" />
</Button>
2<PageTitle/>
26px/800 two-line title at -0.025em — one clear statement of what this page does, replacing a cluttered stack of explanatory copy.
Shape code — TSX + Tailwind
<h1 className="absolute inset-x-5 top-28 text-[26px] font-extrabold leading-[1.18]
  tracking-[-0.025em]">Create your<br />organization</h1>
3<OnePagePromise/>
"Press create and it exists" — sets the expectation that nothing follows, and takes the pressure off getting details right now.
Shape code — TSX + Tailwind
<p className="absolute inset-x-5 top-[180px] text-[13px] leading-relaxed text-ink-500">
  One page. Press create and it exists — you can add photos and details any time after.
</p>
4<NameField/>
Hairline underline field, no box, no placeholder maze — the same field style locked in across onboarding S10/S11B.
Shape code — TSX + Tailwind
<div className="border-b-[1.5px] pb-2">
  <Label className="text-[10.5px] font-bold tracking-[0.1em] text-ink-300">NAME</Label>
  <Input className="border-0 px-0 text-[17px] font-bold focus-visible:ring-0" />
</div>
5<HandleField/>
Focused field takes an Azure 500 underline — one accent marks where you are, never a glowing box.
Shape code — TSX + Tailwind
<div className="border-b-[1.5px] border-azure-500 pb-2">
  <Label className="text-[10.5px] font-bold tracking-[0.1em] text-ink-300">HANDLE</Label>
  <Input defaultValue="@" className="border-0 px-0 text-[17px] font-bold focus-visible:ring-0" />
</div>
6<AvailableTag/>
Live availability in Mint — the same ✓ available affordance used on the personal @name claim in S11B, so the two flows feel like one app.
Shape code — TSX + Tailwind
{status === 'free' && (
  <span className="text-[11.5px] font-bold text-mint">✓ available</span>
)}
7<DividerLabel/>
Quiet 10.5px divider label — the alternatives are equal citizens, not a fallback.
Shape code — TSX + Tailwind
<p className="text-center text-[10.5px] font-bold tracking-[0.1em] text-ink-300">
  OR CONTINUE WITH
</p>
8<GoogleButton/>
Outline capsule, 50px — Google sign-in, which the shipped flow does not offer at all.
Shape code — TSX + Tailwind
<Button variant="outline" onClick={() => signIn('google')}
  className="h-[50px] w-full rounded-full border-[1.5px] text-sm font-bold">
  Continue with Google
</Button>
9<AppleButton/>
Ink-filled capsule — Apple's own convention, and on iOS the one people reach for first.
Shape code — TSX + Tailwind
<Button onClick={() => signIn('apple')}
  className="h-[50px] w-full rounded-full bg-ink-900 text-sm font-bold text-white">
  Continue with Apple
</Button>
10<OptionalJHULine/>
The JHU ID stops being a gate. It becomes an optional campus badge you can verify whenever you like — exactly the "let that be a thing to do if they want" note in the brief.
Shape code — TSX + Tailwind
<button className="w-full text-center text-[12.5px] font-semibold text-azure-600">
  Have a JHU email? Verify it later for a campus badge.
</button>
11<CreateCTA/>
52px Azure 500 capsule. Pressing it writes the org and routes straight to the new page — there is no second step, no photo step, no permissions step.
Shape code — TSX + Tailwind
<Button onClick={createOrg} className="h-[52px] w-full rounded-full bg-azure-500 text-[15px]
  font-bold lowercase text-white">create</Button>
12<NothingAfterNote/>
Says the chain of pages is over. The current flow keeps going after the account is made, which is the specific thing Arad wanted removed.
Shape code — TSX + Tailwind
<p className="text-center text-[11.5px] leading-relaxed text-ink-300">
  That's the whole sign-up. You'll land straight on your new page.
</p>
S1

Home — shipped today ⇄ enhanced

The home grows a real structure: a solid header sandwiches the map (location + Live Pulse — "23 live now · 2 friends out", the one line that answers "is tonight worth it"; flame/calendar/bell absorbed so the map goes clean), and the sheet gains two new squares beside the live list — a Trending square (Heat badge, #1 rank, rising sparkline, "+24 in 10 min": momentum proven, not claimed) and a Crowd Goal square ("Block Party — 34/50 in", dashed azure border because it isn't real YET, "i'm in" capsule: events that only happen if enough people commit). Dark card = happening, light dashed card = potential. Same world, same tokens, three new reasons to open the app.
Before · real screenshot
What2Do home — real iPhone screenshot
After
Crystal Rock Drive Crystal Rock Drive Century Boulevard Waters Landing Drive Locbury Drive Middlebrook Road Germantown MyEyeDr.
Germantown
23 live now · 2 friends out
🔥 1
9:41
2D
Live around you
TRENDING #1 tonight Dndnd +24 in 10 min
UNLOCKS AT 50 Block Party 34/50 in 16 more and it goes live i'm in
LIVE Snndd < 0.1 mi
Home
Chats
Host
Explore
Profile
1<HeaderBar/>
NEW: a solid Cloud header (116px incl. status bar, 1px #E8E6E1 hairline + 2px soft shadow) — the map is now SANDWICHED between header and sheet instead of floating chips over full-bleed. The app gains a stable top anchor; the map becomes a framed window into the world.
Shape code — TSX + Tailwind
<header className="absolute inset-x-0 top-0 z-[8] h-[116px] border-b border-[#E8E6E1] bg-white shadow-[0_2px_12px_rgba(20,18,28,0.05)]">…</header>
2<LocationTitle/>
"Germantown" 21px/800 (−0.4px) Ink 900 + a small gray chevron — tappable to switch area. The home now says WHERE you are looking before anything else.
Shape code — TSX + Tailwind
<button className="flex items-baseline gap-[5px]"><span className="text-[21px] font-extrabold tracking-[-0.4px] text-ink-900">{area.name}</span><Chevron /></button>
3<LivePulseLine/>
THE important header component: a 6px Siren pulse dot + "23 live now" 11.5px/700 Siren + "· 2 friends out" gray — the one line that answers "is tonight worth it" before a single tap. Tapping it scrolls the sheet to the live list. This is the feature the header exists to carry.
Shape code — TSX + Tailwind
<button onClick={openLiveList} className="flex items-center gap-1.5">
  <span className="size-1.5 rounded-full bg-[#ED2F4E] motion-safe:animate-pulse" />
  <span className="text-[11.5px] font-bold text-[#ED2F4E]">{liveCount} live now</span>
  <span className="text-[11.5px] font-semibold text-[#8D8A96]">· {friendsOut} friends out</span>
</button>
4<FlameChip/>
The shipped streak, absorbed into the header as a quiet 28px #F5F3EF pill (🔥 + "1") — same feature, no longer floating over the map.
Shape code — TSX + Tailwind
<span className="flex h-7 items-center gap-1 rounded-2xl bg-[#F5F3EF] pl-2 pr-2.5 text-[12.5px] font-bold">🔥 {streak}</span>
5<CalendarFab/>
Shipped calendar, absorbed into the header: 28px #F5F3EF circle, 15px stroked icon.
Shape code — TSX + Tailwind
<IconButton icon={Calendar} className="size-7 rounded-full bg-[#F5F3EF]" />
6<BellFab/>
Shipped bell, absorbed into the header: 28px #F5F3EF circle, 15px stroked icon.
Shape code — TSX + Tailwind
<IconButton icon={Bell} className="size-7 rounded-full bg-[#F5F3EF]" />
7<Map3DCanvas/>
The same shipped Germantown 3D world, now framed between header (y116) and sheet (y450) — a clean map band with ZERO floating chrome except the 2D toggle. Less to parse, more world visible where it matters.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-[116px] bottom-[402px]"><Map mapStyle={SHIPPED_STYLE} pitch={55} /></div>
8<DimToggle/>
Kept as the map's only floating control: 32px white "2D" circle tucked under the header (right 16, y128).
Shape code — TSX + Tailwind
<button className="grid size-8 place-items-center rounded-full bg-white text-[11px] font-bold shadow-[0_3px_12px_rgba(20,18,28,0.14)]">2D</button>
9<EventPin/>
The shipped double marker (36px photo squircle + 30px host circle, white rings) repositioned to the center of the new map band, upgraded with a 9px ▶ play glyph and a 10px Siren corner dot — a tiny window into the footage.
Shape code — TSX + Tailwind
<Marker>{photoSquircle + playGlyph + liveDot}{hostCircle}</Marker>
10<HeatGlow/>
A soft warm ground glow (radial rgba(255,99,64,.26), 2px blur) under the pin, sized by the real crowd count, plus a contact-shadow ellipse — the pin stands IN the world and shows its heat.
Shape code — TSX + Tailwind
<span className="pointer-events-none absolute rounded-full blur-[2px]" style={{ width: 40 + ev.count * 1.8, background: 'radial-gradient(closest-side,rgba(255,99,64,0.26),rgba(255,99,64,0))' }} />
11<YouDot/>
Your position: 14px Azure 600 dot, 2.5px Cloud ring, 44px accuracy halo — quiet, existing to anchor the route.
Shape code — TSX + Tailwind
<Marker longitude={me.lng} latitude={me.lat}><YouDot /></Marker>
12<RouteRibbon/>
A 2.5px dashed Azure 600 ribbon (75%, arrowhead) from you to the event — distance you can SEE, drawn on the map layer under every control.
Shape code — TSX + Tailwind
<svg className="pointer-events-none absolute inset-0 z-[4]"><path d={walkingPath(me, ev)} stroke="#1D6FF2" strokeWidth="2.5" strokeDasharray="5 6" opacity=".75" fill="none" /></svg>
13<SheetHeader/>
The shipped "Live around you" (19px/800) + 7px Siren pulse dot, on a sheet that now rises to y450 to hold the two new squares.
Shape code — TSX + Tailwind
<div className="flex items-center px-4 pt-[9px]"><span className="mr-2 size-[7px] rounded-full bg-[#ED2F4E] motion-safe:animate-pulse" /><h2 className="text-[19px] font-extrabold tracking-[-0.3px]">Live around you</h2></div>
14<TrendingCard/>
NEW SQUARE (176×150, radius 18): live footage cover darkened 50% + bottom scrim; its whole identity is MOMENTUM — Heat badge, "#1 tonight" rank, title, and a velocity line. Dark like the live cards so "footage = dark card" stays one consistent rule.
Shape code — TSX + Tailwind
<button className="relative h-[150px] w-[176px] overflow-hidden rounded-[18px] text-left">
  <Image src={ev.frame} className="absolute inset-0 brightness-50 saturate-[.85]" />
  <span className="absolute inset-0 bg-gradient-to-b from-transparent via-30% to-[#141518]/70" />
  {badge}{rank}{title}{velocity}
</button>
15<TrendBadge/>
A 17px Heat #FF6340 pill with a tiny white ▲ + "TRENDING" 8.5px/800 .08em — Heat is the crowd/momentum color everywhere in the app, so the badge is instantly legible.
Shape code — TSX + Tailwind
<span className="absolute left-2.5 top-2.5 inline-flex h-[17px] items-center gap-1 rounded-[9px] bg-[#FF6340] px-[7px]"><TriangleUp className="text-white" /><span className="text-[8.5px] font-extrabold tracking-[0.08em] text-white">TRENDING</span></span>
16<VelocitySparkline/>
A 30×12 rising Heat polyline + "+24 in 10 min" 10px/700 #FFB29E — trending PROVEN, not claimed. The sparkline is the niche touch: growth you can see at card size.
Shape code — TSX + Tailwind
<span className="absolute bottom-3 left-[11px] flex items-center gap-[5px]">
  <svg width="30" height="12"><polyline points={spark(ev.history)} fill="none" stroke="#FF6340" strokeWidth="2" /></svg>
  <span className="text-[10px] font-bold text-[#FFB29E]">+{ev.delta10m} in 10 min</span>
</span>
17<CrowdGoalCard/>
NEW SQUARE (176×150): an event that only EXISTS if enough people commit — white card with a 1.5px dashed Azure border (dashed = not yet real, the border itself tells the story), azure "UNLOCKS AT 50" badge, title "Block Party". The inverse of the dark live cards: light = potential, dark = happening.
Shape code — TSX + Tailwind
<div className="relative h-[150px] w-[176px] rounded-[18px] border-[1.5px] border-dashed border-[#1D6FF2]/50 bg-white">{badge}{title}{fraction}{progress}{cta}</div>
18<GoalProgress/>
The threshold mechanic read in under a second: "34" 26px/800 Azure + "/50 in" gray, a 6px azure progress bar on #E7EFFB, and "16 more and it goes live" 9.5px — commitment made visible, scarcity made honest.
Shape code — TSX + Tailwind
<span className="text-[26px] font-extrabold tracking-[-0.5px] text-azure-600">{ev.committed}</span><span className="text-[13px] font-bold text-[#9997A3]">/{ev.goal} in</span>
<span className="mt-2 block h-1.5 rounded-full bg-[#E7EFFB]"><span className="block h-full rounded-full bg-azure-600" style={{ width: `${ev.committed / ev.goal * 100}%` }} /></span>
19<GoalCTA/>
A full-width 26px azure capsule, "i'm in" 12px/800 lowercase (the app's locked CTA voice) — one tap adds you to the count; the card updates live as the crowd builds.
Shape code — TSX + Tailwind
<button onClick={commit} className="absolute inset-x-2.5 bottom-[9px] grid h-[26px] place-items-center rounded-[13px] bg-azure-600 text-[12px] font-extrabold text-white">i'm in</button>
20<CompactLiveRow/>
The rest of the shipped live list compressed to a 52px dark row (LIVE pill + "Snndd" + "< 0.1 mi") — the list survives the two squares without scroll pretense.
Shape code — TSX + Tailwind
<button className="relative h-[52px] w-full overflow-hidden rounded-[15px] bg-gradient-to-br from-[#2B2D31] to-[#141518] text-left">{liveBadge}{title}{distance}</button>
S2

Explore — before’s layout, new system

Before · real screenshot
What2Do explore — real iPhone screenshot
After
9:41
Explore
San Francisco
AllMusicCoffeeSports
Happening near you
See all on map ›
LIVE
Test1
Ellis Street & Stockton
1/10
LIVE
TEST123
Chinatown
1/10
LIVE
Test event
Chinatown
1/10
LIVE
Unemployment Club
Eleanor Avenue
3/10
Trending this week
See all ›
PARTY LIVE
PARTY
Home
Chats2
Host
Explore
Profile
1<HeaderGlow/>
Warm burnt-orange wash fading down from the top of the black canvas — Explore-orange's ambient glow that tints the header zone without a boxed banner.
Shape code — TSX + Tailwind
<div className="pointer-events-none absolute inset-x-0 top-0 h-40
  bg-gradient-to-b from-[#9A3412]/50 to-transparent" />
2<SearchBar/>
Full-width rounded search field on a near-Ink 900 surface with a dark hairline border; icon and placeholder sit in Ink 300 grey.
Shape code — TSX + Tailwind
<button className="absolute inset-x-4 top-16 flex h-[46px] items-center
  gap-2 rounded-full border border-[#26252B] bg-[#1A191E] px-4
  text-[14.5px] text-[#8E8C96]">
  <Search className="size-[17px]" strokeWidth={1.9} />
  Search events, people, or organizations
</button>
3<PageTitle/>
Serif 37px "Explore" in Cloud white sitting inside the orange glow — the editorial page voice; Explore keeps its own orange accent as a sub-identity within the system.
Shape code — TSX + Tailwind
<h1 className="absolute left-5 top-[120px] font-serif text-[37px]
  font-bold leading-none text-[#F4F3F6]">Explore</h1>
4<LocationPill/>
Ink 900-dark rounded-xl pill with a hairline border and Cloud-white city label; the map-pin icon carries the Explore-orange accent — replaces the before's random orange fill.
Shape code — TSX + Tailwind
<button className="inline-flex h-[42px] items-center gap-1.5 rounded-xl
  border border-[#26252B] bg-[#17161B] px-[15px] text-sm font-semibold
  text-[#F4F3F6]">
  <MapPin className="size-4 text-[#F97316]" />
  San Francisco
</button>
5<FilterChip/>
Repeated chip row. Rest: dark surface + hairline border + Ink 300 text (clearly enabled). Active: Explore-orange fill with near-black text.
Shape code — TSX + Tailwind
<button data-active={active}
  className="h-[42px] rounded-full border border-[#26252B] bg-[#17161B]
  px-5 text-[15px] font-semibold text-[#A5A3AD]
  data-[active=true]:border-[#F0654A] data-[active=true]:bg-[#F0654A]
  data-[active=true]:font-bold data-[active=true]:text-[#1A120C]" />
6<SectionHeader/>
22px extrabold Cloud-white rail title ("Happening near you", "Trending this week") — one repeated pattern heading every content rail.
Shape code — TSX + Tailwind
<h2 className="text-[22px] font-extrabold tracking-[-0.01em]
  text-[#F4F3F6]">Happening near you</h2>
7<SeeAllLink/>
Right-aligned 13px semibold link in Explore-orange with a chevron — pairs with each SectionHeader (repeated pattern).
Shape code — TSX + Tailwind
<button className="text-[13px] font-semibold text-[#F97316]">
  See all on map ›
</button>
8<EventCardTile/>
2-column grid cell (repeated pattern): sharp-corner photo cover with a soft black scrim, then Cloud-white title, Ink 300 location and capacity meta stacked beneath — grey debug covers replaced by photo-first covers.
Shape code — TSX + Tailwind
<div className="grid grid-cols-2 gap-x-3 gap-y-3.5">
  <div>
    <div className="relative h-[130px] overflow-hidden rounded
      shadow-[0_1px_8px_rgba(0,0,0,0.5)]">
      <Image src={ev.cover} alt="" fill className="object-cover" />
      <div className="absolute inset-0 bg-gradient-to-b
        from-black/5 via-transparent to-black/30" />
      <LiveBadge />
    </div>
    <p className="mt-2 text-[15.5px] font-bold tracking-[-0.01em]
      text-[#F4F3F6]">{ev.title}</p>
    <p className="mt-px text-xs font-medium text-[#8E8C96]">{ev.place}</p>
    <p className="mt-1 flex items-center gap-[5px] text-[11.5px]
      font-semibold text-[#A5A3AD]">
      <Users className="size-[13px]" strokeWidth={2} />{ev.count}/10</p>
  </div>
</div>
9<LiveBadge/>
Same single badge as Home — translucent Ink 900 capsule with a Siren ring and pulsing Siren dot; the red-outline-only variant is dead (repeated across covers).
Shape code — TSX + Tailwind
<span className="absolute left-2 top-2 z-[2] inline-flex h-6 items-center
  gap-1.5 rounded bg-[#100F12]/70 px-[9px] text-[10.5px] font-extrabold
  tracking-[0.09em] text-white ring-[1.5px] ring-inset
  ring-[#F5304A]/85">
  <m.i className="size-1.5 rounded-full bg-[#ED2F4E]"
    animate={{ opacity: [1, 0.35, 1] }}
    transition={{ duration: 1.6, repeat: Infinity }} />
  LIVE
</span>
10<CategoryTag/>
Tiny uppercase tracking-wide label over the photo — Cloud-white text on a translucent Ink 900 plate (repeated on trending covers).
Shape code — TSX + Tailwind
<span className="absolute left-2 top-2 z-[2] rounded bg-[#100F12]/45
  px-2 py-[3px] text-[10px] font-extrabold tracking-[0.12em]
  text-white/85">PARTY</span>
11<TrendingCard/>
Wide 232×118 photo card in a horizontally scrolling rail that bleeds off the right edge; same scrim treatment as the grid tiles, carrying a CategoryTag and optionally a LiveBadge (repeated pattern).
Shape code — TSX + Tailwind
<div className="-mr-[70px] flex gap-3 overflow-x-auto">
  <button className="relative h-[118px] w-[232px] flex-none
    overflow-hidden rounded shadow-[0_1px_8px_rgba(0,0,0,0.5)]">
    <Image src={t.cover} alt="" fill className="object-cover" />
    <div className="absolute inset-0 bg-gradient-to-b
      from-black/5 via-transparent to-black/30" />
    <CategoryTag>PARTY</CategoryTag>
    <LiveBadge className="left-auto right-2" />
  </button>
</div>
12<TabBar/>
Ink 900-black bottom bar with five tabs; inactive glyphs in muted grey, the active Explore tab lights Azure 500 — nav avatar color no longer changes per screen.
Shape code — TSX + Tailwind
<nav className="absolute inset-x-0 bottom-0 flex h-[86px] items-start
  justify-around bg-[#0E0D10] px-2 pt-[11px]">
  {tabs.map((t) => (
    <button key={t.label} data-active={t.active}
      className="relative flex min-w-[52px] flex-col items-center
      gap-[3px] text-[11px] font-semibold text-[#77747E]
      data-[active=true]:text-azure-500">
      <t.icon className="size-6" strokeWidth={1.8} />
      {t.label}
    </button>
  ))}
</nav>
13<HostButton/>
Raised center action in the bar: 48×28 Azure 600 rounded plate with a Cloud-white plus and an Azure glow shadow, "Host" caption beneath.
Shape code — TSX + Tailwind
<span className="-mt-[3px] flex flex-col items-center gap-0.5">
  <span className="grid h-7 w-12 place-items-center rounded-[10px]
    bg-azure-600 shadow-[0_3px_12px_rgba(29,111,242,0.45)]">
    <Plus className="size-[18px] text-white" strokeWidth={2.5} />
  </span>
  <span className="text-[11px] font-semibold text-azure-600">Host</span>
</span>
14<TabBadge/>
Unread counter on the Chats tab: 17px Azure 600 capsule with a Cloud-white numeral, ringed in the bar's black so it pops off the glyph.
Shape code — TSX + Tailwind
<span className="absolute -top-1 right-0.5 grid h-[17px] min-w-[17px]
  place-items-center rounded-full border-2 border-[#0E0D10]
  bg-azure-600 px-1 text-[10px] font-bold text-white">2</span>
S3

Calendar — covers as days, white glass

Before · real screenshot
What2Do my schedule — real iPhone screenshot
After
9:41
July
Today
SunMonTueWedThuFriSat 282930123 56910 12☀️14151617 192021222325 2627282930311
Today Wednesday · July 8
Tonight at 8pm
Spring Fest Concert
👍 Going
Home
Chats2
Host
Explore
Profile
1<HeaderScrim/>
A soft Azure 100-family tint washing down from the top edge and fading to transparent, seating the month header on the canvas without a boxed app bar.
Shape code — TSX + Tailwind
<div className="pointer-events-none absolute inset-x-0 top-0 h-60
  bg-gradient-to-b from-[#6E71F5]/15 to-transparent" />
2<MonthTitle/>
The big 34px extrabold month word in Ink 900, floating directly on the canvas — the calendar's only heavyweight text.
Shape code — TSX + Tailwind
<h1 className="absolute left-5 top-[58px] text-[34px] font-extrabold
  tracking-[-0.03em] text-ink-900">July</h1>
3<TodayPill/>
Frosted Cloud glass capsule (65% white + blur) with a bold Ink 900 "Today" label — one tap snaps the grid back to the current week.
Shape code — TSX + Tailwind
<button className="inline-flex h-[38px] items-center rounded-full
  bg-white/65 px-[17px] text-[13.5px] font-bold text-ink-900
  shadow-[0_2px_10px_rgba(20,18,28,0.10),inset_0_0_0_0.5px_rgba(255,255,255,0.7)]
  backdrop-blur-[14px]">Today</button>
4<GlassIconButton/>
38px frosted Cloud glass circles holding Ink 900 stroke icons — filter and search. One repeated pattern, two instances.
Shape code — TSX + Tailwind
<button className="grid size-[38px] place-items-center rounded-full
  bg-white/65 shadow-[0_2px_10px_rgba(20,18,28,0.10),inset_0_0_0_0.5px_rgba(255,255,255,0.7)]
  backdrop-blur-[14px]">
  <SlidersHorizontal className="size-[17px] text-ink-900" strokeWidth={2} />
</button>
{/* same shell with <Search /> — one pattern, two instances */}
5<WeekdayRow/>
Seven Sun–Sat labels in Ink 300 semibold heading the 7-column grid — a repeated pattern row.
Shape code — TSX + Tailwind
<div className="grid grid-cols-7 justify-items-center">
  {["Sun","Mon","Tue","Wed","Thu","Fri","Sat"].map((d) => (
    <span key={d} className="text-[11px] font-semibold text-ink-300">{d}</span>
  ))}
</div>
6<DayNumberCell/>
Empty days stay quiet tabular numerals in Ink 500; adjacent-month days fade to a lighter grey. Repeated pattern across the grid.
Shape code — TSX + Tailwind
<span data-out={isOutsideMonth} className="text-base font-medium
  tabular-nums text-ink-500 data-[out=true]:text-[#CFCDD4]">{n}</span>
7<DayCoverThumb/>
Days with an event swap the numeral for the event's 44px rounded cover photo (Partiful-style) with a soft drop shadow — photo-dominant, Azure 500 is the family accent. Repeated pattern.
Shape code — TSX + Tailwind
<button className="relative size-11 overflow-hidden rounded-[11px]
  shadow-[0_1px_5px_rgba(20,18,28,0.18)]">
  <Image src={day.event.cover} alt="" fill className="object-cover" />
</button>
8<SelectedDayRing/>
The selected day's cover gets a 2px Cloud white gap plus a 4.5px Azure 600 ring — the one loud cell in the grid.
Shape code — TSX + Tailwind
<button className="relative size-11 overflow-hidden rounded-[11px]
  shadow-[0_0_0_2px_#FFFFFF,0_0_0_4.5px_#1D6FF2,0_2px_8px_rgba(20,18,28,0.2)]">
  <Image src={day.event.cover} alt="" fill className="object-cover" />
</button>
9<EmojiDayCell/>
A personal note renders as a bare 24px emoji in the grid — Story-gold sun, no chrome at all.
Shape code — TSX + Tailwind
<span className="text-2xl leading-none">☀️</span>
10<GlassSheet/>
Frosted Cloud sheet (72% white, 24px blur + saturate) rising over the grid with 26px rounded top corners and an inner top highlight.
Shape code — TSX + Tailwind
<m.div initial={{ y: 60 }} animate={{ y: 0 }}
  className="absolute inset-x-0 bottom-0 top-[486px] rounded-t-[26px]
  bg-white/70 backdrop-blur-2xl backdrop-saturate-150
  shadow-[0_-6px_30px_rgba(20,18,28,0.10),inset_0_1px_0_rgba(255,255,255,0.8)]" />
11<SheetHandle/>
36×4 drag handle in 15% Ink 900, centered at the sheet's top edge.
Shape code — TSX + Tailwind
<div className="absolute left-1/2 top-2 h-1 w-9 -translate-x-1/2
  rounded-full bg-ink-900/15" />
12<SheetDayTitle/>
One centered line: "Today" bold in Ink 900, then "Wednesday · July 8" medium in Ink 300.
Shape code — TSX + Tailwind
<p className="text-center text-[14.5px]">
  <b className="font-bold text-ink-900">Today</b>{" "}
  <span className="font-medium text-ink-300">Wednesday · July 8</span>
</p>
13<TodayCard/>
The day's event as one big card on a soft lavender→cream pastel gradient over the Paper canvas: 92×108 cover art left, extrabold Ink 900 title right.
Shape code — TSX + Tailwind
<div className="mx-4 flex gap-[13px] overflow-hidden rounded-[18px]
  bg-gradient-to-br from-[#EFE7FB] to-[#FDF3E6] p-3
  shadow-[0_2px_12px_rgba(20,18,28,0.10)]">
  <Image src={event.cover} alt="" className="h-[108px] w-[92px]
    flex-none rounded-xl object-cover" />
  <div className="min-w-0 pt-1">
    {/* <TimeChip/> */}
    <h3 className="mt-[9px] text-[19px] font-extrabold tracking-tight
      text-ink-900">Spring Fest Concert</h3>
    {/* <AttendeeStack/> */}
  </div>
</div>
14<TimeChip/>
Solid Azure 600 capsule with bold Cloud white "Tonight at 8pm" — the card's single loud accent.
Shape code — TSX + Tailwind
<Badge className="inline-flex h-[26px] items-center rounded-full
  bg-azure-600 px-3 text-xs font-bold text-white">Tonight at 8pm</Badge>
15<AttendeeStack/>
Overlapping 22px friend photos with Cloud white rings, then "👍 Going" in semibold Ink 900 — social proof in one glance.
Shape code — TSX + Tailwind
<div className="flex items-center gap-[7px]">
  <div className="flex">
    {going.map((p, i) => (
      <Image key={p.id} src={p.photo} alt="" className={cn(
        "size-[22px] rounded-full border-2 border-white",
        i > 0 && "-ml-2")} />
    ))}
  </div>
  <span className="text-[13px] font-semibold text-ink-900">👍 Going</span>
</div>
16<ViewToggle/>
Floating frosted Cloud glass capsule; the active calendar segment is a solid Ink 900 pill with a white glyph, list view a tap away.
Shape code — TSX + Tailwind
<div className="absolute right-4 top-[698px] z-10 flex gap-1
  rounded-full bg-white/70 p-1 backdrop-blur-[16px]
  shadow-[0_4px_16px_rgba(20,18,28,0.14),inset_0_0_0_0.5px_rgba(255,255,255,0.7)]">
  <button className="grid h-9 w-11 place-items-center rounded-full
    bg-ink-900"><CalendarDays className="size-[17px] text-white" /></button>
  <button className="grid h-9 w-11 place-items-center rounded-full">
    <List className="size-[17px] text-ink-900" /></button>
</div>
17<TabBar/>
Cloud white dock: Home, Chats, Host, Explore, Profile in Ink 300 with a fade-in gradient above; the Chats tab carries a tiny Azure 600 unread badge.
Shape code — TSX + Tailwind
<nav className="absolute inset-x-0 bottom-0 flex h-[86px] items-start
  justify-around bg-white px-2 pt-[11px] before:absolute
  before:inset-x-0 before:-top-8 before:h-8 before:bg-gradient-to-b
  before:from-white/0 before:to-white">
  {tabs.map((t) => (
    <button key={t.label} className="relative flex min-w-[52px]
      flex-col items-center gap-[3px] text-[11px] font-semibold
      text-ink-300">
      <t.icon className="size-6" strokeWidth={1.8} />{t.label}
      {t.badge && <span className="absolute -top-1 right-0.5 grid
        h-[17px] min-w-[17px] place-items-center rounded-full border-2
        border-white bg-azure-600 px-1 text-[10px] font-bold
        text-white">{t.badge}</span>}
    </button>
  ))}
</nav>
18<HostTabButton/>
The raised center action: a 48×28 Azure 600 rounded-rectangle plus button with its azure glow shadow, over an Ink 300 "Host" caption.
Shape code — TSX + Tailwind
<button className="-mt-1 flex flex-col items-center gap-0.5">
  <span className="grid h-7 w-12 place-items-center rounded-[10px]
    bg-azure-600 shadow-[0_3px_12px_rgba(29,111,242,0.45)]">
    <Plus className="size-[18px] text-white" strokeWidth={2.5} />
  </span>
  <span className="text-[11px] font-semibold text-ink-300">Host</span>
</button>
S4

Event — the map is the page

Before · real screenshot
What2Do event — real iPhone screenshot
After · map first
LIVE · 0:24
12 min walk · 0.6 mi
9:41
LIVE Private Free entry
Warehouse Night
Started 8:15 PM · live for 1h 26m · usually till 1 AM
Underground house set in the old print warehouse. Side door on Minna St — say Souham sent you.
Request to Join
Souham Tekriwal @st123 · replies in minutes
3 friends going · 9 spots left
After · tapping the clip
9:41
LIVE Private Free entry
Warehouse Night
Started 8:15 PM · live for 1h 26m · usually till 1 AM
Underground house set in the old print warehouse. Side door on Minna St — say Souham sent you.
12 min
Request to Join
Souham Tekriwal @st123 · replies in minutes
3 friends going · 9 spots left
1<MapCanvas/>
You land on MAP FIRST: the product's one grey map style (Paper) is the entire page canvas — no card, no gallery, the venue's geography IS the hero.
Shape code — TSX + Tailwind
<div className="absolute inset-0 overflow-hidden bg-[#F2F3F5]">
  {/* MapLibre, the one grey product style, camera fitted to you + venue */}
  <MapView styleUrl={GREY_STYLE}
    className="absolute -top-[30px] left-0 h-[115%] w-full" />
</div>
2<RouteTrail/>
A smooth dashed Azure 600 bezier drawn over the map from your dot to the venue pin — the "how do I get there" answer with zero taps.
Shape code — TSX + Tailwind
<svg className="pointer-events-none absolute inset-0 z-[3] size-full"
  viewBox="0 0 393 852">
  <m.path
    d="M66 508 Q112 476 162 472 Q216 466 252 424 Q286 384 298 344"
    fill="none" stroke="#1D6FF2" strokeWidth={4}
    strokeDasharray="8 7" strokeLinecap="round" className="opacity-85"
    initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} />
</svg>
3<YouDot/>
Your location: a 16px Azure 500 dot with a Cloud border and a soft Azure halo — the anchor end of the route trail.
Shape code — TSX + Tailwind
<span className="absolute left-[58px] top-[500px] z-[4] size-4
  rounded-full border-[3px] border-white bg-azure-500
  shadow-[0_0_0_10px_rgba(29,111,242,0.14)]" />
4<EventPin/>
The destination: an Azure 500 teardrop pin with a Cloud border and a star glyph, sitting on a translucent Azure glow disc that marks the venue block.
Shape code — TSX + Tailwind
{/* glow disc under the pin */}
<span className="absolute left-[272px] top-[280px] z-[4] size-[52px]
  rounded-full bg-azure-600/15" />
<m.div className="absolute left-[280px] top-[286px] z-[5] size-9"
  animate={{ y: [0, -3, 0] }} transition={{ repeat: Infinity, duration: 2 }}>
  <span className="absolute inset-0 -rotate-45 rounded-[50%_50%_50%_0]
    border-[2.5px] border-white bg-azure-500
    shadow-[0_4px_10px_rgba(29,111,242,0.4)]" />
  <Star className="absolute left-1/2 top-[44%] size-[15px] -translate-x-1/2
    -translate-y-1/2 text-white" strokeWidth={2} />
</m.div>
5<WalkChip/>
A Cloud chip floating on the route trail: nav glyph in Azure 600, "12 min walk · 0.6 mi" in Ink 900 — distance answered where your eye already is.
Shape code — TSX + Tailwind
<span className="absolute left-[104px] top-[428px] z-[5] inline-flex
  h-[26px] items-center gap-[5px] rounded-lg bg-white px-2.5
  text-[11.5px] font-bold text-ink-900
  shadow-[0_0_0_2px_rgba(255,255,255,0.55),0_1px_5px_rgba(20,18,28,0.18)]">
  <Navigation className="size-3 text-azure-600" strokeWidth={2.2} />
  12 min walk · 0.6 mi
</span>
6<LiveClipBox/>
The cross-preview: a small rounded footage box ringed in Cloud, sitting above the dock on the map frame — play disc in the middle, "LIVE · 0:24" badge on black scrim. Tap it and the footage becomes the entire page.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[556px] z-[7] h-[94px] w-[124px]
  overflow-hidden rounded-[14px]
  shadow-[0_0_0_2.5px_#fff,0_10px_24px_rgba(20,18,28,0.28)]">
  <Image src={clip.poster} alt="" fill className="object-cover" />
  <span className="absolute inset-0 bg-gradient-to-b
    from-ink-900/5 to-ink-900/35" />
  <span className="absolute left-1/2 top-1/2 grid size-[34px]
    -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full
    bg-black/40 ring-[1.5px] ring-white/40">
    <Play className="ml-0.5 size-3.5 fill-white text-white" />
  </span>
  <span className="absolute bottom-1.5 left-[7px] rounded-[5px] bg-black/50
    px-1.5 py-0.5 text-[9px] font-extrabold text-white">LIVE · 0:24</span>
</button>
7<EdgeSwipeChevrons/>
Frosted circles hugging both screen edges — Cloud glass with Ink 900 chevrons on the map, dark glass with white chevrons on the footage — flicking to the previous / next moment near you, so the event page itself becomes a deck.
Shape code — TSX + Tailwind
{/* one on each edge; dark frame swaps to bg-[#100F12]/45 + white glyph */}
<button className="absolute left-2.5 top-[404px] z-[8] grid size-9
  place-items-center rounded-full bg-white/85 backdrop-blur-lg
  shadow-[0_4px_14px_rgba(20,18,28,0.16)]">
  <ChevronLeft className="size-[15px] text-ink-900" strokeWidth={2.2} />
</button>
<button className="absolute right-2.5 top-[404px] z-[8] grid size-9
  place-items-center rounded-full bg-white/85 backdrop-blur-lg
  shadow-[0_4px_14px_rgba(20,18,28,0.16)]">
  <ChevronRight className="size-[15px] text-ink-900" strokeWidth={2.2} />
</button>
8<TitleGlassSheet/>
The frosted Cloud panel pinned to the top of BOTH states — heavy blur, bottom-rounded, zero controls inside it — so the title block reads over the map and over the footage identically.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-0 z-[8] h-[212px] rounded-b-3xl
  bg-white/90 backdrop-blur-2xl backdrop-saturate-150
  shadow-[0_8px_30px_rgba(20,18,28,0.18)]" />
9<LiveBadge/>
A Cloud pill with a pulsing Siren dot and "LIVE" in Ink 900 — the happening-right-now signal leading the meta row.
Shape code — TSX + Tailwind
<span className="inline-flex h-[22px] items-center gap-[5px] rounded-full
  bg-white pl-[7px] pr-[9px] text-[10.5px] font-bold tracking-[0.06em]
  text-ink-900 shadow-[0_1px_5px_rgba(0,0,0,0.22)]">
  <span className="relative size-1.5 rounded-full bg-siren
    after:absolute after:-inset-[3px] after:animate-ping
    after:rounded-full after:border-[1.5px] after:border-siren" />
  LIVE
</span>
10<PrivateChip/>
Access level as a quiet grey chip: lock glyph + "Private" in Ink 500 on a soft grey fill — visible, never shouting.
Shape code — TSX + Tailwind
<span className="inline-flex h-[22px] items-center gap-1 rounded-[7px]
  bg-[#F1F0F3] px-[9px] text-[10.5px] font-bold text-ink-500">
  <Lock className="size-[11px]" strokeWidth={2.2} />
  Private
</span>
11<FreeEntryLabel/>
Cost answered before you ask: "Free entry" in Ink 300, pushed to the right end of the meta row with no chrome at all.
Shape code — TSX + Tailwind
<span className="ml-auto text-[11.5px] font-semibold text-ink-300">
  Free entry
</span>
12<EventTitle/>
The event name at 27px extrabold Ink 900 with tight tracking — the single loudest text on the page.
Shape code — TSX + Tailwind
<h1 className="mt-2 text-[27px] font-extrabold tracking-[-0.025em]
  text-ink-900">Warehouse Night</h1>
13<TimeLine/>
Every time assumption answered in one line — "Started 8:15 PM" bold in Ink 900, then "live for 1h 26m · usually till 1 AM" fading to Ink 300.
Shape code — TSX + Tailwind
<p className="mt-1 text-[12.5px] font-semibold text-ink-900">
  Started 8:15 PM
  <span className="font-medium text-ink-300">
    {" "}· live for 1h 26m · usually till 1 AM</span>
</p>
14<HostNote/>
The host's own words in Ink 500 — what it is, how to get in ("side door on Minna St — say Souham sent you"), two lines max.
Shape code — TSX + Tailwind
<p className="mt-1.5 text-[12.5px] leading-[1.5] text-ink-500">
  Underground house set in the old print warehouse.
  Side door on Minna St — say Souham sent you.
</p>
15<BottomDock/>
A full-bleed Cloud glass sheet rising from the bottom on BOTH states — top-rounded, heavy blur — holding everything you tap plus the trust story stacked under it.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 bottom-0 top-[664px] z-[9]
  rounded-t-3xl bg-white/95 backdrop-blur-2xl backdrop-saturate-150
  shadow-[0_-8px_30px_rgba(20,18,28,0.25)]" />
16<JoinCTA/>
The one action, full width between the icon squares: "Request to Join" in white on Azure 600 with an Azure glow shadow.
Shape code — TSX + Tailwind
<Button className="h-[46px] flex-1 rounded-[13px] bg-azure-600
  text-[15px] font-bold text-white
  shadow-[0_4px_14px_rgba(29,111,242,0.30)]">
  Request to Join
</Button>
17<DockIconButtons/>
Repeated pattern flanking the CTA: identical 46px soft-grey squares with Ink 900 glyphs — back, share, more — every secondary action demoted to the same quiet shape.
Shape code — TSX + Tailwind
{/* one square, three glyphs: ChevronLeft · Share · MoreHorizontal */}
<button className="grid size-[46px] flex-none place-items-center
  rounded-[13px] bg-[#F1F0F3]">
  <ChevronLeft className="size-[17px] text-ink-900" strokeWidth={2.1} />
</button>
<button className="grid size-[46px] flex-none place-items-center
  rounded-[13px] bg-[#F1F0F3]">
  <Share className="size-[17px] text-ink-900" strokeWidth={1.9} />
</button>
<button className="grid size-[46px] flex-none place-items-center
  rounded-[13px] bg-[#F1F0F3]">
  <MoreHorizontal className="size-[17px] text-ink-900" />
</button>
18<HostRow/>
The host's account moved UNDER Request to Join — photo ringed in Cloud, name bold in Ink 900, "@st123 · replies in minutes" credibility in Ink 300, chevron to the full profile.
Shape code — TSX + Tailwind
<button className="flex w-full items-center gap-2 text-left">
  <Image src={host.photo} alt=""
    className="size-7 flex-none rounded-full ring-2 ring-white" />
  <span className="whitespace-nowrap text-xs font-bold text-ink-900">
    Souham Tekriwal</span>
  <span className="truncate text-[10.5px] font-medium text-ink-300">
    @st123 · replies in minutes</span>
  <ChevronRight className="ml-auto size-[13px] flex-none text-ink-300"
    strokeWidth={2} />
</button>
19<FriendsGoing/>
Directly beneath the host row: overlapping Cloud-ringed photos of YOUR friends who are in, with "3 friends going · 9 spots left" — the bold facts in Ink 900, the connective text in Ink 500.
Shape code — TSX + Tailwind
<div className="flex items-center gap-2">
  <div className="flex">
    {friends.map((f, i) => (
      <Image key={f.id} src={f.photo} alt=""
        className={cn("size-5 rounded-full ring-2 ring-white",
          i > 0 && "-ml-[9px]")} />
    ))}
  </div>
  <p className="whitespace-nowrap text-[11px] text-ink-500">
    <b className="font-bold text-ink-900">3 friends</b> going ·{" "}
    <b className="font-bold text-ink-900">9 spots left</b>
  </p>
</div>
20<FootageCanvas/>
Tap the clip and the event's video IS the page — full-bleed footage on a near-Ink 900 base, no card, no frame, what-it-feels-like as the entire canvas.
Shape code — TSX + Tailwind
<div className="absolute inset-0 bg-[#100F12]">
  <video src={clip.src} poster={clip.poster}
    autoPlay muted loop playsInline
    className="absolute inset-0 size-full object-cover" />
</div>
21<CinematicMasks/>
Repeated pattern: two 360px Ink 900 gradient scrims feathering in from the top and bottom edges, keeping the glass sheets and floating controls legible over any footage.
Shape code — TSX + Tailwind
<span className="pointer-events-none absolute inset-x-0 top-0 h-[360px]
  bg-gradient-to-b from-ink-900/45 via-ink-900/20 to-transparent" />
<span className="pointer-events-none absolute inset-x-0 bottom-0 h-[360px]
  bg-gradient-to-t from-ink-900/60 via-ink-900/20 to-transparent" />
22<PlayDisc/>
A 64px black-scrim disc centered on the footage with a Cloud play glyph and a soft white ring — the only control floating on the video itself.
Shape code — TSX + Tailwind
<m.button whileTap={{ scale: 0.92 }}
  className="absolute left-1/2 top-[296px] z-[6] grid size-16
    -translate-x-1/2 place-items-center rounded-full bg-black/40
    backdrop-blur-sm ring-2 ring-white/40">
  <Play className="ml-1 size-[26px] fill-white text-white" />
</m.button>
23<ClipPagerDots/>
Six swipe dots in a black-scrim pill under the play disc — the current clip solid Cloud, the rest at half opacity — telling you there's more footage to flick through.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-[420px] z-[6] flex justify-center">
  <span className="inline-flex h-[18px] items-center gap-1.5
    rounded-[9px] bg-black/40 px-[9px] backdrop-blur-[5px]">
    <span className="size-[7px] rounded-full bg-white" />
    {[...Array(5)].map((_, i) => (
      <span key={i} className="size-[5.5px] rounded-full bg-white/50" />
    ))}
  </span>
</div>
24<MiniMapBox/>
The mirror cross-preview: on the footage, the same rounded box now holds the grey map — Azure 600 dashed route, you-dot, teardrop pin and a "12 min" Cloud chip — so where-it-is is one tap back. One gesture flips between where it is and what it feels like.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[556px] z-[7] h-[94px] w-[124px]
  overflow-hidden rounded-[14px]
  shadow-[0_0_0_2.5px_#fff,0_10px_24px_rgba(0,0,0,0.45)]">
  <MapView styleUrl={GREY_STYLE}
    className="absolute -left-[150px] -top-[160px]" />
  <svg className="absolute inset-0" viewBox="0 0 124 94">
    <polyline points="16,76 46,62 76,44 98,30" fill="none"
      stroke="#1D6FF2" strokeWidth={2.5} strokeDasharray="5 4"
      strokeLinecap="round" />
    <circle cx={16} cy={76} r={7} fill="rgba(29,111,242,.18)" />
    <circle cx={16} cy={76} r={4} fill="#1D6FF2"
      stroke="#fff" strokeWidth={2} />
  </svg>
  <span className="absolute left-[90px] top-3.5 grid size-[17px] rotate-45
    place-items-center rounded-[50%_50%_50%_4px] bg-azure-600
    shadow-[0_2px_6px_rgba(20,18,28,0.4)]">
    <Star className="size-[9px] -rotate-45 text-white" strokeWidth={2.4} />
  </span>
  <span className="absolute bottom-1.5 left-1/2 inline-flex h-[18px]
    -translate-x-1/2 items-center gap-[3px] whitespace-nowrap
    rounded-[9px] bg-white/95 px-2 text-[8.5px] font-extrabold
    text-ink-900">
    <Navigation className="size-[9px] text-azure-600" strokeWidth={2.2} />
    12 min
  </span>
</button>
S5

Chat — list ⇄ new message

Before · real screenshot
What2Do messages — real iPhone screenshot
After
9:41
Chat
🤖
My AI
Sarah
Uptown Fest
Mike
Campus Crew
Leah
Sarah
New Snap· just now
Mike
Opened · 2m
😊
Uptown Fest
New Chat· 5m
Campus Crew
Received · 15m
Alex
Delivered · 20m🔥
😎
Weekend Plans
Opened by 3 · 1h
Jordan
Double tap to reply · 2h
😌
Leah
Delivered · 2h😊
🤖
What2Do AI
New suggestion!· 3h
Home
Chats
Host
Explore
Profile
After · New message
9:41
New message
Search people
RECENT
Alex@alexg
Alexa Collins@alexacollins
ameenraissi@ameenraissi
Ariana Rohanifar@ariana.roh
RECAPS
+
QWERTYUIOP
ASDFGHJKL
ZXCVBNM
123
1<HeaderAvatar/>
Your 44px avatar at the top-left with a Mint presence dot ringed in the page background — you're always "on" to your friends.
Shape code — TSX + Tailwind
<button className="relative size-11 shrink-0 rounded-full">
  <Image src={me} alt="" className="size-full rounded-full object-cover" />
  <span className="absolute bottom-0 right-0 size-3 rounded-full
    bg-[#22C55E] ring-[2.5px] ring-[#F9F9FA]" />
</button>
2<PageTitle/>
Bold 23px "Chat" wordmark in Ink 900, tight -2% tracking — the page names itself without a nav bar.
Shape code — TSX + Tailwind
<h1 className="text-[23px] font-extrabold tracking-[-0.02em]
  text-ink-900">Chat</h1>
3<HeaderActionButton/>
Repeated pattern: 42px grey circle buttons (add-friend, more) with Ink 900 icons — quiet utilities that don't compete with content.
Shape code — TSX + Tailwind
<div className="flex gap-2">
  <button className="grid size-[42px] place-items-center rounded-full
    bg-[#EDECEF]">
    <UserPlus className="size-5 text-ink-900" strokeWidth={1.9} />
  </button>
  <button className="grid size-[42px] place-items-center rounded-full
    bg-[#EDECEF]">
    <MoreHorizontal className="size-5 text-ink-900" />
  </button>
</div>
4<SearchBar/>
Soft grey 44px field with Ink 300 placeholder text and a trailing filter control.
Shape code — TSX + Tailwind
<div className="flex h-11 items-center gap-[9px] rounded-[14px]
  bg-[#ECEBEE] px-3.5 text-[15.5px] text-[#8E8C96]">
  <Search className="size-[18px]" strokeWidth={2} />
  <span className="flex-1">Search</span>
  <SlidersHorizontal className="size-[18px]" />
</div>
5<MyAiStory/>
The AI assistant leads the stories rail: robot glyph on a lavender disc inside a neutral Hairline ring — present but visually "off" until it has something for you.
Shape code — TSX + Tailwind
<button className="w-16 shrink-0 text-center">
  <span className="relative mx-auto block size-[54px] rounded-full p-[1.5px]
    [background:conic-gradient(from_128deg,#E2E1E6_0_96%,transparent_96%_100%)]">
    <span className="grid size-full place-items-center rounded-full
      border-2 border-[#F9F9FA] bg-[#EEECFA] text-2xl">🤖</span>
  </span>
  <span className="mt-1.5 block text-[11.5px] font-semibold
    text-ink-900">My AI</span>
</button>
6<StoryRing/>
Repeated pattern: hairline story rings — a 1.5px colour-coded conic arc (cyan friend, Story-gold live, violet event) with a tiny break where its ends nearly meet, over a 2px inset; the Mint presence dot sits on the rim.
Shape code — TSX + Tailwind
<button className="w-16 shrink-0 text-center">
  <span className="relative mx-auto block size-[54px] rounded-full p-[1.5px]
    [background:conic-gradient(from_128deg,var(--ring)_0_96%,transparent_96%_100%)]">
    <Image src={photo} alt="" className="size-full rounded-full
      border-2 border-[#F9F9FA] object-cover" />
    <span className="absolute bottom-px right-px size-[13px] rounded-full
      bg-[#22C55E] ring-[2.5px] ring-white" />
  </span>
  <span className="mt-1.5 block truncate text-[11.5px] font-semibold
    text-ink-900">{name}</span>
</button>
7<ChatRow/>
Repeated pattern: 44px round avatar, Ink 900 bold name over the status line, meta icons on the right rail; the hairline divider starts after the avatar (inset 55px) so photos never get underlined.
Shape code — TSX + Tailwind
<div className="relative flex items-center gap-[11px] py-1.5
  after:absolute after:bottom-0 after:left-[55px] after:right-0
  after:h-px after:bg-[#EFEEF2]">
  <Image src={photo} alt="" className="size-11 shrink-0 rounded-full
    object-cover" />
  <div className="min-w-0 flex-1">
    <p className="text-[15.5px] font-bold tracking-[-0.01em]
      text-ink-900">{name}</p>
    <StatusLine />
  </div>
  <MetaRail />
</div>
8<StatusLine/>
Repeated pattern: the coloured state line where COLOR is the message type — Chat-violet = snap, Azure 600 = chat/received, Siren = delivered-awaiting-you — and the 13px icon is the state (square = unopened, arrow = sent/opened); timestamp stays Ink 300.
Shape code — TSX + Tailwind
<p className="mt-0.5 flex items-center gap-[5px] text-[12.5px]
  font-semibold text-ink-300">
  {/* COLOR = type: violet snap · azure chat · siren needs-you */}
  <Square className="size-[13px] text-[#7C3AED]" strokeWidth={2.4} />
  <span className="text-[#7C3AED]">New Snap</span>
  <span>· just now</span>
</p>
9<ReplyCamera/>
Repeated pattern: Ink 900 outline camera on nearly every row — one tap replies with a moment instead of text.
Shape code — TSX + Tailwind
<button aria-label="Reply with a moment">
  <Camera className="size-[21px] text-ink-900" strokeWidth={1.8} />
</button>
10<MuteBell/>
Repeated pattern: Ink 900 slashed-bell marking silenced group threads (Uptown Fest, Weekend Plans) — muted state stays visible without shouting.
Shape code — TSX + Tailwind
<BellOff className="size-5 text-ink-900" strokeWidth={1.8} />
11<ReactionEmoji/>
Repeated pattern: a bare 17px emoji in the meta rail — the friend's streak/mood glyph, no chrome around it.
Shape code — TSX + Tailwind
<span className="text-[17px] leading-none">😊</span>
12<UnreadDot/>
Repeated pattern: 9px Azure 500 dot at the row's far right — the single unread signal, no numeric badges in the list.
Shape code — TSX + Tailwind
<span className="size-[9px] rounded-full bg-azure-500" />
13<AiAssistantRow/>
The What2Do AI thread: robot glyph on a lavender disc with a Chat-violet ✦ badge pinned to its corner, and an Azure 600 "New suggestion!" status — the assistant lives inside the normal chat list, not a separate tab.
Shape code — TSX + Tailwind
<div className="flex items-center gap-[11px] py-1.5">
  <div className="relative grid size-11 shrink-0 place-items-center
    rounded-full bg-[#EEECFA] text-[22px]">
    🤖
    <span className="absolute -right-px -top-px grid size-4
      place-items-center rounded-full bg-[#7C3AED] text-[9px]
      text-white">✦</span>
  </div>
  <div className="min-w-0 flex-1">
    <p className="text-[15.5px] font-bold text-ink-900">What2Do AI</p>
    <p className="mt-0.5 flex items-center gap-[5px] text-[12.5px]
      font-semibold text-ink-300">
      <ArrowRight className="size-[13px] text-azure-600"
        strokeWidth={2.4} />
      <span className="text-azure-600">New suggestion!</span>
      <span>· 3h</span>
    </p>
  </div>
</div>
14<TabBar/>
Cloud-white 86px bottom bar with a fade-in gradient above it; inactive tabs Ink 300, the active Chats tab Azure 600, Profile shows your avatar instead of an icon.
Shape code — TSX + Tailwind
<nav className="absolute inset-x-0 bottom-0 flex h-[86px] items-start
  justify-around bg-white px-2 pt-[11px]
  before:pointer-events-none before:absolute before:inset-x-0
  before:-top-8 before:h-8 before:bg-gradient-to-b
  before:from-transparent before:to-white">
  <Tab icon={Home} label="Home" />
  <Tab icon={MessageCircle} label="Chats" active />
  <HostButton />
  <Tab icon={Search} label="Explore" />
  <Tab avatar={me} label="Profile" />
</nav>
{/* Tab: */}
<button className="flex min-w-[52px] flex-col items-center gap-[3px]
  text-[11px] font-semibold text-ink-300
  data-[active=true]:text-azure-600">
  <Icon className="size-6" strokeWidth={1.8} />{label}
</button>
15<HostButton/>
Center tab slot: a 48×28 Azure 600 rounded pill with a white plus and an Azure glow shadow, "Host" label beneath — the create action reads as a button, not a tab.
Shape code — TSX + Tailwind
<button className="-mt-[3px] flex flex-col items-center gap-0.5">
  <span className="grid h-7 w-12 place-items-center rounded-[10px]
    bg-azure-600 shadow-[0_3px_12px_rgba(29,111,242,0.45)]">
    <Plus className="size-[18px] text-white" strokeWidth={2.5} />
  </span>
  <span className="mt-0.5 text-[11px] font-semibold
    text-azure-600">Host</span>
</button>
16<NewMsgTitle/>
The compose state (second frame): "New message" — 26px weight 800, letter-spacing −0.4px, Ink 900 #17161C at x18 y64. The shipped state reuses the "Messages" title for composing; naming the state kills the ambiguity.
Shape code — TSX + Tailwind
<h1 className="text-[26px] font-extrabold tracking-[-0.4px] text-ink-900">New message</h1>
17<CloseCircle/>
Dismiss: 40px circle, chip-grey #F2F1F4 with a 15px Ink 900 ✕ — quiet, instead of the shipped solid-azure X (azure is reserved for GO actions, not cancel).
Shape code — TSX + Tailwind
<button className="grid size-10 place-items-center rounded-full bg-[#F2F1F4]"><X className="size-[15px] text-ink-900" strokeWidth={2.4} /></button>
18<SearchFieldFocused/>
The search input in its focused state at y120: 46px, radius 23px, 1.5px Azure 600 #1D6FF2 border on #F4F6F9, azure search glyph + a 2×18px azure caret + "Search people" 15px/500 Ink 300.
Shape code — TSX + Tailwind
<div className="flex h-[46px] items-center gap-[9px] rounded-[23px] border-[1.5px] border-azure-600 bg-[#F4F6F9] px-[15px]">
  <Search className="size-4 text-azure-600" strokeWidth={2.2} />
  <input autoFocus placeholder="Search people" className="min-w-0 flex-1 bg-transparent text-[15px] font-medium placeholder:text-ink-300" />
</div>
19<SectionLabel ×2/>
"RECENT" (y184) and "RECAPS" (y448) — 11.5px weight 700, letter-spacing .08em, Ink 300 #9997A3. Recent people first (who you actually message), recaps below.
Shape code — TSX + Tailwind
<p className="text-[11.5px] font-bold tracking-[0.08em] text-ink-300">RECENT</p>
20<RecentRow ×4/>
Person rows with handles ADDED: 40px circular photo, name 15px/700 Ink 900 over @handle 12px/500 Ink 300 (the shipped rows show bare names — the handle disambiguates the Alexas). 58px pitch.
Shape code — TSX + Tailwind
<button className="flex h-[58px] w-full items-center gap-3 text-left">
  <Image src={u.avatar} className="size-10 rounded-full" />
  <span className="min-w-0 flex-1">
    <span className="block text-[15px] font-bold text-ink-900">{u.name}</span>
    <span className="block text-[12px] font-medium text-ink-300">@{u.handle}</span>
  </span>
</button>
21<RecapRing ×2/>
Recap circles promoted into the system language: 62px, wrapped in a 2.5px Azure 500→600 gradient ring with a 2.5px Cloud inset — the app's highlight-ring treatment, replacing the shipped ringless circle.
Shape code — TSX + Tailwind
<span className="relative block size-[62px] rounded-full bg-[linear-gradient(135deg,#1D6FF2,#3D8BFF)] p-[2.5px]">
  <Image src={r.cover} fill className="rounded-full border-[2.5px] border-white object-cover" />
</span>
22<NewRecapDisc/>
The "+" recap slot: 62px circle, 1.5px dashed #C9C7CF, 22px Ink 300 plus — replaces the shipped magenta badge (magenta is off-system; violet stays scoped to chat bubbles only).
Shape code — TSX + Tailwind
<button className="grid size-[62px] place-items-center rounded-full border-[1.5px] border-dashed border-[#C9C7CF] text-[22px] text-ink-300">+</button>
23<KeyboardMock/>
The iOS QWERTY docked at the bottom (keypad-grey #CDD0D6, white 42px keys with the 0-1px press shadow) — the state ships with the keyboard up because composing starts with typing a name.
Shape code — TSX + Tailwind
<SystemKeyboard />
S6

Notifications — requests, live pings, people

Before
9:41
Notifications
People you may know
((:@):Add
AAbhi KulkarniJohns Hopkins UniversityAdd
Alden Hey@aldenheyAdd
amiresmaeeli_17@amiresmaeeli_17Add
Aamirmo@amirmoAdd
Aapple@appleAdd
After
9:41
Notifications Mark all read
NEW
Alden Hey sent you a friend request · 2m accept
Sarah invited you to Warehouse Night · LIVE now step in ›
Mike is out now · Union Square · 12m
PEOPLE YOU MAY KNOW See all ›
AK Abhi Kulkarni2 mutual · Johns Hopkins add
amiresmaeeli_171 mutual · out last night add
AM amirmofrom your contacts add
1<BackChip/>
Plain 18px chevron-left stroked Ink 900 #17161C at 2.4px in the header row (x18 y64) — matches the shipped page's bare chevron.
Shape code — TSX + Tailwind
<button><ChevronLeft className="size-[18px] text-ink-900" strokeWidth={2.4} /></button>
2<ScreenTitle/>
"Notifications" — system sans 25px weight 800, letter-spacing −0.3px, Ink 900 #17161C.
Shape code — TSX + Tailwind
<h1 className="text-[25px] font-extrabold tracking-[-0.3px] text-ink-900">Notifications</h1>
3<MarkAllReadLink/>
"Mark all read" — 12.5px weight 700 Azure 600 #1D6FF2, right-anchored in the header.
Shape code — TSX + Tailwind
<button className="ml-auto text-[12.5px] font-bold text-azure-600">Mark all read</button>
4<SectionLabel ×2/>
Group labels "NEW" (y122) and "PEOPLE YOU MAY KNOW" (y376, with an azure "See all ›") — 11.5px weight 700, letter-spacing .08em, Ink 300 #9997A3. THE structural fix vs shipped: notifications LEAD (that is the page's job); suggestions come second instead of being the whole page.
Shape code — TSX + Tailwind
<p className="text-[11.5px] font-bold tracking-[0.08em] text-ink-300">NEW</p>
5<RequestRow/>
Friend request: 44px circular photo, copy "<b>Alden Hey</b> sent you a friend request · 2m" 13.5px/500 Ink 900 with the time in Ink 300 — a sentence, not a bare name, so the row explains itself.
Shape code — TSX + Tailwind
<div className="flex h-16 items-center gap-3">
  <Image src={u.avatar} className="size-11 rounded-full" />
  <p className="min-w-0 flex-1 text-[13.5px] leading-[1.45] text-ink-900"><b>{u.name}</b> sent you a friend request <span className="text-ink-300">· {t}</span></p>
  {accept}
</div>
6<AcceptPill/>
"accept" — the app's 32px lowercase capsule (radius 16, Azure 600 #1D6FF2, 12.5px/800 Cloud) replacing the shipped page's oversized 42px Add buttons.
Shape code — TSX + Tailwind
<button className="h-8 flex-none rounded-2xl bg-azure-600 px-3.5 text-[12.5px] font-extrabold lowercase text-white">accept</button>
7<LiveInviteRow/>
A live invite is a NOTIFICATION with urgency: 44px radius-12 footage thumb with an 10px Siren dot, copy "<b>Sarah</b> invited you to <b>Warehouse Night</b> · LIVE now" with the LIVE tail in Siren #ED2F4E weight 800.
Shape code — TSX + Tailwind
<div className="flex h-16 items-center gap-3">
  <span className="relative flex-none"><Image src={ev.frame} className="size-11 rounded-xl" /><LiveDot /></span>
  <p className="min-w-0 flex-1 text-[13.5px] leading-[1.45] text-ink-900"><b>{f.name}</b> invited you to <b>{ev.title}</b> <span className="font-extrabold text-[#ED2F4E]">· LIVE now</span></p>
  {stepIn}
</div>
8<StepInMini/>
"step in ›" — same 32px azure capsule; the invite is actionable from the list, one tap into the moment (the map contract, honored here too).
Shape code — TSX + Tailwind
<button className="h-8 flex-none rounded-2xl bg-azure-600 px-3.5 text-[12.5px] font-extrabold lowercase text-white">step in ›</button>
9<FriendOutRow/>
Presence ping: "<b>Mike</b> is out now · Union Square · 12m" with a grey chevron — informational, tap opens Mike on the map.
Shape code — TSX + Tailwind
<button className="flex h-16 w-full items-center gap-3 text-left">
  <Image src={f.avatar} className="size-11 rounded-full" />
  <p className="min-w-0 flex-1 text-[13.5px] text-ink-900"><b>{f.name}</b> is out now · {f.area} <span className="text-ink-300">· {t}</span></p>
  <ChevronRight className="size-[15px] text-[#B7B5BE]" />
</button>
10<SuggestRow ×3/>
Suggestions rebuilt with CONTEXT: 44px avatar (photo, or Azure 100 #E8F1FF disc with 16px/800 Azure 600 initials — replacing the shipped random greens), name 14.5px/700 over a reason line 12px/500 Ink 300 ("2 mutual · Johns Hopkins", "1 mutual · out last night", "from your contacts"). Junk rows from the shipped capture ("(:" and "@):", bare "apple") are filtered by handle-quality rules — never shown.
Shape code — TSX + Tailwind
<div className="flex h-16 items-center gap-3">
  {avatarOrInitials}
  <span className="min-w-0 flex-1">
    <span className="block text-[14.5px] font-bold text-ink-900">{u.name}</span>
    <span className="block text-[12px] font-medium text-ink-300">{u.reason}</span>
  </span>
  {add}
</div>
11<AddPill ×3/>
"add" — 32px lowercase azure capsule, consistent with every other action pill in the app.
Shape code — TSX + Tailwind
<button className="h-8 flex-none rounded-2xl bg-azure-600 px-3.5 text-[12.5px] font-extrabold lowercase text-white">add</button>
12<HomeIndicator/>
134×5px bar, radius 3px, centered at bottom 8px, Black #000000 at 40%.
Shape code — TSX + Tailwind
<span className="absolute bottom-2 left-1/2 h-[5px] w-[134px] -translate-x-1/2 rounded-[3px] bg-black/40" />
S7

Profile — TikTok × Instagram grade, real people

Before · real screenshot
What2Do profile — real iPhone screenshot
After
9:41
@arad
Out now
Arad Tabrizi
@arad · San Francisco
live music · night markets · golden hour walks 🎧
23Friends
12Hosted
48Attended
NEXT MOMENT
Rooftop Sunset Social
Sat · 7:00 PM · Mission District
Going ✓
Spring Fest
Beach Day
Park Picnic
New
👥 12 ▶ 0:24 👥 5
Home
Chats2
Host
Explore
Profile
After · Identity switcher
9:41
Switch identity
Arad @arad · personal
W Warehouse SF organization · 2 teammates
+ Create an organization
Same account — switch anytime.
1<ProfileTopBar/>
Floating utility bar over the page top: add-friend and menu icons on quiet 36pt Paper-grey circles flanking the @arad handle in Ink 900 at 15/800. It stays out of the hero's way — no title bar, no chrome.
Shape code — TSX + Tailwind
<header className="absolute inset-x-0 top-[54px] z-[5] flex h-11
  items-center justify-between px-3.5">
  <Button variant="ghost" size="icon" className="size-9 rounded-full bg-[#F4F4F6]">
    <UserPlus className="size-[19px] text-ink-900" strokeWidth={2} />
  </Button>
  <span className="text-[15px] font-extrabold tracking-[-0.1px]
    text-ink-900">@arad</span>
  <Button variant="ghost" size="icon" className="size-9 rounded-full bg-[#F4F4F6]">
    <Menu className="size-[19px] text-ink-900" strokeWidth={2} />
  </Button>
</header>
2<HeroPhotoSandwich/>
The hero is a full-bleed 176pt real photo — no avatar circle, the person IS the header. A vertical Ink-900 scrim (12% top, clear middle, 68% bottom) keeps both the top bar and the identity text legible over any shot.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-[98px] z-[3] h-44 overflow-hidden">
  <Image src={me.cover} alt="" fill className="object-cover" />
  <span className="absolute inset-0
    bg-[linear-gradient(180deg,rgba(10,10,14,0.12)_0%,rgba(10,10,14,0)_30%,rgba(10,10,14,0.68)_100%)]" />
</div>
3<OutNowPill/>
Live presence chip pinned to the hero's top-right: a 22pt frosted Cloud pill (92% white) with a 7pt Mint dot and "Out now" in Ink 900 — social proof that this person actually goes out.
Shape code — TSX + Tailwind
<span className="absolute right-3.5 top-3 inline-flex h-[22px] items-center
  gap-[5px] rounded-[11px] bg-white/90 px-[9px] text-[10px] font-extrabold
  text-ink-900">
  <i className="size-[7px] rounded-full bg-[#22C55E] not-italic" />
  Out now
</span>
4<HeroIdentityBlock/>
Name, handle and bio sit directly on the photo's bottom scrim: Cloud-white name at 22/800, @handle · city at 88% white, and a one-line interests bio at 78% white. Who you are, in three lines, zero cards.
Shape code — TSX + Tailwind
<div className="absolute inset-x-4 bottom-[11px]">
  <h1 className="text-[22px] font-extrabold tracking-[-0.3px]
    text-white">Arad Tabrizi</h1>
  <p className="mt-0.5 text-xs font-semibold text-white/85">@arad · San Francisco</p>
  <p className="mt-[3px] text-[11.5px] font-medium text-white/75">
    live music · night markets · golden hour walks 🎧</p>
</div>
5<StatsRow/>
The TikTok trio — real counts only (Friends · Hosted · Attended): 17/800 Ink 900 numbers over 11pt Ink 300 labels, each column split by a 1px Hairline divider.
Shape code — TSX + Tailwind
<div className="absolute inset-x-[38px] top-[288px] z-[4] flex items-center
  justify-between">
  {[["23","Friends"],["12","Hosted"],["48","Attended"]].map(([n, label], i) => (
    <Fragment key={label}>
      {i > 0 && <i className="h-[22px] w-px bg-[#ECEBEF]" />}
      <button className="flex flex-1 flex-col items-center gap-px">
        <b className="text-[17px] font-extrabold tracking-[-0.2px]
          text-ink-900">{n}</b>
        <span className="text-[11px] font-medium text-ink-300">{label}</span>
      </button>
    </Fragment>
  ))}
</div>
6<ProfileActions/>
Edit profile / Share profile / share-sheet icon — three 44pt buttons in quiet Paper-grey (#F1F1F4) with Ink 900 labels, so the photos stay the hero and no button competes with the Azure CTAs.
Shape code — TSX + Tailwind
<div className="absolute inset-x-4 top-[334px] z-[4] flex gap-2">
  <Button variant="secondary" className="h-11 flex-1 rounded-[10px]
    bg-[#F1F1F4] text-[13px] font-bold text-ink-900">Edit profile</Button>
  <Button variant="secondary" className="h-11 flex-1 rounded-[10px]
    bg-[#F1F1F4] text-[13px] font-bold text-ink-900">Share profile</Button>
  <Button variant="secondary" size="icon" className="h-11 w-11 rounded-[10px]
    bg-[#F1F1F4]"><Share className="size-[17px] text-ink-900" strokeWidth={2} /></Button>
</div>
7<NextMomentCard/>
What2Do's own move — the first card is your next confirmed plan as a 78pt full-bleed photo with a left-to-right Ink 900 scrim (78% → 12%) and a soft drop shadow. The profile answers "what are you doing next?"
Shape code — TSX + Tailwind
<m.button whileTap={{ scale: 0.98 }}
  className="absolute inset-x-4 top-[392px] z-[4] h-[78px] overflow-hidden
  rounded-2xl shadow-[0_6px_18px_rgba(20,18,28,0.16)]">
  <Image src={next.cover} alt="" fill className="object-cover" />
  <span className="absolute inset-0 bg-gradient-to-r
    from-[#0A0A0E]/80 via-[#0A0A0E]/50 to-[#0A0A0E]/10" />
</m.button>
8<NextMomentTitleBlock/>
The card's text stack rides the dark end of the scrim: a NEXT MOMENT eyebrow in light Azure (#9FC5FF, tracking .08em), the truncating Cloud-white title at 14.5/800, and when · where at 82% white.
Shape code — TSX + Tailwind
<div className="absolute left-[13px] right-24 top-3.5 text-left">
  <p className="text-[10px] font-extrabold tracking-[0.08em]
    text-[#9FC5FF]">NEXT MOMENT</p>
  <p className="mt-[3px] truncate text-[14.5px] font-extrabold
    text-white">Rooftop Sunset Social</p>
  <p className="mt-0.5 text-[11px] font-semibold
    text-white/80">Sat · 7:00 PM · Mission District</p>
</div>
9<GoingPill/>
The RSVP state as a solid Azure 600 pill — 28pt tall, Cloud-white "Going ✓" at 11/800, floating on an Azure glow shadow at the card's right edge. One glance = you're committed.
Shape code — TSX + Tailwind
<span className="absolute right-2.5 top-1/2 inline-flex h-7 -translate-y-1/2
  items-center rounded-[14px] bg-azure-600 px-[11px] text-[11px]
  font-extrabold text-white
  shadow-[0_3px_10px_rgba(29,111,242,0.5)]">Going ✓</span>
10<HighlightsRail/>
Moment memories as 62pt photo circles — Instagram highlights, but every circle is a real past event: an Azure 500 → Azure 600 gradient ring, a 2.5px Cloud inset border, and a 10.5/600 label in deep grey beneath.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-[482px] z-[4] flex gap-4 px-4">
  {highlights.map(h => (
    <button key={h.id} className="flex flex-col items-center gap-[5px]">
      <span className="size-[62px] rounded-full p-[2.5px]
        bg-gradient-to-br from-azure-500 to-azure-600">
        <Image src={h.cover} alt="" width={57} height={57}
          className="size-full rounded-full border-[2.5px] border-white
          object-cover" />
      </span>
      <span className="text-[10.5px] font-semibold
        text-[#4A4954]">{h.label}</span>
    </button>
  ))}
</div>
11<NewHighlightCircle/>
The rail's last slot invites the next memory: a 62pt circle with a 1.8px dashed Ink-300-grey border (#C9C8D1), an Ink 300 plus glyph, and a muted Ink 300 "New" label — an affordance, not content.
Shape code — TSX + Tailwind
<button className="flex flex-col items-center gap-[5px]">
  <span className="grid size-[62px] place-items-center rounded-full
    border-[1.8px] border-dashed border-[#C9C8D1]">
    <Plus className="size-5 text-ink-300" strokeWidth={2} />
  </span>
  <span className="text-[10.5px] font-semibold text-ink-300">New</span>
</button>
12<ProfileTabs/>
Icon tabs (grid / calendar / liked) on a Hairline bottom border — inactive icons in mid-grey (#8E8E93), the active one in Ink 900 with a 44pt Ink 900 underline, the TikTok content-switcher pattern.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-[570px] z-[4] flex border-b
  border-[#ECEBEF]">
  {[Grid3x3, Calendar, Heart].map((Icon, i) => (
    <button key={i} className="relative grid h-11 flex-1 place-items-center">
      <Icon className={cn("size-[23px] text-[#8E8E93]",
        i === 0 && "text-ink-900")} strokeWidth={2} />
      {i === 0 && <span className="absolute -bottom-px left-1/2 h-[2.5px]
        w-11 -translate-x-1/2 rounded-sm bg-ink-900" />}
    </button>
  ))}
</div>
13<MomentsGrid/>
Edge-to-edge 3-column photo grid with 3px gutters that scrolls under the nav fade — pure content, no cards, no borders; every tile is a real moment cover.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-[618px] z-[3] flex flex-wrap gap-[3px]">
  {moments.map(mo => (
    <button key={mo.id}
      className="relative aspect-[1.04] w-[calc((100%-6px)/3)] overflow-hidden">
      <Image src={mo.cover} alt="" fill className="object-cover" />
    </button>
  ))}
</div>
14<GridCountChip/>
Overlay chips on grid tiles carry only real metadata — 👥 12 people or ▶ 0:24 — as 18pt blurred near-Ink-900 pills (62% #100F12) with Cloud-white 9.5/700 text, bottom-left of the tile.
Shape code — TSX + Tailwind
<span className="absolute bottom-1.5 left-1.5 inline-flex h-[18px]
  items-center gap-[3px] rounded-[9px] bg-[#100F12]/60 px-1.5
  text-[9.5px] font-bold text-white backdrop-blur-sm">👥 12</span>
15<TabBar/>
Standard 86pt Cloud nav with a white fade above it: Home / Chats / Host / Explore / Profile in Ink 300, the active Profile tab an avatar with a 2px Azure 600 ring, and an Azure 600 unread badge (2) on Chats.
Shape code — TSX + Tailwind
<nav className="absolute inset-x-0 bottom-0 z-[12] flex h-[86px] items-start
  justify-around bg-white px-2 pt-[11px]
  before:absolute before:inset-x-0 before:-top-8 before:h-8
  before:bg-gradient-to-b before:from-white/0 before:to-white">
  {tabs.map(t => (
    <button key={t.label} className={cn("relative flex min-w-[52px] flex-col
      items-center gap-[3px] text-[11px] font-semibold text-ink-300",
      t.active && "text-azure-600")}>
      {t.label === "Profile"
        ? <Image src={me.photo} alt="" width={25} height={25}
            className="rounded-full ring-2 ring-azure-600" />
        : <t.icon className="size-6" strokeWidth={1.8} />}
      {t.label}
      {t.badge && <span className="absolute -top-1 right-0.5 grid h-[17px]
        min-w-[17px] place-items-center rounded-full border-2 border-white
        bg-azure-600 px-1 text-[10px] font-bold text-white">2</span>}
    </button>
  ))}
</nav>
16<HostButton/>
The center create action: a 48×28pt Azure 600 rounded-rect with a Cloud-white plus on an Azure glow shadow, over an Ink 300 "Host" label — the one colored control in the bar.
Shape code — TSX + Tailwind
<span className="-mt-[3px] flex flex-col items-center gap-0.5">
  <span className="grid h-7 w-12 place-items-center rounded-[10px]
    bg-azure-600 shadow-[0_3px_12px_rgba(29,111,242,0.45)]">
    <Plus className="size-[18px] text-white" strokeWidth={2.5} />
  </span>
  <span className="mt-0.5 text-[11px] font-semibold text-ink-300">Host</span>
</span>
17<IdentityChevron/>
The switcher entry point: a 9px weight-800 ▾ in Ink 500 #6C6B76, 4px after the centered @arad handle in the top bar. Only rendered when the account has more than one identity (an org page attached). Tapping the handle opens the identity sheet — the SAME gesture Instagram trained everyone on. During ONBOARDING the switch is different and simpler: the "venue or organization →" capsule on Add-a-photo switches INTO org mode, and the back chip on the org page returns to personal — no sheet needed there.
Shape code — TSX + Tailwind
<button onClick={openIdentitySheet} className="inline-flex items-center gap-1 text-[15px] font-extrabold tracking-[-0.1px] text-ink-900">
  @arad{identities.length > 1 && <b className="text-[9px] text-ink-500">▾</b>}
</button>
18<SwitcherScrim/>
Full-frame dim behind the sheet (third frame): Ink at 44% — rgba(16,15,18,.44) — over the profile. Tap anywhere on it to dismiss.
Shape code — TSX + Tailwind
<div onClick={close} className="absolute inset-0 bg-[#100F12]/[0.44]" />
19<SwitcherSheet/>
Bottom sheet, radius 22px top corners, Cloud #FFFFFF, shadow 0 −12px 34px rgba(20,18,28,.18), padding 10/20/30: a 36×4px grabber #E3E2E7, the title "Switch identity" 15px weight 800 Ink 900 #17161C, the identity rows, and the reassurance footer "Same account — switch anytime." 11px weight 500 Ink 300 #9997A3. Switching swaps the ENTIRE app surface (profile, host composer, chats sent as the org) — one tap, no re-login, because both identities live on the same phone-verified account.
Shape code — TSX + Tailwind
<m.div initial={{ y: '100%' }} animate={{ y: 0 }} className="absolute inset-x-0 bottom-0 rounded-t-[22px] bg-white px-5 pb-[30px] pt-2.5 shadow-[0_-12px_34px_rgba(20,18,28,0.18)]">
  <span className="mx-auto block h-1 w-9 rounded-full bg-[#E3E2E7]" />
  <p className="mt-3.5 text-center text-[15px] font-extrabold text-ink-900">Switch identity</p>
  {rows}
  <p className="mt-2.5 text-center text-[11px] font-medium text-ink-300">Same account — switch anytime.</p>
</m.div>
20<IdentityRowPersonal/>
The active identity row: 40px circular photo avatar, "Arad" 14.5px weight 800 Ink 900 #17161C over "@arad · personal" 12px weight 500 Ink 500 #6C6B76, 12px gap, and the 30×30px azure ON checkbox (fill + border Azure 600 #1D6FF2, ✓ Cloud) marking who you currently are.
Shape code — TSX + Tailwind
<button className="flex w-full items-center gap-3 py-3">
  <Image src={me.avatar} className="size-10 rounded-full object-cover" />
  <span className="min-w-0 flex-1 text-left">
    <span className="block text-[14.5px] font-extrabold text-ink-900">Arad</span>
    <span className="block text-[12px] font-medium text-ink-500">@arad · personal</span>
  </span>
  <Check active />
</button>
21<IdentityRowOrg/>
The org identity row: the 40px radius-13px azure-gradient "W" logo tile (16px/800 Cloud), "Warehouse SF" 14.5px/800 over "organization · 2 teammates" 12px/500 Ink 500, and a resting empty checkbox. Tap = the whole app now acts as Warehouse SF.
Shape code — TSX + Tailwind
<button className="flex w-full items-center gap-3 py-3">
  <span className="grid size-10 flex-none place-items-center rounded-[13px] bg-[linear-gradient(135deg,#1D6FF2,#3D8BFF)] text-[16px] font-extrabold text-white">W</span>
  <span className="min-w-0 flex-1 text-left">
    <span className="block text-[14.5px] font-extrabold text-ink-900">Warehouse SF</span>
    <span className="block text-[12px] font-medium text-ink-500">organization · 2 teammates</span>
  </span>
  <Check />
</button>
22<CreateOrgRow/>
Under a 1px #F0EFF2 divider: a 40px dashed-border (#D8D6DC) tile with a 19px "+" in Ink 300 #9997A3 and "Create an organization" 13.5px weight 700 Ink 500 #6C6B76 — the in-app door to org mode for anyone who skipped it during onboarding. Opens the same minimal "Set up your organization" page (S12B).
Shape code — TSX + Tailwind
<button className="flex w-full items-center gap-3 py-3">
  <span className="grid size-10 flex-none place-items-center rounded-[13px] border-[1.5px] border-dashed border-[#D8D6DC] text-[19px] font-semibold text-ink-300">+</span>
  <span className="flex-1 text-left text-[13.5px] font-bold text-ink-500">Create an organization</span>
</button>
S7B

Edit profile — shipped today (new page)

New page from Arad's Jul 31 capture — recorded as shipped, redesign TBD. Note two captured bugs kept verbatim: the College value reads "Johns Hopkins Univrsity" (typo) and Date of birth shows "Apr 22, 2026" (a future date — the picker isn't clamping). The school-email verification block is the trust anchor for the university graph.
Current · shipped
9:41
Edit profile
+ Add link
College
Johns Hopkins Univrsity
Graduating class
2028
Date of birth
Apr 22, 2026
VERIFICATION
School email
you@university.edu
Send verification code
Save
1<CurrentState/>
Shipped anatomy (capture, Jul 31 2026): sticky "Edit profile" header on #F9F8F6 with a hairline; a white form card ("+ Add link" pill, then labeled hairline fields College / Graduating class / Date of birth — 1.5px #E5E3DE borders, radius 18px, 54px tall); a separate VERIFICATION card with the school-email input and a black "Send verification code" capsule; a full-width black "Save" capsule pinned at the bottom. Redesign to the app system (azure primary, capsule language, clamped date picker) comes in a later pass — this frame documents today's truth.
Captured issues
1. "Johns Hopkins Univrsity" — typo ships in the College value (no spellcheck/suggest).
2. Date of birth "Apr 22, 2026" — future DOB accepted; picker needs a max-date clamp (13+ policy).
3. Black CTAs — off-system: primary actions elsewhere are Azure 500 capsules.
4. Two stacked primary buttons (Send code + Save) compete; verification should collapse after verify.
S8

Camera

Before · real screenshot
What2Do camera — real iPhone screenshot
After
9:41
Moment Recap
Drop a moment — it goes live instantly
1<StatusBar/>
iOS status bar in Cloud white over the dark camera chrome — 9:41 on the left, signal / Wi-Fi / battery glyphs on the right.
Shape code — TSX + Tailwind
<div className="pointer-events-none absolute inset-x-0 top-0 z-20 flex h-14
  items-end justify-between px-[34px] pb-1.5 text-white">
  <span className="text-base font-[650] tracking-[0.02em]">9:41</span>
  <span className="flex items-end gap-1.5">
    <Signal className="size-4" /><Wifi className="size-4" /><BatteryFull className="size-5" />
  </span>
</div>
2<DynamicIsland/>
Ink 900-black hardware pill floating at top center; pure device chrome, nothing rendered inside it.
Shape code — TSX + Tailwind
<div className="absolute left-1/2 top-3 z-30 h-[34px] w-[118px] -translate-x-1/2
  rounded-[18px] bg-black shadow-[inset_0_0_0_1px_rgba(255,255,255,0.03)]" />
3<Viewfinder/>
Inset stage with 32pt corners floating on the black shell — creating feels like composing a shot. A deep night gradient stands in for the live feed.
Shape code — TSX + Tailwind
<div className="absolute inset-x-2.5 top-[52px] bottom-[218px] overflow-hidden
  rounded-[32px]
  bg-[linear-gradient(200deg,#1B2440_0%,#121A2E_45%,#241A16_100%)]">
  {/* live camera feed */}
</div>
4<BokehGlow/> ×3
Repeated pattern of blurred bokeh light orbs inside the feed — one near Azure 500 blue, one pink, one Amber — suggesting a night scene through the lens.
Shape code — TSX + Tailwind
<div className="absolute left-[60px] top-[120px] size-[60px] rounded-full
  bg-[#4E96FF] opacity-45 blur-[26px]" />
<div className="absolute right-[70px] top-[210px] size-11 rounded-full
  bg-[#EF4A9B] opacity-40 blur-[22px]" />
<div className="absolute left-[150px] top-[330px] size-[70px] rounded-full
  bg-[#F59E0B] opacity-30 blur-[30px]" />
5<CloseButton/>
46pt frosted glass disc — Ink 900 at 50% with backdrop blur and a hairline white ring — holding a Cloud-white X, pinned to the viewfinder's top-left.
Shape code — TSX + Tailwind
<m.button whileTap={{ scale: 0.92 }}
  className="absolute left-3.5 top-6 grid size-[46px] place-items-center
  rounded-full border border-white/[0.18] bg-ink-900/50 backdrop-blur-xl">
  <X className="size-[21px] text-white" strokeWidth={1.8} />
</m.button>
6<FlashButton/>
Matching frosted Ink 900 disc with a Cloud-white flash bolt, pinned to the viewfinder's top-right.
Shape code — TSX + Tailwind
<m.button whileTap={{ scale: 0.92 }}
  className="absolute right-3.5 top-6 grid size-[46px] place-items-center
  rounded-full border border-white/[0.18] bg-ink-900/50 backdrop-blur-xl">
  <Zap className="size-[21px] text-white" strokeWidth={1.8} />
</m.button>
7<ModeChips/>
Moment / Recap selector row — the active chip is solid Cloud white with Ink 900 text, the inactive one frosted white at 15% with 75% white text.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 bottom-[232px] flex justify-center gap-[9px]">
  <span className="inline-flex h-6 items-center rounded-lg bg-white px-3
    text-[10px] font-bold uppercase tracking-[0.05em] text-ink-900">Moment</span>
  <span className="inline-flex h-6 items-center rounded-lg bg-white/15 px-3
    text-[10px] font-bold uppercase tracking-[0.05em] text-white/75">Recap</span>
</div>
8<GalleryButton/>
Frosted Ink 900 glass disc opening the camera roll — Cloud-white image glyph, leftmost slot of the control rail.
Shape code — TSX + Tailwind
<button className="absolute left-[34px] top-[15px] grid size-[46px]
  place-items-center rounded-full border border-white/[0.18]
  bg-ink-900/50 backdrop-blur-xl">
  <Image className="size-[21px] text-white" strokeWidth={1.8} />
</button>
9<AIEnhanceButton/>
Same glass disc but the sparkle strokes in light Azure (#7FB5FF) — the single tinted control on the rail, marking the AI touch-up.
Shape code — TSX + Tailwind
<button className="absolute left-24 top-[15px] grid size-[46px]
  place-items-center rounded-full border border-white/[0.18]
  bg-ink-900/50 backdrop-blur-xl">
  <Sparkles className="size-[21px] text-[#7FB5FF]" strokeWidth={1.8} />
</button>
10<Shutter/>
76pt Cloud-white ring with a translucent core, centered on the rail; recording fills Azure 600 with a soft azure bloom.
Shape code — TSX + Tailwind
<m.button whileTap={{ scale: 0.9 }} data-recording={recording}
  className="absolute left-1/2 top-0 size-[76px] -translate-x-1/2 rounded-full
  border-[5px] border-white bg-white/15 shadow-[0_0_0_2px_rgba(0,0,0,0.2)]
  data-[recording=true]:bg-azure-600
  data-[recording=true]:shadow-[0_0_28px_rgba(29,111,242,0.6)]" />
11<FlipCameraButton/>
Frosted Ink 900 disc with a Cloud-white flip glyph, rightmost slot of the rail — switches front/rear camera.
Shape code — TSX + Tailwind
<button className="absolute right-[34px] top-[15px] grid size-[46px]
  place-items-center rounded-full border border-white/[0.18]
  bg-ink-900/50 backdrop-blur-xl">
  <SwitchCamera className="size-[21px] text-white" strokeWidth={1.8} />
</button>
12<PromiseLine/>
One quiet caption in Cloud white at 55% replacing the old debug overlay — the fullscreen ritual keeps a single promise: posting is instant.
Shape code — TSX + Tailwind
<p className="absolute inset-x-0 bottom-[98px] text-center text-xs text-white/55">
  Drop a moment — it goes live instantly
</p>
13<HomeIndicator/>
System home indicator in Cloud white at 40% opacity, resting on the black camera shell — the tab bar itself stays hidden while creating.
Shape code — TSX + Tailwind
<div className="absolute bottom-2 left-1/2 h-[5px] w-[134px] -translate-x-1/2
  rounded-[3px] bg-white/40" />
S9

Onboarding 1 — Welcome

After · 1 Welcome
9:41
What2Do
47 people are out in SF right now
get started →
Already have an account? Log in
1<FootageHero/>
Full-bleed festival-footage still covering the entire 393×852 frame (x0 y0, background-size:cover, centered) over the near-black #100F12 Ink canvas (Ink 900 #17161C family) — the app opens inside a live moment, not on a blank marketing slide.
Shape code — TSX + Tailwind
<div className="absolute inset-0 bg-[#100F12]
  bg-[url('/footage/party-tall.jpg')] bg-cover bg-center" />
2<HeroScrim/>
Full-frame legibility scrim (x0 y0, 393×852px): a 180° Ink wash — rgba(10,10,14,.55) at 0% → rgba(10,10,14,.18) at 42% → rgba(10,10,14,.8) at 100% — darkening top and bottom so the Cloud #FFFFFF status bar, wordmark and CTA stay readable while the footage shows through mid-frame.
Shape code — TSX + Tailwind
<div className="absolute inset-0
  bg-[linear-gradient(180deg,rgba(10,10,14,0.55)_0%,rgba(10,10,14,0.18)_42%,rgba(10,10,14,0.8)_100%)]" />
3<StatusBar/>
Light iOS status bar: 393×56px strip at y0, content bottom-aligned with 0 34px 6px padding; the 9:41 clock set in the SF-Pro-like default sans at 16px weight 650, letter-spacing .02em, Cloud #FFFFFF, with matching white signal / Wi-Fi / battery glyphs on the right.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-0 z-20 flex h-14 items-end
  justify-between px-[34px] pb-1.5 text-white">
  <span className="text-[16px] font-[650] tracking-[0.02em]">9:41</span>
  <StatusGlyphs />
</div>
4<DynamicIsland/>
118×34px pure-black (#000, Ink 900 #17161C family) pill at top:12px, horizontally centered (x137.5–255.5), radius 18px — the hardware cutout the footage plays behind.
Shape code — TSX + Tailwind
<div className="absolute left-1/2 top-3 h-[34px] w-[118px]
  -translate-x-1/2 rounded-[18px] bg-black" />
5<Wordmark/>
The brand lockup "What2Do" at y318 inside 28px side insets (337px column, text centered): Bricolage Grotesque 46px weight 800, letter-spacing −0.03em, Cloud #FFFFFF — with the "2" in light azure #5AA0FF, the on-photo tint of Azure 500 #3D8BFF.
Shape code — TSX + Tailwind
<h1 className="font-bricolage text-[46px] font-extrabold
  tracking-[-0.03em] text-white">
  What<span className="text-[#5AA0FF]">2</span>Do
</h1>
6<LiveProofChip/>
Frosted live-proof pill 18px under the wordmark (≈y391, centered): 26px tall, radius 13px, padding 0 12px, 7px flex gap, Cloud #FFFFFF at 14% fill (rgba(255,255,255,.14)) with an 8px backdrop blur; carries the Siren dot plus "47 people are out in SF right now" in default sans 11.5px weight 700, Cloud at 92% (rgba(255,255,255,.92)).
Shape code — TSX + Tailwind
<div className="mt-[18px] inline-flex h-[26px] items-center gap-[7px]
  rounded-[13px] bg-white/[0.14] px-3 backdrop-blur-[8px]">
  <LiveDot />
  <span className="text-[11.5px] font-bold text-white/[0.92]">
    47 people are out in SF right now
  </span>
</div>
7<LiveDot/>
7×7px Siren #ED2F4E dot, radius 50%, leading the proof chip's row — the "right now" heartbeat that echoes the live-badge red used across the app.
Shape code — TSX + Tailwind
<span className="size-[7px] rounded-full bg-[#ED2F4E]" />
8<PrimaryCTA/>
"get started →" — the only azure on screen: absolute x24–x369 (345px wide), top y716 (bottom:84px), 52px tall, radius 15px, Azure 500 #3D8BFF fill, flat (no glow); label centered in default sans 15.5px weight 800, Cloud #FFFFFF.
Shape code — TSX + Tailwind
<button className="absolute left-1/2 w-[220px] -translate-x-1/2 bottom-[84px] flex h-[52px]
  items-center justify-center rounded-full bg-azure-500
  text-[15.5px] font-extrabold text-white
 ">
  get started →
</button>
9<LoginLine/>
Returning-user line at bottom:46px (≈y790, full-width, centered): "Already have an account?" in default sans 12.5px weight 500, Cloud at 60% (rgba(255,255,255,.6)) — with "Log in" bolded to weight 700 in solid Cloud #FFFFFF.
Shape code — TSX + Tailwind
<p className="absolute inset-x-0 bottom-[46px] text-center
  text-[12.5px] font-medium text-white/60">
  Already have an account?
  <b className="font-bold text-white">Log in</b>
</p>
10<HomeIndicator/>
iOS home indicator: 134×5px bar, radius 3px, at bottom:8px (y839), centered x129.5–263.5, Cloud #FFFFFF at 40% opacity so it reads over the dark footage.
Shape code — TSX + Tailwind
<div className="absolute bottom-2 left-1/2 h-[5px] w-[134px]
  -translate-x-1/2 rounded-[3px] bg-white/40" />
S9B

Onboarding 1B — Log in (phone ⇄ @name)

After · 1B Log in — phone
9:41
Welcome back
🇺🇸 +1 (201) 555-0123
We'll text you a code to log you back in.
log in →
mobile
(443) 285-9002
12ABC3DEF
4GHI5JKL6MNO
7PQRS8TUV9WXYZ
0
After · 1B Log in — @name
9:41
Welcome back
@ arad
•••••••• show
log in →
1<BackChip/>
36×36px circle at x14 y60, fill #F4F4F6, chevron-left 16×16px stroked Ink 900 #17161C at 2.2px (frame 1). Returns to Welcome.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[60px] grid size-9 place-items-center rounded-full bg-[#F4F4F6]">
  <ChevronLeft className="size-4 text-ink-900" strokeWidth={2.2} />
</button>
2<ScreenTitle/>
"Welcome back" — system sans 26px weight 800, letter-spacing −0.4px, Ink 900 #17161C, centered at y152 (frame 1).
Shape code — TSX + Tailwind
<h1 className="absolute top-[152px] inset-x-6 text-center text-[26px] font-extrabold tracking-[-0.4px] text-ink-900">Welcome back</h1>
3<PhoneField/>
Phone input at y232 (frame 1), 24px insets (345×56px): 1.5px border Hairline #E8E6E1, radius 14px, 16px padding, 10px gap on Cloud #FFFFFF. 🇺🇸 18px, "+1" 15px/700 Ink 900 #17161C, 1×22px Hairline divider.
Shape code — TSX + Tailwind
<div className="absolute top-[232px] inset-x-6 flex h-14 items-center gap-2.5 rounded-[14px] border-[1.5px] border-[#E8E6E1] bg-white px-4">
  <span className="text-[18px]">🇺🇸</span><span className="text-[15px] font-bold text-ink-900">+1</span>
  <span className="h-[22px] w-px bg-[#E8E6E1]" />{caret}{input}
</div>
4<InputCaret/>
Active caret in the field: 2×22px bar, radius 1px, Azure 600 #1D6FF2.
Shape code — TSX + Tailwind
<span className="h-[22px] w-[2px] rounded-[1px] bg-azure-600 motion-safe:animate-pulse" />
5<PlaceholderText/>
Ghost number "(201) 555-0123" — 17px weight 500, letter-spacing .02em, Ink 300 #9997A3.
Shape code — TSX + Tailwind
<input className="w-full bg-transparent text-[17px] font-medium tracking-[0.02em] text-ink-900 placeholder:text-ink-300" placeholder="(201) 555-0123" type="tel" />
6<HelperText/>
"We'll text you a code to log you back in." — 12px weight 500, line-height 1.55, Ink 300 #9997A3, centered at y306 in 36px insets (frame 1).
Shape code — TSX + Tailwind
<p className="absolute top-[306px] inset-x-9 text-center text-[12px] font-medium leading-[1.55] text-ink-300">We'll text you a code to log you back in.</p>
7<LoginCTA/>
"log in →" — 220×50px centered capsule at y376 (frame 1), disabled Disabled-azure #A9CCFF until a valid number, then flat Azure 500 #3D8BFF. 15.5px/800 Cloud, lowercase. Continues to the shared code page.
Shape code — TSX + Tailwind
<button disabled={!valid} className="absolute top-[376px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white disabled:bg-[#A9CCFF]">log in →</button>
8<SwitchToNameLink/>
The method switch as PLAIN TEXT — no toggles (locked): "Prefer a username? Sign in with @name" at y446 (frame 1), 12.5px weight 500 Ink 500 #6C6B76, action bolded 700 Azure 600 #1D6FF2. Mirrors the sign-up page's line — one pattern everywhere.
Shape code — TSX + Tailwind
<p className="absolute top-[446px] inset-x-0 text-center text-[12.5px] font-medium text-ink-500">
  Prefer a username? <button onClick={() => setMode('name')} className="font-bold text-azure-600">Sign in with @name</button>
</p>
9<KeypadPanel/>
iOS number pad docked bottom on keypad-grey #D1D4DA — identical to the sign-up Number page.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 bottom-0 bg-[#D1D4DA] pb-8 pt-1.5">{suggestion}{rows}</div>
10<AutofillSuggestion/>
iOS autofill strip "mobile / (443) 285-9002" — 11px Ink 500 #6C6B76 label over the bolded saved number; one tap logs in.
Shape code — TSX + Tailwind
<button className="w-full py-1.5 text-center text-[11px] text-ink-500">mobile<br /><b className="text-[13px] text-ink-900">(443) 285-9002</b></button>
11<DigitKey ×10/>
Digit keys 1–0: flex-1 × 44px, radius 6px, Cloud #FFFFFF on shadow 0 1.5px 0 rgba(20,18,28,.3); 23px digits, 9px letter sub-labels.
Shape code — TSX + Tailwind
<button className="flex h-11 flex-1 flex-col items-center justify-center rounded-md bg-white text-[23px] text-ink-900 shadow-[0_1.5px_0_rgba(20,18,28,0.3)]">5<span className="text-[9px] font-bold tracking-[0.12em]">JKL</span></button>
12<GhostKey ×2/>
Transparent corner keys (empty + ⌫): 44px slot, no fill, glyph 19px Charcoal #3A3945.
Shape code — TSX + Tailwind
<button className="flex h-11 flex-1 items-center justify-center text-[19px] text-[#3A3945]">⌫</button>
13<BlueHalfName/>
Frame 2 flips into the @NAME WORLD, wearing the Join-The-Crowd colors (Arad's call — the username path is the blue one; phone mode stays white): top 426px 180° gradient Azure 600 #1D6FF2 → Azure 500 #3D8BFF, 28px bottom radius; with it come the light status bar, a frosted back chip (Cloud 16%, white chevron) and the white title moved up to y124.
Shape code — TSX + Tailwind
<span className="absolute inset-x-0 top-0 h-[426px] rounded-b-[28px]
  bg-[linear-gradient(180deg,#1D6FF2_0%,#3D8BFF_100%)]" />
14<HandleLoginField/>
The handle input as a floating white card on the blue band (frame 2): y216, 345×56px, NO border — Cloud #FFFFFF on deep-blue shadow 0 10px 26px rgba(13,47,102,.22), radius 14px. "@" 17px/700 Ink 300 #9997A3, "arad" 17px/700 Ink 900 #17161C, azure caret.
Shape code — TSX + Tailwind
<div className="absolute top-[216px] inset-x-6 flex h-14 items-center gap-0.5 rounded-[14px] bg-white px-4 shadow-[0_10px_26px_rgba(13,47,102,0.22)]">
  <span className="text-[17px] font-bold text-ink-300">@</span>
  <input className="min-w-0 flex-1 bg-transparent text-[17px] font-bold text-ink-900" />
</div>
15<PasswordLoginField/>
Password card at y288 (frame 2), same white-on-blue shell: masked "••••••••" 17px/700 Ink 900 at .1em spacing, "show" toggle 12.5px/600 Ink 300 right-anchored. Set on the Claim-your-@name page during sign-up.
Shape code — TSX + Tailwind
<div className="absolute top-[288px] inset-x-6 flex h-14 items-center rounded-[14px] bg-white px-4 shadow-[0_10px_26px_rgba(13,47,102,0.22)]">
  <input type="password" className="min-w-0 flex-1 bg-transparent text-[17px] font-bold tracking-[0.1em] text-ink-900" />
  <button className="ml-auto text-[12.5px] font-semibold lowercase text-ink-300">show</button>
</div>
16<ForgotPasswordLink/>
"Forgot password?" — 12.5px weight 700, Cloud at 90% (rgba(255,255,255,.9)), centered at y360 on the blue band (frame 2). Falls back to a code sent to the backup email if attached; otherwise phone login still works.
Shape code — TSX + Tailwind
<button className="absolute top-[360px] inset-x-0 text-center text-[12.5px] font-bold text-white/90">Forgot password?</button>
17<LoginCTAEnabled/>
"log in →" (frame 2) — 220×50px capsule at y460 in the white half, enabled Azure 500 #3D8BFF (both fields filled). Straight into Home, orgs and friends intact — the same account regardless of login method.
Shape code — TSX + Tailwind
<button className="absolute top-[460px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white">log in →</button>
18<SwitchToPhoneLink/>
The way back, same plain-text pattern: "Use your number? Sign in with phone" at y530 in the white half (frame 2), 12.5px weight 500 Ink 500 #6C6B76, action bolded 700 Azure 600 #1D6FF2.
Shape code — TSX + Tailwind
<p className="absolute top-[530px] inset-x-0 text-center text-[12.5px] font-medium text-ink-500">
  Use your number? <button onClick={() => setMode('phone')} className="font-bold text-azure-600">Sign in with phone</button>
</p>
S10

Onboarding 2 — Phone number

After · 2 Number
9:41
What's your number?
🇺🇸 +1 (201) 555-0123
By signing up you agree to our Terms and Privacy Policy.
next →
Prefer a username? Sign up with @name
mobile
(443) 285-9002
12ABC3DEF
4GHI5JKL6MNO
7PQRS8TUV9WXYZ
0
1<BackChip/>
Circular back button, 36×36px at x14 y60 (z-index 5), radius 50% (fully round), fill chip-grey #F4F4F6 (a near-Cloud #FFFFFF tint). Chevron-left icon 16×16px, stroke Ink 900 #17161C at 2.2px width, no fill.
Shape code — TSX + Tailwind
<button className="absolute left-[14px] top-[60px] z-[5] grid h-9 w-9 place-items-center rounded-full bg-[#F4F4F6]">
  <ChevronLeft className="h-4 w-4 stroke-[2.2] text-ink-900" />
</button>
2<ScreenTitle/>
“What’s your number?” centered at y152, inset 24px per side (345px wide). System sans (SF Pro–style app default), 26px, weight 800, letter-spacing −0.4px, color Ink 900 #17161C.
Shape code — TSX + Tailwind
<h1 className="absolute inset-x-6 top-[152px] text-center text-[26px] font-extrabold tracking-[-0.4px] text-ink-900">
  What&apos;s your number?
</h1>
3<PhoneField/>
Input shell 345×56px at x24 y232, radius 14px, fill Cloud #FFFFFF, border 1.5px solid Hairline #E8E6E1; flex row, 10px item gaps, 16px side padding. Leads with the 🇺🇸 flag emoji at 18px, dial prefix “+1” in system sans 15px weight 700 Ink 900 #17161C, then a 1×22px vertical divider in Hairline #E8E6E1.
Shape code — TSX + Tailwind
<div className="absolute inset-x-6 top-[232px] flex h-14 items-center gap-2.5 rounded-[14px] border-[1.5px] border-hairline bg-white px-4">
  <span className="text-lg">🇺🇸</span>
  <span className="text-[15px] font-bold text-ink-900">+1</span>
  <span className="h-[22px] w-px bg-hairline" />
  {/* InputCaret + PlaceholderText */}
</div>
4<InputCaret/>
Text caret inside the field: 2×22px bar, radius 1px, Azure 600 #1D6FF2, vertically centered in the 56px field (y≈249–271), sitting ≈x110 just right of the Hairline divider.
Shape code — TSX + Tailwind
<span className="h-[22px] w-[2px] rounded-[1px] bg-azure-600 motion-safe:animate-blink" />
5<PlaceholderText/>
Ghost value “(201) 555-0123” beside the caret (≈x122, baseline centered in the field): system sans, 17px, weight 500, letter-spacing 0.02em, Ink 300 #9997A3.
Shape code — TSX + Tailwind
<span className="text-[17px] font-medium tracking-[0.02em] text-ink-300">
  (201) 555-0123
</span>
6<LegalText/>
Terms line centered at y306, inset 36px per side (321px wide): system sans, 12px, weight 500, line-height 1.55, Ink 300 #9997A3. Inline links “Terms” and “Privacy Policy” bolded to weight 700 in Azure 600 #1D6FF2.
Shape code — TSX + Tailwind
<p className="absolute inset-x-9 top-[306px] text-center text-xs font-medium leading-[1.55] text-ink-300">
  By signing up you agree to our{' '}
  <b className="font-bold text-azure-600">Terms</b> and{' '}
  <b className="font-bold text-azure-600">Privacy Policy</b>.
</p>
7<CtaDisabled/>
“next →” primary button in its disabled state: 345×52px at x24 y376, radius 15px, fill Disabled-azure #A9CCFF with the glow shadow removed (enabled state = Azure 600 #1D6FF2 + shadow 0 8px 22px rgba(29,111,242,.4)). Label in system sans, 15.5px, weight 800, Cloud #FFFFFF, flex-centered.
Shape code — TSX + Tailwind
<button
  disabled
  className="absolute inset-x-6 top-[376px] flex h-[52px] items-center justify-center rounded-full bg-azure-500 text-[15.5px] font-extrabold text-white shadow-[0_8px_22px_rgba(29,111,242,.4)] disabled:bg-azure-disabled disabled:shadow-none"
>
  next →
</button>
8<KeypadPanel/>
iOS-style keypad slab pinned to the bottom edge: full-bleed 393×292px (y560–852), fill Keypad-grey #D1D4DA, 6px side padding, z-index 30. Hosts the autofill strip on top plus 4 key rows separated by 7px vertical margins.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 bottom-0 z-30 h-[292px] bg-[#D1D4DA] px-1.5">
  {/* AutofillSuggestion + 4 × key rows */}
</div>
9<AutofillSuggestion/>
Centered autofill strip capping the panel (y≈560–607; padding 7px top / 6px bottom, line-height 1.35): line 1 “mobile” in system sans 11px, weight 400, Ink 500 #6C6B76; line 2 “(443) 285-9002” at 14px, weight 600, Ink 900 #17161C.
Shape code — TSX + Tailwind
<div className="pb-1.5 pt-[7px] text-center text-[11px] font-normal leading-[1.35] text-ink-500">
  mobile
  <br />
  <b className="text-sm font-semibold text-ink-900">(443) 285-9002</b>
</div>
10<DigitKey ×10/>
Ten digit keys across 4 rows (row tops ≈y607 / 658 / 709 / 760): each ≈123×44px (flex-1, 6px column gaps), radius 6px, fill Cloud #FFFFFF, press-shadow 0 1.5px 0 rgba(20,18,28,.3). Digit glyph in system sans 23px, weight 400, line-height 1, Ink 900 #17161C; sub-letter row (ABC…WXYZ) 9px, weight 700, letter-spacing 0.12em, Ink 900 #17161C, 1px below the digit.
Shape code — TSX + Tailwind
<div className="mb-[7px] flex gap-1.5">
  <button className="flex h-11 flex-1 flex-col items-center justify-center rounded-md bg-white text-[23px] font-normal leading-none text-ink-900 shadow-[0_1.5px_0_rgba(20,18,28,.3)]">
    2
    <i className="mt-px h-[9px] text-[9px] font-bold not-italic tracking-[.12em]">ABC</i>
  </button>
  {/* ×10 total: 1 (no letters), 2 ABC … 9 WXYZ, then 0 */}
</div>
11<GhostKey ×2/>
Bottom-row flanks (≈y760): two chromeless key cells ≈123×44px — an empty left spacer and the ⌫ backspace on the right — background transparent so Keypad-grey #D1D4DA shows through, no shadow, radius 6px footprint. ⌫ glyph in system sans 19px, weight 400, charcoal #3A3945.
Shape code — TSX + Tailwind
<button className="flex h-11 flex-1 items-center justify-center rounded-md bg-transparent text-[19px] text-[#3A3945] shadow-none">
  ⌫
</button>
{/* left twin: identical shell, empty content */}
12<SignupAltLink/>
The sign-up method choice as plain text (no toggles): "Prefer a username? Sign up with @name" — 12.5px weight 500 Ink 500 #6C6B76 centered at y446, the action bolded 700 Azure 600 #1D6FF2. PHONE IS THE DEFAULT (this page); tapping jumps straight to the Claim-your-@name page to sign up with @name + password instead — a phone can be attached later in Settings. Same account either way.
Shape code — TSX + Tailwind
<p className="absolute top-[446px] inset-x-0 text-center text-[12.5px] font-medium text-ink-500">
  Prefer a username? <button onClick={() => go('/signup/handle')} className="font-bold text-azure-600">Sign up with @name</button>
</p>
S11

Onboarding 3 — Verification code

After · 3 Code
9:41
What2Do Your verification code is 224-466 now
Enter the code
we texted you
224
Didn't get it? Resend
Use email instead
next →
From Messages
224466
12ABC3DEF
4GHI5JKL6MNO
7PQRS8TUV9WXYZ
0
1<SmsCodeBanner/>
iOS push banner dropped in at y 60→116: 10px side insets (373px wide, ~56px tall), radius 18px, Cloud #FFFFFF card on shadow 0 6px 22px rgba(20,18,28,.16) plus a 1px keyline ring rgba(20,18,28,.04), 11×14px padding, 10px gap, z-index 20. Leading 34×34px app tile, radius 9px, Azure 100 #E8F1FF, holding a 17×17px chat glyph stroked 2px in Azure 600 #1D6FF2. Type is SF Pro-class system sans (-apple-system/"Segoe UI" stack): "What2Do" 12.5px/800 Ink 900 #17161C over "Your verification code is 224-466" 12px/500 Ink 500 #6C6B76, with the trailing "now" stamp 11px/500 Ink 300 #9997A3.
Shape code — TSX + Tailwind
<div className="absolute inset-x-2.5 top-[60px] z-20 flex items-center
  gap-2.5 rounded-[18px] bg-white px-3.5 py-[11px]
  shadow-[0_6px_22px_rgba(20,18,28,0.16),0_0_0_1px_rgba(20,18,28,0.04)]">
  <span className="grid size-[34px] flex-none place-items-center
    rounded-[9px] bg-azure-100">
    <MessageCircle className="size-[17px] text-azure-600" strokeWidth={2}/>
  </span>
  <span className="min-w-0 flex-1">
    <span className="block text-[12.5px] font-extrabold text-ink-900">What2Do</span>
    <span className="block text-[12px] font-medium text-ink-500">
      Your verification code is 224-466</span>
  </span>
  <span className="flex-none text-[11px] font-medium text-ink-300">now</span>
</div>
2<ScreenTitle/>
"Enter the code / we texted you" centered at y 174→239 with 32px side insets — SF Pro-class system sans 26px/800, -0.4px tracking, line-height 1.25, Ink 900 #17161C. Same title block every onboarding step uses, so the flow reads as one voice.
Shape code — TSX + Tailwind
<h1 className="absolute inset-x-8 top-[174px] text-center text-[26px]
  font-extrabold leading-[1.25] tracking-[-0.4px] text-ink-900">
  Enter the code<br/>we texted you</h1>
3<CodeCell/> ×5
The static OTP cells of the six-box row at y 296→352 (row centered with 8px gaps, spanning x≈38→355): each 46×56px, radius 13px, Cloud #FFFFFF fill inside a 1.5px Hairline #E8E6E1 border. Typed digits 2 · 2 · 4 (cells 1–3) set in SF Pro-class system sans 22px/800 Ink 900 #17161C; cells 5–6 wait empty.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 top-[296px] flex justify-center gap-2">
  {digits.map((d, i) =>
    <span key={i} className="grid h-14 w-[46px] place-items-center
      rounded-[13px] border-[1.5px] border-[#E8E6E1] bg-white
      text-[22px] font-extrabold text-ink-900">{d}</span>)}
</div>
4<CodeCellActive/>
Cell 4 of the row (x≈200, y 296, same 46×56px radius-13 Cloud #FFFFFF body) is the live one: border flips to 1.5px Azure 600 #1D6FF2 with a 3px focus halo rgba(29,111,242,.14), and a centered 2×22px radius-1px caret in Azure 600 #1D6FF2 marks the insertion point. An entered digit would inherit the cell type spec — SF Pro-class system sans 22px/800 Ink 900 #17161C.
Shape code — TSX + Tailwind
<span className="grid h-14 w-[46px] place-items-center rounded-[13px]
  border-[1.5px] border-azure-600 bg-white
  shadow-[0_0_0_3px_rgba(29,111,242,0.14)]">
  <span className="h-[22px] w-[2px] animate-caret rounded-[1px]
    bg-azure-600" />
</span>
5<ResendLine/>
Full-width centered helper at y 378 — SF Pro-class system sans 13px/500 Ink 500 #6C6B76 "Didn't get it?" with the inline "Resend" action bolded to 13px/700 Azure 600 #1D6FF2: the one tappable word carries the one accent color.
Shape code — TSX + Tailwind
<p className="absolute inset-x-0 top-[378px] text-center text-[13px]
  font-medium text-ink-500">
  Didn't get it? <b className="font-bold text-azure-600">Resend</b>
</p>
6<UseEmailLink/>
Escape hatch centered at y 404 — "Use email instead" in SF Pro-class system sans 12.5px/500 Ink 300 #9997A3, deliberately half a pixel smaller and one grey step quieter than the Resend row so the fallback never competes with the retry.
Shape code — TSX + Tailwind
<button className="absolute inset-x-0 top-[404px] text-center
  text-[12.5px] font-medium text-ink-300">Use email instead</button>
7<NextCtaDisabled/>
Primary CTA parked at y 448→500 with 24px side insets (345×52px), radius 15px, shown disabled while the code is short: Disabled-azure #A9CCFF fill with the glow shadow stripped. Enabled spec it will snap to: Azure 600 #1D6FF2 + shadow 0 8px 22px rgba(29,111,242,.4). Label "next →" in SF Pro-class system sans 15.5px/800 Cloud #FFFFFF.
Shape code — TSX + Tailwind
<button disabled={code.length < 6} className="absolute inset-x-6
  top-[448px] inline-flex h-[52px] items-center justify-center
  rounded-full bg-azure-500 text-[15.5px] font-extrabold text-white
 
  disabled:bg-[#A9CCFF] disabled:shadow-none">next →</button>
8<KeypadTray/>
The system number pad pinned to the bottom edge, y 560→852: full-bleed 393×292px slab of Keypad-grey #D1D4DA with 6px side padding, square corners (0 radius) and z-index 30 — faithful iOS keypad chrome, drawn not screenshotted, so hover targets stay live.
Shape code — TSX + Tailwind
<div className="absolute inset-x-0 bottom-0 z-30 h-[292px]
  bg-[#D1D4DA] px-1.5">
  <QuickTypeSuggestion />
  {rows.map((r) => <KeyRow key={r.id} keys={r.keys} />)}
</div>
9<QuickTypeSuggestion/>
The autofill strip topping the tray (y≈560→607; 7px top / 6px bottom padding, line-height 1.35, centered): "From Messages" in SF Pro-class system sans 11px/400 Ink 500 #6C6B76 over the tappable code "224466" at 14px/600 Ink 900 #17161C — the one-tap SMS shortcut that fills all six cells at once.
Shape code — TSX + Tailwind
<button className="w-full pb-1.5 pt-[7px] text-center text-[11px]
  leading-[1.35] text-ink-500">
  From Messages<br/>
  <b className="text-[14px] font-semibold text-ink-900">224466</b>
</button>
10<KeypadKey/> ×10
Digits 1–0 in four rows of 6px-gap keys (rows start y≈607 on a 51px pitch — 44px key + 7px gap): each key 123×44px, radius 6px, Cloud #FFFFFF with the pressed-edge shadow 0 1.5px 0 rgba(20,18,28,.3). Numeral in SF Pro-class system sans 23px/400 Ink 900 #17161C, its letter cluster (ABC…WXYZ) beneath at 9px/700, .12em tracking, Ink 900 #17161C.
Shape code — TSX + Tailwind
<div className="mb-[7px] flex gap-1.5">
  <button className="flex h-11 flex-1 flex-col items-center
    justify-center rounded-md bg-white leading-none
    text-[23px] font-normal text-ink-900
    shadow-[0_1.5px_0_rgba(20,18,28,0.3)]">
    2
    <i className="mt-px h-[9px] text-[9px] font-bold not-italic
      tracking-[0.12em]">ABC</i>
  </button>
</div>
11<KeypadGhostKey/> ×2
The bottom row's flanks around the 0 key: same 123×44px radius-6 footprint but transparent fill and no shadow, so the Keypad-grey #D1D4DA tray reads through. The right ghost carries the ⌫ backspace glyph in SF Pro-class system sans 19px/400 charcoal #3A3945; the left is a pure spacer keeping 0 centered.
Shape code — TSX + Tailwind
<button className="flex h-11 flex-1 items-center justify-center
  rounded-md bg-transparent text-[19px] text-[#3A3945]
  shadow-none">⌫</button>
S11B

Onboarding 3B — Claim your @name & password

After · 3B @name & password
9:41
Claim your @name
@ arad ✓ available
•••••••• show
8+ characters — your @name and password log you in on any device.
Add a backup email anytime in Settings.
next →
1<BlueHalf/>
The @name sign-up wears the Join-The-Crowd colors (Arad's call — the username world is blue): top 426px of the 852px frame, 180° gradient Azure 600 #1D6FF2 → Azure 500 #3D8BFF, 28px bottom-corner radius, white below.
Shape code — TSX + Tailwind
<span className="absolute inset-x-0 top-0 h-[426px] rounded-b-[28px]
  bg-[linear-gradient(180deg,#1D6FF2_0%,#3D8BFF_100%)]" />
2<BackChip/>
Frosted 36×36px circle at x14 y60: Cloud #FFFFFF at 16% fill, chevron-left 16×16px stroked Cloud at 2.2px.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[60px] grid size-9 place-items-center rounded-full bg-white/[0.16]">
  <ChevronLeft className="size-4 text-white" strokeWidth={2.2} />
</button>
3<ScreenTitle/>
"Claim your @name" — system sans 26px weight 800, letter-spacing −0.4px, Cloud #FFFFFF, centered at y124 on the blue band. This is where the @name shown all over the app is created, plus the password that backs @name login on new devices.
Shape code — TSX + Tailwind
<h1 className="absolute top-[124px] inset-x-6 text-center text-[26px] font-extrabold tracking-[-0.4px] text-white">Claim your @name</h1>
4<HandleField/>
The @name input as a floating white card on the blue band: y216, 345×56px, NO border — Cloud #FFFFFF on deep-blue shadow 0 10px 26px rgba(13,47,102,.22), radius 14px. "@" 17px/700 Ink 300 #9997A3, "arad" 17px/700 Ink 900 #17161C, 2×22px Azure 600 caret.
Shape code — TSX + Tailwind
<div className="absolute top-[216px] inset-x-6 flex h-14 items-center gap-0.5 rounded-[14px] bg-white px-4 shadow-[0_10px_26px_rgba(13,47,102,0.22)]">
  <span className="text-[17px] font-bold text-ink-300">@</span>
  <input className="min-w-0 flex-1 bg-transparent text-[17px] font-bold text-ink-900" value={handle} />
  {availability}
</div>
5<AvailabilityBadge/>
Live availability check, right-anchored (margin-left:auto): "✓ available" 11.5px weight 700 Azure 600 #1D6FF2; swaps to "taken" in Ink 300 with suggestions.
Shape code — TSX + Tailwind
<span className="ml-auto text-[11.5px] font-bold text-azure-600">✓ available</span>
6<PasswordField/>
The password card at y288, same white-on-blue shell: masked "••••••••" 17px weight 700 Ink 900 #17161C at .1em letter-spacing.
Shape code — TSX + Tailwind
<div className="absolute top-[288px] inset-x-6 flex h-14 items-center rounded-[14px] bg-white px-4 shadow-[0_10px_26px_rgba(13,47,102,0.22)]">
  <input type={shown ? 'text' : 'password'} className="min-w-0 flex-1 bg-transparent text-[17px] font-bold tracking-[0.1em] text-ink-900" />
  {showToggle}
</div>
7<ShowToggle/>
"show" — right-anchored in the password card, 12.5px weight 600 Ink 300 #9997A3, lowercase; toggles to "hide".
Shape code — TSX + Tailwind
<button onClick={() => setShown(v => !v)} className="ml-auto text-[12.5px] font-semibold lowercase text-ink-300">{shown ? 'hide' : 'show'}</button>
8<HelperText/>
Two-line helper in the WHITE half at y466, 36px insets, 12px weight 500 line-height 1.55 Ink 300 #9997A3: "8+ characters — your @name and password log you in on any device." + "Add a backup email anytime in Settings." Email deliberately NOT collected here.
Shape code — TSX + Tailwind
<p className="absolute top-[466px] inset-x-9 text-center text-[12px] font-medium leading-[1.55] text-ink-300">
  8+ characters — your @name and password log you in on any device.<br />Add a backup email anytime in Settings.
</p>
9<NextCTA/>
"next →" — 220×50px centered capsule, radius 999px, flat Azure 500 #3D8BFF, anchored bottom 66px in the white half. Enabled once the handle is available and the password valid; continues to Add a photo.
Shape code — TSX + Tailwind
<button disabled={!valid} className="absolute bottom-[66px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white disabled:bg-[#A9CCFF]">next →</button>
10<HomeIndicator/>
134×5px bar, radius 3px, centered at bottom 8px, Black #000000 at 40% opacity on the white half.
Shape code — TSX + Tailwind
<span className="absolute bottom-2 left-1/2 h-[5px] w-[134px] -translate-x-1/2 rounded-[3px] bg-black/40" />
S12

Onboarding 4 — Add a photo (empty ⇄ added)

After · 4 Photo — empty
9:41
Add a photo
Skip for now
venue or organization → next →
After · 4 Photo — added
9:41
Add a photo
Skip for now
venue or organization → next →
1<BackButton/>
36×36px circle at x14 y60, fill #F4F4F6, chevron-left 16×16px stroked Ink 900 #17161C at 2.2px. Identical on both states.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[60px] grid size-9 place-items-center rounded-full bg-[#F4F4F6]">
  <ChevronLeft className="size-4 text-ink-900" strokeWidth={2.2} />
</button>
2<ScreenTitle/>
"Add a photo" — system sans 26px weight 800, letter-spacing −0.4px, Ink 900 #17161C, centered at y152 in 24px insets. No subtitle: the placeholder itself explains the action.
Shape code — TSX + Tailwind
<h1 className="absolute top-[152px] inset-x-6 text-center text-[26px] font-extrabold tracking-[-0.4px] text-ink-900">Add a photo</h1>
3<EmptyPhotoSquircle/>
State 1 (empty): a 132×132px squircle, radius 60px (~45% — near-circle roundness), quiet grey #F1F1F4 fill on a 1.5px Hairline #E8E6E1 keyline, centered at y308. A single 44×44px camera glyph stroked Ink 300 #9997A3 at 1.8px sits dead-center — the whole shape is the tap target, no extra badge.
Shape code — TSX + Tailwind
<button className="absolute left-1/2 top-[308px] grid size-[132px] -translate-x-1/2 place-items-center rounded-[60px] bg-[#F1F1F4] shadow-[0_0_0_1.5px_#E8E6E1]">
  <Camera className="size-[44px] text-ink-300" strokeWidth={1.8} />
</button>
4<PhotoSquircle/>
State 2 (added): the same 132×132px squircle, radius 60px, now filled edge-to-edge by the chosen photo — no camera icon, no badge, no blue. Only the 1.5px Hairline #E8E6E1 keyline and a soft drop shadow 0 12px 30px rgba(20,18,28,.12) ground it.
Shape code — TSX + Tailwind
<button className="absolute left-1/2 top-[308px] size-[132px] -translate-x-1/2 overflow-hidden rounded-[60px] shadow-[0_0_0_1.5px_#E8E6E1,0_12px_30px_rgba(20,18,28,0.12)]">
  <Image src={draft} alt="" fill className="object-cover" />
</button>
5<SkipLink/>
"Skip for now" — system sans 13px weight 600, Ink 300 #9997A3, centered full-width at y482 on both states.
Shape code — TSX + Tailwind
<button className="absolute top-[482px] inset-x-0 text-center text-[13px] font-semibold text-ink-300">Skip for now</button>
6<OrgSwitchButton/>
The organization entry point at the START of profile setup — upgraded from a text link to a proper SECONDARY capsule because both paths matter equally: 220×46px outline capsule at bottom 128px (12px above the primary CTA), Cloud #FFFFFF fill, 1.5px border Azure 500 #3D8BFF, label "venue or organization →" 14px weight 800 lowercase Azure 500 #3D8BFF, on both photo states. Visibly a first-class alternative to "next →", yet still ONE flow — no fork, no separate signup. Tapping it flips the SAME flow into organization mode — this page becomes "Add your logo", the flow continues through the Organization profile page (S12B below), and the friends page becomes "add your team" — every other page is identical. Under the hood the backbone stays ONE phone-verified account per person — the organization is a page ATTACHED to it, never a second account on the number. The same number can therefore hold both a personal profile and org pages: the org path still creates the personal account quietly underneath, and identity is switched later from the profile menu / Host composer ("acting as: You / Warehouse SF"), Instagram-style. The app never divides.
Shape code — TSX + Tailwind
<button onClick={() => setAccountMode('org')} className="absolute bottom-[128px] left-1/2 h-[46px] w-[220px] -translate-x-1/2 rounded-full border-[1.5px] border-azure-500 bg-white text-[14px] font-extrabold lowercase text-azure-500">venue or organization →</button>
7<NextCTA/>
"next →" — 220×50px centered capsule (radius 999px) at bottom 66px. State 1: disabled #A9CCFF; state 2: enabled Azure 500 #3D8BFF, flat, no glow. Label 15.5px weight 800 Cloud #FFFFFF, lowercase.
Shape code — TSX + Tailwind
<button disabled={!draft} className="absolute bottom-[66px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white disabled:bg-[#A9CCFF]">next →</button>
8<HomeIndicator/>
134×5px bar, radius 3px, centered at bottom 8px, Black #000000 at 40% opacity.
Shape code — TSX + Tailwind
<span className="absolute bottom-2 left-1/2 h-[5px] w-[134px] -translate-x-1/2 rounded-[3px] bg-black/40" />
S12B

Onboarding 4B — Organization profile (org mode only)

After · 4B Org profile
9:41
Set up your organization
Warehouse SF
Photos and location come later.
Skip for now
next →
1<BackButton/>
36×36px circle at x14 y60, fill #F4F4F6, chevron-left 16×16px stroked Ink 900 #17161C at 2.2px. This page appears ONLY after tapping "venue or organization →" on Add-a-photo; back returns there in personal mode.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[60px] grid size-9 place-items-center rounded-full bg-[#F4F4F6]">
  <ChevronLeft className="size-4 text-ink-900" strokeWidth={2.2} />
</button>
2<ScreenTitle/>
"Set up your organization" — system sans 26px weight 800, letter-spacing −0.4px, Ink 900 #17161C, centered at y152 (the same title slot as the personal photo page — the two pages mirror each other). Deliberately minimal: this page asks for exactly TWO things — a logo and a name.
Shape code — TSX + Tailwind
<h1 className="absolute top-[152px] inset-x-6 text-center text-[26px] font-extrabold tracking-[-0.4px] text-ink-900">Set up your organization</h1>
3<OrgLogoTile/>
The logo slot, the page's hero (mirroring the personal photo squircle's y-rhythm): 120×120px squircle (radius 38px) centered at y308, 135° azure gradient #1D6FF2 → #3D8BFF with the monogram "W" 42px weight 800 Cloud #FFFFFF, shadow 0 12px 30px rgba(29,111,242,.28). Tap to upload the real logo; empty state = grey #F1F1F4 + camera glyph, like the personal page.
Shape code — TSX + Tailwind
<button className="absolute left-1/2 top-[308px] grid size-[120px] -translate-x-1/2 place-items-center rounded-[38px] bg-[linear-gradient(135deg,#1D6FF2,#3D8BFF)] text-[42px] font-extrabold text-white shadow-[0_12px_30px_rgba(29,111,242,0.28)]">
  {logo ? <Image src={logo} alt="" fill className="rounded-[38px] object-cover" /> : initial}
</button>
4<OrgNameField/>
The single input on the page: 52px field at y468 in 24px insets — 1.5px border Hairline #E8E6E1, radius 14px, 16px side padding, value "Warehouse SF" 15.5px weight 700 Ink 900 #17161C on Cloud #FFFFFF. No caption label — with only one field, the context is obvious.
Shape code — TSX + Tailwind
<input className="absolute top-[468px] inset-x-6 flex h-[52px] items-center rounded-[14px] border-[1.5px] border-[#E8E6E1] bg-white px-4 text-[15.5px] font-bold text-ink-900" placeholder="Organization name" defaultValue="Warehouse SF" />
5<LaterNote/>
"Photos and location come later." — system sans 11.5px weight 500, Ink 300 #9997A3, centered full-width at y538. The de-clutter contract: everything that used to crowd this page (location field, photo tiles) moved to the org page and the first-host flow, so onboarding stays one-decision-per-screen.
Shape code — TSX + Tailwind
<p className="absolute top-[538px] inset-x-0 text-center text-[11.5px] font-medium text-ink-300">Photos and location come later.</p>
6<SkipLink/>
"Skip for now" — 12.5px weight 600 Ink 300 #9997A3, centered full-width at bottom 132px.
Shape code — TSX + Tailwind
<button className="absolute bottom-[132px] inset-x-0 text-center text-[12.5px] font-semibold text-ink-300">Skip for now</button>
7<NextCTA/>
"next →" — 220×50px centered capsule, radius 999px, flat Azure 500 #3D8BFF, anchored bottom 66px, label 15.5px weight 800 Cloud #FFFFFF lowercase. Continues into the same shared flow (permissions → crowd → team).
Shape code — TSX + Tailwind
<button className="absolute bottom-[66px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white">next →</button>
8<HomeIndicator/>
134×5px bar, radius 3px, centered at bottom 8px, Black #000000 at 40% opacity.
Shape code — TSX + Tailwind
<span className="absolute bottom-2 left-1/2 h-[5px] w-[134px] -translate-x-1/2 rounded-[3px] bg-black/40" />
S13

Onboarding 5 — Permissions

After · 5 Permissions
9:41
Enable permissions
2 of 3 enabled
📍 Location Find live moments near you — the whole app runs on it.
👥 Contacts See which friends are already here.
🔔 Notifications Know when friends step out or ping you.
You can change these anytime in Settings.
next →
1<BackButton/>
36×36px circular chip (border-radius 50%) pinned at x 14px, y 60px on the 393×852 frame, z-index 5, fill light chip-grey #F4F4F6. Inside, a chevron-left glyph 16×16px, stroke Ink 900 #17161C at 2.2px stroke-width, no fill, grid-centered.
Shape code — TSX + Tailwind
<button className="absolute top-[60px] left-[14px] z-[5] grid h-9 w-9 place-items-center rounded-full bg-[#F4F4F6]">
  <ChevronLeft className="h-4 w-4 stroke-ink-900" strokeWidth={2.2} />
</button>
2<ScreenTitle/>
"Enable permissions" — SF-Pro-style system sans, 26px, weight 800, letter-spacing −0.4px, Ink 900 #17161C, text-align center. Positioned top y 124px with 24px left/right insets (345px text column) on the 393px-wide frame.
Shape code — TSX + Tailwind
<h1 className="absolute top-[124px] inset-x-6 text-center text-[26px] font-extrabold tracking-[-0.4px] text-ink-900">
  Enable permissions
</h1>
3<ProgressLine/>
Live counter "2 of 3 enabled" — system sans 13px, weight 600, Ink 300 #9997A3, centered full-width (left 0 / right 0) at y 168px, 44px below the title's top edge.
Shape code — TSX + Tailwind
<p className="absolute top-[168px] inset-x-0 text-center text-[13px] font-semibold text-ink-300">
  2 of 3 enabled
</p>
4<PermissionCardLocation/>
First tri-color permission card, ON state, accent Azure 600 #1D6FF2. Shell: y 216px, 20px side insets → 353px wide (≈88px tall, 114px vertical pitch to the next card), light cream #FBF6EC fill (same beige family as the Add-friends cards), 1px border #F6EFE0, radius 16px, no shadow, padding 18px vertical / 16px horizontal, flex row with 13px gap. Leading icon slot 42×42px, transparent — no box, the emoji sits directly on the cream card — 📍 emoji at 24px, native emoji color. Text column: "Location" system sans 16px weight 800 Ink 900 #17161C; sub-copy "Find live moments near you — the whole app runs on it." 12.5px weight 500 Ink 500 #6C6B76, 2px top margin. Trailing checkbox 30×30px, radius 9px, ON override: fill + border Azure 600 #1D6FF2, ✓ glyph 15px weight 800 Cloud #FFFFFF, glow 0 3px 10px rgba(29,111,242,.35).
Shape code — TSX + Tailwind
<div className="absolute top-[216px] inset-x-5 flex items-center gap-[13px] rounded-2xl border border-[#F6EFE0] bg-[#FBF6EC] px-4 py-[18px]">
  <span className="grid h-[42px] w-[42px] flex-none place-items-center text-[24px]">📍</span>
  <span className="min-w-0 flex-1">
    <span className="block text-[16px] font-extrabold text-ink-900">Location</span>
    <span className="mt-[2px] block text-[12.5px] font-medium text-ink-500">Find live moments near you — the whole app runs on it.</span>
  </span>
  <span className="grid h-[30px] w-[30px] flex-none place-items-center rounded-[9px] border border-azure-600 bg-azure-600 text-[15px] font-extrabold text-white shadow-[0_3px_10px_rgba(29,111,242,.35)]">✓</span>
</div>
5<PermissionCardContacts/>
Second permission card, OFF state, accent Siren #ED2F4E. Same 353px-wide shell at y 330px: light cream #FBF6EC, 1px #F6EFE0 border, radius 16px, 18px/16px padding, 13px gap, no shadow. Icon slot 42×42px, transparent (no box), 👥 emoji at 24px, native emoji color. "Contacts" 16px/800 Ink 900 #17161C; sub-copy "See which friends are already here." 12.5px/500 Ink 500 #6C6B76. Trailing checkbox in the resting OFF state: 30×30px, radius 9px, Cloud #FFFFFF fill, 1.5px border #E3E2E7, no glyph.
Shape code — TSX + Tailwind
<div className="absolute top-[330px] inset-x-5 flex items-center gap-[13px] rounded-2xl border border-[#F6EFE0] bg-[#FBF6EC] px-4 py-[18px]">
  <span className="grid h-[42px] w-[42px] flex-none place-items-center text-[24px]">👥</span>
  <span className="min-w-0 flex-1">
    <span className="block text-[16px] font-extrabold text-ink-900">Contacts</span>
    <span className="mt-[2px] block text-[12.5px] font-medium text-ink-500">See which friends are already here.</span>
  </span>
  <span className="h-[30px] w-[30px] flex-none rounded-[9px] border-[1.5px] border-[#E3E2E7] bg-white" />
</div>
6<PermissionCardNotifications/>
Third permission card, ON state, accent Gold #D4A017. Same 353px-wide shell at y 444px: light cream #FBF6EC, 1px #F6EFE0 border, radius 16px, 18px/16px padding, 13px gap. Icon slot 42×42px, transparent (no box), 🔔 emoji at 24px, native emoji color. "Notifications" 16px/800 Ink 900 #17161C; sub-copy "Know when friends step out or ping you." 12.5px/500 Ink 500 #6C6B76. Trailing checkbox in the gold ON state: 30×30px, radius 9px, fill + border Gold #D4A017, ✓ glyph 15px/800 Cloud #FFFFFF, glow 0 3px 10px rgba(212,160,23,.35).
Shape code — TSX + Tailwind
<div className="absolute top-[444px] inset-x-5 flex items-center gap-[13px] rounded-2xl border border-[#F6EFE0] bg-[#FBF6EC] px-4 py-[18px]">
  <span className="grid h-[42px] w-[42px] flex-none place-items-center text-[24px]">🔔</span>
  <span className="min-w-0 flex-1">
    <span className="block text-[16px] font-extrabold text-ink-900">Notifications</span>
    <span className="mt-[2px] block text-[12.5px] font-medium text-ink-500">Know when friends step out or ping you.</span>
  </span>
  <span className="grid h-[30px] w-[30px] flex-none place-items-center rounded-[9px] border border-[#D4A017] bg-[#D4A017] text-[15px] font-extrabold text-white shadow-[0_3px_10px_rgba(212,160,23,.35)]">✓</span>
</div>
7<SettingsNote/>
Reassurance micro-copy "You can change these anytime in Settings." — system sans 11.5px, weight 500, Ink 300 #9997A3, centered full-width, anchored bottom 44px (top edge ≈ y 794), sitting below the CTA.
Shape code — TSX + Tailwind
<p className="absolute bottom-[44px] inset-x-0 text-center text-[11.5px] font-medium text-ink-300">
  You can change these anytime in Settings.
</p>
8<NextCTA/>
Primary CTA "next →" — 220×50px centered capsule, radius 999px (full pill), fill Azure 500 #3D8BFF (flat, no glow), anchored bottom 70px so it spans y 730–782. Label: system sans 15.5px, weight 800, Cloud #FFFFFF, flex-centered. Flat — no elevation shadow. (Disabled variant per kit: Disabled-azure #A9CCFF, no shadow.)
Shape code — TSX + Tailwind
<button className="absolute bottom-[70px] left-1/2 w-[220px] -translate-x-1/2 inline-flex h-[50px] items-center justify-center rounded-full bg-azure-500 text-[15.5px] font-extrabold text-white disabled:bg-[#A9CCFF] disabled:shadow-none">
  next →
</button>
9<HomeIndicator/>
iOS home indicator — 134×5px bar, radius 3px, horizontally centered (left 50%, translateX −50%), bottom 8px (spans y 839–844), currentColor Black #000000 at 40% opacity, z-index 31.
Shape code — TSX + Tailwind
<span className="absolute bottom-2 left-1/2 z-[31] h-[5px] w-[134px] -translate-x-1/2 rounded-[3px] bg-black/40" />
S14

Onboarding 6 — Join The Crowd (opt-in)

After · 6 Join the crowd
9:41
Join The Crowd
🔥 Count me in Meet friends and friend groups, and see trending spots and parties near you.
👻 Stay invisible You won't meet new friends and friends won't meet you.
next →
1<BlueHalf/>
The page splits exactly in half: the top 426px (of the 852px frame) is a brand-blue band — 180° gradient Azure 600 #1D6FF2 → Azure 500 #3D8BFF — with a 28px bottom-corner radius where it hands off to the white half. Clean, flat, professional: no glows, no pattern, no decoration.
Shape code — TSX + Tailwind
<span className="absolute inset-x-0 top-0 h-[426px] rounded-b-[28px]
  bg-[linear-gradient(180deg,#1D6FF2_0%,#3D8BFF_100%)]" />
2<BackButton/>
Frosted variant for the blue band: 36×36px circle at x14 y60, Cloud #FFFFFF at 16% fill (rgba(255,255,255,.16)), chevron-left 16×16px stroked Cloud #FFFFFF at 2.2px.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[60px] grid size-9 place-items-center rounded-full bg-white/[0.16]">
  <ChevronLeft className="size-4 text-white" strokeWidth={2.2} />
</button>
3<ScreenTitle/>
"Join The Crowd" — system sans 26px weight 800, letter-spacing −0.4px, Cloud #FFFFFF, centered at y124 in 24px insets on the blue band. No subtitle.
Shape code — TSX + Tailwind
<h1 className="absolute top-[124px] inset-x-6 text-center text-[26px] font-extrabold tracking-[-0.4px] text-white">Join The Crowd</h1>
4<OptInCard/>
The SELECTED choice lives in the BLUE half at y216, 20px side insets (353px wide), with its own color: solid Cloud #FFFFFF card, no border, radius 16px, floating on a deep-blue shadow 0 10px 30px rgba(13,47,102,.25). Padding 18px/16px, flex TOP-aligned, 13px gap. 🔥 emoji at 24px in a 42px transparent slot. "Count me in" 16px weight 800 Ink 900 #17161C; copy "Meet friends and friend groups, and see trending spots and parties near you." 12.5px weight 500 Ink 500 #6C6B76, line-height 1.4. Trailing 30×30px checkbox radius 9px, ON: fill + border Azure 600 #1D6FF2, ✓ 15px/800 Cloud #FFFFFF, glow 0 3px 10px rgba(29,111,242,.35).
Shape code — TSX + Tailwind
<button className="absolute top-[216px] inset-x-5 flex items-start gap-[13px] rounded-2xl bg-white px-4 py-[18px] shadow-[0_10px_30px_rgba(13,47,102,0.25)]">
  <span className="grid h-[42px] w-[42px] flex-none place-items-center text-[24px]">🔥</span>
  <span className="min-w-0 flex-1 text-left">
    <span className="block text-[16px] font-extrabold text-ink-900">Count me in</span>
    <span className="mt-[2px] block text-[12.5px] font-medium leading-[1.4] text-ink-500">Meet friends and friend groups, and see trending spots and parties near you.</span>
  </span>
  <span className="grid h-[30px] w-[30px] flex-none place-items-center rounded-[9px] border border-azure-600 bg-azure-600 text-[15px] font-extrabold text-white shadow-[0_3px_10px_rgba(29,111,242,.35)]">✓</span>
</button>
5<OptOutCard/>
The UNSELECTED choice lives in the WHITE half at y470, same 353px shell, with its own color: chip grey #F4F4F6 fill, 1px border #E8E8EC, no shadow — quietly recessive against the white. 👻 emoji at 24px. "Stay invisible" 16px/800 Ink 900 #17161C; clear trade-off copy "You won't meet new friends and friends won't meet you." 12.5px/500 Ink 500 #6C6B76, line-height 1.4. Resting checkbox: 30×30px radius 9px, Cloud fill, 1.5px border #E3E2E7, no glyph.
Shape code — TSX + Tailwind
<button className="absolute top-[470px] inset-x-5 flex items-start gap-[13px] rounded-2xl border border-[#E8E8EC] bg-[#F4F4F6] px-4 py-[18px]">
  <span className="grid h-[42px] w-[42px] flex-none place-items-center text-[24px]">👻</span>
  <span className="min-w-0 flex-1 text-left">
    <span className="block text-[16px] font-extrabold text-ink-900">Stay invisible</span>
    <span className="mt-[2px] block text-[12.5px] font-medium leading-[1.4] text-ink-500">You won't meet new friends and friends won't meet you.</span>
  </span>
  <span className="h-[30px] w-[30px] flex-none rounded-[9px] border-[1.5px] border-[#E3E2E7] bg-white" />
</button>
6<NextCTA/>
"next →" — 220×50px centered capsule, radius 999px, flat Azure 500 #3D8BFF, anchored bottom 70px in the white half, echoing the blue band. Label 15.5px weight 800 Cloud #FFFFFF, lowercase. Always enabled — one choice is preselected.
Shape code — TSX + Tailwind
<button className="absolute bottom-[70px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white">next →</button>
7<HomeIndicator/>
134×5px bar, radius 3px, centered at bottom 8px, Black #000000 at 40% opacity on the white half.
Shape code — TSX + Tailwind
<span className="absolute bottom-2 left-1/2 h-[5px] w-[134px] -translate-x-1/2 rounded-[3px] bg-black/40" />
S15

Onboarding 7 — Add friends (⇄ org team)

After · 7 Add friends
9:41
add friends if you
know them
AT Amin Tabrizian add
Nathaniel G. added ✓
next →
After · 7 Add your team — org mode
9:41
add your team
KD Kayla Doyle invite
MR Marco Ruiz invited ✓
next →
1<BackButton/>
36×36px circle at x14 y60, fill #F4F4F6, chevron-left 16×16px stroked Ink 900 #17161C at 2.2px.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[60px] grid size-9 place-items-center rounded-full bg-[#F4F4F6]">
  <ChevronLeft className="size-4 text-ink-900" strokeWidth={2.2} />
</button>
2<ScreenTitle/>
"add friends if you / know them" — lowercase, two lines broken after "you". System sans 22px weight 800, letter-spacing −0.3px, line-height 1.3, Ink 900 #17161C, centered at y152 in 24px insets. No subtitle.
Shape code — TSX + Tailwind
<h1 className="absolute top-[152px] inset-x-6 text-center text-[22px] font-extrabold leading-[1.3] tracking-[-0.3px] text-ink-900">
  add friends if you<br />know them
</h1>
3<SuggestionCard/> ×2
Contact-suggestion card shell: 20px side insets (353px wide), light cream #FBF6EC fill, 1px border #F6EFE0, radius 16px, no shadow. Padding 11px vertical / 14px horizontal, flex row align-center with 13px gap → ≈62px tall. Card 1 at y246 (Amin), card 2 at y322 (Nathaniel) — 76px pitch, 14px clear gap.
Shape code — TSX + Tailwind
<div className="absolute inset-x-5 top-[246px] flex items-center gap-[13px] rounded-2xl border border-[#F6EFE0] bg-[#FBF6EC] px-3.5 py-[11px]">
  {avatar}{name}{action}
</div>
{/* second card identical, top-[322px] */}
4<InitialsTile/>
"AT" monogram for a contact with no photo — 38×38px circle (radius 50%), warm cream #FDF0D9 fill, initials in Amber 500 #F59E0B, system sans 13px weight 800, grid-centered, flex:none.
Shape code — TSX + Tailwind
<span className="grid size-[38px] flex-none place-items-center rounded-full bg-[#FDF0D9] text-[13px] font-extrabold text-amber-500">AT</span>
5<PhotoAvatar/>
Photo avatar for a contact already on the app — 38×38px circle (radius 50%), photo cover-fill (p-mike portrait), flex:none, no ring.
Shape code — TSX + Tailwind
<span className="block size-[38px] flex-none rounded-full bg-cover bg-center"
  style={{ backgroundImage: `url(${photoMike})` }} />
6<ContactName/> ×2
Contact name ("Amin Tabrizian" / "Nathaniel G.") — system sans 13.5px weight 700, Ink 900 #17161C, left-aligned; flex:1 min-width:0 so it truncates before the action pill.
Shape code — TSX + Tailwind
<span className="min-w-0 flex-1 truncate text-[13.5px] font-bold text-ink-900">Amin Tabrizian</span>
7<AddPill/>
"add" action pill (not-yet-added, card 1) — 32px tall, padding 0 13px, radius 16px full pill, flat Azure 500 #3D8BFF, lowercase label 12.5px weight 800 Cloud #FFFFFF with a 14×14px add-user icon stroked white at 2.2px, 6px gap. Right-anchored in the card row.
Shape code — TSX + Tailwind
<button className="inline-flex h-8 flex-none items-center gap-1.5 rounded-full bg-azure-500 px-[13px] text-[12.5px] font-extrabold lowercase text-white">
  <UserPlus className="size-3.5" strokeWidth={2.2} />add
</button>
8<AddedPill/>
"added ✓" confirmed state (card 2) — same 32px pill geometry, inverted quiet styling: fill Azure 100 #E8F1FF, text Azure 600 #1D6FF2, 12.5px weight 800, no icon. The add→added swap is the tap feedback.
Shape code — TSX + Tailwind
<span className="inline-flex h-8 flex-none items-center rounded-full bg-azure-100 px-[13px] text-[12.5px] font-extrabold text-azure-600">added ✓</span>
9<PrimaryCTA/>
"next →" — 220×50px centered capsule, radius 999px, flat Azure 500 #3D8BFF (no glow), anchored bottom 66px. Label 15.5px weight 800 Cloud #FFFFFF, lowercase.
Shape code — TSX + Tailwind
<button className="absolute bottom-[66px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white">next →</button>
10<OrgScreenTitle/>
"add your team" — the organization-mode variant of this page (second frame): same slot and type as the personal title (22px weight 800, letter-spacing −0.3px, line-height 1.3, Ink 900 #17161C, y152). Same page, same layout — only the copy adapts, which is the whole org strategy: subtle, no forked flow.
Shape code — TSX + Tailwind
<h1 className="absolute top-[152px] inset-x-6 text-center text-[22px] font-extrabold leading-[1.3] tracking-[-0.3px] text-ink-900">
  {accountMode === 'org' ? 'add your team' : <>add friends if you<br />know them</>}
</h1>
11<TeamCard/> ×2
Org mode reuses the exact SuggestionCard shell (cream #FBF6EC, 1px #F6EFE0, radius 16px, 11px/14px padding, y246 / y322) — but lists people in the organization (staff, door, co-hosts) pulled from contacts, not friends. Inviting grants them co-hosting under the org page.
Shape code — TSX + Tailwind
<div className="absolute inset-x-5 top-[246px] flex items-center gap-[13px] rounded-2xl border border-[#F6EFE0] bg-[#FBF6EC] px-3.5 py-[11px]">
  {avatar}{name}{inviteAction}
</div>
12<TeamInitialsTile/> ×2
Teammate monograms ("KD" / "MR") — the same 38×38px circle treatment as the personal frame: #FDF0D9 fill, initials Amber 500 #F59E0B, 13px weight 800.
Shape code — TSX + Tailwind
<span className="grid size-[38px] flex-none place-items-center rounded-full bg-[#FDF0D9] text-[13px] font-extrabold text-amber-500">KD</span>
13<InvitePill/>
"invite" — identical geometry to the personal "add" pill (32px tall, padding 0 13px, radius 16px, flat Azure 500 #3D8BFF, 12.5px weight 800 lowercase Cloud #FFFFFF, 14px add-user icon). Only the verb changes: it sends a team invite instead of a friend request.
Shape code — TSX + Tailwind
<button className="inline-flex h-8 flex-none items-center gap-1.5 rounded-full bg-azure-500 px-[13px] text-[12.5px] font-extrabold lowercase text-white">
  <UserPlus className="size-3.5" strokeWidth={2.2} />invite
</button>
14<InvitedPill/>
"invited ✓" — the quiet confirmed state, same as "added ✓": Azure 100 #E8F1FF fill, Azure 600 #1D6FF2 text, 32px pill, 12.5px weight 800.
Shape code — TSX + Tailwind
<span className="inline-flex h-8 flex-none items-center rounded-full bg-azure-100 px-[13px] text-[12.5px] font-extrabold text-azure-600">invited ✓</span>
S16

Onboarding 8 — Tutorial & complete

After · 8 Tutorial
9:41
🎬 tutorial animation
complete!
1<BackButton/>
36×36px circle at x14 y60, fill #F4F4F6, chevron-left 16×16px stroked Ink 900 #17161C at 2.2px.
Shape code — TSX + Tailwind
<button className="absolute left-3.5 top-[60px] grid size-9 place-items-center rounded-full bg-[#F4F4F6]">
  <ChevronLeft className="size-4 text-ink-900" strokeWidth={2.2} />
</button>
2<AnimationSlot/>
The tutorial-animation placeholder, dead-center of the screen: 353×260px at y296 in 20px insets, 2px DASHED border #C9C7CF, radius 20px, near-white fill rgba(250,250,251,.8). Inside, grid-centered with 10px gap: a 34px 🎬 emoji and the slot label "tutorial animation" in system sans 15px weight 700 Ink 500 #6C6B76. In the shipped app this box is replaced by the app-tour animation (Lottie/video) — the dashed border marks it as a content slot, not final UI.
Shape code — TSX + Tailwind
<div className="absolute top-[296px] inset-x-5 grid h-[260px] place-items-center rounded-[20px] border-2 border-dashed border-[#C9C7CF] bg-[#FAFAFB]/80">
  <Lottie animationData={appTour} loop autoplay />
</div>
3<CompleteCTA/>
"complete!" — the flow's finish line replaces "next →": same 220×50px centered capsule, radius 999px, flat Azure 500 #3D8BFF, anchored bottom 66px, label 15.5px weight 800 Cloud #FFFFFF, lowercase. Tapping it lands the user on the Home live map.
Shape code — TSX + Tailwind
<button className="absolute bottom-[66px] left-1/2 h-[50px] w-[220px] -translate-x-1/2 rounded-full bg-azure-500 text-[15.5px] font-extrabold lowercase text-white">complete!</button>
4<HomeIndicator/>
134×5px bar, radius 3px, centered at bottom 8px, Black #000000 at 40% opacity.
Shape code — TSX + Tailwind
<span className="absolute bottom-2 left-1/2 h-[5px] w-[134px] -translate-x-1/2 rounded-[3px] bg-black/40" />
Playground · not committed

Experiment — palette lab

Your five palettes, mixed for maximum contrast: royal blue actions, burnt-orange LIVE, gold for pending, forest green for positive signals, cream paper. Nothing here touches the real pages — toggle between the shipped palette and the experiment.

#0057C4 action #003B73 deep #D34001 live #D8A84F pending #2F6B4F positive #00ADB5 accent #FBF7EF paper
Original palette
San Francisco UNION SQUARE Geary Street Ellis Street Post Street Sutter Street Bush Street Pine Street California Street Sacramento Street Jackson Street CHINATOWN Powell Street Stockton Street Market Street 4th Street Mission Street
9:41
47 out now M S
2D
Warehouse Night · 0.1 mi
Tap any moment to peek
Happening now See all ›
LIVE 0.1 mi Warehouse Night Union Square · 32 there
LIVE 0.4 mi Rooftop Social SoMa · 18 there
Home
Chats2
Host
Explore
Profile
After · map first
LIVE · 0:24
12 min walk · 0.6 mi
9:41
LIVE Private Free entry
Warehouse Night
Started 8:15 PM · live for 1h 26m · usually till 1 AM
Underground house set in the old print warehouse. Side door on Minna St — say Souham sent you.
Request to Join
Souham Tekriwal @st123 · replies in minutes
3 friends going · 9 spots left
After · tapping the clip
9:41
LIVE Private Free entry
Warehouse Night
Started 8:15 PM · live for 1h 26m · usually till 1 AM
Underground house set in the old print warehouse. Side door on Minna St — say Souham sent you.
12 min
Request to Join
Souham Tekriwal @st123 · replies in minutes
3 friends going · 9 spots left