/* ===== Death animations (TASK-24) =====
   See docs/death-animations.md. Four classes, four vectors, one shape:
   beat 1 is the event (arrival + tight jiggle + drain to grey) landing on a
   single frame; beat 2 is the slow tilt, and whatever did it stays.

   `natural` deliberately breaks that shape — no impact, no jiggle. An old-age
   death that jiggled would read as a murder.

   OWN FILE, not styles.css: styles.css is under concurrent edit by parallel
   work and this repo has no version control, so a shared write clobbers rather
   than merges. Linked from index.html.

   THE STACK, shared by every class:
     .icon-rays          z 0   the decorative burst, when present
     .death-under        z 0   blood / swarm — later in DOM, so it clears the rays
     the portrait        z 1   forced up, so the face stays readable
     .death-over         z 3   blade / antlers, over everything             */

/* ===== THE HEAD TURN — ONE DEFINITION, EVERY DEATH =====
   Iterate the turn HERE and all four classes follow. It is a custom property so
   the keyframe, the resting pose and any future class all read the same value
   from one line.
   TRANSFORM ONLY. What colour a class rests in is a separate concern: most rest
   grey, disease rests GREEN, and mixing the two would mean editing the turn in
   four places. */
.death-host {
  position: relative;
  /* THE HEAD TURN — all of it, for every death type. Split into components so
     the exact INVERSE can be derived below instead of hand-maintained.
     translate Y is the SINK: keep it small so the head cocks rather than slides.
     The easing overshoots (1.35) so the head snaps over and settles. */
  --death-turn-deg: 40deg;
  --death-turn-x: 2%;
  --death-turn-y: 4%;
  /* Pivot at the CHIN, not the middle of the box. A centre pivot swings the head
     like a dial; pivoting low makes it loll over the way a neck gives out. The
     side effect is that the skull swings RIGHT and can overlap a neighbour,
     which is correct: a dead head is not politely inside its own box. */
  --death-turn-origin: 50% 78%;
  --death-turn-ms: 300ms;
  --death-turn-ease: cubic-bezier(.2, 1.35, .4, 1);

  --death-turn: translate(var(--death-turn-x), var(--death-turn-y))
                rotate(var(--death-turn-deg));
  /* DERIVED, never hand-edit: the exact inverse, in reverse order. Layers that
     must not tumble with the head run this against the same origin, duration and
     easing, which cancels the turn at EVERY instant of the animation, not just
     at the end. Editing the knobs above updates both sides at once. */
  --death-unturn: rotate(calc(-1 * var(--death-turn-deg)))
                  translate(calc(-1 * var(--death-turn-x)),
                            calc(-1 * var(--death-turn-y)));

  transform-origin: var(--death-turn-origin);

  /* PALLOR, NOT SHADOW. These used to darken (brightness < 1), which reads as a
     portrait falling into shade rather than a body going bloodless. Brightness
     now goes UP while saturation and contrast come DOWN: the colour drains out
     and the face turns waxy. Measured against the art's mid skin tone #d6aa8a,
     which lands at #d3ccc7 — L61%/S15% before, L80%/S12% after. Hair is left
     around L35% deliberately, so the portrait pales without dissolving. */
  --death-rest-gray: grayscale(0.85) brightness(1.24) contrast(0.85);
  /* HAND-TUNE THE SICK GREEN HERE. Two knobs that matter:
       hue-rotate = WHICH green. 95deg lands the filtered skin at hue 139,
                    one degree off brand Emerald #3CBD67 (hue 140).
       saturate   = HOW green. 1.0 -> ~28% sat. Raising it back toward 1.7
                    returns the lime-poison look this replaced (hue 90).
     Measured against the portrait's mid skin tone #d6aa8a -> #81b892. */
  --death-rest-green: sepia(0.55) hue-rotate(95deg) saturate(0.9) brightness(1.12) contrast(0.9);
  --death-rest-natural: grayscale(0.8) brightness(1.2) contrast(0.88);
}

/* Lift the real content above the under-layer. Needed even with NO rays present:
   an absolutely-positioned layer at z-index 0 paints OVER static in-flow content,
   so without this the blood would cover the face.

   The class is applied by playDeath, and ONLY to children that compute to
   position:static — which is the whole point of doing it in JS. The obvious CSS
   version, `.death-host > :not(.icon-rays):not(...)`, is a (0,5,0) selector that
   overrides any child's own positioning: it drops an absolutely-placed badge (the
   duel slider's crown, which floats above the ruler's head) back into the flow.
   A child that positions itself already paints above a z-0 layer and needs no
   help; only static children do. That also makes the rays opt-outs unnecessary,
   since .icon-rays / .icon-rays-core are both absolute. */
.death-host > .death-face-lift {
  position: relative;
  z-index: 1;
}
.death-under,
.death-over {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
  /* Same box as the host (inset:0) and the same pivot, which is what makes
     --death-unturn an exact inverse rather than an approximation. */
  transform-origin: var(--death-turn-origin);
}
.death-under { z-index: 0; }
.death-over  { z-index: 3; }
/* .rays-overlay stops the rays rule dropping these into the flow, but it also
   parks every opt-out at z-index 2 — in FRONT of the face, wrong for the under
   layer. Three classes (0,3,0) beats its (0,2,0) outright, no file-order luck. */
.icon-rays-host > .death-under.rays-overlay { z-index: 0; }
.icon-rays-host > .death-over.rays-overlay  { z-index: 3; }

/* ---- shared beat-1 / beat-2 motion, used by every class but `natural` ---- */
/* A tight damped rattle that RETURNS TO CENTRE, so beat 2 has a clean zero to
   tilt away from. Small amplitude: this is a flinch, not a swing. */
@keyframes death-jiggle {
  0%   { transform: none; }
  15%  { transform: translate(-6%, 1%) rotate(-5deg); }
  30%  { transform: translate(5%, 2%)  rotate(4deg); }
  45%  { transform: translate(-4%, 1%) rotate(-3deg); }
  60%  { transform: translate(3%, 1%)  rotate(2deg); }
  78%  { transform: translate(-2%, 0)  rotate(-1deg); }
  100% { transform: none; }
}
/* The colour going is what says "dead" on a round emoji head, where rotation
   barely registers. */
@keyframes death-gray {
  0%   { filter: none; }
  100% { filter: var(--death-rest-gray); }
}
/* Reads --death-turn above; the -hit classes supply only the DELAY, so duration
   and easing stay single-sourced. */
@keyframes death-tilt {
  0%   { transform: none; }
  100% { transform: var(--death-turn); }
}
/* Cancels death-tilt for things that are NOT part of the head. Blood pools on
   the floor, germs hang in the air, and a departing soul has already let go, so
   none of them should tumble when the neck gives out. The blade and the antlers
   are deliberately NOT here: those are embedded in the skull and travel with it.
   Delay is per beat, so each layer cancels its own head's turn exactly. */
@keyframes death-unturn {
  0%   { transform: none; }
  100% { transform: var(--death-unturn); }
}
.death-hit         > .death-under { animation: death-unturn var(--death-turn-ms) 420ms var(--death-turn-ease) forwards; }
.death-hit-hunting > .death-under { animation: death-unturn var(--death-turn-ms) 500ms var(--death-turn-ease) forwards; }
.death-hit-disease > .death-under { animation: death-unturn var(--death-turn-ms) 860ms var(--death-turn-ease) forwards; }
/* The splat and the germs OUTLIVE the beat, so their cancellation must too. */
/* The soul used to need a counter-turn here; it is drawn outside the host now,
   so it never inherits the turn in the first place. */
.death-dead > .death-under { transform: var(--death-unturn); }
/* IDENTICAL to the dagger's splat: same art, same placement, same size, same
   spring. The ONLY difference is when it fires, because the goring lands at
   300ms and the blade at 220ms, and the blood has to appear on contact. */
.death-splat.death-splat-gore { animation-delay: 300ms; }

/* The settled state OUTLIVES the animation: the module drops the -hit class when
   the beat ends, and without this the portrait snaps upright. Transform and
   filter are SEPARATE classes so a class can rest green instead of grey without
   redefining the turn. ALL FOUR death types rest via .death-dead. */
.death-dead          { transform: var(--death-turn); }
/* THE TINT LIVES ON THE FACE, NEVER ON THE HOST. filter on a parent applies to
   every descendant, so putting the pallor on the host drained the blood splat
   and the germs along with the portrait. Those are separate objects lying on the
   scene, not made of the same flesh, and they keep the colours they shipped
   with. Unlike the turn this cannot be cancelled on the layer: grayscale is
   lossy, so there is no inverse to apply. It has to not be inherited at all. */
.death-rest-gray     > .death-face { filter: var(--death-rest-gray); }
.death-rest-green    > .death-face { filter: var(--death-rest-green); }
.death-rest-natural  > .death-face { filter: var(--death-rest-natural); }

/* Same split for the animated pallor: transform stays on the host (the layers
   need to inherit the turn, then cancel it), filter rides the face alone. */
.death-hit         > .death-face { animation: death-gray 200ms 220ms linear forwards; }
.death-hit-hunting > .death-face { animation: death-gray 200ms 300ms linear forwards; }
.death-hit-disease > .death-face { animation: ds-sicken 820ms  40ms linear forwards; }
.death-hit-natural > .death-face { animation: dn-fade 1200ms 200ms cubic-bezier(.4, .1, .6, 1) forwards; }
.death-hit {
  animation:
    death-jiggle 200ms 220ms cubic-bezier(.36, .07, .19, .97) forwards,
    death-tilt   var(--death-turn-ms) 420ms var(--death-turn-ease) forwards;
}
/* TIER B (inline, ~2rem): the projectile and the spatter are unreadable at that
   size and a 165%-wide splat overlaps its neighbours. Motion only. */
.death-inline .death-over,
.death-inline .death-under { display: none; }

/* =========================== violent — the dagger ==========================
   From ABOVE, crimson. cubic-bezier(.55,.06,.68,.19) is a pure ease-in: it
   accelerates the whole way down like a dropped weight and arrives at full
   speed, so the halt reads as a hit. NO spring — with an overshoot the blade
   touches the face well before its animation ends, and "when it stops" stops
   being the moment it hit. The end of the drop IS the impact, at 220ms, which
   is the frame the splat, the jiggle and the grey all share.
   The blade never leaves: it rests buried, doubling as a standing
   "this one was murdered" marker for as long as the portrait is on screen. */
.death-blade {
  position: absolute;
  left: 46%;
  top: -18%;
  width: 42%;
  transform-origin: 50% 100%;
  animation: dd-drop 220ms cubic-bezier(.55, .06, .68, .19) forwards;
}
@keyframes dd-drop {
  0%   { transform: translate(-50%, -150%) rotate(12deg); opacity: 0; }
  18%  { opacity: 1; }
  100% { transform: translate(-50%, 0%) rotate(12deg); opacity: 1; }
}
/* Oversized on purpose: the droplets have to break past the head's silhouette
   or nothing shows. Sits BEHIND the face — blood sprays out from behind
   someone, and painting it over the face hides the reaction the gag needs. */
.death-splat {
  position: absolute;
  left: 50%;
  top: 58%;
  width: 165%;
  transform: translate(-50%, -50%) scale(0) rotate(-14deg);
  animation: dd-splat 380ms 220ms cubic-bezier(.2, 1.5, .4, 1) forwards;
}
@keyframes dd-splat {
  0%   { transform: translate(-50%, -50%) scale(0)    rotate(-14deg); opacity: 0; }
  55%  { transform: translate(-50%, -50%) scale(1.12) rotate(2deg);   opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1)    rotate(0deg);   opacity: 1; }
}

/* ============================ hunting — the stag ===========================
   From BELOW, antler brown. The one beat that moves the head UP before it
   comes down: a stag tosses its victim rather than dropping something on them,
   and that inverted vector is what distinguishes it from the dagger at a
   glance. The antlers rise behind the head and stay. */
/* The art is a SYMMETRICAL pair of antlers, bases at the bottom, tines fanning
   up, on a SQUARE canvas. Centred over a head that silhouette is simply a crown,
   which is why the original upward entry read as headwear however it was timed.
   The fix is not the direction of travel, it is WHERE IT STOPS: coming up from
   below and halting LOW keeps the bases off-frame under the chin, so the crown
   shape never completes. Only tines are ever on the face, driving up into the
   jaw, which is how a stag actually gores. A few degrees of tilt breaks the
   symmetry that made it read as worn. */
.death-antlers {
  position: absolute;
  left: 50%;
  top: 30%;
  width: 128%;
  transform-origin: 50% 100%;
  /* invert() flips the solid-black art to solid WHITE. It touches RGB only and
     leaves alpha alone, so the transparent canvas around the rack stays
     transparent. White is what solves the legibility problem the rim-light was
     patching: against dark blood the rack now separates on VALUE, not outline.
     The shadow flips to dark for the opposite case, a white rack over the pale
     face. Order matters: invert first, then shadow the inverted result. */
  filter: invert(1) drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.7));
  /* Ease-IN so the END of the travel is the impact, not somewhere before it. */
  animation: dh-gore 300ms cubic-bezier(.55, .06, .68, .19) forwards;
}
/* Stops LOW on purpose. Travelling far enough to bracket the face is exactly
   what rebuilt the crown silhouette; the tines must stay in the jaw. */
@keyframes dh-gore {
  0%   { transform: translate(-50%, 96%) rotate(-14deg); opacity: 0; }
  15%  { opacity: 1; }
  100% { transform: translate(-50%, 0)   rotate(-14deg); opacity: 1; }
}
/* Tossed: up hard on the goring, then down heavier than it went up. Replaces
   the shared jiggle for this class only. */
/* dh-toss is gone. It lifted the head straight up and settled it back at zero,
   which is the motion of someone PUTTING ON a headdress. The goring reuses the
   violent beat's rhythm instead, because that one already reads: tight jiggle on
   contact, then the neck gives out. The head is thrown RIGHT, away from a strike
   arriving from the LEFT. */
.death-hit-hunting {
  animation:
    death-jiggle 200ms 300ms cubic-bezier(.36, .07, .19, .97) forwards,
    death-tilt   var(--death-turn-ms) 500ms var(--death-turn-ease) forwards;
}

/* ========================== disease — the contagion ========================
   Sickly green, and the one beat with NO arrival vector: illness does not
   strike from anywhere, it wells up. Three germs GROW IN PLACE on staggered
   delays while the face turns green, then the shared head turn takes over.

   It RESTS GREEN, not grey — the pallor IS the cause of death here, and
   draining it to grey at the end would throw away the only thing that
   distinguishes this beat once the motion stops. */
.death-germ {
  position: absolute;
  width: 40%;
  transform: scale(0);
  transform-origin: 50% 50%;
  opacity: 0;
}
/* Placed to peek out around the head's silhouette — they sit in the UNDER layer,
   so anything fully behind the face would never be seen. */
.death-germ-1 { left: -16%; top: 6%;  animation: dg-grow 300ms 40ms  cubic-bezier(.2, 1.5, .4, 1) forwards; }
.death-germ-2 { left:  76%; top: 34%; animation: dg-grow 300ms 280ms cubic-bezier(.2, 1.5, .4, 1) forwards; }
.death-germ-3 { left:   4%; top: 68%; animation: dg-grow 300ms 520ms cubic-bezier(.2, 1.5, .4, 1) forwards; }
@keyframes dg-grow {
  0%   { transform: scale(0)    rotate(-30deg); opacity: 0; }
  60%  { transform: scale(1.15) rotate(6deg);   opacity: 1; }
  100% { transform: scale(1)    rotate(0deg);   opacity: 1; }
}
/* Greens progressively alongside the germs and STAYS there. */
@keyframes ds-sicken {
  0%   { filter: none; }
  100% { filter: var(--death-rest-green); }
}
.death-hit-disease {
  animation:
    death-tilt var(--death-turn-ms) 860ms var(--death-turn-ease) forwards;
}

/* ============================ natural — soul rise ==========================
   UPWARD, pale blue. Deliberately breaks the two-beat shape: no arrival, no
   jiggle, no grey-then-tilt. The portrait dims in place while a pale double
   drifts up and fades. Nothing lands, nothing stays. */
/* ===== THE SOUL IS DRAWN OUTSIDE THE PAGE, NOT INSIDE THE PORTRAIT =====
   It climbs to -108% of its own height, which takes it clear of the portrait's
   box and straight into whatever is above: a modal header, a card edge, the
   kin row's horizontal scroller. Inside the host it was CLIPPED, and no z-index
   can fix that — `overflow: hidden` on an ancestor wins over any stacking order
   (modals.css sets it on the modal container). Nor is stacking alone enough: a
   child cannot paint above its ancestor's siblings when the ancestor sits lower
   in the tree's stacking order.

   So playDeath draws the soul into `.death-soul-layer`, a fixed full-viewport
   layer appended to <body>, and positions it from the host's
   getBoundingClientRect(). Being a child of <body> it has no clipping ancestor
   and no competing stacking context, so it floats over everything.

   z-index 100001 clears the highest thing the app ships (#dev-viewport-readout
   at 100000). It is pointer-events: none, so it never intercepts a tap. */
.death-soul-layer {
  position: fixed;
  inset: 0;
  z-index: 100001;
  pointer-events: none;
  overflow: visible;
}

/* EXPLICIT WIDTH/HEIGHT, and this is the bug that shipped a screen-filling
   ghost. `.death-soul` is an <img>, and a positioned REPLACED element does NOT
   stretch to its inset box the way a div does — it keeps its intrinsic size,
   which for these portraits is 512px. Its box is now written in px by
   playDeath, copied off the host's rect; object-fit letterboxes inside it.
   (The same trap is documented on .realm-map in styles.css.) */
.death-soul {
  position: absolute;
  object-fit: contain;
  opacity: 0;
  /* saturate was 2.4, which pinned the shadows at a hard cyan (#95e2ff). Below
     ~0.8 is where this chain actually starts desaturating rather than just
     lightening, so 0.7 is the first value that reads as WHITE with a cool cast
     instead of blue: shadows land at #d0e8f7. brightness nudged up to keep the
     midtones from dulling as the colour comes out. */
  filter: grayscale(1) brightness(1.65) sepia(1) hue-rotate(175deg) saturate(0.7);
  /* Slower and higher than the first pass: 1500ms -> 2000ms, and the climb went
     from -72% to -108% (half again as far). The two go together — travelling
     50% further in the SAME time would have read as the soul being yanked
     upward rather than drifting off. The waypoint at 22% moved with it so the
     early part of the rise keeps its pace and only the long tail stretches. */
  /* TWO animations, one per property, and that is the whole point.
     They used to be one set of keyframes with a waypoint at 22% that carried
     BOTH the fade-in and a mere -12% of travel. Two things went wrong with that:
     the ghost spent its first 440ms appearing while barely moving, and because a
     CSS timing function is applied to EVERY keyframe segment separately, the
     curve decelerated to near-zero velocity at that waypoint and then had to
     accelerate again. That junction was the hesitation.
     Split apart, the transform is a single uninterrupted 0 -> -108% segment with
     no interior waypoint to stall at, and the opacity envelope rides on top of
     it independently. Different properties, so the two never fight. */
  animation:
    dn-rise   2000ms 240ms cubic-bezier(.25, .5, .45, 1) forwards,
    dn-spirit 2000ms 240ms linear forwards;
}
/* Transform ONLY. One segment, continuous velocity, no interior stop. */
@keyframes dn-rise {
  from { transform: translateY(0) scale(1); }
  to   { transform: translateY(-108%) scale(1.13); }
}

/* THE ARRIVAL (render/vfx/death.js raiseArrival) — an heir taking a dead
   ally's or rival's place: the NORMAL portrait, no pallor, no ghost tint,
   rising up from below the slot and settling exactly over the real portrait
   (held invisible by .death-arriving on the host, which then fades in as the
   copy is removed). The copy rides the soul layer only so the tray's scroll
   clipping cannot cut the climb. Brisk: it is a person arriving, not a
   spirit leaving. */
.death-soul--arrive {
  filter: none;
  animation:
    dn-arrive 700ms cubic-bezier(.22, .61, .36, 1) forwards,
    dn-appear 700ms ease-out forwards;
}
@keyframes dn-arrive {
  from { transform: translateY(100%) scale(1); }
  to   { transform: translateY(0) scale(1); }
}
@keyframes dn-appear {
  0%   { opacity: 0; }
  35%  { opacity: 1; }
  100% { opacity: 1; }
}
.death-arriving { opacity: 0 !important; pointer-events: none; }
/* Opacity ONLY. Up fast so the ghost is visible almost at once — by which point
   it is ALREADY climbing — then out across the long tail. */
@keyframes dn-spirit {
  0%   { opacity: 0; }
  12%  { opacity: 0.85; }
  100% { opacity: 0; }
}
/* Dims and settles, never tilts over. An old-age death should look like
   sleeping, not like being felled. */
/* FILTER ONLY. death-tilt owns transform for every death type including this
   one; if dn-fade also animated transform, the later animation in the list would
   simply stamp out the turn. */
@keyframes dn-fade {
  0%   { filter: none; }
  100% { filter: var(--death-rest-natural); }
}
/* The turn starts at 520ms, just after the soul peaks (~570ms), so the body
   goes slack as the spirit leaves rather than before it. */
.death-hit-natural {
  animation:
    death-tilt var(--death-turn-ms) 520ms var(--death-turn-ease) forwards;
}

/* ---- resting states: whatever did it stays put once the beat ends ---- */
.death-spent .death-blade   { animation: none; transform: translate(-50%, 0%) rotate(12deg); opacity: 1; }
.death-spent .death-splat   { animation: none; transform: translate(-50%, -50%) scale(1); opacity: 1; }
.death-spent .death-antlers { animation: none; transform: translate(-50%, 0) rotate(-14deg); opacity: 1; }
.death-spent .death-germ    { animation: none; transform: scale(1); opacity: 1; }

/* ===== NO HEAD TURN =====
   Zeroing the three turn components makes --death-turn resolve to an identity
   transform, and --death-unturn (derived from the same three) resolves to
   identity with it — so the counter-turn on the layers stays correct for free.
   Everything else about the beat is untouched: the strike, the blood and the
   pallor all still play. Only the neck stops giving out.

   playDeath adds this for BABY portraits. The infant sprite is bottom-anchored
   and scaled down inside its box, so rotating it about the chin swings the whole
   child over sideways: it reads as a toddler being knocked flat rather than as a
   death, which is further than this game's register goes. */
.death-host.death-no-turn {
  --death-turn-deg: 0deg;
  --death-turn-x: 0%;
  --death-turn-y: 0%;
}

/* ===== THE TWO GENERIC BEATS =====
   Neither is keyed to a cause: they pale the face and never turn the head. The
   difference is the soul.

     the FUNERAL    pale from the first frame and ALWAYS the same pale, with the
                    soul rising. It carries no class of its own: playDeath just
                    stamps the resting tint on immediately (see GENERIC_TIERS),
                    because nothing transitions into death on that modal.
     .death-fade    the kin row, which repaints AFTER the funeral. Same pallor
                    but faded IN, because there the portrait is live on screen
                    when the death lands. No ghost: it already went up on the
                    modal, and running it again on a 2rem chip stages one
                    departure twice.

   Neither puts death-tilt on the host, so the overlay needs no counter-turn —
   which is why there is no `> .death-over` rule for either. */
.death-fade > .death-face {
  animation: dn-fade 1200ms 200ms cubic-bezier(.4, .1, .6, 1) forwards;
}
@media (prefers-reduced-motion: reduce) {
  .death-fade { animation: none; }
  .death-fade > .death-face { animation: none; filter: var(--death-rest-natural); }
}

/* Reduced motion keeps the OUTCOME and drops the travel. Silence would read as
   the death failing to register, which is worse than a still frame. */
@media (prefers-reduced-motion: reduce) {
  .death-blade   { animation: none; transform: translate(-50%, 0%) rotate(12deg); opacity: 1; }
  .death-splat   { animation: none; transform: translate(-50%, -50%) scale(1); opacity: 1; }
  .death-antlers { animation: none; transform: translate(-50%, 0) rotate(-14deg); opacity: 1; }
  .death-germ    { animation: none; transform: scale(1); opacity: 1; }
  .death-under   { animation: none; transform: var(--death-unturn); }
  .death-soul    { display: none; }
  .death-hit, .death-hit-hunting, .death-hit-disease, .death-hit-natural {
    animation: none; transform: var(--death-turn);
  }
  .death-hit > .death-face, .death-hit-hunting > .death-face {
    animation: none; filter: var(--death-rest-gray);
  }
  .death-hit-disease > .death-face { animation: none; filter: var(--death-rest-green); }
  .death-hit-natural > .death-face { animation: none; filter: var(--death-rest-natural); }
}

/* ---- dev death lab (TASK-24 prototype only; goes away when the feature ships) ---- */
#dev-death-lab { margin-top: 0.5rem; }
.dbg-death-lab-title {
  margin: 0 0 0.25rem;
  font-family: var(--font-family-2);
  font-size: 0.875rem;
  color: var(--font-color-2);
  letter-spacing: 0.04em;
}
.dbg-death-lab-row { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.3rem; }
.dbg-death-lab-row .button-secondary { flex: 1 1 auto; padding: 0.3rem 0.5rem; font-size: 0.875rem; }
