Skip to content

Hero section, end to end #22

Closed
Closed

Description

@Abuubkar

Parent

Spec: #20

What to build

The first vertical slice through every layer: a visitor loads the site and sees a finished Hero — badge, headline with two coloured emphases, lead paragraph, two calls to action, and the trust points — rendered from tokens, in the Brand font, with every string from Messages.

This ticket establishes the Base component vocabulary that later sections build on, and proves the whole stack end to end.

The hero headline is the only rich text on the site and the only <br>. It is encoded as segments rather than HTML in JSON:

type Segment = string | { text: string; tone: 'brand' | 'success' } | { br: true };

Base components take design decisions as variant props, never raw values. Primitives take a closed as union, never unrestricted. Heading takes level, so semantics and size stay independent.

Messages are accessed as a static typed import — no hook, no key strings, no codegen.

Acceptance criteria

  • Base components: Box, Text, Heading, Stack, Section, Prose, Link, Badge, Dot, VisuallyHidden, RichText
  • Box and Text take closed as unions; as="marquee" does not typecheck
  • Heading takes level (1–6) independently of a size prop
  • Link supports variant="button"; Button has no as prop
  • Every Base component accepts style?: StyleXStyles and spreads stylex.props() itself
  • The Hero renders and matches the canvas at desktop and mobile widths
  • The headline's two emphases render from the segment array, not from markup in JSON
  • Every Hero string comes from Messages; no literal strings in the section
  • Section accepts id and labelledBy and renders aria-labelledby pointing at its own heading
  • The prerendered HTML contains the full Hero text — verifiable with JavaScript disabled
  • Lint passes with jsx-no-literals active on the section

Blocked by

Activity

  1. changed the title [-]Design system and brand font[/-] [+]Hero section, end to end[/+] on Sep 5, 2026
  2. self-assigned this
    on Sep 5, 2026
  3. Abuubkar commented on Sep 5, 2026

    @Abuubkar
    OwnerAuthor

    Done on branch ticket/22-hero (88b30db). Twelve Base components and the Hero composed from them.

    Components

    Box · Text · Heading · Stack · Section · Prose · Link · Badge · Dot · List/ListItem · VisuallyHidden · RichText

    • Closed as unions. Box takes 9 element names, Text takes 3. as="marquee" does not typecheck.
    • Heading takes level and size independently, so an h3 can render large without lying about document structure.
    • Button has no as. Both hero CTAs are Link variant="button" and variant="buttonSecondary" — the canvas's 6 brand-background anchors against 2 real buttons made this the common case, not the exception.
    • Tokens reach components only as variant props. No component takes a raw value.
    • Section owns padding, container width, scroll-margin-top, and — when tone="inverse" — the Dark theme.

    Two things the ticket did not anticipate

    1. The hero emphasis needed a token that did not exist. #8 demoted teal600 to "large text and decoration only" but left no Role pointing at it, and the headline is exactly that case: clamp(34px, 5.4vw, 64px) at 4.18:1, which passes AA for large text and fails for normal text. Added textAccentDisplay with the constraint written into its comment — "Never use it below 24px." Using textLink (teal700) instead would have been safe but would have darkened the headline away from the canvas.

    2. font-family and the page background belong on <body>, not a component. Without them the hero rendered in the UA serif, and a browser in dark mode painted its own dark canvas below the hero — which looked like a theming bug and was not. Both now come from tokens applied to <body> in the root document, plus color-scheme: light in global CSS so no element lacking a background can pick up a dark UA canvas.

    Verified in a browser

    Result
    h1 Satoshi Variable, weight 800, rgb(15,27,45) = ink900, <br> intact
    Emphases "Earned" → rgb(14,138,138) teal600 · "Before They Happen" → rgb(46,158,91) green600
    aria-labelledby hero-h resolves to the h1 id
    main id="top", tabIndex="-1" — ready for the skip link in #23
    Trust points 5 items, list labelled
    CTAs resolve to #assessment and #services
    Layout at 1200px section 1200px, container 880px, no horizontal overflow
    vp check passes on 22 files

    With JavaScript disabled: the prerendered HTML contains the complete hero — badge, headline with both emphases and the <br>, lead, both CTAs, and all five trust points.

    Rich text

    The Segment encoding from #11 works as designed. One cast is needed and it lives in exactly one place: resolveJsonModule widens the segment array to a structural type, so messages.ts narrows it back with heroTitle as readonly Segment[]. No component takes a message key — RichText takes segments, everything else takes resolved strings as children.

    Notes for the tickets this unblocks

    • Header, navigation and mobile menu #23 (header): main already has id="top" and tabIndex="-1", so SkipLink only needs the link itself.
    • Problem, Services and How It Works #24 (Problem/Services/How): Card, Grid, Icon and IconTile are the remaining tier-2 components; List/ListItem already exist.
    • The List and Stack gap props share a SpaceStep union, so any new gap value needs adding in both — worth folding into one shared map if a third component needs it.

    Against the #18 budget

    CSS is now 9.72 kB raw / 3.20 kB gzip, against a ~5 kB budgeted target. That is the whole component vocabulary plus the Hero, and StyleX atomic classes deduplicate as sections are added, so the growth curve should flatten. Worth watching at #24, which adds the most components of any remaining ticket.

  4. Abuubkar commented on Sep 5, 2026

    @Abuubkar
    OwnerAuthor

    Done on branch ticket/22-hero. Twelve Base components plus the Hero, verified in a browser and with JS disabled.

  5. added a commit that references this issue on Sep 5, 2026
    88b30db
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

ready-for-agentSpec is complete and ready for an agent to implement

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions