/* ===========================================================================
   Voidborn - operator panel. One stylesheet, no script, nothing fetched.

   Scale: 11 / 13 / 15 / 19 / 25 / 32 px.  Ground: five values.  Ink: three.
   Accent: one (cyan).  Meaning: three (good / waiting / critical) + money.

   This file is the whole of the panel's look. It is served from
   /assets/admin.css by web.go and it moves with the templates folder: the
   presentation layer is one directory and nothing outside it draws anything.

   AND IT IS THE WHOLE OF IT LITERALLY. Every response carries
   `style-src 'self'`, so a style attribute in a template is read by the browser
   and applied by nothing - not degraded, not warned about, ignored. No template
   here writes one, including for a length that comes from data: that is a class
   off the fixed set under "lengths, as classes" below, chosen in display.go.
   Three tests in style_test.go hold it, and the policy never learns
   'unsafe-inline'.

   WHAT THIS IS A SIBLING OF -------------------------------------------------
   The game has a written language (docs/design/ui-style-guide.md and
   client/Source/Voidborn/VoidbornStyle.h): a four-value ground ramp, four
   weights of ink, one rationed accent - cyan is the interface, amber is money,
   red is a refusal - figures in a monospaced face, near-square corners with a
   hairline edge, and an explicit ban on filled panels with bright borders, on
   gradients and rounded pills, and on more than a few type sizes per surface.

   This panel is that language's SIBLING, not its copy. The HUD is read at
   400 m/s over a starfield; this is read at a desk for six hours, and the two
   want different things from the same ideas.

   Followed: the hue family, contrast carrying hierarchy instead of chrome,
   near-square corners and hairline edges, uppercase labels over monospaced
   readings, the house motion beat of 0.14 s.

   Departed from, on purpose:

   * The values are re-derived, not transcribed. VoidbornStyle's colours are
     FLinearColor - linear-space numbers the renderer encodes on the way to the
     screen. Pasted into a browser as sRGB hex they come out pastel (the game's
     Bad becomes a salmon pink), so the ramp here is rebuilt at the same
     apparent values, in sRGB, with contrast measured rather than assumed.
   * Semantic colour is three states and money is not one of them. On the HUD
     amber means money because the credit figure has to be found in one glance.
     In a table of four hundred figures, amber on every credit is noise - so
     money is carried typographically here (mono, tabular, right-aligned) and
     only its direction is coloured. That frees amber to mean "waiting", which
     is the desk's most common state. One exception is kept deliberately: the
     purse figure on an account page is gold, because it is literally the number
     that pilot sees in gold in their own HUD.
   * A left rail instead of a row of tabs. Eleven surfaces in four groups is a
     rail; eleven pills wrapping onto two lines is what the first cut did.
   * Pills are gone. VoidbornStyle.h documents the incident that banned rounded
     pills from the game, and the first cut of this panel had
     border-radius:999px on every badge. Now 4 px on a badge and 8 px on a
     panel - proportionally the game's 2 px on a 30 px HUD card, the same intent
     at a different size.

   THE THREE REGISTERS -------------------------------------------------------
   A kick is reversible; a ban is an account's life. role.go separates them by
   capability and by role; display.go gives every capability a Severity - read,
   write, grave - so the register a surface is drawn in comes off the permission
   table and is held by TestAKickAndABanCannotBeDrawnAlike. Reading is quiet (no
   rail, no mark). Writing is marked (a rail, a stated badge, a button that
   stays inert until its reason is written: form:has([name=reason]):invalid,
   pure CSS and no script). Ending an account is unmistakable: its own deep
   ground, a red hairline, a hazard rail, and the only filled red button here.
   ========================================================================= */

:root{
  color-scheme:dark;
  accent-color:#7fe3ff;

  /* Ground - five steps, far enough apart that a container and its contents
     separate without either being read. The game's Void/Panel/Card/CardHot
     ramp, as it appears on a screen. */
  --void:#0b0f14;
  --panel:#131920;
  --panel-2:#171f28;
  --card:#1a222c;
  --card-hot:#222d39;
  --line:#2a3644;
  --line-soft:#1e2732;

  /* Ink - three weights. Measured contrast (WCAG 2.1 relative luminance, sRGB):
       ink   #e3eaf2 : 14.6:1 on panel, 13.2:1 on card
       dim   #a3b1c2 :  8.1:1 on panel,  7.4:1 on card
       faint #8798ae :  6.0:1 on panel,  5.5:1 on card, 4.8:1 on a hovered row
     The lowest value measured anywhere in this file is 4.75:1 and the body
     floor is 4.5. No string is drawn without a ground: every piece of text in
     the panel sits on one of the five values above. */
  --ink:#e3eaf2;
  --dim:#a3b1c2;
  --faint:#8798ae;

  /* Accent - one, and rationed: at most one primary action plus one
     "you are here" per screen. Links are written in ink and underlined, so a
     dense table does not turn cyan. */
  --accent:#7fe3ff;            /* 12.1:1 on panel */
  --accent-deep:#10384a;
  --accent-line:#1d5c78;

  /* Meaning - three states. Not a substitute for the accent, and the accent is
     not a substitute for these. */
  --good:#6fd98d;              /* 10.1:1 on panel */
  --warn:#f2b559;              /*  9.7:1 on panel */
  --bad:#ff7a6b;               /*  7.0:1 on panel */
  --good-line:#24532f; --warn-line:#5b4318; --bad-line:#5d2422;

  /* Money - the game's amber, and only on the purse figure. */
  --gold:#ffc978;

  /* The grave register. Its own ground, because it has its own weight. */
  --grave-ground:#1f1013;
  --grave-line:#5d2422;
  --grave-fill:#7a1f1f;
  --grave-fill-hot:#8f2626;
  --grave-ink:#ffe8e4;         /* 8.8:1 on the filled red */

  --r:8px; --r-sm:4px;
  --rail:218px; --top:52px;
  --beat:.14s;                 /* the house beat, VoidbornStyle::DurFast */
}

*{box-sizing:border-box}
html{scrollbar-color:var(--line) var(--void)}
body{margin:0;background:var(--void);color:var(--ink);
  font:15px/1.55 system-ui,"Segoe UI Variable Text","Segoe UI",-apple-system,
       "Helvetica Neue",Arial,sans-serif;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}
::selection{background:var(--accent-deep);color:var(--ink)}
a{color:inherit;text-decoration:none;border-bottom:1px solid var(--accent-line);
  transition:color var(--beat) ease-out,border-color var(--beat) ease-out}
a:hover{color:var(--accent);border-bottom-color:var(--accent)}
a.bare{border-bottom:0}
b,strong{font-weight:620;color:var(--ink)}
code,.mono{font-family:ui-monospace,"Cascadia Mono","Cascadia Code",Consolas,
  "SF Mono","Liberation Mono",monospace;font-size:13px;font-variant-numeric:tabular-nums}
svg.i{width:15px;height:15px;vertical-align:-3px;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;flex:none}

/* --- layout -------------------------------------------------------------- */
.shell{display:grid;grid-template-columns:var(--rail) minmax(0,1fr);min-height:100vh}
.rail{background:var(--panel);border-right:1px solid var(--line);
  position:sticky;top:0;height:100vh;overflow-y:auto;padding:0 0 24px}
.rail-brand{display:flex;align-items:center;gap:9px;height:var(--top);padding:0 16px;
  border-bottom:1px solid var(--line);font-size:13px;font-weight:640;
  letter-spacing:.16em;text-transform:uppercase;color:var(--ink)}
.rail-brand svg{color:var(--accent)}
/* The same lockup away from the rail - on the door and on the gate, where there
   is no rail for it to sit in and no edge for it to sit against. */
.rail-brand.off-rail{border:0;padding:0;height:auto}
.rail-group{padding:16px 10px 0}
.rail-group>.lbl{font-size:11px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--faint);padding:0 8px 6px}
.rail a{display:flex;align-items:center;gap:9px;padding:7px 8px 7px 9px;
  border:0;border-left:2px solid transparent;border-radius:0 var(--r-sm) var(--r-sm) 0;
  color:var(--dim);font-size:14px}
.rail a:hover{background:var(--card);color:var(--ink)}
.rail a.on{background:var(--card-hot);color:var(--ink);border-left-color:var(--accent)}
.rail a.on svg{color:var(--accent)}
.top{position:sticky;top:0;z-index:9;display:flex;align-items:center;gap:16px;
  height:var(--top);padding:0 20px;background:var(--panel);
  border-bottom:1px solid var(--line)}
.crumb{flex:1;font-size:12px;color:var(--faint);letter-spacing:.1em;text-transform:uppercase}
.who{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--dim)}
.who b{color:var(--ink)}
.main{padding:20px 24px 40px;max-width:1560px}
/* A surface that is read rather than scanned: the public door is prose and two
   forms, and prose set 1560 px wide is not read, it is surveyed. */
.main.narrow{max-width:1060px;margin-left:auto;margin-right:auto}
h1{font-size:25px;font-weight:620;letter-spacing:-.01em;margin:4px 0 6px}
h2{font-size:13px;font-weight:620;margin:28px 0 10px;color:var(--faint);
  text-transform:uppercase;letter-spacing:.1em}
h2:first-child{margin-top:0}
.lede{color:var(--dim);margin:0 0 18px;max-width:82ch;font-size:14px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.grid-main{display:grid;grid-template-columns:minmax(0,2fr) minmax(300px,1fr);gap:16px}
.split{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
/* A row of fields sits on its baseline, not on its centre: a labelled input and
   a bare button are different heights and only their bottom edges agree. */
.split.bottom{align-items:flex-end}
.spread{display:flex;gap:12px;align-items:baseline;justify-content:space-between}
/* The vertical rhythm inside a form body. A hidden input is not a row and must
   not open a gap above the first thing anybody can see. */
.stack>*+*{margin-top:10px}
.stack>input[type=hidden]+*{margin-top:0}
/* One step of separation, where a block starts a new thought. */
.gap{margin-top:16px}
@media(max-width:1180px){.grid2,.grid-main{grid-template-columns:1fr}}
@media(max-width:860px){
  .shell{grid-template-columns:1fr}
  .rail{position:static;height:auto;border-right:0;border-bottom:1px solid var(--line)}
  .rail-group{display:flex;flex-wrap:wrap;gap:4px;padding:8px}
  .rail-group>.lbl{width:100%;padding:4px 6px 2px}
  .rail a{border-left:0;border-radius:var(--r-sm)}
  .rail a.on{box-shadow:inset 0 -2px 0 var(--accent)}
  thead th{position:static}
}

/* --- the summary strip: what comes before the detail ----------------------
   A card carries its own tone: something waiting puts an amber edge on it, a
   promise already broken puts a red one. Which card is calling you is legible
   before any figure is read. */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(184px,1fr));
  gap:12px;margin:0 0 18px}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  padding:12px 14px 13px;position:relative;overflow:clip}
.stat:before{content:"";position:absolute;inset:0 auto 0 0;width:2px;background:transparent}
.stat.t-good:before{background:var(--good)}
.stat.t-warn:before{background:var(--warn)}
.stat.t-bad:before{background:var(--bad)}
.stat.t-live:before{background:var(--accent)}
.stat .k{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.stat .v{font-size:32px;line-height:1.1;font-weight:620;margin:6px 0 2px;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.stat .v.sm{font-size:19px;font-weight:600;letter-spacing:0}
.stat .s{font-size:13px;color:var(--dim)}
.stat.t-warn .v{color:var(--warn)} .stat.t-bad .v{color:var(--bad)}
/* A reading too long to sit in a column of its own - a hash, a path. */
.stat.span2{grid-column:span 2}
.stat .hash{font-size:12px;word-break:break-all;margin-top:8px;color:var(--dim)}

/* --- panel --------------------------------------------------------------- */
.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
  overflow:clip;margin:0 0 16px}
.panel>.hd{display:flex;align-items:center;gap:10px;padding:9px 14px;
  border-bottom:1px solid var(--line);font-size:11.5px;color:var(--dim);
  text-transform:uppercase;letter-spacing:.1em;background:var(--panel-2)}
.panel>.hd .grow{flex:1}
.panel>.bd{padding:14px}
/* A panel body opens and closes on its own padding: the first paragraph does not
   push the header away and the last does not pad the bottom edge twice. */
.panel>.bd>p:first-child{margin-top:0}
.panel>.bd>p:last-child{margin-bottom:0}
/* Two forms in one panel are one body split, not two bodies stacked. */
.panel>.bd+.bd{padding-top:0}
.panel>.bd.tight{padding:10px 14px}
.panel>.ft{padding:10px 14px;border-top:1px solid var(--line-soft);font-size:13px}

/* --- the three registers: read / write / grave --------------------------- */
.reg-write{border-left:3px solid var(--line)}
.reg-write>.hd{background:#1a2028;color:var(--ink)}
.reg-grave{border-color:var(--grave-line);background:var(--grave-ground);
  position:relative}
.reg-grave:before{content:"";position:absolute;left:0;top:0;bottom:0;width:5px;z-index:1;
  background:repeating-linear-gradient(135deg,var(--grave-fill) 0 5px,transparent 5px 10px)}
.reg-grave>.hd{background:#2a1316;color:var(--grave-ink);
  border-bottom-color:var(--grave-line);padding-left:18px}
.reg-grave>.bd{padding-left:18px}
.reg-grave .hint,.reg-grave p{color:#f0c9c4}

/* --- badges: a status read as a shape, not only as a word ---------------- */
.b{display:inline-flex;align-items:center;gap:5px;padding:2px 7px;border-radius:var(--r-sm);
  font-size:11px;line-height:1.5;letter-spacing:.06em;text-transform:uppercase;
  font-weight:600;white-space:nowrap;border:1px solid var(--line);
  color:var(--dim);background:var(--card)}
.b:before{content:"";width:5px;height:5px;border-radius:1px;background:currentColor;
  opacity:.9;flex:none}
.b.plain:before{display:none}
.b-live{color:var(--accent);border-color:var(--accent-line);background:var(--accent-deep)}
.b-ok,.b-good{color:var(--good);border-color:var(--good-line);background:#13251a}
.b-warn{color:var(--warn);border-color:var(--warn-line);background:#241d10}
.b-bad{color:var(--bad);border-color:var(--bad-line);background:#261416}
.b-mute{color:var(--faint)}
.b-read{color:var(--dim)}
.b-write{color:var(--warn);border-color:var(--warn-line);background:#241d10}
.b-grave{color:var(--grave-ink);border-color:#a13030;background:var(--grave-fill)}
.b+.b{margin-left:4px}

/* --- tables: the body of the tool ---------------------------------------- */
table{width:100%;border-collapse:collapse;font-size:13.5px}
thead th{position:sticky;top:var(--top);z-index:2;background:var(--panel-2);
  text-align:left;font-weight:600;color:var(--faint);font-size:11px;
  text-transform:uppercase;letter-spacing:.08em;padding:8px 12px;
  border-bottom:1px solid var(--line)}
td{padding:7px 12px;border-bottom:1px solid var(--line-soft);vertical-align:middle}
tbody tr:last-child td{border-bottom:none}
tbody tr:hover{background:var(--card)}
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums}
td.when{color:var(--faint);white-space:nowrap}
td.tight,th.tight{white-space:nowrap}
td.wide{min-width:140px}
th.wide{min-width:140px}
/* A column that carries a drawing rather than a figure needs room to draw in. */
td.wider,th.wider{min-width:260px}
/* The badge column in the legend: as wide as the widest badge, and no wider. */
td.sev{width:110px}
.up{color:var(--good)} .down{color:var(--bad)} .flat{color:var(--faint)}
.cr{font-family:ui-monospace,"Cascadia Mono",Consolas,monospace;
  font-variant-numeric:tabular-nums}
.cr.gold{color:var(--gold)}
.dim{color:var(--dim)} .faint{color:var(--faint)}
.empty{padding:28px 22px;color:var(--faint);text-align:center;font-size:13.5px}

/* --- measures: as much shape as figure ----------------------------------- */
.meter{display:block;height:4px;border-radius:2px;background:var(--card-hot);
  overflow:clip;min-width:52px;margin-top:4px}
.meter>i{display:block;height:100%;background:var(--dim)}
.meter.m-fresh>i{background:var(--good)}
.meter.m-late>i{background:var(--warn)}
.meter.m-overdue>i{background:var(--bad)}
.meter.m-answered>i{background:var(--faint)}
.meter.m-accent>i{background:var(--accent)}
.wait{display:block;font-variant-numeric:tabular-nums}
.wait.t-late{color:var(--warn)} .wait.t-overdue{color:var(--bad)}
.wait.t-answered{color:var(--faint)}

/* A signed flow: length is magnitude, side is direction. One bar cannot say
   that -400 and +400 are opposite readings. */
.flow{display:flex;align-items:center;width:100%;min-width:120px}
.flow>.l{flex:1;display:flex;justify-content:flex-end}
.flow>.r{flex:1}
.flow i{display:block;height:6px;border-radius:2px}
.flow .neg{background:var(--bad)}
.flow .pos{background:var(--good)}
.flow .axis{width:1px;height:12px;background:var(--line);flex:none}

/* The cadence scale: a measurement is not a figure, it is a place on a scale
   with the human band drawn on it. */
.scale{display:block;position:relative;height:26px;margin:10px 0 4px;
  background:var(--card);border-radius:var(--r-sm);overflow:clip}
.scale .band{position:absolute;top:0;bottom:0;background:var(--accent-deep);
  border-left:1px solid var(--accent-line);border-right:1px solid var(--accent-line)}
.scale .band span{position:absolute;top:6px;left:7px;font-size:10.5px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
.scale .pin{position:absolute;top:0;bottom:0;width:2px;background:var(--ink)}
.scale.t-flat .pin,.scale.t-below .pin{background:var(--bad)}
.scale.t-human .pin{background:var(--good)}
.scale.t-unmeasured .pin{background:var(--faint)}
.scale-ends{display:flex;justify-content:space-between;font-size:11px;color:var(--faint);
  font-variant-numeric:tabular-nums}
/* The same scale inside a table row: shorter, and it carries no label. */
.scale.thin{height:10px;margin:4px 0 0}

/* The shape of a pile: three lengths, side by side, never added up. */
.pile{display:grid;grid-template-columns:auto minmax(80px,1fr) auto;
  gap:5px 10px;align-items:center;font-size:12px}
.pile .lbl{color:var(--faint);text-transform:uppercase;letter-spacing:.07em;
  font-size:10.5px;white-space:nowrap}
.pile .track{height:6px;border-radius:2px;background:var(--card-hot);overflow:clip}
.pile .track>i{display:block;height:100%;background:var(--dim)}
.pile .fig{font-variant-numeric:tabular-nums;color:var(--dim);white-space:nowrap}
/* Only the burst is coloured, and it is coloured "attention" rather than "critical":
   the tightest ten minutes is the length that decides whether the operator reads the
   reporters next, and it is the one the eye should land on. The other two are lengths,
   not alarms - and the accent stays where it belongs, on what is interactive. */
.pile .burst>i{background:var(--warn)}
.pile .span>i{background:var(--faint)}
.pile.small{grid-template-columns:auto minmax(60px,1fr) auto;gap:3px 8px}
.shape{font-size:11px;letter-spacing:.06em;text-transform:uppercase}
.shape-tight{color:var(--warn)} .shape-mixed{color:var(--dim)}
.shape-wide{color:var(--dim)} .shape-few{color:var(--faint)}
/* The word under a pile is the pile's own caption, not the next block. */
.pile+.shape{display:block;margin-top:8px}

/* --- lengths, as classes -------------------------------------------------
   THE RULE THIS SECTION EXISTS FOR: no template in this panel writes a style
   attribute, and a data-driven length is therefore not written as one either.

   Every response carries
       Content-Security-Policy: default-src 'none'; style-src 'self'; form-action 'self'
   and under `style-src 'self'` a browser parses `<i style="width:42%">`, keeps
   the attribute in the DOM, and applies none of it - element.style.width reads
   back empty. Nothing is logged anywhere a template author would look. The
   markup is right, this file is right, the server is right, and the screen is
   wrong; on 2026-09-12 that meant every bar on the panel drew at zero and the
   login card drew 1560 px wide. The policy is correct and stays: this tool
   reads a hundred thousand people's support history and 'unsafe-inline' is the
   wrong price for a bar chart.

   So a length is a class, chosen from this fixed set, and display.go picks the
   name (BarClass / AtClass). One rule per whole percent, 0..100:

     .w-N  a bar N percent long        .x-N  a mark N percent along a scale

   ONE PERCENT, NOT A COARSER STEP. Every value that reaches these classes is
   already a whole percent - Share, WaitMark, CVMark and spanScale all round
   before the template sees them - so this set is lossless and changes nothing
   about how the panel draws. A two-percent set would have quantised twice, and
   in the worst place: CVMark puts the human band's lower edge at 25% and a mark
   judged "below the band" can sit at 24%, which on a two-percent set snaps both
   to 24 and draws the pin inside the band it is below. Four kilobytes, served
   once and cached, against a chart that contradicts the figure beside it.

   TestEveryLengthClassIsInTheStylesheet asserts that every name those two
   functions can return is present below. A missing class is a length of zero. */

.w-0{width:0}.w-1{width:1%}.w-2{width:2%}.w-3{width:3%}.w-4{width:4%}.w-5{width:5%}.w-6{width:6%}.w-7{width:7%}.w-8{width:8%}.w-9{width:9%}
.w-10{width:10%}.w-11{width:11%}.w-12{width:12%}.w-13{width:13%}.w-14{width:14%}.w-15{width:15%}.w-16{width:16%}.w-17{width:17%}.w-18{width:18%}.w-19{width:19%}
.w-20{width:20%}.w-21{width:21%}.w-22{width:22%}.w-23{width:23%}.w-24{width:24%}.w-25{width:25%}.w-26{width:26%}.w-27{width:27%}.w-28{width:28%}.w-29{width:29%}
.w-30{width:30%}.w-31{width:31%}.w-32{width:32%}.w-33{width:33%}.w-34{width:34%}.w-35{width:35%}.w-36{width:36%}.w-37{width:37%}.w-38{width:38%}.w-39{width:39%}
.w-40{width:40%}.w-41{width:41%}.w-42{width:42%}.w-43{width:43%}.w-44{width:44%}.w-45{width:45%}.w-46{width:46%}.w-47{width:47%}.w-48{width:48%}.w-49{width:49%}
.w-50{width:50%}.w-51{width:51%}.w-52{width:52%}.w-53{width:53%}.w-54{width:54%}.w-55{width:55%}.w-56{width:56%}.w-57{width:57%}.w-58{width:58%}.w-59{width:59%}
.w-60{width:60%}.w-61{width:61%}.w-62{width:62%}.w-63{width:63%}.w-64{width:64%}.w-65{width:65%}.w-66{width:66%}.w-67{width:67%}.w-68{width:68%}.w-69{width:69%}
.w-70{width:70%}.w-71{width:71%}.w-72{width:72%}.w-73{width:73%}.w-74{width:74%}.w-75{width:75%}.w-76{width:76%}.w-77{width:77%}.w-78{width:78%}.w-79{width:79%}
.w-80{width:80%}.w-81{width:81%}.w-82{width:82%}.w-83{width:83%}.w-84{width:84%}.w-85{width:85%}.w-86{width:86%}.w-87{width:87%}.w-88{width:88%}.w-89{width:89%}
.w-90{width:90%}.w-91{width:91%}.w-92{width:92%}.w-93{width:93%}.w-94{width:94%}.w-95{width:95%}.w-96{width:96%}.w-97{width:97%}.w-98{width:98%}.w-99{width:99%}
.w-100{width:100%}

.x-0{left:0%}.x-1{left:1%}.x-2{left:2%}.x-3{left:3%}.x-4{left:4%}.x-5{left:5%}.x-6{left:6%}.x-7{left:7%}.x-8{left:8%}.x-9{left:9%}
.x-10{left:10%}.x-11{left:11%}.x-12{left:12%}.x-13{left:13%}.x-14{left:14%}.x-15{left:15%}.x-16{left:16%}.x-17{left:17%}.x-18{left:18%}.x-19{left:19%}
.x-20{left:20%}.x-21{left:21%}.x-22{left:22%}.x-23{left:23%}.x-24{left:24%}.x-25{left:25%}.x-26{left:26%}.x-27{left:27%}.x-28{left:28%}.x-29{left:29%}
.x-30{left:30%}.x-31{left:31%}.x-32{left:32%}.x-33{left:33%}.x-34{left:34%}.x-35{left:35%}.x-36{left:36%}.x-37{left:37%}.x-38{left:38%}.x-39{left:39%}
.x-40{left:40%}.x-41{left:41%}.x-42{left:42%}.x-43{left:43%}.x-44{left:44%}.x-45{left:45%}.x-46{left:46%}.x-47{left:47%}.x-48{left:48%}.x-49{left:49%}
.x-50{left:50%}.x-51{left:51%}.x-52{left:52%}.x-53{left:53%}.x-54{left:54%}.x-55{left:55%}.x-56{left:56%}.x-57{left:57%}.x-58{left:58%}.x-59{left:59%}
.x-60{left:60%}.x-61{left:61%}.x-62{left:62%}.x-63{left:63%}.x-64{left:64%}.x-65{left:65%}.x-66{left:66%}.x-67{left:67%}.x-68{left:68%}.x-69{left:69%}
.x-70{left:70%}.x-71{left:71%}.x-72{left:72%}.x-73{left:73%}.x-74{left:74%}.x-75{left:75%}.x-76{left:76%}.x-77{left:77%}.x-78{left:78%}.x-79{left:79%}
.x-80{left:80%}.x-81{left:81%}.x-82{left:82%}.x-83{left:83%}.x-84{left:84%}.x-85{left:85%}.x-86{left:86%}.x-87{left:87%}.x-88{left:88%}.x-89{left:89%}
.x-90{left:90%}.x-91{left:91%}.x-92{left:92%}.x-93{left:93%}.x-94{left:94%}.x-95{left:95%}.x-96{left:96%}.x-97{left:97%}.x-98{left:98%}.x-99{left:99%}
.x-100{left:100%}

/* --- forms --------------------------------------------------------------- */
.form{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;margin:0 0 16px;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r);padding:12px 14px}
.field{display:flex;flex-direction:column;min-width:0}
.field.grow{flex:1;min-width:190px}
label{display:block;font-size:11px;color:var(--faint);text-transform:uppercase;
  letter-spacing:.08em;margin-bottom:5px}
label.inline{display:inline-flex;align-items:center;gap:7px;text-transform:none;
  letter-spacing:0;font-size:13.5px;color:var(--dim);margin:0}
input,select,textarea{background:var(--void);border:1px solid var(--line);color:var(--ink);
  border-radius:var(--r-sm);padding:7px 10px;font:inherit;font-size:14px;min-width:0;
  transition:border-color var(--beat) ease-out}
input::placeholder,textarea::placeholder{color:var(--faint);opacity:.75}
input:hover,select:hover,textarea:hover{border-color:var(--accent-line)}
input[type=checkbox]{width:15px;height:15px;min-width:auto;padding:0}
textarea{width:100%;min-height:104px;resize:vertical;line-height:1.55}
.w100{width:100%}
button{border-radius:var(--r-sm);padding:7px 14px;font:inherit;font-size:14px;font-weight:560;
  border:1px solid var(--line);background:var(--card-hot);color:var(--ink);cursor:pointer;
  transition:background var(--beat) ease-out,border-color var(--beat) ease-out}
button:hover{background:#2b3846;border-color:var(--accent-line)}
button.primary{background:var(--accent-deep);border-color:var(--accent-line);color:#9fe9ff}
button.primary:hover{background:#164e66}
button.wide{width:100%}
/* The grave button: the only filled red in the panel, and square-cornered -
   its shape differs as well as its colour. */
button.grave{background:var(--grave-fill);border-color:#a13030;color:var(--grave-ink);
  border-radius:2px;padding:9px 18px;letter-spacing:.04em;font-weight:620}
button.grave:hover{background:var(--grave-fill-hot);border-color:#c24040}
/* A button whose reason has not been written is not armed yet. No script:
   :invalid on the form, which every browser has had for a decade.

   SCOPED TO FORMS THAT DEMAND A REASON, and that scope is the correction of a
   real fault. The rule used to read `form:invalid button.primary`, which is
   every form on the panel with a required field in it - so the login gate's
   "Gir" and the public door's "Gönder" were served at 45% opacity and a third
   of their saturation before anybody had typed a character. The designer read
   them as pale grey dead buttons, and they were right to: a disabled-looking
   primary action is a page that says it is broken.

   Arming means something narrower than "this form is incomplete". It means
   "this act is not accountable yet": a note, a verdict, a kick, a ban - the
   writes whose reason is mandatory. A form is asked whether it carries one
   rather than being told, so the register follows the markup instead of a list
   somebody has to remember to extend. :has() is baseline in every browser this
   panel runs in; where it is not, the button is merely armed early and the
   server refuses it, which is where the rule is actually enforced. */
form:has([name=reason]):invalid button.primary,
form:has([name=reason]):invalid button.grave,
form:has([name=reason]):invalid button[type=submit]{opacity:.45;cursor:not-allowed;
  filter:saturate(.35)}
form:has([name=reason]):invalid .armed{display:none}
.hint{font-size:12.5px;color:var(--faint);margin:9px 0 0;max-width:72ch}
.hint.armed{margin-left:10px}

/* --- the gate: the one screen with nothing else on it ---------------------
   Sign-in is four lines of markup and it used to be laid out by a style
   attribute the CSP threw away, which left it 1560 px wide and pinned to the
   top of the viewport with a void beneath it. It is a card, it is centred in
   both directions, and it is the first thing anybody sees of this tool. */
.main.gate{max-width:420px;margin:0 auto;padding:32px 20px 40px;
  min-height:100vh;display:flex;flex-direction:column;justify-content:center}
.gate>.rail-brand{margin:0 0 18px}
.gate .lede{margin-bottom:20px}
.gate .panel{margin:0}
.gate form.bd{padding:16px}
.gate form.bd>*+*{margin-top:14px}
/* The action gets more air than the gap between two fields: it ends the form,
   it does not continue it. */
.gate form.bd>button{margin-top:20px}
@media(max-height:620px){.main.gate{min-height:0;padding-top:6vh}}
/* A form a stranger fills in is not the dense form an operator lives in: the
   label stands off its box instead of resting on it, and the box is a target. */
.gate label,.main.narrow label{margin-bottom:8px}
.gate input,.main.narrow input,.main.narrow select{padding:9px 12px}

/* --- notes, messages, ribbons -------------------------------------------- */
.note{display:flex;gap:10px;background:var(--card);border:1px solid var(--line);
  border-left:3px solid var(--dim);border-radius:var(--r-sm);padding:11px 14px;
  color:var(--dim);font-size:13.5px;margin:0 0 16px}
.note svg{margin-top:2px}
.note.live{border-left-color:var(--accent)}
.note.warn{border-left-color:var(--warn)}
.note.bad{border-left-color:var(--bad);background:#201415}
.note.good{border-left-color:var(--good)}
.msg{border:1px solid var(--line);border-radius:var(--r-sm);padding:11px 13px;
  margin:0 0 10px;background:var(--card)}
.msg.internal{background:#221d12;border-color:var(--warn-line)}
.msg.internal .m{color:var(--warn)}
.msg .m{font-size:11.5px;color:var(--faint);margin-bottom:6px;
  display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.msg .t{white-space:pre-wrap;color:var(--ink)}

/* Reads are recorded too, and that is not hidden. A calm strip, not an alarm:
   an operator who knows the look is on the record behaves like one, and a panel
   that records the look and says nothing is keeping a secret about the person
   it is keeping the record about. */
.watch{display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  background:var(--card);border:1px solid var(--line-soft);border-radius:var(--r-sm);
  padding:7px 12px;margin:0 0 16px;font-size:12.5px;color:var(--faint)}
.watch svg{color:var(--dim)}
.watch .seq,.watch .why{color:var(--dim)}
.watch .nowhy{color:var(--warn)}

footer{color:var(--faint);font-size:12px;padding:26px 24px;max-width:84ch}

/* --- what moves, and what does not --------------------------------------- */
@media(prefers-reduced-motion:reduce){*{transition:none!important}}
@media print{
  :root{--void:#fff;--panel:#fff;--panel-2:#fff;--card:#fff;--card-hot:#eee;
        --ink:#000;--dim:#333;--faint:#555;--line:#bbb;--line-soft:#ddd}
  .rail,.top,.form,form,footer{display:none}
  .shell{display:block} .main{padding:0;max-width:none} .panel{break-inside:avoid}
  body{background:#fff;color:#000} a{border-bottom:0}
}

/* --- second factor (VB-625): the enrolment gate is wider for the QR code ---- */
.main.gate.wide{max-width:560px}
.gate .panel+.panel{margin-top:14px}
.enrol>*+*{margin-top:10px}
.enrol p{margin:0}
.qr-box{display:flex;justify-content:center;padding:6px 0}
.qr-box svg.qr{border-radius:6px;max-width:100%;height:auto}
.enrol .key{font-size:17px;letter-spacing:.06em;word-break:break-all}
.enrol .uri{font-size:11.5px;word-break:break-all;color:var(--dim)}
.codes{columns:2;column-gap:28px;margin:6px 0 0;padding-left:22px;font-size:15px}
.codes li{padding:3px 0}
.gate-out{margin-top:14px;text-align:center}
