← All reports

FUN-397 · conformance proof sheet · 2026-07-27

Crew join page, rebuilt against the _v2 canonical

Each row puts the rebuilt page beside the canonical mockup at the same viewport, in the same state. The mockup's own dev-toolbar drives its four states; the built page is driven through the real UI. Every geometric and typographic property was also read with getComputedStyle on both sides — the ledger at the bottom lists what that caught.

1 page type4 stateslight only · v2 ships no dark palette 52 properties compared4 deviations found + fixed

4 / 4 states match

Crew Link join page

funlet-crew-join_v2.html

Pre-submit · default

crew-join-presubmit/c/[token] · form

Bare crew name in the hero — no "Crew" suffix (2026-07-22 ruling). No member count, no member names, no organizer attribution line. The crew name is server-injected, so it is present on first paint with no loading card. First name is autofocused on both sides. The header deliberately differs from the canonical — see the note under the ledger.

BuiltPre-submit — built page
MockupPre-submit — canonical mockup

Validation · both fields empty

crew-join-errorsvalidate-on-tap

The button is gated on the consent checkbox alone, so tapping Join with empty fields surfaces both locked strings — First name required and Phone number required — and moves focus to the first bad field. The previous build disabled the button until the fields were valid, which meant these strings could never appear.

BuiltEmpty-field errors — built page
MockupEmpty-field errors — canonical mockup

Validation · phone under 10 digits

crew-join-badphoneformat error

A non-empty phone that is not 10 digits reports Enter a 10-digit phone number, not the required-field string. The first-name error has already cleared because that field was fixed — errors clear per field, and focus lands on the phone.

BuiltBad phone — built page
MockupBad phone — canonical mockup

Success

crew-join-successjoined

Hero and form are both replaced by the success card; the header and its crew name stay. Bare name again — You joined Pickleball! — with Organizer will be in touch and no trailing period. Left-aligned, no check icon: the canonical has neither. Captured with a stubbed POST so no contact row was written to the staging crew.

BuiltSuccess — built page
MockupSuccess — canonical mockup

What the computed-style pass caught

not visible in a screenshot diff
PropertyWasNowWhy it slipped
input + button radius8px6px rounded-md is not 6px in this repo — the shadcn preset redefines the scale (--radius: 0.625rem) — and rounded-fn-input is 14px, repurposed for the attendee compose input. Pre-dated this ticket.
errored field border, while focused#e8731a#7f2c28 In the canonical .field.has-error .field-input (0,3,0) outranks .field-input:focus (0,2,0), so an errored field stays red — which is the state right after tapping Join. Tailwind emits focus: variants after base utilities, inverting it.
optional labelLast name(optional)Last name (optional) The canonical has a literal space as well as the 4px margin; the build had only the margin.
consent apostropheFunlet’sFunlet's The canonical uses a straight 0x27 and contains no curly apostrophes anywhere. This is a carrier-registration string, so it now matches byte for byte.

Header — deliberate departure from the canonical. The canonical specifies a tile holding a three-bar glyph, with the crew name as the page title. That glyph is a placeholder, not the funlet mark, and following it leaves the page with no brand anywhere — a stranger arriving from an SMS link sees an unfamiliar shape and a crew name. The header therefore matches the sibling public page /event/[code]: the real logo, 52px, hairline rule. Nothing is lost — the hero states the crew name, which is where the canonical puts it too. The same placeholder currently ships on /i; correcting the mockups upstream would stop it propagating into every page rebuilt from them.

Accepted divergences: secondary text stays #5a6e72 rather than the canonical's #6b8184 — Lock 13 darkened it for WCAG AA and the token is marked do-not-override, and it is the only colour delta in the ledger. Title and OG stay static and neutral per the /i and /event lock that keeps per-token data away from link-preview fetchers. The revoked / not-found / error cards are kept, since the canonical puts invalid-token handling out of its own scope and revoke shipped after it was frozen.