/* ═══ app-admin.css — THE ADMIN SUITE'S SHELL ═════════════════════════════════════════════
 *
 * docs/SPEC_admin_suite.md (the STRUCTURE) + docs/design/DESIGN.md (the PAINT).
 * Owner, 2026-09-04: *"make each tab its own sub page and clean up the design … make sure to
 * add the admin's red lightning bolt and color theme."* Owner, 2026-09-05, after rejecting the
 * peer nav: *"look at how they turn boxes and shapes into visually appealing designs. very
 * boxy, yet still beautiful … they figure out how to turn data into visuals."*
 *
 * SCOPE: the SHELL and nothing else — the strip, the rail, the phone's PAGES sheet, and the
 * overview's panels, tiles and GRAPHS. Every panel's own contents keep the styles they already
 * had (app.css, app-moderation.css, app-viewer.css); this sheet must never reach inside one.
 * That boundary is why it is a new part rather than more lines in app-moderation.css: the suite
 * is a layout, the panels are content, and the last time those two shared a file the layout won
 * arguments it should have lost.
 *
 * ⚖ THE GRAMMAR IS FOUR PICTURES, NOT FOUR ADJECTIVES (docs/design/references/). Titled hairline
 * boxes with CORNER BRACKETS; a tab label sitting ON the top edge; ONE accent plus semantic
 * green/amber/red; ornament with intent (brackets, a hazard stripe, a ghost numeral); and every
 * datum drawn as a bar, a gauge, a meter or a swatch rather than written as a sentence. What
 * none of the references do — and what this sheet must never produce — is a grid of same-weight
 * uppercase words, a box holding a paragraph, or two cells in one row with different anatomy.
 *
 * ⚖ THE RED IS A MARK, NOT A MOOD (spec §2, narrowed by the LOOK's colour law). `--bad`
 * (#e0555f) is the club rooms' own site-admin colour and AT REST it appears on exactly two
 * things: the admin strip's top line + bolt + wordmark, and a DEAD/NEVER pipeline (with its
 * hazard stripe). A failed audit act and the focus ring are the two states that also earn it.
 * ⛔ NEVER A RED BADGE FOR AN ORDINARY COUNT — counts are --accent lozenges. Red spent on a
 * queue with three things in it is red that means nothing when it lands on an illegal report.
 *
 * ⚖ TOKENS ONLY, BECAUSE THERE ARE SEVEN SKINS. The tab label is `--accent` ground with
 * `--void` ink: those two are at opposite ends of every skin's ramp (amber on near-black in the
 * five dark ones, dark red / dark navy on cream in the two light ones), which is what makes one
 * rule legible in all seven. ⚠ `--bg` — which the LOOK doc names for that ink — HAS NEVER BEEN
 * DEFINED anywhere in this client (the same phantom `--gold` was until 2026-08); `var(--bg)`
 * would resolve to nothing and the label would inherit its ink from the panel. `--void` is the
 * token that actually holds the value the doc describes.
 *
 * ⚖ THE ONE BREAKPOINT IS BURGER_BP's min-width complement, 1000px (app.css's ladder;
 * mobilebreakpoints.test.js). ≥1000: a grouped left rail. <1000: the SAME grouped list, run
 * vertically inside the archive's bottom drawer, opened by the strip's ☰ — on EVERY suite page,
 * the dashboard included (spec §6). No third layout — the suite is a rail and a column.
 *
 * ⛔ NO HORIZONTAL SCROLLER IN THIS SUITE BELOW 1000px (owner on his phone, 2026-09-04:
 * *"this scrolling bar at the top is a really bad call for mobile; not all of the elements are
 * lined up"*). The chip row this sheet shipped with lived one hour. A sideways row of twenty
 * destinations hides most of itself BY CONSTRUCTION — the pre-scroll only chose which ones —
 * and it put a second scroll axis on a screen whose whole content is a list. `overflow-x`
 * appears nowhere in this file, and the phone's job here is: everything aligns to ONE left
 * edge, and nothing moves sideways.
 */

/* ---- THE STRIP -------------------------------------------------------------------------
   Under the masthead on every suite page, and THE ONLY PLACE THE SUITE PAINTS ITS OWN RED AT
   REST. A hairline top edge in --bad, the bolt and the wordmark; everything else in the strip
   is ordinary chrome. */
/* ⚖ THE PHONE'S STRIP IS ONE COLUMN, AND EVERY ROW STARTS AT THE SAME LEFT EDGE (spec §5).
   The shipped version was `flex-wrap`, which is a machine for misalignment: the row broke
   wherever it happened to run out of width, and what fell through was the COUNT — "1 queue"
   landed on its own line under the 18px bolt, indented by nothing, which is the dangling
   element the owner photographed. A GRID states the intent instead of discovering it:
     row 1   bolt · ADMIN   role chip            count        ☰
     row 2   the page title
     row 3   the sub
     row 4   ← overview  (full width, panel pages only)
   Columns 1, 2 and 4 are `auto` and column 3 takes the slack, so the count is pushed hard
   against the burger at the right edge and `.as-said` / `.as-acts` span all four — every one
   of them begins at the strip's padding edge, which is the bolt's left edge too.
   ⚑ THE BURGER IS ROW ONE ON EVERY SUITE PAGE (spec §6). The overview used to carry a PAGES
   section instead; the owner asked for the dashboard clean, so this is now the ONLY index
   below 1000px and it must never be conditional on which page you are on.
   ⚑ SQUARE CORNERS, like every box on this screen — the references have no radius anywhere,
   and one rounded rectangle among a dozen bracketed ones reads as the odd one out. */
.as-strip{display:grid;grid-template-columns:auto auto minmax(0,1fr) auto;
  grid-template-areas:"mark role count burger" "said said said said" "acts acts acts acts";
  align-items:center;gap:8px 10px;
  padding:10px 12px;margin:0 0 14px;background:var(--panel);
  border:1px solid var(--hair);border-top:2px solid var(--bad)}
.as-mark{grid-area:mark;display:inline-flex;align-items:center;gap:7px;color:var(--bad)}
.as-bolt{width:18px;height:18px;flex:0 0 18px;display:block}
.as-word{font-size:12px;font-weight:700;letter-spacing:calc(.18em * var(--track));
  text-transform:var(--caps)}
.as-role{grid-area:role;font-size:10px;color:var(--dim);text-transform:var(--caps);
  letter-spacing:calc(.1em * var(--track));border:1px solid var(--hair);
  padding:2px 8px;white-space:nowrap}
/* the name of the screen. min-width:0 so a long `sub` ellipsises instead of shoving the count
   and the back link off the row. */
.as-said{grid-area:said;display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 180px}
.as-title{color:var(--bright);font-size:13px;letter-spacing:calc(.04em * var(--track))}
.as-sub{color:var(--dim);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* THE COUNT IS LIT ONLY WHEN IT IS ABOVE ZERO — a badge on a zero is the furniture the
   console's band ruling exists to remove. It is the ACCENT, not the red: this is a workload,
   not an alarm (LOOK, the colour law). */
/* ⚠ AND IT TAKES THE ROW'S CASING (forage-qw2w.34, second half). Owner review 2026-09-05:
   *"/admin/users prints its member count twice, 350px apart, in two different casings — `6
   members` (lowercase, admin bar) and `6 MEMBERS` (uppercase, panel meta)."* Re-measured on
   master at 390x844: both nodes hold the string `6 members`; the panel's `.pn-meta` wears
   `var(--caps)` and this one wore nothing, so ONE printing of one number rendered two ways
   156px apart. Every other token on this row — `ADMIN`, the role lozenge, `PAGES` — is already
   caps, so the lowercase count was the only thing on the strip speaking a different language.
   ⚑ THE DUPLICATION ITSELF IS NOT FIXED HERE and it is not mine to fix: SPEC_admin_suite §5
   puts a live count on the strip for all twenty panels and the roster panel's meta is the
   panel primitive's own slot. Which of the two should stop printing is an owner call. */
.as-count{grid-area:count;justify-self:end;font-size:12px;color:var(--dim);white-space:nowrap;
  text-transform:var(--caps);letter-spacing:calc(.06em * var(--track));
  font-variant-numeric:tabular-nums}
.as-count.on{color:var(--accent);font-weight:700}
/* THE WAY OUT, FULL WIDTH. ≥ the 44px floor on EVERY pointer, not only a coarse one: this is
   the way out of a panel and it is not a control anybody should have to aim at. It was two
   halves; the second was `pages`, which is the burger on row one now.
   ⚖ THE BURGER IS THE SAME FLOOR, AND IT IS SQUARE. A 44px tap target carrying one glyph — the
   only navigation this suite has below 1000px, so it gets the size that says so, and it never
   moves between the overview and a panel page. */
.as-acts{grid-area:acts;display:grid;grid-template-columns:1fr;gap:8px}
.as-back,.as-burger{display:flex;align-items:center;justify-content:center;
  min-height:var(--tap-min);font-size:11px;text-align:center;
  border:1px solid var(--hair)}
.as-back{color:var(--dim);text-decoration:none;white-space:nowrap;padding:5px 9px;
  text-transform:var(--caps);letter-spacing:calc(.1em * var(--track))}
.as-back:hover{color:var(--ink);border-color:var(--hair-hi)}
/* ⚑ IT CARRIES THE WORD NOW (forage-qw2w.25). Two burgers 120px apart, one labelled `MENU`
   and one a bare glyph, is the "some buttons had text, some didn't" defect at its purest — and
   the unlabelled one was the one whose contents nobody could guess. The 44px floor and the grid
   area do not move; only the label appears. */
.as-burger{grid-area:burger;min-width:var(--tap-min);padding:0 11px;font-size:11px;line-height:1;
  gap:7px;background:var(--sunk);color:var(--ink);cursor:pointer;font-family:inherit}
.as-burger-g{font-size:15px;line-height:1}
.as-burger-w{text-transform:var(--caps);letter-spacing:calc(.12em * var(--track));
  white-space:nowrap}
.as-burger:hover{border-color:var(--hair-hi);color:var(--bright)}

/* ---- THE SUITE: RAIL + COLUMN -----------------------------------------------------------
   One column by default (the rail is display:none below 1000px — the PAGES list is its mobile
   spelling, and drawing both would be the same list twice on one screen).

   ⚖ THE GROUND IS A DOT GRID (SCiPNET), AND IT IS `.pn-ground` NOW (web/app-panel.css). The
   column wears the primitive's class; what stays here is the one thing that is about THIS
   column and not about a dot grid — that it may not be wider than its own track. */
.as-suite{display:block}
.as-rail{display:none}
.as-main{min-width:0}

/* ≥1000px THE DESKTOP LAYOUT IS UNCHANGED (owner: *"pretty based desktop layout"*). The strip
   returns to the single flex row it has always been — the grid's areas simply stop applying —
   and the BURGER is removed, because the rail on the left IS the list it would open. */
@media(min-width:1000px){
  .as-strip{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
  .as-acts{display:flex;gap:8px}
  /* ⚠ `.sqdrawer.as-sheet`, TWO classes: app-search.css loads AFTER this sheet
     (server/tests/cssparts.js) and a bare `.as-sheet` loses the tie to `.sqdrawer`'s own
     display. The drawer does not merely hide at this width — it never opens, because the
     rail is on screen and the ☰ that opens it is gone. */
  .as-burger,.sqdrawer.as-sheet,.as-scrim{display:none}
  .as-back{min-height:0;display:inline-flex}
  .as-suite{display:grid;grid-template-columns:216px minmax(0,1fr);gap:22px;align-items:start}
  /* ⚠ THIS RULE SAID `position:sticky` AND THE RAIL HAS NEVER ONCE BEEN STICKY. `.as-panel`
     carried `position:relative` and sat 20 lines BELOW this block in the same file, so it won
     the tie at equal specificity and the rail resolved to `relative` — with `top:14px` still
     applying, because a relative box honours `top` too. What shipped is a rail nudged 14px down
     its column, and that 14px is the offset of a sticky that never engaged.
     ⚑ LIFTING THE PANEL INTO web/app-panel.css WOULD HAVE HANDED IT BACK. The primitive loads
     ABOVE this sheet (that is the whole point of a base), so `.pn{position:relative}` stops
     outranking this line and the rail becomes sticky and 14px higher — measured. That is a
     change to how the screen behaves, which the ticket that moved the panel (forage-qw2w.4) was
     not allowed to make, and it is not obviously an improvement: at 1440×900 this rail is
     1060px tall against a 900px viewport, so pinning its top puts its last rows permanently out
     of reach. So the live behaviour is written down here as itself.
     ⚖ WHETHER THIS RAIL SHOULD STICK IS A REAL QUESTION AND IT BELONGS TO WHOEVER OWNS THE RAIL
     — with the answer probably `position:sticky;top:14px;max-height:calc(100dvh - 28px);
     overflow:auto`, which is the shape that survives a rail taller than the screen. ⛔ Do not
     restore the bare `sticky` on its own: that is the version that was measured and rejected. */
  .as-rail{display:block;position:relative;top:14px}
}

/* ═══ THE PANEL: ONE GRAMMAR, EVERY BOX ON THIS SCREEN ══════════════════════════════════════
   ⚖ THE PANEL ITSELF IS `.pn` NOW (web/app-panel.css, docs/design/PANEL_PRIMITIVE.md). The
   hairline box, the eight-layer corner brackets, the tab label on the top edge and the meta
   slot were written for this screen and immediately wanted by the panel pages and the member
   screens; they live in the primitive so all three adopt and revert them together. Every
   `.as-panel` in web/page-admin.js carries `pn` beside it.

   ⚑ WHAT STAYS HERE IS THIS SUITE'S ONE DEVIATION: the padding lives on an INNER box. The
   primitive pads the panel itself, which is right for a panel whose content starts at the top;
   this suite's meta slot has to sit OUTSIDE that padding below 1000px, hard against the right
   edge, so the panel pads nothing and `.as-in` pads instead. `--pn-pad` is the primitive's own
   hook for exactly this, and it is a value, not an override. */
.as-panel{--pn-pad:0}
.as-meta+.as-in{padding-top:10px}
.as-in{position:relative;padding:22px 16px 16px}
/* the rail's rows run to the panel's own edge, so its inner box carries no side padding. */
.as-rail .as-in{padding:20px 0 10px}

/* ---- THE RAIL, WHICH IS A PANEL TOO (LOOK §1) -------------------------------------------
   ⚖ THE ROW, ITS 44px FLOOR AND THE CURRENT MARK ARE `.pn-rail` / `.pn-mark`, and the group
   header is `.pn-group` (web/app-panel.css). The rail was TERMINAL_UI's sidebar read off the
   references, and every screen with a list of destinations wants the same row. What stays here
   is the wrapper's rhythm and the label's ellipsis, neither of which is about a rail. */
.as-group{margin:0 0 16px}
.as-group:last-child{margin-bottom:0}
.as-cell-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* THE COUNT LOZENGE (there is no other kind — the page does not draw a zero). Accent ground,
   --void ink: the same pair the tab label uses, which is the pair every skin keeps legible.
   ⚑ AND IT IS DELIBERATELY NOT `.pn-loz.is-accent`. The primitive's filled lozenge carries the
   hairline border every lozenge has, in the fill's own colour; this one has never had a border,
   and adopting it would make the rail's count 2px taller and 2px wider on a screen this ticket
   promised not to move. A lozenge that can drop its border is a change to the primitive
   somebody should make deliberately, not a pixel this lane spends on the way past. */
.as-n{flex:0 0 auto;font-size:10px;font-weight:700;line-height:1.5;padding:1px 6px;
  background:var(--accent);color:var(--void);font-variant-numeric:tabular-nums}

/* ---- FOCUS. Red is right here: a focus ring is a state, not a resting colour. -------------- */
.as-prow:focus-visible,.as-cell:focus-visible,.as-tile:focus-visible,.as-burger:focus-visible,
.as-back:focus-visible,.as-more:focus-visible,.as-disk:focus-visible{
  outline:2px solid var(--bad);outline-offset:2px}

/* ---- THE PANEL PAGE. The same box; the panel's own contents style themselves. ------------- */
.as-panelbody{padding:22px 16px 16px}

/* ---- THE OVERVIEW ----------------------------------------------------------------------
   Six panels, in the order somebody opening this page asks them, 22px apart. */
.as-sec{margin:0 0 22px}
.as-more{font-size:11px;color:var(--dim);text-decoration:none;white-space:nowrap;
  text-transform:var(--caps);letter-spacing:calc(.08em * var(--track))}
.as-more:hover{color:var(--accent)}
/* the one caption a panel may carry: what YOU may do here, or how a number was counted.
   ⚠ A MEASURE, because at 1440 an uncapped caption is a 900px line of 11px text. */
.as-note{font-size:11px;color:var(--dim);padding:6px 0;line-height:1.6;max-width:52em}
/* WHOSE PAGE THIS IS — a caption under the last panel, not a paragraph in a box. */
.as-whose{position:relative;z-index:1;border-top:1px solid var(--hair);margin-top:2px;
  padding:10px 2px 0;color:var(--faint);font-size:10.5px;line-height:1.6}
.as-whose b{color:var(--accent);font-weight:400;text-transform:var(--caps);
  letter-spacing:calc(.1em * var(--track))}

/* ---- ① NEEDS YOU (LOOK §2) --------------------------------------------------------------
   Two parts side by side: SCiPNET's OVERALL STATUS block — a header strip, one big word and a
   striped meter — and the tiles it summarises. ⛔ ONE ANATOMY PER TILE: a count and a label,
   every cell, always. The version before this one gave some tiles a third line ("2 illegal")
   and not others, which is exactly the mixed-anatomy row the references never draw. */
.as-nyou{position:relative;z-index:1;display:grid;grid-template-columns:1fr;gap:14px}
.as-status{display:flex;flex-direction:column;border:1px solid var(--hair);background:var(--sunk)}
/* the header strip's own centring and tracking; the type itself is `.pn-label`. */
.as-status-h{text-align:center;letter-spacing:calc(.14em * var(--track));padding:7px 10px;
  border-bottom:1px solid var(--hair)}
.as-status-b{flex:1 1 auto;display:flex;align-items:stretch;gap:14px;padding:18px 14px}
/* ⚖ THE WORD IS CENTRED IN ITS OWN CONTAINER, and the meter is a SIBLING beside it rather than
   a passenger inside it (SCiPNET: the mint SAFE has its own box; the striped bar stands outside
   it). Centring the pair would leave the word itself sitting left of centre, which is the
   "uncentred content in a container" the references never do. The WORD is `.pn-word` and the
   bar beside it is `.pn-meter`; what is admin's own is only that this word fills the space
   left over beside the meter. */
.as-statword{flex:1 1 auto;min-width:0;display:flex;align-items:center;justify-content:center}
/* CELLS IN ONE BOX, NOT TILES FLOATING IN A GRID (TERMINAL_UI) — `.pn-cells` / `.pn-cell`, whose
   1px gap over a --hair ground IS the hairline between cells.
   ⚠ AN ODD LAST TILE SPANS THE ROW. Five queues in a two-column grid left a SIXTH CELL EMPTY,
   and because the hairlines are the grid's own --hair ground showing through a 1px gap, that
   empty cell rendered as a solid grey block in the corner — a hole in the box, photographed at
   390px. A tile that owns its row is still a count over a label; a hole is a defect. */
.as-tile:nth-child(odd):last-child{grid-column:1 / -1}
/* ⚖ THE COUNT IS THE MARK, AND THE TILE STAYS DARK — `.pn-cell.is-faint` for a zero,
   `.pn-cell.is-lit` for anything above it, which is the whole of the difference between a task
   and a door. ⚑ THE 2px IS RESERVED AT REST: `is-lit` paints a top border, and a tile that only
   grew one when it lit would MOVE as the count crossed zero. */
.as-tile{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;
  padding:14px 8px 16px;text-decoration:none;color:var(--ink);
  border-top:2px solid transparent;min-height:var(--tap-min);text-align:center}
.as-tile:hover{background:var(--panel)}
.as-tile-k{letter-spacing:calc(.08em * var(--track));line-height:1.35}
/* ⚠ THE NUMERAL NEEDS A BAND TO STAND IN. The tiles carry an opaque --sunk ground, so a
   watermark "behind" them is a watermark you cannot see — the first cut showed a 20px grey
   stub poking out below the last row and read as a rendering fault, not as ornament. The panel
   keeps a 58px clear strip at its foot; the digit lives there whole, and `overflow` stops it
   from hanging out of the box. */
.as-needs .as-in{overflow:hidden;padding-bottom:58px}

/* ---- ④ THE MACHINE (LOOK §5) ------------------------------------------------------------
   A 2×2 of cells in one box: RUNNING · DISK on the first row, PIPELINES across the second.
   ⚑ THE FOURTH CELL — `SCHEDULED` — IS NOT DRAWN, because /api/admin/summary exposes no clock
   for the sweeps (server/moderation.js adminSummary sends queues, room, version and pipelines,
   and nothing with a next-run time). The LOOK's own instruction for that case is: do not draw
   it, and let PIPELINES span two columns. ⛔ Never invent a datum to fill a grid. */
/* ⚑ ONE COLUMN ON A PHONE, NOT THE PRIMITIVE'S TWO. `.pn-cells` defaults to two below 1000px
   because most cell grids are counts under labels; these four are paragraphs of machine state,
   and two of those side by side at 390px is two columns of wrapped text. */
.as-mgrid{--pn-cols:1}
.as-mcell{display:block;color:var(--ink);text-decoration:none}
a.as-mcell:hover{background:var(--panel)}
.as-mhead{letter-spacing:calc(.14em * var(--track));padding-bottom:7px;margin-bottom:11px;
  border-bottom:1px solid var(--hair)}
/* RUNNING — the stamp, read (spec §5): sha7 big and bright, the instant and the box under it. */
.as-sha{font-size:16px;line-height:1.2;color:var(--bright);font-variant-numeric:tabular-nums;
  overflow-wrap:anywhere}
/* UNSTAMPED, and anything unparseable: the same slot, no colour (the LOOK §5). */
.as-sha.as-off{color:var(--dim);letter-spacing:calc(.08em * var(--track))}
.as-mline{font-size:11px;color:var(--dim);margin-top:4px;overflow-wrap:anywhere;line-height:1.5}
/* DISK — a headline `.pn-num` over a `.pn-gauge`, the LOOK's third ornament.
   ⚑ THE GAUGE IS A DIV NOW, NOT AN SVG. It was an SVG rect so the fill's WIDTH could be a
   geometry attribute rather than an inline style; the primitive reads that width from a
   `--pn-fill` custom property, which is the same argument answered better — a custom property
   IS a declared value handed to a rule that already exists, not a rule invented at runtime. It
   is the ONE inline allowance on this site and app-panel.css's header is where that is written
   down. All that is left here is where the track sits under the number it measures. */
.as-gauge{margin-top:11px}
/* PIPELINES — a name and a STATE WORD, and the word carries the whole verdict. The tones ARE
   the console's ladder (common-chrome.js pipelineCensus), never a second opinion here. */
.as-rows{display:flex;flex-direction:column}
.as-row{display:flex;flex-wrap:wrap;gap:2px 12px;align-items:baseline;
  justify-content:space-between;padding:9px 0}
.as-row+.as-row{border-top:1px solid var(--hair)}
.as-k{flex:0 1 auto;min-width:0;letter-spacing:calc(.1em * var(--track))}
.as-state{flex:0 0 auto;font-size:12px;color:var(--dim);text-transform:var(--caps);
  letter-spacing:calc(.1em * var(--track))}
.as-row.good .as-state{color:var(--good)}
.as-row.warn .as-state{color:var(--accent)}
.as-row.bad .as-state{color:var(--bad)}
/* `dim` — a pass switched OFF by an owner ruling (6658ef04): not late, not dead, just not
   running. It reads as absent rather than as a state, which is the whole point: an amber row
   for a thing nobody asked to run is a false alarm, and a red one is a lie. */
.as-row.dim .as-state{color:var(--dim)}
.as-hint{flex:1 0 100%;text-align:right;font-size:11px;color:var(--dim);line-height:1.5}
/* ⚖ THE HAZARD STRIPE, AND THIS IS THE ONLY PLACE IT APPEARS ON THIS SCREEN (LOOK, ornament 1)
   — SCiPNET's own emphasis mark, spent on the one state the archive has that actually means
   "stop and look". The stripe is `.pn-hazard`; what a DEAD/NEVER row does with it is drop the
   separator it would otherwise sit on and make room for the 6px band. */
.as-row.bad{border-top:0;padding-top:19px}

/* ---- ⑤ RECENT AUTHORITY — KERNEL_LOG (LOOK §6, spec §5) ---------------------------------
   ⛔ NO `overflow-x`, NO FIXED HEIGHT. This box was a scroller holding the audit log's own
   fixed-width code line, and a scroller inside the page's scroller is two surfaces moving
   under one thumb — the owner's scrolling screenshot captured the box twice. The row is a
   wrapping flex line of TOKENS (page-admin.js adminAuthorityRow), so a long path costs a
   second line and never a millimetre of document width.
   ⚖ THE TIME READS FIRST AND THE DOM ORDER DOES NOT MOVE. TERMINAL_UI's log leads with the
   timestamp; the row's DOM sequence (kind · who · verb · what · when) is pinned by
   adminpanels.test.js as the READING order for a screen reader, so `order` moves the pixel and
   leaves the announcement alone. That is what `order` is for.
   ⚑ THE WHOLE ROW IS THE PRIMITIVE NOW: `.pn-log` > `.pn-row`, with `.pn-time`, `.pn-actor`,
   `.pn-verb` and `.pn-path`, and a `.pn-loz` for the kind. The `as-aud-*` classes stay ON the
   elements because adminpanels.test.js reads the row's shape off the FIRST class of each child
   — that list IS the reading order it pins — and one of them still carries paint. */
/* the kind lozenge is coloured BY FAMILY (LOOK §6): money is the accent, harm is the red, the
   keys of the kingdom are the info blue, everything else is furniture. Those are the
   primitive's `.is-warn` / `.is-bad` / `.is-info`, mapped in page-admin.js at the seam.
   ⚠ `order:0` IS LOAD-BEARING AND CANNOT LEAVE. `.pn-time` sits at -1 and the fail mark at -2;
   an element with no `order` takes 0 anyway, but the kind is the row's FIRST DOM child, and
   writing its 0 is what makes three explicit orders read as one sequence instead of two
   exceptions to an unstated rule. */
.as-aud-kind{order:0}
/* A FAILED ACT IS THE OTHER PLACE RED MEANS WHAT IT SAYS (spec §5). */
.as-aud-fail{order:-2;flex:0 0 auto;color:var(--bad);font-weight:700;font-size:10px}

/* ---- PAGES: THE PHONE'S NAVIGATION (spec §5, §6) ----------------------------------------
   Four cards, one per PANEL_GROUPS entry, each a column of ≥44px rows. This is the chip
   scroller's replacement and it is the shape the content already had: twenty named
   destinations in four clusters is a LIST, and a list on a phone runs down.
   ⚑ IT LIVES ONLY IN THE SHEET NOW (spec §6). It was also a section of the overview until the
   owner asked for the dashboard clean; the ☰ on the strip's first row is the one way in. */
.as-pcard{border:1px solid var(--hair);background:var(--sunk);margin:0 0 10px}
.as-pcard:last-child{margin-bottom:0}
.as-pchead{font-size:10px;color:var(--dim);text-transform:var(--caps);
  letter-spacing:calc(.14em * var(--track));padding:9px 12px 7px}
/* THE 44px FLOOR, AND IT IS THE ROW — not a chevron a thumb has to find. */
.as-prow{display:flex;align-items:center;gap:10px;min-height:var(--tap-min);
  padding:8px 12px;border-top:1px solid var(--hair);border-left:2px solid transparent;
  text-decoration:none;color:var(--ink)}
.as-prow:hover{background:var(--panel)}
.as-prow-t{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:2px}
.as-prow-l{font-size:12.5px;color:var(--ink)}
.as-prow-s{font-size:10.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
/* THE LIT ROW'S EDGE — the rail's own mark, in the sheet, so "where am I" reads the same in
   both spellings of this list.
   ⚑ AND IT IS NOT `.pn-mark`, DELIBERATELY. `.as-prow:hover` is two classes and `.pn-mark` is
   one, so the primitive's mark would LOSE the tie to hover and the row you are on would flip to
   the plain hover ground as a thumb dragged past it. Fixing that from here means writing a
   `.pn-*` selector in a screen sheet, which is the one thing the primitive forbids. The rail's
   rows get `.pn-mark` (app-panel.css pairs it with its own `:hover` rule); this list keeps its
   own spelling of the same two declarations until the phone row moves into the primitive too. */
.as-prow.on{background:color-mix(in srgb,var(--accent) 10%,transparent);
  border-left-color:var(--accent)}
.as-prow.on .as-prow-l{color:var(--bright)}

/* ---- THE SHEET (spec §5) -----------------------------------------------------------------
   ⚖ IT WEARS `.sqdrawer` (web/app-search.css), the archive's settled bottom drawer, which
   admin.html already links. That class owns the four decisions this surface must not re-take:
   fixed to the bottom edge, translateY off-screen, `visibility` flipped AFTER the slide so a
   shut drawer cannot be dragged into view by a mobile browser's URL-bar slide, and the
   safe-area inset spent on the SCROLLER rather than on a percentage-height box. Inventing a
   third overlay is how one of those gets missed, and it is always the safe area.
   ⚠ `.sqdrawer.as-sheet` — TWO classes on purpose. app-search.css loads AFTER this sheet
   (server/tests/cssparts.js), so a bare `.as-sheet` loses every tie to `.sqdrawer`.
   What is admin's own is exactly two things: the scrim, and the fact that neither exists at
   ≥1000px, where the rail is on screen and this would open onto its own duplicate. */
.sqdrawer.as-sheet .body{padding:10px 10px calc(12px + env(safe-area-inset-bottom,0px))}
.as-scrim{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:calc(var(--z-sheet) - 1);
  opacity:0;visibility:hidden;transition:opacity .22s ease,visibility 0s linear .22s}
.as-scrim.open{opacity:1;visibility:visible;transition:opacity .22s ease}
@media(prefers-reduced-motion:reduce){.as-scrim,.as-scrim.open{transition:none}}

/* ═══ THE GRAPHS (docs/SPEC_admin_suite.md §6, dressed by the LOOK §3 and §4) ═══════════════
   Owner, 2026-09-04: *"I'd like graphs and such on the dashboard too."* The marks are built in
   web/page-admin.js as inline SVG; what lives here is the paint, the two spacers and the sizes.

   ⚖ TWO SERIES COLOURS, AND THEY WERE VALIDATED, NOT PICKED. `--as-chart-a` is a step down the
   archive's brand amber (--accent #eda733) and `--as-chart-b` a step down its info blue
   (--info #7fd4ff). The `dataviz` skill's validator, run against BOTH surfaces this archive
   can wear (--sunk #0c0f14 on the dark skins, #ffffff on the two light ones in app-skins.css),
   passes them on the lightness band, the chroma floor, CVD separation (worst adjacent ΔE 21.5
   under protanopia, against a ≥8 target), the normal-vision floor (23.4) and dark contrast.
   ⚠ THE BRAND TOKENS THEMSELVES FAIL that band on a dark surface (L .78 and .83 against a
   .48–.67 ceiling) — two near-white marks on near-black is a chart that glares. These are the
   nearest passing steps, which is exactly what the skill says to do with a design system's
   ramp. The light skins return ONE warn (the blue at exactly 3:1) and the skill's relief for
   that is visible labels or a table view; this chart ships both.
   ⛔ NOT `--bad`. Red is the suite's alarm mark (spec §2) and a series wearing it would say
   "uploads are an emergency" every day of the week. Nothing in a chart here is ever red.
   ⚖ VARIABLES RATHER THAN LITERALS so a skin can restate them; the fallback is the validated
   value, so a skin that says nothing still gets a validated chart. */
.as-chart{--as-chart-a:#bd8829;--as-chart-b:#3f9ecf}

/* ---- ② THE LAST 30 DAYS. Six cells in ONE box (LOOK §3), never six floating boxes. --------
   ⚖ TABULAR NUMERALS ON THE VALUE. The house rule for this suite is tabular everywhere the
   eye scans a column, and six cells in a row ARE a column: `1,284` over `981` with
   proportional figures shifts the hundreds digit sideways between neighbours. */
/* the box and its hairlines are `.pn-cells` / `.pn-cell`; the number is `.pn-num` and the label
   `.pn-label`. What is left is this panel's own spacing and its slightly tighter tracking. */
.as-metric{padding:11px 12px 13px}
.as-m-k{letter-spacing:calc(.1em * var(--track))}
.as-m-n{line-height:1.05;margin-top:6px}
/* THE DELTA IS QUIET IN BOTH DIRECTIONS. A rise gets --good; a fall is simply --dim. ⛔ Never
   --bad: a slow Tuesday is not an incident, and red spent on one is red that means nothing
   when it lands on an illegal report. */
.as-m-d{font-size:10px;color:var(--dim);margin-top:3px;font-variant-numeric:tabular-nums}
.as-m-d.up{color:var(--good)}
/* the sparkline. 30 units tall in its own coordinate space, stretched across the cell — the
   stroke is `vector-effect:non-scaling-stroke` in the markup, which is what keeps 2px at 2px
   under a one-axis scale. The AREA FILL under the line (LOOK §3) is what turns a hairline into
   a shape you can read at a glance; the baseline is a real hairline so the fill has a floor. */
.as-spark{display:block;width:100%;height:30px;margin-top:9px;overflow:visible}
.as-spark-a{fill:var(--accent);fill-opacity:.14;stroke:none}
.as-spark-b{stroke:var(--hair);stroke-width:1}
.as-spark-l{fill:none;stroke:var(--dim);stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
/* the current point, in the accent — the skill's stat-tile contract — with a 2px ring in the
   SURFACE colour so it stays a dot where the line runs under it. */
.as-spark-d{fill:var(--accent);stroke:var(--sunk);stroke-width:2}

/* ---- ③ ACTIVITY BY DAY ------------------------------------------------------------------ */
.as-chart{border:1px solid var(--hair);background:var(--sunk);padding:12px 14px 14px}
.as-chead{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 14px;margin-bottom:10px}
.as-legend{display:flex;gap:14px;flex:0 0 auto}
.as-lg{display:inline-flex;align-items:center;gap:6px}
/* THE SWATCH CARRIES THE IDENTITY; THE WORD NEVER DOES. Text in this suite wears text tokens —
   a mid-amber label on --sunk is a legibility bug wearing a design decision. */
.as-lg-sw{width:10px;height:10px;flex:0 0 10px}
.as-lg-sw.a{background:var(--as-chart-a)}
.as-lg-sw.b{background:var(--as-chart-b)}
.as-lg-t{font-size:10px;color:var(--dim);text-transform:var(--caps);
  letter-spacing:calc(.08em * var(--track))}
/* THE READOUT IS THE TOOLTIP, WRITTEN WHERE IT CANNOT BE CLIPPED — and it is this panel's META
   SLOT (LOOK §4). A floating box at 390px either overflows the card or covers the bar it
   describes. `min-height` so the row does not jump by a line the first time somebody touches
   a bar. */
.as-read{display:inline-flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline;min-height:14px;
  justify-content:flex-end}
.as-read-d{color:var(--faint)}
.as-read-v{color:var(--bright)}
/* ⚖ THE Y AXIS IS HTML IN A GUTTER, NOT SVG TEXT. The plot is stretched on ONE axis
   (preserveAspectRatio=none), so a `<text>` inside it would be stretched 2.9× horizontally at
   1440px — measured, and unreadable. The three labels are absolutely positioned at the
   PERCENTAGES the three gridlines sit at (10, 67 and 124 of the 140-unit box), which stay
   correct at both the 140px and the 180px height because the mapping is linear. */
.as-plotwrap{display:flex;gap:9px;align-items:flex-start}
.as-ylab{position:relative;flex:0 0 auto;width:2.8em;height:140px}
.as-ylab span{position:absolute;right:0;transform:translateY(-50%);font-size:10px;
  color:var(--faint);font-variant-numeric:tabular-nums;white-space:nowrap}
.as-yl-t{top:7.14%}
.as-yl-m{top:47.86%}
.as-yl-b{top:88.57%}
.as-plotcol{flex:1 1 auto;min-width:0}
/* ⚖ ONE-AXIS SCALE, FIXED HEIGHT. The plot is 360×140 units; a uniformly-scaled copy would be
   400px tall in the desktop column. `preserveAspectRatio=none` in the markup plus a height
   here gives a band of 8.1px on a 390px phone and 22.9px at 1280 — under the 24px bar cap at
   both ends, which is the number that had to hold. */
.as-plot{display:block;width:100%;height:140px}
/* THE X AXIS — a date every seventh day, laid on a 30-column grid that mirrors the plot's own
   bands, so a tick sits over the day it names. `justify-self` pins the two ends inside the
   box instead of letting them hang off it. */
.as-xax{display:grid;grid-template-columns:repeat(30,1fr);margin-top:6px;font-size:10px;
  color:var(--faint);font-variant-numeric:tabular-nums}
.as-xax span{justify-self:center;white-space:nowrap;text-transform:var(--caps);
  letter-spacing:calc(.06em * var(--track))}
.as-xt1{grid-column:1;justify-self:start}
.as-xt8{grid-column:8}
.as-xt15{grid-column:15}
.as-xt22{grid-column:22}
.as-xt29{grid-column:29;justify-self:end}
/* ⚠ THREE TICKS ON A PHONE, NOT FIVE. A 366px plot gives each of the thirty columns 12px; five
   55px dates centred on their bands run straight into each other and the axis reads as one
   unbroken row of uppercase words — measured at 390px, and it is exactly the failure mode the
   references rule out. The second and fourth are dropped; every value is still in the table
   under the chart, and the ticks that remain still sit over the days they name. */
.as-xt8,.as-xt22{display:none}
/* the two-ended fallback, for a window that is not thirty days long. */
.as-plotax{display:flex;justify-content:space-between;gap:8px;margin-top:6px;
  font-size:10px;color:var(--faint);font-variant-numeric:tabular-nums}
/* HAIRLINE AXES, RECESSIVE, SOLID — never dashed. Three gridlines (0 · mid · max) named by the
   gutter beside them, and a 1px tick under every one of the thirty day-slots so an empty month
   still reads as thirty slots rather than as an empty box. */
.as-axis{stroke:var(--hair-hi);stroke-width:1}
.as-grid{stroke:var(--hair);stroke-width:1}
.as-tick{stroke:var(--hair-hi);stroke-width:1}
.as-bar.a{fill:var(--as-chart-a)}
.as-bar.b{fill:var(--as-chart-b)}
/* the hovered band LIFTS, so the reader sees the chart respond to the pointer rather than
   only the readout changing at the top. It is the one animated thing this feature draws, and
   `prefers-reduced-motion` turns it into an instant state change — never into no feedback.
   `pointer-events` on a transparent fill needs saying, because `fill:none` would take it out
   of the hit test entirely. */
.as-hit{fill:var(--hair);fill-opacity:0;pointer-events:all;transition:fill-opacity .12s ease}
.as-hit:hover{fill-opacity:.35}
@media(prefers-reduced-motion:reduce){.as-hit{transition:none}}
/* THE TABLE VIEW, and the section headings. Offscreen, not absent: the picture is for the eye
   and this is the data; `.as-h` stays for the harness and the screen reader while the TAB
   LABEL carries the name on screen. The idiom is app-curation.css's own 1px clip — one house
   spelling of "reachable, not shown". */
.as-sr,.as-h{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap;border:0;padding:0;margin:-1px}

/* ---- ≥1000px, THE LATE OVERRIDES ---------------------------------------------------------
   ⚠ A SECOND min-width BLOCK, AND IT HAS TO BE HERE. Media queries add no specificity, so a
   desktop rule must be written AFTER the base rule it overrides — put back up in the layout
   block near the top it loses to the base and nothing moves (measured: the plot stayed 140px
   on a 928px-wide column). Everything below is a WIDTH or a HEIGHT decision only. */
@media(min-width:1000px){
  .as-meta+.as-in{padding-top:22px}
  .as-nyou{grid-template-columns:210px minmax(0,1fr)}
  /* `auto-fit`, not `auto-fill`: an empty track here is the same grey hole the phone had. It is
     the one cell grid on this screen whose column count is not a number, so it states a template
     rather than `--pn-cols`. */
  .as-tiles{grid-template-columns:repeat(auto-fit,minmax(122px,1fr))}
  .as-tile:nth-child(odd):last-child{grid-column:auto}
  .as-metrics{--pn-cols:6}
  .as-mgrid{--pn-cols:2}
  .as-mwide{grid-column:1/-1}
  .as-xt8,.as-xt22{display:block}
  .as-plot{height:180px}
  .as-ylab{height:180px}
}

/* ---- SERVICES + CONNECTORS (docs/design/SHREDVERSE_KERNEL.md Phase D) ----------------------
   web/page-admin-services.js. One vertical list of cards at EVERY width — a key cabinet is a
   handful of rows, each a name, some chips and one button — so this is the whole of its CSS
   and there is no table and no breakpoint. Inherits the panel's own colours; the only accent is
   the archive's --bad on a revoked chip and a failed ledger line, which is what red means here. */
.sv-list{display:flex;flex-direction:column;gap:10px;margin-top:12px}
.sv-row{border:1px solid var(--line, rgba(255,255,255,.12));border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:6px}
.sv-row.revoked,.sv-row.off{opacity:.6}
.sv-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.sv-chip{font:inherit;font-size:.78em;letter-spacing:.04em;text-transform:uppercase;padding:1px 7px;border:1px solid var(--line, rgba(255,255,255,.18));border-radius:999px;color:var(--dim)}
.sv-chip.lan{color:var(--accent)}
.sv-chip.bad{color:var(--bad);border-color:var(--bad)}
.sv-line{display:flex;gap:8px;font-size:.92em;flex-wrap:wrap}
.sv-line.bad .sv-v{color:var(--bad)}
.sv-k{color:var(--dim);min-width:6.5em}
.sv-v{overflow-wrap:anywhere}
.sv-form{display:flex;flex-direction:column;gap:6px}
.sv-events,.sv-scopes{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:center}
.sv-events label,.sv-scopes label,.admin-acts label{min-height:44px;display:inline-flex;align-items:center;white-space:nowrap}
.sv-wide{flex:1 1 16em;min-width:12em}
.sv-code{display:block;overflow-wrap:anywhere;user-select:all;padding:8px 0;font-size:1.05em}

/* ═══ THE PANEL PAGES (docs/design/DESIGN.md, forage-qw2w.5) ══════════════════════
 * The twenty /admin/<id> pages, wearing the panel grammar. ⚖ EVERYTHING THE PRIMITIVE COVERS
 * IS `.pn-*` (web/app-panel.css) and nothing below re-declares one: what lives here is only
 * what is true of THIS suite — that a triage card has to stop being a card before it can be a
 * log row, and how a thumbnail sits beside one.
 * ⛔ Never write a bare `.pn-…` selector in this file (panelprimitive.test.js). A deviation
 * goes on an `.as-*` class COMPOUNDED with the primitive's — `.as-trow.pn-hazard` — which is
 * how one shape stays one shape.
 * REVERT: this whole region plus the `pn-*`/`as-*` classes in web/page-admin*.js.
 */

/* the meta slot sits above the body, so the body loses its top padding when one is present —
   the same rule `.as-meta + .as-in` already makes for the overview's boxes. */
.as-meta+.as-panelbody{padding-top:10px}
/* an EMPTY meta must not reserve a line: a panel with no count would otherwise open with a
   blank right-aligned strip that reads as a rendering fault. */
.as-panel>.as-meta:empty{display:none}

/* ---- A TRIAGE CARD, RE-CLOTHED AS A LOG ROW ---------------------------------------------
   ⚖ THE CARD IS NOT REDRAWN, IT IS UNDRESSED. `.console-card` (app-moderation.css) and
   `.modcard` (app-curation.css) are also the masthead console's and the post page's, so their
   own rules stay exactly as they are; `.as-trow` removes only the three properties that make a
   card a card — its border, its own ground and its margin — and `.pn-log > .pn-row` supplies
   the row. What is left is the anatomy, which is the part worth keeping.
   ⚠ `background-color`, NOT `background`. The shorthand would erase `.pn-hazard`'s stripe,
   which is a background-IMAGE on the same element. */
.as-trow{border:0;background-color:transparent;margin:0}
/* the alarm row needs the 6px stripe to stand in, or the first line of text sits under it. */
.as-trow.pn-hazard{padding-top:15px}
/* ⚖ THE ACTIONS GO FLUSH RIGHT ON THE ROW'S OWN LINE (the LOOK: *"ACTIONS flush right as
   `.pn-btn`s"*), which means the card's body stops being a COLUMN of stacked blocks and becomes
   the same wrapping flex line the row is. On a phone the line simply wraps and the buttons take
   their own row — never a millimetre of sideways scroll. */
.as-trow .console-meta,.as-trow .modbody{display:flex;flex-direction:row;flex-wrap:wrap;
  align-items:baseline;gap:5px 10px;min-width:0}
/* ⚑ 2026-09-07 — AND THE ACTIONS THEMSELVES WRAP, WHICH THEY DID NOT.
   ⚖ `flex:0 1 auto`, NOT `0 0 auto`, AND THAT ONE DIGIT IS THE WHOLE FIX. `.console-cardacts`
   (app-moderation.css:241) and `.modacts` (app-curation.css:143) are ALREADY `display:flex;
   flex-wrap:wrap`, so their buttons have always been willing to take a second line — but a flex
   ITEM with `flex-shrink:0` is laid out at its MAX-CONTENT width, which for a wrapping container
   is every button on ONE line. The box therefore sized itself past the viewport and the inner
   wrap could never engage: the row overflowed instead of wrapping, at any width, silently.
   ⚠ MEASURED, /admin/reports at 390x844 on the sample archive, an `illegal` report card — the
   THREE-button case that ships today (`dismiss` 75px · `remove` 67px · `⚠ escalate / preserve`
   186px, common-triage.js): the acts box drew **339px inside a 238px line, 101px past its right
   edge**, and the document scrolled sideways to **448px in a 390px viewport**. After: the box is
   **238px, overflow 0, scrollWidth 390 = clientWidth** — the buttons take three lines instead of
   leaving the screen. A FOURTH action (`read the report →`) then costs 498px→238px and 260px of
   overflow→0. ⛔ 1440 is unchanged in both states (the line is 810px, nothing shrinks).
   ⚠ AND THE FLOOR HOLDS: every act measures 44px tall at 390 before and after, four buttons as
   well as three — wrapping a line does not shorten what is on it (tapfloorcensus.test.js).
   ⚖ THIS IS WHY THE REPORT LANE HAD TO ABBREVIATE `read →` (common-triage.js's own note): the
   fourth action was made to fit the bug rather than the bug fixed. Labels are free again.
   ⚠ `justify-content:flex-end` keeps EVERY line flush right, not just the first — without it a
   wrapped second row of buttons drifts to the left of a box that is itself pinned right, which
   reads as two different alignments in one card.
   ⛔ Nothing here touches the buttons' own size: the 44px floor is bought by `.modacts .vbtn`'s
   `::after` overhang (app-curation.css, `@media(pointer:coarse)`) and by the console act's own
   padding, and wrapping a line does not shorten what is on it. */
.as-trow .console-cardacts,.as-trow .modacts{margin:0 0 0 auto;flex:0 1 auto;
  justify-content:flex-end}
/* the two things that want a line of their own: a reporter's note and the voter breakdown. */
.as-trow .console-note,.as-trow .voterlist{flex:1 1 100%;margin:0}
/* the thumbnail keeps its size (the LOOK, "Media"); it just stops being the tallest thing. */
.as-trow .console-thumbwrap,.as-trow .modthumbwrap{align-self:center}
/* the evidence record is a block, not a token in the line — it wraps under everything. */
.as-trow .console-incident{flex:1 1 100%}
/* a control the primitive now draws must not also carry the console's 10px padding twice. */
.as-trow .console-act{padding:5px 9px}
/* ⚖ THE ID IS THE ROW'S GUTTER, the way `.pn-time` is the audit log's: a fixed --dim tabular
   column at the head of every row, so a column of rows reads as a column of subjects rather
   than as three sentences that happen to start with a number. It is the primitive's shape and
   not its class — `#31` is not a time — so the width is stated here, once. */
.as-trow .console-metatop{flex:0 0 4.2em;color:var(--dim);font-variant-numeric:tabular-nums}

/* ---- A PANEL THAT IS ONE NUMBER AND ONE DOOR (dupes) -------------------------------------
   ⚖ CENTRED IN ITS OWN CONTAINER, which is the reference's rule the owner named twice: the
   figure is a cell of its own rather than a number floating at the left of a wide box.
   ⚠ THE 2px IS RESERVED AT REST — `.pn-cell.is-lit` paints a top border, and a cell that does
   not already carry a transparent one MOVES when it lights (PANEL_PRIMITIVE law 4). */
.as-one{display:block}
.as-one-fig{text-align:center;padding:20px 14px 18px;
  border:1px solid var(--hair);border-top:2px solid transparent}
.as-one-k{margin-top:6px}
/* ⚖ SCiPNET's `<ACTIVATE CONTROL PANEL>`: the one door out of a one-fact panel is a WIDE
   bracketed bar under the figure, not a small button parked at the left of an empty box. A
   panel whose content occupies a third of its width is the "uncentred content in a container"
   the references never do. */
.as-one-act{display:block;margin:12px 0 0}
.as-one-b{width:100%}

/* ---- THE ROLL OF RULINGS (requests) ------------------------------------------------------ */
.as-ruled{margin-top:14px;border-top:1px solid var(--hair);padding-top:10px}
.as-ruledlist{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}

/* ⚠ THE COUNT MUST BE ABLE TO SHRINK. Its track is `minmax(0,1fr)` and the text is `nowrap`,
   so a long sentence ("3 awaiting a verdict") overflowed its cell and printed ON TOP of the
   role chip beside it — photographed at 390px on every panel page. Clipping is the honest
   answer now that the panel's own meta slot carries the same sentence in full. */
.as-count{justify-self:stretch;text-align:right;min-width:0;overflow:hidden;
  text-overflow:ellipsis}

/* ⚖ AN EMPTY STATE IS A SENTENCE, NOT A PARAGRAPH THE WIDTH OF THE SCREEN. Centred in a 900px
   panel, one --dim line ran the full width and read as the "box holding a paragraph" the
   references never do. A measure caps it; the centring is the primitive's. */
.as-empty{max-width:44em;margin-left:auto;margin-right:auto}

/* ---- A FILTER THAT MATCHED NOBODY (forage-jx64.22) ---------------------------------------
   ⚖ THE SAME DRAWING AS AN EMPTY PANEL, plus the way back. The sentence is `.pn-empty.as-empty`
   unchanged — a filter with no answer and a panel with no rows are the same event on screen, so
   inventing a shape for one of them is the fifth style §5 warns about. All this block adds is
   the control under it, centred on the sentence it belongs to.
   ⚠ THE SENTENCE'S OWN PADDING IS 22px TOP AND BOTTOM, so the link is pulled up against it:
   without the negative margin the two sit 22px apart and read as an empty state that happens to
   have a link somewhere beneath it, rather than as one offer. */
.as-nomatch{text-align:center}
/* ⚠ BENT THROUGH `.as-empty`, THIS FILE'S OWN CLASS, AND NEVER THROUGH `.pn-empty`. The panel
   primitive has ONE definition in app-panel.css and panelprimitive.test.js fails any other
   stylesheet writing a `.pn-*` selector — a local override wins its tie by document order and
   reads as two harmless lines while the shape quietly forks. The sentence already wears
   `.as-empty` for its measure, so the trim rides that. */
.as-nomatch .as-empty{padding-bottom:8px;margin-bottom:0}
.as-nomatch-clear{display:inline-block;font-size:11px;color:var(--dim);
  text-transform:var(--caps);letter-spacing:calc(.08em * var(--track))}
.as-nomatch-clear:hover{color:var(--accent)}
@media(pointer:coarse){.as-nomatch-clear{min-height:var(--tap-min);line-height:var(--tap-min)}}

/* ---- THE DM READER'S PAIR MARK (forage-jx64.24) ------------------------------------------
   ⚠ THE BOX IS THE CALLER'S, THE PAINT IS THE NODE'S — the scopeMark contract (app-search.css
   `.sqpill .sci` carries the same warning). scopeMark hands back a node with its own
   fill/stroke so it does NOT inherit `svg.navi` and the masthead's 14px cell; without a width
   here an <svg> with a viewBox computes to 100% and smears across the note.
   ⚖ 15px, AND THE 13px THE SCOPE MARKS WERE RASTERISED AT WAS TRIED FIRST AND PHOTOGRAPHED.
   13px is the size a pill's mark is compared at BESIDE ITS OWN LABEL, where the word carries
   the meaning and the mark only has to be distinguishable from the other five. Here the mark
   stands IN FOR A WORD between two usernames, so it has to be read rather than recognised —
   and `related`'s two nodes are r2.6 in a 24 grid, which at 13px is a 1.4px radius against a
   0.95px stroke: the circles silt up and the barbell reads as a bare diagonal slash. At 15px
   they resolve. ⛔ Do not take it back down to match the pills; they are not the same job.
   ⚠ The baseline nudge is optical: an SVG box sits ON the text baseline, so a mark whose ink
   fills its box hangs high against lowercase without it. */
.as-note .as-pair{width:15px;height:15px;vertical-align:-3px;margin:0 3px}

/* ---- THE FILTER ROW: a header strip, not two controls adrift -----------------------------
   ⚖ TERMINAL_UI PUTS ITS CONTROLS ON A HEADER STRIP WITH A HAIRLINE UNDER IT, which is what
   makes the panel read as one instrument rather than as a box somebody dropped a form into.
   The controls themselves are unchanged; the strip is the frame around them. */
.as-filterbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 12px;
  padding:0 0 11px;border-bottom:1px solid var(--hair)}
.as-filterbar .adminput{flex:1 1 12em;min-width:0}
.as-filterbar .inv-qty{flex:0 0 5em}

/* ---- THE COUNT CELLS ABOVE A LIST -------------------------------------------------------
   ⚠ `auto-fit`, NEVER `auto-fill`. The separators here are the container's --hair ground
   showing through a 1px gap, so an EMPTY track is not a gap — it is a solid block of hairline
   (web/app-panel.css's own warning, photographed once at 390px). auto-fit collapses the empty
   track; auto-fill invents it. */
.as-rolecells,.as-invcells{grid-template-columns:repeat(auto-fit,minmax(8.5em,1fr));
  margin:0 0 14px}
/* the cell reserves the 2px `.pn-cell.is-lit` paints, or lighting one moves it (law 4). */
.as-rolecell{border-top:2px solid transparent;text-align:center}

/* ---- THE ROSTER ROW ----------------------------------------------------------------------
   The log row's tokens, in the order the eye wants them: when they were last here, who they
   are, what they are, how far they are trusted, then everything that is only sometimes true. */
.as-seen{flex:0 0 5.2em}
.as-who{flex:0 0 auto;min-width:0;overflow-wrap:anywhere}
.as-role{min-width:5.5em;text-align:center}
/* TRUST, AS A PICTURE AND A FIGURE. The bar is short on purpose: it lives inside a line of
   text and its job is "roughly where on the scale", which a 54px track answers as well as a
   300px one would — and a long bar in a log row reads as a progress indicator. */
.as-trust{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px}
.as-gauge-s{width:54px;flex:0 0 54px}
.as-trust-n{color:var(--ink);font-variant-numeric:tabular-nums;font-size:11px;min-width:3em}
.as-usermeta{flex:0 0 auto;white-space:nowrap}
.as-burn{font-variant-numeric:tabular-nums}
/* ⚖ THE ACTIONS ARE FLUSH RIGHT AND THEY WRAP AS A UNIT, so a row with three controls and a
   row with one still line their last button up against the same edge.
   ⚠ `flex:1 1 auto` AND `min-width:0`, NEVER `0 0 auto`. A non-shrinking group of four
   nowrap buttons is as wide as its max-content — measured at 390px it made the whole DOCUMENT
   493px wide and pushed `dossier →` off the screen, which is the sideways scroll the log row
   exists to prevent. Shrinking lets the group wrap inside itself first. */
.as-uacts{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:6px;
  margin-left:auto;flex:1 1 auto;min-width:0}
@media(max-width:999px){.as-uacts{flex:1 1 100%;margin-left:0;justify-content:flex-start}}
.as-nocontrol{font-size:10.5px;color:var(--faint)}
/* the roster's rows are taller than a log line — a gauge and a 44px control live in them. */
/* ⚑ OWNER 2026-09-05: *"went from being really info dense to being really pretty and not info
   dense ... find a way to compromise."* The roster row was TWO lines — data, then a full line of
   four 44px controls that wrapped beneath it — so a screen showed three or four members. The
   controls are compact at ≥1000px (where the admin actually lives) and the labels lost their
   verb ("make binkie" → "binkie", the ✓ carrying the on-state, "dossier →" → "dossier"), so a
   member is ONE line and a screen shows a dozen. ⛔ THE 44px TAP FLOOR IS KEPT ON TOUCH: the
   compaction is desktop-only (min-width:1000px), and below it the controls stay full-size and
   wrap exactly as they did — forage-cgy's floor is not spent for density. */
.as-urow{padding-top:6px;padding-bottom:6px}
.as-invrow,.as-staffrow{padding-top:9px;padding-bottom:9px}
/* ⛔ FINE-POINTER ONLY. The compaction takes controls under the 44px tap floor, so it is gated
   on a mouse (hover+fine) as well as width — a touchscreen laptop at 1400px keeps full-size,
   tappable controls (mobiletaps.test.js is the census that holds this). */
@media(min-width:1000px) and (hover:hover) and (pointer:fine){
  .as-urow .as-ubtn{font-size:10.5px;padding:2px 6px;min-height:0;height:auto;line-height:1.5}
  /* ⚖ THE ACTION GROUP HOLDS ITS LINE. `flex:1 1 auto` let it wrap to a row of its own the moment
     the data filled the width — which is the two-line row the compaction exists to remove. At a
     mouse width there is room for both, so the group takes only what it needs and sits beside the
     data; the whole row is `nowrap` so a stray wide field cannot break it either. */
  .as-urow{flex-wrap:nowrap}
  .as-urow .as-uacts{gap:3px;flex:0 0 auto}
  .as-urow .as-gauge-s{width:40px;flex:0 0 40px}
  .as-urow .as-trust-n{min-width:2.5em}
  .as-urow .as-usermeta,.as-urow .as-burn,.as-urow .as-role{font-size:11px}
}
/* ⛔ AND ANY COARSE POINTER GETS THE FLOOR BACK, explicitly — the compaction above is fine+hover
   only, but a hybrid laptop reports hover:hover while a thumb can still reach the screen, so the
   tap target is re-asserted here where mobiletaps.test.js's census reads it (the escape hatch is
   only credited inside a pointer:coarse block). */
@media(pointer:coarse){
  .as-urow .as-ubtn{min-height:var(--tap-min);min-width:var(--tap-min)}
}

/* ---- THE INVITE ROW ---------------------------------------------------------------------
   ⚖ THE LINK TAKES A LINE OF ITS OWN, because a URL is longer than any phone and the code at
   its head is the part a human reads back over a voice call. `flex-basis:100%` gives it the
   whole row rather than letting it push the state lozenges off the end. */
.as-code{font-variant-numeric:tabular-nums;letter-spacing:calc(.04em * var(--track))}
.as-usedby{color:var(--dim)}
.as-invlink{flex:1 1 100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-width:0}
.as-invlink .inv-link{flex:1 1 18em;min-width:0}
.as-invlink .inv-dest-row{flex:1 1 100%;margin:0}

/* ---- THE STAFF ROW ----------------------------------------------------------------------
   ⚖ WHAT A ROLE CAN DO IS A SENTENCE ABOUT THE ROLE, so it wraps to its own line under the
   name rather than squeezing the ladder's buttons off the right edge. */
.as-staffgroup{margin:18px 0 8px;padding-left:0}
.as-staffgroup:first-child{margin-top:0}
/* ⚠ IT SHARES THE LINE WITH THE LADDER at desktop widths and takes its own below them.
   `flex-basis:100%` at every width gave every staff row a full empty line and pushed the two
   buttons onto a third — three rows of furniture for one sentence and two controls. */
.as-staffwhat{flex:1 1 16em;min-width:0;color:var(--dim);font-size:11px;line-height:1.5}
@media(max-width:999px){.as-staffwhat{flex:1 1 100%}}
.as-stafflog .rolechip{min-width:6em;text-align:center}

/* ---- THE BRANCH TREE --------------------------------------------------------------------
   The tree is the picture; only its toolbar joins the suite. `.bv-ctl` keeps its own width so
   `+` and `−` stay square, and `.pn-btn` gives them the hairline box everything else wears. */
.bv-ctl.pn-btn{min-width:2.4em}

/* ---- CONTENT: TASTE, SOUND REVIEW, TEXT RULES, FILTERS ----------------------------------
   ⚖ NOTHING BELOW REDRAWS A SHAPE THE PRIMITIVE ALREADY OWNS. `.console-pipe`, `.sr-row`,
   `.statrow`, `.fr-row` and `.filterroster` are drawn on other surfaces too, so their own
   rules stay where they are; these lines only undress the card and place the tokens. */
.as-tastrow,.as-srow,.as-frrow{border:0;background-color:transparent;margin:0}
.as-tastnum{flex:0 0 auto;font-variant-numeric:tabular-nums;color:var(--ink)}
/* the five taste counts, in ONE box (auto-fit: an empty track here is a solid hairline block,
   never a gap — web/app-panel.css says why). */
.as-tastcells{display:grid;gap:1px;background:var(--hair);border:1px solid var(--hair);
  grid-template-columns:repeat(auto-fit,minmax(8.5em,1fr));margin:14px 0}
/* the ascii sparkline is a PICTURE made of glyphs, so it gets the size a picture needs.
   ⛔ It was two inline style rules written from script; a class is where a skin can reach it. */
.as-sparktext{font-size:16px;letter-spacing:calc(1px * var(--track));color:var(--accent)}
/* a sub-heading INSIDE a panel — best tags / weakest tags — is a spaced --dim label, never a
   fourth size of heading competing with the tab label above it. */
.as-substep{margin:14px 0 6px}
.as-micro{color:var(--ink);font-size:12px}

/* THE FILTER ROSTER: a bar chart with the count beside it (Spectre's own row). The bar is the
   primitive's gauge now, so only the row's proportions live here. */
.as-frrow{gap:6px 12px}
.as-frrow .fr-bar{flex:1 1 8em;min-width:4em;height:8px}
.as-frn{flex:0 0 auto;font-size:14px}

/* SOUND REVIEW: the player keeps its size; the naming controls sit with the actions. */
.as-srow{align-items:center}
.as-srow .sr-audio{flex:1 1 100%;height:32px;margin:2px 0}
.as-srcline{flex:1 1 100%}
.as-srow .sr-slug,.as-srow .sr-label{flex:1 1 9em;min-width:7em}

/* THE FILTER TABS (sound review's pending / accepted / rejected).
   ⚖ "YOU ARE HERE" IS THE ACCENT AND IT IS A CLASS, not a border colour written from script.
   ⚠ THE LIT TAB RESERVES ITS EDGE AT REST via `.pn-btn`'s own 1px border, so switching tabs
   moves nothing — only the colour changes. */
.as-tab{width:auto;padding:6px 12px}
.as-tab.is-on{border-color:var(--accent);color:var(--accent)}

/* ⛔ NO ROUNDED RECTANGLES ON THE PANEL PAGES. `.tr-card` carries a 5px radius from
   app-moderation.css and one rounded box among a dozen bracketed ones reads as the odd one
   out — the references have no radius anywhere. Scoped to the suite, because the class is
   admin-only but the rule is about THIS screen's grammar, not about a card. */
.as-panelbody .tr-card{border-radius:0}
/* the form's own name is a spaced --dim label, not a fourth size of heading under the tab. */
.as-panelbody .tr-new .tr-head{font-size:11px;color:var(--dim);text-transform:var(--caps);
  letter-spacing:calc(.12em * var(--track));border-bottom:1px solid var(--hair);
  padding-bottom:7px;margin-bottom:10px}

/* ---- MACHINE: DISK · AUDIT · SERVICES · CONNECTORS -------------------------------------- */

/* THE DISK FIGURES, in ONE box (auto-fit — an empty track here is a solid hairline block, not
   a gap; web/app-panel.css says why, and it was photographed at 390px once). */
.as-diskcells,.as-cellrow{display:grid;gap:1px;background:var(--hair);border:1px solid var(--hair);
  grid-template-columns:repeat(auto-fit,minmax(9em,1fr));margin:0 0 14px}
.as-diskcells .as-gauge{margin-top:9px}
/* ⚠ THE WORD IS CENTRED IN ITS OWN CELL, like the number in the cell beside it. Left-aligning
   it under a centred label is the "uncentred content in its container" the references never do
   — photographed at 1440 on the first cut of this page. */
.as-purgeword{font-size:22px;margin-bottom:2px}

/* ---- THE AUDIT LOG (the LOOK: "the dashboard's row shape, EXACTLY") ----------------------
   ⛔ THE INNER SCROLLER AND THE `white-space:pre` LINE ARE GONE. `.auditlog` was 60vh of
   `overflow:auto` with a 4px radius and lines that ran off the right edge; a scroller inside
   the page's scroller is two surfaces under one thumb, and the owner's scrolling screenshot
   caught this very box twice. The row wraps now and the page is the only thing that scrolls.
   ⚠ EVERY RULE HERE IS 0,2,0 ON PURPOSE. app-viewer.css holds `.auditline` and `.chip` and
   loads AFTER this sheet (server/tests/cssparts.js), so an equal-specificity override would
   lose the tie. Scoping to the row is what wins it — and it also keeps the console's own copy
   of these classes untouched, which is the point of not editing that sheet. */
/* ⚠ THE CLASS IS DOUBLED, AND THE REPETITION IS THE POINT. app-viewer.css holds `.auditline`
   — `white-space:pre`, a 3px left rule, a zebra `:nth-child(odd)`, a monospace stack — and it
   loads AFTER this sheet (server/tests/cssparts.js), so an equal-specificity override loses the
   tie. `.as-auditrow.as-auditrow` is 0,2,0: enough to beat the rule AND its nth-child sibling,
   and it leaves the console's own copy of `.auditline` exactly as it is, which is why this is
   not an edit to that sheet. */
.as-auditrow.as-auditrow{white-space:normal;border-left:0;background:none;
  font-family:inherit;font-size:11.5px;line-height:1.6;padding:7px 12px}
/* ⚑ A FAILED ROW KEEPS ITS RED, AND IT IS THE ONLY RED IN THE BOX. "something broke" is the
   one fact in an audit log that means stop and look; a coloured left rule on all eleven kinds
   spent that signal on "somebody looked at a page". */
.as-auditlog>.as-auditrow.bad{box-shadow:inset 2px 0 0 var(--bad)}
/* ⚑ THE ELEVEN HEX SEGMENT COLOURS WENT WITH THE CODE LINE, so the overrides this lane wrote
   to neutralise them are gone rather than left as dead weight. app-viewer.css paints
   `.aud-actor` #f0874f, `.aud-subj` #6cc7f0 and nine more — literals chosen for ONE of seven
   skins, illegible in the two light ones — and no row on this suite writes those classes any
   more: `adminAuthorityRow` writes the primitive's `.pn-actor` / `.pn-verb` / `.pn-path`,
   which are tokens. ⚠ The classes still EXIST and are still right where they are: the masthead
   console draws the same segments and this sheet does not own that surface.
   ⚖ THE CONTEXT IS ONE TRAILING FRAGMENT AND IT WRAPS UNDER, NEVER BESIDE. `flex-basis:100%`
   at the phone rung gives `admin · desktop · 127.0.0.1` its own line under the act; at desktop
   widths it is the FROM column (below), which is where furniture belongs.
   ⚑ IT USED TO READ `while on desktop[::ffff:127.0.0.1] using mode[admin]` — the log's own
   notation, fifty-one characters, on every row, and the third line of a three-line entry at
   390px. page-admin.js parses it into its three facts now; nothing is dropped and the two
   server tokens are verbatim on the cell's title. */
.as-aud-ctx{color:var(--faint);font-size:10.5px}
@media(max-width:999px){.as-aud-ctx{flex:1 1 100%}}
.as-auditlog .as-aud-when{flex:0 0 5.4em}
/* the note and the leftovers are the subject's own detail, so they read as detail. */
.as-aud-note,.as-aud-etc{color:var(--faint)}

/* ═══ THE LOG IS A TABLE, AND AT DESKTOP IT IS DRAWN AS ONE (forage-qw2w.28) ══════════════
   Owner site review, /admin/audit at 1440: *"54 rows of identical grey prose with no column
   grid … the one surface on the site that is a TABLE is rendered as ragged prose."* The cause
   is measurable rather than a matter of taste: the kind lozenge is variable-width (READ is 4
   characters, MODERATION is 10), so a wrapping flex line starts the ACTOR at a different x on
   every row and the SUBJECT at a different x again, and the eye has to read each row from its
   start to find either. The reference is TERMINAL_UI's ACTIVE_PROCESSES — PID / NAME / USER /
   STS / TIME on a fixed grid.
   ⚖ FIXED `em` COLUMNS, NOT `max-content`, AND THAT IS THE WHOLE MECHANISM. Every row is its
   OWN grid container (a shared one would need `display:contents`, which would take the row's
   hairline, its hover and the failed row's red edge with it), so a content-sized track is sized
   per row and lands exactly back at ragged. A length is the same length on all 54.
   ⚖ EVERY CELL IS PLACED EXPLICITLY. A row with no failure marker has no cell for column 1, and
   auto-placement would slide the whole line one column left — the ragged edge again, wearing a
   grid. Naming the column is what makes an absent cell a GAP.
   ⛔ NO `height`, NO `overflow` — adminpanels.test.js gates both, and the reason is the owner's
   scrolling screenshot that caught this box twice. TO WHAT wraps inside its track and a long
   subject costs a second line, never a millimetre of document width.
   ⚖ AND THE RUNG IS `PANES_BP` (1280), NOT THIS SUITE'S USUAL `BURGER_BP` — MEASURED, not
   chosen, and a rung off the ladder rather than a fourteenth number (app.css's header;
   mobilebreakpoints.test.js refuses a new one). `.wrap` is `max-width:1200px;padding:0 14px`
   and the rail takes 216px + a 22px gap, so the log row's own content box is
   `min(vw,1200) - 28 - 238 - 32 - 2 - 24`. The five fixed tracks, the FROM cap and their gaps
   cost 647px of that, which leaves TO WHAT — the one elastic column — **229px at 1280** and
   **52px at 1000**, where every path would wrap to three lines.
   ⚠ FROM IS 17em BECAUSE `admin · desktop · 127.0.0.1` IS 176px AND 15em WAS 173. It clipped the
   last two characters of the address on every LAN row — the ellipsis landing on the one fact
   somebody opens this log to pivot on. A longer address still clips, and that is what the
   verbatim title is for; the ordinary row must not. Seven fixed columns need the
   width, so under the rung the row stays the wrapping flex line it has always been, with the
   FROM cell now three short facts instead of two sentences of bracket notation. */
/* ⛔ BELOW THE RUNG THERE ARE NO COLUMNS TO NAME. The row wraps there — that is app-panel.css's
   one decision and it is right on a phone — so a header would be seven words labelling nothing.
   ⚠ IT IS DECLARED BEFORE THE @media BLOCK ON PURPOSE: a media query adds no specificity, so a
   trailing `display:none` would win on document order and the header would never draw at all. */
.as-audhead{display:none}
@media(min-width:1280px){
  /* ⚠ ONE DECLARATION FOR THE HEADER AND THE ROWS TOGETHER. Two templates that must agree are
     two that will drift, and a header one column out of step is worse than none. */
  .as-audhead,.as-auditlog > .as-auditrow.as-auditrow{
    display:grid;column-gap:10px;row-gap:1px;align-items:baseline;font-size:11.5px;
    grid-template-columns:3.4em 5.4em 7.8em 8.5em 9em minmax(0,1fr) minmax(0,17em)}
  .as-auditlog > .as-auditrow > .as-aud-fail{grid-column:1}
  .as-auditlog > .as-auditrow > .as-aud-when{grid-column:2}
  .as-auditlog > .as-auditrow > .as-aud-kind{grid-column:3;justify-self:start}
  .as-auditlog > .as-auditrow > .as-aud-who{grid-column:4}
  .as-auditlog > .as-auditrow > .as-aud-verb{grid-column:5}
  /* TO WHAT is the one elastic column, and everything that explains the subject shares it: the
     `post[N]` a vote was cast on and the `— wrong password` of a failed login stack UNDER the
     subject they belong to rather than colonising a neighbour's column. */
  .as-auditlog > .as-auditrow > .as-aud-what,
  .as-auditlog > .as-auditrow > .as-aud-note,
  .as-auditlog > .as-auditrow > .as-aud-etc{grid-column:6}
  /* FROM is furniture at the far edge. It is the one cell that clips, because it is the one
     cell whose full text is guaranteed to be somewhere else — verbatim on its own title, and
     in the flat server line on the row's (page-admin.js adminAuthorityRow). */
  .as-auditlog > .as-auditrow > .as-aud-ctx{grid-column:7;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* ⚠ NO BORDER OF ITS OWN: the header is the log box's FIRST CHILD, so `.pn-log`'s frame is
     already around it and a second one would draw a box inside a box. Its only edge is the
     hairline the next row carries — `.pn-log > .pn-row + .pn-row` cannot supply it, because
     the header is not a `.pn-row`. */
  .as-audhead{padding:7px 12px;color:var(--faint);
    text-transform:var(--caps);letter-spacing:calc(.1em * var(--track))}
  /* ⚠ THE LABEL IS SMALL, THE HEADER IS NOT — AND THAT SPLIT IS LOAD-BEARING. The tracks are
     `em`, so `em` is the header's OWN font-size: a 9.5px header laid 3.4em out is 32px where the
     11.5px row's is 39px, and the two grids drift further apart at every column. Measured: the
     labels sat 40–95px left of the values they name, which reads as a rendering fault. The
     shared rule fixes the em basis on both; only the LABEL TEXT is 9.5px.
     ⚠ A LABEL MAY NOT WRAP. `to what` is two words in a track sized for the VALUES under it,
     and a header that goes two lines tall while the rows stay one is a box with a lid. */
  .as-audhead-c{font-size:9.5px;white-space:nowrap;overflow:hidden}
  .as-auditlog > .as-audhead + .as-auditrow{border-top:1px solid var(--hair)}
}
/* ⚖ THE PANEL'S META READS LIKE EVERY OTHER PANEL'S. "54 entries, newest first" was lowercase
   and left-aligned where the site says "6 MEMBERS", "20 POSTS", "41 RESULTS" uppercase and
   flush right (owner site review, forage-qw2w.28). ⚠ The casing is the SHEET's, not the
   string's: `--caps` is `none` on a board skin, and a shouted literal cannot follow it back
   down. `max-width` comes off — a right-aligned measure would float away from the edge. */
.as-auditmeta{max-width:none;text-align:right;text-transform:var(--caps);
  letter-spacing:calc(.08em * var(--track));font-size:10px}
/* ⛔ AND THE DOSSIER'S THREE LOG LISTS ARE NOT A 38vh WINDOW ONTO A LOG. `.aa-log` capped them
   (app-moderation.css) back when a row was a fixed-width code line; a scroller inside the
   page's scroller is two surfaces under one thumb, and these lists are short. */
.aa-log{max-height:none;overflow:visible;border-radius:0}

/* THE FILTER CHIPS ARE LOZENGE TOGGLES (the LOOK). `chip` stays — adminroster.test.js reads it
   and so does the coarse-pointer tap floor in app-moderation.css — and the pill radius and the
   per-kind hex fill come off. ⚠ 0,3,0 to beat `.chip.on`, which loads later. */
.as-kindrow{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0}
/* ⚠ THE CLASS IS DOUBLED, AND IT IS NOT TIDINESS. `.chip.on{background:var(--k,#6cf)}` lives
   in app-viewer.css, which loads AFTER this sheet — so `.as-panelbody .as-kchip` (0,2,0) TIED
   with it and lost on document order, and the `all` chip carries no `k-<kind>` class at all, so
   `--k` was unset and the fallback painted. What shipped was a SKY-BLUE block behind the active
   filter, taller and wider than the chip on all four sides, in a palette that is amber plus
   semantic green/amber/red on near-black and has no blue in it anywhere (forage-qw2w.27).
   ⚑ 0,3,0 wins the tie outright, and it leaves the masthead console's own `.chip` untouched —
   which is the whole reason this is not an edit to that sheet.
   ⚑ THE ACTIVE MARK IS THE LOZENGE INSIDE, and it always was: `.pn-loz.is-accent`, --accent on
   --void, the one filled variant the primitive has. The button is a hit area, not a colour. */
.as-panelbody .as-kchip.as-kchip{border:0;background:none;background-color:transparent;
  padding:0;border-radius:0;outline:0;box-shadow:none;color:inherit;font-weight:400}
.as-kchiploz{border-radius:0}

/* ---- SERVICES + CONNECTORS: THE KEY CABINET ---------------------------------------------
   ⛔ NO RADIUS AND NO PILLS. `.sv-row` carried 8px and `.sv-chip` 999px; the references have
   no rounded rectangle anywhere, and a row of pills beside a dozen bracketed boxes is the
   "highschool page" tell. The row is a log row, the chip is the lozenge. */
.as-panelbody .as-svrow{border:0;border-radius:0;padding:11px 12px;background-color:transparent;
  flex-direction:row;flex-wrap:wrap;align-items:baseline;gap:5px 10px}
.as-panelbody .sv-chip{border-radius:0}
.as-svrow .sv-head{flex:1 1 100%;display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.as-svrow .sv-line{flex:1 1 100%}
.as-svrow .sv-form,.as-svrow .sv-events,.as-svrow .sv-scopes{flex:1 1 100%}

/* ⚠ AN EMPTY TRACK IS A HOLE, NOT A GAP, AND IT WAS PHOTOGRAPHED AT 390px. The separators in
   `.pn-cells` are the container's --hair ground showing through a 1px gap, so a grid slot with
   nothing in it renders as a SOLID BLOCK of hairline — five disk figures in a two-column grid
   left one at the bottom right. The primitive's own note gives the two fixes; this is the
   first: an odd last cell spans the row. ⛔ Never `auto-fill`, which invents the empty track. */
@media(max-width:999px){
  .as-diskcells>*:last-child:nth-child(odd),
  .as-cellrow>*:last-child:nth-child(odd),
  .as-tastcells>*:last-child:nth-child(odd),
  .as-rolecells>*:last-child:nth-child(odd),
  .as-invcells>*:last-child:nth-child(odd){grid-column:1/-1}
}

/* ---- THE DOSSIER: SIXTEEN TAB-LABELLED PANELS (docs/design/DESIGN.md) ----------
   ⚠ `--pn-pad` IS RESTATED, because it is a custom property and `.as-panel{--pn-pad:0}` above
   INHERITS into everything inside the page's panel. Without this line every dossier section is
   an unpadded box — the exact failure PANEL_PRIMITIVE.md's revert note warns about.
   ⚑ THE OLD TOP RULE COMES OFF. `.aa-sec` marked a section with a hairline ABOVE it; the panel
   is the mark now, and keeping both is a rule over a box that already has four corners. */
.as-aasec{--pn-pad:20px 14px 14px;margin:16px 0;border-top:1px solid var(--hair);padding-top:20px}
.as-aasec:first-child{margin-top:6px}
/* the head is now only the clipped <h4> plus whatever a caller hangs beside it — no gap of its
   own, or every section opens with 6px of nothing under its tab label. */
.as-aasec .aa-sechead{margin:0}
/* the disclosure sits at the panel's foot, quiet, where the corner brackets can still be seen. */
.as-aasec .aa-what{margin-top:10px}

/* ---- THE AUDIT FILTER ROW, ON A GRID (forage-qw2w.38) ------------------------------------
   ⚠ WHAT WAS THERE: four controls, two rows, FOUR unique widths and no shared edge. The bar
   was a `flex-wrap` line, which is a machine for misalignment — it breaks wherever it happens
   to run out of width, so row B split at ~55/45 and row C at ~66/34 and neither column lined
   up with the other. A GRID states the intent instead of discovering it:
     row 1   the free-text box            full width
     row 2   the member box               full width
     row 3   failures only | heartbeats   two equal halves
     row 4   search                       full width
   Four rows, one gutter, every edge on the same two columns.
   ⚑ THE DOM ORDER ALREADY IS THIS ORDER (`bar.append(q, who, failed, noisy, go)`), so nothing
   moves for a screen reader and no `order` is spent.
   ⚑ ONLY BELOW 1000px. At desktop widths the bar is a single flex line with room for all five,
   which the owner has not complained about and which a grid would make worse. */
@media(max-width:999px){
  .as-auditbar{display:grid;grid-template-columns:1fr 1fr;gap:8px;align-items:stretch}
  .as-auditbar .adminput,.as-auditbar .as-go{grid-column:1/-1;min-width:0;width:100%}
  .as-auditbar .as-toggle{width:100%}
}
/* ⚖ A TOGGLE THAT CANNOT SAY WHETHER IT IS ON IS NOT A TOGGLE. `failures only` and `show
   heartbeats` are drawn with the same border, height and type as the text input beside them
   and had NO on-state a reader could see — app-viewer.css's `.btn.on` is a 1px outline, which
   at 11px reads as an anti-aliasing artefact. The accent border is the mark the sound-review
   tabs already use, so the suite has one answer to "this control is engaged".
   ⚠ 0,3,0, because `.btn.on` (0,2,0) lives in app-viewer.css and that sheet loads AFTER this
   one (server/tests/cssparts.js) — an equal-specificity rule here is a no-op that reads as a
   fix. Same trap as the sky-blue chip one bead over. */
.as-panelbody .as-toggle.on{outline:0;border-color:var(--accent);color:var(--accent);
  background:color-mix(in srgb,var(--accent) 10%,transparent)}

/* ---- THE ROSTER: ONE ANATOMY, AND A STATUS IS NOT A BUTTON (forage-qw2w.26) ---------------
   ⚖ A STATUS IS TEXT, A CONTROL IS A BOX. Four identical grey hairline boxes in one row
   carried four different meanings — [AUTO] a status, [✓ TRUSTED] a status, [GRANT] a button,
   [DOSSIER →] a link — and nothing on screen said which was which. The lozenges on this roster
   keep their tint, which is the part that carries meaning, and drop the border; only a thing
   you can press is boxed. That is also what closes the bead's last complaint: `11 posts` was
   bare text and `4200 · 280.2 KB` forty pixels later sat in a box. */
.as-flatloz{border-color:transparent;padding-left:0;padding-right:0}
/* and the control's box is heavier than the hairline that separates the rows, so it reads as
   raised rather than as another ruled cell. */
.as-ubtn{border-color:var(--hair-hi)}
.as-ubtn:hover:not(:disabled){border-color:var(--accent);color:var(--accent)}
/* ⚑ A REFUSAL THAT IS VISIBLE IS BETTER THAN A HOLE. A control the tier cannot use is drawn
   and disabled — the column stays a column an eye can run down — and it says so by going
   --faint against a sunk ground rather than by disappearing. `not-allowed` is the cursor,
   because the honest answer to a press is "not this account". */
.as-ubtn:disabled{color:var(--faint);border-color:var(--hair);cursor:not-allowed;opacity:1}

/* ═══ THE /data FEEDBACK CONSOLE (web/page-admin-feedback.js, audit 2026-09-13, /data wiring item 9) ═══
   Every list is a column of wrapping cards — results, the member record and the thresholds alike — so
   nothing scrolls sideways at 390px (⛔ no horizontal scroller in this suite below 1000px, spec §5).
   Flat marks, the admin sheet's own tokens. */
.afb-block{margin:0 0 20px}
.afb-h{margin:0 0 8px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:lowercase;color:var(--dim)}
.afb-chips,.afb-bar,.afb-meta,.afb-who{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.afb-bar{margin-bottom:8px}
.afb-btn{font:inherit;font-size:12px;line-height:1.3;padding:4px 9px;border-radius:4px;border:1px solid var(--hair);background:transparent;color:var(--dim);cursor:pointer}
.afb-chip.on,.afb-tab.on{color:var(--ink);border-color:var(--accent)}
.afb-list{display:grid;gap:8px}
.afb-item{display:grid;gap:5px;min-width:0;padding:8px 10px;border:1px solid var(--hair);border-radius:6px}
.afb-row{display:flex;gap:8px;align-items:baseline;min-width:0}
.afb-kind{flex:none;font-size:11px;color:var(--faint);white-space:nowrap}
.afb-subj{min-width:0;color:var(--ink);overflow-wrap:anywhere}
.afb-meta{font-size:12px;color:var(--dim)}
.afb-out{color:var(--ink)}
.afb-out.rev,.afb-flag{color:var(--bad)}
.afb-ans{font-size:11px;padding:1px 6px;border:1px solid var(--hair);border-radius:3px;color:var(--dim)}
.afb-ans.yes{color:var(--good)}
.afb-ans.no{color:var(--bad)}
.afb-revert{justify-self:start;color:var(--bad)}
.afb-item>.afb-chip{justify-self:start}
.afb-num{width:6em}
.afb-save{margin-top:8px}
/* the console's chips reach the tap floor on a touch screen only — the census's documented escape, the
   shape .as-urow .as-ubtn uses above; a mouse keeps the small drawn chip */
@media(pointer:coarse){
  .afb-btn{min-width:var(--tap-min);min-height:var(--tap-min)}
}
