/* legal.css — the fourteen legal pages, and with footer.css the only
 *             stylesheet any of them loads.
 *
 * WHY A THIRD STYLESHEET
 *
 * app.css is 236 KB: the terminal, its grid, its story rail, its charts. Every
 * page that IS the terminal pays that. subscribe.css is already a second file
 * because the commercial front door has no business carrying the terminal, and
 * these are a third thing again — LONG-FORM PROSE SOMEBODY HAS TO WORK THROUGH.
 * The Terms run to 22 numbered clauses and three schedules; the retention
 * schedule is a six-column table; the privacy notice carries two annexes and a
 * sub-processor list. Nothing on the pricing page is laid out for that, so what
 * is reused here is the palette and the masthead, and what is not is the
 * layout: one measured column, a contents list built from the h2s, tables that
 * survive a phone, and a print stylesheet, because a solicitor WILL print these.
 *
 * THE TOKENS BELOW ARE A MIRROR, NOT A FORK — the same rule, and the same
 * values, as subscribe.css. They are copied from app.css's FIRST :root and
 * [data-theme="light"] blocks (app.css re-declares --text-dim and --faint later
 * in its readability layer; build/test_subscribe.py's tokens() reads the first
 * block of each file, and so does subscribe.css's mirror, so this one matches
 * the first block too).
 *
 * COPIED WHOLE, NOT SUBSETTED TO WHAT THIS FILE USES. Three reasons:
 *   (a) the palette test counts the tokens two files SHARE and wants at least
 *       fifteen of them; trimming the mirror to the eleven or twelve colours a
 *       stylesheet happens to use is exactly how that margin disappears without
 *       anyone deciding to remove it;
 *   (b) footer.css declares no tokens of its own. It reads --mono, --grid,
 *       --panel2, --text-dim, --brand and --brand-l from whatever stylesheet
 *       the page did load, which on these pages is this one. A subset that
 *       dropped --panel2 would break the footer's bug-report chip on fourteen
 *       pages and nowhere else;
 *   (c) one whole copy means the same one-line patch applies to app.css,
 *       subscribe.css and this file when a colour moves, and a diff between the
 *       three mirrors is empty rather than "and legal.css also drops four".
 * --brand-d, --field, --footer, --up and --up-l are therefore declared and
 * unused here. That is the cost of (a)-(c) and it is deliberate; do not "tidy"
 * them away.
 *
 * IT IS PINNED. build/test_subscribe.py parses app.css and walks BOTH mirrors —
 * subscribe.css and this file — failing by name on any shared token that has
 * drifted from app.css. It used to walk only subscribe.css, which meant a
 * colour changed in app.css failed the suite, was fixed in subscribe.css, went
 * green, and left the fourteen legal pages painting links, the draft banner's
 * accent and the footer chip in last month's blue — on the only pages a
 * solicitor and a regulator are ever pointed at, with nothing anywhere saying
 * so. A copy nothing is watching is the precise failure the header of
 * subscribe.css was written about.
 *
 * THIS FILE IS PUBLISHED, COMMENTS AND ALL. `copy assets` in build/publish.sh
 * takes the directory whole, so every line of this header is served at
 * /assets/legal.css to anyone, with no key — the same as the pages it styles.
 * A comment in here is therefore public writing, and internal source filenames
 * do not go in it. This one used to name the internal risk register by its
 * exact filename in the paragraph above; build/test_legal.py has a case built
 * to stop precisely that, and the case passed, because the corpus it searched
 * was `os.listdir(ROOT)` filtered to *.html and a stylesheet is not an HTML
 * file. It now searches everything publish.sh actually deploys. Cite a thing
 * by what it is, not by where it lives.
 *
 * British English in anything a reader sees. Never hand-edit a ?v= stamp;
 * build/bump.py owns it.
 */

/* theme.js writes ◑ / ◐ (U+25D1 / U+25D0) into the theme button, and these
   pages load neither app.css nor subscribe.css where that face is declared.
   Without this the glyph matches no @font-face at all — Google serves Inter as
   subsets and none of its unicode-ranges covers these — so it falls through
   --sans to whatever the operating system supplies, at a different weight and
   baseline on every platform. That is how the deals table's disclosure caret
   came to render as a dot on 24 Aug 2026. See assets/fonts/README.md.

   THIS IS THE THIRD COPY of this declaration (app.css, subscribe.css, here),
   and it is a duplicated fact of the kind this repo keeps paying for. It is
   copied verbatim rather than narrowed to the two glyphs a legal page actually
   uses, so the three copies diff to nothing and can be lifted into one shared
   file the moment anybody is allowed to touch more than one stylesheet — which
   is exactly how footer.css came to exist. */
@font-face {
  font-family: "AXIOM Symbols";
  src: url("fonts/sym-noto.woff2") format("woff2");
  font-display: swap;
  unicode-range: U+25B4, U+25B8, U+25BE, U+25D0, U+25D1, U+26A1, U+2715, U+270D;
}

/* ---- the palette, mirrored ---------------------------------------------- */
/* :root carries the DARK values and [data-theme="light"] overrides them, which
   is backwards from the way it is usually written and is the house convention:
   the default is light, and each page's <head> sets data-theme before the first
   paint precisely so an external stylesheet never gets to paint dark and snap.
   Keep the shape — a page whose inline block is missing lands on dark, which is
   a visible bug rather than a silent one. */
:root {
  --bg:        #0A2032;
  --bg2:       #081B2B;
  --panel:     #0E2A3D;
  --panel2:    #13314A;
  --field:     #163952;
  --grid:      #1B374F;
  --grid-soft: #163045;
  --text:      #E9F1F8;
  --text-dim:  #A9BDCD;
  --faint:     #7A93A6;
  --brand:     #4D9BE0;
  --brand-l:   #5BA6E8;
  --brand-d:   #1D6BB4;
  --cont:      #D8944A;
  --up:        #1f9d6b;
  --up-l:      #3ec78f;
  --footer:    #071521;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sans: "Inter", "AXIOM Symbols", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --serif: "Newsreader", Georgia, "Times New Roman", serif;
}

[data-theme="light"] {
  --bg:        #FFFFFF;
  --bg2:       #F2F5F8;
  --panel:     #F2F5F8;
  --panel2:    #E6F1FB;
  --field:     #FFFFFF;
  --grid:      #D8E0E8;
  --grid-soft: #E9EEF3;
  --text:      #0A2032;
  --text-dim:  #5B6B7A;
  --faint:     #547392;
  --brand:     #2F6FAE;
  --brand-l:   #2F6FAE;
  --brand-d:   #1D6BB4;
  --cont:      #B06A1E;
  --up:        #1f9d6b;
  --up-l:      #178a5d;
  --footer:    #F2F5F8;
}

/* ---- page-local tokens, deliberately OUTSIDE the mirror ------------------ */
/* Its own :root block so the two above stay byte-identical to subscribe.css's
   and diff to nothing, and so the palette test — which reads the FIRST :root in
   each file — never sees a token app.css has no opinion about. subscribe.css
   keeps its one page-local token inside the mirror instead; this is the tidier
   of the two and the one to copy.

   THE MEASURE, ONCE. It is set on five different blocks (the title, the draft
   banner, the contents, the prose, the foot) and they must agree to the pixel
   or the document reads as five columns of slightly different widths stacked
   up. One declaration; see the note at .legal for how 38rem was arrived at. */
:root { --measure: 38rem; }

/* WHERE --faint MAY BE USED, measured against build/audit.py's own rule (WCAG
   AA: 4.5:1 for text under 18.66px bold / 24px). --faint clears it on --bg
   (4.95 light, 5.06 dark), on --bg2 (4.52 / 5.45) and on --panel (4.52 / 4.62)
   — and FAILS on --panel2 in both themes (4.43 light, 4.18 dark). Anything
   small sitting on --panel2 (inline code, table headings) is --text-dim or
   --text, never --faint. */

/* ---- base --------------------------------------------------------------- */
* { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  /* The contents list and the clause anchors ARE the navigation of a 22-clause
     document: a reader jumps from 16.3 to Schedule 2 and back. An instant jump
     between two pages of identical grey prose gives no clue whether the page
     moved up or down; the scroll does. Turned off under prefers-reduced-motion
     at the foot of this file, where it is the one thing that block is for. */
  scroll-behavior: smooth;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--brand); }
img { max-width: 100%; }

/* Focus is never removed, only restyled — same ring as the pricing page. The
   only pointer-free route through a 60-clause document is the keyboard. */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---- page shell and masthead -------------------------------------------- */
/* VERBATIM FROM subscribe.css, deliberately. These pages carry the same
   masthead as the pricing page and a reader moves between the two — a wrap that
   is 1120 on one page and 980 on the next makes the mark jump sideways on
   navigation. Second copy of the same ~30 lines; if a third page ever needs
   them, they follow the footer into a shared file rather than becoming a third
   copy. Edit them here and in subscribe.css together. */
.wrap { max-width: 1120px; margin: 0 auto; padding: 0 24px; }

.nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 20px 0 8px;
}
.nav-mark {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text);
  text-decoration: none; line-height: 1.3;
}
.nav-mark span { color: var(--brand); }
.nav-right { display: flex; align-items: center; gap: 8px; }
.nav-a {
  font-size: 13.5px; color: var(--text-dim); text-decoration: none;
  padding: 8px 12px; border-radius: 8px;
}
.nav-a:hover { color: var(--text); background: var(--panel); }
.nav-btn {
  font: inherit; font-size: 13.5px; cursor: pointer;
  padding: 8px 12px; border-radius: 8px;
  border: 1px solid var(--grid); background: transparent; color: var(--text-dim);
}
.nav-btn:hover { color: var(--text); border-color: var(--faint); }
/* THE ONE DIVERGENCE FROM THAT COPY, and it is a measured one. build/audit.py
   judges every control that is not an inline link in prose and wants a 44px
   tap target with a 40px floor; the shared rules give the theme button a 36px
   box (8 + 8 padding + a 13.5px line) and the nav links 35px, which is under
   it. min-height plus a centred line brings both to 40 without moving anything
   a pixel on desktop, where the padding still decides the shape. Worth
   back-porting to subscribe.css — the same two controls are the same size
   there, and this file is not the right place to fix that page. */
.nav-a, .nav-btn { min-height: 40px; display: inline-flex; align-items: center; }
/* The wordmark needs the same 40 and cannot have it the same way: it is an
   inline <a> holding a text node and a <span>, so min-height does not apply to
   it and turning it into a flex or grid box would make those two into separate
   items and break the wrap. Padding instead. It is measured at 390, which is
   the only width audit.py judges targets at and the width where the mark wraps
   to two lines: 29px + 12 = 41. On a desktop it stays 26 and is never judged,
   because a 26px target under a mouse is not the failure this rule is about. */
.nav-mark { padding: 6px 0; }

/* ---- the title block ---------------------------------------------------- */
/* .eyebrow — "TERMS", "PRIVACY" — is the fourth class build/legal_pages.py puts
   in this block and the type treatment is subscribe.css's, character for
   character, because it is the same device doing the same job one page away.
   Only the closing margin differs: 18px there sits under a centred hero, and
   here it is the first line of a left-aligned document head whose title
   follows immediately. */
.eyebrow {
  font-family: var(--mono); font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--faint); margin: 0 0 12px;
}
.legal-head {
  max-width: var(--measure);                 /* the document column; see .legal */
  margin: 0 auto;
  padding: 46px 0 0;
}
.legal-title {
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(29px, 5.4vw, 44px); line-height: 1.1;
  letter-spacing: -.02em; margin: 0 0 14px; text-wrap: balance;
}
/* "Version 1.0 · In force from 1 October 2026 · Last updated 4 September 2026".
   Mono because these are three data points rather than a sentence, and because
   a version stamp that shifts by a pixel when the month changes reads as
   sloppy on a document whose whole claim is that it is under control. It wraps
   to two or three lines on a phone, hence the leading. */
.legal-meta {
  font-family: var(--mono); font-size: 12px; line-height: 1.7;
  letter-spacing: .03em; color: var(--faint); margin: 0 0 4px;
}
.legal-head + .legal-draft { margin-top: 24px; }

/* ---- the DRAFT banner --------------------------------------------------- */
/* UNMISSABLE IS THE ENTIRE REQUIREMENT. Every document in legal/ opens with
   "DRAFT — FOR LEGAL REVIEW. Not yet in force. Do not publish before review.",
   and the cost of a reader missing it is that they act on an unreviewed
   contract. So: amber, the loudest hue the palette has that is not the red the
   terminal uses for a fall; a 5px rail; a tinted ground; and it prints (below).
 *
 * NO GENERATED TEXT. It would be one line to hang the word DRAFT off ::before
 * and it is not done, because the word is already in the document source. Two
 * copies of a fact drift, and the copy in CSS is the one nobody edits when the
 * banner text changes on the day these stop being drafts.
 *
 * THE COLOUR IS IN THE FRAME, NOT THE WORDS. --cont is #B06A1E in light, which
 * measures 4.27:1 on white — over the 3:1 large text needs and under the 4.5:1
 * small text needs. Borders and rails have no such floor, so the amber does the
 * shouting and every character in the box stays --text at full contrast. The
 * tint is mixed to an OPAQUE colour rather than laid on at an alpha, so it
 * composites identically wherever the banner is placed and gives audit.py's
 * contrast pass a real background instead of a stack to blend down. */
.legal-draft {
  background: var(--panel);                                   /* pre-color-mix fallback */
  background: color-mix(in srgb, var(--cont) 13%, var(--bg));
  border: 1px solid var(--cont);
  box-shadow: inset 5px 0 0 var(--cont);
  border-radius: 12px;
  padding: 18px 22px 18px 26px;
  margin: 0 auto 30px;
  max-width: var(--measure);
  color: var(--text);
  font-size: 15px;
  line-height: 1.6;
}
/* The only place on these pages where two tints meet: an inline <code> chip
   carries --panel2, which is blue-grey, and on the amber ground it reads as a
   stain rather than as code. The mono face is signal enough inside a three-line
   banner. */
.legal-draft code { background: none; padding: 0; }
.legal-draft :is(p, ul, ol) { margin: 0 0 8px; }
.legal-draft > :last-child { margin-bottom: 0; }
/* The first sentence is bold in the source of all fourteen documents, so the
   emphasis carries the shout: 19px at 700 also clears WCAG's large-text bar,
   which is what makes it safe to let this line take the amber if it is ever
   wanted. */
.legal-draft :is(b, strong) {
  display: inline-block; font-size: 19px; font-weight: 700;
  line-height: 1.35; letter-spacing: -.01em; margin-bottom: 2px;
}

/* ---- a blank still to be filled ----------------------------------------- */
/* [REGISTERED OFFICE ADDRESS], [SUPPORT EMAIL], [ICO REGISTRATION NUMBER] —
   there are sixty-odd of these across the set and every one is a thing that
   must be true before the page goes up. Amber, boxed, and never subtle: an
   unfilled blank that reads as ordinary prose is how one gets published.
   It survives print too, which a background alone would not: see @media print,
   where it asks for its ink explicitly AND takes a black border and an
   underline, because a reviewer's office printer may well have no amber. */
.fill {
  background: var(--panel2);                                  /* pre-color-mix fallback */
  background: color-mix(in srgb, var(--cont) 26%, var(--bg));
  color: var(--text);
  font-family: var(--mono); font-size: .88em; font-weight: 600;
  letter-spacing: .02em;
  padding: 1px 6px; border-radius: 5px;
  border: 1px solid var(--cont);
  /* A placeholder is one long unbreakable token in a narrow column; without
     this it is the thing that pushes a 390px page sideways. */
  overflow-wrap: anywhere;
}

/* ---- on-page contents --------------------------------------------------- */
.legal-toc {
  max-width: var(--measure); margin: 0 auto 40px;
  background: var(--panel); border: 1px solid var(--grid);
  border-radius: 14px; padding: 18px 22px 12px;
}
.legal-toc :is(h2, h3) {
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-dim);
  margin: 0 0 8px;
}
/* LIST MARKERS OFF, and this is not decoration. Every h2 in these documents
   carries its own number in its text — "## 7. Fees, VAT and payment" — because
   the numbering is part of the contract and a clause is cited by it. An <ol>
   marker would set that as "1. 7. Fees, VAT and payment" and, worse, would
   renumber itself if a clause were ever hidden. The number belongs to the
   document, not to the list. */
.legal-toc :is(ol, ul) { list-style: none; margin: 0; padding: 0; }
.legal-toc li { break-inside: avoid; }
.legal-toc a {
  display: block; padding: 9px 2px; min-height: 40px;
  font-size: 15px; line-height: 1.4; color: var(--text);
  text-decoration: none; border-bottom: 1px solid var(--grid-soft);
}
/* No underline here and underlines in the prose below, on purpose: WCAG's
   "not by colour alone" applies to a link inside a block of text, where nothing
   but the colour says it is a link. A contents list is unambiguously a list of
   links. It gets hover, focus and a 40px row instead — the row is what a thumb
   is aiming at, and audit.py exempts an <a> inside an <li> from its target
   check, so nothing would have caught it being 22px. */
.legal-toc a:hover { color: var(--brand); }
.legal-toc li:last-child a { border-bottom: 0; }
@media (min-width: 720px) {
  /* Two columns from 720 up. Twenty-two clauses in one column is 900px of
     contents before the reader reaches clause 1. */
  .legal-toc :is(ol, ul) { columns: 2; column-gap: 34px; }
}

/* ---- the prose body ----------------------------------------------------- */
/* THE MEASURE. --measure is 38rem = 608px at the 16px root, and it was counted
   rather than chosen: at 1440 in this stylesheet's own type, three long
   paragraphs come out at 69, 70 and 74 characters a line.
 *
 * NOT SET IN ch. The ch unit is the advance of a "0", which in Inter is about
 * 12% wider than the average lowercase character, so a column set at 68ch
 * measures nearer 78 real characters — which is what 40rem did on the first
 * pass, and it was measured and walked back. If this number is ever changed,
 * change it by counting characters on a rendered page, not by reasoning about
 * units. Long documents are both the case where the measure matters most and
 * the case where a full-bleed column is most tempting. */
.legal {
  /* THE COLUMN IS A TRACK, NOT A max-width, and the middle one of three:
       [full-start] 1fr [content-start] the measure [content-end] 1fr [full-end]
     Everything lands in `content` and sits at the measure. A table with five or
     more columns is given `full` and spans the whole of .wrap — 1072px on a
     desktop against the measure's 608 — which is the only way a six-column
     schedule is readable without a horizontal gesture on a screen that has the
     room to spare. Measured: in the 640px column the schedule's cells ran to
     about eight characters a line, which is a column of confetti.
   *
     The tracks are minmax(0, 1fr), NOT minmax(<anything>, 1fr): a fixed minimum
     on the outer tracks does not collapse on a phone, so the three of them add
     up to more than the viewport and the PAGE scrolls sideways instead of the
     table. Same reason every child gets min-width: 0 — a grid item defaults to
     min-width: auto and will refuse to shrink below its min-content, which for
     a wide table means it overflows the track and takes the page with it.
   *
     No 100vw anywhere. subscribe.css's pricing row breaks out with
     `width: calc(100vw - 48px)` and needs three media queries to stay on
     screen, because 100vw counts a scrollbar the viewport does not give you.
     This breaks out to the PARENT instead, which cannot have that bug. */
  display: grid;
  grid-template-columns:
    [full-start] minmax(0, 1fr)
    [content-start] min(100%, var(--measure)) [content-end]
    minmax(0, 1fr) [full-end];
  /* THE RHYTHM, ONCE. Grid does not collapse adjacent margins the way normal
     flow does — two 17px margins between paragraphs would ADD to 34 — so the
     vertical margins are zeroed on the direct children and the gap between any
     two blocks is this one number. A heading then only has to declare what it
     wants ON TOP of that, and an element nobody anticipated still lands in the
     rhythm rather than flush against its neighbour. */
  row-gap: 17px;
  font-size: 17px;
  line-height: 1.65;
  color: var(--text);
  /* Filenames, ICO numbers and mailto addresses run through this prose and a
     390px column has no room to be precious about them. */
  overflow-wrap: break-word;
}
.legal > * { grid-column: content; min-width: 0; }
.legal p { margin: 0; }
.legal :is(ul, ol) { margin: 0; padding-left: 26px; }
.legal li { margin: 0 0 8px; }
.legal li:last-child { margin-bottom: 0; }
.legal li > :is(ul, ol) { margin: 8px 0 0; }
.legal li::marker { color: var(--faint); }
.legal :is(b, strong) { font-weight: 600; color: var(--text); }
/* Underlined, unlike the contents list: these sit inside sentences, and in a
   document that will be read in both themes and printed in neither, colour is
   not allowed to be the only thing that says "this is a cross-reference". */
.legal a {
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  overflow-wrap: anywhere;
}
.legal a:hover { color: var(--brand-l); text-decoration-thickness: 2px; }

/* ---- headings ----------------------------------------------------------- */
/* ROOM TO BREATHE. These are the only landmarks in eight thousand words: the
   space above an h2 is what tells a reader scanning for clause 14 that they
   have arrived. Asymmetric on purpose — a heading belongs to what follows it,
   so the gap above is roughly three times the gap below. */
.legal :is(h2, h3, h4, h5, h6) {
  color: var(--text);
  letter-spacing: -.01em;
  /* Nothing on these pages is sticky, so this is breathing room at the top of
     the viewport after a jump rather than clearance under a bar. */
  scroll-margin-top: 24px;
  position: relative;
}
.legal h2 {
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(22px, 3.1vw, 27px); line-height: 1.22;
  /* + the 17px row-gap = 52px above, 17px below. See the rhythm note. */
  margin: 35px 0 0; padding-top: 18px;
  border-top: 1px solid var(--grid-soft);
}
.legal h3 {
  font-size: 18.5px; font-weight: 700; line-height: 1.3;
  margin: 18px 0 0;
}
/* h4 AND h5 AND h6, ONE TREATMENT. build/legal_pages.py demotes every heading
   inside a Schedule or an Annex by a level so the schedule's own title can be
   the h2 — `out_level = min(level + doc.shift, 6)` — so what is an h4 in the
   Markdown arrives as an h5 on the page. The demotion is structural, not a
   change of meaning, and the three must therefore look alike. Left unstyled an
   h5 would also arrive with the browser's own 1.67em margins, which in this
   grid do not collapse into the row-gap: they add to it. */
.legal :is(h4, h5, h6) {
  font-size: 15px; font-weight: 700; line-height: 1.35;
  letter-spacing: .01em; margin: 10px 0 0; color: var(--text-dim);
}
/* A SCHEDULE IS NOT CLAUSE 23. build/legal_pages.py marks the heading that
   opens a Schedule or an Annex with .legal-part, and those are the seams of the
   document: Schedule 1 of the Terms is the Acceptable Use Policy, which is also
   published on its own. A heavier rule and more air above, so the eye reads it
   as a part break and not as the next clause along. */
.legal h2.legal-part {
  margin-top: 47px;                    /* + row-gap = 64px above */
  padding-top: 26px;
  border-top: 2px solid var(--grid);
  font-size: clamp(24px, 3.6vw, 30px);
}

/* A clause arrived at from a contents link or a deep link says so for a moment
   longer than the scroll takes. Cheap, and it is the difference between "did
   that work" and "here it is". */
.legal :is(h2, h3):target {
  box-shadow: inset 3px 0 0 var(--brand);
  padding-left: 14px;
  margin-left: -14px;
  border-radius: 2px;
}

/* ---- deep links: the anchor marker -------------------------------------- */
/* EVERY h2 GETS A STABLE ANCHOR — the id belongs to the generator (it must be
   derived from the clause number, not from the wording, or every deep link
   anybody has ever sent breaks the first time a heading is reworded). What
   belongs here is the marker that lets a reader COPY that link.
 *
 * Written against a shape rather than a class, because the generator's exact
 * markup is not fixed as this is written and a marker that only styles one of
 * them fails silently. Three shapes are handled:
 *   1. <h2 id="c7">7. Fees <a class="legal-anchor" href="#c7">#</a></h2>  ← preferred
 *   2. the same with an EMPTY <a href="#c7">, the marker drawn from CSS
 *   3. <h2 id="c7"><a href="#c7">7. Fees</a></h2>, the whole title as the link
 * Shape 3 is the dangerous one: styled as a marker it would hide the heading
 * text, so the rules below give it the heading's own colour and no decoration
 * and hang the marker off it instead. The generator should emit shape 1 with
 * an aria-label — "Link to this section" — which no stylesheet can supply. */
.legal :is(h2, h3) > a[href^="#"] {
  color: inherit;
  text-decoration: none;
  overflow-wrap: normal;
}
.legal :is(h2, h3) > a.legal-anchor,
.legal :is(h2, h3) > a[href^="#"]:empty {
  position: absolute; left: -0.85em; top: 0; bottom: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: .85em; padding: 0;
  font-family: var(--mono); font-size: .62em; font-weight: 400;
  color: var(--faint);
  opacity: 0; transition: opacity .14s ease;
}
/* Hash, not §. The section sign is the prettier mark and this site has already
   paid once for a character that was not in the served subset; # is ASCII and
   is in every face in --mono's stack. */
.legal :is(h2, h3) > a.legal-anchor:empty::after,
.legal :is(h2, h3) > a[href^="#"]:empty::after { content: "#"; }
.legal :is(h2, h3):hover > a.legal-anchor,
.legal :is(h2, h3):hover > a[href^="#"]:empty,
.legal :is(h2, h3) > a.legal-anchor:focus-visible,
.legal :is(h2, h3) > a[href^="#"]:empty:focus-visible { opacity: 1; }
.legal :is(h2, h3) > a.legal-anchor:hover { color: var(--brand); }
/* Shape 3 has no separate marker element to reveal, so it gets one drawn after
   the title text on the same hover. */
.legal :is(h2, h3) > a[href^="#"]:not(:empty):not(.legal-anchor)::after {
  content: " #"; color: var(--faint); font-family: var(--mono); font-size: .62em;
  opacity: 0; transition: opacity .14s ease;
}
.legal :is(h2, h3):hover > a[href^="#"]:not(:empty):not(.legal-anchor)::after { opacity: 1; }
/* A device with no pointer can never produce the hover that reveals it, and
   "copy a link to this clause" is exactly what somebody reading on a phone
   wants to do. Shown permanently there, quietly. */
@media (hover: none) {
  .legal :is(h2, h3) > a.legal-anchor,
  .legal :is(h2, h3) > a[href^="#"]:empty { opacity: .55; }
  .legal :is(h2, h3) > a[href^="#"]:not(:empty):not(.legal-anchor)::after { opacity: .55; }
}

/* ---- tables ------------------------------------------------------------- */
/* THE FAILURE CASE OF THE WHOLE SET, and it is 390px. The retention schedule
   is six columns; the cookie policy is five; a single cell in the records of
   processing runs to 300 characters of prose.

   THIS USED TO SCROLL THE <table> ITSELF with display:block + width:max-content
   + max-width:100%, because the class contract gave the generator no wrapper to
   put round it. That laid out correctly and the page never moved sideways — and
   it was wrong for a reason no screenshot shows. Measured in a browser:

       getComputedStyle(table).display                        -> "block"
       a11y tree, Annex A's "Provider" header cell            -> "generic"

   A <table> that is display:block is no longer a table box, and the browser
   drops the table role from the accessibility tree with it. Rows and cells keep
   table-row/table-cell so it LOOKS perfect, while a screen reader gets a flat
   run of text with no column association at all. On a set that publishes its own
   Accessibility Statement, that is not a defect to carry.

   So build/legal_pages.py now emits .legal-tablewrap round every table, the
   wrapper scrolls, and the table stays a table. The conditional behaviour is
   kept by min-width:100% against the cells' own 10em floor: a table whose
   columns fit stretches to the wrapper and never scrolls, and only one that
   genuinely cannot fit overflows it. THE PAGE ITSELF NEVER SCROLLS SIDEWAYS —
   build/audit.py measures that at 390 and reports it as an overflow. */
.legal-tablewrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* A flick that runs off the end of the table must not then drag the page. */
  overscroll-behavior-x: contain;
  margin: 7px 0 11px;                  /* + row-gap = 24 above, 28 below */
}
/* IT IS A TAB STOP, so say so when it lands. A scrollable box that cannot take
   focus hides whatever is off its right edge from anyone not using a mouse
   (WCAG 2.1.1), and one that takes focus silently is an unexplained stop. It is
   focusable unconditionally because a table that fits a desktop still scrolls on
   a phone, and the stylesheet cannot tell the generator which is which. */
.legal-tablewrap:focus-visible {
  outline: 2px solid var(--brand-l);
  outline-offset: 2px;
}
:where(.legal) table {
  width: auto;
  min-width: 100%;                     /* fill the wrapper when it can */
  border-collapse: collapse;
  font-size: 15px;
  line-height: 1.5;
}
/* FIVE COLUMNS OR MORE TAKES THE FULL WIDTH. Counted in CSS, off the fifth cell
   of a row, because the stylesheet has no other way to know a wide table from a
   two-column key/value list — and a key/value list stretched to 1072px next to a
   608px paragraph looks like a mistake. :has() is Chrome 105 / Safari 15.4 /
   Firefox 121; where it is missing the table simply stays in the prose column
   and scrolls, which is the same behaviour as a phone. Degrades, never breaks. */
:where(.legal) > .legal-tablewrap:has(table > * > tr > :nth-child(5)) { grid-column: full; }
/* NEITHER IS IN THE FOURTEEN DOCUMENTS AS THEY STAND — counted: not one fenced
   block among them, and a markdown table emits no <caption>. Both are styled
   anyway. build/legal_pages.py's Markdown subset carries fenced and indented
   code, so a <pre> is one edit away in a source file rather than one decision
   away in the generator; a caption is the accessible way to name a schedule and
   is the same distance off. Unstyled they would land on browser defaults — a
   centred caption, a grey full-bleed <pre> — that look nothing like this page. */
:where(.legal) caption {
  text-align: left; color: var(--text-dim); font-size: 13.5px;
  padding: 0 0 8px;
}
:where(.legal) :is(th, td) {
  /* THE FLOOR THAT DECIDES WHICH TABLES SCROLL. Without it the auto layout
     will happily give a six-column schedule 45px a column at 390 and set every
     cell one word to a line: no overflow, technically, and unreadable — the
     worst of the two outcomes.
   *
     10em, and the number is the padding's fault: box-sizing is border-box here,
     so a 6.5em floor spent 28px of its 98 on padding and left 70px — eight
     characters — which was measured and was no better than no floor at all.
     10em is 150px, about 17 characters of Inter at this size, which is where a
     cell reads as a phrase rather than a stack of words. It is also what
     decides WHICH tables scroll, and it scales itself: two columns floor at
     20em and wrap comfortably inside a 390px phone, four at 40em fit the
     608px desktop measure, and the six-column retention schedule floors at
     60em — 870px against the 358 a phone has — and takes the scrollbar it
     needs. */
  min-width: 10em;
  padding: 10px 14px;
  border-bottom: 1px solid var(--grid-soft);
  text-align: left;
  vertical-align: top;
}
:where(.legal) thead th {
  background: var(--panel2);
  color: var(--text);                  /* not --faint: it fails on --panel2 */
  font-size: 13.5px; font-weight: 700;
  line-height: 1.4;
  border-bottom: 1px solid var(--grid);
}
:where(.legal) tbody tr:nth-child(even) { background: var(--bg2); }
:where(.legal) tbody tr:last-child :is(th, td) { border-bottom: 0; }
:where(.legal) tbody th { font-weight: 600; }
/* THE ROW LABEL STAYS PUT. In a table wide enough to scroll, the first column
   is usually the only thing that says which row you are reading — scroll it
   away and the cookie policy is five columns of unattributed durations, and
   Schedule 3 of the Terms is a price list with no plan names. Counted across
   the set: of the three tables with five or more columns, two lead with a
   label (`Name`, `Plan`) and one leads with an index (`#` in the retention
   schedule), where the freeze costs a column's width and buys nothing. Two out
   of three is why it is here.
   The sticky cell needs an opaque ground of its own or the scrolled columns
   show through it, which is why the zebra row repeats its fill below. Harmless
   when the table is not scrolling: left: 0 with nothing to scroll never
   moves. */
:where(.legal) tbody :is(th, td):first-child,
:where(.legal) thead th:first-child {
  position: sticky; left: 0;
  background: var(--bg);
  border-right: 1px solid var(--grid-soft);
}
:where(.legal) thead th:first-child { background: var(--panel2); }
:where(.legal) tbody tr:nth-child(even) :is(th, td):first-child { background: var(--bg2); }

/* ---- callouts ----------------------------------------------------------- */
/* :where() so this weighs nothing in the cascade. The DRAFT banner is a
   blockquote in every source document, and if the generator emits it as
   <blockquote class="legal-draft"> then a plain `.legal blockquote` rule (0,1,1)
   would beat `.legal-draft` (0,1,0) and paint the loudest thing on the page
   like an ordinary aside. Zero-specificity here means the class wins on its own
   merits, with no !important anywhere. */
:where(.legal) blockquote {
  margin: 7px 0;                       /* + row-gap = 24. See the rhythm note. */
  padding: 14px 20px;
  background: var(--panel);
  border-radius: 10px;
  box-shadow: inset 3px 0 0 var(--brand);
  color: var(--text-dim);
  font-size: 16px;
}
:where(.legal) blockquote > :last-child { margin-bottom: 0; }
:where(.legal) blockquote :is(b, strong) { color: var(--text); }

/* ---- inline monospace --------------------------------------------------- */
/* :where() again, and for the same reason: a [PLACEHOLDER] is written as
   `[SQUARE BRACKETS]` in the sources, so the generator may well emit
   <code class="fill"> and .fill has to be able to win. */
:where(.legal) code {
  font-family: var(--mono);
  font-size: .86em;
  background: var(--panel2);
  color: var(--text);                  /* not --faint: it fails on --panel2 */
  padding: 1px 5px;
  border-radius: 5px;
  overflow-wrap: anywhere;
}
:where(.legal) :is(h2, h3, h4) code { font-size: .8em; background: none; padding: 0; }
:where(.legal) pre {
  background: var(--panel); border: 1px solid var(--grid); border-radius: 10px;
  padding: 14px 16px; overflow-x: auto; font-size: 13.5px; line-height: 1.5;
}
:where(.legal) pre code { background: none; padding: 0; font-size: inherit; }

/* ---- rules -------------------------------------------------------------- */
/* A --- in these sources separates the body of a document from a schedule or
   an annex, so it is a bigger gap than any heading and is allowed to look
   like one. */
.legal hr {
  border: 0; height: 1px; background: var(--grid);
  margin: 31px 0 27px;                 /* + row-gap = 48 above, 44 below */
}
.legal hr + :is(h2, h3) { margin-top: 0; border-top: 0; padding-top: 0; }

/* ---- foot of the document ----------------------------------------------- */
.legal-nav {
  max-width: var(--measure); margin: 56px auto 0; padding-top: 22px;
  border-top: 1px solid var(--grid);
  display: flex; flex-wrap: wrap; align-items: stretch; gap: 10px;
}
.legal-nav a {
  /* 44px, because these ARE judged: build/audit.py exempts an <a> whose parent
     is prose (P, LI, TD and the headings) and judges every other control
     against a 44px target with a 40px floor. Direct children of a <nav> are
     every-other-control. */
  min-height: 44px;
  /* THREE EQUAL BOXES, sharing the row and wrapping their OWN text. Left to
     size themselves, "← Records of processing", "All legal documents" and
     "DSAR & breach procedure →" measure 590px of the 608 the column has, and
     the next document along — "Sources, attribution and third-party rights" —
     is longer still: the row wrapped, and a wrapped flex row leaves the third
     link stranded at the left of a second line. Measured at 1440, where there
     was no shortage of room.
   *
     flex-basis 0, NOT auto — measured, because auto did not fix it. A flex
     container decides where to break the line from each item's hypothetical
     size and only shrinks within a line afterwards, so with basis auto the
     three long labels still wrapped and the third still landed alone on the
     second line. Basis 0 gives every item a hypothetical size of nothing: they
     always share one line, the box is a third of the column whatever the
     neighbouring documents are called, and the wrapping happens inside the
     label where a second line is ordinary. min-width: 0 and the break so a
     narrow third of a column cannot be forced open by one long word. */
  flex: 1 1 0; min-width: 0; overflow-wrap: break-word;
  display: inline-flex; align-items: center; justify-content: center;
  text-align: center;
  padding: 10px 16px;
  border: 1px solid var(--grid); border-radius: 10px;
  background: var(--panel); color: var(--text);
  font-size: 14.5px; line-height: 1.35; text-decoration: none;
}
.legal-nav a:hover { border-color: var(--brand); color: var(--brand); }
/* WHAT THE GENERATOR'S THREE CLASSES ARE USED FOR HERE. .is-all was going to be
   centred with margin-inline: auto, and build/legal_pages.py still says so —
   but auto margins absorb free space, and the equal-box row above leaves none:
   the hub link is in the middle because it is the middle item, so the rule
   would be a silent no-op and is not written. What is worth having is the
   direction: an arrow that points backwards should not be centred over a box,
   so prev and next sit against their own edges and the hub stays centred. */
.legal-nav .legal-prev { justify-content: flex-start; text-align: left; }
.legal-nav .legal-next { justify-content: flex-end; text-align: right; }
/* "Document 7 of 14 in the legal set" — a <p> among the links, so it needs a
   row of its own rather than a place in the queue. */
.legal-nav-note {
  flex: 0 0 100%; margin: 2px 0 0;
  font-size: 12.5px; line-height: 1.6; color: var(--faint);
}

/* ---- 390 ---------------------------------------------------------------- */
@media (max-width: 560px) {
  .wrap { padding: 0 16px; }
  .nav-a.is-hideable { display: none; }
  .legal-head { padding-top: 30px; }
  .legal { font-size: 16.5px; }
  .legal h2 { margin-top: 40px; }
  .legal-toc { padding: 16px 16px 10px; }
  .legal-draft { padding: 16px 16px 16px 20px; border-radius: 10px; }
  .legal-draft :is(b, strong) { font-size: 18px; }
  /* A table that is scrolling needs its cells to stop being generous with
     padding, or a third of the width it just took is whitespace. */
  :where(.legal) :is(th, td) { padding: 9px 11px; }
  :where(.legal) table { font-size: 14.5px; }
  /* The anchor marker sits in the gutter, and at 16px of page padding there is
     no gutter left. Move it inline after the heading. */
  .legal :is(h2, h3) > a.legal-anchor,
  .legal :is(h2, h3) > a[href^="#"]:empty {
    position: static; width: auto; margin-left: .35em; display: inline;
  }
  /* One per row on a phone, and in source order. No reordering to put "all
     legal documents" last: the sequence prev / all / next is the same sequence
     on both, and subscribe.css's plan cards already paid for the lesson that
     rearranging a list on a small screen breaks the reading a person is
     doing. */
  .legal-nav a { flex: 1 1 100%; }
}

/* ---- motion ------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* scroll-behavior is the one that matters here: a smooth jump across two
     thousand words is a long, fast slide, and it is exactly the pattern that
     provokes vestibular symptoms. The rest is the house's blanket rule. */
  html { scroll-behavior: auto; }
  * { transition: none !important; animation: none !important; }
}

/* ---- print -------------------------------------------------------------- */
/* A SOLICITOR WILL PRINT THESE, mark them up in the margin and send them back.
   That is the actual review workflow these fourteen documents exist for, so
   this block is not a courtesy. */
@media print {
  @page { margin: 18mm 15mm; }

  /* Chrome drops backgrounds and light-on-dark text when it prints, so a page
     left in dark theme prints as invisible grey on white. State it. */
  body { background: #fff !important; color: #000 !important; font-size: 11pt; }
  .wrap { max-width: none; padding: 0; }

  /* No chrome: the masthead and the site footer that footer.css draws (.ftr —
     the only footer these pages carry, which is why subscribe.css's .foot is
     not named here). The links at the foot go too; a prev/next you cannot click
     is furniture.
   *
     THE NOTE AT THE FOOT STAYS. "Document 7 of 14 in the legal set" is the one
     line in that block that means something on paper: a reviewer working
     through a printed stack has no other way to tell whether they are holding
     all of it. So the nav keeps its rule and loses its links. */
  .nav, .ftr { display: none !important; }
  .legal-nav a { display: none !important; }
  .legal-nav { border-top: 1px solid #000; padding-top: 8px; margin-top: 28px; }
  .legal-nav-note { color: #000; margin: 0; font-size: 9pt; }

  /* THE MEASURE IS KEPT ON PAPER. It is tempting to release it — the page is
     only ~680px wide inside these margins and the column is 608 — but the
     title block, the banner and the contents would then all be wider than the
     prose they introduce, and a printed contract whose masthead does not line
     up with its clauses reads as two documents stapled together. Only the wide
     tables take the full page, which they already do: they sit in the grid's
     `full` track, which is exactly as wide as the paper allows.
   *
     .legal stays a grid here. Chrome and Safari both fragment a single-column
     grid across printed pages, and every item that must not be split says so
     below. If a reviewer ever reports a clause broken in a stupid place, the
     remedy is `display: block` in this block plus `.legal > * + * { margin-top:
     17px }` to restore the row-gap by hand — but that is a second copy of the
     rhythm, so it is not done until something is actually observed to break. */
  .legal { font-size: 10.5pt; line-height: 1.5; color: #000; }
  .legal-title { font-size: 20pt; color: #000; }
  .legal-meta { color: #000; font-size: 9pt; }
  .legal :is(h2, h3, h4) { color: #000; }
  .legal h2 { font-size: 13pt; border-top: 1px solid #000; }
  .legal h3 { font-size: 11.5pt; }
  .legal :is(h2, h3):target { box-shadow: none; padding-left: 0; margin-left: 0; }

  /* Page breaks. A heading at the foot of a page is a heading in the wrong
     place, and a clause that starts with its last line stranded overleaf is
     how a printed contract gets misread. */
  .legal :is(h2, h3, h4, h5, h6) { break-after: avoid; page-break-after: avoid;
                                   break-inside: avoid; page-break-inside: avoid; }
  /* A Schedule starts on a fresh page, the way a Schedule does on paper — but
     never the FIRST thing in the body. The Acceptable Use Policy is published
     as a standalone copy of Schedule 1 and opens with that heading, and a
     forced break on the first box of the first page is the classic way to
     print a blank sheet before the document even starts.
     (The source filename is deliberately not named here. `copy assets` in
     build/publish.sh ships this stylesheet whole, so every word of it is
     served at /assets/legal.css to anyone with no key — see the note in the
     header about what does and does not belong in a published comment.) */
  .legal > h2.legal-part:not(:first-child) { break-before: page;
                                             page-break-before: always; }
  .legal :is(p, li) { orphans: 3; widows: 3; }
  .legal blockquote, .legal-draft, .legal-toc li { break-inside: avoid;
                                                   page-break-inside: avoid; }

  /* THE WRAPPER STOPS BEING A SCROLLER. On paper there is nowhere to scroll to,
     and an overflow box clips whatever is past its right edge — so the columns
     that were reachable on screen would simply be missing from the print, which
     is the worst of the three outcomes. Unset it and the table paginates: thead
     repeats on every page it spans, and no row is cut in half.
     (The table itself is display:table at every width now — see the note at the
     top of the tables section for the accessibility reason.) */
  .legal-tablewrap { overflow: visible; margin: 0; }
  :where(.legal) table { width: 100%;
                         font-size: 9pt; page-break-inside: auto; }
  :where(.legal) thead { display: table-header-group; }
  :where(.legal) tr { break-inside: avoid; page-break-inside: avoid; }
  :where(.legal) :is(th, td) { min-width: 0; border-bottom: 1px solid #999;
                               color: #000; padding: 6px 8px; }
  :where(.legal) thead th { background: none; border-bottom: 1.5px solid #000; }
  :where(.legal) tbody tr:nth-child(even) { background: none; }
  /* Sticky is meaningless on paper and leaves a stray rule down the page.
     The selector matches the screen rule's shape on purpose: written as
     `:where(.legal) :is(th,td):first-child` it is (0,1,1) against that rule's
     (0,1,2) and loses, which is how the frozen column survived into the first
     print test. Specificity does not care that one is inside @media print. */
  :where(.legal) tbody :is(th, td):first-child,
  :where(.legal) thead th:first-child { position: static; background: none;
                                        border-right: 0; }
  :where(.legal) tbody :is(th, td):first-child { font-weight: 700; }

  /* Callouts and code have to survive a mono laser printer with backgrounds
     off, so each keeps a black edge rather than a tint. */
  :where(.legal) blockquote { background: none; color: #000;
                              box-shadow: none; border-left: 2px solid #000;
                              border-radius: 0; padding-left: 14px; }
  :where(.legal) code { background: none; color: #000; padding: 0; }
  .legal-toc { background: none; border: 1px solid #000; }
  .legal-toc a { color: #000; border-bottom: 0; min-height: 0; padding: 3px 0; }

  /* THE TWO THINGS THAT MUST NOT FADE OUT ON PAPER. A printed draft that does
     not say DRAFT is the whole risk that banner exists for, and an unfilled
     blank that prints as ordinary prose is how one gets missed in review. Both
     ask for their ink explicitly — print-color-adjust is the only way to hold a
     background through a browser's print path — and both carry a black rule as
     well, because a reviewer's office printer may have no amber in it.
   *
     AND BOTH MIX OVER PAPER, NOT OVER --bg. A browser prints the theme the page
     is in; it does not swap the palette on the way to the printer. Printed from
     dark, `color-mix(--cont 13%, var(--bg))` is a near-black brown, and
     print-color-adjust: exact then holds it there — the banner came out as
     black text in a black box, measured in the first print pass. Restating the
     ground as #fff is the fix; --cont is still the token, and both themes'
     ambers land as a pale wash on white. */
  .legal-draft {
    print-color-adjust: exact; -webkit-print-color-adjust: exact;
    background: color-mix(in srgb, var(--cont) 16%, #fff);
    border: 2px solid #000; box-shadow: none; color: #000;
    padding: 12px 16px; margin-bottom: 18px;
  }
  .legal-draft :is(b, strong) { color: #000; }
  .fill {
    print-color-adjust: exact; -webkit-print-color-adjust: exact;
    background: color-mix(in srgb, var(--cont) 30%, #fff);
    color: #000; border: 1px solid #000; font-weight: 700;
    text-decoration: underline;
  }

  /* A link is dead on paper unless its address comes with it. Cross-references
     between these documents are the reason: "as set out in the Privacy Policy"
     has to be followable from a printout. In-page anchors (#) are excluded —
     they point at a clause the reader is already holding — and so is the
     heading anchor marker, which has no business printing at all. */
  .legal a { color: #000; text-decoration: underline; }
  .legal a[href^="http"]::after {
    content: " <" attr(href) ">";
    font-family: var(--mono); font-size: 8.5pt; word-break: break-all;
  }
  .legal :is(h2, h3) > a.legal-anchor,
  .legal :is(h2, h3) > a[href^="#"]:empty { display: none; }
  .legal :is(h2, h3) > a[href^="#"]::after { content: none; }

  /* EXPAND WHAT IS COLLAPSED. If the generator puts a schedule inside a
     <details>, a closed one prints as its summary and the schedule is simply
     absent from the printed contract. ::details-content is the standards-track
     hook for reaching it (Chrome 131+, Firefox 139+, Safari 18.4+); on engines
     older than that the content sits in a UA shadow slot that NO author rule
     can reach, so this is a genuine floor and not a lint. The generator should
     not put anything that must print inside a closed <details>. */
  details { display: block; }
  details > summary { display: block; font-weight: 700; list-style: none; }
  details::details-content { content-visibility: visible !important;
                             block-size: auto !important; }
}
