/*
 * ntlddata.com
 *
 * Palette roles come from the validated reference instance. Sequential blue is
 * the workhorse because almost every figure on this site is a magnitude or a
 * trend; categorical slots are only used where series identity is the subject.
 * Diverging blue/red is reserved for adds-vs-drops, which is genuinely polar.
 *
 * Validator results for the sets used here (surface #fcfcfb / #1a1a19):
 *   ordinal blue 250-650, light + dark ......... ALL PASS
 *   categorical slots 1-4, light + dark ........ ALL PASS
 *     (light aqua 2.74:1 and yellow 2.11:1 are sub-3:1, so every chart using
 *      them ships direct labels and a table view — the relief rule.)
 *
 * The four categorical slots are the rule; --slice-1..10 below is the documented
 * exception. Registrar share within one TLD is a part-to-whole over ten named
 * competitors, which needs ten identities and cannot be told with four. It is a fixed,
 * hand-picked, declared set — not cycled, not generated — and it is used by the share
 * donut only. Adjacent hues are separated in lightness as well as hue so the ordering
 * survives monochrome and the common forms of colour blindness, and the donut always
 * ships a legend carrying the name, count and percentage, so colour is never the only
 * channel.
 */

:root {
  color-scheme: light;

  --surface-1: #fcfcfb;
  --page: #f9f9f7;
  --text-primary: #0b0b0b;
  --text-secondary: #52514e;
  --muted: #898781;
  --grid: #e1e0d9;
  --baseline: #c3c2b7;
  --border: rgba(11, 11, 11, 0.1);

  /* Sequential blue, ordinal-safe steps (250, 350, 450, 550, 650). */
  --seq-1: #86b6ef;
  --seq-2: #5598e7;
  --seq-3: #2a78d6;
  --seq-4: #1c5cab;
  --seq-5: #104281;
  --seq-wash: #cde2fb;

  /* Categorical, fixed order — never cycled, never generated. */
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;

  /* Registrar-share donut. Fixed order, assigned by rank. See the note at the top. */
  --slice-1: #2a78d6;
  --slice-2: #eb6834;
  --slice-3: #1baf7a;
  --slice-4: #eda100;
  --slice-5: #8a5cd6;
  --slice-6: #1d97b4;
  --slice-7: #d1477d;
  --slice-8: #6d8f2c;
  --slice-9: #a9652b;
  --slice-10: #566a8d;
  --slice-other: #a8a6a0;

  /* Diverging: adds vs drops. Neutral midpoint is the gray below. */
  --pos: #2a78d6;
  --neg: #e34948;
  --mid: #f0efec;

  /* Status — reserved, always shipped with an icon and a label. */
  --good: #0ca30c;
  --warning: #fab219;
  --serious: #ec835a;
  --critical: #d03b3b;
  --delta-up: #006300;

  --radius: 6px;
  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --surface-1: #1a1a19;
    --page: #0d0d0d;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --muted: #898781;
    --grid: #2c2c2a;
    --baseline: #383835;
    --border: rgba(255, 255, 255, 0.1);
    /* Dark steps run the other way so the pale end still clears the surface. */
    --seq-1: #184f95;
    --seq-2: #256abf;
    --seq-3: #3987e5;
    --seq-4: #6da7ec;
    --seq-5: #9ec5f4;
    --seq-wash: #14335c;
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;

    /* Registrar-share donut, lifted for the dark surface. */
    --slice-1: #4b93e8;
    --slice-2: #f07b4b;
    --slice-3: #2ec48c;
    --slice-4: #f0b32c;
    --slice-5: #a077e0;
    --slice-6: #35abc6;
    --slice-7: #e26694;
    --slice-8: #86a641;
    --slice-9: #c07f42;
    --slice-10: #7286a6;
    --slice-other: #7d7b75;

    --pos: #3987e5;
    --neg: #e66767;
    --mid: #383835;
    --delta-up: #0ca30c;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --surface-1: #1a1a19;
  --page: #0d0d0d;
  --text-primary: #ffffff;
  --text-secondary: #c3c2b7;
  --muted: #898781;
  --grid: #2c2c2a;
  --baseline: #383835;
  --border: rgba(255, 255, 255, 0.1);
  --seq-1: #184f95;
  --seq-2: #256abf;
  --seq-3: #3987e5;
  --seq-4: #6da7ec;
  --seq-5: #9ec5f4;
  --seq-wash: #14335c;
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  /* Registrar-share donut, lifted for the dark surface. */
  --slice-1: #4b93e8;
  --slice-2: #f07b4b;
  --slice-3: #2ec48c;
  --slice-4: #f0b32c;
  --slice-5: #a077e0;
  --slice-6: #35abc6;
  --slice-7: #e26694;
  --slice-8: #86a641;
  --slice-9: #c07f42;
  --slice-10: #7286a6;
  --slice-other: #7d7b75;
  --pos: #3987e5;
  --neg: #e66767;
  --mid: #383835;
  --delta-up: #0ca30c;
}

* { box-sizing: border-box; }

/*
 * Room for the sticky header when the browser jumps to a #fragment.
 *
 * Without this, /methodology#daily-zones scrolls the target to y=0 and the header — which is
 * sticky, so it is still there — covers it. The heading you followed the link to read is the part
 * that ends up hidden, which is the worst possible thing to lose.
 *
 * scroll-padding-top on the scroll container rather than scroll-margin-top on each target: it
 * applies to every anchor on the site at once, including ones added later, and it also fixes
 * keyboard focus scrolling and Find-in-page, which per-target margins do not.
 *
 * The value is deliberately an over-estimate. The header is two rows — brand block over the nav —
 * and its height depends on how many nav items wrap, so there is no single correct number: at
 * 1200px it measures about 90px, and the nav takes a second row on a narrow screen. Overshooting
 * leaves a little clear space above the target, which reads as deliberate; undershooting hides the
 * heading, which is the bug. So this is set past the tallest case rather than tuned to the common
 * one.
 */
:root { scroll-padding-top: 120px; }

body {
  margin: 0;
  background: var(--page);
  color: var(--text-primary);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--seq-3); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: 1200px; margin: 0 auto; padding: 0 20px; }

/* ------------------------------------------------------------------ header */

.site-header {
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 20;
}
/*
 * Vertical padding, now that the header is two rows rather than one.
 *
 * With everything on a single centred row, min-height alone gave the brand its breathing space. Moving the
 * nav to its own row put the brand at the top of the box, where it sat tight against the border — the
 * centring had been doing work that is no longer available to it.
 */
.site-header .wrap {
  display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
  min-height: 58px; padding-top: 13px; padding-bottom: 9px;
}
/* Brand and its note stack together, so the note reads as belonging to the name rather than as
   the first item of the navigation. line-height is tightened because two lines inside a 58px
   header otherwise push the nav off-centre. */
.brand-block { display: flex; flex-direction: column; gap: 1px; line-height: 1.15; }
.brand { font-weight: 700; font-size: 17px; color: var(--text-primary); letter-spacing: -0.01em; }
.brand span { color: var(--seq-3); }
.brand-note {
  font-size: 11.5px; font-weight: 500; color: var(--text-secondary);
  text-decoration: none; letter-spacing: 0.01em;
}
.brand-note:hover { color: var(--seq-3); text-decoration: none; }
/*
 * The nav sits on its own row, below the brand and the theme toggle.
 *
 * It used to share a row with both, pushed right by margin-left:auto, which meant every new section
 * consumed slack that had already run out — the eleventh item pushed the theme toggle onto a second line by
 * itself. A full-width row holds as many sections as the site grows without the layout being renegotiated
 * each time, and it is what the narrow-screen rule below has always done; this makes it the default rather
 * than the exception.
 */
.site-nav { display: flex; gap: 2px; flex-wrap: wrap; order: 3; width: 100%; margin: 2px 0 0; }
.site-nav a {
  padding: 6px 10px;
  border-radius: var(--radius);
  color: var(--text-secondary);
  font-size: 14px;
}
.site-nav a:hover { background: var(--page); text-decoration: none; }
.site-nav a[aria-current="page"] { color: var(--text-primary); font-weight: 600; background: var(--page); }

.theme-toggle { order: 2; margin-left: auto; }
.theme-toggle {
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-secondary);
  border-radius: var(--radius);
  padding: 5px 9px;
  cursor: pointer;
  font: inherit;
  font-size: 13px;
}

/*
 * Footer operator credit.
 *
 * line-height is pinned to 1 so the text's line box is exactly its glyph box. Inherited at 1.5
 * the box is half again as tall as the text, and centring a 20px mark against that put the logo
 * visibly above the words — the flex centring was correct, it was just centring against
 * leading rather than against letters.
 */
.footer-credit { margin-top: 8px; }
.footer-credit a {
  display: inline-flex; align-items: center; gap: 7px; line-height: 1;
  color: var(--text-secondary); font-size: 13px;
}
.footer-credit a:hover { color: var(--text-primary); text-decoration: none; }
.footer-credit img { display: block; width: 18px; height: 18px; flex: 0 0 auto; }

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

main { padding: 28px 0 60px; }
.page-head { margin-bottom: 22px; }
.page-head h1 { margin: 0 0 6px; font-size: 26px; letter-spacing: -0.02em; }
.page-head p { margin: 0; color: var(--text-secondary); max-width: 70ch; }

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px;
  margin-bottom: 18px;
}
.card > h2 { margin: 0 0 4px; font-size: 16px; }
/*
 * The caption directly under a card's heading.
 *
 * Written with :is() over both the bare h2 and the .card-head wrapper, and with the source line
 * optional in between. The original selector was `.card > h2 + .sub`, which quietly did not apply
 * on any card whose heading shares a row with a control — the keep-rate and registrar-share cards
 * on /tld/{tld} among them, where h2 is a child of .card-head rather than of .card. Those captions
 * have been rendering at body size all along.
 */
.card > :is(h2, .card-head) + .sub,
.card > :is(h2, .card-head) + .chart-source + .sub { margin: 0 0 16px; color: var(--muted); font-size: 13px; }

/*
 * Which source a chart is drawn from, stated under its heading.
 *
 * Named in the selector above as well, because that rule keys off h2 being the .sub's immediately
 * preceding sibling: dropping a source line between them unstyled every caption it was placed
 * above, and did it silently — the text is still there, just at body size and full contrast, which
 * reads as a design accident rather than a broken rule.
 */
.chart-source { margin: 0 0 4px; color: var(--muted); font-size: 13px; }
.chart-source a { color: inherit; text-decoration: underline; text-decoration-color: var(--baseline); text-underline-offset: 2px; }
.chart-source a:hover,
.chart-source a:focus-visible { color: var(--text-secondary); text-decoration-color: currentColor; }

/*
 * Context strip under a zoomable chart: a miniature of the whole series with the selected
 * window drawn over it. The point is that the reader can see what they are cutting off.
 * Everything outside the window is masked rather than hidden, so the excluded shape stays
 * faintly visible.
 */
.chart-context-wrap { position: relative; }
.context-hint {
  position: absolute; top: -2px; left: 0; font-size: 11px; color: var(--muted);
  pointer-events: none; letter-spacing: 0.02em;
}
/* Selector matches `.chart svg` so the fixed height actually wins; as `.chart-context` alone
   it lost to height:auto and the strip silently scaled with the container width. */
.chart svg.chart-context { display: block; width: 100%; height: 46px; margin-top: 2px; touch-action: none; }
.chart-context .context-area { fill: var(--grid); stroke: none; }
.chart-context .context-mask { fill: var(--surface-1); opacity: 0.62; pointer-events: none; }
.chart-context .context-window { fill: var(--seq-wash); opacity: 0.5; cursor: grab; }
.chart-context .context-window:active { cursor: grabbing; }
.chart-context .context-handle { stroke: var(--seq-3); stroke-width: 3; cursor: ew-resize; }
.chart-context.is-dragging { cursor: ew-resize; }

/*
 * Month-range control. Standard form chrome rather than chart marks, in one row above the
 * chart it scopes. Wraps to two lines on narrow screens instead of shrinking the inputs.
 */
.range-control {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 14px;
  margin: 2px 0 10px;
}
.range-label { flex: 0 0 auto; }
/* The window being shown, not a caption for it: this carries a figure that changes under the
   reader's finger as they drag, so it takes the tabular face and the primary colour rather than the
   muted styling of the label it replaced. */
.range-readout { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--text-primary); font-weight: 550; }
/* Pushed to the trailing edge, so the readout reads as the chart's subtitle and the presets as
   controls rather than the two competing at the same starting point. */
.range-presets { display: flex; gap: 10px; align-items: baseline; margin-left: auto; }
.range-presets strong { color: var(--text-primary); }


/*
 * Scope toggle: which slice of the namespace a page covers.
 *
 * Given more weight than the other toggles because it changes what every number on the page
 * means, not merely how it is grouped. A reader who misses it reads legacy figures as new-gTLD
 * figures, which is the same class of error as confusing the two data clocks.
 */
.scope-toggle {
  display: inline-flex; flex-wrap: wrap; margin: 0 0 16px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-1); overflow: hidden;
}
/* A divider between segments rather than a gap. Three words in a row with a few pixels between
   them read as one phrase — "New gTLDs Legacy gTLDs All gTLDs" — which is worse than unstyled,
   because it looks deliberate. */
.scope-toggle a, .scope-toggle strong {
  padding: 7px 14px; font-size: 13.5px; font-weight: 500;
  color: var(--text-secondary); border-left: 1px solid var(--border);
}
.scope-toggle > :first-child { border-left: 0; }
.scope-toggle a:hover { background: var(--page); text-decoration: none; color: var(--text-primary); }
.scope-toggle strong { background: var(--seq-3); color: #fff; border-left-color: var(--seq-3); }
.scope-toggle strong + a { border-left-color: var(--border); }

/* Card heading with a control on the far right (the share view toggle). */
.card-head { display: flex; align-items: baseline; gap: 12px; margin: 0 0 4px; }
.card-head > h2 { margin: 0; font-size: 16px; }
.card-head > :last-child { margin-left: auto; }

/*
 * Registrar-share donut and its legend.
 *
 * Side by side when there is room, stacked below ~640px. The legend is a real table, not
 * a styled list: it is the precise reading of the chart, so it should be selectable,
 * copyable and navigable by a screen reader as tabular data.
 */
.donut-wrap { display: flex; flex-wrap: wrap; gap: 24px 32px; align-items: flex-start; }
.donut { position: relative; flex: 0 0 auto; }
.donut svg { display: block; }
.donut-arc { transition: opacity 0.12s ease; }
.donut-wrap:hover .donut-arc { opacity: 0.55; }
.donut-wrap .donut-arc:hover { opacity: 1; }
.donut-centre {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; pointer-events: none; text-align: center;
}
.donut-centre strong { font-size: 22px; font-variant-numeric: tabular-nums; }

.donut-legend { flex: 1 1 220px; border-collapse: collapse; font-size: 13px; }
.donut-legend th, .donut-legend td { padding: 5px 8px; border-bottom: 1px solid var(--grid); text-align: left; font-weight: 400; }
.donut-legend th { color: var(--text-primary); }
.donut-legend td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.donut-legend tr:last-child th, .donut-legend tr:last-child td { border-bottom: 0; }
.swatch {
  display: inline-block; width: 10px; height: 10px; border-radius: 2px;
  margin-right: 8px; vertical-align: baseline; flex: 0 0 auto;
}
/* Name and its note stack, and the note aligns under the name rather than under the
   swatch — which is what a bare block element in the cell would have done. */
.donut-legend .legend-text { display: inline-block; vertical-align: top; }
.donut-legend .legend-note { display: block; line-height: 1.35; }

@media (max-width: 640px) {
  .donut-wrap { gap: 16px; }
  .donut { margin: 0 auto; }
}

/* Colour is never the only channel: forced-colors keeps the arcs distinguishable by
   outline, and the legend text carries the values regardless. */
@media (forced-colors: active) {
  .donut-arc { forced-color-adjust: none; }
  .swatch { border: 1px solid CanvasText; }
}

.grid { display: grid; gap: 18px; }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
/* Cards of unequal height sit at the top of their row rather than stretching: the share
   legend is taller than the registry facts, and stretching left a large blank. */
.grid.align-start { align-items: start; }

/* -------------------------------------------------------------- stat tiles */

.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 20px; }
.stat-tile {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
}
.stat-tile .label { color: var(--text-secondary); font-size: 13px; margin-bottom: 4px; }
.stat-tile .value { font-size: 25px; font-weight: 650; letter-spacing: -0.02em; line-height: 1.15; }
.stat-tile .meta { color: var(--muted); font-size: 12px; margin-top: 3px; }

.hero-figure { font-size: 52px; font-weight: 700; letter-spacing: -0.03em; line-height: 1; }
.hero-label { color: var(--text-secondary); font-size: 14px; margin-top: 6px; }

/* Deltas pair an arrow glyph with the number: never color alone. */
.delta { font-size: 13px; font-weight: 600; }
.delta.up { color: var(--delta-up); }
.delta.down { color: var(--critical); }
.delta.flat { color: var(--muted); }

/* ------------------------------------------------------------------ tables */

.table-scroll { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: 14px; }
table.data th, table.data td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--grid); }
table.data th { color: var(--text-secondary); font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.03em; white-space: nowrap; }
table.data th a { color: inherit; }
/* Tags under a table row's name, on their own line so the column has one layout rather
   than two depending on how long the name happens to be. */
table.data td.name .name-tags { margin-top: 3px; }

table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Client-sorted tables. The paginated listings render their own arrows server-side, so
   these rules are scoped to headers this script actually took over. */
table.data th.sortable { cursor: pointer; user-select: none; }
table.data th.sortable:hover, table.data th.sortable:focus-visible { color: var(--text-primary); }
table.data th.sortable::after {
  content: " \2195";
  opacity: 0.25;
  font-size: 11px;
}
table.data th.sortable[aria-sort="ascending"]::after { content: " \2191"; opacity: 1; }
table.data th.sortable[aria-sort="descending"]::after { content: " \2193"; opacity: 1; }
table.data tbody tr:hover { background: var(--page); }
table.data td.name { font-weight: 550; }

/* Inline magnitude bar inside a table cell: sequential, one hue. */
/* The figure and its bar, right-aligned as a pair.
   Tabular digits so the numbers in the column line up with each other rather than only sharing a
   right edge — the same reason the standalone bar chart forces fixed decimals. */
.bar-cell { display: flex; align-items: center; gap: 8px; justify-content: flex-end; font-variant-numeric: tabular-nums; }
/* display:block on both, and that is the whole fix for a bar that never appeared.
   These are <span> elements — a non-replaced inline box ignores width, height and min-width
   outright, so every fill in the share column has been a zero-sized element since the column was
   written. The track survived only because .bar-cell is a flex container, which blockifies its
   direct children; .fill is a grandchild, so nothing blockified it.
   Set on the class rather than by switching the markup to <div>, so the rule holds wherever these
   classes are used — six views use them, and a seventh would inherit the bug. */
.bar-cell .track {
  display: block;
  width: 90px; height: 8px; background: var(--mid); border-radius: 4px; overflow: hidden; flex: none;
}
.bar-cell .fill {
  display: block;
  height: 100%; background: var(--seq-3); border-radius: 4px;
  /* A floor, so a real value is never rendered as nothing. The column tops out at 25% of the
     market, so a 0.18% share is 0.7% of a 90px track — under one pixel. */
  min-width: 2px;
}

/* One-line family composition under a registrar-group name. Sits tight to the name so
   the pair reads as a unit, and stays on one line: the point is a glance, not a list. */
.composition {
  font-size: 12.5px;
  color: var(--text-secondary);
  margin-top: 2px;
  line-height: 1.35;
  max-width: 46ch;
}
.composition .muted { color: var(--muted); }

/* Native disclosure for long dormant-accreditation lists — no JavaScript needed. */
details > summary {
  cursor: pointer;
  color: var(--seq-3);
  font-size: 14px;
  padding: 4px 0;
}
details > summary:hover { text-decoration: underline; }
details[open] > summary { margin-bottom: 4px; }

.pill {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 11.5px;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  background: var(--page);
  white-space: nowrap;
}
.pill.inferred { border-style: dashed; }

/* ------------------------------------------------------------------ charts */

.chart { position: relative; }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart .grid-line { stroke: var(--grid); stroke-width: 1; }
.chart .axis-line { stroke: var(--baseline); stroke-width: 1; }
.chart .tick-label { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart .series-line { fill: none; stroke: var(--seq-3); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart .series-area { fill: var(--seq-wash); }
.chart .point { fill: var(--seq-3); stroke: var(--surface-1); stroke-width: 2; }
.chart .bar-pos { fill: var(--pos); }
.chart .bar-neg { fill: var(--neg); }
.chart .mark-label { fill: var(--text-secondary); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart .crosshair { stroke: var(--baseline); stroke-width: 1; stroke-dasharray: 3 3; }

/* Horizontal ranking bars. Sequential single hue; every bar directly labelled,
   which is also the relief for the sub-3:1 light steps. */
/* One grid for the whole chart, not one per row.
   Each .hbar used to be its own grid, so its three column widths were computed from its own
   contents — a row labelled "US 2467 registrars" got a wider first column than "DE 23 registrars",
   and every bar started at a different x position. Bars whose left edges do not line up cannot be
   compared by eye, which is the only thing a bar chart is for.
   `display: contents` drops the row box so its three cells become items of the parent grid, where
   the column widths are shared. The row box carries no styling of its own, so nothing is lost. */
.hbars {
  display: grid;
  grid-template-columns: minmax(120px, 1.2fr) 2fr max-content;
  gap: 7px 12px;
  align-items: center;
  font-size: 14px;
}
.hbar { display: contents; }
.hbar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hbar-track { height: 10px; background: var(--mid); border-radius: 5px; overflow: hidden; }
.hbar-fill { height: 100%; background: var(--seq-3); border-radius: 5px; min-width: 2px; }
.hbar-value {
  color: var(--text-secondary); white-space: nowrap;
  /* Right-aligned with tabular digits, so the decimal points line up down the column even when the
     magnitudes differ. Fixed decimals alone are not enough: 168.32M and 9.46M still need the
     column to align on the right edge. */
  text-align: right; font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
  /* Columns now live on the container, so the narrow layout is set there too. The track spanning
     every column puts each bar on its own line beneath its label. */
  .hbars { grid-template-columns: 1fr max-content; }
  .hbar-track { grid-column: 1 / -1; }
}

.sparkline { display: block; margin-top: 6px; }

.legend { display: flex; gap: 16px; flex-wrap: wrap; margin: 0 0 10px; padding: 0; list-style: none; font-size: 13px; color: var(--text-secondary); }
.legend li { display: flex; align-items: center; gap: 6px; }
.legend .swatch { width: 11px; height: 11px; border-radius: 3px; flex: none; }

.chart-tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 7px 10px;
  font-size: 12.5px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.13);
  opacity: 0;
  transition: opacity 0.1s;
  z-index: 5;
  white-space: nowrap;
}
.chart-tooltip.on { opacity: 1; }
.chart-tooltip .tt-date { color: var(--muted); font-size: 11.5px; }
.chart-tooltip .tt-value { font-weight: 650; font-variant-numeric: tabular-nums; }

.empty-state { color: var(--text-secondary); padding: 28px 0; text-align: center; }
.empty-state strong { display: block; color: var(--text-primary); margin-bottom: 4px; }

/* ----------------------------------------------------------------- notices */

.notice {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted);
  background: var(--surface-1);
  border-radius: var(--radius);
  padding: 11px 14px;
  font-size: 13.5px;
  color: var(--text-secondary);
  margin-bottom: 18px;
}
.notice .ico { flex: none; font-weight: 700; }
.notice.warn { border-left-color: var(--warning); }
.notice.info { border-left-color: var(--seq-3); }
.notice.bad { border-left-color: var(--critical); }
.notice strong { color: var(--text-primary); }

/* Status states always pair the color with a glyph and a word. */
.state { font-weight: 600; white-space: nowrap; }
.state-ok { color: var(--good); }
.state-stale, .state-skipped { color: var(--serious); }
.state-failing, .state-never_run, .state-never_succeeded { color: var(--critical); }

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

.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.toolbar input[type="search"], .toolbar select {
  font: inherit;
  font-size: 14px;
  padding: 6px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  color: var(--text-primary);
}
.toolbar .spacer { margin-left: auto; }
.toolbar button {
  font: inherit; font-size: 14px; padding: 6px 12px; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--seq-3); color: #fff;
}

.pagination { display: flex; gap: 6px; align-items: center; margin-top: 16px; font-size: 14px; flex-wrap: wrap; }
.pagination a, .pagination span {
  padding: 5px 10px; border-radius: var(--radius); border: 1px solid var(--border); color: var(--text-secondary);
}
.pagination .current { background: var(--seq-3); color: #fff; border-color: transparent; font-weight: 600; }
.pagination .gap { border: 0; }

.mono { font-variant-numeric: tabular-nums; }
.muted { color: var(--muted); }
.secondary { color: var(--text-secondary); }
.nowrap { white-space: nowrap; }
.small { font-size: 13px; }
.right { text-align: right; }
dl.facts { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; margin: 0; font-size: 14px; }
dl.facts dt { color: var(--text-secondary); }
dl.facts dd { margin: 0; }

.site-footer {
  border-top: 1px solid var(--border);
  padding: 22px 0 40px;
  color: var(--muted);
  font-size: 13px;
  background: var(--surface-1);
}
.site-footer a { color: var(--text-secondary); }
.site-footer .cols { display: flex; gap: 30px; flex-wrap: wrap; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

@media (max-width: 640px) {
  .hero-figure { font-size: 40px; }
  .site-nav { order: 3; width: 100%; margin-left: 0; }
}

/* Texture is the accessibility channel: opt-in, never decorative. */
@media (forced-colors: active) {
  .chart .series-area { fill: none; }
  .chart .series-line, .chart .bar-pos, .chart .bar-neg { forced-color-adjust: none; }
  .bar-cell .fill { border: 1px solid currentColor; }
}

/* Marks a figure that came from a registry's monthly report rather than a zone file.
   Deliberately quiet and deliberately present: the number is real, but it is on the other of this
   site's two clocks, and an unmarked substitution would make the table's dates mean two things. */
.src-note {
  /* Not uppercased: "REPORTED" beside a right-aligned figure competes with the number it is
     annotating, and this is a footnote rather than a status. */
  font-size: 10px; letter-spacing: 0.01em;
  color: var(--text-secondary); background: var(--surface-1);
  border: 1px solid var(--border); border-radius: 3px;
  padding: 0 3px; font-weight: 500;
  text-decoration: none;
}
.src-note:hover { color: var(--text-primary); border-color: var(--text-secondary); text-decoration: none; }

/* Below the figure, not beside it.
   In the table the marker sits on its own line so the digits of every row still line up on the
   same right edge — a column of numbers is read by comparing lengths, and one row indented by a
   pill defeats that. */
td.num .src-note { display: block; width: fit-content; margin: 3px 0 0 auto; }

/* Inline in prose, where it is naming itself rather than annotating a figure. */
.table-note .src-note { display: inline; margin: 0 2px 0 0; }

/* The footnote is a jump target, so say so when someone arrives at it: a click that scrolls with
   no visible result reads as a click that did nothing. */
#domain-source:target {
  background: var(--surface-1); border-left: 3px solid var(--seq-3);
  padding: 8px 10px; border-radius: 0 var(--radius) var(--radius) 0;
  outline: none;
}

.table-note { margin: 10px 2px 0; font-size: 12.5px; line-height: 1.5; }
.table-note .src-note { margin-left: 0; margin-right: 2px; }

/* Sits under the hero figure when the headline is a reported total rather than a daily one.
   Small, but not hidden: it is the difference between a number the reader can compare with
   yesterday and one they cannot. */
.hero-note { margin: 10px 0 0; font-size: 12.5px; line-height: 1.55; max-width: 52ch; }

/* ── Comparison series ──────────────────────────────────────────────────────────────────────────
   The categorical theme's first six slots, used only where several entities share one plot.
   Validated with the dataviz palette checker in both modes before use, which is how the site's own
   --slice-* dark steps were found wanting: they had been lightened from the light values rather than
   stepped for the dark surface, so four of six sat above the dark lightness band and one pair fell
   below the normal-vision separation floor. These steps pass the lightness band, chroma floor, CVD
   separation, normal-vision floor and dark-surface contrast in both modes.
   Light mode warns on contrast for three of the six (aqua, yellow, magenta below 3:1 on the light
   surface). That warning is discharged rather than dismissed: every comparison carries a legend,
   direct labels on four or fewer series, and the full table of figures beneath the chart.
   Six is the cap. A seventh series is never a generated hue — the page says so and stops. */
:root {
  --cmp-1: #2a78d6;
  --cmp-2: #eb6834;
  --cmp-3: #1baf7a;
  --cmp-4: #eda100;
  --cmp-5: #e87ba4;
  --cmp-6: #008300;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --cmp-1: #3987e5;
    --cmp-2: #d95926;
    --cmp-3: #199e70;
    --cmp-4: #c98500;
    --cmp-5: #d55181;
    --cmp-6: #008300;
  }
}
:root[data-theme="dark"] {
  --cmp-1: #3987e5;
  --cmp-2: #d95926;
  --cmp-3: #199e70;
  --cmp-4: #c98500;
  --cmp-5: #d55181;
  --cmp-6: #008300;
}

/* 2px strokes, no fill: several filled areas over one another read as a stack, which is a different
   and wrong statement about the data. */
.cmp-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* Direct labels sit at each line's last point, so identity is never carried by colour alone. */
.cmp-label { font-size: 11.5px; font-weight: 600; }
/* Text wears text tokens; the swatch beside it carries the identity. */
.cmp-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 10px; padding: 0; list-style: none; font-size: 12.5px; }
.cmp-legend li { display: flex; align-items: center; gap: 6px; color: var(--text-secondary); }
.cmp-legend .swatch { width: 10px; height: 10px; border-radius: 2px; flex: none; }
.cmp-legend a { color: var(--text-primary); text-decoration: none; }
.cmp-legend a:hover { text-decoration: underline; }

/* A multi-series tooltip lists one line per entity, so the value text carries its own newlines. */
.chart-tooltip .tt-value { white-space: pre-line; }

/* The chips naming what is being compared, each with a way out. */
.cmp-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; padding: 0; list-style: none; }
.cmp-chips li {
  display: flex; align-items: center; gap: 7px;
  border: 1px solid var(--border); border-radius: 999px;
  padding: 3px 6px 3px 10px; font-size: 13px; background: var(--surface-1);
}
.cmp-chips .swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; }
.cmp-chips .drop { color: var(--text-secondary); text-decoration: none; line-height: 1; padding: 0 3px; }
.cmp-chips .drop:hover { color: var(--neg); text-decoration: none; }

/* The comparison selector on a listing.
   A row of checkboxes and one button, above the table it applies to — the same place the range and
   scope controls sit, so the page has one band of controls rather than three conventions. */
.select-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 0 0 10px;
}
.select-bar button { margin-left: auto; }
table.data th.pick, table.data td.pick { width: 28px; padding-right: 0; }
table.data td.pick input { margin: 0; cursor: pointer; }

/* The month picker above a share chart. One row, like every other control band on the site. */
.month-control { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 12px; }
.month-control input[type="range"] { flex: 1 1 140px; min-width: 120px; max-width: 320px; accent-color: var(--seq-3); }
.month-control .month-readout { font-variant-numeric: tabular-nums; min-width: 74px; }
.month-control .step {
  text-decoration: none; color: var(--text-secondary); font-size: 18px; line-height: 1;
  padding: 0 5px; border: 1px solid var(--border); border-radius: var(--radius);
}
.month-control .step:hover { color: var(--text-primary); text-decoration: none; }
/* A step that would leave the range is shown as unavailable rather than removed, so the control does
   not change width at the ends and the reader can see they are at one. */
.month-control .step.is-off { opacity: 0.35; pointer-events: none; }
.month-control .step-latest.is-hidden { visibility: hidden; }

/* Play/pause for the month slider. Sized to the step arrows so the row keeps one rhythm. */
.month-control .month-play {
  font: inherit; line-height: 1; cursor: pointer;
  color: var(--text-secondary); background: none;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 2px 7px;
}
.month-control .month-play:hover { color: var(--text-primary); }
.month-control .month-play.is-playing { color: var(--seq-3); border-color: var(--seq-3); }

/*
 * Coverage-change marks on an aggregate chart.
 *
 * Behind the series and deliberately faint: this explains an artefact of measurement, so it must be findable
 * without competing with the data it annotates. Dashed rather than solid so it cannot be mistaken for a
 * gridline or a series.
 */
.chart .chart-event line {
  stroke: var(--text-secondary); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.5;
}
.chart .chart-event:hover line { opacity: 0.9; }
.chart-event-notes { margin: 8px 0 0; }
.chart-event-notes li { margin: 2px 0; }
