/* ==========================================================================
   custsat, base
   Tokens, reset, typography, and the handful of components every page uses.
   Plain CSS on purpose: no build step, no framework, no utility classes.

   The look is "Canopy" (chosen 2026-09-13): a deep forest green for the console's
   chrome, a soft green-grey ground, white cards, Onest for everything, and one
   bright lime accent. Lime is a fill only (the main button, a chosen score, the
   mark next to the name) and always carries forest-dark text. It never carries
   text on white, because it cannot be read there. Where green has to be a line,
   an icon or a word on the light ground, it is the darker `--brand`.

   The token names follow Butler and Checklister (`--paper`, `--ink`, `--brand-*`)
   so styles copied from those products keep working.

   Dark mode follows the device. There is no switch.
   ========================================================================== */

/* Onest, SIL OFL, vendored in assets/vendor/onest/ with its licence so no page asks
   a third party for a font. One variable file per subset covers every weight. */
@font-face {
  font-family: "Onest"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url(/assets/vendor/onest/onest-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Onest"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url(/assets/vendor/onest/onest-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* The ground is a soft green-grey, a card is white on it, a sunken area (a table
     head, a note, a rail) one step darker than the ground. */
  --paper:        #f3f5f0;
  --paper-raised: #ffffff;
  --paper-field:  #ffffff;
  --paper-sunken: #eaeee6;
  --paper-veil:   rgba(243, 245, 240, .88);             /* sticky bars, over blur */
  --ink:          #10231a;
  --ink-soft:     #51615a;
  --ink-faint:    #7c8a82;
  /* A select's arrow, in --ink-soft. A data URI cannot read a token, so it is spelled out. */
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2351615a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  --rule:         #dfe4da;
  --rule-strong:  #c9d1c4;

  /* The chrome: the console's sidebar and the survey page. Its own ink, because
     text on forest is light in both modes. */
  --forest:       #0e2a1c;
  --forest-raised:#173a28;
  --forest-ink:   #e6efe8;
  --forest-soft:  #9db3a5;
  --forest-rule:  rgba(255, 255, 255, .12);
  /* The survey page's controls on forest: a button's edge, a hover, a field's focus. */
  --forest-edge:  rgba(255, 255, 255, .16);
  --forest-hover: rgba(255, 255, 255, .07);
  --forest-ring:  0 0 0 3px rgba(189, 227, 79, .28);

  /* Green. `--brand` is every line, icon and ring that has to be green on the light
     ground (3:1 or better). `--brand-text` is green dark enough for words (4.5:1).
     `--brand-fill` is the lime, for surfaces only, and carries `--brand-ink`. */
  --brand:        #2f8a4a;
  --brand-text:   #276b3b;
  --brand-dark:   #1c4f2b;
  --brand-tint:   #e8f3d6;
  --brand-fill:   #bde34f;
  --brand-fill-hover: #b1d93f;
  --brand-edge:   #a6cc3a;
  --brand-ink:    #0e2a1c;

  /* The accent tokens exist because copied console styles use them for "this frames
     the work" (the current page, the recommended plan). Here that is the same green. */
  --accent:       var(--brand);
  --accent-dark:  var(--brand-text);
  --accent-lift:  var(--brand-edge);
  --accent-tint:  var(--brand-tint);
  --accent-ink:   var(--brand-ink);
  --accent-row:   rgba(47, 138, 74, .05);               /* table hover, barely there */

  /* Scores, in three groups every question type maps onto: high (an NPS promoter, a
     satisfied CSAT answer), mid (passive, neutral) and low (detractor, unsatisfied).
     Named by the group, not by NPS's words, so a new type needs no new colours. The
     solid colours are for marks (a bar, a dot); a badge is the tint with ordinary ink
     on it, so the number is always readable. */
  --score-high:      #2f8a4a;
  --score-high-tint: #ddefd9;
  --score-mid:       #a9b3a4;
  --score-mid-tint:  #e9ece6;
  --score-low:       #e0634f;
  --score-low-tint:  #f8e1db;

  --done:         #2f8a4a;
  --done-tint:    #ddefd9;
  --warn:         #a3611b;
  --warn-tint:    #f6e8d4;
  --danger:       #c0392b;
  --danger-tint:  #f8e1db;

  /* Behind a QR code: white in both modes, because scanners want the contrast. */
  --qr-paper:     #ffffff;
  /* Behind the email snippet's preview: white in both modes, because the snippet carries
     its own dark ink for the light emails it is pasted into. */
  --email-paper:  #ffffff;

  --focus:        var(--brand);
  --ring:         0 0 0 3px rgba(47, 138, 74, .24);

  /* Rounded, but not soft: 10px on controls, 12px on cards. */
  --radius:       10px;
  --radius-lg:    12px;
  --radius-pill:  999px;
  /* Flat. A hairline around a card already says "this is a thing". The large shadow
     is only for what floats over the page: a popover, a dialog, the toast. */
  --shadow:       none;
  --shadow-lg:    0 2px 6px rgba(14, 42, 28, .08), 0 20px 40px -20px rgba(14, 42, 28, .35);
  --measure:      68ch;

  --font-body: "Onest", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:        #0f1a14;
    --paper-raised: #16241c;
    --paper-field:  #1a2a21;
    --paper-sunken: #0b140f;
    --paper-veil:   rgba(15, 26, 20, .88);
    --ink:          #e6efe8;
    --ink-soft:     #a3b5aa;
    --ink-faint:    #74877b;
    --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a3b5aa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    --rule:         #24362b;
    --rule-strong:  #33483b;

    /* The chrome goes darker than the ground rather than lighter, so the sidebar
       still reads as the frame around the work. */
    --forest:       #08110c;
    --forest-raised:#11211a;
    --forest-ink:   #e6efe8;
    --forest-soft:  #8ea497;
    --forest-rule:  rgba(255, 255, 255, .08);
    --forest-edge:  rgba(255, 255, 255, .12);
    --forest-hover: rgba(255, 255, 255, .06);
    --forest-ring:  0 0 0 3px rgba(189, 227, 79, .24);

    --brand:        #6fc27f;
    --brand-text:   #9bd88a;
    --brand-dark:   #c2eab0;
    --brand-tint:   #1f3319;
    /* The lime stays the lime: it is the one thing that is the same in both modes. */
    --brand-fill:   #bde34f;
    --brand-fill-hover: #c9ea66;
    --brand-edge:   #a6cc3a;
    --brand-ink:    #0e2a1c;

    --accent-row:   rgba(111, 194, 127, .06);

    /* The three groups are a diverging set: green and red around a neutral grey. Checked with
       the data-viz palette validator on 2026-09-13 against the card, in both modes: every
       value inside the mode's lightness band, neighbours apart for colour-blind readers
       (ΔE 11.5 here, 12.1 light) and for everyone (20.6, 19.7). The grey is grey on purpose,
       and is under 3:1 on the card, so a chart of groups always labels each one's share. */
    --score-high:      #56a868;
    --score-high-tint: #1b2f20;
    --score-mid:       #526358;
    --score-mid-tint:  #1d2721;
    --score-low:       #d96f5f;
    --score-low-tint:  #3a221d;

    --done:         #6fc27f;
    --done-tint:    #1b2f20;
    --warn:         #e0a15c;
    --warn-tint:    #36271a;
    --danger:       #ef8d82;
    --danger-tint:  #3a221d;

    --qr-paper:     #ffffff;
    --email-paper:  #ffffff;

    --ring:        0 0 0 3px rgba(111, 194, 127, .3);
    --shadow-lg:    0 2px 6px rgba(0, 0, 0, .5), 0 24px 48px -22px rgba(0, 0, 0, .9);
  }
}

/* --- Reset (light touch) ------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 16px/1.55 var(--font-body);
  -webkit-font-smoothing: antialiased;
}

/* Headings are heavy and set tight: the size and the weight do the work together,
   the way the score does on the Overview. */
h1, h2, h3, h4 { line-height: 1.15; margin: 0 0 .5em; font-weight: 700; letter-spacing: -.02em; }
h1 { font-size: 2rem; font-weight: 800; letter-spacing: -.03em; }
h2 { font-size: 1.45rem; }
h3 { font-size: 1.05rem; letter-spacing: -.01em; }
h4 { font-size: .95rem; letter-spacing: 0; }
p, ul, ol { margin: 0 0 1em; }
p { max-width: var(--measure); }

a { color: var(--brand-text); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--ink); }

img, svg, video { max-width: 100%; height: auto; }

small, .small { font-size: .875rem; }
.muted { color: var(--ink-soft); }
.faint { color: var(--ink-faint); }
.mono  { font-family: var(--font-mono); }
/* Scores and counts line up in columns and do not jump as they change. */
.num   { font-variant-numeric: tabular-nums; }

:where(a, button, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}

/* --- Layout -------------------------------------------------------------- */

.wrap      { width: 100%; max-width: 1140px; margin-inline: auto; padding-inline: 20px; }
.wrap-slim { width: 100%; max-width: 640px;  margin-inline: auto; padding-inline: 20px; }
.stack > * + * { margin-top: 1rem; }
.row { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; }
.row-between { display: flex; gap: .75rem; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.grow { flex: 1 1 auto; min-width: 0; }
.hidden { display: none !important; }
/* Words for a screen reader only, such as a chart's values said in full. */
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* --- The brand ------------------------------------------------------------
   The Plezo logo: a speech bubble with a smile, and the name. It is inline SVG
   (src/_includes/logo.njk), so its colours are set here: a forest bubble with a lime
   smile on the light ground, lime with a forest smile in dark mode and on the
   sidebar. The name takes the link's colour. It is sized in em, so a layout sets it
   with font-size as it would a name in type. The files for everything else are in
   src/assets/logo/.
   ------------------------------------------------------------------------ */

.brand {
  --logo-bubble: var(--brand-ink);
  --logo-smile:  var(--brand-fill);
  display: inline-flex; align-items: center;
  font-size: 1.25rem; line-height: 1;
  color: var(--ink); text-decoration: none;
}
@media (prefers-color-scheme: dark) {
  .brand { --logo-bubble: var(--brand-fill); --logo-smile: var(--brand-ink); }
}
/* The bubble is as tall as the name and its tail hangs below it, like a descender: the
   negative margin keeps the box centred on the lowercase letters, not on the tail. */
.brand .logo { display: block; width: 3.72em; height: 1.127em; margin-bottom: -.239em; }
.logo-bubble { fill: var(--logo-bubble); }
.logo-smile  { fill: var(--logo-smile); }
.logo-name   { fill: currentColor; }
.brand:hover { color: var(--ink); }

/* --- Buttons -------------------------------------------------------------
   The primary button is lime with forest words. Everything else is a white
   button with a rule, or a ghost with no edge at all.
   ------------------------------------------------------------------------ */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5em;
  padding: .6em 1.05em;
  font: inherit; font-weight: 600; font-size: .95rem; line-height: 1.25;
  color: var(--ink);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
  text-decoration: none;
  transition: background .14s, border-color .14s, color .14s, transform .06s;
}
.btn:hover { background: var(--paper-sunken); border-color: var(--rule-strong); color: var(--ink); }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; transform: none; }

.btn-primary {
  background: var(--brand-fill);
  border-color: var(--brand-fill);
  color: var(--brand-ink);
}
.btn-primary:hover { background: var(--brand-fill-hover); border-color: var(--brand-edge); color: var(--brand-ink); }

/* Kept as a name for copied pages (the plan page); here it is the primary button. */
.btn-accent { background: var(--brand-fill); border-color: var(--brand-fill); color: var(--brand-ink); }
.btn-accent:hover { background: var(--brand-fill-hover); border-color: var(--brand-edge); color: var(--brand-ink); }

/* The way in, repeated down a list (a survey's Results on every row): green enough to find,
   quieter than the lime, which stays for the one thing to do on a page. */
.btn-soft { background: var(--brand-tint); border-color: var(--brand-tint); color: var(--brand-text); }
.btn-soft:hover { background: var(--brand-tint); border-color: var(--brand-edge); color: var(--brand-text); }

.btn-danger { color: var(--danger); border-color: var(--rule); background: transparent; }
.btn-danger:hover { background: var(--danger-tint); color: var(--danger); border-color: var(--danger); }

.btn-ghost { background: transparent; border-color: transparent; color: var(--ink-soft); }
.btn-ghost:hover { background: var(--paper-sunken); border-color: transparent; color: var(--ink); }

.btn-lg { padding: .85em 1.5em; font-size: 1.03rem; }
.btn-sm { padding: .4em .75em; font-size: .875rem; }
.btn-block { display: flex; width: 100%; }

/* --- Forms --------------------------------------------------------------- */

label { display: block; font-weight: 600; font-size: .9rem; margin-bottom: .4em; }
.field + .field { margin-top: 1.1rem; }
.field-hint { font-size: .875rem; color: var(--ink-soft); margin: .4em 0 0; max-width: 62ch; }

input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="search"], input[type="tel"], input[type="url"], input[type="date"], input[type="time"],
select, textarea {
  width: 100%;
  padding: .62em .8em;
  font: inherit;
  color: var(--ink);
  background: var(--paper-field);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  transition: border-color .14s, box-shadow .14s;
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none; border-color: var(--brand); box-shadow: var(--ring);
}
input[type="checkbox"], input[type="radio"] { accent-color: var(--brand); width: auto; }
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px; box-shadow: none;
}
textarea { min-height: 5em; resize: vertical; }
input:disabled, select:disabled, textarea:disabled { background: var(--paper-sunken); color: var(--ink-faint); }
::placeholder { color: var(--ink-faint); opacity: 1; }
/* The browser's own arrow sits against the right edge; this one is inset like the text.
   After the disabled rule, whose `background` shorthand would otherwise wipe it. */
select:not([multiple]), select:not([multiple]):disabled {
  appearance: none;
  padding-right: 2.4em;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right .8em center;
  background-size: 1em;
}

fieldset { border: 1px solid var(--rule); border-radius: var(--radius-lg); padding: 1rem; margin: 0 0 1rem; }
legend { font-weight: 600; padding-inline: .4em; }

/* --- The hint ------------------------------------------------------------
   A longer explanation folded behind a small i, using the platform's own
   popover: no JavaScript, Esc and a click outside close it, and it sits in the
   top layer so no card clips it. Only where a reader has a real question the
   screen does not already answer.
   ------------------------------------------------------------------------ */

.hint {
  display: inline-flex; align-items: center;
  padding: 0; margin: 0 0 0 .1em;
  border: 0; background: none;
  color: var(--ink-faint);
  cursor: pointer;
  vertical-align: -.15em;
}
.hint:hover { color: var(--brand); }

.hint-pop {
  width: min(30rem, calc(100vw - 2rem));
  max-height: min(24rem, calc(100vh - 2rem));
  overflow-y: auto;
  padding: .9rem 1rem;
  font-size: .875rem; line-height: 1.55; color: var(--ink-soft);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.hint-pop p { margin: 0; max-width: none; }
.hint-pop p + p { margin-top: .7em; }
.hint-pop strong { color: var(--ink); }

@supports (position-area: block-end) {
  .hint-pop {
    inset: auto;
    position-area: block-end span-inline-end;
    position-try-fallbacks:
      block-end span-inline-start,
      block-start span-inline-end,
      block-start span-inline-start;
    margin: .4rem 0;
  }
}

/* --- Surfaces ------------------------------------------------------------ */

.card {
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: 1.35rem;
}
.card + .card { margin-top: 1rem; }
.card-flush { padding: 0; overflow: hidden; }

/* The forest card: the one number that matters on a screen, like the NPS on the
   Overview. One per screen at most, or it stops meaning anything. */
.card-forest {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--forest-ink);
}
.card-forest .muted { color: var(--forest-soft); }

/* A sentence about the state of things. A tinted block and nothing else: no
   stripe down the side, no icon, no border. */
.notice {
  background: var(--brand-tint);
  border-radius: var(--radius-lg);
  padding: .85rem 1.1rem;
  max-width: none;
}
.notice > :last-child { margin-bottom: 0; }
.notice-error { background: var(--danger-tint); }
.notice-warn  { background: var(--warn-tint); }
.notice-done  { background: var(--done-tint); }

/* "Saved", for a save that stays on the page (UI.toast in jsaas): solid green at
   the top, over the sticky bars. Paper on --done reads in both modes, since the
   dark --done is light. It floats, so it gets the large shadow. */
.toast {
  position: fixed; left: 50%; top: .75rem; z-index: 50;
  transform: translate(-50%, -.5rem);
  padding: .35rem .95rem .35rem .8rem;
  background: var(--done); color: var(--paper);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-lg);
  font-size: .92rem; font-weight: 600;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .18s, transform .18s, visibility 0s .18s;
}
.toast-on {
  opacity: 1; visibility: visible; transform: translate(-50%, 0);
  transition: opacity .18s, transform .18s;
}

/* --- The confirm dialog (jsaas/js/modal.js) --------------------------------
   Built by the shared module, styled here, like the toast above it. A white
   sheet with the large shadow, because it is on top of everything.
   ------------------------------------------------------------------------- */

.modal {
  width: min(26rem, calc(100vw - 2rem));
  padding: 0; border: 1px solid var(--rule); border-radius: var(--radius-lg);
  background: var(--paper-raised); color: var(--ink);
  box-shadow: var(--shadow-lg);
}
.modal::backdrop { background: rgba(14, 42, 28, .4); }
.modal-in { padding: 1.25rem 1.3rem 1.15rem; }
.modal-title { margin: 0; font-size: 1.15rem; }
.modal-body { margin: .5rem 0 0; color: var(--ink-soft); }

/* The wrapper is a `label`, which is drawn bold and small, so the label text and the
   field inside it set their own weight and size. */
.modal-field { display: block; margin-top: 1rem; margin-bottom: 0; font-size: 1rem; font-weight: 400; }
.modal-label { display: block; margin-bottom: .3rem; font-weight: 500; }
.modal-field textarea { width: 100%; display: block; resize: vertical; }
.modal-hint { display: block; margin-top: .35rem; font-size: .875rem; color: var(--ink-soft); }

/* Yes last, where the primary button sits everywhere else. */
.modal-acts { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1.2rem; }

@media (prefers-color-scheme: dark) {
  .modal::backdrop { background: rgba(0, 0, 0, .6); }
}

/* --- Tags and scores ----------------------------------------------------- */

.tag {
  display: inline-flex; align-items: center; gap: .35em;
  padding: .16em .6em;
  font-size: .78rem; font-weight: 600;
  border-radius: var(--radius-pill);
  background: var(--paper-sunken); color: var(--ink-soft);
  white-space: nowrap;
}
.tag + .tag { margin-left: .3rem; }
.tag-done   { background: var(--done-tint);   color: var(--done); }
.tag-warn   { background: var(--warn-tint);   color: var(--warn); }
.tag-danger { background: var(--danger-tint); color: var(--danger); }
.tag-accent { background: var(--brand-tint);  color: var(--brand-text); }
.tag-line   { background: transparent; color: var(--ink-soft); box-shadow: inset 0 0 0 1px var(--rule-strong); }

/* Bellbee's mark, where something here came from Bellbee: a link it added, the
   Integrations page, the connect screen. A source marker, not decoration. Bellbee's own
   logo rather than a redraw, because its one job is to be recognised, so it keeps
   Bellbee's colours in both modes and has no token (docs/integration-bellbee.md).

   It borrows .ico for the layout and turns the mask off: .ico paints its mask in
   currentColor, so without `mask: none` the logo is a flat block of text colour. In
   base.css because /connect/ wears marketing.css. Keep it out of `.rail-title`,
   `.sheet-ident` and `.section-title`, whose `.ico::before` rules in app.css are as
   specific as this one, come later, and would paint over the logo. */
.ico.mark-bellbee::before {
  width: 1.3em; height: 1.3em;
  background: url("/assets/img/partners/bellbee.png") center / contain no-repeat;
  -webkit-mask: none;
          mask: none;
}

/* One answer's score, as a small square: the tint says which group it falls in,
   the number is always ordinary ink so it reads in every mode. */
.score {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 2rem; height: 2rem;
  font-size: .875rem; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--score-mid-tint);
  border-radius: 8px;
}
.score-high { background: var(--score-high-tint); }
.score-mid  { background: var(--score-mid-tint); }
.score-low  { background: var(--score-low-tint); }

/* The same three groups as a solid mark: a bar segment, a legend dot. */
.tone-high { background: var(--score-high); }
.tone-mid  { background: var(--score-mid); }
.tone-low  { background: var(--score-low); }

/* --- Tables -------------------------------------------------------------- */

.table-scroll { overflow-x: auto; }
table.data { width: 100%; border-collapse: collapse; font-size: .95rem; }
table.data th, table.data td { padding: .75rem .9rem; text-align: left; border-bottom: 1px solid var(--rule); }
table.data thead th {
  padding-top: .8rem; padding-bottom: .6rem;
  font-size: .8rem; font-weight: 600;
  color: var(--ink-soft);
  background: var(--paper-sunken);
  border-bottom: 1px solid var(--rule);
}
table.data tbody tr { transition: background .12s; }
table.data tbody tr:hover { background: var(--accent-row); }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* The row is the link: the name is a real anchor (Tab, middle-click, screen
   readers), and the rest of the row repeats it with a click handler. */
table.data tbody tr.row-link,
table.data tbody tr.row-link td { cursor: pointer; }
table.data .row-name { color: inherit; text-decoration: none; }

/* --- Utilities ----------------------------------------------------------- */

.skip-link {
  position: absolute; left: -9999px;
  background: var(--brand-fill); color: var(--brand-ink); padding: .6em 1em; border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; top: 0; z-index: 100; }

.spinner {
  width: 1em; height: 1em; border-radius: 50%; flex: none;
  border: 2px solid var(--rule-strong); border-top-color: var(--brand);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

[v-cloak] { display: none; }
