/* ═══════════════════════════════════════════════════════════════════════════
   Emploi — landing page stylesheet.

   Scoped to index.html only. The legal documents keep style.css untouched;
   terms.html, privacy.html and deletion.html are byte-pinned and must not
   change appearance as a side effect of anything here.

   The legal CSP is `style-src 'self'` with no unsafe-inline, so there is no
   <style> block and no style="" attribute anywhere on this page. Every value
   the scripts animate is written through CSSOM property assignment
   (el.style.setProperty), which CSP does not govern.

   One system, applied everywhere: eight type steps, one spacing scale, four
   radii, three buttons, one entrance. A section that wants to look different
   does it with layout and ground colour, not with a new set of primitives.

   Font: Archivo (Omnibus Type, SIL Open Font License 1.1), variable across
   weight 100–900 and width 62–125%, self-hosted because `font-src 'self'`.
   ═══════════════════════════════════════════════════════════════════════════ */

/* One file, subset to the glyphs this page actually sets, with the weight and
   width axes narrowed to the ranges the design uses (400-900 / 90-115%).
   Google's stock pair cost 172 kB across two requests and still had no ₹ in
   the latin file — the rupee sign lives in latin-ext, so every price on the
   page was pulling down an 84 kB second font for one glyph. This is 49 kB,
   one request, ₹ included.

   Regenerate with fontTools after any copy change that introduces a new
   character — the command is in docs/assets.md. A character outside the subset
   silently falls back to a system font. */
@font-face {
    font-family: 'Archivo';
    font-style: normal;
    font-weight: 400 900;
    font-stretch: 90% 115%;
    font-display: swap;
    src: url('/assets/fonts/archivo-emploi-var.woff2') format('woff2');
}

/* ── Tokens ──────────────────────────────────────────────────────────────── */

:root {
    /* Duotone lifted from the app icon: signal orange over cyan, on a warm
       near-black. Orange is the brand and the primary action. Cyan is not a
       second brand colour — it appears only where the product itself is cool:
       verification, distance, and the employer lane. */
    /* #F95B04 — the app's actual welcome-screen orange (app/index.tsx's
       LinearGradient background and splash/icon backgroundColor), not the
       lighter #FF6B35 used for in-app buttons/tabs. --signal-hot is the
       app's own gradient partner (#F7931E) rather than a derived tint. */
    --signal: #F95B04;
    --signal-hot: #F7931E;
    --cyan: #2AD9EC;

    --ink: #0E0906;
    --ink-2: #17100B;
    --ink-3: #241811;
    --ink-4: #3A281D;

    /* The trust band is the one cool room in the building — a blue-black
       rather than the warm black everywhere else. Tokenised because three
       values (ground, edge, rule) have to move together or the room stops
       reading as one. */
    --cool: #0A1116;
    --cool-edge: #14222B;
    --cool-rule: rgba(226, 240, 245, 0.13);

    --bone: #F5EFEA;
    --bone-dim: #C0B3A8;     /* 9.6:1 on --ink */

    --on-signal: #180C05;    /* 5.97:1 on --signal */
    --on-signal-dim: #3A2216; /* 4.59:1 on --signal — tight, close to the 4.5:1 AA floor */
    --bone-faint: #8A7A6E;   /* 4.8:1 on --ink — the dimmest text the page allows */

    --rule: rgba(245, 239, 234, 0.14);
    --rule-strong: rgba(245, 239, 234, 0.28);

    /* Type. Eight steps, and nothing on the page sets a size outside them.
       Hero max is 5.25rem (84px); the next step down is 3.5rem, so no second
       heading anywhere can compete with it.

       Two hero coefficients, because the headline's available width changes
       shape at 1000px: below that it owns the full column and can be generous;
       above it shares the row with the deck, and 9vw would push "Swipe right"
       onto a second row inside its own clip box. */
    --t-hero: clamp(2.75rem, 9vw, 5.25rem);
    --t-h2: clamp(2rem, 5.2vw, 3.5rem);
    --t-h3: clamp(1.25rem, 2.1vw, 1.5rem);
    --t-h4: 1.0625rem;
    --t-lede: clamp(1.0625rem, 1.7vw, 1.3125rem);
    --t-body: clamp(1rem, 1.05vw, 1.0625rem);
    --t-small: 0.9375rem;
    --t-micro: 0.8125rem;
    --t-nano: 0.75rem;       /* card meta only — the app's own smallest label */

    /* Spacing. Every margin and gap on the page is one of these. */
    --s-1: 0.25rem;
    --s-2: 0.5rem;
    --s-3: 0.75rem;
    --s-4: 1rem;
    --s-5: 1.5rem;
    --s-6: 2rem;
    --s-7: 3rem;
    --s-8: 4rem;

    /* Radii. Four values: control, chip, card, pill. */
    --r-xs: 4px;
    --r-sm: 6px;
    --r-lg: 16px;
    --r-pill: 999px;

    --shell: 1240px;
    --gutter: clamp(1.25rem, 4vw, 3rem);
    /* Vertical padding on every full section, so the gap between two sections
       is twice this. At 8rem that gap was 257px on a 900px-tall viewport —
       29% of a screen, and about a third of the page's total scroll height was
       empty space between bands.

       Nearly every boundary also changes ground colour (ink → ink-2 → ink →
       cool → ink → orange), and the colour change is already doing the
       separating; the padding was paying for it a second time. 6.25rem puts
       the gap at 200px, which still reads as deliberate air rather than a
       list. */
    --band: clamp(3.75rem, 7vw, 6.25rem);

    /* Exponential ease-out. No bounce, no elastic. */
    --e-out: cubic-bezier(0.16, 1, 0.3, 1);
    --e-soft: cubic-bezier(0.33, 1, 0.68, 1);

    /* Semantic stacking order. */
    --z-bg: 0;
    --z-content: 1;
    --z-deck-card: 2;
    --z-deck-ui: 3;
    --z-header: 40;

    color-scheme: dark;
}

/* Registered so they are typed and, crucially, non-inheriting. An unregistered
   custom property inherits, so every write during a drag invalidated the style
   of all ~20 nodes inside the card. These stop at the element they are set on. */
@property --x { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --y { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --z { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --rot { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* ── Base ────────────────────────────────────────────────────────────────── */

html {
    /* Deliberately no font-size. A px root pins every rem on the page and
       silently discards the reader's own default-size preference; the browser
       default is 16px, so omitting it costs nothing and honours the setting. */
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    background: var(--ink);
}

body {
    margin: 0;
    background: var(--ink);
    color: var(--bone);
    font-family: 'Archivo', 'Segoe UI', system-ui, -apple-system, sans-serif;
    font-size: var(--t-body);
    line-height: 1.6;
    font-synthesis-weight: none;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

h1, h2, h3, h4 {
    margin: 0;
    font-weight: 800;
    font-stretch: 108%;
    letter-spacing: -0.028em;
    line-height: 1.05;
    text-wrap: balance;
}

p {
    margin: 0;
    text-wrap: pretty;
}

a {
    color: inherit;
}

ul, ol {
    margin: 0;
    padding: 0;
    list-style: none;
}

img {
    max-width: 100%;
    display: block;
}

::selection {
    background: var(--signal);
    color: var(--on-signal);
}

:focus-visible {
    outline: 3px solid var(--cyan);
    outline-offset: 3px;
    border-radius: var(--r-xs);
}

/* Cyan is a fine focus ring on ink — 11.5:1 — but the two drenched folds are
   the one place it stops working. outline-offset draws the ring in the gap
   outside the control, so on the hero and the beta band the colour it is
   actually judged against is --signal, and cyan on persimmon is 1.87:1 where
   WCAG 1.4.11 wants 3:1.

   Ink gives 6.15:1 on the same ground. The controls that are themselves ink
   keep a 3px band of persimmon between them and the ring, so it still reads as
   a ring rather than merging into the button. */
.hero :focus-visible,
.beta :focus-visible {
    outline-color: var(--ink);
}

/* The masthead is sticky, so an anchor jump would otherwise land its target
   under the bar. Every section the nav can reach reserves the bar's height
   plus a little air above the heading. */
main > section[id] {
    scroll-margin-top: calc(68px + var(--s-4));
}

.shell {
    width: 100%;
    max-width: var(--shell);
    margin-inline: auto;
    padding-inline: var(--gutter);
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    background: var(--ink);
    color: var(--bone);
    padding: var(--s-3) var(--s-5);
    font-weight: 700;
}

.skip-link:focus {
    left: var(--s-3);
    top: var(--s-3);
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ── Buttons ─────────────────────────────────────────────────────────────
   Three, and only three. --primary is signal on a dark ground; --invert is
   ink on a signal ground (the hero, the deck, the beta band); --ghost is the
   outlined secondary that only ever appears next to one of the other two.
   All share the same height, radius, weight and transition, so a control is
   recognisable as a control wherever it turns up. */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    min-height: 48px;
    padding: var(--s-3) var(--s-5);
    border: 2px solid transparent;
    border-radius: var(--r-pill);
    font-family: inherit;
    font-size: var(--t-small);
    font-weight: 800;
    letter-spacing: 0.005em;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.2s var(--e-soft), color 0.2s var(--e-soft),
        border-color 0.2s var(--e-soft);
}

/* 44px is the floor for a touch target; the small variant is the smallest
   this page goes, and it still clears it. */
.btn-sm {
    min-height: 44px;
    padding: var(--s-2) var(--s-4);
    font-size: var(--t-micro);
}

.btn-primary {
    background: var(--signal);
    color: var(--on-signal);
    border-color: var(--signal);
}

.btn-primary:hover {
    background: var(--signal-hot);
    border-color: var(--signal-hot);
}

.btn-invert {
    background: var(--ink);
    color: var(--bone);
    border-color: var(--ink);
}

.btn-invert:hover {
    background: #000;
    border-color: #000;
}

.btn-ghost {
    background: transparent;
    color: var(--on-signal);
    border-color: var(--on-signal);
}

.btn-ghost:hover {
    background: var(--on-signal);
    color: var(--signal-hot);
}

/* The one motion a button makes: the arrow moves in the direction the button
   sends you. Nothing lifts, nothing grows. */
.btn svg {
    width: 15px;
    height: 15px;
    flex: none;
    transition: transform 0.25s var(--e-out);
}

.btn:hover svg {
    transform: translateX(3px);
}

/* ── Masthead ────────────────────────────────────────────────────────────── */

/* Ink, not signal orange. The mark is the app icon — its upper half is the
   same orange as the brand field, so on an orange bar the top of the E
   disappeared and only the cyan half read. On ink the artwork sits the way it
   was drawn, and the bar gains a real edge against the orange hero it floats
   over. */
.masthead {
    position: sticky;
    top: 0;
    z-index: var(--z-header);
    background: var(--ink);
    color: var(--bone);
    border-bottom: 1px solid var(--ink-3);
    transition: border-color 0.3s var(--e-soft);
}

.masthead.is-stuck {
    border-bottom-color: var(--ink-4);
}

.masthead .shell {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    min-height: 68px;
    padding-block: var(--s-3);
}

.wordmark {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    text-decoration: none;
}

/* ── The mark ────────────────────────────────────────────────────────────
   The drawn wordmark, as SVG. It carries its own two colours — bone letters,
   a yellow swoosh — so it is never recoloured here, only sized. The intrinsic
   width/height on the tags are the viewBox, present to reserve the right
   aspect before the file lands; the widths below are what actually applies. */

.mark {
    display: block;
    height: auto;
}

/* Sized against the 68px masthead rather than to match the old icon-plus-text
   lockup, which was half again as wide. */
.wordmark .mark {
    width: clamp(76px, 8vw, 92px);
    flex: none;
}

.masthead nav {
    display: flex;
    align-items: center;
    gap: clamp(var(--s-3), 2.2vw, var(--s-5));
}

.masthead nav a:not(.btn) {
    text-decoration: none;
    font-size: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0.01em;
    color: var(--bone-dim);
    padding-block: var(--s-1);
    border-bottom: 2px solid transparent;
    transition: color 0.2s var(--e-soft), border-color 0.2s var(--e-soft);
}

.masthead nav a:not(.btn):hover {
    color: var(--bone);
    border-bottom-color: var(--signal);
}

/* ── Hero ────────────────────────────────────────────────────────────────── */

/* The hero and the beta band are the same room at either end of the page:
   the same ground, the same single light source, the same button. Nothing
   animates behind either of them — the headline and the deck are the only
   two things here asking for attention. */
.hero,
.beta {
    position: relative;
    isolation: isolate;
    background: var(--signal);
    color: var(--on-signal);
    overflow: hidden;
}

.hero::before,
.beta::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: var(--z-bg);
    background: radial-gradient(120% 100% at 14% 0%, var(--signal-hot) 0%, transparent 56%);
    opacity: 0.38;
    pointer-events: none;
}

.hero {
    padding-block: clamp(var(--s-7), 6vw, 5.5rem) clamp(3.5rem, 7vw, 6rem);
}

.hero .shell {
    position: relative;
    z-index: var(--z-content);
    display: grid;
    /* The headline column gets the surplus: the deck is a fixed 340px and only
       needs breathing room, while "Swipe right" is the thing that must never
       break across two rows inside one clip box. */
    grid-template-columns: minmax(0, 1.12fr) minmax(0, 0.88fr);
    gap: clamp(var(--s-6), 5vw, 4.5rem);
    align-items: center;
}

.hero h1 {
    font-size: var(--t-hero);
    font-weight: 900;
    font-stretch: 112%;
    letter-spacing: -0.035em;
    line-height: 0.93;
    /* The breaks here are authored, one .line per line. Balancing on top of
       that makes Chrome split "Swipe right" across two rows inside a single
       clip box even when it fits — so balance is explicitly off. */
    text-wrap: normal;
}

.hero h1 .line {
    display: block;
    overflow: hidden;
    padding-block: 0.09em;
    margin-block: -0.09em;
}

.hero h1 .line span {
    display: block;
}

/* Off-white on signal orange is only 2.82:1 — under the 3:1 large-text floor —
   so the emphasised word is knocked out of an ink plate instead. Orange on
   ink is 8.63:1, and the plate reads as painted signage, which is the point. */
.hero h1 .knock {
    display: inline-block;
    font-style: normal;
    background: var(--ink);
    color: var(--signal-hot);
    padding: 0 0.11em 0.05em;
    border-radius: var(--r-sm);
}

.hero-lede {
    margin-top: clamp(var(--s-5), 2.4vw, 1.75rem);
    font-size: var(--t-lede);
    line-height: 1.5;
    color: var(--on-signal);
    max-width: 34ch;
    font-weight: 500;
}

.hero-actions {
    margin-top: clamp(var(--s-6), 3vw, 2.25rem);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-3) var(--s-5);
}

.hero-note {
    font-size: var(--t-micro);
    font-weight: 700;
    color: var(--on-signal-dim);
}

/* ── The deck: the product's own interaction ─────────────────────────────── */

.deck-stage {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s-5);
    /* Enough depth for the stack behind the top card to read as a stack. The
       deck no longer tilts toward the pointer: the only thing that should move
       a card is a hand on it. */
    perspective: 1400px;
    perspective-origin: 50% 45%;
}

.deck {
    position: relative;
    width: min(21.25rem, 82vw);
    height: min(27rem, 104vw);
    /* The cards behind the top one are absolutely positioned and pushed down
       in 3D, so they project about 28px past this box without contributing to
       its height. Without the reservation the stack's bottom edge lands in the
       flex gap and collides with the Skip / Save buttons. */
    margin-bottom: 1.75rem;
    transform-style: preserve-3d;
    touch-action: pan-y;
}

.deck-card {
    position: absolute;
    inset: 0;
    z-index: var(--z-deck-card);
    background: var(--ink);
    color: var(--bone);
    border: 1px solid var(--ink-4);
    border-radius: var(--r-lg);
    padding: var(--s-5);
    display: flex;
    flex-direction: column;
    transform-style: preserve-3d;
    box-shadow: 0 32px 60px -30px rgba(14, 9, 6, 0.95);
    transform:
        translate3d(var(--x, 0px), var(--y, 0px), var(--z, 0px))
        rotate(var(--rot, 0deg));
    user-select: none;
    -webkit-user-select: none;
}

/* Promoted only while a card is actually moving. It used to sit on all three
   cards for the life of the page, which is three permanent compositor layers
   at 340x432 on a device class this page is explicitly built for. `is-dragging`
   is set on pointerdown before the first transform is written, so the layer
   still exists ahead of any movement. */
.deck-card.is-dragging,
.deck-card.is-settling {
    will-change: transform;
}

.deck-card.is-settling {
    transition: transform 0.55s var(--e-out), opacity 0.35s linear;
}

.deck-card.is-top {
    cursor: grab;
}

.deck-card.is-dragging {
    cursor: grabbing;
}

.deck-card.is-gone {
    opacity: 0;
}

.card-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s-3);
    padding-bottom: var(--s-3);
    border-bottom: 1px solid var(--ink-3);
}

.card-cat {
    font-size: var(--t-micro);
    font-weight: 800;
    letter-spacing: 0.02em;
    color: var(--signal-hot);
}

.card-dist {
    font-size: var(--t-micro);
    font-weight: 700;
    color: var(--cyan);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.card-title {
    margin-top: var(--s-4);
    font-size: var(--t-h3);
    font-weight: 800;
    font-stretch: 105%;
    line-height: 1.1;
    letter-spacing: -0.03em;
    color: var(--bone);
}

.card-where {
    margin-top: var(--s-1);
    font-size: var(--t-small);
    color: var(--bone-dim);
}

.card-desc {
    margin-top: var(--s-3);
    font-size: var(--t-micro);
    line-height: 1.5;
    color: var(--bone-dim);
}

.card-by {
    margin-top: var(--s-4);
    padding-top: var(--s-3);
    border-top: 1px solid var(--ink-3);
    display: flex;
    align-items: center;
    gap: var(--s-2);
    font-size: var(--t-nano);
    font-weight: 700;
    color: var(--bone-dim);
}

.card-by .by-name {
    color: var(--bone);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.card-by .by-rating {
    display: inline-flex;
    align-items: center;
    gap: 0.22rem;
    color: var(--signal-hot);
    font-variant-numeric: tabular-nums;
    flex: none;
}

.card-by .by-rating svg {
    width: 11px;
    height: 11px;
    display: block;
}

.card-by .by-posted {
    margin-left: auto;
    flex: none;
    color: var(--bone-faint);
}

.card-pay {
    margin-top: auto;
    padding-top: var(--s-4);
    display: flex;
    align-items: baseline;
    gap: var(--s-1);
}

.card-pay .amount {
    font-size: 2rem;
    font-weight: 900;
    font-stretch: 100%;
    letter-spacing: -0.04em;
    line-height: 1;
    color: var(--bone);
    font-variant-numeric: tabular-nums;
}

.card-pay .unit {
    font-size: var(--t-small);
    font-weight: 700;
    color: var(--bone-dim);
}

.card-tags {
    margin-top: var(--s-4);
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-1);
}

.card-tags li {
    font-size: var(--t-nano);
    font-weight: 700;
    letter-spacing: 0.02em;
    padding: 0.3rem var(--s-2);
    border-radius: var(--r-sm);
    background: var(--ink-2);
    border: 1px solid var(--ink-3);
    color: var(--bone-dim);
}

.card-tags li.match {
    background: rgba(42, 217, 236, 0.12);
    border-color: rgba(42, 217, 236, 0.4);
    color: var(--cyan);
}

/* Verdict stamps — the app's own like_badge / skip_badge, rotated into the
   card plane so they read as printed on the card, not floated above it. */
.stamp {
    position: absolute;
    top: var(--s-5);
    /* Opaque, because a stamp at the top of the card lands on the category and
       distance row. A transparent one leaves two texts fighting mid-swipe. */
    background: var(--ink);
    font-size: 1rem;
    font-weight: 900;
    font-stretch: 112%;
    letter-spacing: 0.06em;
    padding: var(--s-1) var(--s-3);
    border: 3px solid currentColor;
    border-radius: var(--r-sm);
    opacity: 0;
    pointer-events: none;
    transform: translateZ(1px);
}

/* Opacity is written straight onto these two nodes by the drag handler rather
   than inherited from a custom property on the card — see the @property note. */
.stamp-save {
    left: var(--s-5);
    color: var(--cyan);
    rotate: -12deg;
}

.stamp-skip {
    right: var(--s-5);
    color: var(--signal-hot);
    rotate: 12deg;
}

.deck-controls {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    z-index: var(--z-deck-ui);
}

.deck-hint {
    font-size: var(--t-micro);
    font-weight: 700;
    color: var(--on-signal-dim);
    text-align: center;
}

/* Pointer copy is the default so anything that cannot answer the query — an
   old browser, a crawler, print — gets the keyboard route rather than losing
   it. Touch swaps it out. */
.deck-hint .hint-touch {
    display: none;
}

@media (hover: none) and (pointer: coarse) {
    .deck-hint .hint-touch {
        display: inline;
    }

    .deck-hint .hint-pointer {
        display: none;
    }
}

.deck-hint .k {
    display: inline-block;
    padding: 0.05rem var(--s-1);
    border: 1.5px solid currentColor;
    border-radius: var(--r-xs);
    font-size: var(--t-nano);
    margin-inline: 0.1rem;
}

/* ── Trades ──────────────────────────────────────────────────────────────
   The app's real category list, set as an index. It used to be two marquees
   running against each other, which moved permanently and could not be read;
   a list answers "is my trade on here?" in one glance and costs no frames. */

.trades {
    background: var(--ink);
    border-bottom: 1px solid var(--ink-3);
    padding-block: clamp(var(--s-6), 4vw, var(--s-7));
}

/* A caption, deliberately not an eyebrow: no tracking, no accent colour, no
   micro size. The page carries exactly two tinted section labels (the two
   audience lanes below) and this is not one of them. */
.trades-label {
    font-size: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0;
    color: var(--bone-dim);
}

/* The trades are the content here and the label is scaffolding, so the list
   is set in bone and the label sits under it in weight. They were the same
   size and the same family of grey, which left the band with no hierarchy at
   all — just 25 lines of undifferentiated text. */
.trades-list {
    margin-top: var(--s-4);
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--s-2) var(--s-6);
    font-size: var(--t-small);
    color: var(--bone);
}

/* A hairline lozenge in the brand orange, the same mark the proximity diagram
   plots its jobs with. It is the one piece of ornament on the page and it is
   doing a job: it makes 24 wrapped names scan as a list rather than as prose
   that happened to break. */
.trades-list li {
    display: flex;
    align-items: baseline;
    gap: var(--s-2);
}

.trades-list li::before {
    content: "";
    width: 4px;
    height: 4px;
    flex: none;
    background: var(--signal);
    rotate: 45deg;
    translate: 0 -2px;
}

/* ── Generic band ────────────────────────────────────────────────────────── */

.band {
    padding-block: var(--band);
    position: relative;
}

/* No content-visibility here, deliberately. It looks like the obvious win for
   a long page, but the reveal below is driven by IntersectionObserver, and a
   skipped subtree reports its intersection late enough to be visible as a
   flash. The page is small enough that skipping the layout was never worth
   much anyway. */

/* No measure on the block. It used to carry max-width: 48ch, but ch resolves
   against the element's own font-size — body — while the heading inside is set
   at 56px. That capped every section heading at 440px and wrapped them to
   four and five lines, where the one h2 outside this container had 595px and
   sat on two. Heading and lede each get their own measure, at their own size. */
.band-head h2 {
    max-width: 20ch;
}

.band h2 {
    font-size: var(--t-h2);
    font-weight: 900;
    font-stretch: 108%;
}

.band-lede {
    margin-top: var(--s-4);
    font-size: var(--t-lede);
    line-height: 1.5;
    color: var(--bone-dim);
    max-width: 56ch;
}

/* The page's one repeated label, and it is a system rather than scaffolding:
   it appears exactly twice, on the two sections that address the two sides of
   the market, and it names the audience. Every other section is introduced by
   its own heading. */
.tagline {
    font-size: var(--t-micro);
    font-weight: 800;
    letter-spacing: 0.02em;
    color: var(--signal);
    margin-bottom: var(--s-3);
}

/* ── Flow: how a shift gets filled ───────────────────────────────────────── */

.flow {
    background: var(--ink);
}

.flow-steps {
    margin-top: clamp(var(--s-6), 5vw, var(--s-8));
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(var(--s-5), 3vw, 2.75rem);
    counter-reset: beat;
}

/* Each beat is ruled off at the top and numbered under the rule. The numbers
   used to be three filled circles in three different colours, which made a row
   of feature cards out of what is really one sequence. */
.flow-steps li {
    counter-increment: beat;
    border-top: 1px solid var(--rule);
    padding-top: var(--s-4);
}

.flow-steps li::before {
    content: counter(beat, decimal-leading-zero);
    display: block;
    margin-bottom: var(--s-3);
    font-size: var(--t-micro);
    font-weight: 800;
    letter-spacing: 0.06em;
    font-variant-numeric: tabular-nums;
    color: var(--signal);
}

.flow-steps h3 {
    font-size: var(--t-h3);
    font-weight: 800;
}

.flow-steps p {
    margin-top: var(--s-3);
    color: var(--bone-dim);
    font-size: var(--t-small);
    line-height: 1.6;
    max-width: 34ch;
}

/* ── Proximity ───────────────────────────────────────────────────────────── */

.near {
    background: var(--ink-2);
    border-block: 1px solid var(--ink-3);
    overflow: hidden;
}

.near .shell {
    display: grid;
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    gap: clamp(var(--s-6), 5vw, 4.5rem);
    align-items: center;
}

.radar-wrap {
    position: relative;
    aspect-ratio: 1;
    width: 100%;
    max-width: 480px;
    margin-inline: auto;
}

.radar {
    width: 100%;
    height: 100%;
    display: block;
}

.near-stats {
    margin-top: var(--s-6);
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2) var(--s-6);
    font-size: var(--t-micro);
    font-weight: 700;
    color: var(--bone-dim);
}

.near-stats b {
    color: var(--cyan);
    font-variant-numeric: tabular-nums;
}

/* ── Hiring ledger (employer side) ───────────────────────────────────────── */

.hiring {
    background: var(--ink);
}

.ledger {
    margin-top: clamp(var(--s-6), 5vw, 3.5rem);
    border-top: 1px solid var(--rule-strong);
}

.ledger-row {
    display: grid;
    grid-template-columns: 4.25rem minmax(0, 0.42fr) minmax(0, 0.58fr);
    gap: clamp(var(--s-3), 2.4vw, var(--s-6));
    align-items: baseline;
    padding-block: clamp(var(--s-5), 3vw, 2.25rem);
    border-bottom: 1px solid var(--rule);
}

/* The clock is the argument, so it is the only coloured thing in the row.
   Cyan on ink is 11.5:1, and tabular figures keep the column straight — four
   times of differing width would otherwise wander. */
.ledger-time {
    color: var(--cyan);
    font-size: var(--t-micro);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.01em;
}

.ledger-row h3 {
    font-size: var(--t-h3);
    font-weight: 800;
}

.ledger-body {
    color: var(--bone-dim);
    font-size: var(--t-small);
    line-height: 1.6;
    max-width: 58ch;
}

.ledger-note {
    margin-top: var(--s-4);
    font-size: var(--t-micro);
    color: var(--bone-dim);
}

/* ── Trust ───────────────────────────────────────────────────────────────── */

/* The one cool room in the building. Verification is the product's cyan
   lane, and a blue-black ground lets that read as intent rather than as an
   accent colour dropped onto the same warm black as everything else. */
.trust {
    background: var(--cool);
    border-block: 1px solid var(--cool-edge);
}

/* The heading column carries two sentences at 56px. At 0.85fr it had ~460px
   and broke them across five lines, orphaning single words; the spec list on
   the right loses little by giving that width back. */
.trust .shell {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: clamp(var(--s-6), 5vw, 4.5rem);
}

.spec {
    display: grid;
    gap: 0;
}

.spec li {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr);
    gap: var(--s-4);
    padding-block: var(--s-4);
    border-bottom: 1px solid var(--cool-rule);
    align-items: start;
}

.spec li:first-child {
    border-top: 1px solid var(--cool-rule);
}

.spec .tick {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 2px solid var(--cyan);
    color: var(--cyan);
    display: grid;
    place-items: center;
    margin-top: 0.15rem;
}

.spec .tick svg {
    width: 13px;
    height: 13px;
    display: block;
}

.spec h3 {
    font-size: var(--t-h4);
    font-weight: 800;
    letter-spacing: -0.015em;
}

.spec p {
    margin-top: var(--s-1);
    color: var(--bone-dim);
    font-size: var(--t-small);
    line-height: 1.55;
}

.spec a {
    color: var(--cyan);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.spec a:hover {
    color: var(--bone);
}

/* ── Languages ───────────────────────────────────────────────────────────── */

.tongues {
    background: var(--ink);
}

.tongue-grid {
    margin-top: clamp(var(--s-6), 5vw, 3.5rem);
    display: grid;
    /* Eight languages: fixed 2 or 4 columns so the grid always ends flush.
       auto-fit would settle on five and leave three dead cells. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1px;
    background: var(--rule);
    border: 1px solid var(--rule);
}

/* No hover state at all. These cells are labels, not controls — nothing
   happens when you click one. The old version flooded the cell full signal
   orange on hover, which made eight buttons out of eight labels; replacing it
   with a quieter tint kept the same lie in a smaller voice. An element that
   responds to the pointer is promising something. */
.tongue-grid li {
    background: var(--ink);
    padding: clamp(var(--s-5), 2.4vw, 1.75rem);
}

.tongue-native {
    font-size: var(--t-h3);
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: -0.02em;
    display: block;
}

.tongue-name {
    display: block;
    margin-top: var(--s-1);
    font-size: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--bone-dim);
}

/* ── Beta CTA ────────────────────────────────────────────────────────────── */

.beta .shell {
    position: relative;
    z-index: var(--z-content);
}

/* One step below the hero, deliberately: there is only one 84px headline on
   this page, and it is the first thing you read. */
.beta h2 {
    max-width: 15ch;
    font-size: var(--t-h2);
    font-weight: 900;
    font-stretch: 112%;
    letter-spacing: -0.035em;
    line-height: 0.98;
}

.beta p {
    margin-top: var(--s-4);
    font-size: var(--t-lede);
    line-height: 1.5;
    color: var(--on-signal);
    max-width: 54ch;
    font-weight: 500;
}

.beta-actions {
    margin-top: clamp(var(--s-6), 3vw, 2.25rem);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-4) var(--s-5);
}

/* Two class selectors so this beats `.beta p`, which sets the lede size. */
.beta .beta-note {
    margin-top: 0;
    font-size: var(--t-micro);
    font-weight: 700;
    color: var(--on-signal-dim);
    max-width: 28ch;
}

/* ── Footer ──────────────────────────────────────────────────────────────── */

.site-foot {
    background: var(--ink);
    padding-block: clamp(2.5rem, 5vw, var(--s-8));
    border-top: 1px solid var(--ink-3);
}

.site-foot .shell {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-5) 2.5rem;
    justify-content: space-between;
    align-items: flex-start;
}

/* Two classes, so it outranks `.site-foot .shell`'s space-between — the mark
   and the line belong together, not pinned to opposite margins. */
.site-foot .foot-sign {
    align-items: center;
    justify-content: flex-start;
    gap: clamp(var(--s-5), 3vw, 2.25rem);
    padding-bottom: clamp(var(--s-6), 4vw, var(--s-7));
    margin-bottom: clamp(var(--s-6), 4vw, var(--s-7));
    border-bottom: 1px solid var(--ink-3);
}

/* The lockup is 1.77:1, where the old mark was square, so the width runs
   correspondingly wider to keep the sign-off at the same visual weight. */
.mark-lg {
    width: clamp(148px, 19vw, 200px);
    flex: none;
}

.foot-tag {
    font-size: var(--t-h3);
    font-weight: 800;
    font-stretch: 105%;
    letter-spacing: -0.03em;
    line-height: 1.15;
    color: var(--bone);
}

.foot-links {
    display: flex;
    flex-wrap: wrap;
    /* Narrower gap because the links carry their own inline padding below;
       the visual rhythm is unchanged, the tap targets are not. */
    gap: var(--s-1) var(--s-2);
    margin-inline-start: calc(var(--s-2) * -1);
}

.foot-links a {
    /* The footer links are the only navigation left on a phone, so they get a
       44px target rather than the height of their own text. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    min-width: 44px;
    padding-inline: var(--s-2);
    font-size: var(--t-micro);
    font-weight: 700;
    color: var(--bone-dim);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: color 0.2s var(--e-soft), border-color 0.2s var(--e-soft);
}

.foot-links a:hover {
    color: var(--signal);
    border-bottom-color: var(--signal);
}

.foot-legal {
    font-size: var(--t-micro);
    color: var(--bone-dim);
    max-width: 46ch;
}

.foot-legal a {
    color: var(--bone-dim);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.foot-legal a:hover {
    color: var(--signal);
}

/* ── Entrance ────────────────────────────────────────────────────────────
   One entrance, applied to every block that has one: a short rise and fade
   as it arrives, and nothing else. There is no scroll-linked parallax, no
   blur, no rotation, and no section that arrives differently from its
   neighbour — six bespoke entrances was itself the thing that read as
   decoration.

   Content is visible by default. The .js-motion class is only added by script
   when IntersectionObserver exists and reduced motion is off, so a headless
   render, a hidden tab, or a script failure still ships a fully visible page.
   A watchdog in the script clears the class if observation ever stalls. */

/* The transition lives on the resting state; the hidden state is the class.
   Nothing here hides anything on its own — .is-out is applied by script only
   after IntersectionObserver has delivered a callback proving it is live, so a
   render where the observer never fires paints every section normally. */
.reveal {
    transition:
        opacity 0.6s var(--e-soft) var(--delay, 0ms),
        transform 0.7s var(--e-out) var(--delay, 0ms);
}

.js-motion .reveal.is-out {
    opacity: 0;
    transform: translate3d(0, 18px, 0);
}

/* The hero headline wipes up from its own clipping box on load — the one
   entrance on the page that is not the generic one, because the clip is what
   makes a drawn wordmark's headline look set rather than faded in. Kept short
   on purpose: the hero paragraph is the LCP element, and an 800ms fade with a
   380ms delay in front of it reported a 1.6s Largest Contentful Paint on a
   connection that had finished downloading everything in 200ms. */
.js-motion .hero h1 .line span {
    transform: translate3d(0, 105%, 0);
    transition: transform 0.6s var(--e-out) var(--delay, 0ms);
}

.js-motion .hero.is-ready h1 .line span {
    transform: none;
}

/* The paragraph moves but never fades. It is the Largest Contentful Paint
   element, and an element at opacity 0 has not been painted — fading it in
   pushed the measured LCP from first paint out to wherever the transition
   happened to end. Transform-only keeps it painted from the first frame and
   still gives it an entrance. */
.js-motion .hero-lede {
    transform: translate3d(0, 12px, 0);
    transition: transform 0.5s var(--e-out) var(--delay, 0ms);
}

.js-motion .hero-actions,
.js-motion .deck-stage {
    opacity: 0;
    transform: translate3d(0, 12px, 0);
    transition:
        opacity 0.4s var(--e-soft) var(--delay, 0ms),
        transform 0.5s var(--e-out) var(--delay, 0ms);
}

.js-motion .hero.is-ready .hero-lede,
.js-motion .hero.is-ready .hero-actions,
.js-motion .hero.is-ready .deck-stage {
    opacity: 1;
    transform: none;
}

/* ── Responsive ──────────────────────────────────────────────────────────
   Breakpoints are in em, not px. An em in a media query resolves against the
   browser default font size rather than the page, so when a reader raises
   that default the layout collapses to one column at the point their text
   actually needs it — a px breakpoint cannot know the text got bigger, and
   the hero deck would grow past its column and be clipped. */

@media (max-width: 62.5em) {
    .hero .shell,
    .near .shell,
    .trust .shell {
        grid-template-columns: minmax(0, 1fr);
    }

    .hero .shell {
        gap: clamp(2.5rem, 7vw, 3.5rem);
    }

    .near .shell {
        gap: 2.5rem;
    }

    .radar-wrap {
        max-width: 400px;
        order: -1;
    }

    .flow-steps {
        grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    }
}

@media (min-width: 47.5em) {
    .tongue-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

@media (min-width: 62.5625em) {
    :root {
        --t-hero: clamp(3.25rem, 6.6vw, 5.25rem);
    }
}

@media (max-width: 45em) {
    .masthead .shell {
        min-height: 60px;
    }

    .masthead nav {
        gap: var(--s-3);
    }

    .masthead nav a:not(.btn) {
        display: none;
    }

    /* The hero CTA and its note stack rather than sitting side by side: at
       this width the note wrapped to two lines next to a full-width button
       and the pair read as one ragged block. */
    .hero-actions {
        flex-direction: column;
        align-items: flex-start;
    }

    .flow-steps {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--s-6);
    }

    /* Single column: time, then heading, then body. The time keeps its own
       tighter gap to the heading it labels — at the uniform gap it read as a
       third sibling rather than as the heading's stamp. */
    .ledger-row {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--s-2);
    }

    .ledger-time {
        margin-bottom: calc(var(--s-1) * -1);
    }

    .site-foot .shell {
        flex-direction: column;
    }
}

@media (max-width: 26.25em) {
    .deck {
        width: min(18.75rem, 88vw);
        height: min(25.625rem, 120vw);
    }

    .card-pay .amount {
        font-size: 1.75rem;
    }

    /* 120px, not 130: at 320px the content box can be as little as 265px once
       a scrollbar is present, and 2 x 130 + gap did not fit — dropping the whole
       list to one very long column. */
    .trades-list {
        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
        column-gap: var(--s-3);
    }
}

/* ── Reduced motion ──────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    /* Everything that moves becomes a crossfade or an instant state. The deck
       still drags — that is a direct response to the pointer, not decoration —
       but it no longer throws. */
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.12s !important;
        transition-property: opacity, background-color, color, border-color !important;
    }

    .js-motion .reveal.is-out,
    .js-motion .hero-lede,
    .js-motion .hero-actions,
    .js-motion .deck-stage,
    .js-motion .hero h1 .line span {
        opacity: 1;
        transform: none;
    }
}

/* ── Print ───────────────────────────────────────────────────────────────── */

@media print {
    .masthead,
    .deck-stage,
    .radar-wrap {
        display: none;
    }

    body {
        background: #fff;
        color: #000;
    }

    /* The reveal is gated on IntersectionObserver, which never fires for a
       section that was not scrolled into view, so printing would otherwise
       emit blank pages for everything below the first fold. */
    .js-motion .reveal.is-out,
    .js-motion .hero-lede,
    .js-motion .hero-actions,
    .js-motion .hero h1 .line span {
        opacity: 1;
        transform: none;
    }
}
