/* ==========================================================================
   components.css — EditingTools.pro
   Every shared UI part: buttons, inputs, cards, the readout panel, tabs,
   slots, header + mega menu, mobile menu, footer, hero.
   Nothing here contains a raw colour — all values come from tokens.css.
   ========================================================================== */

/* ==========================================================================
   1. BUTTONS
   ========================================================================== */
.btn {
  --btn-bg: transparent;
  --btn-fg: var(--text);
  --btn-line: var(--hairline-firm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: var(--s-3) var(--s-5);
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-line);
  border-radius: var(--r-input);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.btn:hover { border-color: var(--primary); }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.5; cursor: not-allowed; transform: none;
}

.btn--primary {
  --btn-bg: var(--primary);
  --btn-fg: var(--text-on-accent);
  --btn-line: var(--primary);
}
.btn--primary:hover { filter: brightness(1.08); }

.btn--ghost { --btn-line: transparent; --btn-fg: var(--text-muted); }
.btn--ghost:hover { --btn-fg: var(--text); background: var(--primary-soft); border-color: transparent; }

.btn--lg { min-height: 52px; padding: var(--s-4) var(--s-6); font-size: var(--fs-sm); }
/* 44px, not 36px. These are real controls — table pagination, copy link,
   print, reset — and the quality floor is 44x44 for every touch target, not
   only the primary ones. The padding stays small so they still read as
   secondary; only the hit area grows. */
.btn--sm { min-height: 44px; padding: var(--s-2) var(--s-3); font-size: var(--fs-2xs); }

.btn--icon {
  min-height: 44px; min-width: 44px; padding: 0;
  border-color: transparent;
}
.btn--icon:hover { background: var(--primary-soft); border-color: transparent; }
.btn svg { inline-size: 18px; block-size: 18px; flex: none; }

/* Chip — quick actions, filters, small links. */
.chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: 44px;
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-xs); font-weight: 500;
  text-decoration: none;
  transition: border-color var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.chip:hover { border-color: var(--primary); transform: translateY(-1px); }
.chip svg { inline-size: 16px; block-size: 16px; color: var(--primary-text); flex: none; }

.chip--static { cursor: default; background: var(--surface-2); }
.chip--static:hover { transform: none; border-color: var(--hairline); }

/* Badge / pill — country codes, category tags, "new". */
.pill {
  display: inline-flex; align-items: center; gap: 0.35em;
  padding: 0.15em 0.6em;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--primary-soft);
  color: var(--primary-text);
  white-space: nowrap;
}
.pill--violet { background: var(--violet-soft); color: var(--violet-text); }
.pill--amber  { background: var(--amber-soft);  color: var(--amber-text);  }
.pill--quiet  { background: var(--surface-2);   color: var(--text-faint);  }
.pill--signal { background: var(--signal-soft); color: var(--signal-text); }

/* Time zone planner — one row per city at the chosen hour. Grid rather than
   a table because it collapses to two lines on a phone without a horizontal
   scroller, and the "asleep" pill is the part that must stay readable. */
.zonelist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.zonelist__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s-2);
  min-block-size: 44px;
  padding: var(--s-2) 0;
  border-block-end: 1px solid var(--hairline);
}
.zonelist__row:last-child { border-block-end: 0; }
.zonelist__city { min-inline-size: 0; font-weight: 600; color: var(--text-strong); }
.zonelist__time { font-family: var(--font-mono); text-align: end; white-space: nowrap; }
.zonelist__meta {
  display: block;
  font-family: var(--font-body);
  font-size: var(--fs-2xs);
  font-weight: 400;
  color: var(--text-faint);
  letter-spacing: 0;
  text-transform: none;
}
@media (max-width: 480px) {
  .zonelist__row { grid-template-columns: minmax(0, 1fr) auto; }
  .zonelist__row .pill { grid-column: 1 / -1; justify-self: start; }
}

/* A whole-row tint in the overlap grid. Colour alone never carries the
   meaning — every row also spells the state out in words — so this stays
   decorative, and contrast is met by the text drawn on top of it. */
.table tr.row--work   { background: var(--signal-soft); }
.table tr.row--edge   { background: var(--violet-soft); }
.table tr.row--asleep { background: var(--amber-soft); }

/* ==========================================================================
   2. FORM CONTROLS
   ========================================================================== */
.field { display: grid; gap: var(--s-2); }
.field__label {
  font-size: var(--fs-xs); font-weight: 600; color: var(--text);
  display: flex; align-items: baseline; gap: var(--s-2);
}
.field__hint { font-size: var(--fs-2xs); color: var(--text-faint); }

.input, .select, .textarea {
  inline-size: 100%;
  min-height: 44px;
  padding: var(--s-3);
  background: var(--surface-inset);
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-input);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  transition: border-color var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out);
}
.textarea { font-family: var(--font-body); min-height: 120px; resize: vertical; }
.input:hover, .select:hover, .textarea:hover { border-color: var(--hairline-firm); }
.input:focus-visible, .select:focus-visible, .textarea:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-ring);
  outline: none;
}
.input[aria-invalid="true"] { border-color: var(--danger); }

/* Grid items default to min-width:auto, so a <select> with a long option or a
   number input with a wide value pushes its own column past the container.
   This is the same fix studio.css carries; the calculator pages need it too. */
.input, .select, .textarea { min-inline-size: 0; max-inline-size: 100%; }
.field { min-inline-size: 0; }

/* Checkbox and radio rows. These live here rather than in studio.css because
   the calculator pages use them too and do not load that stylesheet — a
   checkbox on a calculator page was rendering as a bare 13px browser default.

   min-block-size is the touch target: the <input> itself is 16px and always
   will be, so the LABEL is what a person actually hits, and it is what has to
   clear 44px. Without it a single-line option measured 26px. */
.choice { display: grid; gap: var(--s-2); }
.choice__opt {
  display: grid; grid-template-columns: auto 1fr; gap: var(--s-3);
  align-items: start;
  min-block-size: 44px;
  padding: var(--s-3);
  border: 1px solid var(--hairline);
  border-radius: var(--r-input);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out);
}
.choice__opt:hover { border-color: var(--hairline-firm); }
.choice__opt:has(input:checked) { border-color: var(--primary); background: var(--primary-soft); }
.choice__opt input { margin-top: 3px; accent-color: var(--primary); inline-size: 16px; block-size: 16px; }
.choice__label { display: block; font-size: var(--fs-xs); font-weight: 600; color: var(--text); }
.choice__note  { display: block; margin-top: 2px; font-size: var(--fs-2xs); color: var(--text-muted); }

.field__error {
  font-size: var(--fs-2xs); color: var(--danger); font-weight: 500;
  display: flex; align-items: center; gap: var(--s-1);
}
.field__error:empty { display: none; }

/* Input with a leading unit/currency affix */
.affix { position: relative; display: flex; align-items: stretch; }
.affix__mark {
  display: flex; align-items: center; padding-inline: var(--s-3);
  background: var(--surface-2);
  border: 1px solid var(--hairline-firm); border-right: 0;
  border-radius: var(--r-input) 0 0 var(--r-input);
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-muted);
}
.affix .input { border-radius: 0 var(--r-input) var(--r-input) 0; }

/* Segmented control — the country selector and the tool tabs both use it. */
.segmented {
  position: relative;
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--surface-inset);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  isolation: isolate;
}
.segmented__thumb {
  position: absolute; z-index: 0;
  inset-block: 3px; left: 0;
  inline-size: 0;
  background: var(--primary);
  border-radius: var(--r-pill);
  transition: transform var(--t-slow) var(--ease-out),
              width var(--t-slow) var(--ease-out);
  will-change: transform;
}
.segmented__opt {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  /* 44px. These are the country switcher and the theme control — small, but
     the things people actually tap first on a phone. The container's 3px
     padding makes the whole pill 50px, which still clears the 64px header. */
  min-height: 44px; min-width: 44px;
  padding: 0 var(--s-3);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}
.segmented__opt:hover { color: var(--text); }
/* THE ACTIVE OPTION IS MARKED THREE DIFFERENT WAYS, AND ONE OF THEM WAS MISSING.

   The country switcher renders as links on a country-specific page (each one
   goes to a real sibling URL) and as buttons on a universal page (there is
   nowhere to navigate to, so it reformats in place). Links carry
   aria-current="page"; buttons carry aria-pressed="true". The rule here
   matched aria-current and aria-checked — and nothing on this site has ever
   set aria-checked on a .segmented__opt, so on every universal page the
   selected country simply never got its selected colour.

   It went unseen because the old --text-on-accent was a near-black that
   happened to be close to --text-muted, so the wrong colour looked almost
   like the right one. The moment the fill became indigo with white on top,
   the gap was a 1.1:1 pale-grey-on-pale-indigo and the contrast suite named
   it on eleven pages at once.

   aria-checked is dropped rather than kept "just in case". A selector that
   matches nothing is not harmless: it is the reason this looked handled. */
.segmented__opt[aria-pressed="true"],
.segmented__opt[aria-current="page"] { color: var(--text-on-accent); }

/* Country tabs on tool pages are LINKS, not JS state — see the tool template
   rules. Same visual, different element. */
.tabs { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.tabs__tab {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: 500; text-decoration: none;
  color: var(--text-muted);
  transition: all var(--t-fast) var(--ease-out);
}
.tabs__tab:hover { border-color: var(--primary); color: var(--text); }
.tabs__tab[aria-current="page"] {
  background: var(--primary); border-color: var(--primary);
  color: var(--text-on-accent); font-weight: 600;
}

/* ==========================================================================
   3. CARDS
   ========================================================================== */
.card {
  position: relative;
  display: flex; flex-direction: column; gap: var(--s-3);
  padding: var(--s-5);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  text-decoration: none;
  transition: transform var(--t-mid) var(--ease-out),
              border-color var(--t-mid) var(--ease-out),
              box-shadow var(--t-mid) var(--ease-out);
}
a.card:hover, .card--link:hover {
  transform: translateY(-4px);
  border-color: var(--accent, var(--primary));
  box-shadow: var(--elev-hover);
}
.card__icon {
  display: grid; place-items: center;
  inline-size: 44px; block-size: 44px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-input);
  background: var(--surface-2);
  color: var(--primary-text);
  transition: transform var(--t-mid) var(--ease-out),
              border-color var(--t-mid) var(--ease-out);
}
.card__icon svg { inline-size: 22px; block-size: 22px; }
a.card:hover .card__icon { transform: rotate(-6deg); border-color: var(--accent, var(--primary)); }

/* --------------------------------------------------------------------------
   CATEGORY ACCENTS

   `--accent` is set once per card from its data-cat and then every rule below
   reads it. That is the whole mechanism: one custom property, seven values,
   no per-category rule blocks multiplying across states.

   The fallback in `var(--accent, var(--primary))` matters. A tool card, a
   related-tools card and the "Everything else" card have no data-cat, so they
   inherit nothing and fall back to indigo — which means adding a card
   somewhere new cannot produce an uncoloured hole, and adding an eighth
   category cannot produce an unstyled one either. It just looks like the
   brand until someone gives it a hue.
   -------------------------------------------------------------------------- */
.card[data-cat="pdf"]        { --accent: var(--cat-pdf); }
.card[data-cat="image"]      { --accent: var(--cat-image); }
.card[data-cat="finance"]    { --accent: var(--cat-finance); }
.card[data-cat="health"]     { --accent: var(--cat-health); }
.card[data-cat="home-auto"]  { --accent: var(--cat-home); }
.card[data-cat="work"]       { --accent: var(--cat-work); }
.card[data-cat="generators"] { --accent: var(--cat-gen); }

.card[data-cat] .card__icon {
  color: var(--accent);
  /* colour-mix keeps the tint honest against whatever surface the card is on,
     rather than hard-coding seven more background values. 8% is deliberately
     faint: the icon is a tag, and a saturated tile behind every card is the
     fruit-salad failure the tokens file warns about. */
  background: color-mix(in srgb, var(--accent) 8%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 22%, transparent);
}
a.card[data-cat]:hover { border-color: var(--accent); }

/* A hairline of the category colour along the top edge, drawn on hover.
   scaleX from the left rather than a width transition, so it animates on the
   compositor and costs nothing. */
.card[data-cat]::after {
  content: "";
  position: absolute; inset-block-start: -1px; inset-inline: -1px;
  block-size: 2px;
  background: var(--accent);
  border-start-start-radius: var(--r-card);
  border-start-end-radius: var(--r-card);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-slow) var(--ease-out);
}
a.card[data-cat]:hover::after,
a.card[data-cat]:focus-visible::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .card[data-cat]::after { transition: none; }
}
.card__title { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-md); color: var(--text-strong); }
.card__desc  { font-size: var(--fs-xs); color: var(--text-muted); }
.card__meta  { margin-top: auto; display: flex; align-items: center; gap: var(--s-2); }
.card__count { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-faint); letter-spacing: 0.06em; }

/* Compact tool card used in "most used" and "related tools" rows. */
.card--tool { padding: var(--s-4); gap: var(--s-2); flex-direction: row; align-items: flex-start; }
.card--tool .card__icon { inline-size: 36px; block-size: 36px; flex: none; }
.card--tool .card__icon svg { inline-size: 18px; block-size: 18px; }
.card--tool .card__title { font-size: var(--fs-sm); }

/* ==========================================================================
   4. THE READOUT PANEL  (signature element)
   Every tool's output lands here — calculators show numbers, PDF and Image
   tools show file size before/after, page count and dimensions. One
   component, 34 tools, one instrument.
   ========================================================================== */
.readout {
  position: relative;
  padding: var(--s-5);
  background: var(--surface-inset);
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-card);
  overflow: hidden;
}
/* hairline top rule, in --signal, full bleed */
.readout::before {
  content: "";
  position: absolute; inset-block-start: 0; inset-inline: 0;
  block-size: 2px;
  background: var(--primary);
}
.readout__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-weight: 500;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-faint);
  display: block;
  margin-bottom: var(--s-2);
}
.readout__value {
  display: block;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: clamp(2.125rem, 1.6rem + 2.2vw, 2.875rem);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text-strong);
  word-break: break-word;
}
.readout__sub {
  display: block;
  margin-top: var(--s-2);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
/* THE display: block IS THE WHOLE RULE, and it was missing.

   This is rendered as a <span>, so it was an inline box — and an inline box
   ignores margin-top, padding-top and border-top for the purposes of layout.
   All three were set here and all three did nothing to position it: the note
   was laid out eight pixels above the bottom of the grid it is meant to sit
   under, and the hairline it draws above itself was struck through the last
   figure rather than separating it. On the studio panel that meant a rule
   through "2.7 KB".

   It looks correct in the stylesheet, which is why it lasted. The properties
   are all real, all spelled right, and all inert on an inline element. */
.readout__note {
  display: block;
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-2xs);
  color: var(--text-faint);
}

/* Multi-value variant — before/after, breakdowns, split results.
   The readout sits in a narrow sidebar on tool pages and full width on
   others, so the split point depends on the PANEL's width, not the
   viewport's. That is a container query, not a media query — a viewport
   query here puts a vertical rule beside items that are actually stacked. */
.readout--stack { container-type: inline-size; }
.readout--stack .readout__grid {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr;
}
.readout--stack .readout__value { font-size: var(--fs-lg); }
.readout--stack .readout__item + .readout__item {
  padding-top: var(--s-4);
  border-top: 1px solid var(--hairline);
}
@container (min-width: 340px) {
  .readout--stack .readout__grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .readout--stack .readout__item + .readout__item {
    padding-top: 0; border-top: 0;
    padding-left: var(--s-4); border-left: 1px solid var(--hairline);
  }
}
/* No container-query support: fall back to the viewport, which is right more
   often than it is wrong. */
@supports not (container-type: inline-size) {
  @media (min-width: 480px) {
    .readout--stack .readout__grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
    .readout--stack .readout__item + .readout__item {
      padding-top: 0; border-top: 0;
      padding-left: var(--s-4); border-left: 1px solid var(--hairline);
    }
  }
}

/* Delta marker for before/after (compression, savings). */
.readout__delta { font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 500; }
.readout__delta--good { color: var(--signal-text); }
.readout__delta--warn { color: var(--amber-text); }

/* Health-tool notice: lives INSIDE the readout, never in the footer. */
.readout__medical {
  margin-top: var(--s-4);
  padding: var(--s-3);
  background: var(--amber-soft);
  border-left: 2px solid var(--amber);
  border-radius: 0 var(--r-input) var(--r-input) 0;
  font-size: var(--fs-2xs);
  color: var(--text);
}

/* Mobile: readout pins to the bottom of the viewport while inputs are in
   view, so the number is always on screen as you type. */
@media (max-width: 1023px) {
  .readout--pinned {
    position: sticky;
    bottom: var(--s-2);
    box-shadow: var(--elev-lift);
    background: var(--surface);
  }
}

/* ==========================================================================
   5. SLOTS — advertising (reserved, inactive) and affiliate promos
   ADS_ENABLED lives in slots.js. Nothing renders inside .slot--ad today, but
   the space is reserved at desktop so switching it on causes zero CLS.
   ========================================================================== */
.slot { display: block; }

/* An ad slot occupies NO space while advertising is off. slots.js stamps
   <html data-ads="on|off"> synchronously at boot, before any ad script is
   requested, and the reserve below switches on with it.

   This is a deliberate change from "reserve the space now so activating it
   later causes no shift". That version trades a one-time shift at deploy for
   two 250px holes in every live page today, which is a bad trade — an empty
   box is a visible defect for every visitor, every day, for months. What
   actually matters is runtime CLS for a real visitor: with the reserve keyed
   on the attribute, the height is in place from the first paint of the page
   that serves ads, so the unit lands in a box that was already the right
   size. The activation itself changes no markup and no template. */
.slot--ad { min-height: 0; margin-block: 0; }

:root[data-ads="on"] .slot--ad { margin-block: var(--s-6); }

@media (min-width: 1024px) {
  :root[data-ads="on"] .slot--ad { min-height: 250px; }   /* 970x250 / 728x90 */
  :root[data-ads="on"] .slot--ad[data-slot="C"] { min-height: 600px; } /* sidebar */
  :root[data-ads="on"] .slot--ad[data-slot="D"] { min-height: 250px; }
}

.slot--live {
  border: 1px solid var(--hairline-faint);
  border-radius: var(--r-card);
  overflow: hidden;
}

/* Affiliate promo block ---------------------------------------------------
   Visually distinct from tool UI so nobody mistakes a promo for a feature:
   violet hairline instead of signal, recessed background, eyebrow label. */
.promo { display: grid; gap: var(--s-3); margin-block: var(--s-6); }
.promo__disclosure {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  display: flex; flex-wrap: wrap; gap: 0.35em;
  align-items: baseline;
}
.promo__disclosure a { color: var(--violet-text); font-weight: 500; }
.promo__grid {
  display: grid; gap: var(--s-4);
  grid-template-columns: 1fr;
}
@media (min-width: 700px)  { .promo__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .promo__grid--3 { grid-template-columns: repeat(3, 1fr); } }

.promo-card {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: var(--s-4);
  align-items: start;
  padding: var(--s-4);
  min-height: 140px;                 /* reserve height — no layout shift */
  background: var(--surface-2);
  border: 1px solid var(--violet-soft);
  border-left: 2px solid var(--violet);
  border-radius: var(--r-card);
  text-decoration: none;
  transition: border-color var(--t-mid) var(--ease-out),
              transform var(--t-mid) var(--ease-out);
}
.promo-card:hover { transform: translateY(-2px); border-color: var(--violet); }
.promo-card__img {
  inline-size: 96px; block-size: 72px;
  object-fit: cover;
  border-radius: var(--r-input);
  background: var(--surface-inset);
}
.promo-card__eyebrow {
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--violet-text);
}
.promo-card__name { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-sm); color: var(--text-strong); }
.promo-card__tag  { font-size: var(--fs-xs); color: var(--text-muted); }
.promo-card__foot { display: flex; gap: var(--s-2); align-items: center; margin-top: var(--s-2); flex-wrap: wrap; }
.promo-card__price { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-faint); }

/* ==========================================================================
   6. HEADER + NAVIGATION
   ========================================================================== */
/* ==========================================================================
   ANNOUNCEMENT BAR
   ========================================================================== */
/* Set by the inline bootstrap in <head>, before first paint — see the note
   above ANNOUNCE in components.js. Doing it here rather than in JS after load
   is what stops a returning visitor seeing the bar for one frame and then
   watching the page jump up by its height. */
:root[data-announce="off"] .announce { display: none !important; }

.announce {
  position: relative;
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--gutter);
  /* Deep blue to the logo's blue. This was red-to-orange, as originally
     specced, and it came out when the site took its colours from the logo: a
     full-bleed warm band is the first thing on every page, and on a blue site
     it does not read as an accent, it reads as a different site's header that
     got stuck to the top of this one. The CTA buttons keep their warm
     gradient — a warm accent BUTTON on a cool page is a deliberate contrast
     at the size of a button, and it is the size that decides.

     Both stops are dark enough that WHITE text clears 4.5:1 across the whole
     sweep: #082C6B gives 13.3:1 and #0057C8 gives 6.6:1, and every pixel
     between them is a blend of two passing colours, so no point on the
     gradient can fail. Ending on the logo's own #017FFD would look better and
     would put white text at 3.9:1, which is why it does not: this is the
     first text on every page. */
  background: linear-gradient(90deg, #082C6B 0%, #0057C8 100%);
  color: #FFFFFF;
  font-size: var(--fs-xs);
  text-align: center;
}
.announce__text {
  flex: 1;
  max-width: 90ch;
  margin-inline: auto;
  text-wrap: pretty;
}
.announce__text strong { font-weight: 600; }
.announce__close {
  flex: none;
  display: grid; place-items: center;
  /* 44px, like every other control. A close button that is hard to hit is a
     bar that cannot be dismissed. */
  inline-size: 44px; block-size: 44px;
  margin-inline-end: calc(var(--s-2) * -1);
  border-radius: var(--r-input);
  color: #FFFFFF;
  opacity: 0.85;
  transition: opacity var(--t-fast) var(--ease-out),
              background var(--t-fast) var(--ease-out);
}
.announce__close svg { inline-size: 18px; block-size: 18px; }
.announce__close:hover { opacity: 1; background: rgb(255 255 255 / 0.16); }
.announce__close:focus-visible { outline: 2px solid #FFFFFF; outline-offset: -3px; }

/* The bar scrolls away; the header stays. It is deliberately NOT sticky —
   two stacked sticky bars eat a third of a phone screen. */
@media (max-width: 599px) {
  .announce { font-size: var(--fs-2xs); padding-inline: var(--s-3); }
}

/* ==========================================================================
   GRADIENT CALLS TO ACTION

   Two extra button styles on top of .btn, for the one or two actions per page
   that are the point of the page. They are deliberately NOT the default: a
   page where six things shout is a page where nothing does.

   Both set an explicit text colour rather than inheriting, because a gradient
   has no single background colour for a token to be checked against — so the
   contrast is verified against BOTH stops instead. White on #C2321A is 6.4:1
   and on #B4530A is 5.3:1; white on #1B7A46 is 5.4:1 and on #12603A is 7.4:1.
   ========================================================================== */
.btn-cta, .btn-green {
  --btn-fg: #FFFFFF;
  color: #FFFFFF;
  border-color: transparent;
  font-weight: 600;
  box-shadow: 0 1px 2px rgb(20 27 61 / 0.18);
  transition: transform var(--t-fast) var(--ease-out),
              box-shadow var(--t-mid) var(--ease-out),
              filter var(--t-fast) var(--ease-out);
}
.btn-cta  { background: linear-gradient(135deg, #C2321A 0%, #B4530A 100%); }
.btn-green{ background: linear-gradient(135deg, #1B7A46 0%, #12603A 100%); }

.btn-cta:hover, .btn-green:hover {
  color: #FFFFFF;
  border-color: transparent;
  transform: translateY(-1px);
  filter: saturate(1.08);
  box-shadow: var(--elev);
}
.btn-cta:active, .btn-green:active { transform: translateY(0); }
.btn-cta:focus-visible, .btn-green:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .btn-cta, .btn-green { transition: none; }
  .btn-cta:hover, .btn-green:hover { transform: none; }
}

/* The nav is centred by a flexible spacer on each side of it — see
   renderHeader(). The one before the nav only exists at desktop widths,
   because below 1024 the nav itself is hidden and a second spacer would
   push the country switcher off the right edge. */
/* CENTRING THE NAV ON THE PAGE, NOT BETWEEN ITS NEIGHBOURS.

   The obvious approach is a flexible spacer either side of the nav, and it is
   subtly wrong: two auto margins split the FREE space equally, which centres
   the nav between the end of the brand and the start of the controls. Those
   two are different widths — the brand is a logo and a wordmark, the controls
   are a country switcher and a theme button — so the nav lands off-centre by
   however much they differ. Measured on this header: 66px left of centre.

   A three-column grid with equal outer tracks puts the middle track in the
   real centre of the container regardless of what the outer ones contain.
   `minmax(0, 1fr)` on both sides rather than `1fr` so a long wordmark shrinks
   its own track instead of pushing the centre over. */
/* The controls, as one row, so the header has three children and no more. */
.header-actions {
  display: flex; align-items: center; gap: var(--s-2);
  margin-left: auto;
}
@media (min-width: 768px) { .header-actions { gap: var(--s-3); } }



.site-header {
  position: sticky; top: 0; z-index: 100;
  block-size: var(--header-h);
  background: var(--header-bg);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--hairline);
  transition: background var(--t-mid) var(--ease-out),
              box-shadow var(--t-mid) var(--ease-out);
}
@supports not (backdrop-filter: blur(1px)) {
  .site-header { background: var(--header-bg-solid); }
}
.site-header.is-scrolled {
  background: var(--header-bg-solid);
  box-shadow: 0 1px 0 var(--hairline), var(--elev);
}
.site-header__inner {
  block-size: 100%;
  display: flex; align-items: center; gap: var(--s-2);
}
@media (min-width: 768px) { .site-header__inner { gap: var(--s-4); } }

/* The desktop nav appears at 1024px, and at exactly 1024px the brand, the five
   nav links, the country switcher and the theme toggle did not fit inside the
   32px gutter — the toggle overflowed the viewport by 2px. Flex items will not
   shrink below min-content, so the fix is to buy the width back from the gaps
   and padding in the band where it is tight, not to let anything wrap. */
@media (min-width: 1024px) and (max-width: 1199px) {
  .site-header__inner { gap: var(--s-2); }
  .nav { gap: 0; }
  .nav__link { padding: 0 var(--s-2); }
}

/* THIS BLOCK MUST STAY AFTER `.site-header__inner`'s base rule.

   It was originally written a few hundred lines earlier in the file, where it
   set `display: grid` and was then silently overridden by the `display: flex`
   further down — same specificity, later declaration wins. The nav measured
   180px left of centre and every rule in the block looked correct in
   isolation. Cascade order is the only thing that was wrong. */
@media (min-width: 1024px) {
  .site-header__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    gap: var(--s-3);
  }
  .site-header__inner > .brand          { justify-self: start; }
  .site-header__inner > .nav            { justify-self: center; }
  .site-header__inner > .header-actions { justify-self: end; margin-left: 0; }
}

.brand {
  display: inline-flex; align-items: center;
  /* The logo is the primary "back to home" control, so it is a real 44px
     target rather than a 32px image that happens to be clickable. */
  min-block-size: 44px;
  text-decoration: none;
  /* No font properties: the wordmark used to be live text and is now part of
     the artwork, so a font-family here would style nothing. */
}
/* The lockup.

   BOTH DIMENSIONS ARE `auto` AND BOTH ARE CAPPED, WHICH IS NOT FUSSINESS.

   The first version set `block-size: 32px; width: auto`, which is the usual
   way to size a logo and was wrong here. The reset applies `max-width: 100%`
   to every image; at 360px the header has about 147px of room for a 165px
   lockup, so that cap engaged, the width was clamped, the fixed height was
   not — and the wordmark rendered horizontally squashed. A distorted logo is
   worse than a small one, and it only appeared on the narrowest phones, which
   is exactly where nobody looks.

   Capping both instead lets the browser honour the intrinsic ratio against
   whichever constraint binds first: the height on a wide header, the width on
   a narrow one. The img's own width/height attributes still give it a ratio,
   so the box is reserved before the file lands and the header does not shift
   on load. */
.brand__logo {
  display: block;
  inline-size: auto; block-size: auto;
  max-inline-size: 100%; max-block-size: 32px;
}

/* WHICH COPY IS SHOWN, AND THE ONE RULE PEOPLE FORGET.

   The wordmark is near-black navy. On a dark surface it is invisible, so
   there is a knocked-out copy — and the surface that catches people out is
   the FOOTER, which is dark in BOTH themes. The old wordmark had exactly this
   bug: its ".pro" used --primary-text, which is the dark blue in light mode,
   so the footer rendered it at 1.8:1 and nobody noticed, because a two-letter
   suffix that is hard to read looks like a design choice.

   So the footer is not "dark theme"; it is its own case, and it is stated
   separately rather than inherited. */
.brand__logo--dark  { display: none; }
[data-theme="dark"] .brand__logo--light { display: none; }
[data-theme="dark"] .brand__logo--dark  { display: block; }
.site-footer .brand__logo--light { display: none; }
.site-footer .brand__logo--dark  { display: block; }

@media (max-width: 479px) {
  .brand { min-inline-size: 0; }
  .brand__logo { max-block-size: 26px; }
}

.nav { display: flex; align-items: center; gap: var(--s-1); }
.nav__link {
  display: inline-flex; align-items: center; gap: var(--s-2);
  /* 44px, not 40. It fits inside the 64px header with room to spare, and
     "All Tools" is a real button that opens the mega menu. */
  min-height: 44px; padding: 0 var(--s-3);
  border-radius: var(--r-input);
  font-size: var(--fs-xs); font-weight: 500;
  color: var(--text-muted); text-decoration: none;
  transition: color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out);
}
.nav__link:hover { color: var(--text); background: var(--primary-soft); }
.nav__link[aria-current="page"] { color: var(--primary-text); font-weight: 600; }
.nav__link svg { inline-size: 16px; block-size: 16px; }
/* Sized here rather than under .nav__link, because the same caret appears in
   the country dropdown and in the mobile accordion buttons. */
.nav__caret {
  inline-size: 18px; block-size: 18px; flex: none;
  transition: transform var(--t-mid) var(--ease-out);
}
.nav__link[aria-expanded="true"] .nav__caret { transform: rotate(180deg); }
.nav__link[aria-expanded="true"] { color: var(--text); background: var(--primary-soft); }

/* Mega menu — opens on CLICK (hover menus fail on touch), focus-trapped,
   closes on Escape and outside click. */
.mega {
  position: absolute; inset-inline: 0; top: 100%;
  background: var(--header-bg-solid);
  border-block: 1px solid var(--hairline);
  box-shadow: var(--elev);
  opacity: 0; visibility: hidden;
  transform: translateY(-8px);
  transition: opacity var(--t-mid) var(--ease-out),
              transform var(--t-mid) var(--ease-out),
              visibility 0s linear var(--t-mid);
  max-block-size: min(70vh, 640px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.mega.is-open {
  opacity: 1; visibility: visible; transform: translateY(0);
  transition-delay: 0s;
}
/* Below 1024px the mobile menu replaces it. It must be display:none rather
   than just hidden — a visibility:hidden grid still contributes its width to
   the document and would give the page a horizontal scrollbar. */
@media (max-width: 1023px) { .mega { display: none; } }
.mega__inner {
  display: grid; gap: var(--s-5);
  grid-template-columns: repeat(4, 1fr);
  padding-block: var(--s-6);
}
.mega__col { display: grid; gap: var(--s-4); align-content: start; }
.mega__group { display: grid; gap: var(--s-2); align-content: start; }
.mega__head {
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--text-faint);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--hairline);
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-2);
}
/* The category name in a mega-menu heading is a link to that hub. Padded so
   the target clears 24px rather than the 14px the text alone would give. */
.mega__head a {
  display: inline-block;
  padding-block: 6px;
  color: inherit;
  text-decoration: none;
}
.mega__head a:hover { color: var(--primary-text); }
.mega__more { opacity: 0.75; }

.mega__link {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 34px; padding: var(--s-1) var(--s-2);
  margin-inline: calc(var(--s-2) * -1);
  border-radius: var(--r-input);
  font-size: var(--fs-xs); color: var(--text-muted); text-decoration: none;
  transition: color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out);
}
.mega__link:hover { color: var(--text-strong); background: var(--primary-soft); }
.mega__link svg { inline-size: 16px; block-size: 16px; color: var(--primary-text); flex: none; }
.mega__link .pill { margin-left: auto; }
.mega__all {
  grid-column: 1 / -1;
  display: flex; justify-content: space-between; align-items: center; gap: var(--s-4);
  padding-top: var(--s-4); border-top: 1px solid var(--hairline);
  font-size: var(--fs-xs); color: var(--text-muted);
}

/* Country selector in the header */
.country { display: flex; align-items: center; gap: var(--s-2); }
.country__label { font-size: var(--fs-2xs); color: var(--text-faint); }

/* Compact dropdown below 1024px */
.country-menu { position: relative; }
.country-menu__btn {
  display: inline-flex; align-items: center; gap: var(--s-2);
  min-height: 44px; padding: 0 var(--s-3);
  border: 1px solid var(--hairline); border-radius: var(--r-pill);
  font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: 0.08em;
  color: var(--text);
}
.country-menu__btn:hover { border-color: var(--primary); }
/* On the narrowest phones the flag and the caret carry it; the code would
   push the header past the viewport. */
@media (max-width: 419px) { .country-menu__btn .cc-code { display: none; } }
.country-menu__list {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 110;
  min-inline-size: 190px; padding: var(--s-2);
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--r-card); box-shadow: var(--elev);
  display: grid; gap: 2px;
}
.country-menu__list[hidden] { display: none; }
.country-menu__item {
  display: flex; align-items: center; gap: var(--s-3);
  inline-size: 100%; min-height: 40px; padding: 0 var(--s-3);
  border-radius: var(--r-input);
  font-size: var(--fs-xs); color: var(--text-muted); text-align: left;
  text-decoration: none;
}
.country-menu__item:hover { background: var(--primary-soft); color: var(--text); }
.country-menu__item[aria-current="true"] { color: var(--primary-text); font-weight: 600; }
.country-menu__code { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-faint); margin-left: auto; }

/* Hamburger */
.burger { display: grid; place-items: center; }
.burger__box { inline-size: 20px; block-size: 14px; position: relative; }
.burger__box i {
  position: absolute; inset-inline: 0; block-size: 1.5px; background: currentColor;
  border-radius: 2px;
  transition: transform var(--t-mid) var(--ease-out), opacity var(--t-fast) var(--ease-out);
}
.burger__box i:nth-child(1) { top: 0; }
.burger__box i:nth-child(2) { top: 50%; margin-top: -0.75px; }
.burger__box i:nth-child(3) { bottom: 0; }
.burger[aria-expanded="true"] .burger__box i:nth-child(1) { transform: translateY(6.25px) rotate(45deg); }
.burger[aria-expanded="true"] .burger__box i:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] .burger__box i:nth-child(3) { transform: translateY(-6.25px) rotate(-45deg); }

/* Mobile menu — full-screen overlay, accordion by category. */
.mobile-menu {
  position: fixed; inset: 0; z-index: 150;
  display: grid; grid-template-rows: auto 1fr auto;
  background: var(--bg);
  opacity: 0; visibility: hidden;
  transform: translateY(8px);
  transition: opacity var(--t-mid) var(--ease-out),
              transform var(--t-mid) var(--ease-out),
              visibility 0s linear var(--t-mid);
}
.mobile-menu.is-open { opacity: 1; visibility: visible; transform: none; transition-delay: 0s; }
.mobile-menu__top {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) var(--gutter);
  border-bottom: 1px solid var(--hairline);
}
.mobile-menu__body { overflow-y: auto; padding: var(--s-4) var(--gutter) var(--s-6); }
.mobile-menu__foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) var(--gutter);
  border-top: 1px solid var(--hairline);
  background: var(--surface-2);
}
.acc { border-bottom: 1px solid var(--hairline); }
.acc__btn {
  inline-size: 100%; min-height: 56px;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-sm);
  color: var(--text-strong); text-align: left;
}
.acc__btn .nav__caret { transition: transform var(--t-mid) var(--ease-out); color: var(--text-faint); }
.acc__btn[aria-expanded="true"] .nav__caret { transform: rotate(180deg); }
.acc__panel { display: grid; gap: var(--s-1); padding-bottom: var(--s-4); }
.acc__panel[hidden] { display: none; }
.acc__link {
  display: flex; align-items: center; gap: var(--s-3);
  min-height: 44px; padding-inline: var(--s-2);
  border-radius: var(--r-input);
  font-size: var(--fs-xs); color: var(--text-muted); text-decoration: none;
}
.acc__link svg { inline-size: 16px; block-size: 16px; color: var(--primary-text); flex: none; }
.acc__link .pill { margin-left: auto; }

/* ==========================================================================
   7. FOOTER
   --anchor is dark in BOTH themes — a deliberate base for the page.
   ========================================================================== */
.site-footer {
  /* The footer is dark in BOTH themes, so the light theme's text and accent
     tokens would be unreadable here. Remap them for this subtree only —
     anything nested inside the footer (eyebrows, the country control, the
     brand's ".pro") then inherits values that clear 4.5:1 on --anchor. */
  --text:        var(--anchor-head);
  --text-strong: var(--anchor-head);
  --text-muted:  var(--anchor-text);
  --text-faint:  var(--anchor-text);
  --signal-text: var(--c-signal-lit);
  --violet-text: var(--c-violet-lit);
  --hairline:      rgb(255 255 255 / 0.12);
  --hairline-firm: rgb(255 255 255 / 0.22);

  position: relative;
  margin-top: var(--s-8);
  background: var(--anchor);
  color: var(--anchor-text);
  border-top: 1px solid var(--hairline);
  overflow: hidden;
  isolation: isolate;
}
/* Ambient gradient mesh — very low opacity, 20s loop, off under
   reduced-motion. Transform/opacity only; costs ~0 on a mid-range phone. */
.site-footer__mesh {
  position: absolute; inset: -30%; z-index: -1;
  background:
    radial-gradient(40% 40% at 20% 30%, rgb(0 168 143 / 0.16), transparent 70%),
    radial-gradient(35% 35% at 80% 20%, rgb(107 91 210 / 0.14), transparent 70%),
    radial-gradient(45% 45% at 60% 90%, rgb(255 176 32 / 0.08), transparent 70%);
  filter: blur(10px);
  opacity: 0.9;
  pointer-events: none;
}
.site-footer__inner {
  display: grid; gap: var(--s-6);
  grid-template-columns: 1fr;
  padding-block: var(--s-7);
}
@media (min-width: 768px)  { .site-footer__inner { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .site-footer__inner { grid-template-columns: 1.4fr 1fr 1fr 1.2fr; gap: var(--s-6); padding-block: var(--s-8); } }

.f-col { display: grid; gap: var(--s-3); align-content: start; }
.f-col__head {
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--anchor-head);
  padding-bottom: var(--s-3);
  position: relative;
}
/* 24px signal rule that widens to 40px when any link in the column is hovered */
.f-col__head::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  block-size: 2px; inline-size: 24px;
  background: var(--primary);
  transition: width var(--t-slow) var(--ease-out);
}
.f-col:hover .f-col__head::after,
.f-col:focus-within .f-col__head::after { inline-size: 40px; }

/* .f-link IS NOT ONLY A FOOTER LINK, WHICH IS WHY ITS COLOUR IS SCOPED.

   It was written for the dark footer and coloured --anchor-text, a pale grey
   that is correct on --anchor and nowhere else. It then got reused inline in
   the legal prose — "…through the cookie settings link in the footer" appears
   as a real <button class="f-link"> in three places in legal-specs.mjs — where
   it renders pale grey on light paper at 1.6:1. Effectively invisible, on the
   privacy and cookie pages, for over a year, and the automated contrast suite
   is what finally said so.

   The base is now coloured for the page it is most often read on, and the
   footer overrides it. That ordering matters: a component reused in an
   unexpected place should degrade to the ordinary case, not to the special
   one. */
.f-link {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--s-2);
  /* 44px so the footer links — including "Cookie settings", which is a real
     control and not decoration — clear the touch-target floor. */
  min-height: 44px;
  font-size: var(--fs-xs); color: var(--primary-text);
  font-weight: 500;
  text-decoration: none; width: fit-content;
  transition: color var(--t-fast) var(--ease-out);
}
.f-link:hover { color: var(--primary-deep); }

/* Inline in a paragraph, this has to satisfy two things that pull against
   each other: a 44px touch target, and a line of prose whose leading is not
   visibly pushed apart by one word in the middle of it.

   The first attempt used `display: inline` with vertical padding. That reads
   correctly and is wrong twice over: min-height does not apply to a non-
   replaced inline box at all, and the padding brought the hit area to 39px,
   which the responsive suite caught immediately.

   inline-block gets a real 44px box. The negative block margin then pulls the
   surrounding line back to where it would have been, so the paragraph keeps
   its rhythm while the control keeps its target — the box overflows the line
   box rather than expanding it. vertical-align: middle keeps the text on the
   baseline of the words either side of it. */
.prose .f-link, .legal .f-link, p .f-link, li .f-link, dd .f-link {
  display: inline-block;
  min-height: 44px;
  font-size: inherit;
  align-items: center;
  margin-block: calc((44px - 1.62em) / -2);
  vertical-align: middle;
}

.site-footer .f-link { color: var(--anchor-text); font-weight: 400; }
.site-footer .f-link:hover { color: var(--anchor-head); }
/* underline wipes in from the left */
.f-link::after {
  content: ""; position: absolute; left: 0; bottom: 4px;
  inline-size: 100%; block-size: 1px;
  background: var(--primary);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-mid) var(--ease-out);
}
.f-link:hover::after, .f-link:focus-visible::after { transform: scaleX(1); }

.f-about { font-size: var(--fs-xs); color: var(--anchor-text); max-width: 34ch; }
.f-brand { color: var(--anchor-head); }

.f-social { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-2); }
.f-social__link {
  position: relative;
  display: grid; place-items: center;
  /* 44px, not 40 — the quality floor applies in the footer too, and these are
     the smallest targets on the page. */
  inline-size: 44px; block-size: 44px;
  border-radius: var(--r-pill);
  color: var(--anchor-text);
  transition: color var(--t-mid) var(--ease-out), transform var(--t-mid) var(--ease-over);
}
.f-social__link::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  background: rgb(255 255 255 / 0.08);
  transform: scale(0);
  transition: transform 200ms var(--ease-over);
}
.f-social__link:hover { color: var(--primary); transform: scale(1.15); }
.f-social__link:hover::before { transform: scale(1); }
.f-social__link svg { inline-size: 18px; block-size: 18px; }

.f-bottom {
  border-top: 1px solid rgb(255 255 255 / 0.10);
  padding-block: var(--s-4);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-4);
  font-size: var(--fs-2xs);
  color: var(--anchor-text);
}
.f-bottom a { color: var(--anchor-text); text-decoration: underline; text-underline-offset: 3px; }
.f-bottom a:hover { color: var(--anchor-head); }
.f-bottom__disclosure { max-width: 62ch; }

/* Footer country selector mirrors the header one */
.site-footer .segmented { background: rgb(255 255 255 / 0.06); border-color: rgb(255 255 255 / 0.12); }
.site-footer .segmented__opt { color: var(--anchor-text); }
.site-footer .segmented__opt:hover { color: var(--anchor-head); }
/* The footer override above has the same specificity as the active-state rule
   further up the file and comes after it, so it was winning for the SELECTED
   option too — the active country sat in pale footer grey on the indigo fill
   instead of in the on-accent colour. Restating it here, scoped, puts the
   selected state back on top where it belongs.

   This only became visible once the fill was aligned correctly: while it was
   three pixels off, the label was partly on the track, and the wrong-looking
   thing was the alignment rather than the colour. One bug was hiding another. */
.site-footer .segmented__opt[aria-pressed="true"],
.site-footer .segmented__opt[aria-current="page"] { color: var(--text-on-accent); }

/* ==========================================================================
   8. HOMEPAGE HERO
   ========================================================================== */
.hero {
  position: relative;
  padding-block: var(--s-7) var(--s-8);
  overflow: hidden;
  isolation: isolate;
}
/* Graph-paper calibration grid, drifting 20px over 30s. Pure CSS transform. */
.hero__grid {
  position: absolute; inset: -40px; z-index: -1;
  background-image:
    repeating-linear-gradient(0deg,   var(--hairline-faint) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(90deg,  var(--hairline-faint) 0 1px, transparent 1px 40px);
  -webkit-mask-image: radial-gradient(70% 60% at 50% 35%, #000 0%, transparent 100%);
  mask-image: radial-gradient(70% 60% at 50% 35%, #000 0%, transparent 100%);
  pointer-events: none;
}
.hero__inner {
  display: grid; gap: var(--s-6);
  grid-template-columns: 1fr;
  align-items: center;
}
@media (min-width: 1024px) {
  .hero__inner { grid-template-columns: minmax(0, 1fr) minmax(420px, 0.85fr); gap: var(--s-8); }
}
.hero__copy { display: grid; gap: var(--s-4); justify-items: start; }
/* The hero sits in a two-column layout, so its H1 tops out at 46px rather
   than the scale's 60 — 60 would force five lines against the drop zone. */
.hero h1 { max-width: 18ch; font-size: clamp(2.125rem, 1.5rem + 2.2vw, 2.875rem); }
.hero__sub { font-size: var(--fs-md); color: var(--text-muted); max-width: 46ch; }

.hero__trust {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-xs); color: var(--text-muted);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
}
.hero__trust svg { inline-size: 16px; block-size: 16px; color: var(--primary-text); flex: none; }
.hero__trust strong { color: var(--text); font-weight: 600; }

.hero__chips { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* The live drop zone — the thesis of the site, working in the hero.

   THIS ELEMENT WAS THE WORST THING ON THE PAGE, AND IT IS THE MOST IMPORTANT.
   It was a white box on a grey page with a grey dashed outline: the single
   element the whole homepage exists to get someone to use, rendered with less
   visual weight than a paragraph. Nothing about it said "put a file here"
   except the words inside it.

   Three changes, in order of how much each one does:

   1. It is a tinted panel, not white. A wash of the brand indigo, so it reads
      as a target rather than as empty space. The wash is very light — 4% —
      because the text inside it still has to clear 4.5:1.

   2. The dashed border is indigo at 45%, not grey. A dashed outline is the
      universal "drop here" convention and it was being drawn in the same
      colour as every other hairline on the site.

   3. It breathes. A 4-second scale cycle of half a percent, which is below
      the threshold of "animated" and above the threshold of "alive" — enough
      that the eye lands on it first. Inside prefers-reduced-motion, like
      everything else here. */
.dropzone {
  position: relative;
  display: grid; place-items: center; gap: var(--s-3);
  min-block-size: 300px;
  padding: var(--s-6) var(--s-5);
  text-align: center;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--primary) 5%, var(--surface)),
      var(--surface) 70%);
  border-radius: var(--r-card);
  box-shadow: var(--elev-lift);
  transition: transform var(--t-mid) var(--ease-out),
              box-shadow var(--t-mid) var(--ease-out),
              background var(--t-mid) var(--ease-out);
}
.dropzone__border {
  position: absolute; inset: 0; pointer-events: none;
  inline-size: 100%; block-size: 100%;
  overflow: visible;
}
.dropzone__border rect {
  fill: none;
  stroke: color-mix(in srgb, var(--primary) 45%, transparent);
  stroke-width: 2;
  stroke-dasharray: 8 6;
  rx: 10;
  transition: stroke var(--t-mid) var(--ease-out);
}
.dropzone:hover .dropzone__border rect { stroke: var(--primary); }
.dropzone.is-over {
  background: color-mix(in srgb, var(--primary) 12%, var(--surface));
  transform: scale(1.015);
  box-shadow: var(--elev-hover);
}
.dropzone.is-over .dropzone__border rect { stroke: var(--primary); stroke-dasharray: 4 4; }
.dropzone__icon {
  display: grid; place-items: center;
  inline-size: 56px; block-size: 56px;
  border: 1px solid color-mix(in srgb, var(--primary) 24%, transparent);
  border-radius: var(--r-card);
  background: color-mix(in srgb, var(--primary) 10%, var(--surface));
  color: var(--primary-text);
  transition: transform var(--t-mid) var(--ease-out);
}
.dropzone:hover .dropzone__icon { transform: translateY(-2px); }
.dropzone__icon svg { inline-size: 26px; block-size: 26px; }
.dropzone__title { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-md); color: var(--text-strong); }
.dropzone__hint  { font-size: var(--fs-xs); color: var(--text-muted); max-width: 34ch; }
.dropzone__types { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-faint); letter-spacing: 0.06em; }
.dropzone input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.dropzone__status {
  min-block-size: 1.4em;
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  color: var(--primary-text);
}

/* ==========================================================================
   9. MISC BLOCKS — FAQ, feature rows, notices, breadcrumbs, tables
   ========================================================================== */
.faq { display: grid; gap: var(--s-2); max-width: 80ch; }
.faq__item {
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  background: var(--surface);
  overflow: hidden;
}
.faq__item[open] { border-color: var(--hairline-firm); }
.faq__q {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  min-height: 56px; padding: var(--s-3) var(--s-4);
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-sm);
  color: var(--text-strong); cursor: pointer; list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::after {
  content: ""; flex: none;
  inline-size: 10px; block-size: 10px;
  border-right: 1.5px solid var(--text-faint);
  border-bottom: 1.5px solid var(--text-faint);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--t-mid) var(--ease-out);
}
.faq__item[open] .faq__q::after { transform: rotate(-135deg) translate(-2px, -2px); }
.faq__a { padding: 0 var(--s-4) var(--s-4); color: var(--text-muted); font-size: var(--fs-xs); }
.faq__a > * + * { margin-top: var(--s-3); }

.feature { display: grid; gap: var(--s-3); }
.feature__icon {
  display: grid; place-items: center;
  inline-size: 40px; block-size: 40px;
  border: 1px solid var(--hairline); border-radius: var(--r-input);
  background: var(--surface); color: var(--primary-text);
}
.feature__icon svg { inline-size: 20px; block-size: 20px; }
.feature__title { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-md); color: var(--text-strong); }
.feature__body { font-size: var(--fs-xs); color: var(--text-muted); }

.notice {
  display: flex; gap: var(--s-3);
  padding: var(--s-4);
  border-radius: var(--r-card);
  border: 1px solid var(--hairline);
  background: var(--surface-2);
  font-size: var(--fs-xs); color: var(--text-muted);
}
.notice--amber { background: var(--amber-soft); border-color: transparent; border-left: 2px solid var(--amber); color: var(--text); }
.notice svg { inline-size: 18px; block-size: 18px; flex: none; color: var(--primary-text); }

.crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); font-size: var(--fs-2xs); color: var(--text-faint); padding-block: var(--s-4); }
/* Breadcrumb links are text-scale by design, and padding them to 44px on a
   desktop would turn a one-line trail into a toolbar. So they clear WCAG's
   24px minimum with a pointer, and grow to the site's own 44px floor on a
   touch device — where "touch target" is the thing actually being measured. */
.crumbs a {
  display: inline-flex; align-items: center;
  min-block-size: 24px;
  color: var(--text-muted); text-decoration: none;
}
@media (hover: none) {
  .crumbs a { min-block-size: 44px; }
}
.crumbs a:hover { color: var(--primary-text); }
.crumbs li + li::before { content: "/"; margin-right: var(--s-2); color: var(--hairline-firm); }
.crumbs li { display: flex; align-items: center; }

.table-wrap { overflow-x: auto; border: 1px solid var(--hairline); border-radius: var(--r-card); }
.table { font-size: var(--fs-xs); }
.table th, .table td { padding: var(--s-3); text-align: left; border-bottom: 1px solid var(--hairline); white-space: nowrap; }
.table th { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:nth-child(even) { background: var(--surface-2); }

/* ==========================================================================
   10. CONSENT — banner and settings panel
   "Reject all" is deliberately identical in size, weight and colour to
   "Accept all". A reject button that is smaller, greyer or one level deeper
   is the exact pattern that makes consent invalid under the EDPB's and the
   CMA's guidance, and it is worth being unambiguous about in the CSS too.
   ========================================================================== */
.consent {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 160;
  background: var(--surface);
  border-top: 2px solid var(--primary);
  box-shadow: var(--elev-lift);
  opacity: 0;
  transform: translateY(100%);
  transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out);
}
.consent.is-in { opacity: 1; transform: none; }

.consent__inner {
  max-width: var(--container);
  margin-inline: auto;
  padding: var(--s-4) var(--gutter);
  display: grid;
  gap: var(--s-4);
  align-items: center;
}
@media (min-width: 900px) {
  .consent__inner { grid-template-columns: minmax(0, 1fr) auto; }
}

.consent__title { font-size: var(--fs-md); margin-bottom: var(--s-1); }
.consent__copy p { font-size: var(--fs-xs); color: var(--text-muted); max-width: 72ch; }
.consent__copy a { color: var(--primary-text); font-weight: 500; }

/* A grid of equal columns rather than a flex row, so "Reject all" and
   "Accept all" are the SAME WIDTH TO THE PIXEL. In a flex row they size to
   their text and accept ends up a pixel wider, which is meaningless to the
   eye and exactly the kind of asymmetry this pattern should not have. */
.consent__actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(128px, 1fr));
  gap: var(--s-2);
  align-content: start;
}
.consent__actions .btn--ghost { grid-column: 1 / -1; }

/* ---- settings panel ------------------------------------------------------ */
.consent-panel { position: fixed; inset: 0; z-index: 170; display: grid; place-items: center; }
.consent-panel__backdrop {
  position: absolute; inset: 0;
  background: rgb(0 0 0 / 0.55);
  opacity: 0;
  transition: opacity var(--t-mid) var(--ease-out);
}
.consent-panel.is-in .consent-panel__backdrop { opacity: 1; }

.consent-panel__box {
  position: relative;
  inline-size: min(560px, calc(100vw - 2rem));
  max-block-size: min(80svh, 720px);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--elev-lift);
  opacity: 0;
  transform: translateY(8px) scale(0.99);
  transition: opacity var(--t-mid) var(--ease-out), transform var(--t-mid) var(--ease-out);
}
.consent-panel.is-in .consent-panel__box { opacity: 1; transform: none; }

.consent-panel__head,
.consent-panel__foot {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4);
}
.consent-panel__head { justify-content: space-between; border-bottom: 1px solid var(--hairline); }
.consent-panel__head h2 { font-size: var(--fs-md); }
.consent-panel__foot { justify-content: space-between; border-top: 1px solid var(--hairline); }

.consent-panel__body { overflow-y: auto; padding: var(--s-4); display: grid; gap: var(--s-3); }
.consent-panel__sub {
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--text-faint);
  margin-top: var(--s-2);
}

.consent-row {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s-4);
  padding: var(--s-3);
  border: 1px solid var(--hairline);
  border-radius: var(--r-input);
}
.consent-row--fixed { background: var(--surface-2); }
.consent-row__text { display: grid; gap: var(--s-1); justify-items: start; }
.consent-row__name { font-weight: 600; font-size: var(--fs-xs); color: var(--text); }
.consent-row__note { font-size: var(--fs-2xs); color: var(--text-muted); }

/* A switch, big enough to hit on a phone. */
.switch { position: relative; display: inline-flex; flex: none; cursor: pointer; padding: 6px 0; }
.switch input { position: absolute; opacity: 0; inline-size: 100%; block-size: 100%; margin: 0; cursor: pointer; }
.switch__track {
  inline-size: 48px; block-size: 28px;
  border-radius: var(--r-pill);
  background: var(--hairline-firm);
  transition: background var(--t-fast) var(--ease-out);
  display: grid; align-items: center;
  padding: 3px;
}
.switch__dot {
  inline-size: 22px; block-size: 22px;
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.3);
  transition: transform var(--t-mid) var(--ease-out);
}
.switch input:checked + .switch__track { background: var(--primary); }
.switch input:checked + .switch__track .switch__dot { transform: translateX(20px); }
.switch input:disabled + .switch__track { opacity: 0.4; cursor: not-allowed; }
.switch input:focus-visible + .switch__track { outline: 2px solid var(--primary); outline-offset: 2px; }

.consent-list { display: grid; gap: var(--s-2); }
.consent-list li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s-1) var(--s-3);
  padding: var(--s-2) var(--s-3);
  background: var(--surface-inset);
  border-radius: var(--r-input);
  font-size: var(--fs-2xs);
}
.consent-list__key { font-family: var(--font-mono); font-weight: 500; color: var(--text); }
.consent-list__kind { font-family: var(--font-mono); color: var(--text-faint); }
.consent-list__value {
  grid-column: 1 / -1;
  font-family: var(--font-mono); color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* ==========================================================================
   11. LEGAL PAGES — a wider, quieter reading measure
   ========================================================================== */
.legal { max-width: 70ch; }
.legal h2 {
  margin-top: var(--s-7);
  padding-top: var(--s-4);
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-lg);
}
.legal h2:first-of-type { margin-top: var(--s-5); }
.legal h3 { margin-top: var(--s-5); font-size: var(--fs-md); }
.legal > * + * { margin-top: var(--s-4); }
.legal ul { display: grid; gap: var(--s-2); }
.legal li { padding-left: var(--s-5); position: relative; }
.legal li::before {
  content: ""; position: absolute; left: 0; top: 0.7em;
  inline-size: 10px; block-size: 1px; background: var(--primary);
}
.legal dl { display: grid; gap: var(--s-3); }
.legal dt { font-weight: 600; color: var(--text); font-size: var(--fs-xs); }
.legal dd { margin: 0; color: var(--text-muted); font-size: var(--fs-xs); }

.legal-meta {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4);
  padding: var(--s-3) 0;
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--text-faint);
  border-block: 1px solid var(--hairline);
}

/* Table of contents for the longer policies */
.legal-toc {
  padding: var(--s-4);
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
}
.legal-toc ol { display: grid; gap: var(--s-1); counter-reset: toc; }
.legal-toc li { padding-left: var(--s-5); position: relative; counter-increment: toc; }
.legal-toc li::before {
  content: counter(toc);
  position: absolute; left: 0; top: 0;
  inline-size: auto; block-size: auto; background: none;
  font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-faint);
}
.legal-toc a { color: var(--primary-text); font-size: var(--fs-xs); text-decoration: none; }
.legal-toc a:hover { text-decoration: underline; }

/* Sitemap index */
.sitemap-group { margin-top: var(--s-6); }
.sitemap-group__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--hairline);
  margin-bottom: var(--s-3);
}
.sitemap-list {
  display: grid; gap: var(--s-1);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
}
.sitemap-list a {
  display: block;
  padding: var(--s-2);
  border-radius: var(--r-input);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-decoration: none;
}
.sitemap-list a:hover { background: var(--primary-soft); color: var(--text-strong); }
.sitemap-list .pill { margin-left: var(--s-2); }

/* ==========================================================================
   12. CHARTS — SVG, painted with design tokens so a theme change re-colours
   them with no JavaScript at all. See the note at the top of tools/chart.js
   for why this is not Chart.js.
   ========================================================================== */
.chart {
  display: block;
  margin-block: var(--s-4);
}
.chart__svg {
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 640 / 260;
  overflow: visible;
}

.chart__grid { stroke: var(--hairline); stroke-width: 1; }
.chart__axis { stroke: var(--hairline-firm); stroke-width: 1; }
.chart__tick {
  fill: var(--text-faint);
  font-family: var(--font-mono);
  font-size: 10px;
}

.chart__line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart__line--signal { stroke: var(--signal); }
.chart__line--violet { stroke: var(--violet); }
.chart__line--amber  { stroke: var(--amber); }

.chart__area { stroke: none; }
.chart__area--signal { fill: var(--signal); opacity: 0.14; }
.chart__area--violet { fill: var(--violet); opacity: 0.14; }
.chart__area--amber  { fill: var(--amber);  opacity: 0.14; }

.chart__bar--signal { fill: var(--signal); }
.chart__bar--violet { fill: var(--violet); }
.chart__bar--amber  { fill: var(--amber); }

.chart__legend {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4);
  margin-top: var(--s-2);
}
.chart__key {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-2xs); color: var(--text-muted);
}
.chart__swatch {
  inline-size: 12px; block-size: 12px; border-radius: 3px; flex: none;
}
.chart__swatch--signal { background: var(--signal); }
.chart__swatch--violet { background: var(--violet); }
.chart__swatch--amber  { background: var(--amber); }

/* Comparison bars */
.bars { display: grid; gap: var(--s-3); }
.bars__row {
  display: grid;
  grid-template-columns: minmax(0, 8rem) minmax(0, 1fr) auto;
  gap: var(--s-3);
  align-items: center;
}
.bars__label { font-size: var(--fs-2xs); color: var(--text-muted); }
.bars__track {
  block-size: 20px;
  background: var(--surface-inset);
  border-radius: var(--r-input);
  overflow: hidden;
}
.bars__fill {
  display: block; block-size: 100%;
  border-radius: var(--r-input);
  transition: inline-size var(--t-slow) var(--ease-out);
}
.bars__fill--signal { background: var(--signal); }
.bars__fill--violet { background: var(--violet); }
.bars__fill--amber  { background: var(--amber); }
.bars__value { font-size: var(--fs-xs); color: var(--text); white-space: nowrap; }
@media (max-width: 520px) {
  .bars__row { grid-template-columns: minmax(0, 1fr) auto; }
  .bars__track { grid-column: 1 / -1; }
}

/* ==========================================================================
   13. CALCULATOR PAGES
   ========================================================================== */
.calc-form { display: grid; gap: var(--s-4); }
@media (min-width: 560px) {
  .calc-form--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .calc-form--two .field--wide { grid-column: 1 / -1; }
}

/* QR preview. The code must sit on WHITE regardless of theme — a QR rendered
   on a dark surface is a QR that many scanners refuse, and the quiet zone only
   works if it is genuinely light. */
.qr-holder {
  display: flex; align-items: center; justify-content: center;
  padding: var(--s-4);
  background: #ffffff;
  border-radius: var(--r-card);
  border: 1px solid var(--hairline);
}
.qr-holder svg { max-inline-size: 100%; block-size: auto; }

.calc-actions {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  padding-top: var(--s-3);
  border-top: 1px solid var(--hairline);
}

.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  padding-top: var(--s-3);
}
.pager__state { font-size: var(--fs-2xs); color: var(--text-faint); }

/* A worked example, set apart from the prose around it */
.worked {
  padding: var(--s-4);
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-left: 2px solid var(--primary);
  border-radius: 0 var(--r-card) var(--r-card) 0;
  font-size: var(--fs-xs);
}
.worked dl { display: grid; grid-template-columns: 1fr auto; gap: var(--s-2) var(--s-4); margin: 0; }
.worked dt { color: var(--text-muted); }
.worked dd { margin: 0; font-family: var(--font-mono); color: var(--text); text-align: right; }
.worked__total { border-top: 1px solid var(--hairline-firm); padding-top: var(--s-2); font-weight: 600; }

/* Assumptions and sources — dated, because an undated rate is a liability */
/* Source notes carry long URLs, and a URL with no hyphen for 50 characters
   sets the min-content width of whatever contains it — which at 360px pushes
   the whole page into a horizontal scroll with no visible cause. Breaking
   anywhere is the fix, and it has to live here rather than in the copy: an
   author should not have to hand-pick citation URLs that happen to wrap. */
.sources { font-size: var(--fs-2xs); color: var(--text-muted); min-inline-size: 0; }
.sources dt { font-weight: 600; color: var(--text); margin-top: var(--s-3); }
.sources dd { margin: 0; min-inline-size: 0; overflow-wrap: anywhere; }
.prose .worked dd, .prose .worked dt { overflow-wrap: anywhere; }
.sources__date {
  display: inline-block;
  font-family: var(--font-mono);
  padding: 0.1em 0.5em;
  border-radius: var(--r-pill);
  background: var(--amber-soft);
  color: var(--amber-text);
}

@media print {
  .calc-actions, .pager, .slot, .legal-toc { display: none !important; }
  .readout { border: 1px solid #999 !important; break-inside: avoid; }
  .chart { break-inside: avoid; }
  .chart .sr-only {
    position: static !important; width: auto !important; height: auto !important;
    clip-path: none !important; margin: 0 !important; overflow: visible !important;
  }
  .chart__svg { display: none; }
}

/* Service-worker update prompt. Fixed to the bottom rather than pushed into
   the layout: it appears at an unpredictable moment, and a bar that shifts the
   page under someone's cursor is worse than the stale asset it is offering to
   fix. */
.sw-update {
  position: fixed;
  inset-inline: var(--s-4);
  inset-block-end: var(--s-4);
  z-index: 180;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
  max-inline-size: 34rem;
  margin-inline: auto;
  box-shadow: var(--shadow-lift, 0 8px 30px rgb(0 0 0 / 0.18));
}
.sw-update > span { flex: 1 1 14rem; min-inline-size: 0; }
@media (prefers-reduced-motion: no-preference) {
  .sw-update { animation: sw-rise var(--t-mid) var(--ease-out) both; }
  @keyframes sw-rise {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
  }
}

/* ==========================================================================
   DOCUMENT TOOLS — the résumé builder and the invoice generator
   Two things: a checklist that says which required items are present, and a
   preview of the document that is about to be exported.

   The preview is deliberately plain. Both tools export a single column of
   real text, so a preview that looked more designed than the PDF would be
   lying about the output.
   ========================================================================== */

/* .panel__note is defined in studio.css, which the calculator pages do not
   load — so every explanatory line a tool writes under a panel has been
   rendering at body size on those pages. Same declaration, same value, here
   where the calculators can see it. */
.panel__note { font-size: var(--fs-2xs); color: var(--text-muted); }

.checklist { list-style: none; margin: 0 0 var(--s-3); padding: 0; display: grid; gap: var(--s-2); }

.checklist__row {
  display: grid;
  grid-template-columns: 1.35rem 1fr;
  gap: var(--s-2);
  align-items: start;
  padding-block: var(--s-1);
  border-block-end: 1px solid var(--hairline-faint);
  min-inline-size: 0;
}
.checklist__row:last-child { border-block-end: 0; }
.checklist__row--na { opacity: 0.62; }

.checklist__mark {
  inline-size: 1.35rem;
  block-size: 1.35rem;
  border-radius: 999px;
  display: grid;
  place-items: center;
  font-size: var(--fs-2xs);
  font-weight: 700;
  line-height: 1;
}
.checklist__mark--ok   { background: var(--signal-soft); color: var(--signal-text); }
.checklist__mark--miss { background: var(--amber-soft);  color: var(--amber-text); }
.checklist__mark--na   { background: var(--surface-2);   color: var(--text-faint); }

.checklist__body { min-inline-size: 0; display: grid; gap: 2px; }
.checklist__label { font-size: var(--fs-2xs); color: var(--text-strong); font-weight: 600; overflow-wrap: anywhere; }
.checklist__note  { font-size: var(--fs-2xs); color: var(--text-faint); overflow-wrap: anywhere; }
.checklist--quiet .checklist__label { font-weight: 400; color: var(--text-muted); }

/* -------------------------------------------------------------------------- */
.docprev {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  padding: var(--s-5);
  margin-block: var(--s-3);
  font-size: var(--fs-2xs);
  line-height: 1.5;
  color: var(--text);
  min-inline-size: 0;
  overflow-wrap: anywhere;
}
.docprev p { margin: 0 0 var(--s-1); }
.docprev ul { margin: var(--s-1) 0 0; padding-inline-start: 1.1rem; }
.docprev li { margin-block-end: 2px; }

.docprev__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  justify-content: space-between;
  align-items: flex-start;
}
.docprev__title {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text-strong);
}
.docprev__meta   { color: var(--text-faint); }
.docprev__label  { font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint); }
.docprev__strong { font-weight: 600; color: var(--text-strong); }

.docprev__dates { display: grid; gap: 2px; text-align: end; font-family: var(--font-mono); }
.docprev__dates span { font-family: var(--font-body); color: var(--text-faint); margin-inline-end: var(--s-2); }

.docprev__parties {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin-block: var(--s-4);
  padding-block-start: var(--s-3);
  border-block-start: 1px solid var(--hairline);
}

.docprev__table { inline-size: 100%; border-collapse: collapse; margin-block: var(--s-3); }
.docprev__table th, .docprev__table td {
  padding: var(--s-2) var(--s-1);
  border-block-end: 1px solid var(--hairline-faint);
  text-align: start;
  vertical-align: top;
}
.docprev__table th { font-size: 0.68rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); }
.docprev__table .num { text-align: end; font-family: var(--font-mono); white-space: nowrap; }

.docprev__totals {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--s-1) var(--s-4);
  margin: 0 0 var(--s-3);
  padding-block-start: var(--s-2);
}
.docprev__totals dt { color: var(--text-muted); text-align: end; }
.docprev__totals dd { margin: 0; font-family: var(--font-mono); text-align: end; }
.docprev__grand { font-weight: 700; color: var(--text-strong); border-block-start: 1px solid var(--hairline-firm); padding-block-start: var(--s-2); }

.docprev__note { color: var(--text-muted); }

/* ---- résumé ---- */
.docprev--resume { line-height: 1.55; }
.docprev__name {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text-strong);
  margin-block-end: 0;
}
.docprev__headline { color: var(--text-muted); }
.docprev__contact  { color: var(--text); margin-block-end: var(--s-2); }
.docprev__section {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-strong);
  margin-block: var(--s-4) var(--s-2);
  padding-block-end: var(--s-1);
  border-block-end: 1px solid var(--hairline-firm);
}
.docprev__entry { margin-block-end: var(--s-3); }

/* The preview holds a four-column table and the page must not scroll
   sideways at 360px, so the table — and only the table — scrolls. */
/* The line-item table is the one thing in here that cannot always fit a
   360px screen, so it — and only it — scrolls. The page body never does. */
.docprev__scroll { overflow-x: auto; margin-block: var(--s-3); }
.docprev__scroll .docprev__table { min-inline-size: 20rem; margin-block: 0; }

@media (max-width: 30rem) {
  .docprev { padding: var(--s-4); }
  .docprev__dates { text-align: start; }
}

/* ==========================================================================
   TOOL SEARCH — "What do you want to do?"

   A combobox in the hero. The popup is absolutely positioned so opening it
   moves nothing: a list that pushes the page down shifts the drop zone under
   the cursor on the very interaction where somebody is about to click a
   result, and it costs CLS on the page that gets the most traffic.
   ========================================================================== */
/* z-index on the WRAPPER, not only on the popup, and the reason is worth
   writing down because nothing about it is visible in a stylesheet.

   The hero's load animation gives every step `animation-fill-mode: both`. When
   `rise-in` finishes, the element's computed transform is not `none` — it is
   the identity matrix, and the spec says ANY transform other than `none`
   creates a stacking context. So .tsearch became its own stacking context,
   which trapped the popup's z-index: 40 inside it, and .tsearch itself painted
   at the same level as the quick-action chips below it — earlier in document
   order, therefore underneath. The results list opened behind the chips.

   Raising the wrapper puts it above its siblings in the hero's own context,
   which is the level the conflict is actually at. */
.tsearch { position: relative; z-index: 40; inline-size: 100%; max-inline-size: 34rem; }

.tsearch__field {
  display: flex; align-items: center; gap: var(--s-3);
  background: var(--surface);
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-pill);
  padding-inline: var(--s-4);
  box-shadow: var(--elev);
}
.tsearch__field:focus-within {
  border-color: var(--primary);
  box-shadow: var(--elev), 0 0 0 3px var(--primary-ring);
}
.tsearch__field > svg {
  inline-size: 20px; block-size: 20px; flex: none; color: var(--primary-text);
}
.tsearch input {
  flex: 1 1 auto; min-inline-size: 0;
  /* 48px, not 44: this is the primary control on the homepage and the touch
     floor is a floor, not a target. */
  block-size: 48px;
  border: 0; background: none; outline: none;
  font-size: var(--fs-sm); color: var(--text);
}
.tsearch input::placeholder { color: var(--text-faint); }
/* Safari draws its own clear button inside type=search and it collides with
   the rounded field. The list is dismissable with Escape and by clearing. */
.tsearch input::-webkit-search-decoration,
.tsearch input::-webkit-search-cancel-button { -webkit-appearance: none; }

.tsearch__pop {
  position: absolute; inset-inline: 0; inset-block-start: calc(100% + var(--s-2));
  z-index: 40;
  background: var(--surface);
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-card);
  box-shadow: var(--elev-lift);
  padding: var(--s-2);
  display: none;
  max-block-size: min(60vh, 26rem);
  overflow-y: auto;
}
.tsearch.is-open .tsearch__pop { display: block; }

.tsearch__opt {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-input);
  cursor: pointer;
  min-block-size: 44px;
}
.tsearch__opt.is-active,
.tsearch__opt:hover { background: var(--primary-soft); }

.tsearch__icon {
  inline-size: 32px; block-size: 32px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-input);
  background: var(--surface-2);
  color: var(--accent, var(--primary-text));
}
.tsearch__icon svg { inline-size: 17px; block-size: 17px; }
.tsearch__opt[data-cat="pdf"]        { --accent: var(--cat-pdf); }
.tsearch__opt[data-cat="image"]      { --accent: var(--cat-image); }
.tsearch__opt[data-cat="finance"]    { --accent: var(--cat-finance); }
.tsearch__opt[data-cat="health"]     { --accent: var(--cat-health); }
.tsearch__opt[data-cat="home-auto"]  { --accent: var(--cat-home); }
.tsearch__opt[data-cat="work"]       { --accent: var(--cat-work); }
.tsearch__opt[data-cat="generators"] { --accent: var(--cat-gen); }

.tsearch__text { display: grid; gap: 1px; min-inline-size: 0; }
.tsearch__name { font-weight: 600; color: var(--text-strong); }
.tsearch__desc {
  font-size: var(--fs-xs); color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tsearch__empty {
  padding: var(--s-3) var(--s-4);
  font-size: var(--fs-xs); color: var(--text-muted);
}
.tsearch__hint {
  font-size: var(--fs-2xs); color: var(--text-faint);
  margin-block-start: var(--s-2);
}

.pill--soft { background: var(--primary-soft); color: var(--primary-text); }

/* ==========================================================================
   THE PRIVACY DIAGRAM

   The claim "your files never leave your device" is the whole product and it
   was only ever made in prose. Prose is the wrong medium for it: the thing
   that convinces someone is seeing that one route crosses a line and the
   other does not.

   So it is drawn as two lanes with a dashed boundary between the device and
   the internet. The upload lane crosses it twice; this site's lane never
   touches it. Everything is real text in real boxes rather than an image, so
   it is readable by a screen reader in the order it is read by eye, it
   reflows at 360px, and it costs no request.
   ========================================================================== */
.privacy { display: grid; gap: var(--s-5); }

@media (min-width: 900px) {
  .privacy { grid-template-columns: 1fr 1fr; gap: var(--s-6); }
}

.lane {
  position: relative;
  display: grid; gap: var(--s-3);
  padding: var(--s-5);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  background: var(--surface);
}
.lane--ours { border-color: var(--signal); box-shadow: var(--elev); }

.lane__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3); }
.lane__title { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-md); color: var(--text-strong); }

.lane__flow { display: grid; gap: var(--s-2); }

.lane__node {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3);
  border-radius: var(--r-input);
  background: var(--surface-2);
  font-size: var(--fs-xs);
}
.lane__node svg { inline-size: 18px; block-size: 18px; flex: none; color: var(--primary-text); }
.lane__node strong { display: block; color: var(--text-strong); font-size: var(--fs-sm); font-weight: 600; }
.lane__node span { color: var(--text-muted); }

/* The boundary. A node marked data-side="net" sits beyond it, and the label
   on the rule says what "beyond" means. */
.lane__boundary {
  display: flex; align-items: center; gap: var(--s-3);
  font-size: var(--fs-2xs);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-faint);
}
.lane__boundary::before,
.lane__boundary::after {
  content: ""; flex: 1 1 auto;
  border-block-start: 1px dashed var(--hairline-firm);
}
.lane--theirs .lane__node[data-side="net"] {
  background: var(--danger-soft);
}
.lane--theirs .lane__node[data-side="net"] svg { color: var(--danger); }

.lane--ours .lane__node[data-side="device"] { background: var(--signal-soft); }
.lane--ours .lane__node[data-side="device"] svg { color: var(--signal-text); }

.lane__verdict {
  display: flex; align-items: flex-start; gap: var(--s-2);
  font-size: var(--fs-xs); color: var(--text-muted);
  padding-block-start: var(--s-2);
  border-block-start: 1px solid var(--hairline-faint);
}
.lane__verdict svg { inline-size: 16px; block-size: 16px; flex: none; margin-block-start: 2px; }
.lane--ours .lane__verdict svg   { color: var(--signal-text); }
.lane--theirs .lane__verdict svg { color: var(--danger); }

/* The check anyone can run themselves. A privacy claim that can be verified
   in fifteen seconds is worth more than one that has to be believed. */
.privacy__proof {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-card);
  background: var(--primary-soft);
  font-size: var(--fs-xs); color: var(--text);
}
.privacy__proof code {
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  background: var(--surface); padding: 2px 6px; border-radius: var(--r-input);
}

/* ==========================================================================
   LEARNING CENTRE CARDS
   ========================================================================== */
.learn-card {
  display: flex; flex-direction: column; gap: var(--s-2);
  padding: var(--s-5);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  background: var(--surface);
  text-decoration: none;
  transition: transform var(--t-mid) var(--ease-out),
              box-shadow var(--t-mid) var(--ease-out),
              border-color var(--t-mid) var(--ease-out);
}
@media (prefers-reduced-motion: no-preference) {
  .learn-card:hover { transform: translateY(-2px); }
}
.learn-card:hover { border-color: var(--primary); box-shadow: var(--elev-hover); }

.learn-card__kind {
  align-self: flex-start;
  font-size: var(--fs-2xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--primary-text);
  background: var(--primary-soft);
  padding: 3px var(--s-2); border-radius: var(--r-pill);
}
.learn-card__title {
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--fs-md); color: var(--text-strong);
}
.learn-card__dek { font-size: var(--fs-xs); color: var(--text-muted); }
.learn-card__meta {
  margin-block-start: auto; padding-block-start: var(--s-3);
  font-size: var(--fs-2xs); color: var(--text-faint);
  font-family: var(--font-mono);
}

/* The numbered step list an article's HowTo schema is generated from. The
   markup is an <ol>; these are the numbers, drawn rather than borrowed from
   the list marker so they can carry the brand colour. */
.howto { display: grid; gap: var(--s-4); counter-reset: howto; }
.howto__step {
  display: grid; grid-template-columns: 2rem 1fr; gap: var(--s-4);
  counter-increment: howto;
}
.howto__step::before {
  content: counter(howto);
  inline-size: 2rem; block-size: 2rem;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--primary); color: var(--text-on-accent);
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 600;
}
.howto__title {
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--fs-md); color: var(--text-strong);
  margin-block-end: var(--s-1);
}
.howto__body { color: var(--text-muted); }

/* The category label on a search result. Quiet — it is a disambiguator, not a
   headline — but it is TEXT, because the tinted icon beside it carries the
   same information in colour alone and colour alone is not a label. */
.tsearch__tags {
  display: flex; align-items: center; gap: var(--s-2);
  margin-inline-start: auto; flex: none;
}
.tsearch__cat {
  font-size: var(--fs-2xs);
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
  color: var(--text-faint);
  white-space: nowrap;
}
