/* ============================================================================
   GVO PORTAL — DESIGN TOKENS  v4  "INCORPORATED"
   Cream paper, black didone. Supersedes v3 "DAYLIGHT" (white + blue).

   Source of truth is the GVO Incorporated wordmark Oscar sent 2026-08-07.
   Every colour below was SAMPLED from that file, not eyeballed off it:

       convert 16c_didot_tagline.png -colors 8 -format %c histogram:info:
         1026492 px  #F5F1E8   the paper
           17126 px  #1B1B1B   the ink
            1859 px  #595855   the tagline grey

   Still zero deps and zero CDN. One font file is now SELF-HOSTED (§5).

   ── THE FIVE THINGS TO READ BEFORE INTEGRATING ────────────────────────────

   1. THERE IS NO ACCENT HUE, BECAUSE THE ARTIFACT HAS NONE.
      The wordmark is two colours and a grey. So `--blue` — v3's brand and
      interaction hue — collapses to INK, and the build goes monochrome.
      Every v2/v3 name still resolves (`--red-*` → `--blue-*` → ink), for the
      reason v3 gave and it has not stopped being true: an undefined var inside
      a `font:` or `box-shadow:` shorthand kills the whole declaration SILENTLY.

      TWO REAL COSTS, both handled here rather than left to be discovered:

      a. `a:hover{color:var(--blue)}` became a no-op the moment blue equalled
         ink — hover with nothing to see. Links now hover to an UNDERLINE.
         A hue was doing that work; something else has to.
      b. v3 shipped with 5 `--red` sites in agents.html that meant ALARM, not
         brand, and warned that until they were migrated a down agent would
         render blue. They HAVE since been migrated — verified before writing
         this file, `grep -n '\.down' templates/agents.html` returns
         `--alarm-fill` at :205 :259 :261 :263 :278 :334 :336 :352 and no
         `--red`. That check was not optional: under v3 the bug painted a down
         agent BLUE, which is merely wrong; under v4 it would paint it INK —
         identical to a healthy one. The same latent defect gets strictly worse
         as the palette gets quieter, and nothing would have failed.

   2. EVERY NUMBER IS RE-MEASURED, NOT REMAPPED. v3's alphas are WRONG here.
      Its ink was navy rgba(10,22,40) — darker in luminance than #1B1B1B — so
      carrying its alphas across silently loses contrast. WCAG 2.1, real alpha
      compositing, against BOTH the panel (#F5F1E8) and the field (#EEE9DE),
      because micro-type sits on both and the field is the worse case:
          v3 --ink-3 (.62) on this cream ...  4.51:1  ← passes by 0.01
      That is the token the rail badge uses, and it is the token that already
      auto-reverted one ship. 4.51 is not a pass, it is a coin toss against the
      auditor's rounding. Re-solved to .66 → 5.10:1 on the field. §CONTRAST.

   3. THE PANEL IS THE PAPER; THE FIELD SITS BELOW IT.
      v3 made panels #fff on a tinted field. Doing that here would put the
      logo's cream on the FIELD and drift every card toward white — the
      opposite of the artifact. So the cream IS `--s1`, and the field is one
      step darker. Stackability preserved and measured: 1.074:1, v3 was 1.070.
      Hover still tints DOWNWARD toward the field, so every "hover = --s2"
      rule keeps working and keeps meaning the right thing.

   4. GLOW IS STILL DELETED; RINGS ARE STILL RINGS. Unchanged from v3, and
      `--glow`/`--alarm-glow` are still 0-blur box-shadow-legal for the same
      silent-failure reason. See v3 §4 — the three `filter:drop-shadow()` sites
      in server.html and their fix list are still in §MIGRATION.

   5. ONE SELF-HOSTED FONT, AND IT IS NOT A TASTE CHOICE.
      The wordmark is a didone. A CSS stack (`Didot, "Bodoni MT", …`) renders
      correctly on Oscar's Mac and iPhone and falls back to Times on Windows
      and to DejaVu in the headless Chromium that takes our audit screenshots.
      That last one is the trap: I would be approving a picture of a typeface
      nobody else is seeing. So the face is served by the portal and is
      therefore identical everywhere, including in the evidence.
      GFS Didot, SIL Open Font License 1.1, already on this box as a Debian
      package. Subset to Latin + punctuation and converted to woff2:
      166,580 → 13,188 bytes. Licence ships beside it at
      /static/fonts/GFSDidot-OFL.txt, which the OFL requires. No spend, no CDN.
   ========================================================================= */

/* The display face. `swap`, so a slow first paint shows the fallback rather
   than invisible text — the wordmark is in the header of every page. */
@font-face{
  font-family:"GVO Didot";
  src:url("/static/fonts/gvo-display.woff2") format("woff2");
  font-weight:400; font-style:normal; font-display:swap;
}
/* The italic is a SEPARATE FACE, not a convenience. Declare only the roman and
   the browser synthesises the tagline by shearing the roman ~12° — which on a
   didone shears the hairlines too, so the thin strokes go from vertical to
   slanted and the face stops being a didone. It is the one typeface class where
   a faked oblique is obvious at 11px. 10KB to not do that. */
@font-face{
  font-family:"GVO Didot";
  src:url("/static/fonts/gvo-display-italic.woff2") format("woff2");
  font-weight:400; font-style:italic; font-display:swap;
}

:root{
  /* ---- FIELD --------------------------------------------------------------
     See §3. The logo's #F5F1E8 is the PANEL, and the field is one step down
     from it, so a card is the paper and the page is the table it lies on.
     Measured 1.074:1 — reads as one colour, still stacks. */
  --bg:      #eee9de;
  --bg-2:    #e7e1d4;
  --s1:      #f5f1e8;          /* THE PAPER, sampled. Resting panel, pair --sh-2 */
  --s2:      #e9e3d7;          /* row hover / recessed tint — DOWNWARD (see §3) */
  --s3:      #faf7f1;          /* popover, active row — pair with --sh-3 */
  --well:    #ede8dc;          /* inline well: inputs' sibling, small code runs */

  /* The ONE dark surface, and it is a considered exception rather than a
     leak. A terminal pane is a device, not a panel: it is where dense mono
     output and any ANSI colour land, and every convention Oscar has ever used
     puts that on dark. Scoped to `.term`/`pre.term` — NOT to `.well`, because
     8 dark rectangles on the Fleet page would make a white page read dark.
     v4: warmed to match. A blue-black terminal on cream paper reads as a
     foreign object pasted in; #1a1714 is the logo ink taken down, not a new
     hue. */
  --sunk:     #1a1714;
  --sunk-ink: rgba(255,255,255,.86);   /* 13.83:1 on --sunk */
  --sunk-dim: rgba(255,255,255,.60);   /*  6.87:1 on --sunk */

  --panel:   var(--s1);        /* legacy alias — do not remove */
  --panel2:  var(--s2);        /* legacy alias — do not remove */

  /* ---- INK ----------------------------------------------------------------
     rgba(27,27,27,α) — the sampled ink, dead neutral, which is what the
     artifact actually is (26.97, 26.95, 26.91: not a warm black, not a cool
     one). Resisting the urge to warm it is the point; a neutral black is what
     makes the cream read as cream rather than as beige.
     Measured on panel (#f5f1e8) / on field (#eee9de):
       --ink    13.85 / 12.94      --ink-4   1.65 / 1.64   (rules, no meaning)
       --ink-2   9.60 /  9.11      --ink-5   1.21 / 1.21   (grid, no meaning)
       --ink-3   5.26 /  5.10
     🔴 THE ALPHAS ARE NOT v3's, AND CARRYING THEM ACROSS WOULD HAVE SHIPPED A
     FAILURE. v3's ink was navy #0a1628, darker in luminance than #1B1B1B, so
     every level loses ground on the swap. v3's .62 measures 4.51 here — over
     the 4.5 line by one hundredth, on the token that carries ALL micro-type.
     Re-solved, not remapped: .94→.96, .78→.84, .62→.66. */
  --ink:      rgba(27,27,27,.96);   /* headline / primary data */
  --ink-2:    rgba(27,27,27,.84);   /* labels, secondary data, telemetry */
  --ink-3:    rgba(27,27,27,.66);   /* micro-type, units, axes */
  --ink-4:    rgba(27,27,27,.24);   /* rules, brackets, grid furniture */
  --ink-5:    rgba(27,27,27,.10);   /* the overlay grid itself */

  /* Dark-on-light hairlines need MORE alpha than their inverse to carry the
     same weight — v3's finding, and it holds on cream too. Nudged up one notch
     from v3 for the same reason the ink was: less luminance distance to spend. */
  --line:    rgba(27,27,27,.22);
  --line-2:  rgba(27,27,27,.34);
  --edge:    rgba(27,27,27,.10);

  --fg:      #1b1b1b;          /* legacy alias — THE sampled ink, 15.28:1 */
  --dim:     #595855;          /* legacy alias — THE sampled tagline grey, 6.31:1.
                                  The one grey in the build that is quoted rather
                                  than invented. */
  --mute:    #666259;          /* placeholders — 5.39 panel / 5.02 field.
                                  #6b675f measured 4.65 on the field: too close. */

  /* ---- BRAND — ink, because the artifact has no second colour ------------
     The `--blue-*` NAMES survive and the hue does not. ~31 references across
     agents/agent/home/server/login/join/base resolve through here, and
     renaming them would either repaint the build by hand or kill declarations
     silently (§1). So this block is the single place the brand hue lives, and
     today it is black.

     ⚠️ `--blue` and `--fg` are now the same colour. That is correct — brand IS
     ink here — but it means any rule of the form "hover from ink to brand" is
     now a no-op that renders as a dead control. Grep for `:hover` + `--blue`
     before assuming a hover still does something. Fixed at the one site that
     had it (`a:hover`, now an underline); stated here because the next such
     rule someone writes will look fine in the CSS and do nothing on screen. */
  --blue:      #1b1b1b;              /* 15.28:1 on the paper */
  --blue-hi:   #000000;              /* hover/pressed — DEEPER. On light,
                                        "hi" = more ink, not more light. */
  --blue-lo:   #595855;              /* de-energised stroke — the tagline grey,
                                        6.31:1. Sampled, not invented. */
  --blue-line: rgba(27,27,27,.34);
  --blue-wash: rgba(27,27,27,.055);
  --blue-dim:  rgba(27,27,27,.30);
  --brand:     var(--blue);

  /* ---- LEGACY `--red-*` → BLUE (see §1 of the header) --------------------
     DEPRECATED. Kept only so unmigrated markup renders correctly. Do not
     write new `--red-*`. Write `--blue-*` for brand, `--alarm-*` for alarm. */
  --red:      var(--blue);
  --red-lo:   var(--blue-lo);
  --red-hi:   var(--blue-hi);
  --red-dim:  var(--blue-dim);
  --red-line: var(--blue-line);
  --red-wash: var(--blue-wash);
  --red-glow: rgba(27,27,27,.22);
  --accent:   var(--blue);

  /* ---- SIGNAL -------------------------------------------------------------
     THE RULE, RE-DERIVED. v2 had one hue doing two jobs, so the whole signal
     system rested on the user telling a crimson STROKE from a crimson FILL —
     a hard discrimination, and the reason v2 needed "exactly one filled thing
     per page" as a defensive rule. v3 splits the hues, so the rule gets
     simpler and strictly more robust:

         INK   = nominal. A healthy board is navy on white and totally quiet.
         BLUE  = brand + interaction. NEVER state. (This is the new constraint
                 the palette buys, and it is the whole reason the rest works.)
         OCHRE = degraded. Attention, not alarm. Hairline + label + faint wash.
         RED   = broken. Nothing else in this system is red, ever.

     Red is now separated from brand by HUE, not by treatment — so the old
     stroke-vs-fill rule survives as a second, redundant layer instead of the
     only one. Nexus's floor is met independently: every state below also
     carries a text label and a position, so colour is never the only channel.

     Two consequences worth stating because they look like bugs otherwise:
     · LIVE IS NOT WHITE ANY MORE. v2's live dot was #fff with a white glow —
       invisible on a white field. Live is a FILLED navy dot; idle is a HOLLOW
       one. Filled-vs-hollow is a stronger discrimination than light-vs-dim
       and it survives greyscale.
     · RECORDING IS BLUE, NOT RED. A red record dot is a real convention and
       it loses here on purpose: a red mic every time Oscar dictates trains him
       to ignore red, which is the one thing this system spends red on.
       (agent.html gets this for free through the alias — not an accident.)

     v4 NOTE: with the brand gone monochrome, "BLUE = interaction, never state"
     stops being a colour rule and becomes a SHAPE rule — nominal, brand and
     interaction are all ink now, and only degraded and broken carry hue. That
     is a narrower system than v3's, so the redundant channels it already had
     (filled-vs-hollow, a text label, a position) stop being belt-and-braces
     and start being the actual mechanism. Do not remove one of them thinking
     colour will cover it. Both surviving hues still measure:
       --degrade #8a5200 → 5.67 panel / 5.28 field
       --down    #ad0f1f → 6.50 panel / 6.05 field, and 7.33 under white text */
  --live:      var(--ink);             --live-mark:  #1b1b1b;
  --idle:      var(--ink-3);           --idle-mark:  rgba(27,27,27,.32);
  --degrade:   #8a5200;                --degrade-dim: rgba(138,82,0,.11);
  --down:      #ad0f1f;                --down-dim:   rgba(173,15,31,.10);

  --good:      var(--live);            /* legacy alias */
  --warn:      var(--degrade);         /* legacy alias */
  --bad:       var(--down);

  /* ---- ALARM --------------------------------------------------------------
     Reserved. #ad0f1f carries white text at 7.33:1 — v2's #e5222b measured
     4.57:1 under white text, which is why the alarm got deeper rather than
     brighter. On white, "violent" comes from saturation + fill + motion, not
     from luminance; a brighter red is a WEAKER alarm here.
     Nothing else in this sheet is permitted to use --alarm-fill. */
  --alarm-fill: #ad0f1f;
  --alarm-ink:  #ffffff;
  --alarm-glow: 0 0 0 3px rgba(173,15,31,.20);   /* a RING. 0 blur. see §4. */

  /* ---- TYPE ---------------------------------------------------------------
     `--font-display` is the wordmark's face and is used ONLY where the brand
     speaks: the lockup, h1, h2. Body copy stays system sans — a didone at
     14.5px on a screen is a hairline-serif smear, and the family are reading
     paragraphs here, not admiring the typography. The named fallbacks after
     "GVO Didot" are what a device shows during `swap`, so they are ordered by
     how close they sit to a didone rather than by how likely they are. */
  --font-display: "GVO Didot",Didot,"Bodoni MT","Playfair Display",
                  "Times New Roman",Times,serif;
  --font: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --font-mono: ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --mono: var(--font-mono);            /* legacy — an undefined var inside a  */
  --sans: var(--font);                 /* `font:` shorthand kills the whole   */
  --dimmer: var(--ink-3);              /* declaration silently. Keep all three.*/

  --t-micro: 9.5px;
  --t-xs:   10.5px;
  --t-sm:   12px;
  --t-md:   13.5px;
  --t-base: 14.5px;
  --t-lg:   16px;
  --t-xl:   21px;
  --t-2xl:  32px;
  --t-3xl:  46px;

  --track-wide: .16em;   /* .18em on v2. Dark-on-light type reads heavier at */
  --track:      .05em;   /* the same tracking; tightened so micro-caps on    */
  --lh-tight: 1.2;       /* white don't come apart into separate letters.    */
  --lh:       1.55;

  /* ---- SHAPE — unchanged ------------------------------------------------- */
  --r-xs: 0px;  --r-sm: 1px;  --r-md: 2px;  --r-lg: 3px;  --r-xl: 4px;
  --r-pill: 999px;
  --hair: 1px;

  /* ---- ELEVATION — real now ----------------------------------------------
     v2's --sh-1 was `none` because a shadow on true black is invisible. On
     white it is the ONLY depth cue, so all three carry weight. v3 tinted them
     navy to stay on-palette with the blue; the same argument now points the
     other way — a cool shadow under a cream card is the thing that reads
     dirty. Tinted WARM, rgba(74,60,40,α). Still static. */
  --sh-1: 0 1px 2px rgba(74,60,40,.06);
  --sh-2: 0 1px 3px rgba(74,60,40,.09), 0 10px 24px -14px rgba(74,60,40,.16);
  --sh-3: 0 22px 50px -18px rgba(74,60,40,.32);
  --sh-in: inset 0 1px 2px rgba(74,60,40,.08);
  --glow: 0 0 0 3px rgba(27,27,27,.13);    /* a RING, not a blur. see §4. */

  /* ---- GLOBE -------------------------------------------------------------
     globe.js reads these five. Its built-in fallbacks are v2-era dark-theme
     values and are now WRONG in two ways — see §MIGRATION note 3. In
     particular its --globe-marker default is #e5222b, which would spend the
     alarm colour on decoration. The marker is INK here: a filled navy pin
     with a blue halo. It reads against blue land without touching red. */
  --globe-ocean:  rgba(27,27,27,.045);
  --globe-land:   rgba(27,27,27,.62);
  --globe-grid:   rgba(27,27,27,.13);
  --globe-rim:    rgba(27,27,27,.28);
  --globe-marker: #1b1b1b;

  /* ---- MOTION / LAYOUT — unchanged -------------------------------------- */
  --ease:      cubic-bezier(.4,0,.2,1);
  --ease-out:  cubic-bezier(.16,1,.3,1);
  --d-fast:    .1s;
  --d:         .16s;
  --d-slow:    .3s;
  --maxw: 1400px;
  --gut:  24px;
}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
}

/* ============================================================================
   BASE
   ========================================================================= */

*{box-sizing:border-box}
/* `color-scheme:light` is what makes the UA paint scrollbars, form controls and
   the overscroll gutter light. Without it a dark-mode phone renders a light page
   with black scrollbars. This is the whole dark-mode story — one keyword, no
   media query, per the "one theme" instruction. */
html{color-scheme:light;background:#eee9de}

body{
  margin:0; color:var(--fg);
  font:var(--t-base)/var(--lh) var(--font);
  -webkit-font-smoothing:antialiased;
  background:var(--bg);
  /* Two blue washes, fixed, one paint. Much weaker than v2's (.05/.03 vs
     .10/.055): a tint on black adds atmosphere, the same tint on white just makes
     the paper look stained.

     THE 44px GRAPH-PAPER GRID IS GONE — Oscar, 2026-07-31: "Let's remove the grid
     on the background". It was a holdover from the dark instrument-board look he
     has now rejected twice (see also the mono/uppercase chat register). Removed
     rather than faded: a grid at 2% is still a grid, just one you argue about.
     No background-size is needed now — a radial-gradient sized `100% 100%` is what
     the default already gives you.

     v4: the washes are warm now and weaker again (.04/.025). A blue wash on
     cream is the single fastest way to make warm paper look grubby, and the
     cream is already doing the atmospheric work the washes were added for. */
  background-image:
    radial-gradient(900px 520px at 82% -8%, rgba(120,96,58,.05), transparent 62%),
    radial-gradient(760px 480px at 6% 4%,  rgba(120,96,58,.03), transparent 60%);
  background-attachment:fixed;
  min-height:100dvh;
}

.val,.num,td,.sm,.afoot,code,pre,.tick,.stat,input{font-variant-numeric:tabular-nums}

/* 🔴 HOVER IS AN UNDERLINE, NOT A COLOUR — see §1a. v3's `a:hover{color:
   var(--blue)}` was doing real work while blue was a hue; the moment brand
   collapsed to ink it became `ink → ink`, a hover state that changes nothing.
   It would have passed every contrast check in the audit, because both states
   are legible; what it loses is the AFFORDANCE, and no automated check we run
   looks for that. Underline is the device that survives a monochrome palette. */
a{color:var(--ink);text-decoration:none;transition:color var(--d-fast) var(--ease)}
a:hover{color:var(--blue-hi);text-decoration:underline;
        text-underline-offset:3px;text-decoration-thickness:1px}
/* 2px, not 1px. A 1px focus ring is crisp on black and thin on white. */
:focus-visible{outline:2px solid var(--blue);outline-offset:2px}

/* h1 is the one page element that speaks in the brand's own voice, so it gets
   the wordmark face. h2 deliberately does NOT — it stays tracked mono caps,
   which is the logo's "INCORPORATED" register. Two faces, two jobs; giving the
   didone to both would make every section header shout as loud as the title.
   font-weight is 400: GFS Didot ships one weight, and asking for 600 makes the
   browser synthesise a fake bold that thickens the hairlines the face is FOR. */
h1{font-size:var(--t-xl);line-height:var(--lh-tight);margin:0 0 5px;
   letter-spacing:var(--track);text-transform:uppercase;font-weight:400;
   font-family:var(--font-display)}
h2{font-size:var(--t-sm);margin:32px 0 12px;letter-spacing:var(--track-wide);
   text-transform:uppercase;color:var(--ink-2);font-weight:600;
   font-family:var(--font-mono)}
h3{font-size:var(--t-md);margin:0 0 6px}
.sub{color:var(--ink-3);font-size:var(--t-sm);margin-bottom:22px;
     font-family:var(--font-mono);letter-spacing:var(--track)}

.wrap{max-width:var(--maxw);margin:0 auto;padding:24px var(--gut) 80px}

.micro{font:var(--t-micro)/1 var(--font-mono);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--ink-3)}

/* ---- HEADER ----------------------------------------------------------------- */
header{
  display:flex;align-items:center;gap:18px;padding:10px var(--gut);
  position:sticky;top:0;z-index:20;
  /* Was rgba(255,255,255,.88) — pure white over cream reads as a grey-blue
     bar, because white is COOLER than the paper, not brighter than it. This is
     the paper tint at high alpha: the header lifts off the field without
     changing hue when you scroll content under it. */
  background:rgba(250,247,241,.88);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  border-bottom:var(--hair) solid var(--line);
}
header::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;
  background:linear-gradient(90deg,transparent,var(--blue-line) 20%,
             var(--blue) 50%,var(--blue-line) 80%,transparent);
  opacity:.9;
}
/* ---- THE LOCKUP -------------------------------------------------------------
   Oscar's wordmark is stacked: hairline rule / GVO in didone / hairline rule /
   INCORPORATED in wide-tracked caps. A header is 40px tall, so the stack is
   turned on its side: the rule that separated the two lines becomes the rule
   BETWEEN them. Same three elements, same order, same relationship — rotated.
   That is why there is no logo mark: the artwork has none. The old ::before
   ring is deleted rather than recoloured, because a mark Oscar didn't draw is
   an invention, and inventing next to a wordmark is how a brand drifts. */
.brand{
  display:flex;align-items:baseline;gap:10px;
  color:var(--ink);text-decoration:none;
  font:400 19px/1 var(--font-display);
  letter-spacing:.06em;
}
.brand span{
  font:var(--t-micro)/1 var(--font-mono);
  letter-spacing:.24em;text-transform:uppercase;
  color:var(--ink-3);align-self:center;
  padding-left:10px;border-left:var(--hair) solid var(--line-2);
}
/* The global a:hover underlines. Underlining a wordmark is a typo, not a
   hover state — the brand gets the ink shift only. */
.brand:hover{text-decoration:none;color:var(--blue-hi)}
.brand:hover span{color:var(--ink-2)}
header nav{display:flex;gap:2px;margin-left:auto;align-items:center}
header nav a{
  font:var(--t-xs)/1 var(--font-mono);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--ink-3);padding:8px 12px;
  border:var(--hair) solid transparent;
  transition:color var(--d-fast) var(--ease),border-color var(--d-fast) var(--ease),
             background var(--d-fast) var(--ease);
}
header nav a:hover{color:var(--ink);border-color:var(--line);background:var(--s2)}
header nav a.on{color:var(--blue);border-color:var(--blue-line);background:var(--blue-wash)}

/* ============================================================================
   PANEL — hairline frame + corner brackets, now with a real shadow.
   The brackets survive the palette change untouched: a stroke device is
   hue-agnostic, which is why they were the right call in v2.
   ========================================================================= */
.card,.pcard,.apanel,.tile,.note{
  position:relative;
  background:var(--s1);
  border:var(--hair) solid var(--line);
  border-radius:var(--r-md);
  box-shadow:var(--sh-2);
}
.card{padding:16px 17px}

.card::before,.pcard::before,.apanel::before,.tile::before,
.card::after, .pcard::after, .apanel::after, .tile::after{
  content:"";position:absolute;width:11px;height:11px;pointer-events:none;
  border-color:var(--blue);border-style:solid;border-width:0;
  transition:border-color var(--d) var(--ease);
}
.card::before,.pcard::before,.apanel::before,.tile::before{
  top:-1px;left:-1px;border-top-width:var(--hair);border-left-width:var(--hair);
}
.card::after,.pcard::after,.apanel::after,.tile::after{
  bottom:-1px;right:-1px;border-bottom-width:var(--hair);border-right-width:var(--hair);
}
.bracket-4 > .br-tr,.bracket-4 > .br-bl{
  position:absolute;width:11px;height:11px;pointer-events:none;
  border-color:var(--blue);border-style:solid;border-width:0;
}
.bracket-4 > .br-tr{top:-1px;right:-1px;border-top-width:var(--hair);border-right-width:var(--hair)}
.bracket-4 > .br-bl{bottom:-1px;left:-1px;border-bottom-width:var(--hair);border-left-width:var(--hair)}

/* ---- CONTROLS ---------------------------------------------------------------- */
input,textarea,select,button{font:inherit;color:var(--fg)}
input[type=text],input[type=password],input[type=email],textarea,select{
  width:100%;padding:11px 13px;background:var(--s3);
  border:var(--hair) solid var(--line-2);border-radius:var(--r-md);color:var(--ink);
  font-family:var(--font-mono);font-size:var(--t-md);
  box-shadow:var(--sh-in);
  transition:border-color var(--d) var(--ease),box-shadow var(--d) var(--ease);
}
input::placeholder,textarea::placeholder{color:var(--mute)}
input:focus,textarea:focus,select:focus{
  outline:none;border-color:var(--blue);box-shadow:var(--glow);
}
textarea{resize:vertical;min-height:76px}
label{display:block;font:var(--t-micro)/1 var(--font-mono);
  letter-spacing:var(--track-wide);text-transform:uppercase;
  color:var(--ink-3);margin:16px 0 7px}

/* Outline is still the default — density is the point of this board. A solid
   `.btn.primary` is still safe, but the v3 reason for that ("the hue split
   means a blue fill doesn't compete with the alarm") is GONE: v4 has no second
   hue. The reason now is the inverse, and it is stronger — crimson is the only
   chromatic thing left in the sheet, so a black fill cannot be mistaken for it
   at any size or distance. Same rule, opposite mechanism; if you migrate this
   sheet again, re-derive it, don't carry it. One `.primary` per screen. */
.btn{
  position:relative;cursor:pointer;
  font:600 var(--t-xs)/1 var(--font-mono);letter-spacing:var(--track-wide);
  text-transform:uppercase;
  padding:12px 20px;border-radius:var(--r-md);
  color:var(--blue);background:var(--s3);
  border:var(--hair) solid var(--blue-line);
  box-shadow:var(--sh-1);
  transition:color var(--d-fast) var(--ease),border-color var(--d-fast) var(--ease),
             background var(--d-fast) var(--ease),box-shadow var(--d-fast) var(--ease);
}
/* v2 hovered to #fff text + an inset crimson glow. Both are invisible or dirty
   on white, so hover moves the FIELD instead of the ink. */
.btn:hover{color:var(--blue-hi);border-color:var(--blue);background:var(--blue-wash);
  box-shadow:var(--sh-2)}
.btn:active{transform:translateY(1px);box-shadow:none}
.btn.primary{background:var(--blue);border-color:var(--blue);color:var(--bg)}
.btn.primary:hover{background:var(--blue-hi);border-color:var(--blue-hi);color:var(--bg)}
.btn.ghost{color:var(--ink-2);border-color:var(--line);background:transparent;box-shadow:none}
.btn.ghost:hover{color:var(--ink);border-color:var(--line-2);background:var(--s2);box-shadow:none}

.pill{
  display:inline-flex;align-items:center;gap:6px;
  background:var(--s3);border:var(--hair) solid var(--line);
  border-radius:var(--r-md);padding:5px 11px;
  font:var(--t-micro)/1 var(--font-mono);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--ink-3);
}

/* ---- TABLES ------------------------------------------------------------------ */
table{width:100%;border-collapse:collapse;font:var(--t-sm)/1.5 var(--font-mono)}
th,td{text-align:left;padding:9px 11px;border-bottom:var(--hair) solid var(--ink-4)}
th{
  color:var(--ink-3);font-weight:600;font-size:var(--t-micro);
  text-transform:uppercase;letter-spacing:var(--track-wide);
  border-bottom:var(--hair) solid var(--line);
}
tbody tr{transition:background var(--d-fast) var(--ease)}
tbody tr:hover{background:var(--s2)}     /* DOWNWARD — see §3 */
td{color:var(--ink-2)}
.ra{text-align:right}

code{background:var(--well);border:var(--hair) solid var(--ink-4);padding:2px 7px;
  border-radius:var(--r-sm);font:var(--t-sm)/1.4 var(--font-mono);color:var(--blue-hi)}

/* ============================================================================
   STATE — nominal is INK. Colour is an event.
   Live/idle now differ by FILLED vs HOLLOW, not by brightness. See §SIGNAL.
   ========================================================================= */
.dot{width:6px;height:6px;flex:0 0 auto;position:relative;
  background:transparent;border:1.5px solid var(--idle-mark);border-radius:50%}
.dot.working,.dot.live{background:var(--live-mark);border-color:var(--live-mark)}
.dot.idle{background:transparent;border-color:var(--idle-mark)}
.dot.degraded{background:var(--degrade);border-color:var(--degrade)}
.dot.down{background:var(--alarm-fill);border-color:var(--alarm-fill);
  box-shadow:var(--alarm-glow)}

/* Breathing ring on a pseudo element: transform+opacity, GPU-composited,
   never repaints the panel underneath. */
.dot.working::after,.dot.live::after{
  content:"";position:absolute;inset:-4px;border-radius:50%;
  border:1px solid rgba(10,22,40,.42);
  animation:ring 2.4s var(--ease-out) infinite;will-change:transform,opacity;
}
.dot.down::after{
  content:"";position:absolute;inset:-4px;border-radius:50%;
  border:1px solid var(--alarm-fill);
  animation:ring 1.1s var(--ease-out) infinite;will-change:transform,opacity;
}
@keyframes ring{
  0%{transform:scale(.5);opacity:.95} 70%{transform:scale(1.7);opacity:0}
  100%{transform:scale(1.7);opacity:0}
}

.chip,.astate{
  display:inline-flex;align-items:center;
  font:600 var(--t-micro)/1 var(--font-mono);letter-spacing:var(--track-wide);
  text-transform:uppercase;padding:5px 9px;border-radius:var(--r-sm);
  color:var(--ink-3);background:var(--s3);
  border:var(--hair) solid var(--line);
}
.chip.working,.chip.live,.astate.working,.astate.live{
  color:var(--ink);border-color:var(--ink-2)}
.chip.idle,.astate.idle{color:var(--ink-3);border-color:var(--line)}
.chip.degraded,.astate.degraded{color:var(--degrade);border-color:rgba(138,82,0,.45);
  background:var(--degrade-dim)}
/* ALARM — the only RED fill in the system. */
.chip.down,.astate.down{
  color:var(--alarm-ink);background:var(--alarm-fill);border-color:var(--alarm-fill);
  box-shadow:var(--alarm-glow);
  animation:alarm 1.9s steps(1,end) infinite;
}
@keyframes alarm{0%,72%{opacity:1} 76%,84%{opacity:.55} 88%,100%{opacity:1}}

.s-active,.s-online{color:var(--ink)}
.s-inactive,.s-failed,.s-stopped,.s-errored{
  color:var(--alarm-ink);background:var(--alarm-fill);padding:2px 7px;
  border-radius:var(--r-sm);font-weight:600}

/* ---- METER ------------------------------------------------------------------
   Works with the existing JS (`el.style.width = pct + '%'`). Deliberately NOT
   transitioned — transitioning width animates layout on every tick. */
.bar{
  height:3px;background:var(--ink-5);border-radius:0;overflow:hidden;margin-top:10px;
  border-bottom:var(--hair) solid transparent;
}
.bar i{display:block;height:100%;background:var(--ink-2)}
.bar.tx i{width:100%;transform-origin:left center;transform:scaleX(0);
  transition:transform var(--d-slow) var(--ease-out)}
.bar.warn i{background:var(--degrade)}
.bar.crit i{background:var(--alarm-fill)}
.bar.rail{background:var(--ink-5);opacity:.7}

/* ---- WELLS --------------------------------------------------------------------
   TWO materials, on purpose. `.well` is light and inline. `.term` is the dark
   device. Splitting them is what keeps the Fleet page from reading as a dark
   theme with white gutters — see the --sunk comment above. */
.well{
  background:var(--well);border:var(--hair) solid var(--ink-4);
  border-radius:var(--r-md);box-shadow:var(--sh-in);
  font:11px/1.45 var(--font-mono);color:var(--ink-2);
}
.term,pre.term{
  background:var(--sunk);border:var(--hair) solid rgba(10,22,40,.30);
  border-radius:var(--r-md);
  font:11px/1.45 var(--font-mono);color:var(--sunk-ink);
}

/* ---- DROP ZONE ---------------------------------------------------------------- */
.drop{
  border:var(--hair) dashed var(--line-2);border-radius:var(--r-md);padding:28px;
  text-align:center;color:var(--ink-3);cursor:pointer;background:var(--s1);
  font:var(--t-xs)/1.5 var(--font-mono);letter-spacing:var(--track);
  text-transform:uppercase;
  transition:transform var(--d) var(--ease),border-color var(--d) var(--ease),
             color var(--d) var(--ease),background var(--d) var(--ease);
}
.drop.hot{border-color:var(--blue);color:var(--blue-hi);background:var(--blue-wash);
  transform:scale(1.005)}

/* ---- MODAL ---------------------------------------------------------------------
   The scrim stays DARK. This is not a leak from v2: a scrim's job is to
   suppress the layer beneath it, and a pale veil over a pale page suppresses
   nothing. Every light UI that has a lightbox does this.
   The HUE moved though: v3's scrim was rgba(12,20,32,·), a cool navy-black that
   was invisible against v3's cool field and reads distinctly BLUE over cream.
   This is `--sunk`'s hue instead, so the scrim is the same dark the rest of the
   sheet already uses. Dark is the requirement; cool was an accident. */
.modal{
  position:fixed;inset:0;background:rgba(26,23,20,.66);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  display:none;align-items:center;justify-content:center;z-index:100;padding:26px;
}
.modal.on{display:flex;animation:fade var(--d) var(--ease)}
@keyframes fade{from{opacity:0}to{opacity:1}}
.modal video,.modal img{max-width:92vw;max-height:88vh;border-radius:var(--r-md);
  border:var(--hair) solid rgba(255,255,255,.22);box-shadow:var(--sh-3)}
/* On a dark scrim the close affordance is light — the one place in this sheet
   where reversed ink is correct, because it sits ON the scrim, not on the page. */
.modal .x{position:absolute;top:14px;right:22px;font-size:30px;
  color:rgba(255,255,255,.72);cursor:pointer;line-height:1;
  transition:color var(--d-fast) var(--ease)}
.modal .x:hover{color:var(--ink)}

/* ---- GRID ---------------------------------------------------------------------- */
.grid{display:grid;gap:14px}
.projects{grid-template-columns:repeat(auto-fill,minmax(280px,1fr))}
.media{grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}

/* A grid/flex item defaults to min-width:auto, so a track can never shrink
   below its content's min-content width. One <pre> of unwrapped terminal
   output then inflates the whole column past the viewport and `overflow:auto`
   on the pre does nothing, because the pre isn't what's too wide — the track
   is. This is why the agent page still measured 554px on a 390px phone after
   its media query fired. [nexus, round 3 — kept] */
.grid>*,.split>*{min-width:0}

/* ---- PAGE HEAD ----------------------------------------------------------------- */
.phead{display:flex;align-items:flex-end;gap:14px;flex-wrap:wrap;margin-bottom:18px;
  padding-bottom:14px;border-bottom:var(--hair) solid var(--line);position:relative}
.phead::after{content:"";position:absolute;left:0;bottom:-1px;width:84px;height:2px;
  background:var(--blue)}
.phead .grow{flex:1;min-width:240px}
.phead .sub{margin:0}

/* Live tick. Goes to ALARM when polling fails — a board showing stale numbers
   as if they were fresh is worse than one that is visibly down. */
.tick{
  display:inline-flex;align-items:center;gap:8px;
  font:var(--t-micro)/1 var(--font-mono);letter-spacing:var(--track-wide);
  text-transform:uppercase;color:var(--ink-3);
  background:var(--s3);border:var(--hair) solid var(--line);
  border-radius:var(--r-sm);padding:7px 12px;
}
.tick::before{content:"";width:5px;height:5px;border-radius:50%;
  background:var(--live-mark)}
.tick.stale{color:var(--alarm-ink);background:var(--alarm-fill);
  border-color:var(--alarm-fill);box-shadow:var(--alarm-glow)}
.tick.stale::before{background:#fff}

/* ---- TOUCH ---------------------------------------------------------------------
   Round 3, unchanged and re-verified. Measured at 390x844 in a Playwright
   touch context: `.btn` rendered 37px, login inputs 13.5px, `a.pill` 22px.
   Scoped to the phone breakpoint — a 44px control is correct under a thumb and
   wrong under a cursor. `a.pill`/`button.pill`, not `.pill`: most pills are
   status chips, not targets. */
@media (max-width:819px){
  .btn{min-height:44px;padding:13px 20px}
  /* 16px is not a taste choice, it is the iOS zoom threshold. */
  input[type=text],input[type=password],input[type=email],textarea,select{
    font-size:16px;padding:12px 13px}
  a.pill,button.pill{min-height:44px;padding:5px 13px}

  /* 🔴 `a.brand` measured 19px. It is the home link, so it is a real target, and it
     was reported by gojo on 2026-07-29 as the one touch defect left in base.html —
     then not fixed for 17 days while the three defects in the SAME letter were.
     Those three were his own sheet; this one was mine. The sweep landed exactly on
     the author's own list, which is the aperture failure, not the oversight.

     Hit area, NOT min-height. `.brand` is `align-items:baseline` with a `span` on a
     border-left; growing the box would push the wordmark to the top of a 44px line
     and drag that border with it. `::after{inset}` is this build's own idiom for
     this (see `.afoot a::after`) — it grows what a thumb can hit and moves nothing
     a reader can see. Verified 2026-08-15: 19x161 box, 45x177 target. (nexus) */
  a.brand{position:relative}
  a.brand::after{content:"";position:absolute;inset:-13px -8px}
}

/* ============================================================================
   §MIGRATION — what tokens.css CANNOT fix from here. 3 notes, ~10 edits.

   1. FIVE `--red` → `--alarm-fill` in templates/agents.html. These are the
      only places in the build where `--red` meant ALARM. Signature: `--red`
      in a rule whose selector contains `.down`. Until they land, a down agent
      renders BLUE:
          :156  #spokes line.down{stroke:var(--red)}
          :175  .rnode.down .rdot{background:var(--red)}
          :179  (the .down breathing ring)  border:1px solid var(--red)
          :219  .apanel.down{border-color:var(--red)}
          :237  .apanel.down .doing{border-left-color:var(--red)}
      Everything else in agents/agent/home/server/login/join/base is brand
      chrome and correctly becomes blue with no edit.

   2. THREE `filter:drop-shadow()` in templates/server.html + one `box-shadow`
      halo. Blur is the one v2 device with no light-theme equivalent; the
      replacement is WEIGHT.
          :132  .ring .arc.warm  — drop the filter, stroke-width 1 → 2
          :133  .ring .arc.hot   — drop the filter, stroke-width 1 → 2.5
          :153  #spark .sl.cpu   — drop the filter, stroke-width 1 → 1.75
          :148  .k.cpu::before   — drop `box-shadow:0 0 7px var(--accent)`
      Same for agents.html:172 (`box-shadow:0 0 9px rgba(240,160,42,.8)` on the
      degraded dot) — delete it; the dot is already filled ochre.

   3. globe.js FALLBACKS ARE NOW WRONG, in two different ways. Its defaults
      (lines 131-135) are v2-era, and they only fire when a var is missing —
      which is exactly the case while v3 is staged and a page is served from
      cache. Two of them matter:
        · `--globe-marker` defaults to `#e5222b`. That is the alarm colour
          spent on decoration, and it is the only place in the build that
          would put red on the screen for a non-broken reason.
        · the ocean/land/grid/rim defaults are low-alpha blues tuned for a
          black canvas; on white they render near-invisible, so a missing var
          fails to a blank circle rather than to something legible.
      Recommend the fallbacks be re-pointed at this file's values.
   ========================================================================= */

/* ============================================================================
   HUB DARK — 2026-09-18 (Gojo). The redesign Oscar approved ("I like it") and
   then asked for on EVERY page ("update everything without losing any
   information"). This block is appended LAST so it wins on order alone: every
   classic page keeps its markup and its rules and only the tokens change.
   Palette is static/hub.css's (Today / Approvals) so the two shells match.
   Contrast measured against --s1 #151a23: ink 15.6, ink-2 12.2, ink-3 8.9,
   mute/dim 5.5, live 8.7, degrade 9.6, down 6.0. Read tokens.css §above for
   what each token MEANS; nothing here changes a meaning, only a value.
   ========================================================================= */
:root{
  --bg:#0b0e13; --bg-2:#10141b; --s1:#151a23; --s2:#1b2130; --s3:#1b2130; --well:#10141b;
  --sunk:#07090d; --sunk-ink:rgba(255,255,255,.88); --sunk-dim:rgba(255,255,255,.62);
  --ink:rgba(238,241,246,.96); --ink-2:rgba(238,241,246,.82); --ink-3:rgba(238,241,246,.64);
  --ink-4:rgba(238,241,246,.22); --ink-5:rgba(238,241,246,.09);
  --line:rgba(238,241,246,.13); --line-2:rgba(238,241,246,.24); --edge:rgba(238,241,246,.08);
  --fg:#eef1f6; --dim:#929cab; --mute:#929cab;
  --blue:#eef1f6; --blue-hi:#ffffff; --blue-lo:#929cab;
  --blue-line:rgba(238,241,246,.30); --blue-wash:rgba(238,241,246,.07); --blue-dim:rgba(238,241,246,.32);
  --red-glow:rgba(238,241,246,.22);
  --live:#22d3a5; --live-mark:#22d3a5; --idle:var(--ink-3); --idle-mark:rgba(238,241,246,.32);
  --degrade:#f5b73c; --degrade-dim:rgba(245,183,60,.13);
  --down:#ff6b6b; --down-dim:rgba(255,107,107,.13);
  --alarm-fill:#ff6b6b; --alarm-ink:#0b0e13; --alarm-glow:0 0 0 3px rgba(255,107,107,.22);
  --font-display:Inter,var(--font);
  --r-xs:6px; --r-sm:8px; --r-md:12px; --r-lg:14px; --r-xl:16px;
  --sh-1:0 1px 2px rgba(0,0,0,.25); --sh-2:0 1px 3px rgba(0,0,0,.28),0 12px 28px -16px rgba(0,0,0,.5);
  --sh-3:0 22px 50px -18px rgba(0,0,0,.6); --sh-in:inset 0 1px 2px rgba(0,0,0,.35);
  --glow:0 0 0 3px rgba(238,241,246,.14);
  --globe-ocean:rgba(238,241,246,.05); --globe-land:rgba(238,241,246,.55); --globe-grid:rgba(238,241,246,.12);
  --globe-rim:rgba(238,241,246,.26); --globe-marker:#22d3a5;
}
/* Corner brackets were the cream sheet's frame device; the hub's cards are plain
   rounded panels. Hidden rather than deleted so the classic rules stay intact. */
.card::before,.pcard::before,.apanel::before,.tile::before,
.card::after,.pcard::after,.apanel::after,.tile::after{display:none}
.brand{letter-spacing:0;font-weight:700}
body{background-image:none}

/* ---- HUB DARK, part 2: the conversation register (2026-09-18, chat page) --------
   Oscar, 22:31Z, on the chat page: "lets improve this UI also". The approved mock
   (05-chat) is a messenger: normal-case buttons, a green Send, rounded composer,
   your own turns as a soft bubble, no mono-caps chrome around the words. All of it
   is register, none of it is structure, so it lives here as overrides. */
.btn{font:600 14px/1 var(--font);text-transform:none;letter-spacing:0;padding:9px 14px;border-radius:11px;
  color:var(--ink);background:var(--s2);border:var(--hair) solid var(--line-2);box-shadow:none}
.btn:hover{background:var(--s3);color:var(--ink);border-color:var(--line-2)}
.btn.primary{background:#22d3a5;border-color:transparent;color:#06251c}
.btn.primary:hover{background:#3fe0b6;border-color:transparent;color:#06251c}
.btn.ghost{background:transparent}
.card.compose{border-radius:18px;padding:12px 12px 10px;border-color:var(--line-2)}
.compose #msg{background:transparent;border:0;box-shadow:none;padding:8px 6px 2px;min-height:64px;font-size:15.5px;line-height:1.55;color:var(--ink)}
.compose #msg:focus{outline:none;box-shadow:none}
.compose .crow{margin-top:6px}
.compose .cnote,.compose .vnote{font:12.5px/1.4 var(--font);letter-spacing:0;text-transform:none;color:var(--mute)}
.compose .chint{color:var(--mute)}
.turn.me .bub{background:var(--s2);border:var(--hair) solid var(--line-2);border-radius:16px 16px 4px 16px;
  padding:12px 16px;display:inline-block;max-width:100%}
.turn.me .chname{color:var(--ink-3)}
.turn .chname{font:600 13px/1.3 var(--font);color:var(--mute)}
.chat pre{border-radius:10px}
