/* The stats page's styling, served from this container at /assets/app.css.
 *
 * Kept here rather than inline in web/app.py for two reasons. The
 * Content-Security-Policy can then say style-src 'self' instead of
 * 'unsafe-inline', which is the difference between a policy that forbids an
 * injected style and one that merely describes where styles come from. And
 * two thirds of that file used to be CSS and JS inside a Python string,
 * where no editor, linter or formatter could see any of it.
 *
 * Urls here resolve against this file, which is in /assets/, so the
 * typefaces are fonts/... and not assets/fonts/... as they were when this
 * lived in the page.
 */

  /* Avian Network brand system, per https://brand.avn.network/
     Inter and Roboto Mono are served from this container, not a CDN: the
     page's Content-Security-Policy allows no third-party origin, and a
     deployment should not need anything reachable to render one page. */
  @font-face {
    font-family: "Inter";
    src: url("fonts/Inter-Variable.woff2") format("woff2");
    font-weight: 400 700;
    font-style: normal;
    font-display: swap;
  }
  @font-face {
    font-family: "Roboto Mono";
    src: url("fonts/RobotoMono-Variable.woff2") format("woff2");
    font-weight: 400 500;
    font-style: normal;
    font-display: swap;
  }

  :root {
    color-scheme: light dark;

    /* The Testnet palette, verbatim. The guidelines give the purple/blue
       identity to "tools, wallets, and environments running on the Avian
       testnet", which is exactly what this is, and say never to mix the two
       identities in the same context because the colour difference is what
       tells somebody which network they are on. So no teal or mint appears
       anywhere on this page: a pool paying testnet coins must not wear
       mainnet's colours. */
    --avn-test-violet:     #3c2c88;
    --avn-test-indigo:     #2d2178;
    --avn-test-blue:       #3a33bd;
    --avn-test-blue-light: #4449cd;
    --avn-test-navy:       #1f168e;

    /* Brand neutrals. Both themes are stated here, as light-dark() pairs,
       rather than as a second palette behind a media query. The second
       palette is what this file kept getting wrong: it ended up written three
       times, one copy silently won, and the page served a white wordmark on
       white paper to anyone whose system asks for a light page. A token whose
       two values sit on one line cannot drift from itself. It also leaves the
       theme toggle below nothing to do but change color-scheme. */
    --ink:       light-dark(#16232a, #e6f0f2);
    --ink-soft:  light-dark(#4d5e68, #9db4bc);
    --paper:     light-dark(#ffffff, #0d1b21);
    --paper-alt: light-dark(#f4f7f8, #122730);
    --line:      light-dark(#dde5e8, #24404a);

    /* The Testnet palette has no light tone: Blue Light on the dark paper is
       about 2.2:1, well under AA for text, so the dark half is mixed toward
       the ink. var(--ink) inside that half resolves to the dark ink, because
       the half is only ever used when the scheme is dark. The logo files
       themselves are never recoloured; this is a UI token and nothing else. */
    --accent: light-dark(var(--avn-test-blue),
                         color-mix(in srgb, var(--avn-test-blue-light) 45%,
                                   var(--ink)));
    /* The second algorithm's line. Warm, because this network's accent is
       blue and the second line has to be told from it at a glance; the one
       thing it may not be is teal or mint, for the reason at the top of
       this file. Checked against the accent for both kinds of red-green
       colour blindness. */
    --series-2: light-dark(#8a5a00, #e8b339);
    --badge-bg: var(--avn-test-indigo);
    /* Three borders that are identity colours rather than furniture: the
       badge's outline, the rule beside the limits list, and the one under a
       table head. They are tokens so that the mainnet block below can replace
       them. Hardcoding any of them is how a mainnet page ends up wearing
       testnet purple, which the guidelines forbid for the good reason that
       the colour is what says which chain this is. */
    --badge-border: #625bb1;
    --limit-rule: var(--avn-test-blue-light);
    --table-rule: #6866ab;

    /* Functional, and deliberately not from the identity palette: the brand
       defines no success or error colour, and borrowing mainnet's teal for
       "connected" would undo the one thing the testnet palette is for. Both
       pairs clear WCAG AA against their own paper. */
    --ok:  light-dark(#0f7a4f, #43c98b);
    --bad: light-dark(#a3341f, #f08a72);
    --error-bg: light-dark(#fdece9, #36201e);

    /* The second line on a chart, and functional for the same reason as the
       two above. The first line takes --accent, so the second cannot come
       from either identity palette: mainnet's would have to be mint or
       turquoise, a shade of the accent it has to be told apart from, and
       testnet's palette is five variations on one blue. Amber is the one
       hue not already spoken for by the accent, by --ok or by --bad, and
       against a teal or a blue accent it is the complement rather than a
       clash. It is also the safest pair there is for colour blindness: a
       cyan and an amber differ on the blue/yellow axis, which the common
       red/green deficiencies leave intact. Both halves clear WCAG AA
       against their own paper, 5.9:1 and 9.1:1. */

    /* 4px and 1180px, not the guidelines' 10px and 1040px. A deliberate
       departure, recorded here rather than left as an override further down
       the file: a second bare :root used to carry these and took a whole
       competing dark palette with it, which is why nobody ever saw the one
       below. */
    --radius: 4px;
    --container: 1180px;
    --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
                 Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: "Roboto Mono", ui-monospace, "Cascadia Code",
                 "Segoe UI Mono", Consolas, monospace;
  }

  /* Mainnet's identity, for a deployment on the main chain. Set by
     data-network on <html>, which web/app.py writes from NETWORK, so the
     palette cannot disagree with the wordmark or the badge: they come from
     one dict.

     Teal on the light paper is 4.65:1 and Cyan on the dark paper is 6.05:1,
     both clearing WCAG AA for text, so unlike the testnet accent neither
     needs mixing toward the ink. The mainnet palette has light tones and the
     testnet palette does not, which is the whole reason that one had to be
     adjusted and these two can be brand colours verbatim. */
  :root[data-network="mainnet"] {
    --avn-mint:      #34f5c6;
    --avn-turquoise: #34e2d5;
    --avn-cyan:      #17a7b6;
    --avn-teal:      #19827a;
    --avn-slate:     #2a737f;

    --accent: light-dark(var(--avn-teal), var(--avn-cyan));
    /* The second algorithm's line. Violet rather than the warm tone the
       testnet half uses: against teal, warm reads as a warning, and the
       chart's second line is not a warning about anything. Checked against
       the accent on both papers, and separable under both kinds of
       red-green colour blindness as well as blue-yellow. */
    --series-2: light-dark(#5f39b0, #8f63e6);
    --badge-bg: var(--avn-teal);
    --badge-border: var(--avn-cyan);
    --limit-rule: var(--avn-cyan);
    --table-rule: var(--avn-slate);
  }

  /* What the theme toggle does, and the whole of what it does. Forcing a
     theme is a change of color-scheme, which leaves the light-dark() pairs
     above as the only place in this file a colour is written down. It also
     themes the native select and the search inputs, which a palette override
     on its own would have left on the system's theme.

     With no attribute - no stored choice, no JavaScript, or storage blocked -
     the page follows the system preference, which is what it did before the
     toggle existed. */
  :root[data-theme="light"] { color-scheme: light; }
  :root[data-theme="dark"]  { color-scheme: dark; }

  * { box-sizing: border-box; }
  body {
    margin: 0; padding: 2rem 1rem; background: var(--paper); color: var(--ink);
    font: 400 17px/1.6 var(--font-body);
    -webkit-font-smoothing: antialiased;
  }
  main { max-width: var(--container); margin: 0 auto; }

  /* The mark sits on the page itself, never on a panel of its own: the
     guidelines call for the white wordmark on dark backgrounds, and boxing it
     on white instead would be a background the brand does not ask for. Light
     theme gets the testnet gradient, dark theme the white variant, and the
     badge beside it carries the network in both. The margin is the clear
     space the guidelines require, equal to the height of the mark. */
  .masthead {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 1.25rem; margin: 0 0 2rem;
  }
  /* Qualified by .masthead, because a bare .on-light would lose on
     specificity to the .masthead img rule above it and both marks would show
     at once, whatever the order. */
  .masthead img { height: 34px; width: auto; }
  /* Every mark whose owner supplies it in two colours and asks that it not
     be recoloured: the Avian wordmark here, Discord's and GitHub's in the
     footer. Both versions ship and the theme shows one. Not scoped to the
     masthead any more, because the footer needs the same rule and a second
     copy of it is how this file got its last three bugs.

     Which mark shows is the one thing light-dark() cannot carry, being a
     colour function where this is display, so the theme is tested twice:
     once for the system preference, once for a forced choice. The :not() is
     what lets a forced light theme beat a dark system - without it both
     rules would match and source order would decide, which is the bug this
     file already had once. */
  .on-light { display: inline-block; }
  .on-dark  { display: none; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .on-light { display: none; }
    :root:not([data-theme="light"]) .on-dark  { display: inline-block; }
  }
  :root[data-theme="dark"] .on-light { display: none; }
  :root[data-theme="dark"] .on-dark  { display: inline-block; }
  .badge {
    font-family: var(--font-mono); font-size: 15px; font-weight: 500;
    text-transform: uppercase; letter-spacing: .08em;
    background: var(--badge-bg); color: #ffffff;
    padding: .3rem .7rem; border-radius: var(--radius);
  }
  /* Icons. They are drawn once in the sprite at the top of the body and
     pulled in by <use>, and they carry no colour of their own: fill, stroke
     and stroke-width are inherited properties, so setting them here is what
     reaches inside the <use> shadow tree. currentColor is what makes them
     follow the theme and the link colour without knowing about either. */
  .sprite { display: none; }
  .icon {
    width: 1.15em; height: 1.15em; flex: none;
    fill: none; stroke: currentColor;
    stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round;
  }
  /* The UA stylesheet gives [hidden] display:none, which any display rule of
     ours outranks. Everything that ships hidden and is laid out with flex
     needs this to stay hidden until its script unhides it. */
  [hidden] { display: none !important; }
  .visually-hidden {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }

  /* The theme control. A preference rather than anything to do with the
     pool, so it reads as quietly as the meta text beside it. */
  .theme { position: relative; margin-left: auto; }
  /* Icon only. The words are in the menu, where there is room to say all
     three at once and the reader is choosing between them; up here one word
     out of three is a label for a state nobody asked about. */
  .theme-button {
    display: flex; align-items: center; justify-content: center;
    font-size: 15px; line-height: 1;
    color: var(--ink-soft); background: transparent;
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: .5rem; cursor: pointer;
  }
  .theme-button:hover { color: var(--ink); border-color: var(--ink-soft); }
  .theme-menu {
    position: absolute; right: 0; top: calc(100% + .4rem); z-index: 20;
    min-width: 11.5rem; padding: .25rem;
    background: var(--paper);
    border: 1px solid var(--line); border-radius: var(--radius);
    box-shadow: 0 10px 28px rgb(0 0 0 / .22);
  }
  .theme-menu button {
    display: flex; align-items: center; gap: .6rem; width: 100%;
    font: 500 13px var(--font-body); text-align: left;
    color: var(--ink); background: transparent;
    border: 0; border-radius: var(--radius);
    padding: .5rem .6rem; cursor: pointer;
  }
  .theme-menu button:hover { background: var(--paper-alt); }
  .theme-menu button[aria-checked="true"] { color: var(--accent); }
  /* The tick marks the one in force, and keeps its space when it is not
     shown so that choosing a different row does not move the others. */
  .theme-menu .icon-check { margin-left: auto; visibility: hidden; }
  .theme-menu button[aria-checked="true"] .icon-check { visibility: visible; }
  .theme-button:focus-visible,
  .theme-menu button:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
  }

  h1 { font-weight: 700; font-size: 44px; line-height: 1.15;
       margin: 0 0 .5rem; letter-spacing: -0.02em; }
  h2 { font-weight: 700; font-size: 32px; line-height: 1.2;
       margin: 2.5rem 0 .75rem; letter-spacing: -0.01em; }
  h3 { font-weight: 600; font-size: 22px; line-height: 1.3; margin: 0 0 .75rem; }
  .sub { color: var(--ink-soft); margin: 0 0 2rem; max-width: 60ch; }

  .row { display: flex; flex-wrap: wrap; gap: 1.75rem; margin-bottom: 1.5rem; }
  .stat { min-width: 7.5rem; }
  .stat b { display: block; font-family: var(--font-mono); font-size: 1.75rem;
            font-weight: 500; line-height: 1.2; color: var(--accent); }
  .stat span { color: var(--ink-soft); font-size: .8rem; text-transform: uppercase;
               letter-spacing: .06em; }

  table { width: 100%; border-collapse: collapse; margin-bottom: .5rem; }
  th, td { text-align: left; padding: .5rem .65rem;
           border-bottom: 1px solid var(--line); }
  th { color: var(--ink-soft); font-weight: 600; font-size: .78rem;
       text-transform: uppercase; letter-spacing: .06em; }
  thead th { border-bottom: 2px solid var(--accent); }
  td { font-size: 15px; }
  td.num, th.num { text-align: right; font-family: var(--font-mono);
                   font-variant-numeric: tabular-nums; }

  .card { background: var(--paper-alt); border: 1px solid var(--line);
          border-radius: var(--radius); padding: 1.25rem 1.5rem;
          margin-bottom: 1.25rem; }
  .ok  { color: var(--ok); font-weight: 600; }
  .bad { color: var(--bad); font-weight: 600; }
  .note { color: var(--ink-soft); font-size: .9rem; }
  code { font-family: var(--font-mono); font-size: 15px;
         background: color-mix(in srgb, var(--ink) 8%, transparent);
         padding: .1rem .35rem; border-radius: 4px;
         overflow-wrap: anywhere; }
  .limits { border-left: 3px solid var(--limit-rule);
            padding-left: 1.25rem; }
  .limits li { margin-bottom: .5rem; }
  footer { margin-top: 3rem; padding-top: 1.25rem;
           border-top: 1px solid var(--line); color: var(--ink-soft);
           font-size: .9rem; }
  footer a { color: var(--accent); }
  /* Flex with a gap rather than middots between the links: a separator that
     has wrapped to the start of a line is a worse look than none at all, and
     this collapses to a readable column on a phone without a media query. */
  .footer-links { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem;
                  margin: 0 0 1.25rem; }
  .footer-links a {
    display: inline-flex; align-items: center; gap: .45rem; font-weight: 600;
  }
  /* Height only, so the Discord mark keeps its 126.6x96 and does not get
     squared off into GitHub's 98x96. Slightly smaller than .icon because
     these are solid shapes beside hairline drawings and match the weight of
     the text better at this size. */
  .footer-links .mark { height: 1em; width: auto; }
  .footer-links a:focus-visible { outline: 2px solid var(--accent);
                                  outline-offset: 3px; }
  #error { color: var(--bad); font-weight: 600; }

  @media (max-width: 40rem) {
    body { padding: 1.25rem .75rem; }
    h1 { font-size: 32px; }
    h2 { font-size: 24px; }
    .plate { padding: 24px; }
    .plate img { height: 24px; }
    .hide-narrow { display: none; }
  }

  body {
    padding: 0 1.5rem;
    background: var(--paper);
      background-image: repeating-linear-gradient(90deg, transparent 0, transparent 119px, color-mix(in srgb, var(--ink) 3%, transparent) 120px);
    font-size: 16px;
  }
  .masthead {
    min-height: 108px; margin: 0; gap: 2.25rem;
    border-bottom: 1px solid var(--line);
  }
  /* No wordmark swap here. It belongs to the media query above, and
     restating the dark branch unconditionally - which this did - left the
     white mark on white paper for anyone whose system asks for a light
     page. */
  .badge {
    padding: .3rem .6rem; border: 1px solid var(--badge-border);
    border-radius: 2px; font-size: 12px; letter-spacing: 0;
  }
   .intro { padding: 2.75rem 0 2.25rem; border-bottom: 1px solid var(--line); }
  .eyebrow, .section-label {
    color: var(--accent); font: 500 12px var(--font-mono);
    text-transform: uppercase; letter-spacing: 0;
  }
   .eyebrow { display: flex; align-items: center; gap: .65rem; margin: 0 0 .8rem; }
   .eyebrow::before { content: ""; width: 1.5rem; height: 2px; background: var(--accent); }
  h1 { font-size: 44px; letter-spacing: 0; }
  h2 { margin: 0; font-size: 24px; letter-spacing: 0; }
  h3 { font-size: 18px; }
  .sub { margin: .8rem 0 0; max-width: 68ch; }
  .section-heading {
    display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline;
      gap: .6rem 1rem; margin: 2.75rem 0 1.1rem;
  }
   .section-heading h2 { display: flex; align-items: center; gap: .7rem; }
   .section-heading h2::before { content: ""; width: 3px; height: 1em; background: var(--accent); }
  .section-label { color: var(--ink-soft); }
  .miner-link {
    display: inline-flex; align-items: center; gap: .4rem;
    color: var(--accent); font-size: .9rem; font-weight: 600;
  }
  .miner-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
  .row {
    display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
      gap: 0; margin: 0; background: var(--paper-alt); border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
  }
  /* The rules above and below the figures are the figures' own, so an
     empty row would draw a line across the page with nothing in it: which
     is what the overview is while the node catches up and there is no pool
     to count. */
  .row:empty { display: none; }
   .stat { min-width: 0; padding: 1.25rem 1rem; }
   .stat + .stat { padding-left: 1rem; border-left: 1px solid var(--line); }
   .stat b { color: var(--ink); font-size: 1.35rem; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
  .stat span { display: block; margin-top: .5rem; font-size: .7rem; letter-spacing: 0; }
  .card { border-radius: 4px; padding: 1.25rem; }
  .connection { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
  .connection .card { min-width: 0; margin: 0; }
  .connection .card:empty { display: none; }
  /* Across both columns: what this card is for is a command, and a command
     in a half-width box either wraps or scrolls sideways. */
  .connection .card.wide { grid-column: 1 / -1; }
  .connection code { display: inline-block; margin-top: .7rem; }
  /* A flight sheet's fields: the label on the left, what goes in it on the
     right. A grid and not a table because it is a list of pairs, and the
     widths want to follow the longest label rather than be declared. */
  .fields { display: grid; grid-template-columns: max-content 1fr;
            gap: .45rem 1.25rem; margin: 1rem 0 0; }
  .fields dt { color: var(--ink-soft); }
  /* anywhere, because the values are an authority key and a URL: both are
     one unbroken token and neither has a space to wrap at. */
  .fields dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
  .connection .fields code { margin-top: 0; }
  .mining-hint { margin-top: 1.25rem; }
  /* Stacked where a label and a fifty character key will not sit side by
     side. The label keeps its colour, so the pairing survives the layout. */
  @media (max-width: 40rem) {
    .fields { grid-template-columns: 1fr; gap: .2rem; }
    .fields dd { margin-bottom: .7rem; }
  }
  .command-line { display: flex; align-items: flex-start; gap: .5rem; margin-top: .7rem; min-width: 0; }
  .connection .command-line code {
    display: block; flex: 1; min-width: 0; margin: 0; padding: .5rem .65rem;
    overflow-x: auto; overflow-wrap: normal; white-space: pre;
  }
  .copy-command {
    flex: 0 0 2.5rem; height: 2.5rem; border: 1px solid var(--line); border-radius: 4px;
    background: var(--paper-alt); color: var(--ink); font-size: 1.2rem; cursor: pointer;
  }
  .copy-command:focus-visible, .command-line code:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
  }
  .copy-status:not(:empty) { display: block; margin-top: .35rem; }
  .copy-status.copied { color: var(--ok); }
  .algorithm { margin: 0; padding: 1.25rem 0; border-bottom: 1px solid var(--line); }
  .algorithm h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: .6rem 1rem; margin: 0 0 1rem; }
  .algorithm h3 .ok, .algorithm h3 .bad, .algorithm h3 .note { font-size: .8rem; }
  .table-controls, .pagination {
    display: flex; flex-wrap: wrap; align-items: center; gap: .75rem;
  }
  .table-controls { justify-content: space-between; margin-bottom: .75rem; }
  .table-controls label, .pagination { color: var(--ink-soft); font-size: .8rem; }
  .table-controls input, .table-controls select, .pagination button {
    background: var(--paper-alt); color: var(--ink); border: 1px solid var(--line);
    border-radius: 4px; font: inherit; padding: .5rem .65rem;
  }
  .table-controls input { width: min(15rem, 100%); }
  .table-controls label { display: flex; align-items: center; gap: .5rem; }
  .pagination { justify-content: flex-end; margin-top: .75rem; }
  .pagination button { min-width: 2.25rem; cursor: pointer; }
  .pagination button:disabled { opacity: .4; cursor: default; }
  .table-controls :focus-visible, .pagination button:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
  }
  .table-wrap { width: 100%; overflow-x: auto; }
  table { min-width: 690px; margin: 0; }
  th, td { padding: .75rem .65rem; white-space: nowrap; }
  th { letter-spacing: 0; }
  thead th { border-bottom: 1px solid var(--table-rule); }
  td { font-size: 14px; }
  /* Out to the chain's explorer, from a height in the blocks table and from
     the tip each algorithm is working on. Underlined on hover rather than
     always: a column of numbers reads badly with a rule under every one. */
  td a, .algorithm-tip a { color: var(--accent); text-decoration: none; }
  td a:hover, .algorithm-tip a:hover { text-decoration: underline; }
  td a:focus-visible, .algorithm-tip a:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
  }
  .limits { margin: 0; padding-left: 1.25rem; }
  .limits li { margin-bottom: .75rem; }
  .limits b { color: var(--ink); }
  .about-pool { max-width: 76ch; color: var(--ink-soft); }
  #error:empty { display: none; }
  #error { padding: .8rem 1rem; background: var(--error-bg); border-left: 3px solid var(--bad); }
  /* Waiting for a chain to download is not a fault, and colouring it like
     one trains a reader to ignore the colour that means something. */
  #error.waiting { color: var(--ink-soft); font-weight: 500;
                   background: var(--paper-alt); border-left-color: var(--line); }
  footer { margin: 3rem 0 0; padding: 1.5rem 0 2rem; font-size: .8rem; }
  footer p { max-width: 80ch; }

  /* The donation mark in a worker row. Inline, so it needs the baseline
     nudge the flex-laid icons elsewhere do not. Green when the donation
     actually goes somewhere and grey when the pool has nowhere to send it,
     which is a worker being generous into a configuration that cannot
     accept it. */
  .icon.donation { display: inline-block; vertical-align: -.22em;
                   margin-left: .3rem; color: var(--ok); }
  .icon.donation.unpaid { color: var(--ink-soft); }

   .chart { margin: 1.75rem 0 .75rem; }
   .chart h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem .8rem;
                     margin: 0; padding: .8rem 1rem; font-size: 1rem;
                     background: var(--paper-alt); border: 1px solid var(--line); border-bottom: 0; }
  .chart-latest { flex-basis: 100%; color: var(--ink-soft); font: 400 .75rem/1.5 var(--font-mono);
                  overflow-wrap: anywhere; }
  .chart-latest:not(:empty)::before { content: "LATEST RECORDED / "; color: var(--accent); }
  .chart-mount { width: 100%; min-width: 0; padding: .8rem 0 .5rem;
                         background: var(--paper-alt); border: 1px solid var(--line); }

  /* uPlot, wearing this page's clothes.
   *
   * uplot.css is loaded first and sets a system font stack, its own greys
   * and a legend laid out as a table. Everything below is this page having
   * the last word on the parts it has an opinion about; the geometry is
   * left alone, because that is the library doing its job. */
  .uplot, .u-legend { font-family: var(--font-body); }
  /* Left, with the chart, rather than centred under it: every other caption
     and legend on this page starts at the same margin.

     And no min-width, which the rule for this page's own tables was handing
     it: a legend is a <table>, so it inherited the 690px that keeps a data
     table readable, and on a phone that is wider than the screen. The table
     that earns that width sits in a .table-wrap and scrolls inside it; this
     one is not in one, so it pushed the whole document sideways instead.
     Measured at a 501px viewport: the page scrolled to 706. */
  .u-legend { margin: .4rem 0 0; text-align: left; color: var(--ink-soft);
              font-size: .8rem; min-width: 0; }
  .u-legend tr { display: inline-flex; align-items: center;
                 gap: .4rem; margin-right: 1.25rem; }
  /* These are series names, not column headings. The page uppercases a
     thead th and this is neither. */
  .u-legend th { font-weight: 400; text-transform: none; letter-spacing: 0;
                 padding: 0; border: 0; white-space: nowrap; }
  .u-legend td { padding: 0; border: 0; }
  /* The value beside each name while the cursor is over the chart. Tabular
     figures, because it changes on every pixel of movement and a column of
     numbers that jitters sideways is harder to read than one that does
     not. */
  .u-legend .u-value { font: 400 .8rem var(--font-mono);
                       font-variant-numeric: tabular-nums; color: var(--ink); }
  /* A hidden series is still listed, so there is something to click to
     bring it back. */
  .u-legend .u-off .u-marker, .u-legend .u-off .u-label { opacity: .45; }
  .u-legend .u-series:hover { cursor: pointer; }
  .u-select { background: var(--accent); opacity: .15; }
  .u-cursor-x, .u-cursor-y { border-color: var(--line); }
  .u-cursor-pt { border-color: var(--paper); }
  .uplot :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .chart-legend { display: flex; flex-wrap: wrap; gap: .25rem 1.25rem;
                  margin: .4rem 0 0; color: var(--ink-soft);
                  font-size: .8rem; }
  .chart-legend span { display: inline-flex; align-items: center; gap: .4rem; }
  .chart-info, .chart-dialog-close { display: inline-flex; align-items: center; justify-content: center;
                                     width: 1.75rem; height: 1.75rem; padding: .25rem;
                                     border: 0; background: transparent; color: var(--ink-soft); cursor: pointer; }
  .chart-info:hover, .chart-dialog-close:hover { color: var(--accent); }
  .chart-info:focus-visible, .chart-dialog-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .chart-info svg, .chart-dialog-close svg { width: 100%; height: 100%; fill: none;
                                             stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
  .chart-dialog { box-sizing: border-box; width: min(36rem, calc(100% - 2rem)); max-height: calc(100% - 2rem);
                  padding: 1.5rem; border: 1px solid var(--line); border-top: 2px solid var(--accent);
                  background: var(--paper-alt); color: var(--ink); }
  .chart-dialog::backdrop { background: rgb(0 0 0 / .65); }
  .chart-dialog-heading { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
  .chart-dialog-heading h2 { margin: 0; font-size: 1.1rem; }
  .chart-dialog p { margin: 1rem 0 0; color: var(--ink-soft); line-height: 1.65; }
  /* The swatches are wrapped only so that rebuilding them cannot take the
     cursor readout beside them with it. The wrapper is not a layout: its
     children are the legend's own items, spaced by the rule above. */
  .chart-legend .block-legend { display: contents; }
  @media (max-width: 60rem) {
    .row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
   .stat:nth-child(4) { border-left: 0; padding-left: 1rem; }
    .stat:nth-child(n+4) { border-top: 1px solid var(--line); }
  }
  @media (max-width: 40rem) {
    body { padding: 0 1rem; }
    .masthead { min-height: 96px; gap: 2.125rem; }
    .intro { padding: 2.5rem 0 2rem; }
    h1 { font-size: 32px; }
    h2 { font-size: 22px; }
    .row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .stat:nth-child(4) { border-left: 1px solid var(--line); padding-left: 1rem; }
   .stat:nth-child(odd) { border-left: 0; padding-left: 1rem; }
    .stat:nth-child(n+3) { border-top: 1px solid var(--line); }
    .connection { grid-template-columns: 1fr; }
    .hide-narrow { display: table-cell; }
  }

  main { max-width: 1540px; }
  .masthead { min-height: 100px; }
  .workspace { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 2.5rem; }
  .section-nav { position: sticky; top: 1.5rem; align-self: start; display: flex;
                 flex-direction: column; padding: 2.25rem 0; }
  .nav-caption { color: var(--ink-soft); font: 500 11px/1.6 var(--font-mono); }
  .nav-caption { margin: 0 0 1.1rem .75rem; text-transform: uppercase; }
  .section-nav a { display: flex; align-items: center; gap: .9rem; padding: .7rem .75rem;
                   color: var(--ink-soft); text-decoration: none; font-size: .85rem;
                   border-left: 2px solid transparent; }
  .section-nav a span { color: var(--accent); font: 11px var(--font-mono); }
  .section-nav a:hover, .section-nav a:focus-visible, .section-nav a.is-active { color: var(--ink); background: var(--paper-alt);
                                                        border-left-color: var(--accent); }
  .section-nav a:focus-visible, .connect-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .dashboard { min-width: 0; padding-bottom: 1rem; }
  .dashboard section { scroll-margin-top: 1.5rem; }
  .intro { display: grid; grid-template-columns: minmax(0, 1fr) 230px; gap: 2rem;
           padding: 3rem 0; align-items: end; }
  .intro h1 { font-size: 34px; }
  .intro .sub { max-width: 70ch; font-size: .94rem; }
  .connect-link { display: inline-flex; align-items: center; gap: 1.1rem; margin-top: 1.4rem;
                  padding: .7rem .9rem; background: var(--accent); color: var(--paper);
                  font-size: .85rem; font-weight: 700; text-decoration: none; }
  .connect-link:hover { filter: brightness(1.12); }
  .intro-specs { margin: 0; border-top: 1px solid var(--line); }
  .intro-specs div { display: flex; justify-content: space-between; gap: .75rem;
                     padding: .6rem 0; border-bottom: 1px solid var(--line); }
  .intro-specs dt { color: var(--ink-soft); font-size: .75rem; }
  .intro-specs dd { margin: 0; font: 500 .75rem var(--font-mono); text-align: right; }
  .section-heading { align-items: center; margin: 3rem 0 1.25rem; }
  .section-heading h2 { font-size: 20px; }
  .section-heading h2::before { height: 1.15em; }
  .row { border: 1px solid var(--line); }
  .stat { min-height: 112px; display: flex; flex-direction: column-reverse; justify-content: center; gap: .6rem; }
  .stat span { margin: 0; }
  .stat b { font-size: 1.55rem; }
  .algorithm { padding: 1.5rem 0; }
  .algorithm h3 { margin-bottom: 1.25rem; }
  .table-wrap { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
  tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
  .connection .card { border-top: 2px solid var(--accent); }
  .copy-command:hover, .pagination button:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }
  footer { display: grid; grid-template-columns: minmax(0, 1fr) minmax(220px, .7fr);
           gap: 2rem 4rem; margin-top: 4rem; padding: 2.25rem 0 3rem;
           border-top: 2px solid var(--line); }
  .footer-about { min-width: 0; }
  footer .footer-kicker { margin: 0 0 .65rem; color: var(--accent);
                          font: 500 11px/1.5 var(--font-mono); text-transform: uppercase; }
  footer .footer-title { margin: 0 0 .9rem; color: var(--ink); font-size: 1.1rem; font-weight: 700; }
  .footer-about > p:not(.footer-kicker):not(.footer-title) { max-width: 58ch; margin: .65rem 0 0;
                                                             line-height: 1.65; overflow-wrap: anywhere; }
  .footer-resources h2 { margin: 0 0 .85rem; color: var(--ink-soft);
                         font: 500 11px/1.5 var(--font-mono); text-transform: uppercase; }
  .footer-links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .25rem 1rem; margin: 0; }
  .footer-links a { min-width: 0; padding: .5rem 0; color: var(--ink); text-decoration: none;
                    font-size: .85rem; overflow-wrap: anywhere; }
  .footer-links a:hover { color: var(--accent); }
  .chart-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 1.25rem; }
  .chart-column { display: grid; grid-template-rows: subgrid; grid-row: span 3; min-width: 0; }
  .chart { display: grid; grid-template-rows: subgrid; grid-row: span 2; margin: 0; }
  .chart h3 { min-height: 100px; align-content: start; padding: 1.15rem;
              border-top: 2px solid var(--accent); }
  .chart-mount { padding: .7rem 0 1rem; }
  .chart-latest { margin-top: .35rem; }
  .chart-legend { margin-top: .75rem; }
  #panel-history > .table-controls { justify-content: flex-end; margin-bottom: 1rem; }
  #panel-history > .table-controls label { text-transform: uppercase; font: 500 11px var(--font-mono); }
  .table-controls input, .table-controls select, .pagination button { min-height: 38px; }
  @media (max-width: 80rem) {
    .chart-grid { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; }
    .chart-column, .chart { display: block; }
    .chart h3 { min-height: 0; }
  }
  @media (max-width: 60rem) {
    .workspace { display: block; }
    .section-nav { position: static; flex-direction: row; overflow-x: auto; gap: .25rem;
                   padding: .5rem 0; border-bottom: 1px solid var(--line); }
    .section-nav a { flex: 0 0 auto; border-left: 0; border-bottom: 2px solid transparent; }
    .section-nav a:hover, .section-nav a:focus-visible, .section-nav a.is-active { border-bottom-color: var(--accent); }
    .nav-caption { display: none; }
  }
  @media (max-width: 40rem) {
    .masthead { min-height: 88px; gap: 1rem; }
    .theme { margin-left: auto; }
    .intro { grid-template-columns: 1fr; gap: 1.5rem; padding: 2rem 0; }
    .intro h1 { font-size: 28px; }
    .intro-specs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .75rem; }
    .intro-specs div { display: block; min-width: 0; }
    .intro-specs dd { margin-top: .25rem; text-align: left; overflow-wrap: anywhere; }
    .stat { min-height: 96px; }
    footer { grid-template-columns: minmax(0, 1fr); gap: 1.75rem; padding-bottom: 2rem; }
  }

  /* The explanation a cell has no room for.
   *
   * This stands in for the title attribute, which is the wrong carrier for a
   * sentence. It never appears on a touch screen at all, which is the width
   * this table was laid out for. It waits about a second and then takes
   * itself away again. It cannot wrap, be styled, or hold markup. Several of
   * the ones here are over a hundred characters of real explanation, which
   * is too much to hand to a mechanism that mostly does not run.
   *
   * One element reused for every trigger, rather than a pseudo element on
   * each. The tables scroll sideways and a pseudo element belonging to a
   * cell is clipped by that; a fixed one is positioned against the viewport
   * instead, which is also what lets it sit above an open dialog.
   *
   * Laid out whether or not it is shown, because it has to be measured
   * before it can be placed. visibility, not display, for the same reason. */
  .tip {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 60;
    max-width: min(26rem, calc(100vw - 2rem));
    padding: .5rem .65rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--paper-alt);
    color: var(--ink);
    font: 400 13px/1.45 var(--font-body);
    text-align: left;
    text-transform: none;
    letter-spacing: normal;
    box-shadow: 0 6px 20px light-dark(rgb(22 35 42 / 18%), rgb(0 0 0 / 45%));
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .12s ease;
  }

  .tip[data-shown] { opacity: 1; visibility: visible; }

  /* Pointing at the trigger, not at the middle of a bubble that had to slide
     sideways to stay on screen. */
  .tip::after {
    content: "";
    position: absolute;
    left: var(--arrow-x, 50%);
    width: 8px;
    height: 8px;
    margin-left: -4px;
    background: var(--paper-alt);
    border: 1px solid var(--line);
    transform: rotate(45deg);
  }

  .tip[data-side="above"]::after { bottom: -5px; border-top: 0; border-left: 0; }
  .tip[data-side="below"]::after { top: -5px; border-bottom: 0; border-right: 0; }

  /* The one affordance, since a title never had one either. A button or a
     link already says what it is with its own cursor. */
  [data-tip]:not(button):not(a) { cursor: help; }

  @media (prefers-reduced-motion: reduce) {
    .tip { transition: none; }
  }
