/* ==========================================================================
   style.css — /privacy and /support

   Shipped from round legal-pages-r1, take A ("make it a better document"),
   which won the blind comparison 3-0. Built as
   ../design/prototypes/website/legal-document.css; the round is logged in
   ../design/gauntlet-log/website.md and audited in
   ../design/rounds/legal-pages-r1/AUDIT.md.

   Take A: these are DOCUMENTS. One column, read top to bottom. The fix is
   typographic and structural, not a re-invention: legible ink, a real web
   type scale, a document that can navigate itself, and the same chrome the
   hero wears so a reader who clicks Privacy has not left the product.

   This file is loaded by /privacy and /support ONLY. site/index.html loads
   /hero.css and nothing here reaches it — which is also why the two
   accessibility defects this file fixes are still live on the landing page.
   See "the same defect is still on /" below.

   TWO THINGS CHANGED AT CONVERGENCE, NOT BY THE BUILDER, and each carries
   its reasoning at the rule it changed: the contents rail is 18rem instead
   of 15.5rem and the contact address is set at the body step instead of the
   sub step (both because the address was breaking at the `@`), and one
   paragraph anchor — #email-us — gets the same landing offset and arrival
   marker as the headings.

   THAT DEFECT WAS ON / TOO, AND IS NOT ANY MORE. When this file shipped,
   hero.css's .term__w measured 4.47:1, .term__k 2.60:1 and the footer 4.47:1
   with a 3.60:1 link, all failing WCAG AA for the same two reasons: a
   secondary ink carrying primary text, and system blue used as text colour.
   / was owned by a concurrent workflow at the time and deliberately not
   touched, so this paragraph stood as a pointer at live debt. It was repaid;
   every text/ground pair on / now clears AA, 11 measured, 4.70 minimum.

   The paragraph is kept, past-tense, rather than deleted, because a comment
   that says a defect was found here and fixed there is worth more than a
   silent file. But note what it did between the fix and now: it asserted in
   the present tense that / was still broken, and it named four ratios that
   were no longer true. A reader trusting it would have gone looking for a
   defect that was not there. A comment describing another file has no test
   holding it honest, which is the argument for keeping very few of them.

   NO EXTERNAL RESOURCE OF ANY KIND. No webfont, no icon set, no script.
   That is not minimalism — it is what keeps vercel.json's
   `default-src 'none'; style-src 'self'; img-src 'self' data:` true, which
   is what turns "no trackers" from a promise on the privacy page into
   something a stranger can read out of the response headers. A redesign
   that reaches for a webfont trades the product's central claim for
   typography, and that trade is not available.

   Everything interactive below is CSS or plain HTML: <details> for the
   contents disclosure, :target for "you landed here", :hover/:focus-visible
   for affordances. There is no script because there cannot be one.
   ========================================================================== */


/* ==========================================================================
   1 · COLOUR — ground and ink decided together

   THE DEFECT THIS REPLACES, measured (design/rounds/legal-pages-r1/AUDIT.md
   §2): in light appearance 78.2% of rendered text lines on /privacy and
   85.1% on /support failed WCAG AA. Running prose was --ink-2
   (rgba(60,60,67,.60)) at 3.30:1; captions, the last-updated date, the link
   subtitles and the footer were --ink-3 (.32) at 1.77:1. Those are Apple's
   real secondaryLabel/tertiaryLabel values — but Apple spends them on
   SECONDARY UI TEXT, and here they were carrying primary prose.

   THE TRAP, also measured: copying hero.css's --ink-2 alpha of .72 onto the
   old #F2F2F7 ground lands at 4.47:1 — still failing, by 0.03. Ink and
   ground are coupled; neither move works alone. So both are made here.

   THE RULE THIS PAGE NOW FOLLOWS, and the one to carry into
   design/system.md: A DOCUMENT HAS TWO CONTENT INKS. Primary and secondary.
   Hierarchy below that is made with SIZE and WEIGHT, never by fading text
   toward the ground. There is no third ink, because there was no third ink
   in the app either — --label-3 is a *label* colour, and the moment a
   caption carries a fact (the last-updated date is the first thing a
   suspicious reader checks) it is content and it must be readable.
   ========================================================================== */

:root{
  --rhythm:10px;

  /* GROUND. White, matching hero.css. Its reason there was round 2c —
     "the grey ground makes the page read as a spec document rather than a
     product" — and it is worth ~0.2-0.5 of a contrast point against every
     ink, which is the margin all three failing families sat inside.
     #F2F2F7 is kept for insets, which is what it is for. */
  --page:#FFFFFF;
  --inset:#F2F2F7;

  /* INK. Two, and both clear WCAG AAA (7:1) on both grounds:
       --ink    #1D1D1F   16.83:1 on --page,  15.08:1 on --inset
       --ink-2  a=.90      8.01:1 on --page,   7.40:1 on --inset
     Pure #000 was available and is not used: 5.4 minutes of running prose
     is where Apple's own web ink (#1d1d1f) earns its keep.
     --ink-2 is a secondary ink, not a faded one. It reads as secondary
     because of what wears it — 11px uppercase captions, 13px metadata and
     rail links — not because it is closer to the ground than the prose. */
  --ink:#1D1D1F;
  --ink-2:rgba(60,60,67,.90);
  /* F5, round 4 craft — the 11px/600/uppercase micro-label (.eyebrow on /,
     .caption and .rail__cap here) is one repeated device and was drawing two
     different inks: hero.css's --ink-3 (.74, 5.02:1) there, this file's
     --ink-2 (.90, 8.07:1) here, 61% more contrast for the same component with
     no reason the SIZE/weight/case device should differ. This file had no
     page-level --ink-3 of its own — checked, the closest value here was
     --band-ink-2's .80 (a different token for a different job, the masthead
     nav ink). Added here as a page-level token, reused verbatim from
     hero.css's own --ink-3 rather than re-derived: 5.02:1 on --page, 4.72:1
     on --inset — both clear the 4.5:1 floor. */
  --ink-3:rgba(60,60,67,.74);

  --sep:rgba(60,60,67,.20);          /* design/system.md §2, unchanged */
  --hairline:rgba(60,60,67,.29);

  /* LINK. design/system.md §2 reserves #007AFF for two meanings — "this is
     settled" and "this commits". A hyperlink is neither, and as 17px text
     ink #007AFF measures 3.60:1 on grey and 4.02:1 on white: it FAILS as
     text in both places, including on the support card where it renders
     contact@relationalnotes.com, the product's only contact route.
     So text links get their own text-safe pair, and system blue stays a
     control colour. That is a rule kept, not a rule broken.
     Both members clear AAA on both of their own grounds. That mattered
     most on --inset when the support card was a grey fill; 2026-08-31
     moved .contact to --page with a --hairline border (see .contact
     below), so contact@relationalnotes.com now sits on --page. Both
     numbers are kept because --inset is still a correct measurement of
     the value, and --page was always the higher of the two.
       light  #004CB0   7.89:1 on --page,  7.07:1 on --inset
       dark   #4DA3FF   8.00:1 on --page,  7.06:1 on --inset */
  --link:#004CB0;

  /* CTA FILL, reused verbatim from hero.css's --cta-fill (#0071E3, single
     value, not re-themed for dark) rather than re-derived: white label =
     4.70:1 in both appearances. Not used for text links — --link above is
     that pair, for the reason recorded there. This is the fill a white
     label sits ON, and the only consumer here is .skip, which used --link
     as a background and failed dark appearance at 2.63:1 for the same
     reason hero.css's .skip failed at 3.65:1 — --link is a text-ink token,
     tuned for ink readable ON --page, not for white ink readable on IT. */
  --cta-fill:#0071E3;

  /* CHROME BAND — base/dark values. 2026-09-01: this comment used to claim
     "near-black in BOTH appearances, exactly as hero.css's --band is, so the
     masthead is the same object on all three pages." That was found false:
     hero.css's `.mast, .fold` has re-themed to white-in-light since
     2026-08-30 (see hero.css:208-252), and this file was never updated to
     match. It is now true instead of rewritten — the masthead re-themes here
     too. These near-black values are the DARK-appearance masthead values;
     they are restated verbatim in the dark media query's `.mast` restore
     below, the same two-block pattern hero.css uses. The LIGHT-appearance
     override lives at the `.mast` rule itself, next to the element it
     styles, not here — see the masthead section below for why. */
  --band:#0A0A0C;
  --band-ink:#FFFFFF;
  --band-ink-2:rgba(235,235,245,.78);
  /* DECLARED AND UNUSED, ON PURPOSE — noted 2026-09-01. Its four siblings
     (--band, --band-ink, --band-ink-2, --band-link) all have consumers in this
     sheet and this one has none. It is the separator the chrome band would use
     if the legal pages ever carried one; the masthead moved to --hairline when
     it stopped being a dark band. Kept so the group stays complete, in the same
     spirit as hero's --blue and --inset. */
  --band-sep:rgba(235,235,245,.16);
  --band-link:#4DA3FF;

  /* Site-only. The TODO marker is deliberately loud: an unfilled legal
     field that looks finished is the failure mode this styling exists to
     prevent. Unused today, kept on purpose — the next person who needs one
     should use it rather than inventing a quieter marker. */
  --todo-ink:#7A4200;
  --todo-bg:rgba(255,159,10,.22);

  /* House easing, from AGENTS.md's animation philosophy: ease-out, never
     ease-in; enter ~200ms, exit ~140ms. */
  --ease:cubic-bezier(.23,1,.32,1);
  --in:200ms;
  --out:140ms;

  /* ======================================================================
     2 · THE WEB TYPE SCALE — derived, not bolted on

     design/system.md §4 is a PHONE scale: five roles across three pixel
     values (11 / 15 / 17), authored for a device held about eight inches
     from the eye, showing one screenful at a time with no scanning task.
     The old stylesheet claimed to use that table plus "one addition"; it
     actually declared NINE distinct sizes, six of them outside the table,
     and only one of the six carried the written reason the comment claimed
     covered them all.

     A five-minute document read at arm's length on a 27" display is a
     different problem in two ways:
       · the reader is 2.5-3x further away, on a lower-ppi surface;
       · the reader SCANS. A ten-section document is navigated by heading,
         so the ramp has to carry a scanning signal the phone never needed.
     The old ramp carried none: h2 rendered at 17px over 17px prose — a
     size ratio of exactly 1.00 — and h3 at 15px was SMALLER than the body
     it headed.

     THE DERIVATION. One anchor, one ratio, six members.
       anchor  17px body. Apple's own web body size, and the app's own
               "answer / sheet row" value, so the two systems still agree
               at the size that matters most.
       ratio   1.26. Chosen because it is the ratio that lands the app's
               11px caption exactly two steps below the anchor
               (17 / 1.26² = 10.71) — the phone scale's smallest member
               falls out of the web scale rather than being imported into
               it. Three steps up gives 34.0, which is a document title.

       17 / 1.26² = 10.7  ->  11   caption      (uppercase, +.075em)
       17 / 1.26  = 13.5  ->  13   meta         (footer, rail, fine print)
       17                 =  17   body          (running prose)
       17 x 1.26  = 21.4  ->  21   sub          (lede, h3)
       17 x 1.26² = 27.0  ->  27   head         (h2)
       17 x 1.26³ = 34.0  ->  34   title        (h1)

     Ratios that result — every heading level is now strictly larger than
     the prose beneath it, which is the actual repair:
       h1:body 2.00   (was 1.47)
       h2:body 1.59   (was 1.00)
       h3:body 1.24   (was 0.79 — inverted)

     h1 is 34 and not the hero's 58 deliberately. 58px is a marketing
     assertion that has to beat a photograph of a phone; a document title
     only has to out-rank its own h2. Making them equal would be matching
     the hero's costume rather than its system.

     h3 (21/600) and the lede (21/400) share a step. They never sit
     adjacent — the lede appears exactly once, above the first h2 — so the
     pair reads as one size at two weights, which is what the app's own
     "answer, sheet row: 17 / 400-640" row already does.

     THE PHONE PASS IS RE-AUTHORED, NOT SCALED. Same ratio, new anchor of
     16px, rounded to the same integer discipline: 11 / 13 / 16 / 20 / 25 /
     30. The ratios hold (h2:body 1.56, h1:body 1.88) so the scanning
     signal survives the narrow viewport, which is exactly what the old
     ramp lost there — it dropped body to 16 and left h2 at 17, one pixel
     apart, and h3 at 15, a pixel SMALLER.
     ====================================================================== */
  /* REM, 2026-08-30, same defect as hero.css's px ramp: every member below
     was px, so a reader who raised their browser or OS default font size got
     identical pixels on /privacy and /support — the worst place for it, these
     are five-minute documents. Each value is the same number of px at the
     default 16px root (11px = 0.6875rem exactly), so nothing moves for a
     default-settings reader; a larger root now scales the whole document.
     Table in docs/reports/site-a11y-fixes.md. */
  --t-cap:0.6875rem;
  --t-meta:0.8125rem;
  --t-body:1.0625rem;
  --t-sub:1.3125rem;
  --t-head:1.6875rem;
  --t-title:2.125rem;

  /* Leading. 1.60 for running prose: this is multi-minute reading, not
     display type. hero.css pins 1.30 for its fold, measured against Apple
     display type, and that value is correct there and wrong here — the
     audit says so explicitly and it is not "harmonised" downward. */
  --lh-body:1.6;
}

/* THE MASTHEAD RE-THEMES WITH THE READER'S APPEARANCE — white band, black
   ink in light; near-black band, white ink in dark — exactly as hero.css's
   `.mast, .fold` has done since 2026-08-30. This file used to pin --band
   near-black unconditionally and the CHROME BAND comment above used to say
   so ("near-black in BOTH appearances... the same object"); that was true
   of hero.css's masthead once, stopped being true of hero.css on
   2026-08-30, and was never updated here. It was also its own defect
   independent of matching hero.css: in light appearance this page's own
   ground (--page) is white, so a near-black band sitting directly on it was
   the exact seam hero.css:208-252 was written to name and avoid ("a dark
   strip above a light [ground] is a seam nobody would author") — here
   between the masthead and the document instead of the masthead and the
   fold. Values below are hero.css's light `.mast, .fold` values verbatim,
   with one deliberate substitution: the underline on the current-page nav
   link uses --link (#004CB0, 7.89:1 on white) instead of --band-link
   (#4DA3FF, 2.63:1 on white) — --band-link is the DARK-ground member of
   that pair and fails AA the moment the ground turns white, the same class
   of failure hero.css's own --band-blue button fix (line ~234 there)
   already corrected once. This rule sits here, right after :root and
   before the dark media query, at the exact position hero.css's own
   `.mast, .fold` light override occupies relative to ITS dark media query
   — same specificity as :root, later in source order, which is the whole
   mechanism. The dark restore lives inside the dark media query directly
   below, mirroring hero.css's own dark `.mast, .fold` restore block; it has
   to be there and not merged into that block's :root, because :root cannot
   restore a value scoped to `.mast` only.

   F7, round 4 craft — DECIDED: the masthead takes its page's ground on the
   legal pages, in both appearances, not just in light. Dark measured
   #101014 masthead on a #000000 page, a 1.11:1 step — too weak to read as
   deliberate elevation and, on a page with no adjacent fold to merge with,
   it read as an unfinished edge instead. --band is now var(--page) in both
   this rule and its dark restore below, so the masthead is defined as "this
   page's own ground" rather than as two separately hand-picked near-matches
   that can drift out of sync, which is exactly what happened here. Light
   was already #FFFFFF against a #FFFFFF page (0 step) before this change;
   var(--page) keeps that result and removes the duplicate hex. This is not
   the promise band's case (see hero.css's decision record on `.promise`):
   that band is a deliberate elevated surface at 1.23:1; a masthead is
   chrome and sits WITH the page. */
.mast{
  --band:var(--page);
  --band-ink:#000000;
  --band-ink-2:rgba(60,60,67,.80);   /* 5.97:1 on white — hero.css's own value */
  --band-link:var(--link);           /* 7.89:1 on white; NOT --band-link */
}

@media (prefers-color-scheme: dark){
  :root{
    /* The page ground stays #000000 — the same value hero.css uses for its
       own page ground. There is no token to diverge here; the hero's
       lifted near-blacks are its bands, insets and bezels, not its page.
       What was actually wrong in dark appearance was that /privacy is 4.95
       screens of unbroken black, broken once by a single card and then
       never again (98.9% of the page area carrying no other ground). The
       repair is the full-width bands and insets below, not a lifted page.
       2026-08-31: most of those insets (.rail, .contact, .foot) are now
       --page with a --hairline border instead — the owner's call that an
       ordinary grey-filled block is ugly outranks this area-break tactic
       for them specifically. .claim keeps its fill, and is now the one
       deliberate break this finding asks for on /privacy; /support has no
       equivalent filled element left, which is a real, named trade against
       this finding, not an oversight. */
    --page:#000000;
    --inset:#131316;      /* white 18.54:1, --ink-2 8.51:1 */

    --ink:#FFFFFF;
    --ink-2:rgba(235,235,245,.72);   /* 8.99:1 on page, 8.51:1 on inset */
    /* F5, round 4 craft — dark half of the promoted --ink-3, reused verbatim
       from hero.css's own dark --ink-3: 4.93:1 on --page, 4.98:1 on --inset,
       both above the 4.5:1 floor. Was .78 on --band-ink-2's dark restore
       below, a different token for a different job (masthead nav ink), not
       reused here. */
    --ink-3:rgba(235,235,245,.52);

    --sep:rgba(84,84,88,.60);
    --hairline:rgba(84,84,88,.72);

    --link:#4DA3FF;

    --band:#101014;

    --todo-ink:#FFC061;
    --todo-bg:rgba(255,159,10,.20);
  }

  /* The dark half of the masthead re-theme: restores the values the light
     `.mast` rule (see the masthead section below) overrides, so dark
     appearance renders exactly what this file always rendered — near-black
     band, white ink. Same specificity as that rule, later in source order;
     that is the whole mechanism, mirroring hero.css's own dark `.mast,
     .fold` restore block. Every token the light rule touches has to be
     named here or the dark masthead would inherit light-on-dark values
     through the narrower `.mast` scope.
     F7, round 4 craft: --band is now var(--page) here too (was #101014, a
     hand-picked near-black that measured 1.11:1 off the real #000000 page —
     see the decision record at the light `.mast` rule above), so dark
     appearance renders the masthead flush with its page, matching light. */
  .mast{
    --band:var(--page);
    --band-ink:#FFFFFF;
    --band-ink-2:rgba(235,235,245,.78);
    --band-link:#4DA3FF;
  }
}

@media (max-width:759px){
  :root{
    --t-cap:0.6875rem;
    --t-meta:0.8125rem;
    --t-body:1rem;
    --t-sub:1.25rem;
    --t-head:1.5625rem;
    --t-title:1.875rem;
  }
}


/* ------------------------------------------------------------------ base */
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}

body{
  margin:0;
  background:var(--page);
  color:var(--ink);
  /* The app's face. San Francisco where it exists, and a real fallback
     stack everywhere else — no webfont is fetched. */
  font:400 var(--t-body)/var(--lh-body) -apple-system,BlinkMacSystemFont,"SF Pro Text","SF Pro Display","Helvetica Neue",Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
}

h1,h2,h3,p,ul,ol,figure,dl,dd{margin:0}
ul{padding:0}

a{color:var(--link)}
a:hover{text-decoration-thickness:2px}
/* F3, round 11 craft — hero.css's exact rule, verbatim: see `a:active` and
   `.btn:active` there for the fuller argument. This file had NO `:active`
   rule at all, so a real press on a rail row or a footer link answered
   with nothing, in any appearance, under any motion preference. The same
   currentColor ring covers `.rail ol a`, `.foot__nav a`, `.contact a`,
   `.anchor`, and `.skip` (which shares hero.css's `--cta-fill` component
   and gets the same white-on-blue ring `.btn` gets, for the same reason:
   currentColor is already this control's own checked ink, whatever it is
   sitting on). `summary` (the phone-layout contents disclosure) is the one
   control on these pages that is not an `<a>`, so it is named separately.
   Unconditional, outside any motion query — a ring is not motion — and it
   never changes ink or fill, so the "clears 4.5:1 for text on the pressed
   state" constraint holds trivially: no text colour moves. */
a:active,summary:active{box-shadow:0 0 0 1.5px currentColor}
a,summary{-webkit-tap-highlight-color:transparent}

:focus-visible{
  outline:2px solid var(--link);
  outline-offset:3px;
  border-radius:4px;
}

/* 2026-09-01: this was a second, different skip-link component from
   hero.css's — 13px/600 on --link (2.63:1 in dark, --link is a text-ink
   token, not a fill) versus hero's 17px/400 on --blue (3.65:1 in dark,
   for the reason --cta-fill exists). Rebuilt to hero.css's exact spec —
   same background token, same padding/radius, and font-size/weight left
   unset so it inherits body's 400/--t-body here exactly as it does there —
   so /privacy and /support render the same skip link / does, not a
   lookalike. */
.skip{
  position:absolute;left:0;top:0;transform:translateY(-100%);
  background:var(--cta-fill);color:#fff;
  padding:12px 18px;border-radius:0 0 10px 0;z-index:9;
  text-decoration:none;
}
.skip:focus{transform:none}
/* R10 — audited for a hover cue and deliberately left without one. .skip
   is off-canvas (translateY(-100%)) until :focus brings it on screen, so the
   only realistic way a pointer ever meets it is already-focused, where the
   :focus-visible ring (fixed this round, F5) is already the affordance.
   A hover-only treatment on a control that is never visible without focus
   would duplicate that ring rather than add anything a user could see. */
/* F5, round 10 craft — DECIDED, LEFT AS IS. Focused box [0,0,151.17,46.09];
   :focus-visible's ring sits at outline-offset:3px, so its top and left
   sides fall at y=-5/x=-5 — off-canvas, unpainted. Measured perimeter:
   217.26 of 434.52px, 50%.

   `border-radius:0 0 10px 0` is the tell this is not an oversight: only the
   bottom-right corner is rounded, meaning the element is drawn as flush
   against the viewport's top-left corner ON PURPOSE, a tab with two edges
   that ARE the edge of the world, not two free edges a ring failed to
   reach. A ring cannot add a boundary cue on a side that has no "outside"
   to distinguish from.

   The tempting fix — `outline-offset:-3px`, drawing the ring inside the
   button so all four sides paint — was checked against the actual tokens,
   not just the geometry, and REJECTED: the ring is `outline:2px solid
   var(--link)` (global :focus-visible rule, above) and `.skip`'s own
   background is `--cta-fill`, a DIFFERENT blue. --link #004CB0 on
   --cta-fill #0071E3 contrasts at 1.68:1 in light mode and --link #4DA3FF
   on the same #0071E3 at 1.79:1 in dark — both well under the 3:1 WCAG
   non-text floor every other ring on this page clears. The CURRENT ring,
   painted only where it sits over --page, contrasts at 7.89:1 (light) and
   8.00:1 (dark) — the geometry that looks like a shortfall is the reason
   the ring is legible at all. Trading a passing, high-contrast half-ring
   for a failing, low-contrast whole one is not a fix. Left as is; the
   button's own transition from off-canvas to a solid, full-colour corner
   tab already is the primary, unambiguous focus signal, and the two
   painted sides are real reinforcement of it rather than a broken
   attempt at four. */

/* The one container. Full-width bands live outside it; the document column
   lives inside it.
   2026-09-01: max-width raised from 1180px to hero.css's 1280px — reused,
   not invented. This is the box the masthead and footer sit in DIRECTLY
   (.mast__in, .foot__grid's own edge), so a narrower .pad here than
   hero.css's put the wordmark 209px from the viewport edge at 1500 CSS px
   against hero.css's 159px, on chrome that is supposed to be the same
   object on every page. The document column itself does not move: .doc and
   .foot__grid are centered a SECOND time inside .pad at their own fixed
   width (18rem + gap + 34rem), and centering a fixed-width box inside a
   centered container cancels the container's own width out of the sum — the
   rail and the footer's 300px inset are unchanged by this, verified by
   arithmetic, not by assumption: doc_left = viewport/2 - doc_width/2
   regardless of .pad's max-width, as long as .pad is wide enough to hold it,
   which it is at every shipped breakpoint. */
.pad{
  max-width:1280px;
  margin:0 auto;
  padding:0 clamp(20px,4vw,48px);
}


/* -------------------------------------------------------------- masthead
   hero.css's masthead, matched: a 64px band with a 600 wordmark that
   RE-THEMES WITH THE READER'S APPEARANCE — white band, black ink in light;
   near-black band, white ink in dark. The token overrides that do the
   re-theming sit up near :root, at the same relative position hero.css
   keeps its own `.mast, .fold` light override (before the dark media
   query) — see the CHROME BAND comment there for the full reasoning. This
   rule is the structural one: background and ink colour, unconditional on
   appearance, reading whatever `.mast`'s custom properties currently are.

   F7, round 4 craft — A HAIRLINE, CHECKED: once --band equals --page (see
   the decision record above), the masthead paints the exact colour of the
   page under it, at every width and in both appearances — measured, 0 step
   everywhere. Unlike index.html, where the masthead merges into the fold's
   own dark band directly beneath it, here the masthead sits directly above
   ordinary page content with nothing else marking where chrome ends and the
   document begins; without a rule, only the wordmark and nav text would say
   "this is a bar," and a reader scrolled past the top would have no edge to
   read at all. So it gets one: --hairline is the exact token .rail/.contact/
   .foot already use for "a distinct thing on the page's own ground, no fill"
   — reused, not invented, and the same reasoning applies one component up. */
.mast{
  background:var(--band);
  color:var(--band-ink);
  border-bottom:1px solid var(--hairline);
}

/* What this replaces: an unstyled 75.5px gap with a black 17px/640 word
   floating in it, 20px above a 28px/640 h1 of the same weight, colour and
   family — nothing but 11px of size separated the site's identity mark
   from the page title, and the wordmark did not read as a link at all
   (--ink, no underline, blue only on :hover, which does not exist on
   touch). A reader who wanted to get back to the product had an invisible
   affordance at the top or a "Home" row 4.5 screens down. Now the band is
   chrome, it is obviously chrome, and it carries both siblings. */
.mast__in{
  display:flex;
  align-items:center;
  gap:calc(var(--rhythm) * 2);
  min-height:64px;
  flex-wrap:wrap;
}
/* Matches hero.css's own phone masthead — 64px is a desktop number on both
   files, and hero.css shrinks it to 56px at this same breakpoint. Placed
   here, after the base rule, on purpose: an earlier attempt put this same
   override inside the `@media (max-width:759px)` block up near :root, which
   sits BEFORE this base `.mast__in` rule in the file, and lost the same-
   specificity, later-wins tie-break to it at every width — measured at 393
   CSS px, .mast__in still read min-height:64px. A media query changes WHEN a
   rule applies, never WHERE it sits in the cascade order. */
@media (max-width:759px){
  .mast__in{min-height:56px}
}
.wordmark{
  position:relative;
  color:var(--band-ink);
  font-size:var(--t-body);
  font-weight:600;
  letter-spacing:-.01em;
  text-decoration:none;
  margin-right:auto;
}
.wordmark:hover{text-decoration:underline;text-underline-offset:.22em}
/* Same hit-area rule as .foot__nav a and .anchor, at 73.1x25.6 at 393px. */
.wordmark::after{
  content:"";
  position:absolute;
  top:50%;left:0;right:0;
  height:44px;
  transform:translateY(-50%);
}

.mast__nav{
  display:flex;
  gap:calc(var(--rhythm) * 2.2);
  font-size:var(--t-meta);
  font-weight:600;
}
.mast__nav a{
  position:relative;
  color:var(--band-ink-2);
  text-decoration:none;
  padding:2px 0;
  border-bottom:2px solid transparent;
}
.mast__nav a:hover{color:var(--band-ink)}
.mast__nav a[aria-current="page"]{
  color:var(--band-ink);
  border-bottom-color:var(--band-link);
}
/* Same hit-area rule as .foot__nav a and .anchor — the site's only
   navigation, measured at 46.8x26.8 at 393px. A generated ::after frame,
   not min-height: this is a flex row centred with align-items, and a taller
   real box would recentre against its siblings; a frame that never enters
   layout cannot.
   R10 — `top:50%` on an absolutely positioned child resolves against the
   PADDING box of its `position:relative` parent, not the border box. This
   anchor carries `border-bottom:2px solid transparent` (the aria-current
   underline's reserved space), so its padding box is 2px shorter than the
   26.8px border box a pointer actually meets, and 50% of the shorter box
   sits 1px above the anchor's real centre. A strict point-probe at exactly
   ±22px from the anchor's own centre found it: 23px of reach up, 20 down —
   44px total, present, just off-centre. `calc(50% + 1px)` — half that
   border — puts the frame back on the anchor's true centre. */
/* "YOU ARE HERE" CANNOT BE DRAWN WITH A TRANSPARENT BORDER UNDER
   forced-colors. The resting state declares `border-bottom:2px solid
   transparent` and the current page overrides only its COLOUR, which is the
   ordinary way to reserve the space. But forced-colors repaints `transparent`
   as a real system colour, so every nav link gained the underline: measured on
   support.html, Privacy and Support both computed `rgb(0,0,159)` in light and
   both `rgb(255,255,0)` in dark. On the two pages where the indicator carries
   meaning, a forced-colors reader could not tell which page they were on.

   `forced-color-adjust:none` restores the original values and was rejected: it
   opts the component out of the reader's chosen palette entirely, which is the
   opposite of what the setting is for. System colour keywords keep the palette
   and restore the distinction — `Canvas` is the page's own ground, so a border
   painted in it is invisible exactly where transparency was meant to be. */
@media (forced-colors: active){
  .mast__nav a{border-bottom-color:Canvas}
  .mast__nav a[aria-current="page"]{border-bottom-color:LinkText}
}

.mast__nav a::after{
  content:"";
  position:absolute;
  top:calc(50% + 1px);left:0;right:0;
  height:44px;
  transform:translateY(-50%);
}


/* ============================================================== the shell
   A two-column document: a contents rail that stays on screen, and the
   prose column at its measure.

   THE MEASURE IS UNCHANGED AT 34rem. Measured, /privacy runs 67.4
   characters per line at 1440 and /support 64.3 — both inside the 55-75
   band and better controlled than the hero's, whose range reaches 149. It
   is the one typographic decision on these pages that was already right,
   and it is the one a "make it match the hero" round is most likely to
   break. What was wrong was not the measure but its CONSEQUENCE: a naked
   544px column centred in a 1440px void spent 62.2% of the viewport on
   empty ground. The rail spends it instead.

   DOM ORDER IS THE PHONE ORDER; GRID PLACEMENT IS THE DESKTOP ORDER.
   The document is three blocks — head, rail, sections — in that order in
   the markup, so a phone reads title, date, lede, claim, THEN the contents
   list, THEN the policy. The first capture of this page had the rail first
   and it cost the entire phone fold: the reader landed on a table of
   contents instead of on the page. On desktop the rail is placed into
   column 1 spanning both rows, which is a grid placement and not a second
   copy of the markup — there is exactly one contents list on the page, in
   one place in the accessibility tree, at every viewport.

   THE RAIL IS 18rem AND THE ADDRESS IS WHY — changed at convergence, not by
   the builder. Through the round it was 15.5rem: a 248px panel with a 206px
   content box, while `contact@relationalnotes.com` at the sub step measures
   276.1px. So the rail broke the site's only contact route across a line at
   the `@`. Three of the four critics named it, one calling it "the worst
   defect in either version's support page", and it is visible in the judged
   capture at
   ../design/rounds/legal-pages-r1/raw/take-a-document/legal-document-support-1440-light.png.
   An email address is one unbreakable token, so the panel that carries it is
   sized to hold it rather than asked to break it politely: 18rem gives a
   246px content box against the address's 229.4px at the body step (see
   .contact__addr). Verified one line, no element overflow and no horizontal
   page scroll at 320, 390, 768, 900, 940, 1000, 1100, 1280, 1440, 1728 and
   2560 CSS px. If the address ever gets longer, this number moves with it. */
.doc{
  display:grid;
  grid-template-columns:minmax(0,18rem) minmax(0,34rem);
  grid-template-rows:auto 1fr;
  column-gap:clamp(32px,4.5vw,72px);
  justify-content:center;
  align-items:start;
}
/* 2026-09-01 — THE ONE DOCUMENT WITH NO RAIL.
   `.doc` is a two-column grid, and both columns earn their place on the legal
   pages: column 1 holds the sticky contents rail, row 1 holds the head with its
   claim card. 404.html has neither. Round 14 measured what borrowing the grid
   anyway cost: an empty 288px track, a 0px head row, the leftmost painted ink at
   x=540.8 where the legal pages put it at 195.2, the content column 172.8px
   right of the viewport centre, and the h1 at y=65 where both legal pages put it
   at 105.

   That is the exact condition style.css:595 argues against in its own words: "a
   naked 544px column centred in a 1440px void spent 62.2% of the viewport on
   empty ground. The rail spends it instead." The 404 recreated the void and
   pushed it off-centre, on the page every stale App Store link lands on.

   One column, at the same 34rem measure the prose already uses, centred. The
   head keeps its own padding so the h1 lands where a reader who clicks through
   to Privacy will find the next one. */
.doc--plain{
  grid-template-columns:minmax(0,34rem);
  grid-template-rows:auto auto;
}
.doc--plain > .doc__head,
.doc--plain > .doc__sections{grid-column:1}
/* The phone rule that tightens the first section exists because the rail sits
   between the head and it there, and its top margin lands on the row gap. With
   no rail there is nothing to compensate for, and the compensation just reads
   as 24px where every other first section reads 40. */
@media (max-width:899px){
  .doc--plain > .doc__sections > .sec:first-of-type{margin-top:calc(var(--rhythm) * 4)}
}

.doc__head{
  grid-column:2;grid-row:1;
  min-width:0;
  padding-top:calc(var(--rhythm) * 4);
}
.doc__sections{
  grid-column:2;grid-row:2;
  min-width:0;
  /* 2026-09-01: raised from rhythm*6 (60px) to rhythm*10 (100px), matching
     hero.css's .price — the marketing page's own last section before its
     footer — so the breathing room above the footer's divider is the same
     on every page. Unlike the ink/hierarchy findings elsewhere in this
     file, nothing here ties the smaller number to the document's own
     scanning rhythm: rhythm*6 carried no comment and no stated reason,
     which is unusual in this file, where every other rhythm multiplier is
     justified at the point it's used. The footer is shared chrome; the gap
     that introduces it reads as part of that chrome, not as part of the
     document's internal rhythm, so it is unified rather than left to
     differ by default. */
  padding-bottom:calc(var(--rhythm) * 10);
}

/* ------------------------------------------------------------------ rail
   A bordered panel rather than bare text in the gutter, at every viewport.
   It makes the contents read as an object rather than as loose links.

   2026-08-31, GROUND CHANGED FROM --inset TO --page: the owner's call is
   that a flat grey fill on an ordinary block reads as inert, and a
   hairline-bordered block on the page's own ground reads as a distinct
   thing you can read — round 2c's page-ground reasoning ("the grey ground
   makes the page read as a spec document rather than a product") applied
   one level down, from the page to the block. Fill is now spent only where
   something is genuinely emphasised (.claim, below), never on an ordinary
   panel like this one.

   NAMED COST, not silently dropped: this panel used to be the OTHER half
   of the measured dark-appearance finding above .foot in this file — it is
   STICKY, so its --inset fill used to put a second ground on screen for
   the whole scroll, not just the "single card at half a screen" a static
   capture could see. A --hairline border does not buy back that area-break;
   it buys legibility-as-an-object instead. On /privacy, .claim still
   supplies one filled card, so that page keeps its one deliberate break;
   on /support, which has no .claim, this is a real trade against that
   older finding, made because the owner ranked "not ugly" above it. */
.rail{
  grid-column:1;grid-row:1 / span 2;
  position:sticky;
  top:calc(var(--rhythm) * 3);
  /* 2026-09-01 — A STICKY CARD MUST FIT THE WINDOW IT STICKS TO.
     On /support the card measures 740.75px, so it fitted only when the viewport
     was 771px tall or more. Measured on real device shapes: at 1440x764 it hung
     6.75px past the window and lost its bottom border and 14px corner; at
     1280x720 it lost an entry; at 1366x640 it lost two. The last entry,
     "Reporting a privacy or security problem", did not enter the viewport until
     83% of the way down the document, well past the section it links to.
     /privacy's rail is 489px and was always safe above 519.

     top is 30px, so the card can have the window minus that, minus the same
     again at the foot so it does not sit flush against the bottom edge. It
     scrolls inside itself when the list is longer than that, which is what a
     tall table of contents does everywhere else. max-height is inert whenever
     the card already fits, so /privacy and every tall window are untouched. */
  max-height:calc(100vh - var(--rhythm) * 6);
  overflow-y:auto;
  overscroll-behavior:contain;
  margin-top:calc(var(--rhythm) * 4);
  background:var(--page);
  border:1px solid var(--hairline);
  border-radius:14px;
  padding:calc(var(--rhythm) * 1.8) calc(var(--rhythm) * 2);
  font-size:var(--t-meta);
  line-height:1.5;
}

/* THE CAP CARRIES `hidden` IN THE MARKUP AND THIS RULE TAKES IT BACK.
   The comment in the phone block below already knew these two elements say the
   same word — "the summary already says Contents; the desktop caption would be
   a second copy" — and the CSS hides whichever one is wrong at each width. With
   no CSS at all, nothing hides either, and privacy.html reads "Contents" twice
   back to back. A stylesheet-stripping proxy, a blocked request or a broken
   deploy of just the HTML all land there.

   `hidden` is a UA-stylesheet `display:none`, so it holds when no author CSS
   arrives and the native <summary> is left to do the job alone. An author
   `display` beats the UA rule, so with CSS present nothing changes: this rule
   shows the cap on desktop, and the phone block below still swaps which of the
   two is visible. */
.rail__cap{
  display:block;
  margin:0 0 calc(var(--rhythm) * 1.2);
  font-size:var(--t-cap);
  /* 2026-09-01: was 660 (design/system.md §4's spec weight, inherited
     unchanged). hero.css's matching device (.eyebrow/.step__n/.term__k)
     sits at 600 by measured, documented deviation from that same spec
     value, and is capped at two weights (600/400) end to end — a
     constraint this file does not share, since it already runs
     600/640/660 elsewhere. Moving here to 600 is the lower-friction side
     of that mismatch: one repeated device, one weight, on both pages.
     F5, round 4 craft: colour moves the same way, same reason — was --ink-2
     (.90, 8.07:1), now --ink-3 (.74, 5.02:1), hero.css's own value for this
     device, so the last uncaught divergence in "one repeated device" is
     closed. */
  font-weight:600;
  letter-spacing:.075em;
  text-transform:uppercase;
  color:var(--ink-3);
}

.rail ol{
  margin:0;
  padding:0;
  list-style:none;
}
/* F3, round 4 craft — THE SPINE MOVES FROM THE LINK TO THE ITEM. It used to
   be a border-left on `.rail ol a`, which is exactly as tall as the link's
   own row. A nested <ol> is a SIBLING of that link inside the same <li>, so
   the border never covered it: measured on /support, the outer spine ran
   432.25-540.25, stopped for the whole 112.25px the nested group occupies,
   then resumed at 652.25 — 42% of the outer spine's own run missing, and the
   nested group reading as outside the list rather than inside it. An <li>
   contains both its own link AND any nested <ol> beneath it, so a border on
   the <li> spans that full height automatically, closing the hole without
   a second rule for the nested case — and because the selector is not
   depth-limited, a nested <li> draws its own indented spine the same way,
   which is the look this file already had (`.rail ol ol` shifts the whole
   nested block right by rhythm*1.4, carrying its border with it). /privacy
   has no nested <ol>, so its seven-segment unbroken spine is unchanged by
   this move — verified below. */
.rail ol li{
  border-left:2px solid var(--sep);
}
/* F3, round 4 craft — top/bottom MARGIN here, not padding, left a residual
   2px gap in the outer <li>'s new border-left: a margin on this block's own
   top/bottom edge collapses through its parent <li> (nothing on the <li>
   stops it, since border-left does not), so the <li>'s own border box ended
   2px short of the space the margin visually reserved. Padding is inside
   the box that carries the border, so it cannot collapse out from under it —
   same visible spacing, verified continuous spine (0px gap, was 2px). */
.rail ol ol{
  padding:calc(var(--rhythm) * .2) 0 calc(var(--rhythm) * .2) calc(var(--rhythm) * 1.4);
}

/* F2, round 10 craft — this file's usual move for an undersized control is
   a generated ::after frame (.foot__nav a, .anchor, .wordmark, .mast__nav a
   all use it): out-of-flow, so it grows the hit area without touching
   layout. That move was tried here first and MEASURABLY FAILS for this
   component specifically, because these rows are CONTIGUOUS (gapToNext
   0-2px) — there is no free space around a row for a frame to claim.
   Overlapping frames on adjacent rows do not both win: at any pixel where
   two absolutely-positioned ::after boxes overlap, the one later in DOM
   order paints on top and wins elementFromPoint, so a frame extending
   DOWN into the next row's territory is immediately shadowed by that next
   row's OWN frame extending up. Measured after trying it: reachUp jumped
   to the full 22 (nothing later in the DOM contests upward growth) but
   reachDown was cut to 6-8, netting the exact same ~29px total — a
   redistribution of the same territory, not a real enlargement, for
   every row except the last in the list (which has nothing below it to
   lose to). A frame-based fix here would ship a probe that reads green in
   isolation while every interior row measures unchanged.
   The only way to give a row MORE hit height when it already borders its
   neighbours on both sides is for the row to OCCUPY more real space, so
   this grows the actual padding box instead: rhythm*1.25 top/bottom (was
   rhythm*.5) makes the ink box 19.5 (line) + 25 (padding) = 44.5, clearing
   the floor with real, non-shared layout — verified by the same walking
   probe with no dependency on paint order, at 320/375/393/430/800 on both
   pages, all seven-to-nine rows including the nested group. The visible
   cost is a taller list (roughly +14.5px per row, ~+100-120px total across
   the rail) and a taller sticky card; there was no cheaper way to make
   contiguous 44px targets real rather than borrowed. */
.rail ol a{
  display:block;
  padding:calc(var(--rhythm) * 1.25) 0 calc(var(--rhythm) * 1.25) calc(var(--rhythm) * 1.4);
  color:var(--ink-2);
  text-decoration:none;
}
/* The hover border-colour swap that used to live here traded on the border
   belonging to the link; it now belongs to the item, which may hold a whole
   nested list, so recolouring it on a single row's hover would recolour the
   nested group's spine too. Dropped rather than faked with a second
   overlay: the text-colour change below is still the hover cue. */
.rail ol a:hover{
  color:var(--link);
}

.rail .contact{
  background:none;
  border:0;
  border-radius:0;
  padding:0;
  margin-bottom:calc(var(--rhythm) * 2);
  padding-bottom:calc(var(--rhythm) * 1.8);
  border-bottom:1px solid var(--sep);
}

.rail__cap--after{margin-top:calc(var(--rhythm) * 1.6)}

.rail__note{
  margin-top:calc(var(--rhythm) * 1.8);
  padding-top:calc(var(--rhythm) * 1.4);
  border-top:1px solid var(--sep);
  color:var(--ink-2);
  font-size:var(--t-meta);
}


/* ------------------------------------------------------------------ type */
h1{
  margin:0 0 calc(var(--rhythm) * 1.2);
  font-size:var(--t-title);
  line-height:1.1;
  font-weight:600;
  /* hero.css ledger line 21, measured: Apple tracks display type at the
     -0.005em order. The old h1 was at -0.02em, four times tighter. */
  letter-spacing:-.006em;
}

/* 2026-09-01 — text-wrap:balance. hero.css:685 sets the same on .fold__say
   h1 for the same reason given there: balance evens the lines of a short
   display block, which is what a section-title heading is, as opposed to
   the running-prose case (.fold__tail, .fold__say .lede, .claim) that takes
   `pretty` instead to protect prose's last line. style.css had grep -c
   text-wrap: 0 before this — no wrap policy at all — while support.html and
   privacy.html strand a heading's last word at ordinary phone widths: "it"
   alone at 0.031 of the measure (privacy's "who receives it", 480-505px and
   900px), "problem" at 0.263 (support's "security problem", 393px), "notes"
   at 0.152 (support's h3 "you keep your notes", 375px). */
h2{
  margin:0 0 var(--rhythm);
  font-size:var(--t-head);
  line-height:1.18;
  font-weight:600;
  letter-spacing:-.006em;
  text-wrap:balance;
}

h3{
  margin:calc(var(--rhythm) * 2.8) 0 calc(var(--rhythm) * .6);
  font-size:var(--t-sub);
  line-height:1.28;
  font-weight:600;
  letter-spacing:-.008em;
  text-wrap:balance;
}

p{margin:0 0 calc(var(--rhythm) * 1.4);color:var(--ink)}
p:last-child{margin-bottom:0}
p strong,li strong{font-weight:640}

/* 2026-09-01 — the only <code> on the site that ships (privacy.html's
   inline domains and identifiers: ingest.de.sentry.io, eu.i.posthog.com,
   Purchasing.start(), AppEnvironment, /_vercel/insights/script.js) had no
   rule here and rendered as the UA default: monospace at 1.00em. hero.css
   declares a mono treatment for exactly this job (.checkable code) but it
   styles nothing that ships — document.querySelectorAll('code') returns 0
   on index.html. Reused rather than reinvented: same size compensation
   (.94em, monospace faces read large at parity), and .todo's own mono
   stack immediately below, so this page has one mono voice, not two. No
   colour set — code inherits whatever ink the surrounding prose already
   uses, since it appears inline inside ordinary paragraphs, not as its
   own block. */
code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.94em;
}

.lede{
  font-size:var(--t-sub);
  line-height:1.42;
  letter-spacing:-.008em;
  margin-bottom:calc(var(--rhythm) * 2.4);
}

.meta{
  font-size:var(--t-meta);
  color:var(--ink-2);
  margin:0 0 calc(var(--rhythm) * 2.4);
}

.caption{
  margin:0 0 var(--rhythm);
  font-size:var(--t-cap);
  font-weight:600;   /* see .rail__cap's 2026-09-01 comment: same device, same fix */
  letter-spacing:.075em;
  text-transform:uppercase;
  color:var(--ink-3);   /* F5, round 4 craft: see .rail__cap's comment */
}

/* Prose lists. The body ink, not a faded one — the audit's sharpest
   observation about this page is that the bold black lead-ins read loudly
   while the evidence for them receded into 3.30:1 grey. On a page that
   exists to be checked rather than believed, the claim was legible and the
   evidence was not. */
.prose ul{
  margin:0 0 calc(var(--rhythm) * 1.4);
  padding-left:calc(var(--rhythm) * 2);
  color:var(--ink);
}
.prose li{margin-bottom:calc(var(--rhythm) * .7)}
.prose li:last-child{margin-bottom:0}

/* Inline links inside prose: underlined, offset, with a lightened rule —
   hero.css's .link treatment, so a link is the same object on all three
   pages.
   F2, 2026-09-01 — the offset and mix % had drifted from that treatment
   (.2em/45% here vs hero.css's .22em/40%): #8CAEDB at 2.284:1 on white
   against hero.css's own #99B7DF at 2.060:1, a visible colour difference on
   an object this comment already says is meant to be identical. Corrected
   to hero.css's exact numbers; the 0.02em offset difference never resolved
   to a different pixel row at 3x, so the colour was the only part actually
   wrong. */
.prose a,.rail__note a,.foot a{
  text-decoration:underline;
  text-underline-offset:.22em;
  text-decoration-thickness:1px;
  text-decoration-color:color-mix(in srgb,currentColor 40%,transparent);
}


/* ============================================================== sections
   Every section and sub-section carries an id, and every heading carries a
   permalink to it.

   WHY, precisely: before this, /privacy had ZERO elements with an id. A
   reader arriving to check one claim — "if I turn on iCloud sync, can you
   or Apple read my notes?" — had to scroll 1,810px past three complete
   sections to the authoritative answer, having passed a summary of it at
   half a screen with nothing to say the fuller version existed. And there
   was no way for Aaron to answer a support email with a link to a specific
   claim; the only reply available was "it's on the privacy page, scroll
   down." A policy written to be checked has to be addressable. */
.sec{
  margin-top:calc(var(--rhythm) * 4.4);
  scroll-margin-top:calc(var(--rhythm) * 4);
}
.sec--sub{
  margin-top:0;
  scroll-margin-top:calc(var(--rhythm) * 4);
}
.sec > h2{
  padding-top:calc(var(--rhythm) * 1.8);
  border-top:1px solid var(--sep);
}

/* The permalink. Present at rest rather than revealed on hover, because
   :hover does not exist on a phone and this affordance is most useful to
   the person answering a support email from one. */
.prose a.anchor,
.anchor{
  position:relative;
  margin-left:.4em;
  font-size:var(--t-meta);
  font-weight:400;
  color:var(--ink-2);
  text-decoration:none;
  vertical-align:.12em;
}
.prose a.anchor:hover,
.anchor:hover{color:var(--link);text-decoration:none}
/* THE HIT AREA, which a screenshot cannot see — this file's own words, on
   the rule it already wrote down once and applied to one component
   (.foot__nav a, below). This is the sharpest miss of that rule: the
   glyph measured 8x16 at 393px, and it is "visible at rest specifically
   because :hover does not exist on a phone and this affordance is most
   useful to the person answering a support email from one" (the comment
   above) — the stated audience is exactly who could not hit it. A generated
   hit frame, not padding: the anchor sits at the end of a heading with
   nothing after it, so a real padding box would still be safe there, but a
   frame that never enters layout is the same guarantee with zero flow risk
   next to the :target marker this same heading already carries. Sized 44px
   square, Apple's own floor and the number style.css:1030 already used,
   centred on the glyph — the "#" itself does not move, only what's
   invisible around it grows. */
.anchor::after{
  content:"";
  position:absolute;
  top:50%;left:50%;
  width:44px;height:44px;
  transform:translate(-50%,-50%);
}

/* "You landed here." Pure CSS, no script — :target is the whole mechanism.
   A first pass drew this with two stacked box-shadows and the later shadow
   painted BEHIND the earlier one, so the marker rendered as a pale grey
   notch and the blue was never visible. Caught in the capture. */
.sec > h2,
.sec--sub > h3,
.prose p[id]{position:relative}
.sec:target > h2::before,
.sec--sub:target > h3::before,
.prose p[id]:target::before{
  content:"";
  position:absolute;
  left:-18px;
  top:.14em;
  bottom:.14em;
  width:3px;
  border-radius:2px;
  background:var(--link);
}
/* F4, round 10 craft — `.pad`'s gutter is `clamp(20px,4vw,48px)`; 4vw only
   overtakes the 20px floor once the viewport is wider than 500px (4vw of
   500 is exactly 20), so every width from 320 to 500 runs the same fixed
   20px gutter this marker was never checked against. `left:-18px` reaches
   18 of those 20px, landing the marker's own left edge at viewport x = 2 —
   measured 2.00px at 320 through 430, on every :target anchor on both
   pages, with nothing else on the page to its left. On the phone the
   contents rail is the primary navigation, so a jump through it fires this
   on nearly every visit. Scoped to the exact range that floors: -8px still
   reaches past the text column into the gutter (this marker's whole point
   — a bar OUTSIDE the prose, not touching it) while landing at x = 12,
   comfortably inboard of the edge. Desktop is untouched: at 1500 the
   marker already sits at x 638 with 300px of page to its left.

   F2, round 11 craft — that fix stopped one gutter-floor step short of the
   actual symptom. The 500px boundary was chosen because that is where
   `4vw` overtakes the 20px floor, but the marker's x-position depends on
   TWO things crossing at different widths: the gutter floor (500px) and
   the `.doc` single-column layout's own 34rem (544px) prose cap, which
   this query never accounted for. Above 591.3px the prose column stops
   growing with the viewport and centres instead, so the column's own left
   edge — and therefore this marker, which is offset from it — keeps
   moving right as the viewport widens, all the way to where `left:-18px`
   (this rule's un-scoped default, still in force above 500px) finally
   clears 12px again. That crossing is at exactly 604px: `left:-18px`
   lands at x = 2.03 at 501, rising to x = 12.00 at 604 and above (matches
   the already-known x = 12.50 at 605) — so 501 through 603 is the exact
   gap, one gutter-floor boundary short of where the fix actually needed
   to reach. Widened to `max-width:603px`: `left:-8px` now lands at
   x = 12.03 at 501, rising monotonically to x = 21.5 at 603, and the
   un-scoped `-18px` rule immediately above the boundary lands at exactly
   x = 12.00 at 604 — never below 12 anywhere from 320 up. No other rule
   lives inside this query; widening its boundary touches nothing else. */
@media (max-width:603px){
  .sec:target > h2::before,
  .sec--sub:target > h3::before,
  .prose p[id]:target::before{
    left:-8px;
  }
}

/* One anchor on the site is a paragraph rather than a section — #email-us,
   the exception to "the app collects nothing". It gets the same landing
   offset and the same arrival marker as every heading, because an anchor
   that does not confirm where it landed is worse than no anchor. */
.prose p[id]{scroll-margin-top:calc(var(--rhythm) * 4)}


/* ============================================================ the summary
   THE ANSWER TO "I CAME TO CHECK ONE THING".

   The four supporting lines are canonical in AGENTS.md and identical
   across the site, the App Store listing, the about screen and the paywall
   — so their WORDS are reproduced here exactly as they already stood on
   this page, untouched. What is added is an exit from each line to the
   section that treats it in full, which is the thing the page did not have:
   a summary that admits a fuller answer exists and says where it is. */
.prose ul.four{
  list-style:none;
  margin:0 0 calc(var(--rhythm) * 1.4);
  padding:0;
}
.prose ul.four > li{
  margin:0;
  padding:calc(var(--rhythm) * 1.8) 0;
  border-top:1px solid var(--sep);
  line-height:1.5;
}
.prose ul.four > li:first-child{border-top:0;padding-top:0}
.prose ul.four > li:last-child{padding-bottom:0}

/* DECLARED AND UNUSED, ON PURPOSE — noted 2026-09-01 after round 15 flagged
   both blocks as dead with no explanation, which was fair: hero's --blue and
   --inset carry a note saying they are kept deliberately and these did not.

   .deeper is the "read more" affordance. It was written here first, and round 9
   found the complete component sitting in the file with no element for it while
   the file that HAS one carried nothing. The live version is hero.css's, at that
   file's own type scale. This copy is kept because the legal pages may want the
   same affordance and it should look like one object when they do, not because
   anything renders it. Nothing on /privacy, /support or /404 uses it. */
.prose a.deeper,
.deeper{
  display:block;
  margin-top:calc(var(--rhythm) * .8);
  font-size:var(--t-meta);
  font-weight:600;
  text-decoration:none;
}
.deeper:hover{text-decoration:underline;text-underline-offset:.24em}


/* DECLARED AND UNUSED, ON PURPOSE, same note as .deeper above. The measured
   argument below is the reason to keep it: it is the record of why full-width
   grounds exist on this site at all, and .claim now does that job on /privacy
   with one filled card. If a legal page ever needs a second ground break, this
   is the mechanism and the numbers that justified it. Nothing uses it today. */
/* ================================================================= bands
   Full-width grounds. Two jobs at once:
     · they break the ground, which in dark appearance is the whole
       finding — /privacy was 98.9% unbroken pure black by area against the
       hero's 42.1% carrying some other ground;
     · they spend the viewport width that a centred 544px column was
       leaving empty, without touching the measure.
   The prose inside them stays at its measure; only the GROUND is wide. */
.band{
  background:var(--inset);
  border-top:1px solid var(--sep);
  border-bottom:1px solid var(--sep);
}
.band__in{
  max-width:34rem;
  margin-left:auto;
  margin-right:auto;
  padding:clamp(36px,6vh,64px) 0;
}
.band .caption{color:var(--ink-2)}


/* ------------------------------------------------------------- the claim
   The one thing on the page that earns being lifted off the ground —
   design/system.md §3, "does every container hold as much as it costs?".
   It holds the sanctioned sentence at the sub step, so it is the largest
   run of text on the page after the title, which is the correct ranking:
   it is the sentence every other surface has to match. */
.claim{
  background:var(--inset);
  border:1px solid var(--sep);
  border-radius:14px;
  padding:calc(var(--rhythm) * 2.2) calc(var(--rhythm) * 2.4);
  margin:0 0 calc(var(--rhythm) * 1.4);
}
.claim__text{
  margin:0;
  font-size:var(--t-sub);
  font-weight:500;
  line-height:1.38;
  letter-spacing:-.008em;
  /* 2026-09-01 — text-wrap:pretty, carried over from hero.css's .claim (same
     sanctioned sentence, same fix, one file over). Left at computed
     text-wrap:wrap, "else." stranded at 0.087-0.130 of the measure at every
     width from 414 to 1720 CSS px — worse than hero.css's own case, because
     this copy has no authored <br> splitting the two sentences, so the
     strand held continuously instead of in bands. `pretty` protects the
     last line of running prose; `balance` stays reserved for short display
     blocks (the h1), per hero.css's own 2026-09-01 comment on .fold__tail. */
  text-wrap:pretty;
}


/* ---------------------------------------------------------- the contact
   design/system.md §3: "does every container hold as much as it costs?"
   The old support card spent a full-width white ground, a 12px radius and
   20px of padding on a five-character caption and a 27-character address,
   and came out 10.2% filled — the exact failure §3 names, on the page's
   single most important element. It is now the sticky rail's whole job on
   desktop, and it holds the address, what happens after you send, and how
   long to wait: the three things a person about to write actually needs.

   2026-08-31: ground changed from --inset to --page with a --hairline
   border, same move and same reasoning as .rail above (round 2c's
   page-ground call, one level down) — kept for whenever this component is
   used un-nested. On /support today it is always nested in .rail, where
   `.rail .contact` below already zeroes this fill and border back out and
   uses a plain --sep divider instead, so the grey the owner saw here was
   never this rule's own fill — it was .rail's, showing straight through.
   The actual visible fix for /support is .rail's background/border change
   above; this rule just stops being a second, unreachable grey definition
   that would mislead the next person reading it. */
.contact{
  background:var(--page);
  border:1px solid var(--hairline);
  border-radius:14px;
  padding:calc(var(--rhythm) * 2);
}
/* CORRECTED, and the paragraph it corrects is left below because its reasoning
   is right about WHY breaking an address is ugly and wrong about whether this
   panel can avoid it. "The panel is 18rem wide because that is what holds it"
   was true at exactly one font size. 18rem here is pinned by the 320px
   viewport while the text scales with the reader's own preference, so the two
   diverge the moment that preference moves:

     Chrome font setting   box    text    over
     16px (default)        238px  238px    0px
     20px (Large)          238px  264px   26px
     24px (Very Large)     238px  313px   75px
     32px (custom minimum) 238px  417px  179px

   And .rail carries overflow-y:auto, which computes overflow-x to auto as
   well, so the excess became a horizontal scroll with --hide-scrollbars
   showing no scrollbar: the panel read as `contact@relationalnotes` with the
   `.com` simply absent. Not lost, but with no ellipsis and no affordance, an
   address that is silently wrong for the reader most likely to have raised
   their text size, on the page whose only job is getting somebody to write.

   So the rail now carries the same single <wbr> after the @ as the prose
   copies. `contact@` / `relationalnotes.com` on two lines is precisely what
   the paragraph below calls "an address that reads as damaged". That was a
   fair description. It is still better than an address that is wrong.

   Measured after: 0px over at 16, 20 and 24, which is every preset Chrome's
   UI offers. 42px at a custom 32px minimum, where `relationalnotes.com` alone
   is 280px and no 238px panel can hold it; there it scrolls inside the rail
   with the address intact, which is the last honest option before breaking a
   domain in half.

   The general lesson, and it is the reason this is written out rather than
   patched: THE PARAGRAPH BELOW RECORDED A WIDTH AS A GUARANTEE. A width only
   guarantees anything against a fixed font size, and nothing here said so.
   Round 17 read it, believed it, and deliberately fixed the prose copies while
   leaving this one alone, citing it. A comment that states a conclusion
   without the condition it holds under does not merely go stale; it actively
   steers the next fix away from the instance that needs it.

   THE ADDRESS IS SET AT THE BODY STEP, NOT THE SUB STEP, AND IT NEVER BREAKS.
   Changed at convergence. `overflow-wrap:anywhere` plus a `<wbr>` in the
   markup used to let this wrap, and what it produced was
   `contact@` / `relationalnotes.com` — an address that reads as damaged on
   the one page whose entire job is getting somebody to write to it. Breaking
   an email address is never the graceful degradation it looks like, so both
   break opportunities are gone: no `<wbr>` in support.html, no
   `overflow-wrap` here, and `hyphens:none` so no engine invents one. The
   panel is 18rem wide because that is what holds it (see .doc).
   This is a demotion in size only, and only of a duplicate: the same address
   is in the page's first sentence, in prose, at the sub step. The rail's job
   is persistence, not prominence — and at 17/600 among 13px definition text
   it is still the largest thing in the panel. */
/* THE PROSE INSTANCE GETS THE OPPOSITE RULE, and the two are not in conflict.
   The rail below never breaks because its panel is 18rem and 18rem holds the
   address. Prose carries no such guarantee: at 320px with Chrome's font-size
   setting one notch above default, the unbreakable token pushed support.html
   12px sideways, and 74px at Very Large, 23% of the viewport, on the page a
   confused user reaches when something is already wrong.

   So the prose copies carry exactly ONE break opportunity, a <wbr> after the
   @, and no other. `overflow-wrap` stays off and `hyphens:none` holds, so no
   engine may invent a break anywhere else. When the line fits, nothing
   happens; when it cannot, it breaks at the one place a reader still parses as
   an address rather than as damage.

   This is the seam round 17 named: the same token, decided in one place and
   unhandled in the other. Both places now have a policy, and they differ
   because their containers differ. */
.prose a[href^="mailto:"],.doc__sections a[href^="mailto:"],
.doc__sections a[href^="https://reportaproblem"],.contact__addr,
.foot a[href^="mailto:"]{
  hyphens:none;
  /* LAST-RESORT BREAKING, AND NO `<wbr>` ANYWHERE. Round 17 gave every prose
     copy of the address one `<wbr>` after the @, so a break would land at the
     one place a reader still parses as an address. Round 26 found that a
     `<wbr>` is a GREEDY break opportunity and removed it from the 404 page,
     where a fixed column made it fire at every desktop width. Round 30 found
     the reason to remove it everywhere.

     `<wbr>` INJECTS A SPACE INTO THE ACCESSIBLE NAME. Measured through
     Chrome's accessibility tree: ten of eleven mailto links reported
     `contact@ relationalnotes.com`, and the one that did not was the 404 copy
     round 26 had already stripped. Isolated outside all site CSS, with a
     control — an empty `<b></b>` in the same position injects nothing, so the
     effect is `<wbr>` specifically. A screen reader was reading the address
     with a space in the middle of it, on the two pages whose entire job is
     getting somebody to write to us.

     `break-word` gives the same protection without the markup: it breaks only
     when the token cannot otherwise fit, so the address is whole at every
     reading width AND at every font size Chrome's UI offers, and gives way
     THE FOOTER COPY IS IN THIS LIST BECAUSE IT WAS THE FOURTH TIME. This one
     string has been swept into a single policy three times — rounds 17, 26 and
     30 — and each sweep missed a copy. The footer's link carries no class and
     lives in `.foot`, so a selector list built from `.prose`, `.doc__sections`
     and `.contact__addr` walked straight past it on all four pages. Measured at
     320px: 5px of page overflow at a 30px root and 25px at 32, and privacy from
     28px, while every other copy on the same page stayed inside because it had
     the rule.

     It sat just past the ceiling the gate tests, and this file has already been
     wrong once about exactly that kind of ceiling. Enumerate the instances from
     the STRING, not from the selectors you happen to have written.

     only past those. The cost is that its break lands at an arbitrary
     character rather than at the @, and round 31 measured where: at Chrome's
     Large preset the prose copies break `contact@relationalnotes.c` / `om`,
     two characters from the end. THIS PARAGRAPH PREVIOUSLY CLAIMED THAT WAS
     "a case Chrome's settings cannot even reach" AND THAT WAS WRONG — 20 and
     24 are two of the three presets this file's own table lists.

     It is still the right trade, and the alternative is now measured rather
     than assumed. Without a break opportunity the page scrolls sideways at
     those same presets, which is the r17/r19 defect. And there is no third
     option: `display:inline-block` on the domain half was tested as a way to
     put the break at the @ without markup, and Chrome's accessible-name
     computation injects a space at an atomic-inline boundary exactly as it
     does for `<wbr>` — measured `contact@ relationalnotes.com` for both, and
     clean for both plain text and `break-word`. A chosen break point and an
     uncorrupted spoken address cannot both be had here. */
  overflow-wrap:break-word;
}
/* AND THE CODE SPANS, which are the same problem wearing a different tag. The
   privacy page names vendors and API calls inline — `ingest.de.sentry.io`,
   `eu.i.posthog.com`, `Purchasing.start()` — and a hostname is exactly the
   shape this whole policy exists for: one long token with no space in it.
   Measured at 320px with a 32px root, they pushed the page 44px and 26px
   sideways while every mailto on the same page stayed inside, because the
   mailtos had the rule and these did not.

   Fifth instance of one policy missing a copy. The first three were missed by
   SELECTOR, the fourth by FILE, this one by TAG. The lesson has been the same
   every time and is worth stating once more in the terms that would have caught
   all five: enumerate from the SHAPE — every long unbreakable token a reader
   can meet — not from the elements you already thought of.

   A MEASURED LIMIT, NOT A FIX, AND ROUND 34 LOOKED AT WHAT THE BREAK ACTUALLY
   SAYS. At a custom root font of 27px or more AND a width of 320-375px — past
   every preset Chrome's UI offers, reachable through an OS minimum-font setting
   — the break lands mid-token and two of the three cases produce a fragment
   that reads as something else:

     eu.i.posthog.com   -> `eu.i.posthog.co` / `m.`      .co is a real TLD
     ingest.de.sentry.io -> `ingest.de.sentr` / `y.io.`   splits "Sentry"
     Purchasing.start()  -> `Purchasing.star` / `t()`     leaves the word "star"

   The first is the one that matters: on a page whose whole premise is naming
   the exact host, a reader can skim a domain that is not the one receiving
   their data.

   There is no lever. CSS has no property that chooses WHERE an overflow break
   lands — `break-word` and `break-all` both cut at the last character that
   fits, and the point moves with the width, so it cannot be designed around.
   `<wbr>` after the dots would choose it and injects a space into the spoken
   text, which is the defect round 30 removed. `display:inline-block` makes the
   token atomic, which restores the 50px sideways scroll AND corrupts the spoken
   text the same way. Shrinking `code` below its .94em penalises exactly the
   low-vision reader who asked for larger text.

   So: a 50px sideways scroll for everyone at that setting, or a misleading
   fragment for the same people. Kept the fragment, wrote down the numbers, and
   left the alternative here so the next round does not re-derive it. */
/* THE SIXTH, AND THIS TIME THE RULE GOES ON THE CONTAINER SO THERE CANNOT BE A
   SEVENTH. `relationalnotes.com` sits in privacy.html as BARE PROSE TEXT — no
   link, no code span, no element of any kind to hang a selector on. Every one
   of the five previous sweeps targeted an element type, so every one of them
   walked past it. Measured at 320px: 0px overflow through 36px root, then 32px
   over at 40 and 96px at 48.

   Five sweeps, five misses, and each by a different mechanism: three by
   SELECTOR, one by FILE, one by TAG, and this one by MARKUP — text with nothing
   to select. The right unit was never the element. It is the CONTAINER a reader
   reads in, because that is the only thing guaranteed to enclose every long
   token, including the ones written as plain characters.

   `break-word` on a text container costs nothing for ordinary prose: it breaks
   a word only when the word cannot otherwise fit. The narrower element rules
   above are now redundant and kept only for their recorded reasoning. */
.prose,.doc__sections,.foot p{
  overflow-wrap:break-word;
}

.prose code,.doc__sections code{
  overflow-wrap:break-word;
}

/* THE 404's COPY IS THE ONE PLACE THE POLICY ABOVE INVERTS, and round 26 caught
   it by measuring line boxes rather than trusting the rule. `<wbr>` is a NORMAL
   break opportunity, so the line-breaking algorithm takes it greedily to fill
   the line — it is not a last resort. Everywhere else that is fine, because the
   surrounding text only crowds the address at narrow widths, which is when the
   break is wanted. `.doc--plain` caps its column at 34rem and never grows, so
   on this page that sentence's wrap lands in the same bad place at 1024, 1440
   and 2560 alike: the address split as `contact@` / `relationalnotes.com` at
   every ordinary desktop width, which this repo's own record calls the worst
   defect its support page ever had.

   So here the `<wbr>` is gone and `overflow-wrap` does the job instead, because
   `break-word` breaks ONLY when the word cannot otherwise fit. Measured: whole
   at 320, 393, 1024, 1440 and 2560, and the only break left is at 320px with
   Chrome's Very Large text, where the column is 280px and the address is 313px
   and something has to give. The break is arbitrary rather than at the `@`, and
   that is the deliberate trade: round 17 preferred the `@` break because it
   assumed the break was the common case. On this page it was. Now it is not,
   and the rare ugly break buys a correct common one. Without it the same
   setting scrolled the page 27px sideways. */

/* AND THE SAME TREATMENT FOR A BARE URL IN PROSE, for the same reason and by
   the same evidence. `reportaproblem.apple.com` is 24 characters with no `@`
   to break at, and at a custom 32px font on a 320px viewport it was the whole
   page's remaining sideways scroll: 67px after the security address was fixed.

   A `<wbr>` was tried first and behaved exactly as round 26 recorded: it cut
   the overflow to 32px and split the URL across two lines at 1024, 1440 AND
   2560, because a `<wbr>` is a normal break opportunity and line breaking takes
   it greedily. A domain broken at every desktop width to save 35px at a font
   size Chrome's UI does not offer is a bad trade.

   `break-word` breaks only when the token cannot otherwise fit, so the URL is
   whole at every reading width and gives way only at the extreme. The break
   lands at an arbitrary character rather than a chosen one, which is the same
   deliberate trade the rule above makes. */

/* THREE ROUNDS, THREE POSITIONS ON THIS ONE ELEMENT, and the third is here
   because the second rested on a premise that turned out to be false.

   Round 30 put this element on `break-word` while removing every `<wbr>`.
   Round 31 took it back out, citing the older recorded policy: the rail is a
   fixed 18rem panel with `overflow-x:auto`, so a long address "scrolls inside
   the card with the address intact". Round 32 measured what is actually
   PAINTED, and that sentence is wrong:

     Chrome preset   card shows            hidden
     16px default    contact@relationalnotes.com   0
     20px Large      contact@relationalnotes.co    1 character
     24px Very Large contact@relationalnote        5 characters

   It does not scroll. It CLIPS, with no ellipsis, no fade and no scrollbar
   affordance, and `scrollLeft` stays 0 because focusing the link does not move
   it — the overflow lives outside the focused element's own rect, so the
   browser's focus-scroll has nothing to act on. A keyboard user can reveal it
   with arrow keys, and nothing tells them to.

   THAT IS WORSE THAN A VISIBLE BREAK. `contact@relationalnotes` reads as a
   complete address. A reader can copy it, send to it, and never learn why
   nothing arrived. A break across two lines is ugly and unmistakably one
   address; a silent truncation is tidy and wrong.

   So `break-word` it is, and the ugliness at two presets is the price. The
   general rule, worth more than this element: WHEN A CONTAINER CLIPS, "intact"
   IS A PROPERTY OF THE STRING, NOT OF WHAT THE READER CAN SEE. Round 31's
   check counted line boxes and passed while five characters were missing. */
.contact__addr{
  position:relative;
  display:block;
  margin:0 0 calc(var(--rhythm) * 1.6);
  font-size:var(--t-body);
  font-weight:600;
  letter-spacing:-.01em;
  line-height:1.25;
  hyphens:none;
  text-decoration:underline;
  text-underline-offset:.2em;
  text-decoration-thickness:1px;
  text-decoration-color:color-mix(in srgb,currentColor 45%,transparent);
}
/* Same hit-area rule as .foot__nav a and .anchor. This is the product's
   only contact route rendered as a link — 311x20 at 393px, under the
   floor — and the surrounding dl's metadata rows sit close enough above
   and below that a real padding box risked lapping into them; a generated
   ::after frame, out of flow, cannot. */
.contact__addr::after{
  content:"";
  position:absolute;
  top:50%;left:0;right:0;
  height:44px;
  transform:translateY(-50%);
}
.contact dl{
  margin:0;
  font-size:var(--t-meta);
  line-height:1.5;
}
.contact dt{
  font-weight:660;
  color:var(--ink);
}
.contact dd{
  margin:0 0 calc(var(--rhythm) * 1.1);
  color:var(--ink-2);
}
.contact dd:last-child{margin-bottom:0}


/* ------------------------------------------------------------------ todo
   Unfilled fields a stranger must not see on a live page. Loud on purpose,
   and kept while unused. */
.todo{
  display:inline-block;
  padding:1px 6px;
  border-radius:4px;
  background:var(--todo-bg);
  color:var(--todo-ink);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:var(--t-meta);
  letter-spacing:0;
}


/* ---------------------------------------------------------------- footer
   A designed footer set off by a rule, as the hero's is — not 13px of
   1.77:1 grey on nothing carrying the company's legal identity.
   2026-08-31: the "own ground" that set it off used to be an --inset fill;
   it is now --page with a --hairline top rule instead, the same round-2c
   reasoning as .rail and .contact above, applied to hero.css's matching
   footer in the same pass so the two footers stay the same object. */
.foot{
  background:var(--page);
  border-top:1px solid var(--hairline);
  /* 2026-09-01 — the gap ABOVE this rule was unified to 100px on both
     pages (see .doc__sections above: "the footer is shared chrome").
     The gap BELOW it, this padding-top, was left at clamp(40px,6vh,64px)
     — capping at 64 against hero.css's own footer, which uses a fixed
     calc(var(--rhythm) * 10) = 100px — so on verbatim identical footer
     copy the hairline sat at the centre of a 100/100 gap on index and a
     100/60 gap here. Matched to hero.css's value, which is also this
     rule's own reasoning ("the two footers stay the same object")
     finally applied to the one property it had missed. */
  padding:calc(var(--rhythm) * 10) 0 clamp(36px,5vh,56px);
}
/* 2026-09-01: FINDING — the footer's content started at the .pad box's own
   edge (208px at 1500 CSS px, against the .pad max-width in effect that day,
   1180px) while the rail, the intro and the body column all start 92px
   further in, at 300, because .doc is a narrower block (18rem + gap + 34rem)
   CENTERED inside .pad rather than flush with it. The footer, sharing one
   element with .pad instead of nesting inside it, never got that same
   centering pass, so its left margin quietly disagreed with the rest of the
   page by 92px on both pages, both themes.

   THE FIX reuses .doc's own numbers rather than inventing a new inset: this
   grid is now a separate element nested inside .pad (mirroring .pad > .doc),
   capped at the exact width .doc's two columns already resolve to — 18rem +
   the same column-gap clamp + 34rem — and centred with the same margin:auto.
   Centering an IDENTICAL width inside the SAME .pad box reproduces the SAME
   300px inset by construction, at every viewport, without a second formula
   to keep in sync by hand.

   THIS 300px SURVIVES .pad's LATER max-width CHANGE (1180 -> 1280, same
   day, see .pad above) UNCHANGED, and that is not a coincidence to
   re-verify by eye: centering a fixed-width box a second time inside a
   container that is itself centered in the viewport cancels the container's
   own width out of the arithmetic — doc_left = viewport/2 - doc_width/2
   either way. The 208px flush-edge number above is the one figure here that
   was specific to the old 1180px .pad and is retained as history, not as a
   currently reachable value: the flush-edge bug it measured was fixed by
   the centering rework in this same comment, before .pad's width ever
   changed. */
.foot__grid{
  display:grid;
  /* 2026-09-01 — was 1.5fr 1fr, unified to hero.css:1312's 1.4fr 1fr. This
     is the same footer component on both pages (see this file's own
     padding-top and .foot__wm comments above, both already matched to
     hero.css's value for that reason). Measured the computed column widths
     at 900, 1024, 1280, 1500 and 1720 CSS px on both pages: the ratio holds
     at exactly 1.400 on index and exactly 1.500 here at every one of those
     widths, so the two pages' different container widths were not forcing
     the split apart — the two stylesheets had simply never been asked to
     agree. */
  grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);
  gap:clamp(24px,4vw,64px);
  max-width:calc(18rem + clamp(32px,4.5vw,72px) + 34rem);
  margin:0 auto;
}
/* 2026-09-01 — the footer's own breakpoint, matching hero.css:1400's
   @media (max-width:759px) block (which the phone layout pass folds this
   same rule into). Before this, style.css had no breakpoint of its own for
   .foot__grid; the one column collapse rode inside the @media
   (max-width:899px) block above that stacks the WHOLE document's .doc
   grid, so between 760 and 899 CSS px — 768, 800, 810, 820, 834, 880 all
   included — a reader who left index's two-column footer at that same
   width arrived at a one-column footer 126px taller here, with the second
   column's width sitting empty on the right because nothing had moved into
   it. The document may keep stacking at 899; the footer is a separate
   component and decides for itself, at the same 760 index already uses. */
@media (max-width:759px){
  .foot__grid{grid-template-columns:minmax(0,1fr)}
}
/* 2026-09-01: FOUND — this was `.foot__wm{...}`, a single class, specificity
   (0,1,0). `.foot p` below is a class-plus-element selector, specificity
   (0,1,1), which is HIGHER regardless of source order, and this heading is
   a <p class="foot__wm"> — so `.foot p`'s font-size (--t-meta, 13px) was
   winning over this rule's own --t-body every time, on both legal pages.
   Measured effect: the "Relational" mark rendered at --t-meta (13px flat,
   the same figure at every breakpoint, since --t-meta doesn't move at the
   759px break) instead of the intended --t-body (17px desktop / 16px
   phone) — visibly smaller than hero.css's own footer wordmark, which
   scopes the equivalent rule as `.foot .foot__wm` and never had this
   defect. Scoped the same way here, which is the fix: one more type
   selector than `.foot p`, so this rule wins on its own merits instead of
   by relying on `.foot p` never being tightened. */
.foot .foot__wm{
  font-size:var(--t-body);
  font-weight:600;
  letter-spacing:-.01em;
  margin:0 0 calc(var(--rhythm) * 1.2);
  color:var(--ink);
}
.foot p{
  color:var(--ink-2);
  font-size:var(--t-meta);
  line-height:1.6;
  max-width:40rem;
  /* 2026-09-01 — was rhythm*1.1 (11px), a multiplier used nowhere else in
     either stylesheet for this job. hero.css's own footer renders every
     one of its four left-column gaps at 12px (--rhythm*1.2, the same
     "normal item gap" this file already uses everywhere else — see
     .claim's own margin-bottom, .rail__cap's, .contact's), because its
     .foot p + p margin-top collapses cleanly with .foot .foot__wm's
     margin-bottom. This file uses margin-bottom on every item instead of
     margin-top on the next one, so there is no collapse to hide behind:
     1.1 landed short by a whole pixel, on every one of the three gaps
     after the wordmark, on both legal pages. Matched to 1.2, the value
     the wordmark rule two lines up already uses, so the four items read
     as one uniform rhythm instead of 12/11/11. */
  margin:0 0 calc(var(--rhythm) * 1.2);
}
.foot p:last-child{margin-bottom:0}

.foot__nav{
  display:flex;
  flex-direction:column;
  /* 2026-09-01 — was rhythm*1.6 (16px); hero.css's own .foot__nav uses
     rhythm*1.2 (12px), the same "normal item gap" value the rest of this
     footer already agrees on (see .foot p above). 1.6 is a real value used
     elsewhere in both files (heading margins, .rail__cap--after), just not
     for this job — it was never argued for the footer nav specifically, it
     just never got checked against hero.css's matching rule. Matched so
     the two footers' nav columns are textually the same rule, not two
     rules that happen to look similar. */
  gap:calc(var(--rhythm) * 1.2);
}
/* **The same fix hero.css got, and this file needed it too.** These were
   painted --ink, the same flat black as the non-interactive footer heading
   beside them, with only a :hover cue that a touch never triggers. --link is
   the token this file already defines for exactly this and clears AA on both
   grounds. Found because a craft round captured only the home page, so the
   legal pages kept the defect after it was fixed next door. */
.foot__nav a{
  font-size:var(--t-body);
  font-weight:600;
  color:var(--link);
  text-decoration:none;
  /* **The hit area, which a screenshot cannot see.** The anchor wraps its own
     description span, so it is two lines tall, which lands near 44pt but not
     reliably above it. This guarantees it. A 44pt minimum was already missed
     once on this project, on a paywall close control, and the reason it was
     hard to catch is that a hit frame has no pixels. */
  display:block;
  min-height:44px;
}
/* F1, 2026-09-01 — was var(--blue), a token this file never defines, so the
   var() was invalid at computed-value time and color (inherited) fell back
   to --ink: the hover state re-painted the link the same flat black as the
   non-interactive .foot__wm beside it, exactly the condition the rule above
   exists to remove. hero.css had the same intent with a token that DOES
   exist there, and it fails AA anyway — #007AFF is 4.02:1 on white, under
   the 4.5:1 floor — so this is not a value to port, only the fix.

   SECOND PASS, same day — a colour equal to the resting colour is a hover
   state that does nothing, which is what shipped first: --link is already
   this link's resting colour, so pointing the hover at --link too changed
   nothing visible at all. Underline-on-hover instead, built to this file's
   own already-established spec (.prose a/.rail__note a/.foot a, F2, just
   above) rather than a fourth treatment: this link has no decoration at
   rest, so this is additive, not a redraw, and text colour never moves —
   still --link's 7.89:1 / 8.00:1 in both cases. */
.foot__nav a:hover{
  text-decoration:underline;
  text-underline-offset:.22em;
  text-decoration-thickness:1px;
  text-decoration-color:color-mix(in srgb,currentColor 40%,transparent);
}
.foot__nav span{
  display:block;
  margin-top:3px;
  font-size:var(--t-meta);
  font-weight:400;
  line-height:1.5;
  color:var(--ink-2);
}


/* ========================================================== THE PHONE PASS
   One column, in DOM order: title, date, lede, claim, contents, policy.
   The rail keeps its panel and its links and its ids — it is the same
   element, re-placed, not a second copy — and gains a <summary> so a
   reader who does not want a fourteen-item index can fold it away. It is
   open by default because on a document 6.1 screens tall the index is
   worth more than the ~0.3 screen it costs, and because a closed
   disclosure is a thing a worried reader has to guess is there.
   ========================================================================== */
@media (max-width:899px){
  .doc{
    grid-template-columns:minmax(0,34rem);
    grid-template-rows:none;
    justify-content:center;
    row-gap:calc(var(--rhythm) * 2.6);
  }
  .doc__head{grid-column:1;grid-row:auto;padding-top:calc(var(--rhythm) * 3)}
  .rail{grid-column:1;grid-row:auto;position:static;margin-top:0}
  .doc__sections{grid-column:1;grid-row:auto}

  .rail summary{
    position:relative;
    display:block;
    list-style:none;
    cursor:pointer;
    font-size:var(--t-cap);
    font-weight:600;   /* matches .rail__cap/.caption, see their 2026-09-01 comment */
    letter-spacing:.075em;
    text-transform:uppercase;
    color:var(--ink-3);   /* F5, round 4 craft: matches .rail__cap/.caption */
  }
  /* R10 — the disclosure toggle had cursor:pointer and nothing else: no
     rule in this file named `.rail summary:hover` at all, so the one
     control the phone layout exists to provide answered a pointer with a
     cursor change and no visible cue. `.rail ol a:hover` two rules up
     (this same component's own links) already does exactly this move on
     the same muted --ink-3 resting colour; reused rather than invented. */
  .rail summary:hover{color:var(--link)}
  .rail summary::-webkit-details-marker{display:none}
  .rail[open] summary{margin-bottom:calc(var(--rhythm) * 1.6)}
  .rail summary::after{content:" \25BE"}
  .rail[open] summary::after{content:" \25B4"}
  /* The same hit-area rule as .foot__nav a and .anchor, on the one control
     the phone layout exists to provide: this measured 311x16.5 at 393px,
     the shortest box in the whole finding. ::after already carries the
     disclosure glyph, so the frame goes on ::before instead — a generated
     box, out of flow, so it cannot move the summary's own row or the
     rhythm-gap .rail[open] summary already sets before the list. */
  .rail summary::before{
    content:"";
    position:absolute;
    top:50%;left:0;right:0;
    height:44px;
    transform:translateY(-50%);
  }
  /* the summary already says "Contents"; the desktop caption would be a
     second copy of the same word directly beneath it */
  /* THE DESKTOP HEIGHT CLAMP MUST NOT SURVIVE INTO THIS LAYOUT. `.rail` carries
     `max-height:calc(100vh - rhythm*6)` with `overflow-y:auto` and
     `overscroll-behavior:contain`, all added to fit the STICKY desktop card to
     the window. This block already resets `grid-column`, `grid-row`,
     `position` and `margin-top` — and never reset those three, so a fixed cap
     followed the card into the in-flow phone layout where it is open by
     default and has no reason to be capped at all.

     Measured content hidden inside the card's own scroll, with
     `overscroll-behavior:contain` still in force, so a finger scrolling the
     card cannot chain out to the page:

       375x667 support   87px hidden      (iPhone SE, portrait)
       320x568 support  264px hidden
       812x375 support  341px hidden      (phone landscape)
       812x375 privacy  139px hidden

     The standing gate could not see it because every mobile probe it runs
     fixes height at 900, where `100vh - 60` always exceeds the content and the
     clamp never engages. The defect lives on the axis nothing varied. */
  .rail{max-height:none;overflow-y:visible;overscroll-behavior:auto}
  .rail__cap:not(.rail__cap--after){display:none}
  .rail > ol{margin-top:calc(var(--rhythm) * 1.2)}
  .rail__cap--after{display:block;margin-top:calc(var(--rhythm) * 1.6)}

  /* design/system.md §3 hunts dead space explicitly. On a phone the rail
     sits between the head and the first section, so the section's own
     top margin lands on top of the grid row-gap and reads as a hole. */
  .doc__sections > .sec:first-of-type{margin-top:calc(var(--rhythm) * 1.4)}
}

@media (min-width:900px){
  .rail[open] summary{display:none}
  /* F1, round 10 craft — the trap: `open` is DOM state, not a media query,
     so it survives a resize. Collapse the disclosure below 900px, widen
     past this breakpoint without reloading, and — before this rule — the
     rail was a 288x38 empty sticky card with no way back: the only control
     that could reopen it was `summary`, and the old unconditional
     `.rail summary{display:none}` up here removed it regardless of `open`.

     The tempting other shape — make the CONTENT immune to `open` up here,
     leaving `summary` hidden either way — does not work, and not for a CSS
     specificity reason: a closed `<details>` is spec-hidden. Overriding
     `display` on its children changes their computed style (verified:
     `display:block`, real geometry, non-zero `getBoundingClientRect()`)
     but `checkVisibility()` still returns false and `elementFromPoint`
     still resolves to the ancestor `<div class="doc">`, because
     `Element.checkVisibility()` and hit-testing both carry a hardcoded
     rule for "inside a closed `<details>`, and not its first `<summary>`"
     that no author stylesheet reaches. `<summary>` is the one element the
     platform always keeps interactive on a closed `<details>` — which
     makes it the only working escape hatch, not a matter of preference.

     So: hide `summary` only while `[open]` — the normal load state, so the
     plain always-visible panel round 4 built is completely unchanged for
     every reader who never touches the toggle. The instant `open` is
     absent at this width (only reachable by resizing down, closing it,
     and resizing back up), `summary` reappears with the same styling and
     44px hit area the phone layout already gives it, so there is always a
     click that leads back to `[open]` — native `<details>` behaviour, no
     script. Verified against the real sequence (collapse at 800px, widen
     to 1500px, no reload): before, 0 of 7 links on /privacy and 0 of 9
     items on /support answered `checkVisibility()`; after this rule the
     reopened summary is itself visible and clickable, and clicking it
     (`.open = true`) restores every link to `checkVisibility(): true`. */
  .rail:not([open]) summary{
    position:relative;
    display:block;
    list-style:none;
    cursor:pointer;
    margin-bottom:calc(var(--rhythm) * 1.6);
    font-size:var(--t-cap);
    font-weight:600;
    letter-spacing:.075em;
    text-transform:uppercase;
    color:var(--ink-3);
  }
  .rail:not([open]) summary:hover{color:var(--link)}
  .rail:not([open]) summary::-webkit-details-marker{display:none}
  .rail:not([open]) summary::after{content:" \25BE"}
  /* same generated 44px hit-box as the phone layout's `.rail summary::before`
     — this row measures 311x16.5 at rest, same shortfall, same fix. */
  .rail:not([open]) summary::before{
    content:"";
    position:absolute;
    top:50%;left:0;right:0;
    height:44px;
    transform:translateY(-50%);
  }
}


/* ------------------------------------------------------------------ misc */
@media (prefers-reduced-motion:no-preference){
  /* R10 — .foot__nav a dropped from this list: its hover no longer changes
     colour (see .foot__nav a:hover, F1 second pass), it gains an underline,
     and text-decoration-line is not an animatable property in any browser
     — there is nothing left here for it to transition. .rail summary
     added: it now has a real colour hover (just above) that deserves the
     same timing every other hover on this page gets. */
  .rail a,.rail summary,.mast__nav a,.anchor,.wordmark{
    transition:color var(--out) var(--ease),border-color var(--out) var(--ease);
  }
  .rail a:hover,.rail summary:hover,.mast__nav a:hover,.anchor:hover{
    transition-duration:var(--in);
  }
}

/* Nothing on this page moves on its own, so under `reduce` there is
   nothing to switch off. The rule above is the whole motion budget. */

@media print{
  .mast,.rail,.skip,.anchor{display:none}
  /* NOTHING THAT READS AS ONE THING MAY BE SPLIT ACROSS TWO SHEETS.
     Before this rule the site had no fragmentation policy at all: `grep -n
     break-inside site/*.css` returned nothing, on four pages that all print.
     What that produced on 404.html was a footer link severed from its own
     description — "Home" as the last line of page 1, "What Relational is." as
     the first line of page 2, a dangling heading above an unlabelled fragment.

     The other three pages were not safe, only lucky: the same markup and the
     same missing policy, with content that happened to put the break somewhere
     harmless. One added paragraph moves it. So this is written as a rule about
     which boxes are indivisible, not as a patch on the one that broke.

     THE LIST, NOT ONLY EACH ITEM. The first version of this rule protected
     `.foot__nav a` and stopped there, which fixed the severed link that
     prompted it and left the nav itself splittable. Two pages later 404.html
     gained a third footer link, became the only page whose footer nav carries
     all three, and split: "Privacy" alone at the foot of sheet 1 with 33.3pt
     of unused space under it, "Support" and "Home" alone on sheet 2 with 90%
     of that sheet blank. A three-item list is one thing; so it is named here
     as one.

     Worth keeping as the reason this rule is written the way it is: a fix
     aimed at the instance that failed will hold exactly until the neighbouring
     instance changes size. The comment already said "nothing that reads as one
     thing", and the selector did not match the comment.

     `break-after:avoid` on headings is the same idea from the other side: a
     heading alone at the foot of a sheet is a promise the page does not keep
     until you turn it over.

     Related but distinct: hero.css resets .rv's transform for print, because a
     TRANSLATED box is monolithic and gets cut through the glyphs rather than
     broken between them. That is the engine refusing to fragment. This is us
     telling it not to. Both are needed; neither implies the other. */
  .foot__nav,.foot__nav a,.claim,.contact,.sec>figure{break-inside:avoid;page-break-inside:avoid}
  h1,h2,h3{break-after:avoid;page-break-after:avoid}

  /* AND THEY MUST STREAM IN READING ORDER. `position:relative` on these
     headings exists for the `:target` marker, a screen affordance that means
     nothing on paper. Its cost in print was invisible to every check this
     project owns: Chrome paints a positioned box after its in-flow siblings,
     so the PDF's content stream carried every h2 AFTER the body text of its
     own page while the rendered pixels stayed perfectly correct. On
     privacy.html that displaced all seven headings, on all seven sheets.

     Scope, measured rather than assumed: the PDF is tagged, its StructTreeRoot
     orders each H2 before its P siblings correctly, and Preview.app copies the
     text in the right order. What breaks is raw content-stream extraction,
     which is what most PDF ingestion does. Narrow, real, and free to fix,
     because the property has no job here once .anchor is display:none. */
  .sec>h2,.sec--sub>h3,.prose p[id]{position:static}

  .doc{grid-template-columns:minmax(0,1fr);padding:0}
  body{background:#fff;color:#000}
}
