/* ==========================================================================
   CALIBAN LEDGER — ledger-carry.css — WHAT THE NEW SHEET DOES NOT SAY.

   Load order is ledger.css, then this. ledger.css is the redesign; this file
   is the part of the old inline <style> in index.html that the redesign has
   no answer for, lifted out so that block can be deleted a screen at a time
   instead of all at once.

   HOW THE LIST WAS MADE, BECAUSE IT IS NOT A LIST ANYBODY WROTE DOWN.
   Three sets, and a subtraction:
     A  class names with a rule in index.html's inline <style>        570
     C  class names with a DECLARING rule in ledger.css               304
     A − C  names the new sheet has no answer for                     455
            of those, named nowhere in index.html or ui/app/*.js       80
     CARRIED                                                         375

   TWO THINGS THAT LOOK LIKE COVERAGE AND ARE NOT, both found by counting
   rather than by reading the sheet:

   AN EMPTY RULE IS NOT A RULE. ledger.css line 157 is
   `.crew-place:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){}` — a declared name with nothing
   in it. Counting selectors would have called .crew-place covered and thrown
   away the mono, the tracking and the gold of a squad mark. C above counts
   only rules that declare something, which is why it is 304 and not 305.

   THE MARKUP IS NOT THE ONLY PLACE A CLASS IS WRITTEN. index.html's own
   <script> blocks build class lists the templates then interpolate —
   `tickCls: on ? "pick-box pick-on" : "pick-box"`,
   `faceCls(base, has) => base + " lazy-avatar"` — so a survey that reads
   class= attributes alone misses .pick-box, .lazy-avatar, .avatar.avatar-face
   and 70-odd more, and reports 292 where the true figure is 375. Set B here
   is every word in index.html AND every word in the js modules, which errs
   deliberately toward carrying: a carried rule for a name nobody uses costs
   bytes, and a missing one costs a screen.

   WHAT THAT CAME TO, counted by parsing this file in the same engine that
   renders it: 580 style rules over 610 selector parts and 44 @media blocks —
   517 scoped component rules (§ 3), 59 servo-skull rules (§ 1), 29 @keyframes
   (§ 4), and four small blocks written here rather than lifted (§ 1's :root
   tokens and its box-sizing, § 2's fourteen tokens, § 4's reduced-motion
   guard). 262 selector parts were left behind: each names only classes
   ledger.css already declares, or names no class at all (`*`, `html`,
   `body`, `a`, `input`, the scrollbar pseudo-elements), which is the new
   sheet's base layer and not ours to re-assert.

   THE COMMENTS COME ACROSS WITH THE RULES. Where the author wrote down why a
   declaration is what it is — an owner's words, a measurement, a mistake made
   twice — that comment is carried with its rule. A rule whose reason is left
   behind is a rule the next person deletes.

   The scoping is tools/scope-ledger.mjs's, run over this file's component
   half: `:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))` on each selector's leftmost compound,
   which adds no specificity and counts the scope root as inside itself. Of
   the 610 selector parts here, the only ones that can match outside .ledger
   are § 1's — the skull's 63, his box-sizing and the :root he reads from.
   § 2, § 3 and § 4 escape nothing.
   ========================================================================== */

/* ==========================================================================
   § 1 · THE SERVO-SKULL — UNSCOPED, AND IT HAS TO BE.

   Everything else in this file is confined to .ledger. These are not, because
   the skull is not in the app's tree. skull.js appends .skull-host,
   .skull-glass and .skull-toggle to <body> (skull.js builds the host at
   :1168 and the glass at :1378; the toggle is the only way back and is
   present even when he is not loaded at all), and the rest — the cone, the
   field, the pool, the fire, the puffs — hang off that host. The scope root
   is a div inside body, not body itself: commit 637c36a measured it as
   `<div class="ledger page filing">`. So `:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))` on
   .skull-host matches nothing, and a scoped skull is not a mis-placed skull,
   it is an absent one.

   THE SAME ARGUMENT APPLIES TO HIS TOKENS, and this is the half that is easy
   to miss. Every one of these rules reads var(--ink), var(--serif),
   var(--ui), var(--rule-head) and the like. Today those come from the inline
   sheet's `:root`. ledger.css declares its tokens on `:where(.ledger)`, so
   the moment the inline block goes, a body-level element inherits nothing and
   the bubble loses its type, its colour and its scale in one step. The block
   below re-declares, on :root, exactly the 25 tokens the unscoped sheets read
   — the skull's 18, plus the seven more that skull-chat.css wants, since it
   is linked separately and lives at body level for the same reason. They are
   the old sheet's values, unchanged. They cannot fight ledger.css: .ledger
   re-declares all 25 for its own subtree, and a declaration on the element
   beats one inherited from the document.
   ========================================================================== */

:root{
  --bad-lit:#C4685F;
  --fs-1:calc(9px * var(--ui));
  --fs-2:calc(10px * var(--ui));
  --fs-4:calc(12px * var(--ui));
  --fs-5:calc(13px * var(--ui));
  --fs-7:calc(16px * var(--ui));
  --gold:#C9A45C;
  --gold-line:rgba(201,164,92,.35);
  --gold-warm:#E2C079;
  --good:#7FB489;
  --good-2:#6E9E7A;
  --gut:clamp(12px,2.1vw,34px);
  --ink:#E4EBE2;
  --ink-2:#A9BCAF;
  --ink-3:#8E9E93;
  --ink-4:#6D7D70;
  --ink-5:#5F7066;
  --line-2:#2B3A26;
  --ls-2:.14em;
  --mono:'JetBrains Mono',monospace;
  --r2:3px;
  --rule:rgba(178,201,177,.10);
  --rule-head:rgba(201,164,92,.22);
  --serif:'Cormorant Garamond',serif;
  --ui:1;
}

/* AND HE LOSES border-box WITH THE REST OF THE BASE LAYER. The inline sheet
   opens `*{box-sizing:border-box}`; ledger.css's equivalent is written
   `:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))`, which again does not reach body level. It is
   not cosmetic here: .skull-say sets max-width AND padding AND a border, so
   under content-box the reading bubble grows 25px wider than the 310.5px it
   was measured at — 23px of padding and 2px of border, outside the max-width
   instead of inside it. Asserted on his own subtree only — the host page's box
   model is not this file's to set. */
[class^="skull-"],[class*=" skull-"],
[class^="skull-"] *,[class*=" skull-"] *{box-sizing:border-box}

/*
 * WHAT HE IS READING, SAID OUT LOUD. Owner: "lets have that little guy when he
 * typing too put in a small chat bubble the name of the brothers, the operation
 * or mission and outcome."
 *
 * Positioned entirely from skull.js -- it rides his own coordinates -- so
 * everything here is paint. Fixed, because he is; pointer-events none, because
 * he is an ornament and a sergeant must never have to click past him.
 */
.skull-say{position:fixed;left:0;top:0;z-index:21;display:none;
  pointer-events:none;max-width:310.5px;
  padding:calc(8.05px * var(--ui)) calc(11.5px * var(--ui));
  border:1px solid var(--rule-head);border-radius:var(--r2);
  background:linear-gradient(180deg,rgba(19,34,27,.96),rgba(11,20,16,.96));
  box-shadow:0 10px 26px -12px rgba(0,0,0,.9),
             inset 0 1px 0 rgba(228,240,226,.06);
  /* The tail. Drawn on the RIGHT because the bubble sits to his left. */
  --tail:10px}

.skull-say::after{content:"";position:absolute;right:-6px;bottom:12px;
  width:10px;height:10px;transform:rotate(45deg);
  background:rgba(13,23,18,.96);
  border-right:1px solid var(--rule-head);border-top:1px solid var(--rule-head)}

.skull-say[data-side="right"]::after{left:-6px;right:auto;border-right:0;border-top:0;
  border-left:1px solid var(--rule-head);border-bottom:1px solid var(--rule-head)}

/* --ink-1 WAS NEVER DECLARED. The ladder is --ink then --ink-2..--ink-6, so
   this resolved to nothing and the title fell back to whatever it inherited --
   it has never once been the colour written here. --ink is the top of that
   ladder and what a title beside an --ink-4 line wants. */
.skull-say-k{font-family:var(--serif);font-size:calc(var(--fs-5) * 1.15);color:var(--ink);
  line-height:1.25;letter-spacing:.01em}

.skull-say-v{font-family:var(--mono);font-size:calc(var(--fs-2) * 1.15);color:var(--ink-4);
  line-height:1.5;letter-spacing:.02em;margin-top:2px}

/* The verdict of the PREVIOUS report, set quieter and ruled off, so it cannot
   be misread as a judgement on the one he is working now. */
/* --rule-2 WAS NEVER DECLARED EITHER, and an undefined colour in a border
   shorthand does not vanish -- it falls back to currentColor. So the rule that
   exists to set the previous verdict QUIETER apart has been drawing itself in
   the text's own colour, the loudest line in the bubble. --rule is the quiet
   one this asked for. */
.skull-say-v:last-child:not(:nth-child(2)){color:var(--ink-5);margin-top:5px;
  padding-top:5px;border-top:1px solid var(--rule)}

/* HIS OWN LINES ARE NOT A DATA READING. The reading below him is dense --
   a title and up to four fields -- and 13px is what fits it. A quip is one
   short sentence with a temper, the owner asked for it bigger ("the quotes are
   a little too small"), and one sentence can afford the width and the size that
   four fields cannot. */
.skull-say-q{font-family:var(--serif);font-size:calc(var(--fs-7) * 1.15);color:var(--ink);
  line-height:1.3;letter-spacing:.01em}

.skull-say-big{max-width:391px}

@media (max-width: 999px){
.skull-say{display:none!important}
}

@media (prefers-reduced-motion: reduce){
.skull-say{transition:none}
}

/*
 * THE OPEN REPORT'S HEADER, ON A PHONE.
 *
 * Owner: "we see awaiting judgment back and worth its too tall and takes up too
 * much screen." It is sticky, so whatever it costs it costs on every scroll --
 * and it was costing three wrapped rows before a single screenshot.
 *
 * The state word drops to the size of a heading rather than a title, the
 * sentence under it goes on the same line, and the padding halves. Nothing is
 * removed: the word, the reason, the worth and the buttons are all still there
 * -- they stop being a title page.
 */
/* THE RULES THAT ANSWER THIS ARE WITH THE REST OF THE JUDGING PAGE'S, far
 * below, and they have to be: a media query adds no specificity, so a phone
 * override for .aar-head written HERE would be silently overruled by
 * .aar-head's own base rule declared later in the sheet. The two selectors
 * that used to sit here named a `.serif` and a `.row.g6` that this header has
 * not held since it was rebuilt -- both matched nothing at any width.
 */
/*
 * ══ THE SERVO-SKULL'S CANVAS ══════════════════════════════════════════════
 *
 * It sits UNDER the phosphor and the bezel (60 and 61), so the screen's own
 * grain falls over it and it reads as being inside the machine rather than
 * pasted on top of it. It takes no pointer events at all -- the rule the whole
 * of this file keeps is that ornament never costs a reader a click.
 */
.skull-host{position:fixed;inset:0;z-index:55;pointer-events:none}

.skull-host canvas{position:absolute;inset:0;width:100%;height:100%}
.skull-dodge{position:absolute;width:160px;height:160px;max-width:none;
  transform:translate(-50%,-50%);pointer-events:none}
.skull-dodge[hidden]{display:none}

/*
 * THE AUSPEX BEAM: A FIELD, AND A CONE THAT THROWS IT.
 *
 * Owner, 2026-09-02: "the beem needs to be wide then scan across something like
 * reading a barcode from top to bottom."
 *
 * So it is not a pointer. The CONE fans from the lens down onto the thing, and
 * the FIELD lies over the thing's own bounds with a bright bar travelling down
 * it -- a document being read, not a laser being aimed. Neither can be baked
 * into the model: both are sized and placed from the target's rectangle, which
 * nothing knows until the moment it is chosen.
 */
.skull-reading,.skull-reading *{
  text-shadow:0 0 2px rgba(158,255,205,.8),0 0 6px rgba(112,232,168,.5),
    0 0 12px rgba(100,220,158,.15)}

.skull-field{position:absolute;left:0;top:0;pointer-events:none;opacity:0;
  transition:opacity .18s ease;overflow:visible}

/* NO BACKGROUND HERE AT ALL. It used to carry the area glow, but this element
   is overflow:hidden and exactly the size of the text -- so a gradient reaching
   260% of its height was cut off flat at the box, which is the square the owner
   saw. All it does now is CLIP the travelling bar. The glow belongs on .skull-
   pool below, which has no clip and room to fade out properly. */
/*
 * IT LIGHTS THE WORDS, NOT A BOX.
 *
 * Owner: "his scanner lights up a square can it light up JUST the text and not
 * the square?" So the field itself is now invisible -- no border, no tint. All
 * it does is CLIP the travelling bar to the glyph bounds.
 *
 * The bar carries a backdrop-filter, which brightens whatever is BEHIND it
 * rather than painting over it. On this page the ground is nearly black and the
 * words are pale, so raising the brightness of everything under the bar shows up
 * almost entirely on the letters -- the text lights as the bar passes and the
 * background barely moves. mix-blend-mode cannot do this: the canvas is a fixed,
 * z-indexed layer and so an isolated stacking context, and a blend would only
 * ever reach the other ornaments inside it.
 */
.skull-field::after{content:"";position:absolute;left:0;right:0;height:13px;
  top:var(--scan,0%);margin-top:-6px;
  /*
   * SOFT ON ALL FOUR SIDES, AND IT HAS TO BE THE MASK THAT DOES IT.
   *
   * Owner: "but it looks like a square." It did, and the background gradient
   * was never going to fix it: BACKDROP-FILTER BRIGHTENS THE WHOLE OF AN
   * ELEMENT'S BOX, evenly, and a background only tints on top of that. So the
   * colour faded at the ends while the BRIGHTENING stopped dead -- a hard
   * rectangle of lit page with a soft-coloured bar sitting inside it.
   *
   * A mask does modulate the backdrop-filter, so the fade has to live there.
   * Two of them, intersected: one across the bar and one along it, which takes
   * the brightening itself to nothing on every edge.
   */
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 38%,#000 62%,transparent),
                     linear-gradient(90deg,transparent,#000 18%,#000 82%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 38%,#000 62%,transparent),
             linear-gradient(90deg,transparent,#000 18%,#000 82%,transparent);
  -webkit-mask-composite:source-in;
  mask-composite:intersect;
  backdrop-filter:brightness(2.9) saturate(1.5) hue-rotate(-18deg);
  -webkit-backdrop-filter:brightness(2.9) saturate(1.5) hue-rotate(-18deg);
  background:linear-gradient(90deg,
    transparent, rgba(170,255,205,.22) 14%, rgba(170,255,205,.22) 86%, transparent);
  box-shadow:0 0 16px rgba(130,255,190,.45)}

/* ACROSS, NOT DOWN. A row of a table is read top to bottom; a line of text is
   read left to right, so the bar turns ninety degrees and travels the other
   way. Same field, same cone, different grain. */
/*
 * BURNING IT IN, rather than reading it.
 *
 * Owner: "i would LOVE for the part of the quote that gets written following
 * the scanner it glows a tiny bit like it was burned into the background maybe
 * even with a tiny bit of smoke TINY bit. but like he burns it in leaving the
 * text?"
 *
 * So the two halves of a quote change no longer look alike. ERASING keeps the
 * cold green auspex light. WRITING runs the same bar hot -- the backdrop-filter
 * pushes brightness far harder and swings the hue toward ember, so the letters
 * flare orange-white exactly as they appear.
 *
 * ::before is the COOLING TRAIL: everything already written, tinted warm and
 * falling off with distance from the edge, so what he laid down a moment ago is
 * still glowing faintly and what he wrote first has gone cold.
 */
/*
 * THE AUSPEX PANEL IS WRITTEN, NOT FOUND.
 *
 * Its body is clipped away from the moment a report opens and drawn back in
 * left to right under the beam, so the finding appears as the skull reads it.
 * The class is only ever added by skull.js, and only when he is actually coming
 * -- so with no skull loaded (a phone, reduced motion, no WebGL, or simply no
 * ?skull=1) this rule never applies and the panel is just there, as it always
 * was. An ornament may not be the reason a reader cannot see a finding.
 */
/* IT WRITES IN THE FINDINGS, NOT THE RECORD. This used to name `> .col`, which
 * was the auspex panel's only body. THE RECORD carries three bodies now -- the
 * table, the findings and the override footer -- and `> .col` would clip all
 * three, so the six fields a sergeant opened the report to read would be blank
 * for the length of a flight. The findings are the part the skull actually
 * produces, so the findings are the part it draws in. .auspex-say is a direct
 * child of the plate: the sc-if around it compiles to a Fragment and emits no
 * box, so `>` passes straight through it. */
.aar-auspex.skull-writing > .auspex-say{clip-path:inset(0 var(--aw,0%) 0 0)}

/*
 * THE SWITCH, AND WHAT HAPPENS WHEN YOU THROW IT.
 *
 * A small brass plate above the bottom-right footer with a lamp in it: lit while he is
 * about, dark when he is not. It is always present even when he is not loaded
 * at all, because it is the only way back.
 */
.skull-toggle{position:fixed;right:14px;z-index:62;
  bottom:calc(72px + env(safe-area-inset-bottom,0px));
  display:flex;align-items:center;gap:7px;cursor:pointer;flex:none;white-space:nowrap;
  padding:5px 9px 5px 7px;border:1px solid var(--gold-line);
  background:linear-gradient(180deg,rgba(28,36,31,.94),rgba(14,20,17,.94));
  clip-path:polygon(7px 0,100% 0,100% 100%,0 100%,0 7px);
  font-family:var(--mono);font-size:var(--fs-1);letter-spacing:var(--ls-2);
  text-transform:uppercase;color:var(--ink-4);
  box-shadow:inset 0 1px 0 rgba(214,196,150,.14), 0 2px 10px rgba(0,0,0,.5);
  transition:color .18s ease, border-color .18s ease, box-shadow .18s ease}

.skull-toggle:hover{color:var(--gold-warm);border-color:var(--gold);
  box-shadow:inset 0 1px 0 rgba(214,196,150,.2), 0 0 14px rgba(180,143,50,.28)}

/* The lamp. Lit is a live thing; dark is a glow that has gone out. */
.skull-toggle i{width:8px;height:8px;border-radius:50%;flex:none;
  background:#2A342E;box-shadow:inset 0 0 3px rgba(0,0,0,.7);
  transition:background .25s ease, box-shadow .25s ease}

.skull-toggle.on i{background:#7BE8A8;
  box-shadow:0 0 7px rgba(123,232,168,.9), 0 0 16px rgba(123,232,168,.45)}

.skull-toggle.on{color:var(--gold-warm)}

@media (max-width: 999px){
.skull-toggle{display:none}
}

/*
 * HITTING THE GLASS. A ring thrown out from the point of impact and a brief
 * bloom over the whole page -- the only moment anything on this site flashes,
 * which is what makes it land. Both are gone inside half a second.
 */
/* ==========================================================================
   THE TEAR -- where he comes through the gallery.

   Owner: "when looking at a screenshot and the screenshot gallery is on top of
   the plane where he is can we have him rip through the plane on one of the
   edges so h[e] doesnt hit a screenshot make a tear in the gallery that there
   until we close the gallery then have him continue his scans on that plane?"

   The lightbox is z-index 70 and he lives at 55, so a screenshot genuinely does
   bury him. Rather than raise him quietly through it -- which would look like a
   z-index change, because it is one -- he goes to an EDGE, opens the overlay
   like a sheet, and climbs out onto the front of it. The rip stays open behind
   him the whole time the gallery does, so there is always a reason he is up
   there.

   It is a real gap and not a drawn one: the fill is darker than the overlay
   above it and carries the page's own ground, so what shows through the slit
   reads as the room behind the sheet. The jagged edges are generated per tear
   in skull.js, so no two rips are the same shape.
   ========================================================================== */
/* ==========================================================================
   THE HOLE HE CAME THROUGH.

   Owner: "make it look like glass instead of a tear w[i]th a silouette of him
   left behind and some cracks eminating from the silouette."

   Two attempts at a TEAR failed the same way, and the second time I wrote down
   why without acting on it: a hole in a dark sheet over a dark room has nothing
   to show. The gallery backdrop is rgba(4,8,7,.94) and the page beneath it is
   nearly black, so there is no contrast to be had from a gap however it is
   shaded.

   Glass has no such problem. Cracks are LINES, and a line is visible against
   anything -- so the break carries its own legibility instead of borrowing it
   from whatever is behind. The silhouette is the shape he left in it, and the
   fractures run out from that shape rather than from a point, which is what
   says something went THROUGH rather than hit it.
   ========================================================================== */
.skull-glass{position:fixed;z-index:71;pointer-events:none;
  opacity:0;transition:opacity .3s ease;
  filter:drop-shadow(0 0 6px rgba(200,240,255,.55))
         drop-shadow(0 0 20px rgba(120,220,180,.3))}

.skull-glass.open{opacity:1;transition:none}

@media (prefers-reduced-motion:reduce){
.skull-glass{transition:none}
}

.skull-crack{position:fixed;z-index:59;pointer-events:none;opacity:0;
  border:2px solid rgba(214,236,224,.85);border-radius:50%;
  box-shadow:0 0 22px rgba(190,255,220,.5), inset 0 0 18px rgba(190,255,220,.3)}

.skull-crack.hit{animation:crackout .5s cubic-bezier(.15,.7,.3,1) forwards}

.skull-bloom{position:fixed;inset:0;z-index:58;pointer-events:none;opacity:0;
  background:radial-gradient(circle at var(--hx,50%) var(--hy,50%),
    rgba(226,255,238,.30) 0%, rgba(150,230,190,.10) 26%, transparent 58%)}

.skull-bloom.hit{animation:bloomout .42s ease-out forwards}

@media (prefers-reduced-motion: reduce){
.skull-crack,.skull-bloom{display:none}
}

/*
 * SPARKS. Struck off him when the cursor knocks into him -- bright, small, and
 * gone in under a second. Thrown from the render loop rather than by a keyframe,
 * because they have to leave from wherever he was hit.
 */
.skull-spark{position:absolute;left:0;top:0;width:3px;height:3px;pointer-events:none;
  opacity:0;border-radius:50%;
  background:radial-gradient(circle, #FFF6DC 0%, #FFC96A 45%, rgba(255,140,40,.5) 75%, transparent 100%);
  box-shadow:0 0 6px rgba(255,190,90,.9)}

/* A SHARD, NOT AN EMBER. The ember above is right for a knock and wrong for a
   window: glass carries no heat, so it is pale rather than amber, and it is a
   splinter rather than a dot -- longer than it is wide, lit hard along its
   length, and turning as it flies (skull.js gives each one its own spin). */
.skull-spark.glassy{width:9px;height:2.5px;border-radius:1px;
  background:linear-gradient(90deg,
    rgba(214,255,248,0) 0%, #FFFFFF 32%, #E6FFFA 62%, rgba(150,235,255,0) 100%);
  box-shadow:0 0 9px rgba(214,250,255,.95), 0 0 20px rgba(140,230,255,.5)}

/* AN ARC OFF A CUT CABLE. Owner: "the sparks are weak i wanna see big blue...
   then see the explosion".

   The ember above is a hot fleck struck off metal -- amber, 3px, right for a
   knock and wrong for this. Mains going to earth is not hot, it is BRIGHT: a
   white core that reads as overexposed, a hard cyan corona around it, and a
   blue glow thrown far enough to light the casing it is coming from. Four
   times the ember's size and three shadows deep, because the whole point is
   that you see it from behind a machine that is hiding the thing making it. */
.skull-spark.arc{width:12px;height:12px;
  background:radial-gradient(circle, #FFFFFF 0%, #DFF6FF 22%, #6FD4FF 46%,
    rgba(60,150,255,.55) 72%, transparent 100%);
  box-shadow:0 0 14px rgba(150,225,255,1), 0 0 34px rgba(80,180,255,.8),
    0 0 68px rgba(40,120,255,.45)}

@media (prefers-reduced-motion: reduce){
.skull-spark{display:none}
}

/*
 * THE CRACK. Real radiating fractures, drawn fresh each time so no two impacts
 * look alike, held for a moment and then fading out over a few seconds -- glass
 * that stayed cracked would become part of the furniture, and this is meant to
 * be something that just happened.
 */
.skull-shatter{position:fixed;z-index:59;pointer-events:none;
  opacity:0;filter:drop-shadow(0 0 4px rgba(200,240,255,.5))}

.skull-shatter.hit{animation:shatterfade 4.6s ease-out forwards}

@media (prefers-reduced-motion: reduce){
.skull-shatter{display:none}
}

/*
 * THE DAY HE FLIES TOO NEAR THE CANDLES.
 *
 * Owner: "you think randomly he can get too clos to the cangles and one of his
 * feet could catch fire then have him rush off the page with a smoke trail
 * behind him?"
 *
 * Rarely -- about one errand in fourteen -- he goes down to look at the candles
 * burning along the bottom of the page, gets too close, and one of them takes.
 * Then he bolts for the nearest edge trailing smoke, and comes back a few
 * seconds later from somewhere else, having learnt nothing.
 *
 * It is the only thing on this page that happens TO him rather than being
 * something he does, which is why it is worth having: a thing that can be
 * caught out is a thing with a life of its own.
 */
/*
 * WHAT POSITIONS IT CANNOT BE WHAT ANIMATES IT.
 *
 * Owner: "theres a flame at the top left of the screen i was wondering what
 * that was its ALWAYS at the top left of the screen..."
 *
 * Mine, and the same mistake twice over. The outer element carried opacity:0
 * AND ran `flicker` -- which sets opacity -- so it was lit from the moment the
 * page loaded. Worse, `flicker` also sets TRANSFORM, and an animation beats an
 * inline style just as surely, so the per-frame `translate(...)` that puts the
 * flame on the skull was being thrown away every frame. The flame has never
 * once appeared on him; it has always been that corner flame.
 *
 * So the two jobs are separated for good. The OUTER element is nothing but a
 * position -- no background, no animation, nothing a keyframe can reach. The
 * INNER one is the flame and does all the moving. Neither can overwrite the
 * other's work.
 */
.skull-fire{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;
  opacity:0;visibility:hidden;transition:opacity .12s ease}

.skull-fire.lit{opacity:1;visibility:visible}

.skull-fire > i{position:absolute;left:0;top:0;width:46px;height:72px;
  margin:-39px 0 0 -23px;display:block;
  border-radius:50% 50% 45% 45% / 62% 62% 38% 38%;
  background:radial-gradient(ellipse at 50% 74%,
    #FFF6DA 0%, #FFDC8C 22%, #F49C2C 50%, rgba(206,92,20,.62) 76%, transparent 100%);
  animation:flicker .3s ease-in-out infinite}

/* IT THROWS LIGHT, the way the candles along the bottom of the page do -- their
   warmth comes from a halo rather than a box-shadow, because a shadow is a hard
   ring at whatever radius it is given and a radial gradient falls off, and
   falling off is the whole of what makes light look like light. Owner: "he needs
   to look ON FIRE" -- so it is one he is inside, not one sitting on him. */
.skull-fire > i::after{content:"";position:absolute;left:50%;top:50%;
  width:255px;height:232px;margin:-116px 0 0 -127px;border-radius:50%;
  background:radial-gradient(circle,
    rgba(255,220,146,.50) 0%, rgba(255,182,80,.30) 22%,
    rgba(252,144,40,.16) 42%, rgba(226,100,20,.06) 64%, transparent 80%)}

/* A tighter core on its own timing, so the middle churns rather than pulsing as
   one shape. */
.skull-fire > i::before{content:"";position:absolute;left:50%;top:58%;
  width:39px;height:46px;margin:-23px 0 0 -19px;
  border-radius:50% 50% 45% 45% / 60% 60% 40% 40%;
  background:radial-gradient(ellipse at 50% 70%,
    #FFFBEC 0%, #FFE9A8 30%, rgba(255,190,90,.55) 62%, transparent 100%);
  animation:flicker .43s ease-in-out infinite reverse}

/* The trail. Each puff is placed and faded from the render loop rather than by
   a keyframe, because they have to be left BEHIND him along a path nobody knows
   in advance. */
.skull-puff{position:absolute;left:0;top:0;width:15px;height:15px;pointer-events:none;
  opacity:0;border-radius:50%;filter:blur(4px);
  /* Owner: "leave brighter white smoke behind him and double the amount". Near
     white rather than the grey it was, because smoke off a burning thing on a
     near-black page has to be pale to read at all. */
  background:radial-gradient(circle, rgba(246,246,242,.85) 0%, rgba(226,224,216,.35) 45%, transparent 72%)}

@media (prefers-reduced-motion: reduce){
.skull-fire,.skull-puff{display:none}
}

/* THE SMOKE, and it has to stay tiny -- the owner said so twice. Three puffs on
   deliberately unrelated timings, drifting up off the burn line and gone in
   under two seconds. Any more and it stops being a wisp and starts being a
   fire. */
.skull-smoke{position:absolute;width:9px;height:9px;pointer-events:none;
  opacity:0;border-radius:50%;filter:blur(3px);
  background:radial-gradient(circle, rgba(214,210,200,.34) 0%, transparent 72%)}

.skull-smoke.on{opacity:1}

.skull-smoke::before,.skull-smoke::after{content:"";position:absolute;inset:0;
  border-radius:50%;background:inherit;
  animation:smokeup 1.7s ease-out infinite}

.skull-smoke::after{animation-duration:2.3s;animation-delay:-.9s}

@media (prefers-reduced-motion: reduce){
.skull-smoke{display:none}
}

/*
 * THE BEAM, REBUILT AS LIGHT RATHER THAN AS A SHAPE.
 *
 * Owner: "the 'scanning area' with the lines is not great can you rethink that
 * and make it look more realistic??"
 *
 * The old one was a filled trapezoid with two drawn lines down its sides, and
 * that is the problem in one sentence: hard geometric edges read as vector art,
 * because nothing in the physical world has them. Light from a lens is brightest
 * at the lens, spreads at an angle, softens at its edges where the aperture
 * blurs it, and dims with distance.
 *
 * So it is a CONIC GRADIENT struck from the lens itself -- a true angular wedge,
 * with the two edges falling off over several degrees instead of stopping dead.
 * A radial mask over the top takes the brightness down with distance travelled.
 * No clip-path, no drawn lines, nothing with a border.
 *
 * The angles are computed per frame from the lens to the first and last letters,
 * so the wedge still lands exactly on the words -- it just no longer announces
 * where it stops.
 */
.skull-cone{position:absolute;left:0;top:0;pointer-events:none;opacity:0;
  transition:opacity .2s ease;
  background:conic-gradient(from var(--from,170deg) at var(--ax,50%) 0%,
    transparent 0deg,
    rgba(150,255,196,.05) var(--soft,4deg),
    rgba(150,255,196,.20) calc(var(--soft,4deg) * 2),
    rgba(150,255,196,.20) calc(var(--span,20deg) - var(--soft,4deg) * 2),
    rgba(150,255,196,.05) calc(var(--span,20deg) - var(--soft,4deg)),
    transparent var(--span,20deg));
  /* IT FADES OUT BEFORE IT LANDS, rather than stopping on a line.
  
     Owner: "theres this sharp line at the top of the scan too where his beam
     ends on the verticlt bar if that could just be a fade that would be
     perfect."
     
     The element is exactly as tall as the gap between the lens and the words,
     so the gradient simply ran out at the bottom edge and left a hard
     horizontal cut across the beam. The radial mask above was supposed to take
     care of distance, but its circle is sized to the FARTHEST CORNER of the
     box -- about 400px on a wide, short wedge -- so at the bottom edge, 130px
     down, it is still near full strength.
     
     A second mask runs along the beam instead of out from its apex, and takes
     it to nothing over the last fifth. The light thinning as it arrives is also
     what a beam does; the pool underneath is what actually lights the words. */
  -webkit-mask-image:radial-gradient(circle at var(--ax,50%) 0%,
    rgba(0,0,0,.95) 0%, rgba(0,0,0,.55) 55%, rgba(0,0,0,0) 100%),
    linear-gradient(180deg,#000 0,#000 52%,rgba(0,0,0,.55) 80%,rgba(0,0,0,0) 100%);
  mask-image:radial-gradient(circle at var(--ax,50%) 0%,
    rgba(0,0,0,.95) 0%, rgba(0,0,0,.55) 55%, rgba(0,0,0,0) 100%),
    linear-gradient(180deg,#000 0,#000 52%,rgba(0,0,0,.55) 80%,rgba(0,0,0,0) 100%);
  -webkit-mask-composite:source-in;
  mask-composite:intersect}

/* Quote light reaches the lettering before fading. Combining both distance
   masks erased the end of this beam above the dark footer. */
.skull-field.across ~ .skull-cone{
  -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,.95) 0%,rgba(0,0,0,.7) 82%,transparent 100%);
  mask-image:linear-gradient(180deg,rgba(0,0,0,.95) 0%,rgba(0,0,0,.7) 82%,transparent 100%)}

.skull-cone.burn{
  background:conic-gradient(from var(--from,170deg) at var(--ax,50%) 0%,
    transparent 0deg,
    rgba(255,190,120,.06) var(--soft,4deg),
    rgba(255,190,120,.24) calc(var(--soft,4deg) * 2),
    rgba(255,190,120,.24) calc(var(--span,20deg) - var(--soft,4deg) * 2),
    rgba(255,190,120,.06) calc(var(--span,20deg) - var(--soft,4deg)),
    transparent var(--span,20deg))}

/*
 * WHERE IT LANDS: A POOL, AND A ROUND ONE.
 *
 * Owner: "make the square in the background on the slement a little more round
 * and gradient though."
 *
 * The box is now much larger than the words it sits under, and the gradient is
 * finished well inside it -- transparent by 74% of a 42% radius -- so the light
 * has faded to nothing long before it reaches any edge. That is the whole trick
 * to a soft glow: it is not the gradient that makes a square, it is the gradient
 * running out of room. Four stops rather than two, so the falloff is a curve
 * instead of a ramp.
 */
.skull-pool{position:absolute;left:0;top:0;pointer-events:none;opacity:0;
  transition:opacity .2s ease;
  background:radial-gradient(ellipse 42% 44% at 50% 50%,
    rgba(158,255,205,.26) 0%,
    rgba(136,246,190,.15) 30%,
    rgba(112,232,168,.06) 52%,
    rgba(100,220,158,.02) 66%,
    transparent 74%)}

.skull-pool.burn{background:radial-gradient(ellipse 42% 44% at 50% 50%,
    rgba(255,206,150,.30) 0%,
    rgba(255,182,112,.17) 30%,
    rgba(255,158,74,.07) 52%,
    rgba(240,140,60,.02) 66%,
    transparent 74%)}

/* ACROSS, NOT DOWN. A row of a table is read top to bottom; a line of text is
   read left to right, so the bar turns ninety degrees and travels the other
   way. Same field, same cone, different grain. */
/* THE BAR TURNED ON ITS SIDE NEEDS ITS MASK TURNED WITH IT.
   
   .skull-field::after above is a HORIZONTAL bar, and its mask fades the ends
   with a 90deg gradient and the long edges with a 180deg one. Standing that bar
   up for a quote without redefining the mask leaves a vertical 5px sliver
   wearing a horizontal bar's fade: the 90deg pass takes it to nothing at 18%
   and 82% of FIVE PIXELS, and the 180deg pass -- meant to soften the long edges
   -- instead cuts the travelling line off flat at top and bottom. The bar is
   almost erased across its width and hard-edged along its length, which is
   backwards on both axes.
   
   And the mask modulates the backdrop-filter, so this is not a tint going
   wrong: it is the BRIGHTENING that stops dead, which is what shows up as an
   artifact against the words rather than as a softer bar.
   
   One gradient, running the length of the bar, so its two ends fade out. The
   width is 5px and needs no fade across it. */
.skull-field.across::after{left:var(--scan,0%);top:0;bottom:0;right:auto;
  width:5px;height:auto;margin-top:0;margin-left:-2px;
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 12%,#000 88%,transparent);
  background:linear-gradient(180deg,
    transparent, rgba(170,255,205,.22) 14%, rgba(170,255,205,.22) 86%, transparent)}

/*
 * BURNING IT IN, rather than reading it.
 *
 * Owner: "i would LOVE for the part of the quote that gets written following
 * the scanner it glows a tiny bit like it was burned into the background maybe
 * even with a tiny bit of smoke TINY bit. but like he burns it in leaving the
 * text?"
 *
 * So the two halves of a quote change no longer look alike. ERASING keeps the
 * cold green auspex light. WRITING runs the same bar hot -- the backdrop-filter
 * pushes brightness far harder and swings the hue toward ember, so the letters
 * flare orange-white exactly as they appear.
 *
 * ::before is the COOLING TRAIL: everything already written, tinted warm and
 * falling off with distance from the edge, so what he laid down a moment ago is
 * still glowing faintly and what he wrote first has gone cold.
 */
.skull-field.burn::after{
  backdrop-filter:brightness(3.6) saturate(2.4) sepia(.5) hue-rotate(-30deg);
  -webkit-backdrop-filter:brightness(3.6) saturate(2.4) sepia(.5) hue-rotate(-30deg);
  background:linear-gradient(180deg,
    transparent, rgba(255,200,130,.34) 14%, rgba(255,200,130,.34) 86%, transparent);
  box-shadow:0 0 18px rgba(255,170,80,.55), 0 0 42px rgba(255,120,40,.22)}

.skull-field.burn::before{content:"";position:absolute;top:0;bottom:0;left:0;
  width:var(--scan,0%);pointer-events:none;
  background:linear-gradient(90deg,
    rgba(255,150,60,0) 0%, rgba(255,150,60,.05) 70%, rgba(255,186,110,.16) 100%)}

/*
 * THE SERVO-SKULL, and what it is allowed to say.
 *
 * Owner: "how fucking awesome would it be to have a servo skull floating
 * around the page? small but visible. when you load up a report the servo
 * skull gives it a quick scan and says auspex clean or whatever the 40k lingo
 * would be?"
 *
 * A servo-skull is a dead servant's skull on anti-grav impellers, running one
 * task without judgement -- scouting ahead and reporting what it sees. That is
 * exactly what the reader does, so the skull is the reader's face and the
 * auspex is what it hands you.
 *
 * IT REPORTS, IT DOES NOT DECIDE. The sweep is theatre over an answer that was
 * worked out on a GPU at the owner's house hours earlier; nothing about what
 * it says is invented while it flies. If the ledger holds no reading it says
 * so rather than playing an animation and landing on "clear".
 *
 * AND IT HOLDS STILL FOR ANYONE WHO ASKS. A thing that bobs forever in the
 * corner of a screen somebody is trying to read is a cost, not a flourish --
 * particularly for the readers this site is actually for. prefers-reduced-
 * motion stops every part of it and leaves the verdict, which is the half that
 * carries the meaning.
 */
/* The drawn skull's own styles went with the drawing. */
/* The beam it drops while it reads, across the panel it is reading. */
.skull-beam{position:absolute;left:0;right:0;top:0;height:2px;
  background:linear-gradient(90deg,transparent,var(--good),transparent);
  box-shadow:0 0 12px var(--good);
  animation:skullbeam 1.15s ease-in-out;pointer-events:none}

@media (prefers-reduced-motion: reduce){
.skull-beam{ display: none }
}

/* ==========================================================================
   § 2 · THE FOURTEEN TOKENS ledger.css NEVER DECLARED.

   The old sheet's :root carries 103 custom properties and ledger.css's own
   103 cover all but these. They are read by rules in § 3 and by nothing in
   ledger.css, so they come across at the old sheet's values. Anything the two
   sheets BOTH declare is deliberately absent from this block: where the
   redesign has an opinion about --gold or --panel, the redesign wins, which
   is the whole point of cutting over.
   ========================================================================== */

:where(.ledger){
  --cast-1:0 10px 22px -12px rgba(0,0,0,.85),0 2px 5px -2px rgba(0,0,0,.5);
  --cast-2:0 16px 34px -16px rgba(0,0,0,.9),0 3px 8px -3px rgba(0,0,0,.55);
  --con-b:0px;
  --con-r:calc(var(--s9) * 6);
  --con-w:131px;
  --face-1:linear-gradient(180deg,rgba(60,92,72,.16),rgba(0,0,0,.20));
  --hair-3:rgba(120,170,140,.1);
  --ink-cool:#C4D0C4;
  --lift-hard:rgba(92,66,26,.44);
  --lip:inset 0 1px 0 rgba(228,240,226,.07);
  --sink-1:inset 0 1px 3px rgba(0,0,0,.5);
  --sink-2:inset 0 2px 8px rgba(0,0,0,.6);
  --track:rgba(24,44,34,.5);
  --ui-page:1;
}

/* ==========================================================================
   § 3 · THE ORPHANS — 375 class names, 517 rules, in the old sheet's order.

   Source order is preserved exactly, because a media query adds no
   specificity: a phone override written before the base rule it overrides is
   silently lost, and the old sheet interleaves the two. Re-sorting this
   section by component would break screens at 500px and nowhere else.

   Being loaded after ledger.css, a rule here wins a tie against it. That is
   intended and it is narrow: a carried selector always names at least one
   class ledger.css has never heard of, so the only ties it can win are inside
   an orphan component — `.trace .btn` tuning a button that only the trace
   sheet contains.
   ========================================================================== */

/* ==========================================================================
COMPONENTS — declared BEFORE the utilities so a utility can adjust one.
========================================================================== */
.panel:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--panel)}

/* THE SAME MATERIAL AS A PLATE. A card and a banner were iron with a gold
   edge and no bevel, sitting beside plates that had one -- so two boxes a few
   pixels apart caught the light differently for no reason anybody chose. They
   take the plate's lit top lip and dark bottom now. Their gold border stays:
   a card and a banner CARRY something (a notice, a rule you can edit), and the
   plate's quieter --line-3 edge is for a panel that merely holds a section. */
.card:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border:1px solid var(--gold-line);background:var(--panel-2);
padding:var(--s6) var(--s7);display:flex;flex-direction:column;gap:var(--s5);
box-shadow:var(--bevel)}

/* ==========================================================================
   FORGE -- the one inline edit.

   Three of the banners on this site were the same thing spelled three times:
   `col g4 banner r1 bd-gold danim` with a 300ms entrance, opened under a
   control to edit the thing above it. A sergeant met that shape three times
   and had to learn it three times.

   ORNAMENT AUDIT
     left rail + faint ember  STATE  this, and only this, is being edited.
                              EDITORS_SHUT already guarantees that at the
                              level of state; this is the same sentence said
                              where the reader can see it.
     the 220ms entrance       STATE  it appeared just now, under your hand.
                              It is the only motion in the shared layer, it
                              plays once, and it is never on something being
                              read -- only on the thing you just opened.
   It reuses the sheet's existing `enter` keyframe rather than adding one.
   ========================================================================== */
.forge:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;display:flex;flex-direction:column;gap:var(--s4);
padding:var(--s5) var(--s6);
border:1px solid var(--gold-line);border-left:2px solid var(--gold);
border-radius:var(--r1);
background:var(--panel-3);
box-shadow:var(--bevel),0 0 24px -14px var(--glow-live);
animation:enter var(--dur-in) var(--ease-settle)}

/* A menu that floats over the page: the roster's rank and operative pickers,
and anything else that has to sit above a row without being part of it. */
.pop:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;z-index:30;top:100%;left:0;max-height:260px;overflow:auto;
border:1px solid var(--gold);border-radius:var(--r1);background:var(--pop);
box-shadow:0 14px 40px -10px rgba(0,0,0,.9);padding:var(--s3);
display:flex;flex-direction:column;gap:var(--r2)}

/* A ROW OF A MENU. Laid out rather than run as text because half of them
   carry something on the right -- a count, a worth -- and the mark that says
   which one is set needs a column of its own so the words below it line up
   with the words above. */
.pop-item:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;gap:var(--s3);
cursor:pointer;padding:5px var(--s4);font-size:var(--fs-5);color:var(--ink-cool)}

.pop-item:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(27,48,36,.8);color:var(--ink)}

.pop-on:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold)}

.pop-mark:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:none;width:9px;font-size:var(--fs-2);color:var(--gold)}

.account-avatar:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;overflow:hidden}

.account-avatar:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:var(--bg)}

/* THE PLACES STAND APART. Owner, on the strategium's company beside the
   judging page: "give it some nice seperation and such to look like this."
   What that page does is not more ink -- it is air. A fireteam's groups carry
   a margin above them and its men sit in cards with real gaps, so YOURS TO
   JUDGE and OTHER SQUADS read as two things rather than as one list with a
   line in it. This aside had ten pixels above a place and two between its men,
   which is a single dense block however many rules run through it.
   The rule stays and the space around it grows. */
.cmd-tier:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:baseline;gap:var(--s4);
  padding:var(--s5) 0 var(--s2);margin-top:var(--s6);
  border-bottom:1px solid var(--hair)}

.cmd-tier:first-child:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-top:0}

/* NO RULE UNDER A MAN HERE, AND THIS IS THE THIRD TIME THAT ANSWER WAS WRONG.
   A hairline was put under each name to "get some seperation here like in the
   reports", and the result was lined paper: a place head already carries a
   rule, so a company of one-man places became heading-rule, name-rule,
   heading-rule, name-rule down the whole column. Owner: "please fix this!!"

   THE PATTERN, WRITTEN DOWN SO IT STOPS REPEATING. cbeea05 ruled the muster's
   rows and made a spreadsheet. This ruled a company's men and made a ledger.
   Both times the request was for SEPARATION and both times ink was the wrong
   currency for it. What the judging page does -- the page the owner keeps
   pointing at -- is air: a margin above each group and a real gap between
   cards, and its only hairline is the one beside a GROUP heading.

   So separation here is the gap above a place and the gap between its men, and
   the single rule under the place head. Nothing else. */
/* AND THE RHYTHM IS THE RECORD'S. Owner, pointing at the judging page: "needs
   the right spacing." What that panel actually does is give a row ROOM --
   .record-row pays --s5 top and bottom and .record-grid holds a 34px floor --
   and its rule is rgba(178,201,177,.05), half the strength of --hair, which is
   why you have to look for it rather than at it.

   A man here was 4px of padding in a 24px line, which is why nothing but ink
   ever looked like separation. He gets the record's floor instead. No rule:
   with a ruled place head above him, even a .05 hairline reads as lined paper
   at this column width. */
.cmd-aside:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .rail-sm{padding:var(--s4) var(--s3);min-height:calc(34px * var(--ui))}

.cmd-tier-name:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-3);
  letter-spacing:var(--ls-2);text-transform:uppercase;color:var(--gold-dim);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

.hair-b:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-bottom:1px solid var(--hair)}

.hair-t:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-top:1px solid var(--hair)}

.hair-r:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-right:1px solid var(--line)}

.rule-b:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-bottom:1px solid var(--rule)}

.notch:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){clip-path:polygon(13px 0,100% 0,100% 100%,0 100%,0 13px)}

.notch-sm:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){clip-path:polygon(7px 0,100% 0,100% 100%,0 100%,0 7px)}

.topbar-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){gap:var(--s6);padding-top:var(--s6);padding-bottom:var(--s6);row-gap:0}

@media (min-width:1000px){
.topbar-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){gap:30px}
}

/* The band of big numbers at the top of a tab. Owner liked it on the roster
and asked for it on reports and statistics, so it is one shape in one place
rather than three that drift. */
.totals:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;flex-wrap:wrap;border-bottom:1px solid var(--line)}

.total:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:1;min-width:130px;padding:var(--s6) var(--s7);display:flex;
flex-direction:column;gap:5px;border-right:1px solid var(--line)}

.total:last-child:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-right:none}

.total-n:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-9);font-weight:500;
font-variant-numeric:tabular-nums}

/* ---- controls ---- */
.act:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){cursor:pointer;transition:background var(--t),box-shadow var(--t),
color var(--t),border-color var(--t)}

/* AND THE 860px QUERY THAT WAS HERE COULD NEVER FIRE. This tab is in
   DESKTOP_ONLY and the strip is gated on `wide = s.w >= 1000`, so the
   narrowest width at which the screen exists AT ALL is 1000. Everything below
   it was dead CSS pretending to be responsive. */
/* FOUR BROTHERS SIDE BY SIDE. Owner, 2026-09-12: "infact be able to open up 4
   people at once and see EVERYTHING they did and everything dont to them and
   by who."

   AS MANY COLUMNS AS THERE ARE MEN, supplied by the markup as --dn, the way
   .grid-hdr takes --dcols. Not auto-fit: two men asked for must be two wide
   squares and not two narrow ones beside two empty tracks.

   AND NOT ONE MEDIA QUERY, which is a correction. The tab is in DESKTOP_ONLY
   and its strip is gated on `wide = s.w >= 1000`, so the 860px rule directly
   above this can never fire on this screen -- it was written for a width that
   cannot reach the tab. The real narrow case IS 1000px, where four panes are
   about 230px each (about 340px at 1440), and that is why a pane holds a rail
   and a count of rungs rather than a ladder: nothing here needs a breakpoint
   because the narrowest live case is the case it is drawn for. */
.panes:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:repeat(var(--dn),minmax(0,1fr));
gap:var(--s5);align-items:start}

/* ======================================================== TRACE A BROTHER --
   Owner, 2026-09-12, on the audit log exactly as it shipped: "yeah as i thought
   nothing but text shit everywhere i have no idea what the fyuck im looking
   at." Then what he actually wanted, in his own words: "this needs to be member
   related not fucking role or action related", "I need to know what heppened to
   THIS GUY!! who dhd THIUS GUY effect?", "and when i see what what done to him
   i want to see by who?!? i need ot be able to trace through events."

   TWO COLUMNS, AND THE LEFT ONE IS PEOPLE. 268px is the narrowest column that
   holds a face, a name of the length this guild really uses -- "Grand Master
   Chaplain Sapphon | Legion" is 38 characters -- and a count; the right column
   takes the rest and its two panels split that between them.

   AND NOT ONE MEDIA QUERY BELOW 1000px. The tab is in DESKTOP_ONLY and the
   strip is gated on `wide = s.w >= 1000`, so the narrowest window that can
   reach this screen at all is 1000 -- I shipped an 860px breakpoint for this
   very tab once that could never fire. At 1000 the two panels are about 330px
   each, which is the width the rows below are drawn for. */
.trace:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:268px minmax(0,1fr);
gap:var(--s6);align-items:start}

/* THE ROLL STAYS WHERE IT IS AND THE LIST INSIDE IT SCROLLS. Sticking the
   whole plate would take the search box and the four orders out of reach the
   moment a reader scrolled 572 men, and they are the only way back. */
.trace-roll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:sticky;top:var(--s5)}

.trace-list:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){overflow-y:auto;max-height:64vh;min-height:0;
border-top:1px solid var(--hair)}

/* THE BOX SITS INSIDE THE PLATE'S RIVETS AND NOT ON THEM. .plate has no
   padding of its own and draws four rivets on its ::before at inset 5px, so a
   full-width control flush to its edge is drawn over them. The rows below have
   their own padding and clear it already. */
.trace-roll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .search{margin:var(--s4) var(--s5)}

/* ONE MAN ON THE ROLL. A grid and not a flex row, for the reason .audit-row
   states: a list whose left edge moves per row cannot be scanned. The count
   column is `auto` and sized to its own content -- a fixed column here is how
   "CHANNEL PERMISSIONS CHANGED" came to be painted over a brother's name at
   1440px on the feed. */
.trace-man:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:calc(22px * var(--ui)) minmax(0,1fr) auto;
align-items:center;gap:var(--s4);padding:var(--s3) var(--s5);
border-bottom:1px solid var(--rule);border-left:2px solid transparent}

.trace-man:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--row-alt)}

.trace-man-on:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--panel-on);border-left-color:var(--gold)}

.trace-nm:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--serif);font-size:var(--fs-6);line-height:1.15}

.trace-co:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);letter-spacing:var(--ls-meta);
text-transform:uppercase;color:var(--ink-4)}

/* A MAN WHO HAS GONE AND A BOT ARE MARKED, NEVER DROPPED. House rule: nothing
   is hidden because a man has left -- his file is often the one somebody
   wants. 15 of the 567 have left and 11 are bots (measured against the live
   ledger 2026-09-12 21:15Z; the mock-up he approved was built on an earlier
   sweep that read 572/20/11, which is why the two disagree). */
.trace-gone:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .trace-nm,.trace-bot:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .trace-nm{color:var(--ink-3)}

.trace-gone:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .trace-co{color:var(--bad-lit)}

.trace-ct:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-2);color:var(--ink-4);
text-align:right;white-space:nowrap}

/* 476 OF 572 HAVE NOTHING ON THEM (measured 2026-09-12), so his row is quiet
   rather than a pair of noisy zeroes down two thirds of the list. */
.trace-ct-quiet:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-6)}

/* THE MAN, AND THEN THE MEN HIS LOG NAMES. Owner, typing his own name and
   getting thirteen rows: "how come when i trype revelation i get all this
   shit?" The thirteen are a real answer to a different question, so they are
   banded rather than dropped. It spans all three columns of the row it is
   carried by -- see `band` in traceRoll for why it is not a row of its own --
   and the rule above it is the seam. */
.trace-band:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){grid-column:1/-1;font-family:var(--mono);font-size:var(--fs-1);
letter-spacing:var(--ls-2);text-transform:uppercase;color:var(--ink-5);
padding-bottom:var(--s3);margin-bottom:var(--s3);
border-bottom:1px solid var(--hair)}

/* WHY HE MATCHED, WHEN IT WAS NOT HIS NAME. A man found by a role nobody can
   see on his row reads as a wrong answer. */
.trace-lit:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(196,138,58,.28);color:var(--ink);border-radius:2px;padding:0 1px}

.trace-hit:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);color:var(--gold);
letter-spacing:var(--ls-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* HOW THE ROLL IS ORDERED. Busiest answers "who is being worked on", latest
   answers "what just happened", a-z finds a man already in mind, and roster is
   the order the chapter itself musters in. Four questions, one control. */
.trace-sorts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;border-bottom:1px solid var(--hair)}

.trace-sort:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:1;text-align:center;cursor:pointer;
font-family:var(--mono);font-size:var(--fs-1);letter-spacing:var(--ls-2);
text-transform:uppercase;color:var(--ink-5);padding:var(--s3) var(--s2);
border-right:1px solid var(--rule);background:none}

.trace-sort:last-child:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-right:none}

.trace-sort:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-2)}

.trace-sort-on:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold);box-shadow:inset 0 -2px 0 var(--gold)}

/* FOURTEEN DROPDOWNS, OVER BOTH PANELS AND INSIDE NEITHER. Owner: "filters
   would be rolls server actions etc etc all the filter dropdown you can think
   of", and then, on the two panels: "actually who he evffected and and dont to
   him is perfect." A filter that only applied to half the screen would be a
   filter nobody could trust, so they sit above both. auto-fit rather than a
   fixed count: fourteen controls at 146px wrap to four rows at 1000px and
   three at 1440 without a breakpoint for either. */
.trace-drops:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:repeat(auto-fit,minmax(146px,1fr));
gap:1px;background:var(--hair);border:1px solid var(--line)}

.trace-drop:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--bg);border:none;color:var(--ink-3);
font-family:var(--mono);font-size:var(--fs-2);letter-spacing:var(--ls-1);
padding:var(--s3) var(--s4);cursor:pointer;min-width:0;text-overflow:ellipsis}

.trace-drop:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink)}

.trace-drop:focus:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){outline:1px solid var(--gold);outline-offset:-1px}

/* A SET FILTER SAYS SO. Fourteen controls is exactly the number at which one
   left on by accident becomes "the screen is broken". */
.trace-drop-on:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold);background:var(--panel-on)}

/* TWO PANELS, SIDE BY SIDE AND EQUAL. Owner: "the right side needs to be the
   action", then "actually who he evffected and and dont to him is perfect."
   Neither half is the more important, so neither gets more room. */
.trace-sheet:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
gap:var(--s5);align-items:start}

.trace-side:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:0}

.trace-rail:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){overflow-y:auto;max-height:64vh;min-height:0}

/* THE DAY, ONCE, OVER THE ROWS THAT HAPPENED IN IT. Owner: "need to be able to
   sort by date as well" -- and before that, the fault it fixes: the feed
   printed "09-12" against two hundred consecutive rows, which is the same fact
   two hundred times and no help at all. The rows carry the time only. */
.trace-day:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);letter-spacing:var(--ls-2);
text-transform:uppercase;color:var(--gold-ash);
padding:var(--s5) var(--s6) var(--s2);border-bottom:1px solid var(--rule)}

.trace-ev:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:calc(38px * var(--ui)) minmax(0,1fr);
gap:var(--s4);align-items:start;padding:var(--s4) var(--s6);
border-bottom:1px solid var(--rule)}

.trace-ev:last-child:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-bottom:none}

/* A KICK, A BAN AND A TIMEOUT GET THE EDGE. Those three are what a commander
   is scanning a wall of nicknames for; a rename is not one of them and stays
   quiet, which is the whole point of giving only three of them a colour. */
.trace-ev-heavy:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(155,44,36,.10);box-shadow:inset 2px 0 0 var(--bad)}

.trace-t:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-2);color:var(--ink-5);
font-variant-numeric:tabular-nums;padding-top:2px}

.trace-said:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-size:var(--fs-5);line-height:1.5;min-width:0}

/* THE VERB LEADS AND THE OTHER MAN IS THE LAST WORD ON THE LINE, because he is
   the next step of the trace. Owner, on the first cut: "need to be more clear
   what happened" -- the rows read "- Battle Brother by Caine", which is the
   payload said aloud rather than an account of an event. */
.trace-lead:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-weight:600;color:var(--ink)}

.trace-verb:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-3)}

.trace-mono:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-4)}

/* HOW MANY DAYS TO SET A MAN ASIDE FOR. Owner: "remind me in X days let me
   specify that number" -- so it is a box he types in, not a menu of guesses.
   Sized to three digits because the server refuses anything over a year, and
   tabular figures so 7 and 30 do not jump the button beside them about. */
.days-box:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:calc(46px * var(--ui));text-align:center;
font-family:var(--mono);font-size:var(--fs-2);font-variant-numeric:tabular-nums;
color:var(--ink-2);background:var(--panel);border:1px solid var(--rule);
border-radius:2px;padding:var(--s2) var(--s3)}

.days-box:focus:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){outline:none;border-color:var(--gold);color:var(--ink)}

/* THE ONE THING THIS SCREEN CANNOT ANSWER, SAID RATHER THAN LEFT BLANK.
   Discord records who DID a voice drag, the room and a count, and never the
   brother. Set quieter than the sentence it ends and allowed to wrap, because
   it is a caveat and not the event -- but it is on the line, not in a tooltip:
   the owner went looking for a move he had made and found nothing, which is
   exactly what an unexplained gap costs. */
.trace-unsaid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-5);font-size:var(--fs-2);font-style:italic}

/* A ROLE THAT MOVED, AND WHICH WAY IT WENT. Green is given, red is taken --
   the two colours the rest of this site already uses for those two meanings.
   Mono, because a role name is a token and not prose. */
.trace-pill:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:inline-block;font-family:var(--mono);font-size:var(--fs-3);
padding:0 var(--s3);margin:1px 2px 1px 0;border:1px solid;white-space:nowrap}

.trace-add:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--good);background:rgba(127,180,137,.12);
border-color:rgba(127,180,137,.34)}

.trace-rem:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--bad-lit);background:rgba(196,104,95,.11);
border-color:rgba(196,104,95,.32)}

/* EVERY NAME IS A FACE AND EVERY FACE IS THE NEXT STEP. Owner: "and when i see
   what what done to him i want to see by who?!? i need ot be able to trace
   through events." So the other man in the act is drawn as himself -- his own
   Discord picture where he has one, 111 of 567 do (measured 2026-09-12
   21:15Z: `authors.avatar_path` joined to guild_members) -- and
   clicking him opens his file. That IS the feature. */
.trace-peer:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:inline-flex;align-items:center;gap:var(--s3);
vertical-align:-4px;font-family:var(--serif);font-size:var(--fs-6);
font-weight:600;color:var(--ink);padding:1px var(--s3) 1px 2px;border-radius:999px}

.trace-peer:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--panel-on);color:var(--gold-warm)}

/* A MAN WHO IS ON NO ROLL -- Discord's own acts, and the handful of ids this
   guild has never seen -- is still named and still drawn, and is not a door,
   because there is no file behind him to open. */
.trace-peer-gone:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-3);cursor:default}

.trace-reason:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:block;font-family:var(--mono);font-size:var(--fs-3);
color:var(--ink-4);margin-top:2px}

/* AN EMPTY PANEL IS THE COMMON CASE -- 294 of 567 men have nothing on them at
   all, measured 2026-09-12 -- so it says so in a sentence, with the floor
   named. A blank box reads as a broken screen. */
.trace-none:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s7) var(--s6);color:var(--ink-3);font-size:var(--fs-5);
margin:0}

/* THE WALK, ACROSS THE TOP. Every step back is one click, and the trail is the
   only record of where a reader has been -- the panels hold one man. */
.trace-trail:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--serif);font-size:var(--fs-5);
color:var(--ink-2);background:none;border:1px solid var(--line);
padding:1px var(--s4);cursor:pointer}

.trace-trail:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-warm);border-color:var(--gold-line)}

.trace-trail-on:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink);border-color:var(--gold-line)}

.trace-who:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;flex-wrap:wrap;gap:var(--s5);
padding:var(--s6) var(--s7)}

.trace-who-name:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--serif);font-size:var(--fs-8);font-weight:600;
line-height:1.05}

.trace-tally:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-left:auto;display:flex;gap:var(--s8)}

.trace-tally-n:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-8);font-weight:500;
display:block;text-align:right}

.trace-tally-k:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);
letter-spacing:var(--ls-2);text-transform:uppercase;color:var(--ink-4);
display:block;text-align:right}

/* EVERY LANE LEAVES A LIGHT. Owner: "a flashing blue symbol on them".
   The fleet reads six reports at once; its single current id cannot name all
   six. Claimed rows pulse blue, queued rows wait dimly, and the glyph makes
   the work visible beside the words without taking away the row's tint. */
.scanning:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;background:rgba(122,146,196,.07);
box-shadow:inset 2px 0 0 var(--esc);animation:scanrow 1.4s ease-in-out infinite}

.scan-queued:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;background:rgba(122,146,196,.03);
box-shadow:inset 2px 0 0 rgba(122,146,196,.4)}

.scan-glyph:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:inline-block;color:var(--esc-lit)}

.scan-glyph-reading:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:scanmark 1.4s ease-in-out infinite}

.scan-glyph-queued:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--esc);opacity:.5}

/* The reader's own backlog pulses, because it drains by itself and the only
   question about it is whether it is moving. */
.tally-scan:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:#3BC8B2;border-color:#3BC8B2;
animation:led 2.2s ease-in-out infinite}

/* ---- A FILTER SAYS ITS QUESTION AND ITS ANSWER ----
   Owner: "all the damn filters are crazy... lets combine them into their
   groups and make them dropdowns so its not so cluttered." Seventeen chips in
   four unlabelled groups are four controls, each the width of what it says.
   Built on .decide-v's shape -- the same gold-lined box with the same caret --
   because opening a list of words and picking one is the same gesture the
   override on a report already is, on the same screen. */
.filt:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;display:flex;align-items:center;gap:var(--s3);
flex:none;cursor:pointer;border:1px solid var(--gold-line);
background:var(--panel-3);padding:var(--s3) var(--s5);
transition:border-color var(--t)}

.filt:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:var(--gold)}

.filt:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::after{content:"▾";font-size:var(--fs-3);color:var(--gold-dim)}

.filt-k:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);font-weight:500;
letter-spacing:var(--ls-key);text-transform:uppercase;color:var(--ink-6)}

.filt-v:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-size:var(--fs-5);color:var(--gold-warm);white-space:nowrap}

/* Lit once it is narrowing the list, so a bar with something set on it reads
   as set at a glance and nobody has to open four menus to find out why the
   list is short. */
.filt-set:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:var(--gold)}

.filt-set:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .filt-v{color:var(--gold)}

/* .chip-group is gone with the last row of filter chips. .chip itself stays:
   the rank scope on the permissions screen is one, and it is a control that
   says a value rather than a filter that says a choice. */
.search:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;gap:7px;border:1px solid var(--hair-2);
background:var(--panel-3);padding:5px 9px;min-width:190px}

.search:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) input{background:transparent;border:none;outline:none;padding:0;
font-size:var(--fs-5);color:var(--ink);width:100%}

.field-num:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:calc(60px * var(--ui));text-align:right}

.field-min:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:calc(48px * var(--ui));text-align:right;border-color:var(--hair-2);
background:var(--panel-3);padding:3px 7px;font-size:var(--fs-5)}

.tick:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:11px;height:11px;flex:none;border:1px solid var(--line-3);background:transparent}

/* THE WORD IN AN EMPTY TILE. One type for both lines -- the slot it wants and
   the reason the picture is not here -- so the second reads as the quieter
   half of one sentence rather than as a different kind of text. */
.shot-slot:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:calc(9.5px * var(--ui));line-height:1.6;
letter-spacing:var(--ls-ctl);color:#556457}

/* A CSS BACKGROUND, NOT AN <img>, and that is not a style preference.
The template is served as ordinary HTML and the browser parses it before the
runtime ever sees it, so an img whose src is a binding gets fetched verbatim
by the preload scanner -- two 404s for the unresolved placeholder text on
every single page load. An unresolved binding inside url() is invalid CSS
instead, so the declaration is dropped and nothing is requested. */
.shot-img:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:100%;height:100%;background-position:center;
background-repeat:no-repeat;background-size:cover}

/* ==========================================================================
   MARK -- the one state pill, in the four lamps and nothing else.

   .shot-mark and .shot-mark-on are second names on the base and on the green
   one: skull.js points at `.aar-auspex .shot-mark` to place its verdict, so
   the class has to stay in the DOM. Whether a stats card came off a picture is
   one use of this pill, not a component of its own.

   ORNAMENT AUDIT: every one of these SHOWS A STATE, which is the whole
   component. The default is the COLD one -- a mark with no job given to it
   gives off no light, because --glow-spent is transparent. Four colours, four
   meanings, and the word inside says what the colour says, so nothing here is
   carried by hue alone.
   ========================================================================== */
.shot-mark:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:inline-flex;align-items:center;gap:var(--s4);flex:none;
padding:var(--s3) var(--s5);font-family:var(--mono);font-size:var(--fs-1);
font-weight:500;letter-spacing:var(--ls-2);text-transform:uppercase;white-space:nowrap;
color:var(--ink-5);border:1px solid var(--line-2);border-radius:var(--r1);
background:var(--panel-2);box-shadow:0 0 14px -6px var(--glow-spent)}

.shot-mark-on:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--good);border-color:var(--good-2);
box-shadow:0 0 14px -6px var(--glow-read)}

/* THE LAMP, opt-in. A dot in currentColor with its own halo, for the marks
   reporting a MACHINE's state -- the auspex verdict, a live sweep. Never on an
   ordinary label: a lamp on every pill is a string of lights, which is the
   noise the rule exists to stop. */
.shot-mark[data-verdict]:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::before{content:"";flex:none;
width:6px;height:6px;border-radius:50%;
background:currentColor;box-shadow:0 0 7px currentColor}

/* THE AUSPEX VERDICT TAKES ITS OWN HUE, EDGE AND ALL. The pill used to be a
   neutral green box with a coloured word in it, so the one component on the
   screen whose whole job is a verdict said it in text alone. data-verdict is
   the fact the page already states for skull.js -- "reading" / "bad" / "good"
   -- so the border and the wash read the same fact rather than a second copy
   of it, and no script has to hand out a class for it. */
.shot-mark[data-verdict="bad"]:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--warn-2);
border-color:rgba(181,112,90,.35);background:rgba(181,112,90,.08)}

.shot-mark[data-verdict="good"]:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:rgba(127,180,137,.3);
background:rgba(127,180,137,.07)}

/* HOW FAR THE JUDGEMENT HAS GOT. A report is settled a man at a time, and this
   is read at a glance a hundred times an evening, so it is a line rather than
   a sentence. */
/* 3px on a rounded ash track, which is the mockup's own bar. It draws ONE
   fill rather than the mockup's one pip per brother: a pip row needs an sc-for
   over the men, and {{ dSettledPct }} -- the fraction the sentence beside it
   quotes -- would then have nothing reading it. A dropped binding costs more
   than three gaps in a 3px line. */
.meter:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;height:3px;border-radius:var(--r1);
background:rgba(178,201,177,.14);overflow:hidden}

.meter-fill:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;left:0;top:0;bottom:0;border-radius:var(--r1);
background:linear-gradient(90deg,#3F7A5C,#79C48D);transition:width var(--t)}

/* THE TWO COLUMNS OF AN OPEN REPORT: what was filed on the left, what is being
   decided on the right. They were three columns in one wrapping row -- team,
   filing, evidence -- which put the buttons above the pictures they are about
   and left the sergeant scrolling between them. Both fall to one column on a
   phone, where the order is evidence first and the decision under it. */
/* THE SPACE BETWEEN THE COLUMNS IS THE CONTAINER'S, and it was seven panels'
   margins instead. Every panel carried `mt7 mx7`, so the gap between the two
   columns was two 16px margins meeting in the middle -- 32px that no single
   edit could retune -- and each panel sat --gut PLUS 16px in from the glass
   while the gutter's whole reason for existing is to be the one inset. Gaps
   here, no margins there: the columns are 24px apart, the panels in a column
   22px, and all three numbers are stated once. */
/* ONE TRACK GROWS AND IT IS THE FILING. The mockup's page is a grid --
   `minmax(620px,1fr) 560px` -- so the deciding column is 560px at every width
   and the filing column absorbs all the slack. Both tracks were flex:1 1 with
   grow enabled, which made the side column a function of the viewport: 565px
   at 1600 and 491px at 1280, against a drawn 560 at both. flex:0 1 lets it
   shrink below 560 when there is genuinely no room and never above it.
   The page's own top and bottom room is here too: 22px under the title block's
   rule and the mockup's deep 72px foot, which the panels used to answer for
   with margins and then stopped answering for at all. */
/* NOWRAP ABOVE THE BREAKPOINT, and that is a condition of the fixed track. A
   wrapping row breaks its line on the items' BASIS, not on their shrunk size,
   so the moment the deciding column stopped being a percentage and became a
   fixed 560px the two columns would have STACKED anywhere under about 1390px
   -- in source order, with no phone reordering, which puts the men and the
   buttons below everything a sergeant has already read. Both children carry
   min-width:0, so nowrap shrinks them instead and nothing overflows. Under
   1000px the media query below turns this into a single column properly. */
/*
 * THE MOCKUP IS A GRID, AND APPROXIMATING IT IN FLEX WAS WRONG.
 *
 * Its line 68, verbatim: display:grid; grid-template-columns:minmax(620px,1fr)
 * 560px; gap:24px; align-items:start. This was built as flex:1 1 620px against
 * flex:0 1 560px, which lands within a few pixels of the grid at 1280px and at
 * 1600px -- the two widths the mockup is drawn at, and the two I measured. It
 * does NOT land anywhere else: flex-basis is a starting point and the free
 * space is then shared, so on a 2526px monitor the filing column took ~1900px
 * and dragged every panel in it wide. The owner saw that before I did.
 *
 * AND IT IS NOT CAPPED. It was, briefly, at the mockup's own 1636px and
 * centred -- on the reasoning that "pixel for pixel" past 1600px can only mean
 * the page stops growing at the width it was drawn for. The owner looked at it
 * full width and settled it: "make them full width but respect the page
 * padding."
 *
 * Which is right, and the grid is why. Under the old flexbox BOTH columns grew
 * and the deciding column came out at 616px against a drawn 560; under
 * minmax(620px,1fr) 560px only the filing column takes the slack, so the
 * fireteam holds its drawn width at any monitor and the extra room goes to the
 * evidence and the record, which are the things that want it. The sides come
 * from .gutter on the scrollport, so the page's own padding is already
 * respected and nothing here needs to restate it.
 */
.aar-cols:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;
grid-template-columns:minmax(calc(620px * var(--ui)),1fr) calc(560px * var(--ui));
align-items:start;
gap:calc(22px * var(--ui)) calc(24px * var(--ui));
padding-top:calc(22px * var(--ui));padding-bottom:calc(72px * var(--ui))}

.aar-main:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:0;gap:calc(22px * var(--ui))}

/* IT STICKS, and the header above it does not -- the mockup pins the deciding
   column at top:20px and lets the filing scroll under it, which is the whole
   reason its header is a flat block rather than a bar. .aar-cols already sets
   align-items:flex-start, which is the precondition: a stretched item is full
   height and sticky never engages. On a phone .aar-side is display:contents
   and position is inert, so there is no narrow-width case to answer for. */
.aar-side:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:0;gap:calc(20px * var(--ui));
position:sticky;top:calc(20px * var(--ui));
/* CAPPED, OR THE PINNING HIDES THE BUTTONS IT EXISTS TO KEEP ON SCREEN.
   The deciding column holds the fireteam head, a ~140px card per brother
   with its own accept/refuse row, THE UNBOUND and the notes well: with
   three brothers it is taller than the scrollport at the mockup's own
   1600x1000. Sticky without a height cap pins the TOP of that column, so
   the later men's buttons sit below the fold for the whole scroll -- the
   exact inverse of the reason the column was made sticky. The cap makes
   the column scroll inside itself instead. No phone guard needed:
   .aar-cols > .aar-side is display:contents under 1000px, where position
   is inert. */
max-height:calc(100dvh - calc(40px * var(--ui)));overflow:auto}

/*
 * THE ORDER A PHONE READS AN AAR IN, which is not the order a desk reads it in.
 *
 * Owner, 2026-09-01: "fornthe phone view lets have the fire team at the top
 * then the evidence then the as written then the call then the notes." On a
 * desk the two columns stand side by side and there is no order to argue
 * about; folded into one, the columns simply concatenate -- evidence, filing,
 * notes, then the fireteam and the call underneath -- so the men he is
 * deciding about ended up below everything he had already read.
 *
 * AS WRITTEN IS GONE FROM THAT SEQUENCE and its order:5 with it. Owner,
 * 2026-09-04: "the as written hiddin shit is not needed!! at the bottom." THE
 * RECORD's first column carries what the brother filed, field by field, so the
 * panel was repeating what was already above it behind a click. The gap in the
 * order values is deliberate and costs nothing -- `order` sorts, it does not
 * count -- and leaving it means the panels that remain keep the numbers this
 * comment has always given them.
 *
 * `display: contents` is what makes this possible without a second copy of the
 * markup: the two column wrappers stop generating boxes, their panels become
 * direct children of .aar-cols, and one `order` on each puts them in his
 * sequence. One set of panels, one set of bindings, two readings.
 *
 * The breakpoint is 1000px because that is where `wide` flips in renderVals,
 * and a layout that disagreed with the script about which shape it is in would
 * be worse than either shape.
 */
@media (max-width: 999px){
/* CHILD SELECTORS, because both wrappers also carry `.col`, which sets
     display:flex at the same specificity and wins on source order -- measured
     in the browser: a wrapper went to contents and these two stayed flex, so
     nothing reordered and the side column kept a fixed width wider than the
     phone and clipped its own text. */
.aar-cols:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > .aar-main,.aar-cols:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > .aar-side{ display: contents; }
/* STRETCH AND DO NOT WRAP. .aar-cols is a wrapping row with
     align-items:flex-start, which is right for two columns side by side and
     wrong for one column of panels: measured, the panels came out 433, 433,
     433, 311 and 205 wide inside a 366 phone -- each sized to its own content,
     some overflowing and some stranded. In one column every panel is the width
     of the screen. */
/* One column. Was flex-direction, which a grid does not read -- with
     .aar-main and .aar-side both display:contents their panels are the
     grid's own children, so the track count is what stacks them. */
.aar-cols:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){ grid-template-columns: 1fr; align-items: stretch; }
/* THE AUSPEX WAS LEFT OUT, so on a phone it kept source order and led the
     report -- the machine's reading arriving before the men it is about. It
     belongs directly under the pictures it read. Anything unordered defaults to
     0 and jumps to the front, which is the bug, so every panel is named. */
.aar-team:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){ order: 1 }
/* Directly under the men, because that is what it is about: somebody who
     played and whom the ledger cannot name. Above the pictures, because the
     answer is a person on this report and not a detail of the evidence. */
.aar-unbound:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){ order: 2 }
.aar-evidence:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){ order: 3 }
/* THE STRIP UNDER THE PICTURES WAS THE ONE PANEL NOBODY NAMED, and the law
     three lines up is exactly what it broke: unordered, it defaulted to 0 and
     led the phone -- "nobody has read these screenshots yet" arriving above THE
     FIRETEAM, before the screenshots it is about. It draws only when
     dUnchecked is true, which is why it was never seen. Same order as the
     evidence, so source order keeps it directly underneath. */
.aar-unread:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){ order: 3 }
/* THE RECORD IS THE CALL, so it keeps the call's place in the owner's phone
     sequence -- "fornthe phone view lets have the fire team at the top then the
     evidence then the as written then the call then the notes". The auspex's
     order:4 went with the panel that carried it: its findings are inside THE
     RECORD now, and a rule naming .aar-auspex would ALSO match this panel (it
     still carries the class for skull.js) and fight this one on source order. */
.aar-record:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){ order: 6 }
.aar-notes:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){ order: 7 }
}

/* A menu hung off a control near the right edge opens inward. */
.pop-right:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){left:auto;right:0}

.lightbox:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:fixed;inset:0;z-index:70;display:flex;align-items:center;
justify-content:center;padding:var(--s7);
background:rgba(4,8,7,.94);backdrop-filter:blur(6px);
animation:fadein .18s ease}

.lightbox-panel:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;flex-direction:column;gap:var(--s5);
max-width:min(96vw,1500px);max-height:94vh}

/* contain, because this is where the picture is actually read: a tall phone
grab and a wide monitor capture are both shown whole rather than cropped to
a box that suits neither. */
.lightbox-img:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:min(96vw,1500px);height:78vh;background-position:center;
background-repeat:no-repeat;background-size:contain;
border:1px solid var(--gold-line);background-color:var(--bg-deep)}

/* THE BROTHER'S CARD, which is a lightbox holding a panel rather than a
picture. It borrows the overlay and the click-away-to-close, and parts company
after that: a picture wants the whole viewport and a card wants to be a card,
so this one is narrow, has the panel's own ground under it, and scrolls
INSIDE itself rather than growing past the screen. */
/* A BROTHER ON AN AAR, and how far his approval has got. Three of these sit
where the filer's name used to, because an aar is settled by each man on it
being answered for separately -- so the row has to show all three and which of
them are done. Bordered rather than filled: three solid green blocks in a
sixty-row table is a wall, and the point is to be scannable. */
/* THE ONE CONTROL ON A FIRETEAM ROW. Owner, 2026-08-31: "simple checkbox next
to their name that only the sgt or above in THEIR company can click." Ticking
it credits him at what the aar is worth; the cross beside it refuses him and
asks why. It is drawn, not an <input>, for the same reason the WHO MAY ACT grid
is: a native checkbox cannot be given this palette on every browser. */
.tickbox:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;justify-content:center;
width:22px;height:22px;border:1px solid;border-radius:2px;
font-size:calc(13px * var(--ui));line-height:1}

/* .crew-chip is gone. It was the rounded pill a brother wore on a report, and
   it is the thing the owner pointed at when he asked for the roster to look
   like the reports section -- so it became .ident, the plate, and both of its
   users took the plate instead. */
.battle-link:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:inherit;text-decoration:underline;text-decoration-style:dotted;
text-underline-offset:.25em;cursor:pointer}

.battle-link:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.battle-link:focus-visible:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink);text-decoration-style:solid}

.card-panel:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:min(96vw,620px);gap:0;
background:var(--bg);border:1px solid var(--gold-line);
box-shadow:0 18px 60px rgba(0,0,0,.6)}

/* auto-fit rather than a fixed count: the same grid carries six facts on a
desktop and two across on a phone with nothing to switch on. */
.grid-facts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
gap:1px;background:var(--line);border-top:1px solid var(--line);
border-bottom:1px solid var(--line)}

@media (prefers-reduced-motion:reduce){
.lightbox:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
/* The forge's entrance is the only animation the shared layer adds, and it
   plays once. It still goes: a reader who asked for no motion asked for none. */
.forge:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
}

/* ---- lists: every table on the site is these two rules plus a template ---- */
.grid-hdr:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;align-items:center;gap:9px;padding:var(--s4) var(--s8);
border-bottom:1px solid var(--hair);background:rgba(9,18,14,.66);
color:var(--ink-4)}

/* The colour is INHERITED from .grid-hdr above rather than set here, so a
.c-gold on one header cell still wins. Setting it on this selector made it
(0,1,1) and it silently beat every colour utility -- which is how the points
column came out grey. */
.grid-hdr:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>div{font-size:var(--fs-2);font-weight:600;letter-spacing:var(--ls-2);
text-transform:uppercase;white-space:nowrap}

/* A shape, a line, and what to do next. The mark's job is BOUNDARY: something
   belongs in this space and is not here. It is UNLIT on purpose -- an empty
   state must never be the brightest thing on a screen, or absence reads as an
   event. The only light in the block is on the button, if the reader may act
   at all. (No wash behind the glyph: the border already bounds it and the
   glyph already says "missing", so a gradient there would mark nothing.) */
.empty-mark:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;justify-content:center;flex:none;
width:calc(44px * var(--ui));height:calc(44px * var(--ui));
border:1px solid var(--line-2);
font-family:var(--serif);font-size:var(--fs-8);line-height:1;color:var(--ink-6)}

.empty-title:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--serif);font-size:var(--fs-7);
letter-spacing:var(--ls-2);color:var(--ink-3)}

.empty-line:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){max-width:46ch;font-size:var(--fs-6);line-height:1.45;color:var(--ink-4)}

.navtab-sm:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-size:var(--fs-3);font-weight:600;letter-spacing:var(--ls-1);
text-transform:uppercase}

/* ==========================================================================
   ROUND THREE — new components, not conversions.

   Meant to be appended into ledger.css's COMPONENTS region (before the
   UTILITIES block), the same place .card/.pop/.totals/.bar live, so that a
   utility class placed alongside one of these still wins. Nothing in this
   file writes a raw hex or rgba() colour and nothing sizes text off a raw
   px value -- every colour and font size below is one of the tokens already
   declared in :root. No @keyframes name here collides with an existing one
   (led, shimmer, scanv, rise, glow, fadein, spin, enter, bootmark, bootline,
   track, wipe, tickin, glowtext, hairline, reticle, emberrise, emberglow,
   flame, halo, breathe, phosphor) -- this file's two are quotein/quoteout.
   ========================================================================== */
/* ==========================================================================
   THE QUOTE FOOTER

   Owner: "a fade in then fade and slide out making room for another to fade
   in", with "a blur and acceleration effect". One line rotates every 60s;
   the strip is atmosphere, so it gets a fixed height (no page jump when a
   longer quote replaces a shorter one) and stays out of the interface's way.

   .quote-in and .quote-out are the two overlapping instances themselves --
   each is an inset:0 layer inside .quote, which is why .quote only needs
   position + a real height and neither instance needs its own class for
   that. The entrance reuses the sheet's own decelerating settle curve
   (cubic-bezier(.2,.8,.2,1), the same one .anim-enter already uses); the
   exit reuses the sheet's own accelerating "pulled away" curve
   (cubic-bezier(.5,0,.75,0), the same one every .boot-leaving reversal
   already uses) rather than inventing a third feel for the same motion.
   ========================================================================== */
/*
 * THE SHELL IS AS TALL AS THE VISIBLE WINDOW, NOT AS TALL AS 100vh.
 *
 * Owner, 2026-09-01: "the buttons at the bottom dont exist just the underline
 * are they under the cool quote bar?"
 *
 * They were under the URL BAR. On a phone `100vh` is the viewport with the
 * browser's chrome HIDDEN -- the biggest it ever gets -- so while the address
 * bar is showing, the bottom of a 100vh column sits below the glass. The nav
 * strip is the last child, so it went first, and its top border was the last
 * thing still on screen: exactly "just the underline".
 *
 * `100dvh` is the viewport as it is RIGHT NOW and resizes as the chrome slides
 * away. The 100vh line stays above it as the fallback for a browser that does
 * not know dvh, which gets today's behaviour rather than a collapsed page.
 *
 * And the strip clears the home indicator. A row of buttons a thumb cannot
 * reach without hitting the system gesture area is a row of buttons that
 * misfires, so the inset is added to its padding rather than assumed to be
 * zero.
 */
.shell:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;flex-direction:column;overflow:hidden;background:#0B1210;
  height:100vh;height:100dvh}

.navbar:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-bottom:env(safe-area-inset-bottom,0px)}

.quotebar:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;flex:none;width:100%;overflow:hidden;
  /* TALL ENOUGH FOR THE TWO LINES IT ALLOWS. The cross-fade needs both quotes
     absolutely positioned over one another, so the bar cannot grow to its
     content and the height has to be stated. 56px fits one line and the
     attribution; a phone wraps far more often than a desk does, so it gets the
     second line's worth. Measured at 390x664: two lines at this size plus the
     attribution need 78. */
  height:calc(56px * var(--ui));
  padding:var(--s2) var(--s9);
  border-top:1px solid var(--hair-3);
  background:var(--bg-deep)}

/* GROUPED, NOT RANKED. Owner: "too many of them theres shold be smaller fewer
   clusters of them", then "3-5 of different sizes in 2 clusters on the left and
   right side." Twenty-two evenly spaced candles read as a fence. Two groups
   flanking the quote read as candles someone lit, and they frame the line
   instead of running under it. Four on the left, five on the right -- matched
   pairs look placed, uneven ones look lit. */
.cluster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:flex-end;gap:4px}

/* The wick's flame. Not a circle -- a teardrop, wide at the base and drawn to a
   point, which is the shape the eye actually reads as fire. */
.flame:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;left:50%;bottom:100%;width:4px;height:7.5px;
  margin-left:-2px;border-radius:50% 50% 45% 45% / 62% 62% 38% 38%;
  background:radial-gradient(ellipse at 50% 72%,
    #FFF2CE 0%, #F6D98A 26%, #D8A63C 55%, rgba(190,120,30,.55) 78%, transparent 100%);
  transform-origin:50% 100%;
  animation:flicker 2.3s ease-in-out infinite}

/* The light it throws. Kept wide and very faint: this is what makes the bar
   feel lit from below instead of having stickers on it. */
.flame:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::after{content:"";position:absolute;left:50%;top:50%;width:30px;height:26px;
  margin:-13px 0 0 -15px;border-radius:50%;
  background:radial-gradient(circle, rgba(232,176,74,.20) 0%, rgba(216,150,50,.07) 45%, transparent 72%)}

/* Burnt down unevenly, and the two clusters share no silhouette. */
.cluster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(1){height:15px}

.cluster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(2){height:8px}

.cluster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(3){height:19px}

.cluster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(4){height:11px}

.cluster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(5){height:22px}

.cluster:nth-child(2):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(1){height:10px}

.cluster:nth-child(2):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(2){height:17px}

.cluster:nth-child(2):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(3){height:9px}

.cluster:nth-child(2):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(4){height:21px}

.cluster:nth-child(2):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .candle:nth-child(5){height:13px}

/* OUT OF STEP, TWELVE WAYS. Duration comes from the cluster and delay from the
   candle's place in it, so every flame gets its own pair without twelve rules.
   The numbers share no factors -- a rank that flickers together reads instantly
   as one animation applied N times, which is what makes this look cheap. */
.cluster:nth-child(1):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .flame{animation-duration:2.9s}

.cluster:nth-child(2):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .flame{animation-duration:2.3s}

.candle:nth-child(1):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .flame{animation-delay:-.3s}

.candle:nth-child(2):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .flame{animation-delay:-1.7s}

.candle:nth-child(3):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .flame{animation-delay:-1.1s}

.candle:nth-child(4):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .flame{animation-delay:-2.3s}

.candle:nth-child(5):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .flame{animation-delay:-.9s}

/* A guttering candle is still a lit candle. Reduced motion stops the movement
   and keeps the light. */
@media (prefers-reduced-motion: reduce){
.flame:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
}

/*
 * CROPPED BY THE BOX, NOT BY A CLIP PATH. background-size:100% auto with the
 * position at 0 0 draws the picture --con-w SQUARE from the top-left corner,
 * and the box is only 600/768 of that tall -- so image rows 600 to 768, the
 * cabinet plinth there is no room for, fall outside the padding box and are
 * never painted at all. The mask takes the last thirty rows down to nothing so
 * the machine sinks into the page instead of ending on a cut line.
 */
/* ABOVE THE QUOTE BAR, BELOW EVERYTHING THAT MATTERS. 22 clears .quote (1) and
   the bar itself so the plinth draws over them, and stays under .pop (30), the
   toast (40), the skull (55) and the phosphor/bezel (60/61) -- a popup, a
   message and the servitor himself all still come out in front of it. */
.aar-console:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:fixed;z-index:22;pointer-events:none;isolation:isolate;
  --lit:0;--pop:0;
  right:var(--con-r);bottom:var(--con-b);
  /*
   * THE WHOLE PICTURE, BASE AND ALL. This was `* 600 / 768` -- the top 600 rows
   * of a 768x768 asset -- so that the plinth ran off the bottom and the machine
   * appeared to sink into the page. That was right while it sat behind the
   * quote bar. It now draws OVER the bar, and the owner saw the consequence at
   * once: "the console is missing its base." The crop was cutting rows 600 to
   * 767, which is 22% of the machine and every part of it that meets the floor.
   *
   * Square, because the asset is. Every percentage below moved with it -- they
   * were fractions of the 600 crop and are fractions of 768 now.
   */
  width:var(--con-w);height:var(--con-w);
  background:var(--console-art, none) 0 0/100% auto no-repeat;
  /* A breath of a fade at the very foot so it meets the bar rather than ending
     on a line. 2%, not the 5% that was hiding the plinth. */
  -webkit-mask-image:linear-gradient(180deg,#000 0 98%,transparent 100%);
          mask-image:linear-gradient(180deg,#000 0 98%,transparent 100%)}

/*
 * z-index 20 puts the machine above the embers, the candles and the quote, and
 * BELOW .pop (30) so a dropdown opening into the corner is never crossed and
 * below the toast (40) so a message to a reader always wins. He himself flies
 * at 55, so he is drawn in front of it -- which is the whole tableau: we watch
 * him work, over his shoulder.
 */
/*
 * THE GLASS, MEASURED OFF THE FILE RATHER THAN GUESSED. The tube's inner edge
 * traced at pixel resolution sits at x[176,596], y[152,391] of the 768px
 * picture. Against the element that is left 176/768, width 420/768, and -- the
 * box being cropped at row 600 -- top 152/600 and height 239/600.
 */
.aar-crt:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;overflow:hidden;
  /* The glass, traced at x[176,596] y[152,391] of the 768px asset. Vertical
     figures are of 768 now that the whole picture shows; the horizontal pair
     never changed, because the width never was cropped. */
  left:22.917%;top:19.792%;width:54.688%;height:31.120%;
  border-radius:4.8% / 8.4%;
  /* The phosphor wash behind the glyphs, and the spill onto the bezel. #7BE8A8
     is the green the skull's own fill light already throws at this page. */
  background:radial-gradient(118% 130% at 50% 42%,
    rgba(123,232,168,calc(.11 * var(--pop,0))) 0%,
    rgba(123,232,168,calc(.04 * var(--pop,0))) 58%, transparent 100%);
  box-shadow:0 0 calc(20px * var(--pop,0)) rgba(123,232,168,calc(.20 * var(--pop,0)))}

/*
 * THE LIGHT DIES UNDER THE FRAME. This is the piece that actually sells "inside
 * the tube": the bezel's own shadow continued inward, so the rain falls off
 * beneath the metal instead of stopping flat at a rectangle. It has to be the
 * pseudo-element and not an inset shadow on .aar-crt itself, because an inset
 * shadow paints under the element's children and the canvas is a child.
 *
 * There are deliberately NO scanlines here. .phosphor already lays them over
 * the whole viewport at 3px, above both the console and the skull, so a second
 * set at a second pitch would only beat against the first.
 */
.aar-crt:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::after{content:"";position:absolute;inset:0;border-radius:inherit;
  pointer-events:none;
  box-shadow:inset 0 0 13px 4px rgba(0,0,0,.82), inset 0 2px 6px rgba(0,0,0,.9)}

/*
 * mix-blend-mode:screen is what makes the rain live IN the glass: the tube's
 * baked specular sweep and its vignette survive underneath it, and a black
 * pixel is the identity under screen, so the trails simply stop contributing as
 * they decay. The blend is confined to the machine because .aar-console is
 * position:fixed with a z-index and therefore already an isolated stacking
 * context -- isolation:isolate up there says so out loud rather than leaving it
 * to be inferred. The radial mask kills the rain before it reaches the corners,
 * which both matches the tube's own curvature and hides the pixel of
 * registration error that would otherwise show as a green line on the iron.
 */
.aar-rain:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;inset:0;width:100%;height:100%;
  opacity:var(--lit,0);mix-blend-mode:screen;
  -webkit-mask-image:radial-gradient(118% 128% at 50% 45%,
    #000 58%,rgba(0,0,0,.55) 84%,transparent 100%);
          mask-image:radial-gradient(118% 128% at 50% 45%,
    #000 58%,rgba(0,0,0,.55) 84%,transparent 100%)}

/*
 * THE WORKING SURFACE, PUBLISHED AS AN ELEMENT so that skull.js never has to
 * hold a number about this machine. It measures this box and puts his ARM TIPS
 * at its centre; everything else about where he hovers follows from the model.
 * Image x[144,624], y[462,558] -- the key deck, centred under the tube.
 */
/* The keys he stands at. Vertical figures rebased on 768 with the crop gone --
   77% and 16% of the old 600 are 60.156% and 12.5% of the whole. */
.aar-console-deck:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;left:18.75%;top:60.156%;width:62.5%;height:12.5%}

/* THE SAME LINE THE SKULL KEEPS, in both the places he keeps it: skullWanted()
   refuses anything under 1000px and .skull-toggle disappears there. A console
   exists FOR him to work at, and below this there is nobody to work it -- an
   AAR arriving still announces itself in the reports list. The height floor is
   the other half of the same judgement: on a short window the machine would
   swallow the page rather than stand in the corner of it. */
/* THE COMPANY GOES LAST ON A PHONE. Owner: "in the stratigum id like to put
   the company below everything else. Just want to see what I need to do."

   Side by side it is a column beside the work and reads as context, which is
   why it was put first -- but a phone stacks this row, and stacked it becomes
   a hundred-odd brothers standing between the reader and ASKED OF YOU. His
   own work is what the screen is for; the roll of the company is what he
   scrolls to when he wants it.

   ORDER, NOT A SECOND COPY OF THE MARKUP. .row-stretch is a flex row, so one
   declaration moves the whole panel and the desktop layout -- where the
   company still leads -- is untouched. */
/* A SUMMARY IS ONE LINE ON A PHONE. Owner: "I like the summaries but their way
   too big."

   Each tile is a three-line stack -- a label, a 28px figure, and a note under
   it -- which is right beside four of its kind on a wide screen and is most of
   the first viewport on a narrow one. Laid on its side the same tile is a
   label and a number on one line, and the four of them take two rows instead
   of eight.

   THE NOTE IS WHAT GOES, and nothing else. It is the line that explains the
   figure -- "Matches the filters below", "1 active vox channel" -- which is
   worth its room beside a column of numbers and is not worth a phone's screen
   under one. The FIGURES all stay, which is what was asked for.

   This rule once had to spare a hand as well: `gone quiet` was a tile carrying
   a control, drawn with the same .t4 the notes use. That tile is gone, so no
   tile holds anything but text and the exception went with it. If one ever
   does again, mark the control and spare it by name -- do not widen this to
   the class, or a hand will disappear on a phone and nothing will say so. */
/* The number drops from --fs-9 to --fs-6 because it no longer has a line to
   itself and must sit on the baseline of the words beside it. */
@media (max-width: 999px){
.total:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:1 1 auto;min-width:0;flex-direction:row;flex-wrap:nowrap;
    align-items:baseline;gap:var(--s4);
    padding:calc(7px * var(--ui)) var(--s6)}
.total:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > .t4{display:none}
.total:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .total-n{font-size:var(--fs-6)}
/* THE LABEL GIVES, THE FIGURE NEVER DOES. Owner: "the summary for mobile on
     roster needs to be styled like reports" -- and the two were already the
     same rule. What differed was the words: the reports bar's longest label is
     `awaiting auspex`, the roll's is `brothers on roster`, and a nowrap label
     that long pushed its own figure onto a second line. One tile on the roster
     was two lines high while every tile on reports was one, which is the whole
     of what he was looking at.
     So the row does not wrap at all now and the LABEL is what runs out of
     room, ellipsised, because a summary whose number has been pushed out of
     sight is not a summary. Reports is untouched by this: nothing there is
     long enough to reach the ellipsis. */
.total:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
    min-width:0;flex:0 1 auto}
.total:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .total-n{flex:none}
}

@media (max-width: 999px){
.cmd-aside:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){order:9}
}

@media (max-width: 999px){
.aar-console:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:none}
}

@media (max-height: 699px){
.aar-console:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:none}
}

/* A dead screen that lights is still a screen that lights. Reduced motion takes
   the rain's movement away -- console.js never starts a frame loop at all and
   paints one still screenful instead -- and keeps the light, which is the
   candles' own rule directly above. The cross-fade is what is left of it. */
@media (prefers-reduced-motion: reduce){
.aar-rain:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){transition:opacity .24s ease}
.aar-crt:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){transition:box-shadow .24s ease}
}

/* THE QUOTE RIDES ABOVE THE RANK, and carries its own darkness with it.
   Measured at 1440: a flame landed squarely on the "40K" of the attribution.
   Ornament never gets to cost a word, so the quote lays down a soft pool of the
   deep background under itself -- the candles still burn through at the edges,
   the letters stay crisp, and nothing had to be moved or removed. */
.quotebar:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > .quote{position:relative;z-index:1;
  background:radial-gradient(60% 130% at 50% 55%,
    rgba(11,18,16,.94) 0%, rgba(11,18,16,.82) 45%, rgba(11,18,16,0) 100%)}

/*
 * HE SHOOK THE MONITOR, AND LEFT A CRACK IN IT.
 *
 * Owner: "can we add a little shake to the whole page and a little crack in the
 * glass too? like he shook the monitor crask goes away after a few seconds
 * fades away?"
 *
 * THE SHAKE IS ON THE SHELL, which carries the bezel and the phosphor with it --
 * so what moves is the SCREEN, while he stays exactly where he struck it. That
 * is the right way round: he is the one doing the hitting. It is also why he is
 * not inside the shell; his canvas hangs off the body, untouched by this.
 *
 * Small and brief. A page that lurches is a page nobody can read, and the whole
 * effect is over in a third of a second.
 */
.shell.rattled:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:rattle .34s cubic-bezier(.36,.07,.19,.97) both}

@media (prefers-reduced-motion: reduce){
.shell.rattled:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
}

/* THE PHOSPHOR WASH. The whole app is one screen in one machine, and a screen
   has scanlines and falls off at its edges. Kept far below the threshold where
   it costs anybody a word: 3% on the lines, and a vignette that only reaches
   the corners. It sits above everything and takes no clicks. */
.phosphor:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:fixed;inset:0;z-index:60;pointer-events:none;
  background:
    repeating-linear-gradient(180deg, rgba(0,0,0,.055) 0 1px, transparent 1px 3px),
    radial-gradient(120% 100% at 50% 50%, transparent 55%, rgba(0,0,0,.38) 100%)}

/* THE BEZEL. Where the screen stops and the machine begins: a lit inner lip on
   the top and left, shadow on the bottom and right, as though the glass is set
   into a frame lit from the same corner as everything else. */
.bezel:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:fixed;inset:0;z-index:61;pointer-events:none;
  /* ONE LIT EDGE. The left lip said the light came from up and to the left,
     which no other surface in the sheet agrees with -- every plate, card and
     banner lights its top only. The sides take the shadow instead. */
  box-shadow:
    inset 0 1px 0 rgba(255,228,158,.18),
    inset 1px 0 0 rgba(0,0,0,.4),
    inset -1px 0 0 rgba(0,0,0,.55),
    inset 0 -1px 0 rgba(0,0,0,.6),
    inset 0 0 40px rgba(0,0,0,.28)}

@media (prefers-reduced-motion: reduce){
.phosphor:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:none}
}

/* AFTER .quotebar, not before it: same specificity, so source order decides,
   and a media query above the rule it overrides does nothing at all. */
@media (max-width: 999px){
.quotebar:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){height:calc(78px * var(--ui))}
}

/*
 * A QUOTE THAT DOES NOT FIT WRAPS RATHER THAN STOPPING MID-WORD.
 *
 * Owner: "some ofnthe quotes are too big and dont fit how do we solve that?"
 * It was one line, nowrap, with an ellipsis -- so on a phone the longer ones
 * ended in "…" and the line nobody could read was the punchline. They are
 * quotes; the last few words are the point of them.
 *
 * Two lines, clamped. Two is the number because the bar is a fixed height --
 * the cross-fade needs both quotes absolutely positioned over each other, so
 * the bar cannot grow to fit its content -- and two lines of this size fit
 * inside it with the attribution underneath. A quote too long for two lines
 * still ends in an ellipsis, which is the honest floor rather than a promise.
 */
/* NO ITALIC. Cinzel is loaded as wght@400;600;700 -- there is no italic axis,
   so the browser was shearing the roman by hand. The same lines already render
   upright on the Settings tab, so this was inconsistent as well as synthetic. */
.quote-text:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--serif);font-size:var(--fs-5);
  letter-spacing:var(--ls-1);color:var(--ink-3);line-height:1.35;
  max-width:100%;overflow:hidden;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
  text-overflow:ellipsis}

.quote-who:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-2);font-weight:600;
  letter-spacing:var(--ls-2);text-transform:uppercase;color:var(--gold-dim);
  max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

.quote-in:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.quote-out:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;inset:0;
  /* Both sweeps clip at the scanner, in the quote container's coordinates. */
  clip-path:inset(0 var(--wipe-r,0px) 0 var(--wipe-l,0px));
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--s2);text-align:center}

.quote-in:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:quotein .9s cubic-bezier(.2,.8,.2,1) forwards}

.quote-out:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:quoteout .7s cubic-bezier(.5,0,.75,0) forwards}

/* The servo-skull suppresses this on the layer itself when IT turns the strip
   over -- see skull.js. Toggling a rule from here restarted the animation the
   moment the toggle came off, which is worse than leaving it alone. */
/* The same hundred lines, read all at once on the Settings screen instead of
   one a minute along the bottom. It is a LISTING and not the strip: nothing
   here is a fixed height and nothing is clipped to one line, because the point
   of the screen is to read the whole of each one. The columns reflow off the
   same 280px measure the company grid uses, so a quote is never a single word
   wide on a large display or two words wide on a small one. */
/* ==========================================================================
   THE SUB-TAB BAR

   Sits under the main nav (Settings' permissions/admins/overrides/quotes,
   the Roster's brothers/companies), so resting state stays dim and small --
   only the active tab is allowed to touch gold, via its own class rather
   than a colour the row hands out for free.
   ========================================================================== */
.subtabs:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;gap:var(--s2);width:100%;
  border-bottom:1px solid var(--hair-3)}

.subtab:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){cursor:pointer;flex:none;
  padding:var(--s4) var(--s6);
  font-size:var(--fs-3);font-weight:600;letter-spacing:var(--ls-2);
  text-transform:uppercase;color:var(--ink-4);
  border-bottom:2px solid transparent;
  transition:color var(--t),border-color var(--t)}

.subtab:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-2)}

.subtab-on:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink);border-bottom-color:var(--gold)}

.decide:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;display:flex;flex-direction:column;
  gap:var(--s3);min-width:calc(150px * var(--ui));flex:1 1 auto}

/* THE FIELD'S NAME, SET LIKE A COLUMN HEAD AND NOT LIKE PROSE. It was the
   body face in sentence case at the size of the value beside it, so on a row
   whose whole shape is "name, then what it says" the two ran together. */
.decide-k:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);font-weight:500;
  letter-spacing:var(--ls-key);text-transform:uppercase;color:var(--ink-6)}

.decide-v:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;display:flex;align-items:center;gap:var(--s3);
  cursor:pointer;border:1px solid var(--gold-line);background:var(--panel-3);
  padding:var(--s3) var(--s5);
  font-size:var(--fs-5);color:var(--gold-warm);
  transition:border-color var(--t)}

.decide-v:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:var(--gold)}

.decide-v:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::after{content:"▾";margin-left:auto;
  font-size:var(--fs-3);color:var(--gold-dim)}

.decide-set:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:var(--gold);color:var(--gold)}

/* THE AUSPEX RETURN. A sensor names what comes back: a lock is the whole
   contact, a ghost is a reading that may not be there at all. Coloured on the
   same three-step ramp the rest of the panel uses -- brass for good, ash for
   partial, warm for a return worth doubting -- rather than a fourth palette. */
.lock:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);font-weight:500;
  letter-spacing:var(--ls-2);text-transform:uppercase;white-space:nowrap;
  padding:2px 7px;border-radius:2px;border:1px solid currentColor}

.lock-full:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--good)}

.lock-strong:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold)}

.lock-part:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-ash)}

.lock-ghost:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--warn-2)}

.decide-src:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);
  letter-spacing:var(--ls-1);text-transform:uppercase;color:var(--ink-5)}

/* ONE ROW OF THE RECORD. Its own padding rather than .py5, because the row needs
   side padding for the hover ground and the ellipsis to have air, and its own
   divider rather than .rule-b, because a rule BETWEEN ROWS is half the weight
   of the rule that ends a block -- .05 against .10. The hover is cool and very
   faint on purpose: --lift is the warm "you are here" lamp for a control, and
   a row of a record is not a control. */
.record-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s5) var(--s3);
  border-bottom:1px solid rgba(178,201,177,.05);
  transition:background var(--t)}

.record-row:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(178,201,177,.035)}

/*
 * THE RECORD DRAWS AT 70%. Owner, 2026-09-04: "lets also make the record a
 * little smaller 30% smaller."
 *
 * IT IS ONE SCALE, NOT TWENTY EDITS. Every measure in this panel is already
 * written as `calc(Npx * var(--ui))` or as a token off the same ladder, so the
 * whole thing shrinks by moving --ui underneath it -- and it keeps shrinking
 * correctly the day somebody changes a track width or a row height, which
 * hand-multiplying twenty numbers would not.
 *
 * THE LADDER HAS TO BE DECLARED AGAIN HERE, and this is the part that is easy
 * to get wrong: a custom property is substituted where it is DECLARED, not
 * where it is used. --s5 and --fs-4 are declared on :root, so they resolved
 * against the PAGE's --ui before this panel ever inherited them; redefining
 * --ui alone would shrink the four bare calc()s in the rules below and leave
 * every padding and every type size exactly as they were. Re-declaring the
 * ladder against the local --ui is what makes the panel scale as one thing.
 *
 * Only the rungs the record actually draws with are listed. A rung it does not
 * use would be dead weight, and a rung it starts using later will inherit the
 * page's value and stand out immediately -- which is the failure being visible
 * rather than silent.
 */
.aar-record:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){
  /* AND BACK UP A LITTLE. Owner, 2026-09-05: "the ENTIRE record needs to be a
     little bigger." .7 was 30% off a full-size table and it read as a table
     seen from further away; .8 keeps the record smaller than everything around
     it, which is what the original ask was for, without making a sergeant lean
     in. The whole panel still moves as ONE number -- that is the point of the
     paragraph above -- so this line is the entire change to its box. */
  --ui:calc(var(--ui-page) * .8);
  /*
   * THE BOX SHRINKS BY 30%. THE TYPE DOES NOT, and running them together was
   * the mistake. Owner: "the text is WAY too small in the record now."
   *
   * He asked for the RECORD to be smaller, and what makes a table smaller is
   * its rows and its gutters -- the padding, the row height, the track widths,
   * all of which ride --ui above. Type is not a dimension of the box: --fs-1 is
   * already 9px, and 30% off it is 6.3px, which is not small type, it is
   * unreadable type. The ladder is declared against the PAGE's scale with a
   * gentle trim instead, so the panel reads as tighter rather than as further
   * away.
   */
  /* THE TYPE COMES BACK TO FULL SIZE with it. The .92 trim existed to make a
     30%-smaller box read as tighter rather than as further away; at .8 the box
     no longer needs the words apologising for it, and "the ENTIRE record"
     means the words as well as the table. */
  --fs-1:calc(9px * var(--ui-page));  --fs-2:calc(10px * var(--ui-page));
  --fs-3:calc(11px * var(--ui-page)); --fs-4:calc(12px * var(--ui-page));
  --fs-5:calc(13px * var(--ui-page)); --fs-6:calc(14px * var(--ui-page));
  --s1:calc(2px * var(--ui));  --s2:calc(4px * var(--ui));
  --s3:calc(6px * var(--ui));  --s4:calc(8px * var(--ui));
  --s5:calc(10px * var(--ui)); --s6:calc(12px * var(--ui));
  --s7:calc(16px * var(--ui)); --s8:calc(20px * var(--ui))}

/* ---- THE RECORD'S THREE COLUMNS -----------------------------------------
   ONE TRACK LIST, TWO USERS. The column strip and every row's inner grid read
   the same declaration, so a column that widens widens in both and the heads
   stay over the values they name. They are the mockup's own tracks, its own
   12px gutter and its own 34px row. */
.record-hdr:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.record-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;
  grid-template-columns:minmax(calc(165px * var(--ui)),1fr)
                        minmax(calc(165px * var(--ui)),1fr)
                        minmax(calc(150px * var(--ui)),max-content);
  gap:0 var(--s6)}

/* 9px down to the rule, which is .plate-head's own step and not on the --s
   ladder. Sides are --s3, so the strip starts on the same 26px as the rows
   under it -- the panel's 20 plus the row's 6, which is the mockup's 20+6. */
.record-hdr:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){align-items:center;padding:0 var(--s3) calc(9px * var(--ui));
  border-bottom:1px solid var(--line-3)}

/* The middle head takes the brass ash the mockup gives it. --gold-ash IS its
   #8c7a52; nothing new is minted for it. */
.record-hdr-mid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-ash)}

/* HIS OWN COMPANY, SET APART FROM THE REST OF THE STRIKE TEAM. Owner,
   2026-09-15: "when you're approving aars as the sgt I want to see your
   company seperated a bit from the rest of the brothers in the strike team."

   The heading was already drawn and carried FOUR PIXELS above it -- less than
   the gap between two brothers of the same squad -- so the first man he cannot
   answer for sat level with the last man he can, and the division he is meant
   to read was the only thing on the card that did not look like one.

   THE GAP IS THE DIVISION, not another rule: the group heading already draws a
   hairline of its own, and two lines four pixels apart is a box, not a break.
   The cards below it are stood down rather than hidden -- he still has to see
   who else was on the run, and the men he is answering for are the ones the
   eye should land on. Hover brings a stood-down card back to full, because
   reading a name is not the same as being asked for a word about it. */
.crew-group-other:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-top:calc(22px * var(--ui))}

.crew-card-other:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){opacity:.66}

.crew-card-other:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.crew-card-other:focus-within:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){opacity:1}

@media (prefers-reduced-motion:no-preference){
.crew-card-other:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){transition:opacity .14s ease}
}

/* WHICH ONE IS YOU. Set in the gold the page uses for the reader's own name
   everywhere else, so it is recognised before it is read. */
/* ON LEAVE. Owner: "he just gets a label on his name in the roster 'on leave'."
   Quiet on purpose -- it is a fact about a man, not a warning about him, so it
   reads like the rank beside it rather than the ON VOX badge, which is a thing
   happening right now. */
/* THE COMPANY AS ITS POST READS IT. A position is a heading in the chapter's
   own words with its men under it, indented by what it answers to -- the same
   shape a man sees in #legionnaires-rank-and-file, which is the point. */
.org-post-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;gap:var(--s5);align-items:baseline;
  padding:var(--s5) var(--s6) 2px var(--pad);margin-top:var(--s4);cursor:grab;border-radius:3px}

.org-post-row:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(230,223,204,.05)}

.org-man-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;padding:2px var(--s6) 2px var(--pad);border-radius:3px}

.org-man-row:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(230,223,204,.03)}

.org-held:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){opacity:.4;cursor:grabbing;background:rgba(180,143,50,.12)}

.leave-badge:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);font-weight:500;
  letter-spacing:.08em;text-transform:uppercase;white-space:nowrap;
  padding:1px 5px;border:1px solid #4A5A6A;border-radius:3px;color:#8FA3B8}

/* THE GUILD KNOWS HIM. A green disc and a mono word beside the YOU badge, the
   same measure as it so a man who is both does not get two different chips.
   It is drawn only when the last sweep listed him -- see teamOf(). */
.crew-dc:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:inline-flex;align-items:center;gap:calc(5px * var(--ui));
  font-family:var(--mono);font-size:var(--fs-1);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--good);
  margin-left:calc(6px * var(--ui));padding:1px 5px;
  border:1px solid rgba(121,196,141,.35);border-radius:2px;vertical-align:2px}

.crew-dc-dot:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:4px;height:4px;border-radius:50%;background:var(--good);flex:none}

/* HIS THREAD, indented under his row so it reads as belonging to him rather
   than to the card's edge, and separated by a rule the way the log upstairs is. */
.crew-thread:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-top:calc(8px * var(--ui));margin-top:calc(2px * var(--ui));
  border-top:1px solid var(--rule)}

.crew-say:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-top:calc(6px * var(--ui))}

.h-esc-line:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:rgba(122,146,196,.55);color:var(--esc-lit)}

.pill-variance:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--bad-lit);
  padding:2px 7px;border:1px solid rgba(192,90,60,.34);border-radius:2px;
  background:rgba(192,90,60,.10);white-space:nowrap}

.record-hdr-end:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){text-align:right}

.record-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){align-items:center;min-height:calc(34px * var(--ui))}

/* STRETCHED, AND RULED ON ITS LEADING EDGE. The divider belongs to the COLUMN,
   not to the line of type, so it runs the full height of the row -- which is
   what makes six rows read as a table rather than as six stacked pairs. */
.record-mid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;align-self:stretch;min-width:0;
  padding-left:var(--s6);border-left:1px solid var(--hair)}

/* NO overflow:hidden, and the mockup's own rule would have been a bug here:
   the override menu is an absolutely-positioned .pop hanging off this cell, and
   a clipped cell would cut it off at the row's edge. min-width:0 is what the
   ellipsis beside it actually needs. */
.record-end:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;justify-content:flex-end;align-items:center;
  gap:var(--s4);min-width:0}

/* THE TABLE FOLDS, because the mockup never had to. It is drawn at a hard
   min-width:1280px, where three tracks with a 480px floor always fit; this app
   runs down to a phone, where 480px of tracks inside a 340px panel would push
   the row's own text off the side of the screen. One column, no rule, heads
   dropped -- every row already carries its field name in .decide-k.
   These sit HERE and not in the block at the top of the sheet: a media query
   adds no specificity, so written up there they would lose to the base rules
   above on source order. */
@media (max-width:999px){
.record-hdr:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:none}
.record-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){grid-template-columns:1fr;gap:var(--s3);align-items:flex-start}
.record-mid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){align-self:auto;padding-left:0;border-left:0}
.record-end:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){justify-content:flex-start}
}

/* Sized to its own column rather than to the panel, so a long explanation
   makes its field taller instead of stretching the whole row. */
.decide-help:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-size:var(--fs-3);color:var(--ink-4);line-height:1.4;
  max-width:calc(260px * var(--ui))}

.decide-worth:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-left:auto;font-family:var(--mono);font-size:var(--fs-3);
  color:var(--gold-dim);text-align:right}

/* ==========================================================================
   THE MUSTER -- the companies, and the one standing open beside them.

   Owner, 2026-09-01: "lets workflow the whole companies tab in the roster i
   want that to look and feel fucking awesome", then "i want it to look and
   feel good NOT POPULATE IT", then "yeah look feel functionality framework for
   when the bot is done... it looks like shit right now... and is clunky to
   use."

   WHAT WAS WRONG WAS NEVER THE CONTENT. Seven near-identical cards filled the
   top third of a 1600px screen and abandoned the rest; the grid resolved to
   four columns so the seventh row was three cells short; the widest run on a
   card about a company was its sergeant's name; and every place inside an
   opened company got a full-width iron plate whether it held six men or one.
   Nothing below adds a fact to the screen. One count is repaired and one
   sentence -- "no discord role", printed fourteen times above the men on the
   Legion -- is deleted rather than restyled.

   .co-slot is DELETED, not kept out of caution: declared, used nowhere, and
   named in .rail's own doctrine as the inferior version of itself -- "and
   :focus-within so a keyboard can reach it, which .co-slot's version could
   not". .co-group is deleted too. It is a rank-group divider and it was used,
   once, to wrap the LAST line of a company card, where its :first-child escape
   hatch can never fire -- so every card carried an accidental 13px indent and
   an accidental --hair-3 rule, the outermost division in this sheet, drawn
   inside a 300px card. Nobody chose that. .co-grid goes with the grid;
   .quote-grid keeps the rule it was sharing.

   ORNAMENT AUDIT
     the plate's rivets     BOUNDARY  the tab is one panel, said once
     the roll row's rule    BOUNDARY  where one company ends
     .co-lead               BOUNDARY  where a place's name ends and the figure
                                      that answers it begins. A leader: the
                                      oldest device in typesetting, and the
                                      only thing on this screen that is not a
                                      letter.
     .rail-act hover/press  STATE     you are here, and you are pressing
     .rail-on               STATE     this company is the one standing open
     .rail-you              WHOSE     yours -- a shape, not a second gold
     .mark-read .mark-lamp  STATE     a machine fills this and is watching it
   THREE OF THE FOUR LAMPS ARE UNSPENT HERE. --glow-owed and --glow-spent are
   left alone on purpose: when the bot starts governing a place there will be a
   real machine state to light, and a screen that has already used its lights
   to say "this is a company" will have nothing left to say it with.
   ========================================================================== */
/* Two names for one grid now: the quotes, and any plate laid beside another.
   The companies left it -- seven cards in an auto-fill grid resolve to four
   columns at 1440px, and seven is 4+3 with a hole. */
.quote-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;gap:var(--s5);
  grid-template-columns:repeat(auto-fill,minmax(calc(280px * var(--ui)),1fr))}

/* THE WHOLE TAB IS ONE PLATE. A grid of cards de-rivets because twelve riveted
   rectangles say "this is a panel" twelve times; a single panel rivets, which
   is the same rule read the other way.

   overflow is the INITIAL value restated, and it is a guard rather than a
   layout: .plate does not clip today, and this is (0,1,0) declared after it,
   so if a later hand gives .plate an overflow the place menus still escape
   their column. Said plainly because a rule that only restates a default
   reads as load-bearing to the next person, and this one is only a latch. */
.muster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){overflow:visible}

/* THE SPLIT. 1000px is the threshold the interface already calls `wide` and the
   one --ui steps at, and renderVals decides which panes exist, so the layout
   and the script cannot disagree about which shape they are in.
   min-height so a four-brother company is FRAMED rather than a stub under a
   full roll -- the plate reaches, and the room shows below it, which is what
   the fixed arch behind this screen has always been for. */
.muster-split:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:stretch;min-width:0;gap:var(--s6);padding:var(--s6)}

@media (min-width:1000px){
.muster-split:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-height:calc(480px * var(--ui))}
}

@media (max-width:999px){
.muster-split:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex-direction:column;padding:var(--s5);gap:var(--s5)}
}

/* A PANEL EACH, WITH THE SAME DEPTH EVERY OTHER BLOCK ON THE SITE HAS. Owner,
   2026-09-11: "it needs sepertion the middle and the left both need tiehr own
   depth panels." One hairline between two panes made them one pane with a rule
   down it; these are two objects now -- lit top edge, cast shadow, the plate's
   own face -- sitting in the plate that holds them both. */
.muster-roll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.muster-one:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;border-radius:var(--r2);
  background:var(--panel-3);background-image:var(--face-1);
  border:1px solid var(--line-3);border-top-color:rgba(210,225,210,.14);
  box-shadow:var(--bevel),var(--cast-2)}

.muster-roll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:0 0 calc(330px * var(--ui));min-width:0;overflow:hidden}

.muster-roll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .rail:last-child{border-bottom:none}

/* A LITTLE AIR BETWEEN THE COMPANIES. Owner: "the companines tab on the left
   needs a little seperation". Seven companies stacked on one rule each read as
   a single ruled block; a company is a thing you pick, not a line you scan.
   The rules stay -- this roll is a list of choices and they mark where one
   ends -- and the room around them grows, which is the answer that has worked
   every other time on this site. */
.muster-roll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .rail{padding-top:var(--s5);padding-bottom:var(--s5)}

.muster-one:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:1 1 auto;min-width:0}

/* A CHAPTER WITH NO COMPANIES IN IT. shownCo is null, so atCoOne is false and
   the roll is the only pane -- and an sc-if compiles to a React.Fragment with
   no wrapper element (support.js:645), so the roll genuinely IS the only child
   here. Without this, the first screen a new chapter ever sees is NO COMPANIES
   YET centred in a 363px gutter with a 528px vertical rule beside it and a
   thousand pixels of empty plate to its right: the abandoned screen this whole
   rebuild exists to kill, in the one state it cannot be argued with. */
.muster-roll:only-child:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:1 1 auto}

@media (max-width:999px){
.muster-roll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:1 1 auto}
}

/* A COMPANY IS A ROW IN A LIST, and everything else about it is the rail's --
   the brass rule under it, the warm hover, the 3px state edge, the
   :focus-within that reveals its own controls. Only the axis changes: two
   lines rather than one, and --s5 top and bottom against .rail's --s4, because
   seven of these are the whole left-hand side of the screen and are read at
   arm's length rather than scanned at a hundred an evening. */
.co-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s5) var(--s7);gap:var(--s5)}

/* THE FIGURE, IN ITS OWN COLUMN, so the seven form a column down the roll
   instead of floating to wherever each name ran out. text-align:right and
   .num's tabular figures, because a column that twitches is not a column. */
.co-tally:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:none;display:flex;flex-direction:column;align-items:flex-end;
gap:var(--s1);text-align:right}

/* A PRESS, which this sheet has never expressed anywhere, and --lift-hard has
   sat in the tokens with no user since it was named. One step of the same warm
   lamp. On a phone it is the only answer a thumb ever gets. */
.muster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .rail-act:active{background:var(--lift-hard)}

/* A KEYBOARD CAN REACH EVERY CONTROL ON THIS TAB NOW, so it has to be able to
   see where it is: the sheet's only focus style was input:focus, and the three
   text boxes were the only focusable things on 8467 lines. Inset by a pixel so
   a focused row does not grow. */
.muster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) :focus-visible{outline:1px solid var(--gold);outline-offset:-1px}

/* A THUMB HAS NO HOVER. .on-hover is opacity-only so a screen reader keeps it
   and :focus-within so a keyboard reaches it -- and a touch device, which has
   neither, lost it completely. Scoped to this tab so no other screen changes. */
@media (hover:none){
.muster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .rail .on-hover,.muster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .co-place-head .on-hand{opacity:1}
}

.co-place:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:0;padding-bottom:var(--s6)}

/* A BIG PLACE FLOWS INTO COLUMNS RATHER THAN RUNNING OFF THE SCREEN. Owner:
   "this just has so much ampty space and scrolls down".

   The split above says plainly why it cannot fix this by itself: "the order of
   places IS the chain of command, and re-ordering to square the columns would
   throw away the one fact the order carries... the room under RESERVES is the
   shape of the 10th, not a fault." That is true of the PLACES. It is not true
   of the MEN inside one: seventy-three reserves have no order worth keeping
   beyond their run headings, and a tower of them is what makes the 10th scroll
   while a third of the window sits empty.

   So the chain of command keeps its order and a crowd stops pretending to be a
   queue. Only a place big enough to be a wall gets this -- below that a second
   column would put two names beside each other and call it a layout. */
.co-men-split:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){column-width:calc(230px * var(--ui));column-gap:var(--s8)}

/* A man, and the run heading above him, must not be torn across the break. */
.co-men-split:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > *{break-inside:avoid}

.co-men-split:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > .rail-group{break-after:avoid}

/* THE ONE GLYPH A PLACE REVEALS. Deliberately NOT named .on-hover: a place
   contains rails, `.rail .on-hover` is already scoped to the rail, and reusing
   the name here would light every man's x the moment a hand entered the place
   -- which is the ninety-glyph screen this replaces, arrived at from the other
   direction. Two reveals at two scales need two names. */
.co-place-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .on-hand{opacity:0;transition:opacity var(--t)}

.co-place-head:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .on-hand,.co-place-head:focus-within:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .on-hand{opacity:1}

/* The confirmation is the menu's own box with the question in it: same
   component, no new one, and nothing floats over the page that the reader did
   not open.

   .pop.co-ask, not .co-ask, and the doubled class is load-bearing: the sheet
   sizes the floating menu a second time in the UTILITIES region at :2162, and
   the utilities land after the components. At (0,1,0) this rule would set a
   measure that a later rule of equal weight silently took back -- the exact
   class of defect the ordering comment on this block exists to prevent. A
   sentence needs more measure than a list of four words does. */
.pop.co-ask:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){gap:var(--s4);padding:var(--s5);min-width:calc(268px * var(--ui))}

.co-empty:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .empty-mark{width:calc(30px * var(--ui));height:calc(30px * var(--ui));
font-size:var(--fs-6)}

.co-empty:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .empty-line{font-size:var(--fs-5)}

/* THE BAR WRAPS ON A PHONE, HERE, because this one carries a decision and not
   only a filter. Below 1000px the shared bar goes flex-wrap:nowrap with
   overflow-x:auto and no scroll affordance of any kind: measured at 390px its
   content was 480px in a 332px box, so a button could sit with its centre off
   the screen and be reached only by guessing that a 27px strip scrolls
   sideways. A control you cannot see is not a control. THE SECOND BUTTON HAS
   SINCE GONE -- `build from discord` was the one measured off the edge, and
   the seed it triggered runs on its own after every worker push -- but the
   rule stays: the search placeholder is the longest on the site at 164px and
   wrapping is still the only way it fits the box it is in. Scoped to .muster
   so no other list screen changes. */
@media (max-width:999px){
.muster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>.bar{flex-wrap:wrap;overflow-x:visible}
.muster:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>.bar>.search{flex:1 1 100%;min-width:0}
}

@media (prefers-reduced-motion:reduce){
/* A guttering candle is still a lit candle: the movement goes, the light
     stays. .anim-enter, .danim and .forge are already cut by the sheet's own
     three hooks; these two are the transitions it does not reach. */
.co-place-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .on-hand{transition:none}
}

/* ==========================================================================
   REDUCED MOTION -- one consolidated block, the same pattern the sheet
   already uses for .act/.btn/.chip and for .boot-leaving. The quote
   crossfade is disabled outright rather than slowed: the outgoing instance
   is pinned to opacity:0 and the incoming to opaque and sharp, so the swap
   still happens, it just happens as a cut instead of a slide-and-blur.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
.quote-in:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.quote-out:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
.quote-in:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){opacity:1;filter:none;transform:none}
.quote-out:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){opacity:0;filter:none;transform:none}
.subtab:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.decide-v:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){transition:none}
}

/* A brother's own Discord picture, filling the same circle the initials use.
   The URL lives in data-lazy-bg until IntersectionObserver sees the circle. A
   CSS background in the rendered row is fetched even when that row is hundreds
   of places below the viewport; the roster used to download every full-sized
   Discord avatar on first open. Initials remain visible until the image loads.

   THE SELECTOR IS DOUBLED and that is load-bearing. `color:transparent` is
   what hides the initials once the picture is behind them, and at (0,1,0) it
   was silently taken back by .c-2 -- also (0,1,0), declared 96 lines later in
   the utilities -- on every face whose class string carries a colour. Measured
   on the companies tab: "avatar avatar-sm c-2 lazy-avatar avatar-face" resolved
   to rgb(169,188,175), so two-letter initials were drawn ON TOP of fifty-three
   brothers' photographs. .avatar.avatar-face is (0,2,0) and cannot lose to a
   utility whatever order the sheet is read in. */
.avatar.avatar-face:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:transparent;background-size:cover;background-position:center;
background-repeat:no-repeat;text-shadow:none}

/* Owner: "put notes in its own section in a bigger text box under overrides",
   and this was 132px tall with 96px of padding under the caret to answer it.
   THE ELEMENT IS AN <input>, not a textarea -- it has always been one line of
   text -- so the height bought no room to write in, only 96px of dead box
   below a caret pinned to the top of it. The mockup draws a single-line
   composer, which is what the control actually is. If he wants the room back
   it is a textarea, not a min-height. */
.notes-box:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:100%;line-height:1.5;
  padding:var(--s5) var(--s6);
  font-family:var(--sans);font-size:var(--fs-5)}

.row-stretch:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:stretch}

.fn:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:none}

.mh0:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-height:0}

.h100:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){height:100%}

.ac:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){align-items:center}

.at:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){align-items:flex-start}

.as:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){align-self:flex-start}

.jc:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){justify-content:center}

.jb:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){justify-content:space-between}

.fill:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute;inset:0}

.abs:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:absolute}

.fixed-fill:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:fixed;inset:0}

.rel:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative}

.clip:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){overflow:hidden}

.scroll:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){overflow:auto}

.scroll-x:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){overflow-x:auto}

.none:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){pointer-events:none}

.p0:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:0}

.p4:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s4)}

.p5:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s5)}

.p6:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s6)}

.p7:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s7)}

.p8:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s8)}

.px5:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s3) var(--s5)}

.px6:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s4) var(--s6)}

.px7:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s5) var(--s7)}

.pad-card:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s6) var(--s8)}

.pad-panel:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s7) var(--s8)}

/* A section heading sits closer to what it introduces than to what is above
it, which is why the three-value paddings existed in eleven places. */
.pad-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s7) var(--s8) var(--s3)}

.pt3:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-top:var(--s3)}

.pt5:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-top:var(--s5)}

.pb7:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-bottom:var(--s7)}

.py5:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--s5) 0}

.m0:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin:0}

.mt1:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-top:var(--s1)}

.mt7:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-top:var(--s7)}

.mx7:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin-left:var(--s7);margin-right:var(--s7)}

/* THE GUTTER, and it is one number so nothing can drift out of line with
   anything else. Every screen used to run to the glass: the roster's first
   column, the reports table and the report's own sticky header all began at
   pixel zero, which on a wide monitor reads as an unfinished window rather
   than a full one. The nav bar above takes the same value, so the sigil, the
   tabs and the content below them share one left edge.

   It scales with the window rather than sitting at a fixed width -- a laptop
   cannot spare 40px and a 32-inch monitor wants more than 12 -- and it is
   applied to the SCROLL CONTAINER, so a sticky header inside a screen is
   inset with everything else instead of sliding out from under it. */
.gutter:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-left:var(--gut);padding-right:var(--gut)}

.num:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-variant-numeric:tabular-nums}

.w5:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-weight:500}

.w6:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-weight:600}

.w7:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-weight:700}

.ls1:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){letter-spacing:var(--ls-1)}

.ls2:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){letter-spacing:var(--ls-2)}

.ls3:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){letter-spacing:var(--ls-3)}

.ls-key:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){letter-spacing:var(--ls-key)}

.ls-ctl:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){letter-spacing:var(--ls-ctl)}

.up:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){text-transform:uppercase}

.tight:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){line-height:1.1}

.snug:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){line-height:1.3}

.c-cool:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-cool)}

.c-6:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-6)}

.c-gold-warm:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-warm)}

.c-gold-dim:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-dim)}

.c-ash:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-ash)}

.c-bad-lit:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--bad-lit)}

/* ---- fills ---- */
.bg-base:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--bg)}

.bg-deep:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--bg-deep)}

.bg-panel3:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--panel-3)}

.bg-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--row-alt)}

.bg-track:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--track)}

.bg-line:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--line)}

.bg-gold:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--gold)}

.bg-good:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--good)}

.bg-bad:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--bad)}

/* ---- edges ---- */
.bd:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border:1px solid var(--gold-line)}

.bd-hair:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border:1px solid var(--hair-2)}

.bd-line:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border:1px solid var(--line-3)}

.bd-gold:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border:1px solid var(--gold)}

.bd-bad:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border:1px solid var(--bad)}

.r1:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-radius:var(--r1)}

.r2:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-radius:var(--r2)}

.rc:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-radius:50%}

.anim-enter:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:enter var(--dur-screen) var(--ease-settle)}

.sticky:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:sticky}

.grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid}

.pre:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){white-space:pre-wrap}

.block:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:block}

.fit:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){object-fit:contain}

.cover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){object-fit:cover}

.pop:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:calc(230px * var(--ui))}

/* A card and a banner already took the plate's lip; they take its cast too, at
   the NESTED step, because they sit inside sections rather than on the page.
   The forge keeps its ember and gains the ground under it. */
.card:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background-image:var(--face-1);box-shadow:var(--bevel),var(--cast-1)}

.forge:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:var(--bevel),var(--cast-1),0 0 24px -14px var(--glow-live)}

/* WHAT YOU TYPE INTO IS CUT INTO THE PANEL, NOT SITTING ON IT. One groove, on
   both text surfaces the app has, so a field and a search box are the same
   depth on every screen. */
.search:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:var(--sink-1)}

/* ---- the four steps as classes, for everything that is not a component ----
   A tile, a bar, a drawer, a reading well. Composable: they set one property
   and expect a border and a ground from the classes already on the element. */
.raise-1:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:var(--bevel),var(--cast-1)}

.sink-2:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:var(--sink-2)}

.face-1:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background-image:var(--face-1)}

/* THE PINNED SURFACE, with a name. The nav, the mobile nav and .bar all solve a
   sticky translucent surface with blur(7px) and .bar's own comment calls that
   "one material"; the AAR header was a fourth pinned surface solving it with a
   hand-written gradient and no blur. background-COLOR rather than the
   shorthand, so a face can be laid over it. */
.glass:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background-color:var(--panel);backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px)}

/* A THING THAT RISES UNDER THE HAND — the tile going from resting on the panel
   to held above it. THE FULL TRANSITION LIST IS RESTATED ON PURPOSE: a bare
   `transition:box-shadow` here lands after .shot at equal specificity and would
   REPLACE its list, and the tile would stop lifting at the moment it was given
   a shadow. If .shot's transition ever changes, change this with it. */
.h-raise:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){transition:box-shadow var(--t),transform var(--t),border-color var(--t),background var(--t)}

.h-raise:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:var(--bevel),var(--cast-2)}

.pip-sm:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:8px;height:8px;flex:none}

.pip-gold:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--gold-warm);border-color:var(--gold-warm)}

/* A reason that actually cost him the points, against one that only notes something. */
.pip-bad:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--bad);border-color:var(--bad)}

/* A BUTTON THAT IS OFF. It was two colour utilities and an inline cursor, so
   the shared layer had a state it could not say. */
.btn-off:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){cursor:not-allowed;color:var(--ink-6);border-color:var(--line-3)}

/* THE DRAWER THAT OPENS UNDER A ROW: an editor cut into the card it belongs to,
   which is why it sinks rather than rises. Its entrance was a CONSTANT passed
   through --danim on three byte-identical elements — the dynamic hook being
   used as a literal. It reuses the sheet's `enter` keyframe, the same one the
   forge opens with. */
.drawer:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:var(--sink-2);animation:enter var(--dur-in) var(--ease-settle)}

.list-cap:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){max-height:calc(230px * var(--ui))}

.pop-wide:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:calc(250px * var(--ui));max-height:calc(320px * var(--ui))}

@media (prefers-reduced-motion:reduce){
.h-raise:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){transition:none}
/* The drawer's entrance plays once and is the same one the forge's is. A
   reader who asked for no motion asked for none. */
.drawer:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
}

/* ==========================================================================
DYNAMIC HOOKS — the only thing a style= attribute may carry.
A value the server computes cannot live in a stylesheet, so it arrives as a
custom property and one of these reads it. See DESIGN.md for the form.
========================================================================== */
.dc:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--c)}

.dbg:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--dbg)}

.dbd:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:var(--dbd)}

.dw:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:var(--dw)}

.dh:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){height:var(--dh)}

.dpad:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:var(--dpad)}

.dgap:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){gap:var(--dgap)}

.dfs:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-size:var(--dfs)}

.dcols:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){grid-template-columns:var(--dcols)}

.danim:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:var(--danim)}

.dflex:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:var(--dflex)}

.dta:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){text-align:var(--dta)}

.dcur:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){cursor:var(--dcur)}

.dshadow:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:var(--dshadow)}

.dstyle:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-style:var(--dstyle)}

/* ==========================================================================
   THE MUSTER — a brother, marked, on every list there is.

   Owner: "id like the users everywhere in the roster to be more awesome
   looking", then "I didnt want the whoele row to be a glass tube just the
   brother...", then "yeah not a fan of the glass bubbles how can we make them
   look badass?" -- and then a picture of the thing he actually wanted, with
   "lets actually make it look more liks this."

   TWO WRONG ANSWERS BEFORE THIS ONE, and they were wrong the same way: both
   put the brother in a CONTAINER -- a glass capsule, then a stamped plate --
   and his mockup has none. A man on a muster roll is not an object in a box.
   He is an index, the numeral of his company, the stamp of his rank and his
   name, laid out on a ruled table, and the only thing drawn around anything is
   the small dark chip his rank sits in.

   WHAT MAKES IT READ is the ruling, not the ornament: full-height hairlines
   between the columns, the brother's own column on a ground a shade lighter
   than the rest, and a legend under the foot of the list. That is a muster
   roll, and it is what the picture is.
   ========================================================================== */
/* THE MARK ITSELF: no ground, no border, no shadow. A row of them IS the list. */
.ident:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:inline-flex;align-items:center;gap:var(--s4);
  min-width:0;max-width:100%}

/* The name is the only part that gives -- and on the two-line form it is the
   COLUMN holding the name that has to. .ellipsis and .col both set a floor of
   their own content, and a flex item's floor is its content, so without this a
   long name pushes the marks off the end instead of truncating. */
.ident:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>.ellipsis,.ident:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>.col{min-width:0}

/* A BROTHER ON A REPORT keeps an edge, because a dozen of them wrap inside one
   cell and have to be told apart -- and the edge is the colour of what his
   sergeant did with him, which is the one piece of state a crew mark carries. */
.ident-tint:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:2px var(--s4) 2px var(--s4);border-radius:var(--r2);
  background:rgba(0,0,0,.22);box-shadow:inset 0 0 0 1px var(--dbd)}

/* THE SAME MARK WITH NOTHING TO SAY. THE RECORD's AS WRITTEN column is the
   brother's own words, so the men he named are drawn without the credit
   colour: a sergeant's decision inside the brother's quoted squad is the
   conflation this record's three columns exist to undo. */
.ident-quiet:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:2px var(--s4);border-radius:var(--r2);
  background:rgba(0,0,0,.22);box-shadow:inset 0 0 0 1px var(--hair)}

/* ---- THE RULED TABLE ----
   .rail-grid centres its cells and spaces them with a gap, which leaves a
   divider nothing to run down: a border on a centred cell is only as tall as
   its own text. So the muster stretches its cells, drops the gap, and pays for
   the spacing with padding -- and the hairline then runs the full height of
   the row, which is what a ruled table is. The header takes the same class, or
   the ruling and the labels part company at the first long name. */
.muster-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){align-items:stretch;gap:0}

/* THE HEADER KEEPS ITS OWN VERTICAL PADDING AND GIVES UP ITS HORIZONTAL. The
   cells below supply that now, and a header that kept both would sit its
   labels 16px to the right of the columns they name. */
.grid-hdr.muster-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-left:0;padding-right:0}

.muster-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>*{display:flex;align-items:center;min-width:0;padding:0 var(--s6)}

.muster-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>*:first-child{padding-left:var(--s7)}

.muster-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>*+*{border-left:1px solid var(--hair)}

.muster-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>*:last-child{justify-content:flex-end;padding-right:var(--s7)}

/* The men are lit a shade above the facts beside them. It is the difference
   between a list of brothers and a spreadsheet about them. */
.muster-grid:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>*:first-child{background:rgba(178,201,177,.025)}

/* ON VOX IS THE ONLY THING WORTH A BADGE. The column used to print OFF VOX six
   hundred times, which is six hundred rows saying nothing -- the fact anybody
   opens this screen for is who is in a channel NOW. Off and unknown are blank,
   and the cell's title still says which of the two it is. */
.vox-badge:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-1);font-weight:500;
  letter-spacing:var(--ls-2);white-space:nowrap;
  padding:2px 6px;border-radius:var(--r2);
  color:var(--good);border:1px solid rgba(127,180,137,.45);
  background:rgba(127,180,137,.08)}

/* THE FOOT OF THE ROLL: what the dashes meant, and how many men were counted. */
/* ROOM FOR A MAN WITH TWO NAMES. Owner, on the roll: "we need some padding
   here." Every other grid on this site puts ONE line in a row, and the 10px of
   --dpad that is right for those wraps a thirty-pixel block here: the muster's
   first cell carries a man's name and his handle stacked, so the roll reads as
   one paragraph of men rather than as a list of them.

   A FLOOR RATHER THAN MORE PADDING, for the reason the record gave when the
   strategium had the same complaint: a row told how short it may be keeps its
   rhythm whether a man has a handle, a vox flag, both or neither. Reports and
   the honours are untouched -- their rows are one line and already sit right. */
/* A BADGE IS THE ROLE'S OWN PICTURE, at the size a name can carry. Discord
   draws these at 32 and up. It went out at 16px, half-lit, on the reasoning
   that a badge must not compete with a man's face -- and the owner read it the
   way it looked: "make the icons a little bigger they are way too small". An
   honour that has to be squinted at is not being shown. 22px is a little over
   a line of type, which is what a thing worn ON a name should be, and the
   dimming is gone: "that is a distinguised icon its important to always show."
   The bytes are asked for at 64 so the picture is still sharp on a retina
   screen at this size. Never allowed to shrink -- a row running out of width must
   take it from the name, which ellipsises, and not from the badge, which would
   simply become a smudge. */
/* AND ROOM AT THE EDGES OF THE ROLL. Owner: "please give the brother column
   some padding now since you removed the old badges." The stencils that used
   to sit in front of a man's face were also, accidentally, his left margin --
   with them gone his portrait starts hard against the plate's own edge, and
   the "brother" heading above him is indented 16px by .muster-grid's first
   cell while he is not.

   PADDING INSIDE THE CELL, never on the row. --dpad is the row's box and
   widening it there would shrink every track by 32px while the header's stay
   full width, walking all six columns to the left of the labels that name
   them. Padding inside the first and last cells moves the ink and leaves the
   tracks exactly where the header put them. The figures at the far end get
   the same treatment for the same reason -- .muster-grid ends on --s7 too, and
   a points column flush to the edge reads as an overflow. */
.muster-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>*:first-child{padding-left:var(--s7)}

.muster-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>*:last-child{padding-right:var(--s7)}

/* THE TICK ON A PICKED REPORT. A box rather than a native checkbox: the rows
   are a grid of hairlines and mono figures, and a platform control would be the
   one thing on the screen drawn by somebody else's designer. Fixed size so a
   ticked row and an unticked one are the same height -- a list that shifts as
   you tick down it is a list you lose your place in. */
.pick-box:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:calc(15px * var(--ui));height:calc(15px * var(--ui));
  border:1px solid var(--line-3);border-radius:var(--r1);
  font-family:var(--mono);font-size:var(--fs-2);line-height:1;color:transparent}

/* HONOURS ARE A DESK VIEW. Owner: "lets remove them from the momile view only
   keep them on the pc view." Uncapped, they are the widest thing on a roster
   row, and a phone row has a name and a handle to fit before it has room for
   ornament. Hidden rather than capped, so the desk view stays complete. */
@media (max-width:999px){
.role-badge:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:none}
}

.role-badge:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:none;width:calc(22px * var(--ui));height:calc(22px * var(--ui));
  object-fit:contain;vertical-align:middle}

.role-badge-lg:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:calc(30px * var(--ui));height:calc(30px * var(--ui))}

.muster-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-height:calc(52px * var(--ui))}

.muster-card:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-top:var(--s7);padding-bottom:var(--s7)}

.muster-foot:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;gap:var(--s5);
  padding:var(--s5) var(--s7);border-top:1px solid var(--hair)}

/* THE ROW A HANDLE NOBODY HOLDS SITS ON, on the honours. */
.row-orphan:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){box-shadow:inset 2px 0 0 var(--ink-6)}

/* ==========================================================================
   THE AAR JUDGING PAGE — the places the owner's mockup and the shared
   components disagree, and the mockup wins for this page alone.

   EVERY RULE HERE IS SCOPED. .mark is the app's one state pill, .btn-gold its
   one primary action, .field its one form control and .pad-card its one body
   inset — the mockup contradicts all four, but only on this screen, and this
   sheet has been burned before by a page's opinion escaping into a component.
   Scoped here means: it lands on the judging page and nowhere else, without
   touching the component or reordering anything.

   Placed after the d* hooks on purpose. .dbg, .dbd and .dcur are `background`,
   `border-color` and `cursor` at (0,1,0) declared last, so a component's own
   declaration always loses to them; a rule that has to survive one of those
   has to sit here AND carry two classes.
   ========================================================================== */
/* ---- the page title block ------------------------------------------------
   Flat on the page ground: the mockup gives it padding and a 1px bottom rule
   and nothing else — no ground, no blur, no shadow and no position. It is the
   DECIDING column that is pinned now (see .aar-side), which is what lets the
   filing scroll under a fixed fireteam rather than under a bar repeating a
   word the fireteam already says. Sides come from .gutter, so the block starts
   on the same left edge as the panels beneath it. */
/* THE DECIDING COLUMN IS A TRACK, NOT A SHARE, and the track is declared on
   .aar-cols now rather than here. This used to be flex:0 1 560px, overriding
   the `dflex` binding (--dflex:1 1 {{ dSideW }}, flex-GROW enabled, which made
   the column a function of the viewport: measured 565px at 1600 and 491px at
   1280 against a drawn 560 at both). Both are inert under a grid, which is
   what the mockup actually declares, so the width is the 560px track and the
   binding stays in the markup untouched and unused. It is left there because
   removing it is a markup change for no gain, and the script still sets it. */
/* IT RUNS THE FULL WIDTH BECAUSE THE COLUMNS DO, and that is the whole rule:
   the way back has to sit on THE RECORD's left edge and WORTH on the
   fireteam's right, so the header can only ever be as wide as the page under
   it. Both were capped at 1636px and centred for an hour; the owner asked for
   full width, .aar-cols lost its cap and THIS ONE WAS LEFT BEHIND -- which is
   how the header came to sit centred and narrow over columns running edge to
   edge. Same two declarations on both, or neither. */
.aar-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:calc(22px * var(--ui)) 0 calc(18px * var(--ui))}

.aar-head-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){gap:var(--s9)}

.aar-ref:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border:0;background:none;padding:0;text-align:left}

.aar-ref:focus-visible:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){outline:1px solid var(--gold);outline-offset:3px}

/* Owner, on the header this block replaces: "we see awaiting judgment back and
   worth its too tall and takes up too much screen." The WORTH figure is the
   tall thing, so it is what comes down -- 26px against the desktop's 40 keeps
   the whole block to one row's height on a phone without losing the figure. */
@media (max-width:999px){
.aar-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-top:var(--s7);padding-bottom:var(--s6)}
.aar-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .worth-n{font-size:var(--fs-9)}
}

/* BACK IS A BUTTON. Owner: "make it an actuall button and give it a little
   space". .btn is already the mockup's declaration character for character —
   mono 500, --fs-2 (its 10px), --ls-ctl (its .12em), uppercase, --r1, 9px
   vertical, --ink-2 — so this adds only the three it differs on: the flex the
   arrow needs a gap in, its 13px sides, and its slightly lifted hairline. */
.btn-back:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:inline-flex;align-items:center;justify-content:center;
gap:var(--s4);padding:9px calc(13px * var(--ui));border-color:var(--hair-2)}

/* WHAT THE REPORT IS WORTH, AS THE PAGE'S HEADLINE FIGURE. It was a 22px mono
   chip in a bordered box down in the fireteam's action row; the mockup sets it
   at 40px Cormorant, unboxed, at the top right of the title block. There is no
   type token at 40 (--fs-9 is 26, --fs-10 is 44), so it is a literal scaled by
   --ui. The colour stays on .dc: {{ dScoreCo }} is a live binding. */
.aar-worth:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding:0 var(--s3)}

/* THE STATE, AS A PILL RATHER THAN A LOOSE WORD. The mockup has no state word
   in its title block at all — it has a headline, and the app has no headline
   binding to draw. So the word stays and takes the shape the mockup DOES use
   for a state, its verdict chip in THE RECORD's head; that also gives
   {{ dBannerBd }} an edge to colour, which is what it was computed for. */
.aar-banner:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-radius:var(--r1);padding:5px var(--s5)}

/* ---- the panel's body ----------------------------------------------------
   THE MOCKUP PADS THE PLATE, NOT THE BLOCKS INSIDE IT: 18px 20px 20px on the
   panel, with the head adding 9px down to its rule and 14px of margin under
   it. The app splits that between .plate-head and .pad-card, which is fine
   until a panel stacks two bodies — then the plate's single 20px foot gets
   paid twice and the 14px under the rule gets paid to a block that is not
   under the rule. These two say it once each: the first body after the head
   takes the 14, the last body in the panel takes the 20, and a body in the
   middle keeps --s6. */
.plate:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > .plate-head + .pad-card{padding-top:calc(14px * var(--ui))}

.plate:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) > .pad-card:last-child{padding-bottom:var(--s8)}

/* THE ONE PANEL THE MOCKUP DOES NOT LIGHT. Every other plate it draws has the
   lifted top edge .plate now carries; THE FIRETEAM is a uniform hairline,
   because it is the one panel whose contents are themselves cards. */
.aar-team:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-top-color:var(--line-3)}

/* THE HEAD BLOCK: title, bar and gate line, closed by ONE hairline.
   The mockup pads this whole block 17/20/14 and rules it off; .plate-head
   already pays the 18 above and the 9 down to its brass rule, so the block
   adds only the 14 underneath and the cool hairline that ends it. The bar and
   the sentence take the side padding the head has, so all three start on the
   same 20px line. 11 and 14 are off the --s ladder and are literals scaled by
   --ui, the way .plate-head's own 18 and 9 already are. */
.aar-team-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-bottom:calc(14px * var(--ui));
border-bottom:1px solid var(--hair)}

.aar-team-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .meter{margin:calc(11px * var(--ui)) var(--s8) 0}

.aar-team-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .gate-line{margin:var(--s5) var(--s8) 0}

/* Keep the compact record legible and its history available on demand.
   The .plate-head-sub/-meta pair that sat here is now the component's own
   default -- every head on the site reads the way this page's did. */
.aar-record:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .decide-k,.aar-record:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .meta-ash{font-size:max(10px,var(--fs-2));color:var(--ink-3)}

.aar-record:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .record-hdr-mid{color:var(--gold)}

.aar-chronicle:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) summary{cursor:pointer;line-height:1.6}

.aar-chronicle:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) summary:hover{color:var(--ink)}

.chronicle-hint:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink-3);font-size:max(10px,var(--fs-3));margin-left:var(--s4)}

.chronicle-body:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){padding-top:var(--s6)}

.chronicle-value:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){white-space:pre-line;overflow-wrap:anywhere}

.record-dispatch:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){margin:0;white-space:pre-wrap;overflow-wrap:anywhere}

.crew-meta:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.crew-owner:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){overflow-wrap:anywhere;line-height:1.5}

.crew-card:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .crew-meta,.crew-card:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .crew-owner{color:var(--ink-3);font-size:max(11px,var(--fs-3))}

@media(max-width:600px){
.aar-head-row:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){gap:var(--s4);align-items:center}
.aar-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .aar-banner{flex:1;min-width:0;padding:var(--s3);gap:var(--s3)}
.aar-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .aar-banner>span{white-space:normal;font-size:11px;letter-spacing:.05em}
.aar-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .aar-worth{padding:0}
.aar-team:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .plate-head,.aar-evidence:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .plate-head{flex-wrap:wrap;gap:var(--s3) var(--s5)}
.aar-team:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .plate-head > .plate-head-meta{flex-basis:100%;margin-left:0}
.crew-identity:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:grid;grid-template-columns:auto minmax(0,1fr);align-items:start}
.crew-identity:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .crew-state{grid-column:2;justify-self:start;padding:var(--s2) var(--s4)}
}

/* zoom-in is a promise, and a tile whose bytes were never fetched cannot keep
   it. .shot-slot is what a tile with no picture draws, so it is the test. */
.gallery:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .shot:has(.shot-slot){cursor:default}

/* TWO CHIPS IN ONE CORNER HAVE TO BE ONE HEIGHT. .mark grew to 6px/10px for
   the auspex verdict, which put a 28px pill beside the 19px number badge on
   every tile. Inside a tile it keeps the badge's own 2px/6px. */
.shot-tags:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .shot-mark{gap:var(--s2);padding:var(--s1) var(--s3)}

/* ---- the auspex verdict --------------------------------------------------
   5px/10px is the mockup's pill, and it draws NO halo: the lamp on .mark-lamp
   marks a machine at work, and a verdict that has already been reached is not
   one. The dot stays 6px, which is the mockup's exactly. */
.aar-auspex:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .shot-mark{padding:5px var(--s5);box-shadow:none}

.aar-auspex:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .shot-mark[data-verdict]::before{box-shadow:none}

/* ---- the ledger notes well -----------------------------------------------
   A fixed well the log sits on the FLOOR of and grows upward out of, so a
   report with two notes and a report with twenty are the same height and the
   newest line is always in the same place. --sink-2 is inset 0 2px 8px
   rgba(0,0,0,.6) against the mockup's .65, near enough not to mint a seventh
   shadow for. */
.notes-well:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){height:calc(188px * var(--ui));
border:1px solid rgba(178,201,177,.12);border-top-color:rgba(0,0,0,.5);
border-radius:var(--r1);background:rgba(6,11,9,.62);box-shadow:var(--sink-2);
padding:calc(13px * var(--ui)) calc(14px * var(--ui)) calc(14px * var(--ui))}

.log-line:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){line-height:1.65}

/* The composer's edge is a hairline here, not the app's gold field edge: the
   mockup keeps gold for the button beside it, and two golds in one row is the
   emphasis spent twice. */
.aar-notes:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .field{border-color:var(--hair);background:rgba(8,14,11,.6)}

.aar-notes:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .btn-outline{background:rgba(201,164,92,.12);color:var(--gold-warm);
letter-spacing:var(--ls-ctl)}

/* The held / barred sentences under the action row. Mono at 1.7, which is the
   mockup's gate line; the sheet's .lead is 1.45 and has no looser step. */
.gate-line:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){line-height:1.7;text-wrap:pretty}

/* ---- the brother card ----------------------------------------------------
   TWO UTILITIES WERE EATING THE COMPONENT. .dbg is the `background` SHORTHAND,
   so on `plate plate-bare crew-card dbg dbd` it reset background-image to none
   and background-color to {{ m.dbg }} — the literal string "transparent" for
   every man not yet credited. The cards had no fill at all. .dbd did the same
   to the border, painting the saturated status colour on all four sides where
   the mockup wants a hairline and a 3px inset bar down the left. Both are
   repaired at (0,2,0), which wins without reordering and without touching the
   markup, so {{ m.dbg }} and {{ m.bd }} keep their jobs — the tint becomes the
   underlayer the mockup actually draws it as, and the status colour becomes
   the accent rail it actually draws it as. */
.crew-card.dbg:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:linear-gradient(180deg,rgba(32,49,39,.85),rgba(15,25,20,.55)),var(--dbg)}

.crew-card.dbd:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:var(--line-3);border-top-color:rgba(210,225,210,.16);
box-shadow:inset 3px 0 0 var(--dbd),var(--lip),var(--cast-1)}

@media (max-width:1199px){
.desk-cards:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){grid-column:1 / -1}
}

@media (max-width:799px){
.desk-cards:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){grid-column:1}
}

.desk-pane:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;min-width:0;border-radius:var(--r2);
  background:var(--panel-3);background-image:var(--face-1);
  border:1px solid var(--line-3);border-top-color:rgba(210,225,210,.14);
  box-shadow:var(--bevel),var(--cast-2)}

.desk-pane-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;gap:var(--s4);
  padding:var(--s5) var(--s5) var(--s3);border-bottom:1px solid var(--rule-head);
  font-family:var(--mono);font-size:var(--fs-1);letter-spacing:var(--ls-meta);
  text-transform:uppercase;color:var(--ink-5)}

.desk-cards:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;flex-direction:column}

/* A CARD PER JOB, SHUT UNTIL YOU OPEN IT. Owner: "each one of those things
   would be a card that can be expanded / opened." */
.desk-card:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-bottom:1px solid var(--hair)}

.desk-card:last-child:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-bottom:none}

.desk-card-head:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;align-items:center;gap:var(--s4);cursor:pointer;
  padding:var(--s4) var(--s5);transition:background var(--t)}

.desk-card-head:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(178,201,177,.05)}

.desk-fold:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex:none;width:12px;text-align:center;font-family:var(--mono);
  font-size:var(--fs-4);color:var(--ink-5)}

.desk-card-open:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(201,164,92,.05)}

.desk-card-open:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .desk-fold{color:var(--gold)}

.desk-card-body:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){display:flex;flex-direction:column;gap:var(--s4);
  padding:0 var(--s5) var(--s5) calc(var(--s5) + 12px)}

/* HIS POSITION, BESIDE HIS COMPANY. Gold and boxed when it is the reader's own
   squad, so "who is in my squad" is one glance rather than three lines of
   "awaiting". Owner, 2026-09-11. */
.crew-place:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){font-family:var(--mono);font-size:var(--fs-2);letter-spacing:var(--ls-1);
  color:var(--ink-4);white-space:nowrap}

.crew-place-mine:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-warm);border:1px solid var(--gold-line);border-radius:2px;
  padding:0 var(--s3);background:rgba(201,164,92,.10)}

.crew-group-mine:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .decide-k{color:var(--gold-warm)}

.crew-acts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .bg-bad{border-color:var(--warn);background:rgba(181,112,90,.3)}

.crew-lead:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;font-weight:600}

/* The lit top edge and the ring are what carry it at a glance; the fill alone
   reads as a hover on a dark ground. */
.crew-acts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .crew-lead{box-shadow:inset 0 1px 0 rgba(255,255,255,.3),inset 0 -1px 0 rgba(0,0,0,.35),
  0 3px 0 #07100B,0 5px 7px rgba(0,0,0,.3),0 0 12px -5px var(--lead-ring);
  background-color:var(--lead-bg);border-color:var(--lead-bd);color:var(--lead-ink)}

/* Raised keys become recessed only after a recorded decision. */
.crew-acts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .btn.crew-pressed{
  transform:translateY(2px);background-image:linear-gradient(180deg,rgba(0,0,0,.35),rgba(0,0,0,.06) 60%,rgba(215,240,220,.04));
  box-shadow:inset 0 3px 6px rgba(0,0,0,.7),inset 1px 0 3px rgba(0,0,0,.3),0 1px 0 rgba(205,230,210,.18);
  border-top-color:rgba(0,0,0,.6)}

/* ACCEPT LEADING: the report is clean and he is owed his points. */
.crew-lead-good:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){--lead-bg:rgba(127,180,137,.32);--lead-bd:var(--good);
  --lead-ring:rgba(127,180,137,.45);--lead-ink:#EAF5EA}

/* REFUSE LEADING: somebody has already said no, and the card should say so
   louder than the button that would undo it. */
.crew-lead-bad:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){--lead-bg:rgba(155,44,36,.42);--lead-bd:var(--bad-lit);
  --lead-ring:rgba(196,104,95,.45);--lead-ink:#F6E6E4}

/* ESCALATE LEADING: it is with the CO, and neither of the other two applies. */
.crew-lead-esc:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){--lead-bg:rgba(122,146,196,.30);--lead-bd:var(--esc-lit);
  --lead-ring:rgba(122,146,196,.45);--lead-ink:#E4EBF8}

.crew-acts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .crew-lead-live{animation:crew-breathe 2.4s ease-in-out infinite}

.aar-accept-fx:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:relative;isolation:isolate}

.aar-accept-locked:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){cursor:default;pointer-events:none}

/* The original green plate stays. A contained power sweep charges its edges. */
.aar-accept-ready:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::before,.aar-accept-ready:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::after{content:"";position:absolute;pointer-events:none}

.aar-accept-ready:not(.crew-lead):where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(127,180,137,.32);border-color:var(--good);color:#EAF5EA}

.aar-accept-ready:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::before{inset:-1px;border:1px solid transparent;
  background:linear-gradient(110deg,transparent 28%,rgba(124,239,169,.65) 44%,#EDFFF1 49%,rgba(124,239,169,.65) 54%,transparent 70%) border-box;
  background-size:320% 100%;padding:1px;
  mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask-composite:exclude;
  animation:seal-charge 3.6s cubic-bezier(.4,0,.2,1) infinite}

.aar-accept-ready:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::after{inset:1px;overflow:hidden;
  background:linear-gradient(110deg,transparent 34%,rgba(111,229,160,.07) 43%,rgba(201,255,220,.25) 49%,rgba(111,229,160,.07) 55%,transparent 64%);
  background-size:320% 100%;animation:seal-charge 3.6s cubic-bezier(.4,0,.2,1) infinite}

.approval-veil:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){position:fixed;inset:0;z-index:65;display:grid;place-items:center;padding:24px;background:rgba(4,12,8,.84)}

.approval-dialog:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){width:min(100%,480px);padding:24px;border:1px solid var(--gold);background:#13231B;box-shadow:0 20px 80px #000}

@media (prefers-reduced-motion:reduce){
.aar-accept-ready:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::before,.aar-accept-ready:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))::after{display:none}
}

/* A guttering candle is still a lit candle: the movement goes, the light stays
   -- the same rule the sheet applies to every other animation it carries. */
@media (prefers-reduced-motion:reduce){
.crew-acts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .crew-lead-live{animation:none}
}

/* THE TWO THAT ARE NOT LEADING still have to be legible: the quiet base is a
   hairline on nothing, which beside a lit button reads as disabled. */
.crew-acts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .btn:not(.crew-lead){background-color:rgba(178,201,177,.05)}

.crew-acts:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .btn:not(.crew-lead):hover{background-color:rgba(178,201,177,.12);
  border-color:var(--tile-edge)}

/* An unclaimed handle's mark. It was an EMPTY .mark — a 24x15 orange-bordered
   rectangle with a halo and nothing in it, because .mark sizes itself off a
   label and this one has none. The mockup's state markers are all small round
   dots, so it becomes one. */
.pip-warn:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--warn-2);border-color:var(--warn-2)}

/* ==========================================================================
HOVER — was 16 distinct style-hover attributes on 42 elements.
========================================================================== */
.h-ink:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--ink)}

.h-warm:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--gold-warm)}

.h-bad:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--bad-lit)}

.h-bad-line:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){color:var(--bad-lit);border-color:var(--bad)}

.h-gold-line:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-color:rgba(201,164,92,.5);color:var(--gold-warm)}

/* FIVE HOVERS THAT DIFFERED ONLY IN THE ALPHA OF ONE GREEN -- .7, .6, .5, .8,
   .6 -- across eighteen elements. Nobody chose that; it accreted, one element
   at a time, each new one guessing at a value near the last.
   
   There is one lamp in this room now and it is WARM, because the reader's own
   attention is the light and light has a source. Green-on-green said "the
   surface changed"; brass says "you are here". Two shapes, not five values:
   a lift, and a lift with a rail down the edge for a row in a list.
   
   The names are kept as they are so no markup moves; they are aliases of one
   behaviour rather than five behaviours with one look. --lift and --lift-hard
   are the only two values, and .rail-act:hover uses the same --lift, so a row
   in a list and a tile in a grid answer the hand identically. */
.h-lift-soft:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--lift)}

.h-bad-fill:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--bad);color:var(--ink)}

.h-gold-fill:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--gold);color:var(--ink-on-gold)}

.h-good-fill:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:rgba(121,196,141,.16)}

.h-ink-fill:hover:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){background:var(--ink)}

@media (prefers-reduced-motion:reduce){
.act:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){transition:none}
.anim-enter:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
}

/* ==========================================================================
THE GATE CLOSING

Each piece of the boot panel replays ITS OWN ENTRANCE BACKWARDS -- the same
four keyframes with direction:reverse, not four new ones written to look
like the opposite. Two sets would drift the first time one was tweaked; one
set cannot.

The !important is doing real work rather than papering over specificity:
every one of these elements carries its entrance in an inline style
attribute (they are one-off, screen-specific motion), and an inline
animation is otherwise unbeatable by a stylesheet.

Delays run outward from the middle: the mark goes first, the rule and title
follow, the lines last, and the panel's own fade starts at 280ms over the
top of all of it.
========================================================================== */
.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-mark{animation:bootmark .34s cubic-bezier(.5,0,.75,0) reverse both!important}

.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-line{animation:bootline .3s .06s cubic-bezier(.5,0,.75,0) reverse both!important}

.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-title{animation:track .34s .04s cubic-bezier(.5,0,.75,0) reverse both!important}

.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-row{animation:tickin .26s .1s ease-in reverse both!important}

@media (prefers-reduced-motion:reduce){
.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-mark,.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-line,.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-title,.boot-leaving:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)) .boot-row{animation:none!important}
}

/*
 * AND EVERY ANIMATION WRITTEN IN THE MARKUP, which is most of them.
 *
 * An inline `style="animation:…"` beats a stylesheet rule unless the rule
 * carries !important -- the block above already knows this, and says so, and
 * then the lesson is applied nowhere else in the file. So a reader who asked
 * for no motion still got the boot mark scaling in, the reticle pulsing, the
 * rule drawing itself, the title's tracking collapsing, the rows ticking in,
 * the loading hairline, the sign-in scanline, the button's glow, the nav badge,
 * the spinner and the reader panel's shimmer. FORTY-FOUR inline animations, and
 * the one rule that existed killed only the boot gate's EXIT -- so they saw the
 * whole entrance and then a hard cut. The asymmetry, backwards.
 *
 * `--danim` is not the string "animation:", so the two hooks have to be named
 * separately. Nothing in the app waits on animationend, so cancelling them
 * cannot strand a state: leaveBoot() runs on a plain 720ms timer and says so.
 */
@media (prefers-reduced-motion:reduce){
.danim:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none!important}
/* The title's tracking is the END STATE of `track`, not a declaration --
   nothing in the sheet gives .boot-title a letter-spacing -- so cancelling the
   animation would leave CALIBAN LEDGER at default. Declared here it also fixes
   the snap-back the .boot-leaving rule above already causes. */
.boot-title:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){letter-spacing:.22em}
/* Stops the movement and keeps the light, which is the candles' own rule. */
.scanning:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.scan-glyph-reading:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)),.tally-scan:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none}
}

/* ==========================================================================
THE PHONE

Owner: "the mobile view is a little fucked... its missing the bottom nav it
used to have. and the headers and such are ugly."

Measured at 390px before touching anything, because two of the three were
worth checking rather than assuming:

- THE SIZE IS NOT THE PROBLEM. --ui is 1 below 1000px, so the desktop 10%
never reached a phone. Left alone.
- THE BOTTOM NAV IS THERE and correctly pinned: six items, 62px tall,
sitting at 782-844 in an 844 viewport. Nothing to restore.
- THE HEADERS REALLY ARE UGLY, and the measurement says why. On the
reports screen the tool bar is 135px TALL and STILL 555px wide inside a
381px column: a search box, a five-chip group 370px wide and another
539px wide. flex-wrap cannot fix that -- one group is wider than the
phone on its own -- so wrapping only built a three-storey tower that
overflowed anyway.

A tool bar on a phone is therefore ONE SCROLLING LINE, which is what every
other application does with a filter strip and what the wrap was failing to
be. The spacer goes: it exists to push the count to the far right of a wide
bar, and inside a scrolling line it is 255px of nothing.
========================================================================== */
@media (max-width:999px){
/* THE ONE BAR THAT MUST NOT SCROLL. A .pop is positioned against its control
   and an overflow:auto parent clips it, so a filter menu opened inside a
   scrolling line would be cut off at the bar's own edge. It no longer needs
   the scroll anyway -- that rule exists because seventeen chips could not fit
   on a phone, and four controls wrap onto two comfortable rows. */
.bar-filt:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){flex-wrap:wrap;overflow:visible}
.bar-filt:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *))>.spacer{display:none}
.search:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:150px}
/* A column that fills the screen has nothing to its right to be divided
from, and its 1px edge was the whole of a 5px overflow on statistics. */
.hair-r:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){border-right:none}
/* The band of big numbers is two-up on a phone rather than four-across at
130px each, which is narrower than the numbers it carries. */
.total:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){min-width:44%}
}

@media (prefers-reduced-motion:reduce){
.ember:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none!important;opacity:0;filter:none}
}

/* ==========================================================================
   § 4 · MOTION — the 29 @keyframes the new sheet lacks.

   The inline block defines 32 (a 33rd match is the words "@keyframes name"
   inside a comment, not a rule). ledger.css defines three of them — enter,
   flicker and shimmer — and those three are NOT carried: the redesign gets to
   redefine its own motion, and a duplicate here would win on source order and
   quietly overrule it.

   TWO OF THE THREE ARE NOT THE SAME ANIMATION, and a reader should know which
   way that cuts. `shimmer` costs nothing — the old one slides a highlight
   (translateX -140% → 340%), the new one slides a background-position, and no
   carried rule names it. `enter` is a fade-and-rise in both; the new one
   rises 6px instead of 9px, so .anim-enter, .drawer and .forge arrive very
   slightly shallower. `flicker` is the one to watch: the old one is a
   five-stop loop that returns to where it started, the new one runs 0% → 100%
   without closing the circle, and .skull-fire > i runs it `infinite` — so his
   flame will step rather than breathe until ledger.css's flicker is given a
   100% that matches its 0%, or is set to `alternate` the way ledger.css's own
   .candle already does.
   ========================================================================== */

@keyframes scanrow{0%,100%{background:rgba(122,146,196,.05)}50%{background:rgba(122,146,196,.14)}}

@keyframes scanmark{0%,100%{opacity:.4}50%{opacity:1}}

@keyframes crackout{
  0%   {opacity:1;   transform:translate(-50%,-50%) scale(.12)}
  100% {opacity:0;   transform:translate(-50%,-50%) scale(1)}}

@keyframes bloomout{0%{opacity:1}100%{opacity:0}}

@keyframes rattle{
  0%   {transform:translate(0,0)}
  12%  {transform:translate(-6px,3px)}
  26%  {transform:translate(5px,-4px)}
  42%  {transform:translate(-4px,-2px)}
  58%  {transform:translate(3px,3px)}
  74%  {transform:translate(-2px,-1px)}
  88%  {transform:translate(1px,1px)}
  100% {transform:translate(0,0)}}

@keyframes shatterfade{
  0%   {opacity:0}
  4%   {opacity:.92}
  34%  {opacity:.66}
  100% {opacity:0}}

@keyframes smokeup{
  0%   {transform:translate(0,0) scale(.45);opacity:.55}
  100% {transform:translate(-6px,-30px) scale(2.1);opacity:0}}

@keyframes skullbeam{0%{top:0;opacity:0}12%{opacity:1}88%{opacity:1}100%{top:100%;opacity:0}}

@keyframes quotein{
  0%{opacity:0;filter:blur(6px);transform:translateY(10px)}
  100%{opacity:1;filter:blur(0);transform:translateY(0)}
}

@keyframes quoteout{
  0%{opacity:1;filter:blur(0);transform:translateY(0)}
  100%{opacity:0;filter:blur(6px);transform:translateY(-14px)}
}

/*
 * AND THE RECOMMENDED ONE BREATHES. Owner, 2026-09-04: "the buttons that
 * recomend the sgt judgment needs to shimmer green on the accepts or red on the
 * refuse."
 *
 * ONLY WHEN IT IS A RECOMMENDATION, which is the distinction the animation has
 * to carry: a card that is already settled lights its button to say SO, and a
 * settled state that pulsed would read as asking to be pressed again. So the
 * shimmer rides .crew-lead-live, set only where nothing has been decided yet
 * and the auspex has an opinion -- see the bindings.
 *
 * Brightness carries the pulse so the raised key keeps its bevel and shadow.
 * Settled controls keep their colour and sit recessed without pulsing.
 */
@keyframes crew-breathe{
  0%,100%{filter:brightness(1)}
  50%{filter:brightness(1.17)}
}

@keyframes seal-charge{0%,18%{background-position:110% 0;opacity:0}24%{opacity:1}
  70%{background-position:-10% 0;opacity:1}82%,100%{background-position:-10% 0;opacity:0}}

@keyframes led{0%,100%{opacity:.28}50%{opacity:1}}

@keyframes scanv{0%{transform:translateY(-12%)}100%{transform:translateY(1100%)}}

@keyframes rise{0%{opacity:0;transform:translateY(6px)}100%{opacity:1;transform:translateY(0)}}

@keyframes glow{
  0%,100%{box-shadow:inset 0 1px 0 rgba(255,236,180,.55),inset 0 0 10px -3px rgba(255,236,180,.28)}
  50%    {box-shadow:inset 0 1px 0 rgba(255,236,180,.55),inset 0 0 18px 0 rgba(255,236,180,.66)}}

@keyframes fadein{0%{opacity:0}100%{opacity:1}}

@keyframes spin{100%{transform:rotate(360deg)}}

@keyframes bootmark{0%{opacity:0;transform:scale(.55) rotate(-10deg)}70%{opacity:1;transform:scale(1.04) rotate(0)}100%{opacity:1;transform:scale(1)}}

@keyframes bootline{0%{transform:scaleX(0)}100%{transform:scaleX(1)}}

@keyframes bootout{0%{opacity:1;transform:scale(1);filter:blur(0)}100%{opacity:0;transform:scale(1.045);filter:blur(3px);visibility:hidden}}

@keyframes track{0%{letter-spacing:.6em;opacity:0}100%{letter-spacing:var(--ls-brand);opacity:1}}

@keyframes tickin{0%{opacity:0;transform:translateY(-7px)}100%{opacity:1;transform:translateY(0)}}

@keyframes glowtext{0%,100%{text-shadow:none}50%{text-shadow:0 0 16px rgba(59,200,178,.55)}}

@keyframes hairline{0%{transform:translateX(-100%)}100%{transform:translateX(400%)}}

@keyframes reticle{0%,100%{opacity:.35}50%{opacity:1}}

@keyframes emberrise{0%{transform:translate3d(0,0,0);opacity:0}4%{opacity:var(--peak,.6)}50%{transform:translate3d(var(--dx,12px),-47vh,0)}66%{opacity:var(--peak,.6)}86%{opacity:0}100%{transform:translate3d(0,-94vh,0);opacity:0}}

@keyframes emberglow{0%,100%{filter:brightness(.74)}50%{filter:brightness(1.16)}}

@keyframes flame{0%,100%{transform:scaleY(1) scaleX(1);opacity:.92}30%{transform:scaleY(1.14) scaleX(.92);opacity:1}55%{transform:scaleY(.9) scaleX(1.06);opacity:.8}78%{transform:scaleY(1.06) scaleX(.97);opacity:.97}}

/* THE REDUCED-MOTION GUARD, which is not a class rule and is carried anyway.
   `[style*="animation:"]` cancels animations written into a style attribute —
   the boot sequence and the embers set theirs from JS, so a class-based
   override cannot reach them. ledger.css has no equivalent. Dropping it would
   not unstyle anything; it would just quietly stop honouring a preference,
   which is worse, because nothing would look wrong. */
/* AND IT HAD FALLEN OUT OF THE MEDIA QUERY IT BELONGS TO, which is the whole
   of why the owner said "i want the embers back". At top level it did not
   honour a preference, it enforced one: `animation:none!important` on every
   element inside `.ledger` carrying an inline animation, for every reader,
   always. Twenty-two embers were in the page the entire time with their
   animations cancelled, and the boot sequence with them. Silent in exactly the
   way the rest of this cutover has been silent -- a thing that does not move
   looks the same as a thing that was never there -- so the wrapper is written
   explicitly around it here rather than left to whichever block precedes it. */
@media (prefers-reduced-motion:reduce){
  [style*="animation:"]:where(.ledger,.ledger *):not(:where(.oldskool,.oldskool *)){animation:none!important}
}
