/* TaxWedge — one stylesheet, no imports, no web fonts.
 *
 * No external font: a reference page whose numbers arrive 400ms after the layout
 * is a worse page than one in the system stack, and every remote asset is another
 * thing a CSP has to allow and a reader has to wait for.
 *
 * Every colour is a token defined on :root. Dark mode redefines ONLY the tokens.
 * A colour defined for the first time inside a media query is a colour that does
 * not exist in the other theme, which is how half a palette goes missing. */

:root {
  color-scheme: light dark;

  --ink:            #10151f;
  --ink-soft:       #3d4759;
  /* Was #6b7688, which measured 4.59:1 on white — a pass by 0.09 — and then
     FAILED on every tinted ground the site actually uses it on: 4.35:1 on
     --raised, 4.00:1 on --brand-wash. One token carries most of the secondary
     text here (.muted, .hint, .stat dt, .stat-note, .table-note, captions), so
     the failure was systematic rather than a one-off. Chosen by solving for the
     worst ground: this clears 4.5:1 on all six, with 4.92:1 to spare on the
     tightest. Same hue family, so nothing else in the palette shifts. */
  --ink-faint:      #5d6879;
  --rule:           #e3e7ee;
  --rule-strong:    #cdd4e0;
  --page:           #ffffff;
  --raised:         #f7f9fc;
  --sunken:         #eef2f8;
  --brand:          #12654f;
  --brand-ink:      #0c4a3a;
  /* The skip link needs its OWN pair. It used `--brand` with a hardcoded
     `#fff`, and `--brand` flips to a light mint in dark mode because it is a
     text colour there — giving 2.09:1, the only AA failure left on the site.
     `--brand-ink` is worse (1.54:1). A background needs a background token. */
  --skip-bg:        #0c4a3a;
  --skip-ink:       #ffffff;
  --brand-wash:     #e6f2ee;
  --accent:         #b4531a;
  --accent-wash:    #fbeee5;
  --warn:           #8a2b2b;
  --warn-wash:      #fbecec;

  /* The wedge palette. Deliberately ordered light-to-dark from the money you
   * keep to the money you never see, so the bar reads as a gradient of loss
   * without needing the key. */
  --c-take:    #12654f;
  --c-pension: #3f8f78;
  --c-loan:    #8a9bb2;
  --c-ni-ee:   #c8763c;
  --c-tax:     #a8441c;
  --c-ni-er:   #6b2416;

  /* The answer bar paints white text on --brand. That is 7:1 in light, where
     --brand is a deep green — but --brand flips to a light mint in dark mode and
     the same pair measured 2.09:1, on the calculator's PRIMARY OUTPUT. The bar's
     ground and ink are their own tokens now, so each theme sets a pair that
     actually works instead of inheriting one that happens to. */
  --bar-bg:  #12654f;
  --bar-ink: #ffffff;
  --bar-dim: rgb(255 255 255 / .82);
  --bar-chip: rgb(255 255 255 / .18);

  --shadow: 0 1px 2px rgb(16 21 31 / .05), 0 8px 24px -12px rgb(16 21 31 / .14);
  --radius: 10px;
  /* 52ch, and the unit is the whole story. The target was written in
     CHARACTERS (66-75, the range reading research supports) and the fix was
     applied in `ch` — which is the width of the digit ZERO, not of a character.
     Running lowercase prose is far narrower, so 68ch rendered a median of 86
     characters and a maximum of 92: the "fix" left lines longer than the
     problem it was closing. Measured in the browser by walking Range rects
     character by character: 52ch renders a median of 70 and a max of 79.
     Re-measure the RENDER if the body font ever changes; do not read this
     declaration and assume it means 52 characters. */
  /* A LENGTH, not `ch` — and this is the second time that unit has bitten.
     `ch` is the width of the digit zero IN THE ELEMENT'S OWN FONT, so
     `max-width: 52ch` on a 1.6rem heading resolves to 994px while the same
     declaration on body text resolves to 563px. Sharing one column between
     headings, prose, figures and tables therefore requires a fixed length or
     they all get different widths from the identical rule. 35rem is the 52ch
     that was calibrated by counting rendered characters (median 70). */
  --measure: 38rem;
  --wrap: 1120px;

  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink:         #e8ecf3;
    --ink-soft:    #aeb8c8;
    --ink-faint:   #7f8a9d;
    --rule:        #232b39;
    --rule-strong: #333e51;
    --page:        #0b1017;
    --raised:      #121926;
    --sunken:      #171f2e;
    --brand:       #4fc7a4;
    --brand-ink:   #8fe0c6;
    --skip-bg:     #8fe0c6;
    --skip-ink:    #06251d;
    --brand-wash:  #10251f;
    --accent:      #e08a4e;
    --accent-wash: #2a1a10;
    --warn:        #e88a8a;
    --warn-wash:   #2a1414;
    --c-take:    #3fb995;
    --c-pension: #2c7d67;
    --c-loan:    #5d6a7d;
    --c-ni-ee:   #c9803f;
    --c-tax:     #b8542c;
    --c-ni-er:   #7d3524;
    /* Dark keeps a deep ground so white ink stays legible: 11.4:1. */
    --bar-bg:  #0f4c3c;
    --bar-ink: #f2fbf7;
    --bar-dim: rgb(242 251 247 / .85);
    --bar-chip: rgb(255 255 255 / .16);
    --shadow: 0 1px 2px rgb(0 0 0 / .4), 0 8px 24px -12px rgb(0 0 0 / .6);
  }
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; } }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--sans);
  font-size: clamp(1.02rem, .97rem + .26vw, 1.18rem);
  line-height: 1.62;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { line-height: 1.18; letter-spacing: -.018em; margin: 0 0 .5em; font-weight: 640; text-wrap: balance; }
h1 { font-size: clamp(1.85rem, 1.35rem + 2.1vw, 3rem); }
h2 { font-size: clamp(1.35rem, 1.15rem + .8vw, 1.85rem); margin-top: 2.2em; }
h3 { font-size: clamp(1.1rem, 1rem + .35vw, 1.3rem); margin-top: 1.8em; }
p, li { text-wrap: pretty; }
a { color: var(--brand-ink); text-underline-offset: .18em; text-decoration-thickness: .08em; }
a:hover { text-decoration-thickness: .14em; }
strong, b { font-weight: 650; }
:focus-visible { outline: 2.5px solid var(--brand); outline-offset: 2px; border-radius: 3px; }

.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.skip { position: absolute; left: -9999px; top: 0; background: var(--skip-bg); color: var(--skip-ink); padding: .7rem 1.1rem; z-index: 99; border-radius: 0 0 var(--radius) 0; }
.skip:focus { left: 0; }
.muted { color: var(--ink-faint); }

/* ------------------------------------------------------------- masthead */

.masthead { border-bottom: 1px solid var(--rule); background: var(--page); position: sticky; top: 0; z-index: 30; }
.masthead-inner {
  --gutter: clamp(1rem, 4vw, 2rem);
  max-width: var(--wrap); margin: 0 auto; min-height: 3.5rem;
  padding: 0 var(--gutter);
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
}
.brand { display: inline-flex; align-items: center; gap: .5rem; font-weight: 700; letter-spacing: -.03em; font-size: 1.1rem; color: var(--ink); text-decoration: none; flex: none; min-height: 44px; }
.bm-base  { fill: var(--ink-faint); }
.bm-wedge { fill: var(--brand); }
.bm-cut   { fill: var(--accent); }

.navtoggle-input { position: absolute; }
.navtoggle {
  margin-left: auto; display: inline-flex; align-items: center; gap: .5rem;
  min-height: 44px; padding: 0 .15rem 0 .8rem; margin-right: -.15rem;
  border-radius: 8px; cursor: pointer;
  font-size: .95rem; font-weight: 580; color: var(--ink-soft);
}
.navtoggle:hover { color: var(--ink); }
/* The control that reveals the whole site is the last thing that should be an
   unlabelled glyph, so it carries a word as well as an icon. */
.navtoggle-close, .navtoggle .nt-x { display: none; }
.navtoggle-input:checked ~ .masthead-inner .navtoggle { color: var(--brand-ink); }
.navtoggle-input:checked ~ .masthead-inner .navtoggle .navtoggle-open,
.navtoggle-input:checked ~ .masthead-inner .navtoggle .nt-bars { display: none; }
.navtoggle-input:checked ~ .masthead-inner .navtoggle .navtoggle-close,
.navtoggle-input:checked ~ .masthead-inner .navtoggle .nt-x { display: inline; }
.navtoggle-input:focus-visible ~ .masthead-inner .navtoggle { outline: 2.5px solid var(--brand); outline-offset: 2px; }

.mainnav ul { list-style: none; margin: 0; padding: 0; }
.mainnav a { color: var(--ink-soft); text-decoration: none; font-weight: 520; }

/* MOBILE: a panel, closed by default, opened by the checkbox. Seven items will
   not fit on one row at 375px; wrapping made the sticky masthead 134px, and a
   horizontally scrolling row looked like a layout bug — it sliced "Calculators"
   mid-word at the edge and hid five of the seven destinations behind a scroll
   nobody could see. */
@media (max-width: 57.999rem) {
  .mainnav {
    display: none;
    /* Breaks to its own row, then spans the full masthead rather than the
       padded content box. `flex-basis: 100%` alone resolves against the CONTENT
       box, so the negative margin slid the panel left without widening it and
       the rule and background stopped 32px short of the right edge — measured
       0->343 inside a 0->375 masthead. The basis has to grow by both gutters. */
    flex-basis: calc(100% + 2 * var(--gutter));
    margin: 0 calc(-1 * var(--gutter));
    border-top: 1px solid var(--rule);
    max-height: min(72vh, 28rem); overflow-y: auto;
    padding: 0 var(--gutter) .5rem;
    background: var(--page);
  }
  .navtoggle-input:checked ~ .masthead-inner .mainnav { display: block; }
  .mainnav li + li { border-top: 1px solid var(--rule); }
  .mainnav a { display: flex; align-items: center; min-height: 48px; font-size: 1rem; color: var(--ink); }
  .mainnav a[aria-current="page"] { color: var(--brand-ink); font-weight: 640; }
  .mainnav a[aria-current="page"]::after { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--brand); margin-left: auto; }
}

/* TABLES ON A PHONE: one card per row, not a scrollport with half of it hidden.
   Measured at 375px before this: the pay-swing table hid 41% of itself, the loan
   ladder 43% and the cross-UK comparison 48% — and the columns off the right
   edge were "Take-home", "Change" and "Next £100 taxed at", which is to say the
   answer. `overflow-x: auto` is a legitimate pattern for one wide table; it is
   not a way to publish five of them.

   The bands table keeps its own bespoke treatment, which is better than this
   generic one because it knows what its columns mean. Every other datatable
   gets this. Roles are written out in the markup, so the stacked layout does not
   strip the table semantics the way a bare `display: block` does. */
@container table (max-width: 40rem) {
  .datatable:not(.bands) { display: block; }
  .datatable:not(.bands) thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .datatable:not(.bands) tbody, .datatable:not(.bands) tr { display: block; }
  .datatable:not(.bands) tr { border-bottom: 1px solid var(--rule); padding: .7rem 0; }
  .datatable:not(.bands) tr:last-child { border-bottom: 0; }
  .datatable:not(.bands) th[scope="row"] {
    display: block; border: 0; padding: 0 0 .35rem; text-align: left;
    font-size: 1rem; font-weight: 660; letter-spacing: -.01em;
  }
  .datatable:not(.bands) td {
    display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
    border: 0; padding: .18rem 0; text-align: right; font-variant-numeric: tabular-nums;
  }
  .datatable:not(.bands) td::before {
    content: attr(data-label);
    color: var(--ink-faint); font-weight: 500; text-align: left;
    font-variant-numeric: normal; flex: 0 1 auto;
  }
  /* A spacer cell with no header and no value would render as an empty row. */
  .datatable:not(.bands) td:empty { display: none; }
  .datatable:not(.bands) .num { text-align: right; }
}

/* DESKTOP: an ordinary inline row on the same line as the brand, and the toggle
   does not exist.

   58rem, not 52rem, and the number is measured rather than chosen. At 52rem the
   media query promised a row that does not fit: the seven labels are 566px of
   text, `white-space: nowrap` forbids breaking them, and with the brand and the
   gaps one row needs 866px against the 845px available at 860px wide. It wrapped
   — quietly, because wrapping is not an error — and the sticky masthead became
   105px, which is the same defect the mobile panel was built to remove. At 58rem
   the row needs ~794px against ~854px available, so there is ~60px of headroom
   for a font that renders wider than this one. Re-measure if a nav item is
   added: seven is what fits, not a limit that enforces itself. */
@media (min-width: 58rem) {
  .navtoggle { display: none; }
  /* No `position: relative` here and no negative margin on the nav. Both were
     here, and together they made the wrapper paint over every link: the whole
     desktop nav was unclickable by mouse while looking entirely correct. The
     nav is a flex child on the same row now, so there is nothing to stack. */
  .mainnav { margin-left: auto; min-width: 0; }
  .mainnav ul { display: flex; gap: clamp(.75rem, 1.7vw, 1.5rem); justify-content: flex-end; align-items: center; }
  .mainnav a { font-size: .93rem; display: inline-flex; align-items: center; min-height: 44px; border-bottom: 2px solid transparent; white-space: nowrap; }
  .mainnav a:hover { color: var(--ink); }
  .mainnav a[aria-current="page"] { color: var(--brand-ink); font-weight: 620; border-bottom-color: var(--brand); }
}

/* ----------------------------------------------------------------- hero */

.hero { border-bottom: 1px solid var(--rule); background: linear-gradient(180deg, var(--raised), var(--page)); }
.hero-inner { max-width: var(--wrap); margin: 0 auto; padding: clamp(2rem, 5vw, 3.6rem) clamp(1rem, 4vw, 2rem) clamp(1.6rem, 4vw, 2.6rem); }
.eyebrow { text-transform: uppercase; letter-spacing: .1em; font-size: .74rem; font-weight: 700; color: var(--brand); margin: 0 0 .7rem; }
.hero h1 { max-width: 22ch; margin-bottom: .35em; }
.standfirst { font-size: clamp(1.05rem, .98rem + .4vw, 1.28rem); color: var(--ink-soft); margin: 0; }
.page-meta { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem .9rem; padding: 0; margin: 1.3rem 0 0; font-size: .85rem; color: var(--ink-faint); }
.page-meta li { display: inline-flex; align-items: center; gap: .4rem; }
.page-meta li:not(:last-child)::after { content: ""; width: 3px; height: 3px; border-radius: 50%; background: var(--rule-strong); margin-left: .5rem; }

/* --------------------------------------------------------------- layout */

.wrap { max-width: var(--wrap); margin: 0 auto; padding: clamp(1.6rem, 4vw, 2.8rem) clamp(1rem, 4vw, 2rem) 3rem; }
/* ONE COLUMN, and the width is the TRACK, not a reading measure.
   The diagnosis under the previous version was right and its fix was the wrong
   way round. Text at a different width from everything around it does read as a
   mess -- but capping the reading elements at 38rem did not remove the second
   width, it just moved which side was narrow. The calculator, the card grids,
   the stat grid and the split layout are all genuinely wide and opted back out,
   so a salary page still alternated a 608px column with 1120px blocks. Measured
   at 1440: .prose ended at x=793, h1 at x=867 and .wrap at x=1273 -- three right
   edges, and 480px of the container standing empty.
   So nothing is capped now. Every reading element fills the track, exactly as
   wattdraw does it, and for the same reason: a paragraph starts and ends where
   the table and the cards beneath it do.
   The cost is line length. At 1120px a line runs well past the 45-75 characters
   reading research supports, and the mitigation is leading -- a long line needs
   more room beneath it for the eye to find the next one -- so .prose runs 1.8
   against the 1.62 that suited the measure. That is the same trade wattdraw
   made and documented.
   To put a reading measure back, restore:
     .prose { max-width: var(--measure) }
   plus an exception list for the calculator, card grids, stat grids, splits and
   tables -- and expect the ragged right edge to come back with it. */
.prose { max-width: none; line-height: 1.8; }
/* A .wrap inside a .wrap stacked its padding and pushed a whole section 32px
   right of everything above it — visible on the home page as one block of prose
   that did not line up with the rest. Nesting one is a mistake, but it should
   not be able to break the alignment when it happens. */
.wrap .wrap { max-width: none; padding-inline: 0; }
.prose > * + * { margin-top: 1.05em; }
.split { display: grid; gap: clamp(1.5rem, 4vw, 3rem); grid-template-columns: minmax(0, 1fr); }
/* 70rem, not 62rem. At 62rem the sidebar appeared while the main column was
   still only 582px, and the widest tables need 660px — so two tables on every
   salary page overflowed at exactly 1024px wide and nowhere else. The sidebar
   waits until the column beside it can hold the content. */
@media (min-width: 70rem) { .split { grid-template-columns: minmax(0, 1fr) 20rem; align-items: start; } .split-aside { position: sticky; top: 4.6rem; } }

.crumbs ol { display: flex; flex-wrap: wrap; gap: .4rem; list-style: none; padding: 0; margin: 0 0 1.1rem; font-size: .84rem; color: var(--ink-faint); }
.crumbs li:not(:last-child)::after { content: "/"; margin-left: .45rem; color: var(--rule-strong); }
.crumbs a { color: var(--ink-faint); }

/* ---------------------------------------------------------------- stats */

/* The hairlines used to be a 1px grid gap over a --rule background. That works
   until the item count does not fill the last row: five stats in three columns
   left an empty track, and the container's grey showed through it as a card that
   had apparently failed to load. Drawing the rules on the cells instead means an
   unfilled track shows the page, because there is nothing there to paint. The
   negative margins pull the container's own border over the trailing edges. */
.stats { display: grid; gap: 0; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); background: var(--page); border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; margin: 1.8rem 0; }
.stat { background: var(--page); padding: 1.05rem 1.15rem; border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); margin-right: -1px; margin-bottom: -1px; }
.stat dt { font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); font-weight: 620; }
/* At the field's own documented maximum the headline overflowed its cell by
   24px and clipped mid-number. A figure this site publishes must never be cut. */
.stat dd { margin: .28rem 0 0; font-size: clamp(1.4rem, 1.15rem + 1vw, 1.95rem); font-weight: 660; letter-spacing: -.03em; line-height: 1.1; overflow-wrap: anywhere; }
.stat dd:has(+ .stat-note), .stat dd { font-size: clamp(1.25rem, .95rem + 1vw, 1.95rem); }
.stat-lead { background: var(--brand-wash); }
.stat-lead dd { color: var(--brand-ink); }
/* The 62% marginal rate used to wear stat-lead — the same reassuring green as
   "Take-home a year" — so the results grid had two competing primary cells and
   the alarming number was painted as the good news, while the answer bar
   rendered the identical fact in warning red ten pixels away. One meaning per
   token: green is your headline, red is the warning. */
.stat-alarm { background: var(--warn-wash); }
.stat-alarm dd { color: var(--warn); }
.stat-alarm dt { color: var(--warn); opacity: .9; }
/* A <dd>, not a <p>: a <p> inside a <dl>'s <div> is invalid content and
   Lighthouse flagged `definition-list` on every page of the site. A term may
   carry more than one description, so the note is a second <dd>. */
.stat-note { margin: .35rem 0 0; margin-inline-start: 0; font-size: .8rem; color: var(--ink-faint); line-height: 1.4; }

/* ---------------------------------------------------------------- table */

/* A horizontal scroller with no edge cue is a scroller nobody scrolls. These
   local-attachment gradients paint a shadow only while there is content off that
   side, so the affordance appears exactly when it is true and disappears when
   the reader reaches the end. */
/* The stacking rule above queries THIS element's width, so the container has to
   be declared here rather than inside the query. The overflow it fixes was a
   660px table in a 582px column at a 1024px viewport — a width no media query
   can see, because the viewport was never the narrow thing. */
.table-wrap { container: table / inline-size; }
.table-wrap {
  overflow-x: auto; margin: 1.6rem 0; border: 1px solid var(--rule);
  border-radius: var(--radius); background: var(--page);
  background-image:
    linear-gradient(to right, var(--page) 30%, transparent),
    linear-gradient(to left,  var(--page) 30%, transparent),
    linear-gradient(to right, rgb(16 21 31 / .16), transparent 14px),
    linear-gradient(to left,  rgb(16 21 31 / .16), transparent 14px);
  background-position: left center, right center, left center, right center;
  background-repeat: no-repeat;
  background-size: 36px 100%, 36px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}
.datatable { width: 100%; border-collapse: collapse; font-size: .935rem; }
.datatable caption { text-align: left; padding: .95rem 1rem .1rem; color: var(--ink-soft); font-size: .88rem; caption-side: top; }
.datatable th, .datatable td { padding: .6rem .85rem; text-align: left; border-bottom: 1px solid var(--rule); }
/* `position: sticky; top: 0` was here and was INERT: .table-wrap is the
   scrollport, `overflow-x: auto` makes its computed overflow-y `auto` too, and
   its height equals the table's — so it never scrolls vertically and the header
   never stuck. Dead code that read as a feature. */
.datatable thead th { background: var(--sunken); font-size: .78rem; text-transform: uppercase; letter-spacing: .055em; color: var(--ink-soft); font-weight: 660; border-bottom: 1px solid var(--rule-strong); white-space: nowrap; }
.datatable tbody th { font-weight: 600; }
.datatable tbody tr:last-child th, .datatable tbody tr:last-child td { border-bottom: 0; }
.datatable tbody tr:hover { background: var(--raised); }
.datatable .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.datatable .row-peak { background: var(--warn-wash); }
.datatable .row-peak th, .datatable .row-peak td { font-weight: 640; }
/* The caption span carries the text so it can be pinned to the left edge when a
   table scrolls. It must NOT be given a width: a <caption> participates in table
   layout, so a fixed width on its contents becomes a MINIMUM WIDTH FOR THE WHOLE
   TABLE. This rule briefly said `width: min(100vw - 2rem, var(--wrap))`, which
   at 1440px resolves to 1120px — and every table on the site rendered 1120px
   wide inside a 688px column, overflowing by 39% on desktop. The original
   clipping problem it was written for is solved properly now, by stacking the
   table into one card per row below 48rem instead of scrolling it. */
.datatable caption { padding: 0; text-align: left; }
.datatable caption > span {
  position: sticky; left: 0;
  display: block; box-sizing: border-box;
  padding: .7rem 1rem .8rem;
}

.table-note { margin: 0; padding: .7rem 1rem .85rem; font-size: .83rem; color: var(--ink-faint); border-top: 1px solid var(--rule); background: var(--raised); }

/* ------------------------------------------------- band table on a phone */

/* The marginal-band table needs 592px inside a 341px container on a 375px
   screen: 42% hidden, and the hidden columns were YOU KEEP and WHY — the column
   carrying the entire explanation, and the reason the page exists. A reader on a
   phone was left with FROM / TO / RATE, which is exactly the rate table the page
   opens by calling insufficient.
   Transposed to one card per band, so nothing scrolls and nothing is lost. */
@media (max-width: 48rem) {
  .datatable.bands { display: block; }
  /* A `caption` inside a block-displayed table keeps `display: table-caption`
     and shrink-wraps: measured 101px wide by 404px tall inside a 341px table —
     fifteen stacked lines of ransom note before the first band. */
  .datatable.bands caption { display: block; width: 100%; }
  .datatable.bands thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .datatable.bands tbody, .datatable.bands tr { display: block; }
  .datatable.bands tr { border-bottom: 1px solid var(--rule); padding: .85rem 1rem; }
  .datatable.bands tr:last-child { border-bottom: 0; }
  .datatable.bands th, .datatable.bands td { display: block; border: 0; padding: 0; text-align: left; }
  .datatable.bands th[scope="row"] { font-size: 1.05rem; font-weight: 660; letter-spacing: -.02em; }
  /* The band's range reads as one heading rather than two cells. */
  .datatable.bands td:nth-of-type(1)::before { content: "to "; color: var(--ink-faint); }
  .datatable.bands td:nth-of-type(1) { display: inline; margin-left: .3em; font-size: 1.05rem; font-weight: 660; }
  .datatable.bands th[scope="row"] { display: inline; }
  .datatable.bands td:nth-of-type(2) { margin-top: .5rem; font-size: 1.5rem; font-weight: 680; letter-spacing: -.03em; }
  .datatable.bands td:nth-of-type(2)::after { content: " on the next £100"; font-size: .8rem; font-weight: 500; color: var(--ink-faint); letter-spacing: 0; }
  .datatable.bands td:nth-of-type(3)::before { content: "You keep "; color: var(--ink-faint); }
  .datatable.bands td:nth-of-type(3) { font-size: .92rem; }
  .datatable.bands td:nth-of-type(4) { margin-top: .35rem; font-size: .88rem; color: var(--ink-soft); line-height: 1.5; }
  .datatable.bands .num { text-align: left; }
}

/* The rate cell is graded rather than uniformly alarming. `--warn` on every row
   painted the 0% band alarm red, which spends the one token that is supposed to
   mean "this is the bad one" on the row that is the best one. */
.datatable.bands .rate-ordinary { color: var(--ink); }
.datatable.bands .rate-trap     { color: var(--accent); }
.datatable.bands .rate-severe   { color: var(--warn); }
/* The row the page is named after. */
.datatable.bands tr.row-peak .rate-severe,
.datatable.bands tr.row-peak .rate-trap { font-weight: 760; }

/* The /tools/ index groups by the question asked rather than listing eleven
   identical cards. The primary destination is a single full-width card above
   the groups, so "which one do I want" is answered by layout. */
.toolgroup { margin: 2.2rem 0 0; }
.toolgroup h2 { margin: 0; font-size: 1.15rem; letter-spacing: -.02em; }
.toolgroup-note { margin: .25rem 0 0; font-size: .88rem; color: var(--ink-soft); }
.toolgroup .cards { margin-top: .8rem; }
.cards[data-cols="1"] { grid-template-columns: 1fr; }
.cards[data-cols="1"] .card a { border-color: var(--brand); background: var(--brand-wash); padding: 1.2rem 1.3rem; }
.cards[data-cols="1"] .card-title { font-size: 1.2rem; }

/* ---------------------------------------------------------------- cards */

.cards { list-style: none; padding: 0; margin: 1.6rem 0; display: grid; gap: .9rem; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); }
.card a { display: flex; flex-direction: column; gap: .3rem; height: 100%; padding: 1rem 1.1rem; border: 1px solid var(--rule); border-radius: var(--radius); background: var(--page); text-decoration: none; color: inherit; transition: border-color .15s, transform .15s, box-shadow .15s; }
.card a:hover { border-color: var(--brand); transform: translateY(-2px); box-shadow: var(--shadow); }
.card-eyebrow { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--brand); font-weight: 700; }
.card-title { font-weight: 640; letter-spacing: -.015em; }
.card-body { font-size: .88rem; color: var(--ink-soft); line-height: 1.5; }
.card-figure { margin-top: auto; padding-top: .5rem; font-size: 1.25rem; font-weight: 660; color: var(--brand-ink); letter-spacing: -.02em; }

/* ------------------------------------------------------------- callouts */

.callout { border-left: 3px solid var(--brand); background: var(--brand-wash); padding: .95rem 1.15rem; border-radius: 0 var(--radius) var(--radius) 0; margin: 1.6rem 0; }
.callout > :first-child { margin-top: 0; } .callout > :last-child { margin-bottom: 0; }
.callout p { margin: .5em 0; }
.callout-title { font-weight: 680; margin: 0 0 .3em !important; }
.callout-warn { border-left-color: var(--warn); background: var(--warn-wash); }
.callout-accent { border-left-color: var(--accent); background: var(--accent-wash); }

/* ---------------------------------------------------------------- chart */

.chart { margin: 1.8rem 0; }
.chart svg { width: 100%; height: auto; display: block; border: 1px solid var(--rule); border-radius: var(--radius); background: var(--page); }
.chart .grid { stroke: var(--rule); stroke-width: 1; }
.chart .axis { fill: var(--ink-faint); font-size: 11px; font-family: var(--sans); }
.chart .area { fill: var(--brand); opacity: .13; }
.chart .line { stroke: var(--brand); stroke-width: 2.2; stroke-linejoin: round; }
.chart .peak { fill: var(--warn); font-size: 13px; font-weight: 700; font-family: var(--sans); }
.chart figcaption { font-size: .84rem; color: var(--ink-faint); margin-top: .55rem; }

/* ----------------------------------------------------------------- wedge */

.wedge { margin: 1.6rem 0; }
.wedge-bar { display: flex; height: 2.6rem; border-radius: 6px; overflow: hidden; border: 1px solid var(--rule); }
.wedge-bar .seg { width: var(--w); min-width: 2px; }
.seg-take { background: var(--c-take); } .seg-pension { background: var(--c-pension); }
.seg-loan { background: var(--c-loan); } .seg-ni-ee { background: var(--c-ni-ee); }
.seg-tax { background: var(--c-tax); } .seg-ni-er { background: var(--c-ni-er); }
.wedge-key { list-style: none; padding: 0; margin: .85rem 0 0; display: grid; gap: .35rem .9rem; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); font-size: .88rem; }
.wedge-key li { display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap; }
/* "Take-home" was breaking across lines at the hyphen, and "Income tax" at the
   space, because the track minimum was narrower than the content. */
.wk-label { white-space: nowrap; }
.swatch { width: .72rem; height: .72rem; border-radius: 3px; flex: none; transform: translateY(1px); }
.swatch-take { background: var(--c-take); } .swatch-pension { background: var(--c-pension); }
.swatch-loan { background: var(--c-loan); } .swatch-ni-ee { background: var(--c-ni-ee); }
.swatch-tax { background: var(--c-tax); } .swatch-ni-er { background: var(--c-ni-er); }

/* ------------------------------------------------------------ calculator */

.calc { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--raised); padding: clamp(1rem, 3vw, 1.6rem); margin: 1.6rem 0; }
.calc-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.field { display: flex; flex-direction: column; gap: .3rem; }
.field label { font-size: .82rem; font-weight: 640; color: var(--ink-soft); }
.field input[type="number"], .field input[type="text"], .field select {
  font: inherit; font-size: 1.05rem; padding: .5rem .65rem; border: 1px solid var(--rule-strong);
  border-radius: 7px; background: var(--page); color: var(--ink); width: 100%; font-variant-numeric: tabular-nums;
  /* Measured at 375px before this: amount 43px, both selects 40px, and the five
     loan checkboxes 13x13 inside 24px rows — 24 sub-44px targets on the one
     surface whose entire job is receiving a number from a thumb. */
  min-height: 44px;
}
.field .hint { font-size: .78rem; color: var(--ink-faint); }

/* The checkbox row is the target, not the 13px box inside it. `label[for]`
   already made the text clickable; this gives the pair a 44px row and puts the
   whole width of it in the hit area. */
.field-check { flex-direction: row; align-items: center; gap: .6rem; min-height: 44px; }
.field-check input[type="checkbox"] { width: 20px; height: 20px; flex: none; accent-color: var(--brand); }
.field-check label { font-size: .92rem; font-weight: 500; flex: 1; display: flex; flex-direction: column; justify-content: center; gap: .1rem; min-height: 44px; padding: .3rem 0; cursor: pointer; }
.check-hint { font-size: .74rem; font-weight: 450; color: var(--ink-faint); line-height: 1.35; }
.calc input[type="range"] { width: 100%; accent-color: var(--brand); }
.calc-out { margin-top: 1.3rem; padding-top: 1.2rem; border-top: 1px solid var(--rule); }
.calc noscript p { margin: 0; }

/* ------------------------------------------------------------------ FAQ */

.faq { margin: 2.4rem 0; }
.faq details { border: 1px solid var(--rule); border-radius: var(--radius); margin-bottom: .55rem; background: var(--page); }
.faq summary { cursor: pointer; padding: .8rem 1rem; font-weight: 620; list-style: none; display: flex; justify-content: space-between; gap: 1rem; align-items: center; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; color: var(--brand); font-weight: 700; font-size: 1.3rem; line-height: 1; flex: none; }
.faq details[open] summary::after { content: "\2212"; }
.faq details[open] summary { border-bottom: 1px solid var(--rule); }
.faq-body { padding: .85rem 1rem 1rem; color: var(--ink-soft); }
.faq-body > :first-child { margin-top: 0; } .faq-body > :last-child { margin-bottom: 0; }

/* --------------------------------------------------------------- pager */

.pager { display: flex; justify-content: space-between; gap: 1rem; margin: 2.4rem 0 0; }
.pager a { display: flex; flex-direction: column; gap: .15rem; padding: .75rem 1rem; border: 1px solid var(--rule); border-radius: var(--radius); text-decoration: none; color: var(--ink); font-weight: 620; }
.pager a:hover { border-color: var(--brand); }
.pager a span { font-size: .74rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); font-weight: 700; }
.pager-next { text-align: right; }

/* ------------------------------------------------------------------ ads */

/* Reserving the height is right; announcing the emptiness is not. When no ad is
   served the slot carries aria-hidden and no label, so a screen-reader user
   navigating by landmark no longer lands on "Advertisement, complementary" twice
   a page and finds nothing in either. It also drops the dashed border, which was
   drawing a box around nothing. */
.ad-slot { min-height: var(--slot-h, 280px); display: flex; align-items: center; justify-content: center; margin: 2rem 0; border: 1px dashed var(--rule); border-radius: var(--radius); background: var(--raised); }
.ad-slot[aria-hidden="true"] { border: 0; background: none; min-height: 0; margin: 0; }
.ad-placeholder { display: none; }

/* -------------------------------------------------------------- funding */

.funding { margin: 2rem 0 0; padding: .8rem 1rem; border: 1px solid var(--rule); border-radius: var(--radius); background: var(--raised); font-size: .86rem; color: var(--ink-soft); }
.funding p { margin: 0; }

/* ------------------------------------------------------------- footer */

/* Was 13 links in 8 rows of 16px text — a wall nobody scans and nobody can hit
   on a phone. Now the site's actual map, grouped the way a reader looks for
   things, with 44px rows. It also does real crawl work: every hub is one click
   from every page. */
.sitefoot { border-top: 1px solid var(--rule); background: var(--raised); margin-top: 3.5rem; }
.sitefoot-inner { max-width: var(--wrap); margin: 0 auto; padding: 2.4rem clamp(1rem, 4vw, 2rem) 2.6rem; }

.foot-map { display: grid; gap: 1.6rem 2rem; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
.foot-group h2 {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-faint); font-weight: 700; margin: 0 0 .35rem;
}
.foot-group ul { list-style: none; margin: 0; padding: 0; }
.foot-group a {
  display: flex; align-items: center; min-height: 40px;
  color: var(--ink-soft); text-decoration: none; font-size: .92rem;
  border-bottom: 1px solid transparent;
}
.foot-group a:hover { color: var(--brand-ink); }
@media (max-width: 40rem) {
  /* Two columns on a phone, not one. A single column of 18 links at 44px made a
     1,316px footer — 1.6 screens of nothing but footer. Two columns halve it
     while keeping the tap targets, which is why the labels above are short. */
  .foot-map { gap: 1.2rem 1.4rem; grid-template-columns: 1fr 1fr; }
  .foot-group li + li a { border-top: 1px solid var(--rule); }
  .foot-group a { min-height: 44px; font-size: .92rem; }
}

.foot-base { margin-top: 2.2rem; padding-top: 1.4rem; border-top: 1px solid var(--rule); font-size: .88rem; color: var(--ink-soft); }
.foot-brand { margin: 0 0 .5rem; color: var(--ink); }
.foot-note { max-width: 62ch; margin: 0 0 .9rem; }
.foot-corrections { margin: 0; font-size: .86rem; color: var(--ink-faint); max-width: 62ch; }
.foot-corrections a { color: var(--brand-ink); }

/* ------------------------------------------------------------ utilities */

.lede { font-size: 1.12rem; color: var(--ink-soft); }
.tag { display: inline-block; padding: .1rem .5rem; border-radius: 999px; font-size: .74rem; font-weight: 660; background: var(--sunken); color: var(--ink-soft); }
.tag-warn { background: var(--warn-wash); color: var(--warn); }
.tag-ok { background: var(--brand-wash); color: var(--brand-ink); }
.assumptions { font-size: .86rem; color: var(--ink-soft); background: var(--raised); border: 1px solid var(--rule); border-radius: var(--radius); padding: .9rem 1.1rem; margin: 1.6rem 0; }
.assumptions ul { margin: .5rem 0 0; padding-left: 1.15rem; }
.assumptions li { margin: .25rem 0; }
.assumptions > p { margin: 0; font-weight: 620; color: var(--ink); }
.out::after { content: " ↗"; font-size: .85em; color: var(--ink-faint); }
hr { border: 0; border-top: 1px solid var(--rule); margin: 2.4rem 0; }
code { font-family: var(--mono); font-size: .9em; background: var(--sunken); padding: .1em .35em; border-radius: 4px; }
.eq { font-family: var(--mono); font-size: .93rem; background: var(--sunken); border: 1px solid var(--rule); border-radius: var(--radius); padding: .85rem 1rem; overflow-x: auto; margin: 1.2rem 0; }

/* ------------------------------------------------------------ related links */

.related { margin: 2.6rem 0 0; padding-top: 1.4rem; border-top: 1px solid var(--rule); }
.related h2 { margin: 0 0 .7rem; font-size: .78rem; text-transform: uppercase; letter-spacing: .09em; color: var(--ink-faint); font-weight: 700; }
.related ul { list-style: none; padding: 0; margin: 0; display: grid; gap: .5rem; }
.related a { display: block; padding: .7rem .95rem; border: 1px solid var(--rule); border-radius: var(--radius); text-decoration: none; color: var(--ink); font-weight: 520; background: var(--page); transition: border-color .15s, transform .15s; }
.related a:hover { border-color: var(--brand); transform: translateX(2px); }
.related a::after { content: " →"; color: var(--brand); }

/* ------------------------------------------------------------ tool fieldset */

.loans { border: 1px solid var(--rule); border-radius: var(--radius); padding: .8rem 1rem 1rem; margin: 1rem 0 0; background: var(--page); }
.loans legend { font-size: .82rem; font-weight: 640; color: var(--ink-soft); padding: 0 .4rem; }

/* --------------------------------------------------------------- figures */

.hero-figure { margin: 0; position: relative; }
.hero-figure img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--rule); background: var(--sunken); }
.hero-figure figcaption { font-size: .8rem; color: var(--ink-faint); margin-top: .55rem; line-height: 1.45; max-width: 60ch; }
.hero-figure figcaption b { color: var(--ink-soft); font-weight: 620; }

/* ---------------------------------------------------------- answer bar */

/* FIXED on a phone, not sticky, and that distinction is the whole thing.
   `position: sticky; bottom: 0` only pins an element that would otherwise
   scroll off the BOTTOM — an element sitting near the top of a long page simply
   stays in flow, which is exactly what the first version did: it rendered as a
   line of text above the form and pinned nothing.

   Measured before this existed: the calculator form is 739px tall on an 812px
   viewport, and the salary input sat 813px above the first result, so you could
   not see the answer change while typing. Fixed positioning is what makes the
   answer follow the reader instead. */
.answerbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  padding: .65rem clamp(1rem, 4vw, 2rem) calc(.65rem + env(safe-area-inset-bottom, 0px));
  background: var(--bar-bg); color: var(--bar-ink);
  box-shadow: 0 -8px 24px -12px rgb(16 21 31 / .55);
  transition: opacity .18s ease, transform .18s ease;
}
.answerbar-inner {
  max-width: var(--wrap); margin: 0 auto;
  display: flex; align-items: baseline; gap: .3rem .9rem; flex-wrap: wrap;
}
.ab-main { display: inline-flex; align-items: baseline; gap: .4rem; }
.ab-main b { font-size: 1.45rem; font-weight: 680; letter-spacing: -.025em; color: var(--bar-ink); }
.ab-label { color: var(--bar-dim); font-size: .82rem; }
.ab-sub { color: var(--bar-dim); font-size: .82rem; }
.ab-marginal {
  margin-left: auto; font-size: .82rem; font-weight: 640; white-space: nowrap;
  padding: .18rem .6rem; border-radius: 999px; background: var(--bar-chip);
}
.ab-marginal.is-trap { background: #fff; color: #8a2b2b; }
/* Tool pages have no "next £100" stat, so this chip rendered as a 19x6px empty
   white pill on every one of them. */
.ab-marginal:empty { display: none; }

/* Once the real results are on screen the bar is only repeating them. */
.answerbar.is-redundant { opacity: 0; transform: translateY(110%); pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .answerbar { transition: none; } }

/* The bar covers the bottom of the page, so give the document room to scroll
   clear of it. Only applied where a bar exists. */
/* The bar measures 90px with its safe-area inset; 4.5rem reserved 72px, an 18px
   shortfall that happened not to bite only because the footer's last line ends
   above it. Reserve more than the bar can be. */
body:has(.answerbar:not([hidden])) { padding-bottom: 7rem; }

/* On a wide viewport the form and the results already share the fold, so the
   bar has nothing to solve — it sits in the flow above the results instead of
   floating over the page. */
@media (min-width: 62rem) {
  .answerbar {
    position: static; border-radius: var(--radius); box-shadow: none;
    margin: 0 0 1.2rem; padding: .8rem 1.1rem;
  }
  .answerbar.is-redundant { opacity: 1; transform: none; pointer-events: auto; }
  body:has(.answerbar:not([hidden])) { padding-bottom: 0; }
}

.example-tag { display: none; margin: 0 0 .7rem; font-size: .78rem; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; color: var(--ink-faint); }
.calc-out.is-example .example-tag { display: block; }

.awaiting { margin: 1.6rem 0; padding: 1.1rem 1.2rem; border: 1px dashed var(--rule-strong); border-radius: var(--radius); color: var(--ink-soft); background: var(--raised); }

/* ------------------------------------------------------- money input */

/* A currency prefix inside the field, so the unit is where the number is
   rather than in a label above it. */
.money-input { display: flex; align-items: stretch; border: 1px solid var(--rule-strong); border-radius: 7px; background: var(--page); overflow: hidden; }
.money-input:focus-within { outline: 2.5px solid var(--brand); outline-offset: 1px; }
.money-input > span { display: flex; align-items: center; padding: 0 .1rem 0 .65rem; color: var(--ink-faint); font-size: 1.05rem; }
.money-input input { border: 0 !important; border-radius: 0 !important; background: transparent !important; padding-left: .25rem !important; }
.money-input input:focus-visible { outline: none; }

.calc-hero { margin-top: 0; }
.hero-more { margin: .2rem 0 2rem; font-size: .92rem; }

/* ------------------------------------------------- advanced disclosure */

/* Most readers have no student loan, and five permanently open checkboxes were
   ~200px of a 739px form. Collapsed — but the summary always states what is
   inside, because a closed panel hiding an active setting is a trap. */
.advanced { border: 1px solid var(--rule); border-radius: var(--radius); background: var(--page); margin: 1rem 0 0; }
.advanced summary { cursor: pointer; padding: .7rem 1rem; font-weight: 620; font-size: .92rem; display: flex; justify-content: space-between; align-items: center; gap: 1rem; list-style: none; }
.advanced summary::-webkit-details-marker { display: none; }
.advanced summary::after { content: "+"; color: var(--brand); font-weight: 700; font-size: 1.2rem; line-height: 1; }
.advanced[open] summary::after { content: "\2212"; }
.advanced[open] summary { border-bottom: 1px solid var(--rule); }
.advanced > .calc-grid { padding: .9rem 1rem 1rem; }
.adv-state { font-weight: 500; font-size: .84rem; color: var(--ink-faint); margin-left: auto; margin-right: .3rem; }
.adv-state.is-set { color: var(--brand-ink); font-weight: 620; }

/* ------------------------------------------------------------- share */

.share { font: inherit; font-size: .88rem; font-weight: 560; margin-top: 1rem; padding: .5rem .9rem; border: 1px solid var(--rule-strong); border-radius: 7px; background: var(--page); color: var(--ink-soft); cursor: pointer; transition: border-color .15s, color .15s; }
.share:hover { border-color: var(--brand); color: var(--brand-ink); }
.share.is-done { border-color: var(--brand); color: var(--brand-ink); background: var(--brand-wash); }
