/* ============================================================================
   TOKENS
   Every colour, gradient, font and radius the design uses is named here. No rule
   below this block carries a raw hex value, a literal gradient or a font name —
   the one exception is the @font-face descriptors immediately following, which
   *define* the family names --display and --body point at.
   ============================================================================ */
:root {
  color-scheme: dark;

  --bg: #070707;
  --surface: #121212;
  --surface-2: #1A1A1A;
  --line: rgba(255,255,255,.09);
  --ink: #FFFFFF;
  --muted: #9CA3AF;

  --pink: #F535B5;
  --magenta: #E842DB;
  --purple: #9B6CF0;
  --blue: #5085F3;
  --cyan: #59C2F4;
  --up: #3DDC97;
  --down: #FF5C7A;

  --radius: 14px;
  --grad: linear-gradient(90deg,#F535B5,#E842DB,#9B6CF0,#5085F3,#59C2F4);
  --display: 'Anton', system-ui, sans-serif;
  --body: 'Inter', system-ui, sans-serif;

  /* The near-black well behind the passkey input and, later, card interiors. */
  --well: #000000;
  /* Label colour on any gradient-filled control. Its own copy of the well value, deliberately
     not var(--well): white measures 3.47 / 3.37 / 3.62 / 3.51 and 2.01 against the gradient's
     five stops, all below the 4.5:1 floor. The two jobs are one colour today and move for
     independent reasons — a card interior lifting off pure black is a depth tweak, this is
     pinned by a measured contrast floor — and the print block re-declares --well for paper,
     which an alias would drag a gradient label along with. */
  --ink-on-grad: #000000;
  /* The pinned "(all links)" row. Purple-tinted rather than a fourth neutral step: a further
     grey reads as "this row is hovered", which is what --surface-2 already means. */
  --pinned: #241B2E;
  --pinned-hover: #332545;

  /* Forward contract — declared here so the component slice adds components without reopening
     the token set. Unused by this slice by design. */
  --series-clicks: var(--pink);
  --series-visitors: var(--purple);
  --series-regs: var(--blue);
  --series-subs: var(--cyan);
  --banner-bg: rgba(232,66,219,.08);
  --banner-line: rgba(232,66,219,.4);

  /* Paper is a consumer like any other, so it gets tokens rather than literals: the print block
     below re-points the screen tokens at these and carries no raw value of its own. */
  --paper: #FFFFFF;
  --paper-ink: #111111;
  --paper-muted: #4A4A4A;
  --paper-line: #CCCCCC;
  --paper-down: #B00020;
  --paper-accent: #14567D;
}

/* ============================================================================
   FONTS
   Self-hosted, latin-subset woff2. To add or replace a face:
     1. Take it from the family's own upstream release (the Anton / Inter repositories, or the
        Google Fonts family directory) — never from a bare woff2 URL in a css2 API response,
        which serves font bytes with no copyright notice to ship beside them.
     2. Subset it: pyftsubset <face>.ttf --unicodes=U+0000-00FF,U+2000-206F --flavor=woff2
        Keep that range identical across the faces — a face subset wider than its siblings
        changes the fallback behaviour for one file only, which is invisible on screen.
     3. Ship that family's own OFL text beside the files. OFL 1.1 conditions redistribution on
        each copy carrying the licence *and* that family's copyright notice, and every instance
        of this dashboard is a whole-folder copy — so the .txt files are a condition, not clutter.
        (.txt, not .md: .htaccess denies .md to the web.)
   ============================================================================ */
@font-face {
  font-family: 'Anton';
  src: url('fonts/Anton-Regular-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-Regular-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-Medium-latin.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-SemiBold-latin.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ============================================================================
   BASE
   ============================================================================ */
* { box-sizing: border-box; }
html, body { margin: 0; }
body {
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
/* Controls inherit no font at all by default — the UA gives them a ~13.3px system face — so this
   is the full shorthand rather than font-family alone. */
button, input, select, textarea { font: inherit; }
button { cursor: pointer; }
img { max-width: 100%; }
a { color: var(--cyan); text-decoration: none; }
a:hover { text-decoration: underline; }
::selection { background: var(--purple); color: var(--ink); }

.grad-text {
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.tagline { font-size: .72rem; letter-spacing: .4em; text-transform: uppercase; font-weight: 600; }
.linklike {
  background: none; border: none; padding: 0; color: var(--muted);
  font-size: .78rem; text-decoration: underline; text-underline-offset: 3px;
}
.linklike:hover { color: var(--ink); }

/* ============================================================================
   GATE
   ============================================================================ */
body.login { display: grid; place-items: center; min-height: 100vh; padding: 1rem; }
.login-card {
  width: 100%; max-width: 420px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 2.4rem 2rem; text-align: center;
  display: grid; gap: 12px;
}
/* gap is the card's only spacing mechanism: UA block margins on a grid item are not collapsed
   and would add to it. The two seams the design deliberately opens wider are margins below. */
.login-card > * { margin: 0; }
.login-card img.logo { width: 180px; height: auto; justify-self: center; }
.login-card .tagline { margin-bottom: 2rem; }
.login-card h1 {
  font-family: var(--display); font-weight: 400; font-size: 1.35rem;
  letter-spacing: .03em; text-transform: uppercase;
}
.login-card label {
  display: block; text-align: left; font-size: .76rem; color: var(--muted);
  letter-spacing: .08em; text-transform: uppercase;
}
.login-card input {
  width: 100%; padding: .9rem 1rem; border-radius: 10px;
  border: 1px solid var(--line); background: var(--well); color: var(--ink);
  /* At least 16px against a 16px root: iOS Safari zooms the page on focus of any smaller text
     input, and the inherited base is 15px. */
  font-size: 1.05rem;
  outline: none;
}
.login-card input:focus { border-color: var(--purple); }
.login-card button[type="submit"] {
  width: 100%; padding: .9rem; border: none; border-radius: 10px;
  background: var(--grad); color: var(--ink-on-grad);
  font-family: var(--display); font-size: 1rem; letter-spacing: .12em; text-transform: uppercase;
}
/* The slot is always in the DOM so a message appearing cannot re-centre the card. Scoped to the
   gate: .err is shared with the dashboard's whole load-failure block, which must never be hidden.
   One line, sized for "Incorrect passkey." — the state a viewer meets repeatedly. */
body.login .err { min-height: 1.2rem; font-size: .82rem; visibility: hidden; }
body.login .err.show { visibility: visible; }
.gate-foot { margin-top: 1.6rem; font-size: .72rem; color: var(--muted); }

/* ============================================================================
   SHELL
   .header keeps its class name and its sticky positioning: scrollToDrill() derives its scroll
   offset from this element's own offsetHeight, so the two are one decision.
   ============================================================================ */
.header {
  position: sticky; top: 0; z-index: 5; background: var(--surface);
  border-bottom: 1px solid var(--line); padding: 12px 20px;
}
.header .wm { height: 26px; width: auto; }
.hrow { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
/* Holds the header's two longest strings — the instance title and the as-of stamp — beside both
   actions; without its own wrap the cluster overflows a 375px header. */
.hactions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.aff-pill {
  margin: 0; background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
  padding: .35rem .9rem; font-size: 1rem; font-weight: 500;
}
.refreshed { font-size: .74rem; color: var(--muted); }
.hactions button:disabled { opacity: .6; cursor: default; }
.winsel { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
/* .retry takes the resting pill treatment rather than restating it; only .active adds the gradient.
   The resting colour is explicit because a <button> does not inherit colour — the UA sets it. */
.winsel button, .retry {
  padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink);
}
.winsel button.active { background: var(--grad); color: var(--ink-on-grad); border-color: transparent; }

.section { padding: 16px 20px 4px; }
.section h2 {
  font-family: var(--display); font-weight: 400; font-size: 1.5rem;
  letter-spacing: .03em; text-transform: uppercase; margin: 8px 0 10px;
}
footer { margin: 3rem 0 2rem; text-align: center; }
footer .tagline { font-size: .62rem; }

/* Three separate wrap fixes each resolve a horizontal overflow by adding a row, and the sum is
   permanently pinned chrome on a 667px screen that scrollToDrill also subtracts. The header is
   five rows at this width in every version of this design, so this compacts rows rather than
   reclaiming one — contents and the gaps between them both. Budget: .header stays under 180px. */
@media (max-width: 480px) {
  .header { padding: 8px 12px; }
  .header .wm { height: 18px; }
  .hrow { gap: 8px; }
  .hactions { gap: 8px; }
  .aff-pill { font-size: .86rem; padding: .2rem .7rem; }
  .refreshed { font-size: .7rem; }
  .linklike { font-size: .74rem; }
  .winsel { margin-top: 6px; gap: 4px; }
  .winsel button { padding: 3px 10px; font-size: .82rem; }
}

/* ============================================================================
   TABLE
   ============================================================================ */
.tablewrap {
  overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
/* nowrap is what makes twelve columns wider than a 375px viewport, which is what makes .tablewrap
   scroll. Let the cells wrap and the container never scrolls. */
th, td { padding: 7px 12px; border-bottom: 1px solid var(--line); white-space: nowrap; }
thead th {
  background: var(--surface); text-align: right; font-weight: 600; font-size: .66rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; user-select: none;
}
thead th.left { text-align: left; }
thead th.sorted { color: var(--cyan); }
td.num, .num { text-align: right; }
td.left { text-align: left; }
td.link { color: var(--ink); font-weight: 600; }
td .na { color: var(--muted); }
tbody tr { cursor: pointer; }
tbody tr:hover { background: var(--surface-2); }
/* Declared after the hover rule — equal specificity, so an emphasis background written before it
   would lose under the cursor and both rows would revert to the ordinary hover colour. */
tbody tr.all, tbody tr.total { background: var(--pinned); font-weight: 600; }
/* The pinned row stays distinct while hovered by two things, not one. The bar is a border on the
   first cell rather than a box-shadow on the row: under border-collapse Blink and WebKit paint
   neither box-shadow nor border-radius on rows or cells. */
tbody tr.all td:first-child { border-left: 3px solid var(--purple); }
tbody tr.all:hover { background: var(--pinned-hover); }
tbody tr.drilled { outline: 2px solid var(--purple); outline-offset: -2px; }
/* The qualifier on the page's largest claim, and its whole purpose is that it is read — so it
   takes the body size and a step of weight rather than the freshness line's peripheral treatment. */
.tbl-note { margin: 10px 0 0; color: var(--muted); font-weight: 500; }

/* ============================================================================
   DRILL
   A panel in its own right: its own padding replaces .section's, and the page gutter is a margin,
   so the border sits inset from the viewport edge and the interior is padded once.
   ============================================================================ */
.section.drill {
  background: var(--surface); border: 1px solid var(--purple); border-radius: var(--radius);
  padding: 1.2rem 1.3rem; margin: 8px 20px 0;
}
.drill-head {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: .6rem; margin-bottom: 1rem;
}
.drill-head h2 { margin: 0; }
.section.drill thead th { cursor: default; }
.section.drill tbody tr { cursor: default; }
.section.drill tbody tr:not(.total):hover { background: transparent; }

/* ============================================================================
   DATA-MISSING STATES
   .pad carries three: loading, the load failure, and the matched-nothing message — which unlike
   the other two renders below a fully drawn header.
   ============================================================================ */
.pad { padding: 24px; color: var(--ink); }
.err { color: var(--down); }
.pad p { margin: 0 0 10px; }
.pad p:last-child { margin-bottom: 0; }

/* ============================================================================
   PRINT
   Re-points the tokens at the paper ones rather than declaring dark-on-light at body level: every
   colour below the token block is a token, so each more specific rule would override that. Aliases,
   never literals — nine hex values here would be nine raw values below the token block.
   What it keeps is the point: the instance title, the freshness stamp, the window-label heading,
   the table, the caption and an open drill. What it drops is chrome that means nothing on paper.
   ============================================================================ */
@media print {
  :root {
    --bg: var(--paper);
    --surface: var(--paper);
    --surface-2: var(--paper);
    --well: var(--paper);
    /* The pinned row's fill collapses to paper with the other surfaces. Print drops backgrounds by
       default, so this only bites when the viewer enables "Background graphics" — at which point
       the anchor row would keep a near-black fill while its ink turned dark, on the one row the
       caption below the table exists to qualify. */
    --pinned: var(--paper);
    --pinned-hover: var(--paper);
    --ink: var(--paper-ink);
    --muted: var(--paper-muted);
    --line: var(--paper-line);
    --down: var(--paper-down);
    --cyan: var(--paper-accent);
  }
  /* Print drops background images, so a gradient-clipped word would print as blank space. */
  .grad-text { background: none; color: inherit; }
  /* A paged overflow box clips — there is no continuation onto a second sheet — so twelve nowrap
     columns would print as the left half only, losing every ratio column. */
  .tablewrap { overflow: visible; }
  th, td { white-space: normal; }
  /* The controls go by their own class: they sit inside .hactions beside the title chip and the
     freshness line, so hiding that wrapper would strip exactly what this block exists to keep. */
  .winsel, .linklike, footer { display: none; }
}
