/* =============================================================================
 * SUSUME — Tower of the Brawler
 * Layout, theme, and the 2D battle stage.
 * ===========================================================================*/

:root {
  --bg: #14110f;
  --bg-2: #1c1815;
  --panel: #221d19;
  --panel-2: #2a241f;
  --line: #3a322b;
  --ink: #ece4d8;
  --ink-dim: #a2968a;
  --ink-faint: #6f6459;

  --gold: #d9a441;
  --gold-dim: #a67c2c;
  --blood: #a83232;
  --blood-bright: #d84b4b;
  --jade: #5f9c6a;
  --steel: #6f8296;
  --violet: #7d5fa8;

  --radius: 10px;
  --radius-sm: 6px;
  --shadow: 0 12px 32px rgba(0, 0, 0, .45);
  --font: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-ui: "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "Cascadia Mono", Consolas, "SF Mono", ui-monospace, monospace;
}

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

/* Layout rules like `.btn-row { display: flex }` match with the same
 * specificity as the browser's `[hidden] { display: none }` and, coming
 * later, win -- so a hidden row stays on screen. Settle it once, here. */
[hidden] { display: none !important; }

body {
  margin: 0;
  /* The dojo the whole tower is climbed from. It sits still while the page
   * scrolls, and every layer above it is a scrim: without them the lantern
   * light and floorboards fight the panels for attention and the text stops
   * being readable. */
  background:
    radial-gradient(1100px 620px at 50% 0%, rgba(62, 49, 38, .20) 0%, transparent 66%),
    linear-gradient(180deg, rgba(12, 10, 9, .30) 0%, rgba(11, 9, 8, .44) 52%, rgba(9, 8, 7, .62) 100%),
    url('assets/img/site-bg.jpg') center 34% / cover no-repeat,
    linear-gradient(180deg, var(--bg) 0%, #100e0c 100%);
  background-attachment: fixed, fixed, fixed, fixed;
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { font-family: var(--font); font-weight: 600; margin: 0; }

.app {
  max-width: 1060px;
  margin: 0 auto;
  padding: 20px 18px 48px;
}

.muted { color: var(--ink-dim); font-size: .88rem; }

/* --------------------------------------------------------------- topbar */
.topbar {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 10px 0 18px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 26px;
  flex-wrap: wrap;
}

/* The way out, sitting with Sound at the right end of the topbar. It is an
   anchor rather than a button, so it needs the alignment and the stripped
   underline that .btn gets for free on a real <button>. */
.brand {
  font-size: 1.35rem;
  letter-spacing: .28em;
  color: var(--gold);
  display: flex;
  align-items: center;
  gap: 12px;
  margin-right: auto;
}

.brand__kanji {
  font-size: 1.6rem;
  letter-spacing: 0;
  color: var(--blood-bright);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 2px 9px;
  background: var(--panel);
}

.meters { display: flex; gap: 10px; }

.meter {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 5px 12px;
  min-width: 74px;
  text-align: center;
  line-height: 1.25;
}

.meter__label {
  display: block;
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.meter strong { font-family: var(--font); font-size: 1.05rem; color: var(--gold); }

/* --------------------------------------------------------------- buttons */
.btn {
  font: inherit;
  font-weight: 600;
  letter-spacing: .06em;
  color: var(--ink);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px 20px;
  cursor: pointer;
  transition: transform .12s ease, background .18s ease, border-color .18s ease, box-shadow .18s ease;
}

.btn:hover { background: #342c25; border-color: var(--gold-dim); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.btn:disabled { opacity: .35; cursor: default; }
.btn:disabled:hover { background: var(--panel-2); border-color: var(--line); }

.btn--primary {
  background: linear-gradient(180deg, var(--blood) 0%, #7d2020 100%);
  border-color: #93312f;
  color: #fff4e6;
  box-shadow: 0 6px 18px rgba(168, 50, 50, .28);
}
.btn--primary:hover { background: linear-gradient(180deg, #c03c3c 0%, #8d2626 100%); border-color: var(--blood-bright); }

.btn--ghost { background: transparent; }

.btn--sm { padding: 6px 13px; font-size: .82rem; min-width: 84px; }
.btn--lg { padding: 14px 34px; font-size: 1.05rem; }

/* The way out of the tower. Gold -- the brand's own colour -- rather than
   the topbar's ghost grey: it sits next to Abandon run, and the two must
   never be mistaken for each other at a glance. It is an anchor, so it also
   needs the alignment and the stripped underline a real <button> gets from
   .btn for free. */
.btn--home {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  white-space: nowrap;
  color: var(--gold);
  background: rgba(217, 164, 65, .14);
  border-color: rgba(217, 164, 65, .45);
}

.btn--home:hover {
  background: rgba(217, 164, 65, .26);
  border-color: var(--gold);
}


.btn-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; }
.btn-row--center { justify-content: center; }

/* --------------------------------------------------------------- panels */
.screen { animation: fade-in .28s ease both; }

@keyframes fade-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.panel {
  /* Slightly translucent so the dojo behind is felt rather than merely
   * framed. The blur keeps the floorboards from competing with body text. */
  background:
    linear-gradient(180deg, rgba(26, 22, 19, .90) 0%, rgba(18, 15, 13, .93) 100%);
  backdrop-filter: blur(9px) saturate(1.05);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px;
  box-shadow: var(--shadow);
}

@supports not (backdrop-filter: blur(1px)) {
  .panel { background: linear-gradient(180deg, var(--panel) 0%, var(--bg-2) 100%); }
}

.panel--center { text-align: center; }
.panel--slim { padding: 18px 22px; margin-top: 26px; }

.panel__title { font-size: 1.6rem; letter-spacing: .04em; }
.panel__title--loss { color: var(--blood-bright); }
.panel__subtitle {
  font-size: .74rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 10px;
}
.panel__lede { color: var(--ink-dim); max-width: 52ch; margin: 8px auto 0; }

/* --------------------------------------------------------------- avatars */
.avatar-host {
  width: 96px;
  flex: 0 0 96px;
  aspect-ratio: 100 / 140;
  display: grid;
  place-items: center;
}

.avatar-host--lg { width: 190px; flex-basis: 190px; margin: 0 auto; }

.avatar--fallen { filter: grayscale(.75) brightness(.7); transform: rotate(-6deg); }

/* The shadow clone: your own Brawler with the colour drained out. Everything
 * else about it is identical, which is the whole threat. */
.avatar--shadow { filter: grayscale(1) brightness(.52) contrast(1.15); }

/* --------------------------------------------------------------- forge */
.forge {
  display: grid;
  grid-template-columns: 210px 1fr;
  gap: 30px;
  align-items: start;
  margin-top: 24px;
  text-align: left;
}

.forge__portrait {
  background: repeating-linear-gradient(45deg, #1b1714 0 8px, #1f1a16 8px 16px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px;
}

.field { display: block; margin-bottom: 18px; }

.field__label {
  display: block;
  font-size: .7rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}

.field__input {
  width: 100%;
  font: inherit;
  font-size: 1.05rem;
  color: var(--ink);
  background: #191512;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 11px 14px;
}
.field__input:focus { outline: none; border-color: var(--gold-dim); box-shadow: 0 0 0 3px rgba(217, 164, 65, .12); }

.forge__info { display: grid; gap: 14px; }

/* --------------------------------------------------------- weapon + stats */
.weapon {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto auto;
  column-gap: 12px;
  align-items: center;
  margin: 0;
  padding: 10px 14px;
  background: #1a1613;
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold-dim);
  border-radius: var(--radius-sm);
}

.weapon__glyph { grid-row: 1 / 4; font-size: 1.5rem; color: var(--gold); }
.weapon__name { font-family: var(--font); font-size: 1.02rem; }
.weapon__blurb { font-size: .82rem; color: var(--ink-dim); }

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 0;
}

.stats__cell {
  background: #1a1613;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 7px 4px;
  text-align: center;
}

.stats__key { font-size: .62rem; letter-spacing: .14em; color: var(--ink-faint); }

.stats__val {
  margin: 0;
  font-family: var(--font);
  font-size: 1.2rem;
  color: var(--ink);
}

.chips { list-style: none; display: flex; flex-wrap: wrap; gap: 7px; margin: 0; padding: 0; }

.chip {
  font-size: .76rem;
  letter-spacing: .04em;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid rgba(125, 95, 168, .5);
  background: rgba(125, 95, 168, .14);
  color: #cdbde6;
}

/* A boss power is unranked and much stronger than a passive, so it is marked
 * in the tower's gold rather than the purple the passives share. */
.chip--boss {
  border-color: rgba(214, 173, 92, .62);
  background: rgba(214, 173, 92, .15);
  color: var(--gold);
  font-weight: 700;
}

.chips__empty { font-size: .8rem; color: var(--ink-faint); font-style: italic; }

/* --------------------------------------------------------------- tower */
.floor-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.floor-head__title { font-size: 1.9rem; letter-spacing: .06em; }

.badge {
  display: inline-block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  padding: 5px 13px;
  border-radius: 999px;
  border: 1px solid;
}

.badge--standard { color: var(--steel); border-color: rgba(111, 130, 150, .55); background: rgba(111, 130, 150, .12); }
.badge--mini { color: var(--gold); border-color: rgba(217, 164, 65, .55); background: rgba(217, 164, 65, .12); }
.badge--major {
  color: #ffb9b9;
  border-color: rgba(216, 75, 75, .7);
  background: rgba(216, 75, 75, .16);
  animation: pulse 1.8s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(216, 75, 75, .35); }
  50% { box-shadow: 0 0 0 7px rgba(216, 75, 75, 0); }
}

.matchup {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 18px;
  align-items: start;
}

.versus {
  align-self: center;
  font-family: var(--font);
  font-size: 1.3rem;
  letter-spacing: .1em;
  color: var(--ink-faint);
}

.card {
  background: linear-gradient(180deg, var(--panel) 0%, var(--bg-2) 100%);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
  box-shadow: var(--shadow);
}

.card--player { border-top: 3px solid var(--jade); }
.card--enemy { border-top: 3px solid var(--blood); }

.card__head { display: flex; gap: 14px; align-items: center; margin-bottom: 14px; }
.card__name { font-size: 1.15rem; line-height: 1.25; }
.card__sub { margin: 2px 0 0; font-size: .82rem; color: var(--ink-dim); }
.card__body { display: grid; gap: 12px; margin-top: 14px; }

.ability {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(216, 75, 75, .45);
  background: rgba(216, 75, 75, .1);
}

.ability__glyph { font-size: 1.3rem; color: var(--blood-bright); }
.ability__text { display: grid; gap: 2px; }
.ability__text strong { font-family: var(--font); color: #ffc9c9; }
.ability__text span { font-size: .82rem; color: var(--ink-dim); }

/* --------------------------------------------------------------- gauges */
.gauge { margin-bottom: 9px; }

.gauge__track {
  height: 11px;
  background: #120f0d;
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow: hidden;
}

.gauge__fill {
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #4b8a55, #6fbd7c);
  transition: width .3s cubic-bezier(.4, 0, .2, 1);
}

.gauge__fill--foe { background: linear-gradient(90deg, #8f2a2a, #cf4a4a); }
.gauge--xp .gauge__fill { background: linear-gradient(90deg, #6b53a0, #a488d8); }

/* The same health scale as the arena. XP keeps its own violet: it is not a
 * health bar and must never be read as one. */
.gauge__fill.is-hurt { background: linear-gradient(90deg, #c9922a, #f0c04a); }
.gauge__fill.is-critical { background: linear-gradient(90deg, #a52f1e, #e05135); }

.gauge__text {
  display: block;
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--ink-dim);
  margin-top: 3px;
}

/* Two buttons now: Fight & Ascend, and the Abandon run that moved down
   from the topbar. They wrap rather than shrink on a narrow phone. */
.tower-actions {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin: 28px 0 6px;
}

/* Said once, where it can be read: the menu is back by the time these
   fire, so the host/join status lines are hidden. */
.friend-alert { margin: 4px 0 10px; color: var(--blood-bright); }

.destiny-log {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--ink-dim);
  max-height: 190px;
  overflow-y: auto;
}

.destiny-log li { border-left: 2px solid var(--gold-dim); padding-left: 10px; }
.destiny-log--center { justify-items: center; text-align: left; max-width: 320px; margin: 0 auto; }

/* ======================================================= 2D BATTLE STAGE */
.battle-controls { display: flex; gap: 8px; }

.stage {
  position: relative;
  height: 408px;
  border: 1px solid #6b4d22;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow), inset 0 0 0 3px rgba(0, 0, 0, .35);
  --lunge: 116px;
  --floor-h: 42%;
  --sand: #d9a95c;
  --sand-dark: #a97a35;
}

/* Backdrop. `--stage-bg` is set from JS when real artwork is enabled;
   otherwise the CSS-only wall, floor and duel ring below stand in for it. */
.stage__sky {
  position: absolute;
  inset: 0;
  background-image: var(--stage-bg, none);
  background-size: cover;
  /* Framed low so the painted floor -- where the fighters stand -- stays in shot. */
  background-position: center 62%;
  background-color: #b8862f;
}

/* Temple wall: warm ochre masonry with a recessed central panel. */
.stage__wall {
  position: absolute;
  inset: 0 0 var(--floor-h) 0;
  opacity: var(--stage-plain, 1);
  background-image:
    linear-gradient(180deg, rgba(60, 34, 8, .42) 0%, transparent 30%),
    linear-gradient(180deg, #93a44b 0%, #6d7d33 100%),
    linear-gradient(180deg, #5a3308, #5a3308),
    repeating-linear-gradient(90deg, #c8912f 0 78px, #bd8829 78px 82px),
    repeating-linear-gradient(0deg, #c08a2c 0 46px, #b8842a 46px 50px);
  background-size: auto, 30% 72%, 34% 78%, auto, auto;
  background-position: center, center bottom, center bottom, center, center;
  background-repeat: repeat, no-repeat, no-repeat, repeat, repeat;
}

/* Pillars flanking the panel */
.stage__wall::before,
.stage__wall::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 15%;
  background:
    linear-gradient(90deg, rgba(80, 46, 10, .5), transparent 30%, rgba(255, 226, 150, .28) 55%, transparent 75%, rgba(80, 46, 10, .5)),
    repeating-linear-gradient(0deg, #d0982f 0 54px, #c28d2b 54px 58px);
  border-inline: 3px solid rgba(90, 52, 12, .55);
}
.stage__wall::before { left: 6%; }
.stage__wall::after { right: 6%; }

/* Flagstone floor, lit from the centre */
.stage__floor {
  position: absolute;
  inset: auto 0 0 0;
  height: var(--floor-h);
  opacity: var(--stage-plain, 1);
  border-top: 4px solid rgba(90, 52, 12, .6);
  background-image:
    radial-gradient(70% 120% at 50% 0%, rgba(255, 244, 206, .5) 0%, transparent 65%),
    linear-gradient(180deg, rgba(90, 52, 12, .5) 0%, transparent 20%),
    repeating-linear-gradient(90deg, rgba(150, 92, 20, .35) 0 2px, transparent 2px 96px),
    repeating-linear-gradient(0deg, rgba(150, 92, 20, .28) 0 2px, transparent 2px 42px);
  background-color: #dfb668;
}

/* Chalk duelling circle */
.stage__ring {
  position: absolute;
  left: 50%;
  bottom: 5%;
  width: 62%;
  height: 30%;
  transform: translateX(-50%);
  border: 4px solid rgba(255, 249, 226, .5);
  border-radius: 50%;
  opacity: var(--stage-plain, 1);
  z-index: 1;
}

.stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 6;
  background: radial-gradient(92% 82% at 50% 58%, transparent 58%, rgba(72, 38, 4, .38) 100%);
}

.stage__camera {
  position: absolute;
  inset: 0;
  transform-origin: 50% 62%;
  will-change: transform;
}

/* Decaying shake: biggest on the first frame, settling to nothing. */
.stage__camera.is-shaking { animation: cam-shake calc(.34s * var(--spd, 1)) cubic-bezier(.2, .9, .3, 1); }

@keyframes cam-shake {
  0% { transform: translate(0, 0) scale(1); }
  10% { transform: translate(calc(var(--shake, 9px) * -1), calc(var(--shake, 9px) * .66)) scale(1.012); }
  25% { transform: translate(calc(var(--shake, 9px) * .78), calc(var(--shake, 9px) * -.55)) scale(1.008); }
  42% { transform: translate(calc(var(--shake, 9px) * -.55), calc(var(--shake, 9px) * -.33)) scale(1.005); }
  60% { transform: translate(calc(var(--shake, 9px) * .33), calc(var(--shake, 9px) * .22)) scale(1.002); }
  78% { transform: translate(calc(var(--shake, 9px) * -.22), calc(var(--shake, 9px) * .11)) scale(1); }
  100% { transform: translate(0, 0) scale(1); }
}

/* Punch-zoom: a short push in on a heavy connect. */
.stage__camera.is-punched { animation: cam-punch calc(.36s * var(--spd, 1)) cubic-bezier(.15, .85, .3, 1); }

@keyframes cam-punch {
  0% { transform: scale(1); }
  14% { transform: scale(1.075); }
  40% { transform: scale(1.055); }
  100% { transform: scale(1); }
}

/* HIT STOP: freeze every bone and effect for a few frames on connect. It is
   the single strongest way to make a blow feel like it landed. */
.is-frozen,
.is-frozen * { animation-play-state: paused !important; }

.stage__fx { position: absolute; inset: 0; z-index: 5; pointer-events: none; }

/* The clip director owns attack transforms; CSS still owns idle, defence,
   damage, launches and revival. Never run two attack rigs simultaneously. */
.is-directed{transform-origin:50% 85%}
.is-directed .rig{animation:none!important}
.brawler.is-directed,
.brawler.is-directed .bone { animation: none !important; }
.brawler.is-directed { will-change: transform; }
.battle-read {
  position: absolute; bottom: 13px; left: 12px; right: 12px;
  z-index: 7; pointer-events: none; text-align: center;
  color: #fff4cf; text-shadow: 0 2px 5px #25160d, 0 0 12px #25160d;
  font: 800 12px/1.4 var(--font-mono); letter-spacing: .18em;
  text-transform: uppercase;
}
.stage.is-finisher::after {
  background: linear-gradient(#16101bd9 0 5%, transparent 5% 94%, #16101bd9 94%),
    radial-gradient(ellipse at 50% 65%, transparent 28%, #27112199 100%);
}
.stage.is-finisher .battle-read { color: #fff; letter-spacing: .36em; font-size: 18px; }
@media (prefers-reduced-motion: reduce) {
  .stage__camera { animation: none !important; }
  .stage .fx-streak, .stage .fx-ghost { display: none; }
  .stage .bone, .stage .brawler__sprite { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
  .stage.is-finisher::after { background: none; }
}
/* ======================================================= SKELETAL FIGHTERS
 * Fighters are an SVG skeleton of nested bones. Each bone rotates about its
 * own joint and drags its children with it, so animating a shoulder moves the
 * forearm and the weapon for free. `--dir` (+1 player, -1 enemy) mirrors every
 * horizontal motion so one set of keyframes drives both sides, and `--spd`
 * scales all durations to the playback speed.
 * ---------------------------------------------------------------------- */
.brawler {
  position: absolute;
  bottom: 11%;
  width: calc(var(--body-w, 152px) * var(--tier-scale, 1));
  z-index: 3;
  --lunge-len: 120px;
  --lunge: calc(var(--lunge-len) * var(--dir));
  --knock-len: 24px;                    /* set per hit, scaled by damage */
  --knock: calc(var(--knock-len) * -1 * var(--dir));
  --atk: calc(.62s * var(--spd, 1));
  --hit: calc(.42s * var(--spd, 1));
}

/* Combat range, measured out from the centre of the stage rather than in from
 * its edges, so the distance between the two is the same whatever the arena is
 * doing. They used to stand at 12% and 88%, better than half the stage apart,
 * which meant every single swing threw the whole body across the gap and
 * dragged it back -- two puppets being swung at each other rather than two
 * fighters trading blows inside one another's reach. --stance is half the
 * distance between their centres; an attack is now a step, not a charge. */
.brawler--player {
  left: 50%;
  margin-left: calc(-1 * var(--stance, 118px) - var(--body-w, 152px) / 2);
  --dir: 1;
}
.brawler--enemy {
  right: 50%;
  margin-right: calc(-1 * var(--stance, 118px) - var(--body-w, 152px) / 2);
  --dir: -1;
}

.brawler .avatar-host { width: 100%; flex-basis: auto; }

/* The arena sets --scene-tint; unlit fighters read as stickers on a painting. */
.brawler .brawler__sprite {
  position: relative;
  filter: var(--scene-tint, none);
  transition: filter .4s ease;
}

/* The SVG carries its own contact shadow now; this stays only as the extra
   pool the arena floor casts under a heavier fighter. */
.brawler__shadow {
  position: absolute;
  left: 50%;
  bottom: 2px;
  width: 64px;
  height: 11px;
  margin-left: -32px;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(12, 8, 2, .45), transparent);
  filter: blur(2px);
  transition: transform .18s ease, opacity .18s ease;
}

/* The rig itself */
.avatar { position: relative; width: 100%; aspect-ratio: 100 / 140; }
.avatar--flip { transform: scaleX(-1); }
.avatar--boss-set{position:relative}
.avatar--boss-set::before{content:"";position:absolute;inset:8%;border-radius:50%;background:radial-gradient(ellipse,rgba(255,40,50,.38),rgba(160,0,20,.16) 48%,transparent 72%);pointer-events:none}

/* The floor boss whose full armor set is worn, standing faded in behind its
 * wearer at its own real size (the --tier-scale a major boss fights at),
 * centered on the same spot as the fighter -- not shrunk to fit, not
 * offset to a side. */
.avatar--bossghost{
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 142%;
  transform: translateX(-50%);
  opacity: .35;  /* 65% transparent -- was .2 (80%), too faint to read as the set bonus it signals */
  pointer-events: none;
}
.avatar--bossghost.avatar--flip{ transform: translateX(-50%) scaleX(-1); }

.rig {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Every bone pivots about the joint set inline by js/assets.js. */
.bone {
  transform-box: view-box;
  transform-origin: 50% 50%;   /* overridden inline per bone */
  will-change: transform;
}

/* ---- gear and charge: cape sway, drifting embers, glowing runes ---- */
.avatar .bone--cape { animation: id-cape 3.2s ease-in-out infinite; }
.brawler--enemy .bone--cape { animation-delay: -1.6s; }
.brawler.is-attacking .bone--cape { animation: atk-cape var(--atk) cubic-bezier(.3, .8, .35, 1); }
.brawler.is-hurt .bone--cape { animation: hurt-tails var(--hit) ease-out; }

@keyframes id-cape { 0%, 100% { transform: rotate(0deg) skewX(0deg); } 50% { transform: rotate(2.4deg) skewX(-3deg); } }
@keyframes atk-cape {
  0% { transform: rotate(0deg) skewX(0deg); }
  30% { transform: rotate(-9deg) skewX(9deg); }
  56% { transform: rotate(11deg) skewX(-11deg); }
  100% { transform: rotate(0deg) skewX(0deg); }
}

/* Embers rise off the fighter and fade, staggered so they never pulse in step. */
.rig__embers .ember {
  opacity: 0;
  animation: ember-drift 3.4s linear infinite;
  transform-box: view-box;
}
.rig__embers .ember--1 { animation-duration: 4.1s; animation-delay: -.7s; }
.rig__embers .ember--2 { animation-duration: 3.8s; animation-delay: -1.6s; }
.rig__embers .ember--3 { animation-duration: 4.6s; animation-delay: -2.4s; }
.rig__embers .ember--4 { animation-duration: 3.2s; animation-delay: -3.1s; }

@keyframes ember-drift {
  0% { opacity: 0; transform: translate(0, 6px) scale(.5); }
  16% { opacity: .85; }
  70% { opacity: .5; }
  100% { opacity: 0; transform: translate(-5px, -74px) scale(.2); }
}

/* Runes on an enchanted blade, breathing heat. */
.wpn-runes { stroke: #ffb347; stroke-linecap: round; animation: rune-pulse 1.9s ease-in-out infinite; }
@keyframes rune-pulse {
  0%, 100% { opacity: .55; filter: drop-shadow(0 0 1px #ff8c22); }
  50% { opacity: 1; filter: drop-shadow(0 0 3.5px #ffb347); }
}

/* ============================ IDLE: a breathing stance =================
 * A stiff idle loop is one sine wave shared by every bone, all peaking on
 * the same beat. A living one is a chain reaction: the stance settles
 * fastest at the torso, the arms catch up a beat behind, and the forearms
 * whip through last -- and each bone falls fast (gravity winning) but
 * rises slow, lingering at the top of the breath, instead of bouncing on a
 * perfectly even clock. Lead (front) fist travels further than the trailing
 * one, same as a real guard bounce.
 * ref: https://www.slynyrd.com/blog/2024/9/26/pixelblog-52-idle-fighting-stance
 * ------------------------------------------------------------------------- */
.avatar .bone--pelvis { animation: id-pelvis 2.5s ease-in-out infinite; }
.avatar .bone--torso { animation: id-torso 2.4s ease-in-out infinite; }
.avatar .bone--head { animation: id-head 2.7s ease-in-out infinite; }
.avatar .bone--armF { animation: id-armF 2.4s ease-in-out infinite; }
.avatar .bone--foreArmF { animation: id-foreF 2.4s ease-in-out infinite; }
.avatar .bone--armB { animation: id-armB 2.8s ease-in-out infinite; }
.avatar .bone--foreArmB { animation: id-foreB 2.8s ease-in-out infinite; }
.avatar .bone--legF { animation: id-legF 2.5s ease-in-out infinite; }
.avatar .bone--legB { animation: id-legB 2.5s ease-in-out infinite; }
.avatar .bone--shinF,
.avatar .bone--shinB { animation: id-shin 2.5s ease-in-out infinite; }
.brawler--enemy .bone { animation-delay: -1.2s; }
/* Gentle whole-body breathing, so a fighter looks alive even at rest. */
.avatar .rig { animation: id-bob 2.4s ease-in-out infinite; }
.brawler--enemy .rig { animation-delay: -1.2s; }
@keyframes id-bob {
  0% { transform: translateY(0); }
  58% { transform: translateY(-3px); }
  78% { transform: translateY(-2.6px); }
  100% { transform: translateY(0); }
}

@keyframes id-pelvis {
  0% { transform: rotate(0deg) translateX(0); }
  54% { transform: rotate(-1.6deg) translateX(-.4px); }
  74% { transform: rotate(-1.3deg) translateX(-.4px); }
  100% { transform: rotate(0deg) translateX(0); }
}
@keyframes id-torso {
  0% { transform: rotate(0deg) translateY(0) translateX(0); }
  56% { transform: rotate(2.6deg) translateY(-1.3px) translateX(.5px); }
  76% { transform: rotate(2.2deg) translateY(-1.1px) translateX(.5px); }
  100% { transform: rotate(0deg) translateY(0) translateX(0); }
}
@keyframes id-head {
  0% { transform: rotate(0deg); }
  60% { transform: rotate(-3.6deg); }
  80% { transform: rotate(-3deg); }
  100% { transform: rotate(0deg); }
}
@keyframes id-armF {
  0% { transform: rotate(0deg); }
  62% { transform: rotate(6.5deg); }
  82% { transform: rotate(5.6deg); }
  100% { transform: rotate(0deg); }
}
@keyframes id-foreF {
  0% { transform: rotate(0deg); }
  68% { transform: rotate(-9deg); }
  86% { transform: rotate(-7.4deg); }
  100% { transform: rotate(0deg); }
}
@keyframes id-armB {
  0% { transform: rotate(0deg); }
  62% { transform: rotate(-4deg); }
  82% { transform: rotate(-3.4deg); }
  100% { transform: rotate(0deg); }
}
@keyframes id-foreB {
  0% { transform: rotate(0deg); }
  68% { transform: rotate(5.5deg); }
  86% { transform: rotate(4.4deg); }
  100% { transform: rotate(0deg); }
}
@keyframes id-legF {
  0% { transform: rotate(0deg); }
  54% { transform: rotate(2.4deg); }
  74% { transform: rotate(2deg); }
  100% { transform: rotate(0deg); }
}
@keyframes id-legB {
  0% { transform: rotate(0deg); }
  54% { transform: rotate(-2.4deg); }
  74% { transform: rotate(-2deg); }
  100% { transform: rotate(0deg); }
}
@keyframes id-shin {
  0% { transform: rotate(0deg); }
  60% { transform: rotate(2.6deg); }
  80% { transform: rotate(2.1deg); }
  100% { transform: rotate(0deg); }
}

/* The arena HUD portrait is a 74px window cropped to the head. Six of the
 * twelve idle animations belong to bones that fall entirely outside it -- both
 * forearms, both thighs, both shins -- so they were costing a repaint every
 * frame to move something no one can see. The pelvis, torso, head and upper
 * arms do overlap the crop and keep breathing, so the portrait still looks
 * alive; only the invisible half stops. */
.portrait .bone--foreArmF,
.portrait .bone--foreArmB,
.portrait .bone--legF,
.portrait .bone--legB,
.portrait .bone--shinF,
.portrait .bone--shinB { animation: none; }

/* Headband tails: secondary motion, always a beat behind the head. */
.avatar .bone--tails { animation: id-tails 2.4s ease-in-out infinite; }
.brawler--enemy .bone--tails { animation-delay: -1.05s; }
.brawler.is-attacking .bone--tails { animation: atk-tails var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-hurt .bone--tails { animation: hurt-tails var(--hit) ease-out; }
.brawler.is-dodging .bone--tails { animation: atk-tails calc(.5s * var(--spd,1)) ease-in-out; }
@keyframes id-tails { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(8deg); } }
@keyframes atk-tails {
  0% { transform: rotate(0deg); }
  30% { transform: rotate(-24deg); }
  56% { transform: rotate(28deg); }
  74% { transform: rotate(-10deg); }
  100% { transform: rotate(0deg); }
}
@keyframes hurt-tails { 0%,100% { transform: rotate(0deg); } 26% { transform: rotate(calc(-22deg * var(--dir))); } }

/* ============================== ATTACK =================================
 * Four phases, but the split now depends on the move's weight. `impact` in
 * MOVE_TIMING fixes the millisecond the blow lands; `total` decides how much
 * timeline is left after it, and the keyframes below are authored against that
 * fraction. A dagger lands at 58% and is home by 100%; an overhead lands at 40%
 * and spends the remaining 60% dragging its own mass back to the mark.
 *
 * The kinetic chain runs ground -> hips -> chest -> shoulder -> hand, so the
 * pelvis turns about a frame ahead of the torso and the torso ahead of the arm.
 * Every bone is rotation-only; only the body itself translates.
 * ---------------------------------------------------------------------- */
.brawler.is-attacking { animation: atk-body var(--atk) cubic-bezier(.3, .8, .35, 1); }
.brawler.is-attacking .bone--pelvis { animation: atk-pelvis var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--torso { animation: atk-torso var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--head { animation: atk-head var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--armF { animation: atk-armF var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--foreArmF { animation: atk-foreF var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--armB { animation: atk-armB var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--foreArmB { animation: atk-foreB var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--legF { animation: atk-legF var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--shinF { animation: atk-shinF var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--legB { animation: atk-legB var(--atk) cubic-bezier(.3,.8,.35,1); }
.brawler.is-attacking .bone--shinB { animation: atk-shinB var(--atk) cubic-bezier(.3,.8,.35,1); }

/* Anticipation is slow and readable; the strike itself fires in a couple of
   frames on a near-vertical curve; then a held over-extension before recovery.
   Per-keyframe timing functions are what buy the snap -- a single easing across
   the whole animation always reads as a smooth wave instead of a hit. */

/* ---- SLASH (light): lands at 56%, settles in 44% ---- */
@keyframes atk-body {
  0% { transform: translate(0, 0); animation-timing-function: ease-out; }
  31% { transform: translate(calc(var(--lunge) * -.18), 3px); animation-timing-function: cubic-bezier(.7, 0, .84, 0); }
  48% { transform: translate(var(--lunge), -7px); animation-timing-function: cubic-bezier(.9, 0, 1, .4); }
  56% { transform: translate(calc(var(--lunge) * 1.06), 0); animation-timing-function: ease-out; }
  66% { transform: translate(var(--lunge), 0); }
  84% { transform: translate(calc(var(--lunge) * .3), -3px); }
  93% { transform: translate(calc(var(--lunge) * -.04), 1px); }   /* micro-settle */
  100% { transform: translate(0, 0); }
}
@keyframes atk-pelvis {
  0% { transform: rotate(0deg); animation-timing-function: ease-out; }
  28% { transform: rotate(-9deg); animation-timing-function: cubic-bezier(.9, 0, 1, .45); }
  45% { transform: rotate(-6deg); animation-timing-function: cubic-bezier(.95, 0, 1, .5); }
  53% { transform: rotate(10deg); animation-timing-function: ease-out; }
  70% { transform: rotate(4deg); }
  100% { transform: rotate(0deg); }
}
@keyframes atk-torso {
  0% { transform: rotate(0deg) scale(1); animation-timing-function: ease-out; }
  31% { transform: rotate(-12deg) scale(1.05, 1.04); animation-timing-function: cubic-bezier(.9, 0, 1, .45); }
  48% { transform: rotate(-8deg) scale(1.04, 1.03); animation-timing-function: cubic-bezier(.95, 0, 1, .5); }
  56% { transform: rotate(13deg) scale(.98, .99); animation-timing-function: ease-out; }
  72% { transform: rotate(6deg) scale(1, 1); }
  100% { transform: rotate(0deg) scale(1); }
}
@keyframes atk-head {
  0% { transform: rotate(0deg); animation-timing-function: ease-out; }
  31% { transform: rotate(16deg); animation-timing-function: cubic-bezier(.9, 0, 1, .5); }
  56% { transform: rotate(-18deg); }
  74% { transform: rotate(-6deg); }
  100% { transform: rotate(0deg); }
}
@keyframes atk-armF {
  0% { transform: rotate(0deg); animation-timing-function: ease-out; }
  31% { transform: rotate(-116deg); animation-timing-function: cubic-bezier(.92, 0, 1, .4); }
  46% { transform: rotate(-100deg); animation-timing-function: cubic-bezier(.96, 0, 1, .55); }
  56% { transform: rotate(66deg); animation-timing-function: ease-out; }
  68% { transform: rotate(44deg); }
  100% { transform: rotate(0deg); }
}
@keyframes atk-foreF {
  0% { transform: rotate(0deg); animation-timing-function: ease-out; }
  31% { transform: rotate(-104deg); animation-timing-function: cubic-bezier(.92, 0, 1, .4); }
  46% { transform: rotate(-92deg); animation-timing-function: cubic-bezier(.96, 0, 1, .6); }
  56% { transform: rotate(12deg); animation-timing-function: ease-out; }
  70% { transform: rotate(-14deg); }
  100% { transform: rotate(0deg); }
}
/* The far arm is a counterweight, not scenery: it swings against the spine. */
@keyframes atk-armB {
  0%, 100% { transform: rotate(0deg); }
  31% { transform: rotate(52deg); }
  56% { transform: rotate(-48deg); }
  74% { transform: rotate(-16deg); }
}
@keyframes atk-foreB {
  0%, 100% { transform: rotate(0deg); }
  31% { transform: rotate(62deg); }
  56% { transform: rotate(8deg); }
}
@keyframes atk-legF {
  0%, 100% { transform: rotate(0deg); }
  22% { transform: rotate(12deg); }    /* weight settles back */
  40% { transform: rotate(-22deg); }   /* front foot steps out */
  56% { transform: rotate(-26deg); }   /* planted, taking the weight */
  82% { transform: rotate(-8deg); }
}
@keyframes atk-shinF {
  0%, 100% { transform: rotate(0deg); }
  40% { transform: rotate(24deg); }
  56% { transform: rotate(7deg); }     /* straightens as the weight lands */
}
@keyframes atk-legB {
  0%, 100% { transform: rotate(0deg); }
  22% { transform: rotate(-13deg); }
  40% { transform: rotate(18deg); }
  56% { transform: rotate(24deg); }    /* trailing leg drives from behind */
  82% { transform: rotate(7deg); }
}
@keyframes atk-shinB {
  0%, 100% { transform: rotate(0deg); }
  40% { transform: rotate(-22deg); }
  56% { transform: rotate(-26deg); }   /* heel up, pushing off */
}

/* ---- OVERHEAD (heavy): leap, apex, slam at 40%, then 60% of dead weight ---- */
.brawler.is-attacking.move-overhead { animation-name: ovh-body; }
.brawler.is-attacking.move-overhead .bone--pelvis { animation-name: ovh-pelvis; }
.brawler.is-attacking.move-overhead .bone--armF { animation-name: ovh-armF; }
.brawler.is-attacking.move-overhead .bone--foreArmF { animation-name: ovh-foreF; }
.brawler.is-attacking.move-overhead .bone--armB { animation-name: ovh-armB; }
.brawler.is-attacking.move-overhead .bone--foreArmB { animation-name: ovh-foreB; }
.brawler.is-attacking.move-overhead .bone--torso { animation-name: ovh-torso; }
.brawler.is-attacking.move-overhead .bone--legF { animation-name: ovh-legF; }
.brawler.is-attacking.move-overhead .bone--shinF { animation-name: ovh-shinF; }

@keyframes ovh-body {
  0% { transform: translate(0, 0); animation-timing-function: ease-out; }
  13% { transform: translate(calc(var(--lunge) * -.2), 7px); animation-timing-function: cubic-bezier(.2, .7, .4, 1); }
  23% { transform: translate(calc(var(--lunge) * .34), -44px); animation-timing-function: linear; }
  30% { transform: translate(calc(var(--lunge) * .72), -56px); animation-timing-function: cubic-bezier(.85, 0, 1, .5); }
  40% { transform: translate(var(--lunge), 9px); animation-timing-function: ease-out; }
  50% { transform: translate(var(--lunge), 3px); }
  /* Tier 1: most of the distance comes back fast. */
  58% { transform: translate(calc(var(--lunge) * .3), 0); animation-timing-function: cubic-bezier(.33, 0, .68, .5); }
  /* Tier 2: the last third drags, carrying the weight home. */
  72% { transform: translate(calc(var(--lunge) * .14), 2px); }
  /* Tier 3: overshoot past the mark and squash back onto it. */
  86% { transform: translate(calc(var(--lunge) * -.05), 2px); }
  94% { transform: translate(0, -1px); }
  100% { transform: translate(0, 0); }
}
@keyframes ovh-pelvis {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: rotate(-11deg); }
  39% { transform: rotate(13deg); }
  58% { transform: rotate(5deg); }
  86% { transform: rotate(-2deg); }
}
@keyframes ovh-armF {
  0%, 100% { transform: rotate(0deg); }
  26% { transform: rotate(-170deg); }
  40% { transform: rotate(52deg); }
  58% { transform: rotate(34deg); }
  86% { transform: rotate(6deg); }
}
@keyframes ovh-foreF {
  0%, 100% { transform: rotate(0deg); }
  26% { transform: rotate(-64deg); }
  40% { transform: rotate(6deg); }
  70% { transform: rotate(-4deg); }
}
/* Back arm tucks tight through the coil -- elbow locked, forearm across the
   chin -- and only uncoils about 80ms after the blade starts down, so the
   torque of the swing still reads as coming out of the body. */
@keyframes ovh-armB {
  0%, 100% { transform: rotate(0deg); }
  26% { transform: rotate(-22deg); }
  40% { transform: rotate(-18deg); }
  47% { transform: rotate(42deg); }
  72% { transform: rotate(14deg); }
}
@keyframes ovh-foreB {
  0%, 100% { transform: rotate(0deg); }
  26% { transform: rotate(96deg); }
  40% { transform: rotate(90deg); }
  47% { transform: rotate(12deg); }
}
@keyframes ovh-torso {
  0%, 100% { transform: rotate(0deg); }
  28% { transform: rotate(-15deg); }
  41% { transform: rotate(16deg); }
  60% { transform: rotate(6deg); }
  88% { transform: rotate(-2deg); }
}
@keyframes ovh-legF {
  0%, 100% { transform: rotate(0deg); }
  23% { transform: rotate(-54deg); }
  40% { transform: rotate(22deg); }
  62% { transform: rotate(-8deg); }
}
@keyframes ovh-shinF {
  0%, 100% { transform: rotate(0deg); }
  23% { transform: rotate(72deg); }
  40% { transform: rotate(4deg); }
}

/* ---- LOW (heavy): drop under the guard, sweep up, land at 44% ---- */
.brawler.is-attacking.move-low { animation-name: low-body; }
.brawler.is-attacking.move-low .bone--pelvis { animation-name: low-pelvis; }
.brawler.is-attacking.move-low .bone--torso { animation-name: low-torso; }
.brawler.is-attacking.move-low .bone--armF { animation-name: low-armF; }
.brawler.is-attacking.move-low .bone--foreArmF { animation-name: low-foreF; }
.brawler.is-attacking.move-low .bone--armB { animation-name: low-armB; }
.brawler.is-attacking.move-low .bone--foreArmB { animation-name: low-foreB; }
.brawler.is-attacking.move-low .bone--legF { animation-name: low-legF; }
.brawler.is-attacking.move-low .bone--shinF { animation-name: low-shinF; }
.brawler.is-attacking.move-low .bone--legB { animation-name: low-legB; }

@keyframes low-body {
  0% { transform: translate(0, 0); }
  16% { transform: translate(calc(var(--lunge) * -.1), -4px); }
  38% { transform: translate(calc(var(--lunge) * .96), 14px); }
  44% { transform: translate(var(--lunge), 16px); }
  61% { transform: translate(calc(var(--lunge) * .3), 5px); animation-timing-function: cubic-bezier(.33, 0, .68, .5); }
  74% { transform: translate(calc(var(--lunge) * .14), 2px); }
  88% { transform: translate(calc(var(--lunge) * -.05), 2px); }
  95% { transform: translate(0, -1px); }
  100% { transform: translate(0, 0); }
}
@keyframes low-pelvis {
  0%, 100% { transform: rotate(0deg); }
  17% { transform: rotate(-6deg); }
  43% { transform: rotate(15deg); }
  61% { transform: rotate(6deg); }
  88% { transform: rotate(-2deg); }
}
@keyframes low-armF {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(-46deg); }
  44% { transform: rotate(96deg); }
  61% { transform: rotate(70deg); }
  88% { transform: rotate(8deg); }
}
@keyframes low-foreF {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(-70deg); }
  44% { transform: rotate(14deg); }
}
@keyframes low-armB {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(44deg); }
  44% { transform: rotate(-62deg); }
  70% { transform: rotate(-18deg); }
}
@keyframes low-foreB {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(54deg); }
  44% { transform: rotate(6deg); }
}
@keyframes low-torso {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(-7deg); }
  46% { transform: rotate(19deg); }
  64% { transform: rotate(7deg); }
  90% { transform: rotate(-2deg); }
}
@keyframes low-legF {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(26deg); }
  44% { transform: rotate(-38deg); }
  70% { transform: rotate(12deg); }
}
@keyframes low-shinF {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(58deg); }
  44% { transform: rotate(10deg); }
}
@keyframes low-legB {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(-30deg); }
  44% { transform: rotate(42deg); }
}

/* ---- THRUST (light): a straight-line burst, lands at 58% ---- */
.brawler.is-attacking.move-thrust { animation-name: thr-body; }
.brawler.is-attacking.move-thrust .bone--pelvis { animation-name: thr-pelvis; }
.brawler.is-attacking.move-thrust .bone--torso { animation-name: thr-torso; }
.brawler.is-attacking.move-thrust .bone--armF { animation-name: thr-armF; }
.brawler.is-attacking.move-thrust .bone--foreArmF { animation-name: thr-foreF; }
.brawler.is-attacking.move-thrust .bone--armB { animation-name: thr-armB; }
.brawler.is-attacking.move-thrust .bone--foreArmB { animation-name: thr-foreB; }

@keyframes thr-body {
  0% { transform: translate(0, 0); }
  18% { transform: translate(calc(var(--lunge) * -.24), 2px); }
  50% { transform: translate(calc(var(--lunge) * 1.08), 4px); }
  58% { transform: translate(var(--lunge), 4px); }
  84% { transform: translate(calc(var(--lunge) * .26), 0); }
  93% { transform: translate(calc(var(--lunge) * -.04), 1px); }   /* micro-settle */
  100% { transform: translate(0, 0); }
}
@keyframes thr-pelvis {
  0%, 100% { transform: rotate(0deg); }
  16% { transform: rotate(-7deg); }
  46% { transform: rotate(8deg); }
  74% { transform: rotate(3deg); }
}
@keyframes thr-armF {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(-52deg); }
  50% { transform: rotate(-96deg); }
  70% { transform: rotate(-90deg); }
}
@keyframes thr-foreF {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(-72deg); }
  50% { transform: rotate(2deg); }
  70% { transform: rotate(0deg); }
}
/* Tuck and hold: the off hand guards the chin through the lunge and only
   releases roughly 80ms after the point goes out. */
@keyframes thr-armB {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(-20deg); }
  58% { transform: rotate(-16deg); }
  78% { transform: rotate(34deg); }
}
@keyframes thr-foreB {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(92deg); }
  58% { transform: rotate(86deg); }
  78% { transform: rotate(10deg); }
}
@keyframes thr-torso {
  0%, 100% { transform: rotate(0deg); }
  18% { transform: rotate(-8deg); }
  52% { transform: rotate(9deg); }
}

/* ---- KICK (heavy): plant, chamber, snap, and a long settle ---- */
.brawler.is-attacking.move-kick { animation-name: kick-body; }
.brawler.is-attacking.move-kick .bone--pelvis { animation-name: kick-pelvis; }
.brawler.is-attacking.move-kick .bone--torso { animation-name: kick-torso; }
.brawler.is-attacking.move-kick .bone--legF { animation-name: kick-legF; }
.brawler.is-attacking.move-kick .bone--shinF { animation-name: kick-shinF; }
.brawler.is-attacking.move-kick .bone--legB { animation-name: kick-legB; }
.brawler.is-attacking.move-kick .bone--armF { animation-name: kick-armF; }
.brawler.is-attacking.move-kick .bone--armB { animation-name: kick-armB; }
.brawler.is-attacking.move-kick .bone--foreArmB { animation-name: kick-foreB; }

@keyframes kick-body {
  0% { transform: translate(0, 0) rotate(0deg); }
  17% { transform: translate(calc(var(--lunge) * -.14), 2px) rotate(0deg); }
  38% { transform: translate(calc(var(--lunge) * .9), -11px) rotate(calc(-4deg * var(--dir))); }
  43% { transform: translate(var(--lunge), -8px) rotate(calc(-6deg * var(--dir))); }
  60% { transform: translate(calc(var(--lunge) * .3), -2px) rotate(calc(-2deg * var(--dir))); animation-timing-function: cubic-bezier(.33, 0, .68, .5); }
  74% { transform: translate(calc(var(--lunge) * .14), 2px) rotate(0deg); }
  88% { transform: translate(calc(var(--lunge) * -.05), 2px) rotate(0deg); }
  95% { transform: translate(0, -1px) rotate(0deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes kick-pelvis {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(9deg); }
  42% { transform: rotate(-14deg); }
  60% { transform: rotate(-5deg); }
  88% { transform: rotate(2deg); }
}
@keyframes kick-torso {
  0%, 100% { transform: rotate(0deg); }
  22% { transform: rotate(5deg); }
  44% { transform: rotate(-17deg); }
  62% { transform: rotate(-6deg); }
  90% { transform: rotate(2deg); }
}
@keyframes kick-legF {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(38deg); }
  43% { transform: rotate(-104deg); }
  60% { transform: rotate(-62deg); }
  88% { transform: rotate(-8deg); }
}
@keyframes kick-shinF {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(64deg); }
  43% { transform: rotate(4deg); }   /* snaps straight into the target */
}
@keyframes kick-legB { 0%, 100% { transform: rotate(0deg); } 43% { transform: rotate(30deg); } }
@keyframes kick-armF { 0%, 100% { transform: rotate(0deg); } 43% { transform: rotate(58deg); } }
/* The whole lower body has gone forward, so the far arm has to whip across
   past the torso line or the fighter reads as falling rather than striking. */
@keyframes kick-armB {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(34deg); }
  43% { transform: rotate(-96deg); }
  62% { transform: rotate(-54deg); }
  88% { transform: rotate(-10deg); }
}
@keyframes kick-foreB {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(40deg); }
  43% { transform: rotate(-28deg); }
}

/* ---- UPPERCUT (heavy): wind low, explode up ---- */
.brawler.is-attacking.move-uppercut { animation-name: upc-body; }
.brawler.is-attacking.move-uppercut .bone--pelvis { animation-name: upc-pelvis; }
.brawler.is-attacking.move-uppercut .bone--torso { animation-name: upc-torso; }
.brawler.is-attacking.move-uppercut .bone--armF { animation-name: upc-armF; }
.brawler.is-attacking.move-uppercut .bone--foreArmF { animation-name: upc-foreF; }
.brawler.is-attacking.move-uppercut .bone--armB { animation-name: upc-armB; }
.brawler.is-attacking.move-uppercut .bone--foreArmB { animation-name: upc-foreB; }
.brawler.is-attacking.move-uppercut .bone--legF { animation-name: upc-legF; }

@keyframes upc-body {
  0% { transform: translate(0, 0); }
  18% { transform: translate(calc(var(--lunge) * -.12), 10px); }
  36% { transform: translate(var(--lunge), 8px); }
  43% { transform: translate(var(--lunge), -20px); }
  60% { transform: translate(calc(var(--lunge) * .3), -4px); animation-timing-function: cubic-bezier(.33, 0, .68, .5); }
  74% { transform: translate(calc(var(--lunge) * .14), 2px); }
  88% { transform: translate(calc(var(--lunge) * -.05), 2px); }
  95% { transform: translate(0, -1px); }
  100% { transform: translate(0, 0); }
}
@keyframes upc-pelvis {
  0%, 100% { transform: rotate(0deg); }
  22% { transform: rotate(9deg); }
  42% { transform: rotate(-11deg); }
  60% { transform: rotate(-4deg); }
  88% { transform: rotate(2deg); }
}
@keyframes upc-armF {
  0%, 100% { transform: rotate(0deg); }
  24% { transform: rotate(54deg); }
  43% { transform: rotate(-130deg); }
  60% { transform: rotate(-96deg); }
  88% { transform: rotate(-12deg); }
}
@keyframes upc-foreF {
  0%, 100% { transform: rotate(0deg); }
  24% { transform: rotate(-88deg); }
  43% { transform: rotate(-10deg); }
}
/* Same counterbalance problem as the kick: the drive is vertical and huge, so
   the far arm crosses the body to keep the mass centred. */
@keyframes upc-armB {
  0%, 100% { transform: rotate(0deg); }
  24% { transform: rotate(38deg); }
  43% { transform: rotate(-104deg); }
  62% { transform: rotate(-58deg); }
  88% { transform: rotate(-12deg); }
}
@keyframes upc-foreB {
  0%, 100% { transform: rotate(0deg); }
  24% { transform: rotate(46deg); }
  43% { transform: rotate(-24deg); }
}
@keyframes upc-torso {
  0%, 100% { transform: rotate(0deg); }
  26% { transform: rotate(12deg); }
  45% { transform: rotate(-13deg); }
  62% { transform: rotate(-4deg); }
  90% { transform: rotate(1deg); }
}
@keyframes upc-legF {
  0%, 100% { transform: rotate(0deg); }
  24% { transform: rotate(22deg); }
  45% { transform: rotate(-34deg); }
  70% { transform: rotate(-6deg); }
}

/* ============================ TAKING A HIT ============================= */
.brawler.is-hurt { animation: hurt-body var(--hit) cubic-bezier(.2,.9,.3,1); }
.brawler.is-hurt .rig { animation: hurt-flash var(--hit) ease-out; }
.brawler.is-hurt .bone--pelvis { animation: hurt-pelvis var(--hit) ease-out; }
.brawler.is-hurt .bone--torso { animation: hurt-torso var(--hit) ease-out; }
.brawler.is-hurt .bone--head { animation: hurt-head var(--hit) ease-out; }
.brawler.is-hurt .bone--armF { animation: hurt-armF var(--hit) ease-out; }
.brawler.is-hurt .bone--foreArmF { animation: hurt-foreF var(--hit) ease-out; }
.brawler.is-hurt .bone--armB { animation: hurt-armB var(--hit) ease-out; }
.brawler.is-hurt .bone--legF { animation: hurt-legF var(--hit) ease-out; }
.brawler.is-hurt .bone--shinF { animation: hurt-shinF var(--hit) ease-out; }
.brawler.is-hurt .bone--legB { animation: hurt-legB var(--hit) ease-out; }

@keyframes hurt-body {
  0% { transform: translate(0, 0); }
  18% { transform: translate(var(--knock), -5px); }
  46% { transform: translate(calc(var(--knock) * .55), 0); }
  72% { transform: translate(calc(var(--knock) * -.15), 0); }
  100% { transform: translate(0, 0); }
}
@keyframes hurt-flash {
  0% { filter: brightness(4) saturate(0); }
  14% { filter: brightness(4) saturate(0); }   /* held, not a single frame */
  38% { filter: brightness(1.5) saturate(.6); }
  100% { filter: none; }
}
@keyframes hurt-torso {
  0%, 100% { transform: rotate(0deg) scale(1, 1); }
  12% { transform: rotate(calc(8deg * var(--dir))) scale(1.09, .93); }   /* squash on contact */
  30% { transform: rotate(calc(15deg * var(--dir))) scale(.96, 1.06); }  /* stretch away */
  60% { transform: rotate(calc(-4deg * var(--dir))) scale(1.01, .99); }
}
@keyframes hurt-pelvis { 0%,100% { transform: rotate(0deg); } 10% { transform: rotate(calc(9deg * var(--dir))); } 34% { transform: rotate(calc(-4deg * var(--dir))); } }

/* The head snaps on an axis set by where the blow came from, not always flat
   backwards. --snap-up is +1 for a rising blow, -1 for one coming down, 0 for
   a level one, and it tilts the head out of the horizontal by --snap-tilt. */
@keyframes hurt-head  { 0%,100% { transform: rotate(0deg); }  20% { transform: rotate(calc(22deg * var(--dir) + var(--snap-tilt, 0deg))); } }
@keyframes hurt-armF  { 0%,100% { transform: rotate(0deg); } 22% { transform: rotate(calc(38deg * var(--dir))); } }
@keyframes hurt-foreF { 0%,100% { transform: rotate(0deg); } 22% { transform: rotate(calc(-40deg * var(--dir))); } }
@keyframes hurt-armB  { 0%,100% { transform: rotate(0deg); }  22% { transform: rotate(calc(-34deg * var(--dir))); } }
@keyframes hurt-legF  { 0%,100% { transform: rotate(0deg); }  24% { transform: rotate(calc(17deg * var(--dir))); } }
@keyframes hurt-shinF { 0%,100% { transform: rotate(0deg); }   24% { transform: rotate(21deg); } }
@keyframes hurt-legB  { 0%,100% { transform: rotate(0deg); }   24% { transform: rotate(calc(-15deg * var(--dir))); } }


/* Where the blow came from. A rising hook throws the chin up and back; a low
   sweep drives it down into a tuck; a level strike just turns the head. */
.brawler.hit-high { --snap-tilt: calc(-9deg * var(--dir)); }
.brawler.hit-low  { --snap-tilt: calc(7deg * var(--dir)); }
.brawler.hit-high .bone--torso { animation-name: hurt-torso-high; }
.brawler.hit-low  .bone--torso { animation-name: hurt-torso-low; }

@keyframes hurt-torso-high {
  0%, 100% { transform: rotate(0deg) scale(1, 1); }
  12% { transform: rotate(calc(6deg * var(--dir))) scale(1.07, .95); }
  30% { transform: rotate(calc(19deg * var(--dir))) scale(.94, 1.09); }  /* lifted off the feet */
  60% { transform: rotate(calc(-5deg * var(--dir))) scale(1.01, .99); }
}
@keyframes hurt-torso-low {
  0%, 100% { transform: rotate(0deg) scale(1, 1); }
  12% { transform: rotate(calc(12deg * var(--dir))) scale(1.12, .9); }   /* folded over it */
  30% { transform: rotate(calc(13deg * var(--dir))) scale(.97, 1.04); }
  60% { transform: rotate(calc(-3deg * var(--dir))) scale(1.01, .99); }
}

/* Ran out of arena: bounce off the edge instead of sliding through it. */
.brawler.is-walled { animation: wall-bounce calc(.52s * var(--spd, 1)) cubic-bezier(.2, .9, .3, 1); }
.brawler.is-walled .bone--torso { animation: wall-torso calc(.52s * var(--spd, 1)) ease-out; }

@keyframes wall-bounce {
  0% { transform: translate(0, 0) rotate(0deg); }
  22% { transform: translate(var(--knock), -6px) rotate(calc(-10deg * var(--dir))); }
  36% { transform: translate(var(--knock), 0) rotate(calc(-14deg * var(--dir))); }
  62% { transform: translate(calc(var(--knock) * -.45), -12px) rotate(calc(8deg * var(--dir))); }
  84% { transform: translate(calc(var(--knock) * -.12), 0) rotate(0deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes wall-torso {
  0%, 100% { transform: rotate(0deg) scale(1, 1); }
  30% { transform: rotate(calc(30deg * var(--dir))) scale(1.18, .86); }
  64% { transform: rotate(calc(-14deg * var(--dir))) scale(.94, 1.08); }
}

/* ============================== AIR STATE ==============================
 * The lift rides on __sprite, not .brawler, so knockback and recoil -- which
 * animate .brawler -- still compose on top of a fighter hanging in the air.
 * ---------------------------------------------------------------------- */
.brawler.is-airborne .brawler__sprite { animation: air-float 1.15s ease-in-out infinite; }
.brawler.is-airborne .brawler__shadow { transform: scale(.55); opacity: .3; }
/* The rig draws its own contact shadow; it must not follow them upward. */
.brawler.is-airborne .rig__shadow { opacity: 0; }
.brawler.is-airborne .bone--legF { animation: air-legF 1.15s ease-in-out infinite; }
.brawler.is-airborne .bone--legB { animation: air-legB 1.15s ease-in-out infinite; }
.brawler.is-airborne .bone--torso { animation: air-torso 1.15s ease-in-out infinite; }

@keyframes air-float { 0%, 100% { transform: translateY(-46px); } 50% { transform: translateY(-53px); } }
@keyframes air-legF { 0%, 100% { transform: rotate(-34deg); } 50% { transform: rotate(-28deg); } }
@keyframes air-legB { 0%, 100% { transform: rotate(26deg); } 50% { transform: rotate(20deg); } }
@keyframes air-torso { 0%, 100% { transform: rotate(-16deg); } 50% { transform: rotate(-11deg); } }

/* Juggled: struck again before landing, so popped higher still. */
.brawler.is-juggled .brawler__sprite { animation: air-juggle var(--hit) cubic-bezier(.2, .9, .3, 1); }
@keyframes air-juggle {
  0% { transform: translateY(-46px); }
  26% { transform: translateY(-84px); }
  100% { transform: translateY(-46px); }
}

/* Landing: absorb through the knees, cloth overshoots forward then settles. */
.brawler.is-landing { animation: land-body calc(.52s * var(--spd, 1)) cubic-bezier(.2, .9, .3, 1); }
.brawler.is-landing .bone--torso { animation: land-torso calc(.52s * var(--spd, 1)) ease-out; }
.brawler.is-landing .bone--legF { animation: land-legF calc(.52s * var(--spd, 1)) ease-out; }
.brawler.is-landing .bone--shinF { animation: land-shinF calc(.52s * var(--spd, 1)) ease-out; }
.brawler.is-landing .bone--tails,
.brawler.is-landing .bone--cape { animation: cloth-settle calc(.52s * var(--spd, 1)) cubic-bezier(.2, .9, .3, 1); }

@keyframes land-body {
  0% { transform: translateY(-40px); }
  30% { transform: translateY(6px); }
  52% { transform: translateY(-5px); }
  100% { transform: translateY(0); }
}
@keyframes land-torso { 0%, 100% { transform: rotate(0deg) scale(1, 1); } 30% { transform: rotate(9deg) scale(1.14, .86); } }
@keyframes land-legF { 0%, 100% { transform: rotate(0deg); } 30% { transform: rotate(-38deg); } }
@keyframes land-shinF { 0%, 100% { transform: rotate(0deg); } 30% { transform: rotate(46deg); } }

/* Cloth carries its own inertia: it keeps going forward, then snaps back. */
@keyframes cloth-settle {
  0% { transform: rotate(0deg); }
  22% { transform: rotate(-34deg); }
  48% { transform: rotate(12deg); }
  72% { transform: rotate(-6deg); }
  100% { transform: rotate(0deg); }
}

/* ============================== BLOCKING =============================== */
.brawler.is-blocking { animation: blk-body var(--hit) ease-out; }
.brawler.is-blocking .bone--torso { animation: blk-torso var(--hit) ease-out; }
.brawler.is-blocking .bone--head { animation: blk-head var(--hit) ease-out; }
.brawler.is-blocking .bone--armF { animation: blk-armF var(--hit) ease-out; }
.brawler.is-blocking .bone--foreArmF { animation: blk-foreF var(--hit) ease-out; }
.brawler.is-blocking .bone--legF { animation: blk-legF var(--hit) ease-out; }
.brawler.is-blocking .bone--shinF { animation: blk-shinF var(--hit) ease-out; }

@keyframes blk-body { 0%,100% { transform: translate(0,0); } 26% { transform: translate(calc(var(--knock) * .35), 5px); } }
@keyframes blk-torso { 0%,100% { transform: rotate(0deg); } 26% { transform: rotate(calc(12deg * var(--dir))); } }
@keyframes blk-head { 0%,100% { transform: rotate(0deg); } 26% { transform: rotate(calc(16deg * var(--dir))); } }
@keyframes blk-armF { 0%,100% { transform: rotate(0deg); } 22% { transform: rotate(-118deg); } 62% { transform: rotate(-106deg); } }
@keyframes blk-foreF { 0%,100% { transform: rotate(0deg); } 22% { transform: rotate(74deg); } 62% { transform: rotate(66deg); } }
@keyframes blk-legF { 0%,100% { transform: rotate(0deg); } 26% { transform: rotate(-26deg); } }
@keyframes blk-shinF { 0%,100% { transform: rotate(0deg); } 26% { transform: rotate(28deg); } }

/* PARRY: catch it on the weapon instead of bracing */
.brawler.is-blocking.evade-parry .bone--armF { animation-name: parry-armF; }
.brawler.is-blocking.evade-parry .bone--foreArmF { animation-name: parry-foreF; }
@keyframes parry-armF { 0%,100% { transform: rotate(0deg); } 24% { transform: rotate(-76deg); } 66% { transform: rotate(-64deg); } }
@keyframes parry-foreF { 0%,100% { transform: rotate(0deg); } 24% { transform: rotate(-8deg); } 66% { transform: rotate(-16deg); } }

/* ============================== DODGING ================================ */
.brawler.is-dodging { animation: ddg-body calc(.5s * var(--spd,1)) cubic-bezier(.25,.9,.3,1); }
.brawler.is-dodging .bone--torso { animation: ddg-torso calc(.5s * var(--spd,1)) ease-in-out; }
.brawler.is-dodging .bone--legF { animation: ddg-legF calc(.5s * var(--spd,1)) ease-in-out; }
.brawler.is-dodging .bone--shinF { animation: ddg-shinF calc(.5s * var(--spd,1)) ease-in-out; }
.brawler.is-dodging .bone--legB { animation: ddg-legB calc(.5s * var(--spd,1)) ease-in-out; }
.brawler.is-dodging .bone--shinB { animation: ddg-shinB calc(.5s * var(--spd,1)) ease-in-out; }
.brawler.is-dodging .bone--armF { animation: ddg-armF calc(.5s * var(--spd,1)) ease-in-out; }
.brawler.is-dodging .bone--armB { animation: ddg-armB calc(.5s * var(--spd,1)) ease-in-out; }

@keyframes ddg-body {
  0%,100% { transform: translate(0,0) rotate(0deg); }
  22% { transform: translate(calc(var(--knock) * 1.1), -34px) rotate(calc(-18deg * var(--dir))); }
  50% { transform: translate(calc(var(--knock) * 1.5), -42px) rotate(calc(-26deg * var(--dir))); }
  78% { transform: translate(calc(var(--knock) * .7), -8px) rotate(0deg); }
}
@keyframes ddg-torso { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(calc(-20deg * var(--dir))); } }
@keyframes ddg-legF { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(-70deg); } }
@keyframes ddg-shinF { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(84deg); } }
@keyframes ddg-legB { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(-38deg); } }
@keyframes ddg-shinB { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(70deg); } }
@keyframes ddg-armF { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(-64deg); } }
@keyframes ddg-armB { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(62deg); } }

/* DUCK: drop under the swing, no retreat */
.brawler.is-dodging.evade-duck { animation-name: duck-body; }
.brawler.is-dodging.evade-duck .bone--torso { animation-name: duck-torso; }
.brawler.is-dodging.evade-duck .bone--legF { animation-name: duck-legF; }
.brawler.is-dodging.evade-duck .bone--shinF { animation-name: duck-shinF; }
.brawler.is-dodging.evade-duck .bone--legB { animation-name: duck-legB; }
.brawler.is-dodging.evade-duck .bone--shinB { animation-name: duck-shinB; }

@keyframes duck-body { 0%,100% { transform: translate(0,0); } 40% { transform: translate(calc(var(--knock) * .25), 20px); } 70% { transform: translate(calc(var(--knock) * .1), 12px); } }
@keyframes duck-torso { 0%,100% { transform: rotate(0deg); } 40% { transform: rotate(calc(24deg * var(--dir))); } }
@keyframes duck-legF { 0%,100% { transform: rotate(0deg); } 40% { transform: rotate(-52deg); } }
@keyframes duck-shinF { 0%,100% { transform: rotate(0deg); } 40% { transform: rotate(66deg); } }
@keyframes duck-legB { 0%,100% { transform: rotate(0deg); } 40% { transform: rotate(46deg); } }
@keyframes duck-shinB { 0%,100% { transform: rotate(0deg); } 40% { transform: rotate(-52deg); } }

/* SIDESTEP: skate back low, no air */
.brawler.is-dodging.evade-sidestep { animation-name: side-body; }
.brawler.is-dodging.evade-sidestep .bone--torso { animation-name: side-torso; }
.brawler.is-dodging.evade-sidestep .bone--legF { animation-name: side-legF; }
.brawler.is-dodging.evade-sidestep .bone--legB { animation-name: side-legB; }
.brawler.is-dodging.evade-sidestep .bone--shinF { animation-name: side-shinF; }

@keyframes side-body { 0%,100% { transform: translate(0,0); } 35% { transform: translate(calc(var(--knock) * 1.8), 0); } 65% { transform: translate(calc(var(--knock) * 1.5), 0); } }
@keyframes side-torso { 0%,100% { transform: rotate(0deg); } 35% { transform: rotate(calc(16deg * var(--dir))); } }
@keyframes side-legF { 0%,100% { transform: rotate(0deg); } 35% { transform: rotate(calc(-42deg * var(--dir))); } }
@keyframes side-legB { 0%,100% { transform: rotate(0deg); } 35% { transform: rotate(calc(38deg * var(--dir))); } }
@keyframes side-shinF { 0%,100% { transform: rotate(0deg); } 35% { transform: rotate(26deg); } }

/* ============================ COUNTER FLASH ============================ */
.brawler.is-countering .rig { animation: ctr-flash calc(.45s * var(--spd,1)) ease-out; }
.brawler.is-countering .bone--torso { animation: ctr-coil calc(.45s * var(--spd,1)) ease-out; }
@keyframes ctr-flash {
  0%,100% { filter: none; }
  35% { filter: brightness(1.7) drop-shadow(0 0 14px rgba(205,189,230,.95)); }
}
@keyframes ctr-coil { 0%,100% { transform: rotate(0deg); } 35% { transform: rotate(calc(-16deg * var(--dir))); } }

/* ============================== HEALING ================================ */
.brawler.is-healing .rig { animation: heal-glow calc(.6s * var(--spd,1)) ease-out; }
.brawler.is-healing .bone--torso { animation: heal-rise calc(.6s * var(--spd,1)) ease-out; }
@keyframes heal-glow {
  0%,100% { filter: none; }
  40% { filter: brightness(1.3) drop-shadow(0 0 18px rgba(120,225,145,.95)); }
}
@keyframes heal-rise { 0%,100% { transform: rotate(0deg) translateY(0); } 40% { transform: rotate(-4deg) translateY(-5px); } }

/* ============================== DEFEAT ================================= */
/* ======================= KNOCKED DOWN (survivable) =====================
 * A critical hit takes the legs out. Unlike is-down, which is the end of a
 * fight, this one gets up again: driven off the feet, a beat flat on the
 * floor, then a push back to the mark. The body pivots about the feet rather
 * than its middle, so the fall reads as toppling instead of spinning.
 * ---------------------------------------------------------------------- */
.brawler.is-floored {
  transform-origin: 50% 94%;
  animation: flr-body var(--floor-dur, calc(1.15s * var(--spd, 1))) cubic-bezier(.3, .7, .35, 1);
}
.brawler.is-floored .rig { animation: flr-flash var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--pelvis { animation: flr-pelvis var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--torso { animation: flr-torso var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--head { animation: flr-head var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--armF { animation: flr-armF var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--foreArmF { animation: flr-foreF var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--armB { animation: flr-armB var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--legF { animation: flr-legF var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--shinF { animation: flr-shinF var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--legB { animation: flr-legB var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .bone--shinB { animation: flr-shinB var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }
.brawler.is-floored .brawler__shadow { animation: flr-shadow var(--floor-dur, calc(1.15s * var(--spd, 1))) ease-out; }

@keyframes flr-body {
  0% { transform: translate(0, 0) rotate(0deg); animation-timing-function: cubic-bezier(.1, .8, .4, 1); }
  13% { transform: translate(calc(var(--knock) * .8), -15px) rotate(calc(-24deg * var(--dir))); animation-timing-function: cubic-bezier(.6, 0, .9, .4); }
  29% { transform: translate(calc(var(--knock) * 1.45), 20px) rotate(calc(-80deg * var(--dir))); animation-timing-function: ease-out; }
  34% { transform: translate(calc(var(--knock) * 1.55), 25px) rotate(calc(-87deg * var(--dir))); }  /* hits the floor */
  40% { transform: translate(calc(var(--knock) * 1.52), 22px) rotate(calc(-83deg * var(--dir))); }  /* small bounce */
  58% { transform: translate(calc(var(--knock) * 1.5), 24px) rotate(calc(-85deg * var(--dir))); }   /* lies there */
  76% { transform: translate(calc(var(--knock) * 1.05), 11px) rotate(calc(-40deg * var(--dir))); }  /* pushes up */
  90% { transform: translate(calc(var(--knock) * .45), 2px) rotate(calc(-9deg * var(--dir))); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes flr-flash {
  0% { filter: brightness(4) saturate(0); }
  10% { filter: brightness(4) saturate(0); }
  32% { filter: brightness(1.4) saturate(.65); }
  100% { filter: none; }
}
@keyframes flr-pelvis {
  0%, 100% { transform: rotate(0deg); }
  13% { transform: rotate(calc(12deg * var(--dir))); }
  34% { transform: rotate(calc(-6deg * var(--dir))); }
  76% { transform: rotate(calc(14deg * var(--dir))); }   /* hips come under them */
}
@keyframes flr-torso {
  0%, 100% { transform: rotate(0deg) scale(1, 1); }
  13% { transform: rotate(calc(14deg * var(--dir))) scale(1.08, .94); }
  34% { transform: rotate(calc(22deg * var(--dir))) scale(1, 1); }
  58% { transform: rotate(calc(20deg * var(--dir))); }
  80% { transform: rotate(calc(-12deg * var(--dir))); }  /* curls up to rise */
}
@keyframes flr-head {
  0%, 100% { transform: rotate(0deg); }
  13% { transform: rotate(calc(30deg * var(--dir))); }
  34% { transform: rotate(calc(16deg * var(--dir))); }   /* lolls back on the floor */
  58% { transform: rotate(calc(20deg * var(--dir))); }
  84% { transform: rotate(calc(-10deg * var(--dir))); }
}
@keyframes flr-armF {
  0%, 100% { transform: rotate(0deg); }
  13% { transform: rotate(calc(74deg * var(--dir))); }
  34% { transform: rotate(calc(48deg * var(--dir))); }   /* flung out to break the fall */
  76% { transform: rotate(calc(-36deg * var(--dir))); }  /* pushing off the ground */
}
@keyframes flr-foreF {
  0%, 100% { transform: rotate(0deg); }
  13% { transform: rotate(calc(-52deg * var(--dir))); }
  34% { transform: rotate(calc(-14deg * var(--dir))); }
  76% { transform: rotate(calc(-46deg * var(--dir))); }
}
@keyframes flr-armB {
  0%, 100% { transform: rotate(0deg); }
  13% { transform: rotate(calc(-62deg * var(--dir))); }
  34% { transform: rotate(calc(-30deg * var(--dir))); }
  76% { transform: rotate(calc(28deg * var(--dir))); }
}
@keyframes flr-legF {
  0%, 100% { transform: rotate(0deg); }
  13% { transform: rotate(calc(-40deg * var(--dir))); }  /* feet fly up */
  34% { transform: rotate(calc(-22deg * var(--dir))); }
  76% { transform: rotate(calc(34deg * var(--dir))); }   /* knee gathers underneath */
}
@keyframes flr-shinF {
  0%, 100% { transform: rotate(0deg); }
  34% { transform: rotate(46deg); }
  76% { transform: rotate(62deg); }
}
@keyframes flr-legB {
  0%, 100% { transform: rotate(0deg); }
  13% { transform: rotate(calc(-18deg * var(--dir))); }
  34% { transform: rotate(calc(-34deg * var(--dir))); }
  76% { transform: rotate(calc(12deg * var(--dir))); }
}
@keyframes flr-shinB {
  0%, 100% { transform: rotate(0deg); }
  34% { transform: rotate(28deg); }
  76% { transform: rotate(40deg); }
}
/* The cast shadow spreads as they go flat and gathers as they stand. */
@keyframes flr-shadow {
  0%, 100% { transform: scaleX(1); opacity: .5; }
  13% { transform: scaleX(.7); opacity: .32; }
  34% { transform: scaleX(1.75); opacity: .55; }
  58% { transform: scaleX(1.7); opacity: .55; }
}

@media (prefers-reduced-motion: reduce) {
  .brawler.is-floored, .brawler.is-floored .bone,
  .brawler.is-floored .rig, .brawler.is-floored .brawler__shadow { animation: none; }
}

.brawler.is-down { animation: down-body calc(1s * var(--spd,1)) cubic-bezier(.35,.1,.3,1) forwards; }
.brawler.is-down .rig { animation: down-fade calc(1s * var(--spd,1)) ease-out forwards; }
.brawler.is-down .bone--torso { animation: down-torso calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .bone--head { animation: down-head calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .bone--armF { animation: down-armF calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .bone--foreArmF { animation: down-foreF calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .bone--armB { animation: down-armB calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .bone--legF { animation: down-legF calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .bone--shinF { animation: down-shinF calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .bone--legB { animation: down-legB calc(1s * var(--spd,1)) ease-in forwards; }
.brawler.is-down .brawler__shadow { transform: scaleX(1.5); opacity: .45; }

@keyframes down-body {
  0% { transform: translate(0,0) rotate(0deg); }
  22% { transform: translate(calc(var(--knock) * .9), -16px) rotate(calc(-16deg * var(--dir))); }
  100% { transform: translate(calc(var(--knock) * 1.7), 26px) rotate(calc(-84deg * var(--dir))); }
}
@keyframes down-fade {
  0% { filter: brightness(3.2) saturate(0); }
  30% { filter: none; }
  100% { filter: grayscale(.5) brightness(.66); }
}
@keyframes down-torso { 0% { transform: rotate(-1deg); } 100% { transform: rotate(calc(18deg * var(--dir))); } }
@keyframes down-head  { 0% { transform: rotate(2deg); }  100% { transform: rotate(calc(40deg * var(--dir))); } }
@keyframes down-armF  { 0% { transform: rotate(-14deg); } 100% { transform: rotate(calc(104deg * var(--dir))); } }
@keyframes down-foreF { 0% { transform: rotate(-30deg); } 100% { transform: rotate(calc(30deg * var(--dir))); } }
@keyframes down-armB  { 0% { transform: rotate(12deg); }  100% { transform: rotate(calc(-80deg * var(--dir))); } }
@keyframes down-legF  { 0% { transform: rotate(-6deg); }  100% { transform: rotate(calc(-46deg * var(--dir))); } }
@keyframes down-shinF { 0% { transform: rotate(4deg); }   100% { transform: rotate(58deg); } }
@keyframes down-legB  { 0% { transform: rotate(7deg); }   100% { transform: rotate(calc(-16deg * var(--dir))); } }

/* ---- floating numbers and impact bursts ---- */
.fx-num {
  position: absolute;
  transform: translate(-50%, -50%);
  font-family: var(--font);
  font-size: 1.5rem;
  font-weight: 700;
  color: #ffd8d8;
  text-shadow: 0 2px 0 #000, 0 0 12px rgba(0, 0, 0, .9);
  animation: fx-float 1s ease-out forwards;
  white-space: nowrap;
  pointer-events: none;
}

.fx-num--crit { font-size: 2.2rem; color: #ffd479; }
.fx-num--heal { color: #8fe3a3; }
.fx-num--miss { font-size: 1.05rem; letter-spacing: .18em; color: var(--ink-dim); }
.fx-num--block { font-size: 1.05rem; letter-spacing: .18em; color: #a9c4dd; }
.fx-num--counter { font-size: 1.15rem; letter-spacing: .1em; color: #d6c2f0; }
.fx-num--combo2 { font-size: 1.3rem; letter-spacing: .12em; color: #ffcf6b; }
.fx-num--combo3 { font-size: 1.9rem; letter-spacing: .14em; color: #ff8a4d;
  text-shadow: 0 0 14px rgba(255, 120, 40, .55); }
.fx-num--thorns { color: #e0b070; }

@keyframes fx-float {
  0% { opacity: 0; transform: translate(-50%, -30%) scale(.6); }
  18% { opacity: 1; transform: translate(-50%, -60%) scale(1.15); }
  35% { transform: translate(-50%, -68%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -160%) scale(1); }
}

.fx-burst {
  position: absolute;
  width: 96px;
  height: 96px;
  margin: -48px 0 0 -48px;
  border-radius: 50%;
  background-image: var(--fx-impact, none),
    radial-gradient(closest-side, rgba(255, 238, 196, .95), rgba(255, 148, 58, .45) 45%, transparent 70%);
  background-size: contain, contain;
  background-repeat: no-repeat;
  background-position: center;
  animation: burst .34s ease-out forwards;
  pointer-events: none;
}

.fx-burst--crit { width: 152px; height: 152px; margin: -76px 0 0 -76px; }

@keyframes burst {
  0% { opacity: 0; transform: scale(.35) rotate(0); }
  30% { opacity: 1; transform: scale(1.05) rotate(12deg); }
  100% { opacity: 0; transform: scale(1.4) rotate(22deg); }
}

/* Weapon arc drawn through the contact point */
.fx-slash {
  position: absolute;
  width: 150px;
  height: 150px;
  margin: -75px 0 0 -75px;
  border-radius: 50%;
  border: 10px solid transparent;
  border-top-color: rgba(255, 251, 232, .95);
  border-right-color: rgba(255, 226, 160, .6);
  animation: slash .32s cubic-bezier(.2, .8, .3, 1) forwards;
  pointer-events: none;
}

.fx-slash--crit {
  width: 210px;
  height: 210px;
  margin: -105px 0 0 -105px;
  border-width: 14px;
  border-top-color: #fff6d6;
  border-right-color: rgba(255, 196, 70, .85);
}

@keyframes slash {
  0% { opacity: 0; transform: rotate(calc(-84deg * var(--dir, 1))) scale(.5); }
  30% { opacity: 1; }
  100% { opacity: 0; transform: rotate(calc(58deg * var(--dir, 1))) scale(1.18); }
}

/* Debris thrown from the point of impact */
.fx-spark {
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: #ffe9a8;
  box-shadow: 0 0 8px rgba(255, 208, 110, .9);
  animation: spark .5s cubic-bezier(.15, .8, .3, 1) forwards;
  pointer-events: none;
}

.fx-spark--blood { background: #e4574a; box-shadow: 0 0 8px rgba(210, 60, 50, .85); }

@keyframes spark {
  0% { opacity: 1; transform: translate(-50%, -50%) rotate(var(--a)) translateX(0) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) rotate(var(--a)) translateX(var(--d)) scale(.15); }
}

/* Dust kicked up by a dash or a landing */
.fx-dust {
  position: absolute;
  width: 74px;
  height: 30px;
  margin: -15px 0 0 -37px;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(255, 244, 214, .8), rgba(226, 189, 122, .25) 60%, transparent);
  animation: dust .55s ease-out forwards;
  pointer-events: none;
}

@keyframes dust {
  0% { opacity: .95; transform: scale(.3) translateY(0); }
  100% { opacity: 0; transform: scale(1.75) translateY(-10px); }
}

/* Speed lines trailing a charging fighter */
.fx-streak {
  position: absolute;
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, rgba(255, 248, 224, .75));
  animation: streak .34s ease-out forwards;
  pointer-events: none;
}

@keyframes streak {
  0% { opacity: .85; transform: translate(-50%, -50%) scaleX(.2); }
  100% { opacity: 0; transform: translate(-50%, -50%) scaleX(1.3) translateX(calc(-26px * var(--dir, 1))); }
}

/* Ghost lines: motion blur behind a fast fist or boot */
.fx-ghost {
  position: absolute;
  height: 4px;
  border-radius: 3px;
  background: linear-gradient(90deg, transparent, rgba(255, 250, 232, .85));
  transform: translate(-50%, -50%) scaleX(var(--dir, 1));
  animation: ghost .26s ease-out forwards;
  pointer-events: none;
}

@keyframes ghost {
  0% { opacity: .9; transform: translate(-50%, -50%) scaleX(calc(.3 * var(--dir, 1))); }
  100% { opacity: 0; transform: translate(-50%, -50%) scaleX(calc(1.4 * var(--dir, 1))) translateX(calc(-30px * var(--dir, 1))); }
}

/* A flat dust wave rolling outward along the floor from a heavy landing. */
.fx-wave {
  position: absolute;
  width: 34px;
  height: 12px;
  margin: -6px 0 0 -17px;
  border: 3px solid rgba(255, 236, 196, .8);
  border-radius: 50%;
  animation: wave .6s cubic-bezier(.1, .8, .3, 1) forwards;
  pointer-events: none;
}

@keyframes wave {
  0% { opacity: .95; transform: scale(.3, .5); }
  100% { opacity: 0; transform: scale(6.5, 1.6); }
}

/* Successive crescents laid down along a swing read as a blade trail. */
.fx-slash--trail { animation-duration: .3s; filter: blur(.6px); }

/* Shockwave ring on impact */
.fx-shock {
  position: absolute;
  width: 60px;
  height: 60px;
  margin: -30px 0 0 -30px;
  border: 5px solid rgba(255, 233, 168, .9);
  border-radius: 50%;
  animation: shock .45s cubic-bezier(.15, .75, .3, 1) forwards;
  pointer-events: none;
}

@keyframes shock {
  0% { opacity: 1; transform: scale(.3); }
  100% { opacity: 0; transform: scale(3.4); }
}

/* ---- arena HUD (names, health, portrait insets) ---- */
.hud {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 7;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  padding: 10px 12px 0;
  pointer-events: none;
}

/* Scrim so names and bars stay legible over a bright backdrop. */
.hud::before {
  content: "";
  position: absolute;
  inset: 0 0 -40px 0;
  background: linear-gradient(180deg, rgba(44, 22, 2, .72) 0%, rgba(44, 22, 2, .32) 55%, transparent 100%);
  pointer-events: none;
}

.hud__side { display: grid; gap: 5px; position: relative; }
.hud__side--enemy { justify-items: end; }

.hud__name {
  font-family: var(--font);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #fffaf0;
  line-height: 1.1;
  -webkit-text-stroke: 1.5px #3a2004;
  paint-order: stroke fill;
  text-shadow: 0 3px 0 rgba(58, 32, 4, .95), 0 0 14px rgba(0, 0, 0, .5);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.hud__bar {
  position: relative;
  width: 100%;
  height: 22px;
  border-radius: 999px;
  border: 3px solid #5a3308;
  background: linear-gradient(180deg, #4a2c09, #6b4310);
  overflow: hidden;
  box-shadow: 0 2px 0 rgba(0, 0, 0, .35);
}

.hud__fill {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: linear-gradient(180deg, #a8e6b0 0%, #5cc46e 42%, #2f8a45 100%);
  box-shadow: inset 0 -4px 0 rgba(20, 70, 32, .45);
  transition: width .3s cubic-bezier(.4, 0, .2, 1), background .25s ease;
}

/* Both sides read on the same scale. Which bar is whose is already said by the
 * name above it and the side it fills from; what the colour is for is how much
 * trouble that fighter is in, and that has to mean the same thing on both. */
.hud__fill--foe { background: linear-gradient(180deg, #a8e6b0 0%, #5cc46e 42%, #2f8a45 100%); }

.hud__fill.is-hurt {
  background: linear-gradient(180deg, #ffe9a0 0%, #ffc72e 42%, #e79a12 100%);
  box-shadow: inset 0 -4px 0 rgba(150, 84, 6, .5);
}

.hud__fill.is-critical {
  background: linear-gradient(180deg, #ffa9a0 0%, #e8452e 42%, #ab2313 100%);
  box-shadow: inset 0 -4px 0 rgba(110, 20, 12, .55);
}
.hud__side--enemy .hud__bar { direction: rtl; }

.hud__hp {
  position: absolute;
  inset: 0;
  direction: ltr;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  color: #4a2a06;
  text-shadow: 0 1px 0 rgba(255, 240, 200, .5);
}

.hud__row { display: flex; align-items: center; gap: 8px; }

/* Portrait inset: the same procedural layers, framed and cropped to the head. */
.portrait {
  position: relative;
  width: 74px;
  height: 74px;
  flex: 0 0 74px;
  overflow: hidden;
  border: 3px solid #5a3308;
  border-radius: 6px;
  background: linear-gradient(180deg, #ffcf70, #e09a2c);
  box-shadow: 0 3px 0 rgba(0, 0, 0, .3);
}

.portrait .avatar {
  position: absolute;
  width: 186px;
  left: -56px;
  top: -19px;
}

.portrait .avatar.avatar--modular { width: 148px; left: -40px; top: -32px; }
@media(max-width:760px){.portrait .avatar.avatar--modular{width:90px;left:-25px;top:-19px;}}
@media (min-width: 761px) and (max-width: 1194px) and (pointer: coarse) and (hover: none) {
  .portrait .avatar.avatar--modular { width: 90px; left: -25px; top: -19px; }
}

/* Boss powers in play, beneath the health bar. The carry cap holds this to
 * three a side, so the row can never crowd the arena the way the old full
 * loadout did. */
.gear { list-style: none; display: flex; gap: 5px; margin: 0; padding: 0; }

.gear__slot {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  font-size: .9rem;
  border-radius: 5px;
  transform: rotate(-4deg);
  color: var(--gold);
  border: 2px solid rgba(214, 173, 92, .6);
  background: rgba(214, 173, 92, .16);
}

/* ---- log ---- */
.log-wrap {
  margin-top: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #171310;
}

.log-wrap__toggle {
  cursor: pointer;
  padding: 10px 16px;
  font-size: .72rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  list-style: none;
}

.log-wrap__toggle::-webkit-details-marker { display: none; }

.log-wrap__toggle::before {
  content: "B8";
  display: inline-block;
  margin-right: 8px;
  color: var(--gold-dim);
  transition: transform .16s ease;
}

.log-wrap[open] .log-wrap__toggle::before { transform: rotate(90deg); }
.log-wrap__toggle:hover { color: var(--ink-dim); }

.log {
  height: 190px;
  overflow-y: auto;
  padding: 0 18px 14px;
  font-family: var(--font-mono);
  font-size: .8rem;
  line-height: 1.7;
  scroll-behavior: smooth;
}

.log__line { margin: 0; color: var(--ink-dim); animation: fade-in .18s ease both; }
.log__line--player { color: #b7d8bd; }
.log__line--enemy { color: #e0b0b0; }
.log__line--system { color: var(--gold); border-bottom: 1px dashed var(--line); padding-bottom: 8px; margin-bottom: 8px; }
.log__line--crit { color: #ffd479; font-weight: 700; }
.log__line--miss { color: var(--ink-faint); font-style: italic; }
.log__line--block { color: var(--steel); }
.log__line--heal, .log__line--regen { color: #8fd6a0; }
.log__line--counter { color: #cdbde6; }
.log__line--thorns { color: #d5a86a; }
.log__line--victory, .log__line--defeat { color: var(--gold); font-weight: 700; margin-top: 8px; }

/* --------------------------------------------------------------- outcome */
/* The result lands on the arena itself, over the fight that produced it, so
 * the player never has to scroll to find out what happened. */
.outcome {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  z-index: 6;
  width: min(92%, 520px);
  padding: 14px 20px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(10, 12, 16, .84);
  backdrop-filter: blur(6px);
  text-align: center;
  animation: outcome-rise .34s cubic-bezier(.2, .9, .3, 1) both;
}

.outcome__body { margin-bottom: 12px; }
.outcome .btn { width: 100%; }

@keyframes outcome-rise {
  from { opacity: 0; transform: translate(-50%, 16px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

@media (max-width: 640px) {
  .outcome { padding: 11px 14px 12px; bottom: 8px; }
  .outcome h2 { font-size: 1.1rem; }
}

.outcome h2 { font-size: 1.35rem; margin-bottom: 4px; }
.outcome p { margin: 0; color: var(--ink-dim); }
.outcome__note { color: var(--gold) !important; font-weight: 700; letter-spacing: .06em; margin-top: 6px !important; }
.outcome--win { border-color: rgba(95, 156, 106, .7); background: rgba(16, 34, 20, .86); }
.outcome--win h2 { color: #9fe0ac; }
.outcome--loss { border-color: rgba(216, 75, 75, .7); background: rgba(38, 14, 14, .86); }
.outcome--loss h2 { color: #ffb1b1; }

/* --------------------------------------------------------------- destiny */
.destiny-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
  margin-top: 26px;
  text-align: left;
}

.destiny-card {
  position: relative;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  display: grid;
  gap: 8px;
  padding: 24px 22px;
  background: linear-gradient(180deg, var(--panel-2) 0%, #1e1a16 100%);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}

.destiny-card:hover {
  transform: translateY(-4px);
  border-color: var(--gold);
  box-shadow: 0 14px 30px rgba(217, 164, 65, .16);
}
.destiny-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

/* The release step: these cards take something away rather than granting it,
 * so they must not read as another reward on the same shelf. */
.destiny-card--release { border-color: rgba(194, 90, 74, .55); }
.destiny-card--release .destiny-card__glyph { color: #c25a4a; }
.destiny-card--release:hover {
  border-color: #c25a4a;
  box-shadow: 0 14px 30px rgba(194, 90, 74, .18);
}

/* The prize torn off a floor boss. It is the only card in the row that is not
 * simply another upgrade, and it is easy to skip past if it looks like one. */
.destiny-card--boss {
  border-color: #c2453a;
  background: linear-gradient(180deg, #2b1614 0%, #1d1210 100%);
  box-shadow: 0 0 0 1px rgba(194, 69, 58, .35) inset;
}
.destiny-card--boss .destiny-card__glyph { color: #ff6b5e; }
.destiny-card--boss .destiny-card__name { color: #ffb3aa; }
.destiny-card--boss .destiny-card__kind {
  color: #ff8a7e;
  border-color: rgba(255, 59, 48, .55);
  background: rgba(255, 59, 48, .14);
}
.destiny-card--boss:hover {
  border-color: #ff6b5e;
  box-shadow: 0 14px 30px rgba(194, 69, 58, .28);
}

/* A weapon swap. Steel-blue rather than red: it is not a prize, it is a trade
 * -- whatever you are holding goes, rider and all. */
.destiny-card--weapon {
  border-color: #4a7fa8;
  background: linear-gradient(180deg, #16222b 0%, #111a20 100%);
  box-shadow: 0 0 0 1px rgba(74, 127, 168, .3) inset;
}
.destiny-card--weapon .destiny-card__glyph { color: #7fc4ef; }
.destiny-card--weapon .destiny-card__name { color: #a8dcfb; }
.destiny-card--weapon .destiny-card__kind {
  color: #8cc6ff;
  border-color: rgba(90, 169, 255, .5);
  background: rgba(90, 169, 255, .12);
}
.destiny-card--weapon:hover {
  border-color: #7fc4ef;
  box-shadow: 0 14px 30px rgba(74, 127, 168, .26);
}

/* A passive that ranks up: violet, the colour the game already uses for
 * something earned and permanent. */
.destiny-card--skill {
  border-color: #6f5aa6;
  background: linear-gradient(180deg, #1e1930 0%, #151223 100%);
  box-shadow: 0 0 0 1px rgba(111, 90, 166, .3) inset;
}
.destiny-card--skill .destiny-card__glyph { color: #b79cf0; }
.destiny-card--skill .destiny-card__name { color: #cfbcf9; }
.destiny-card--skill .destiny-card__kind {
  color: #cfa6ff;
  border-color: rgba(180, 119, 255, .5);
  background: rgba(180, 119, 255, .13);
}
.destiny-card--skill:hover {
  border-color: #b79cf0;
  box-shadow: 0 14px 30px rgba(111, 90, 166, .26);
}

/* Raw growth keeps the plain card. It is the baseline the other kinds are
 * marked *against* -- if everything is coloured, nothing is. */

/* The weapon taken off a mini-boss: green, matching the glow on the weapon
 * itself, so the card and the thing it gives you are recognisably the same
 * prize. */
.destiny-card--champion {
  border-color: #4a9e5c;
  background: linear-gradient(180deg, #142318 0%, #101b13 100%);
  box-shadow: 0 0 0 1px rgba(74, 158, 92, .32) inset;
}
.destiny-card--champion .destiny-card__glyph { color: #5ccf6e; }
.destiny-card--champion .destiny-card__name { color: #9fe8ae; }
.destiny-card--champion .destiny-card__kind {
  color: #7fd694;
  border-color: rgba(92, 207, 110, .55);
  background: rgba(92, 207, 110, .14);
}
.destiny-card--champion:hover {
  border-color: #5ccf6e;
  box-shadow: 0 14px 30px rgba(74, 158, 92, .28);
}

/* The rider chip on a champion weapon, wherever it is shown. */
.weapon__bonus--champion, .rider--champion {
  color: #9fe8ae;
  border-color: rgba(92, 207, 110, .55);
  background: rgba(92, 207, 110, .14);
}

/* The weapon torn off your own shadow at floor 100/200/300... . Every other
 * weapon-replacing card is cool (blue for an ordinary swap, green for a
 * champion's) -- this one is a hot, saturated red (matching the Godlike
 * tier's own #ff3b30 glow, see WEAPON_TIERS/.rider--godlike) so the rarest
 * prize in the game never reads as just another weapon card. */
.destiny-card--godlike {
  border-color: #ff3b30;
  background: linear-gradient(180deg, #2c1210 0%, #1d0e0d 100%);
  box-shadow: 0 0 0 1px rgba(255, 59, 48, .45) inset;
}
.destiny-card--godlike .destiny-card__glyph { color: #ff6a5e; }
.destiny-card--godlike .destiny-card__name { color: #ffb0a8; }
.destiny-card--godlike .destiny-card__kind {
  color: #ff8a7e;
  border-color: rgba(255, 59, 48, .65);
  background: rgba(255, 59, 48, .18);
}
.destiny-card--godlike:hover {
  border-color: #ff6a5e;
  box-shadow: 0 14px 34px rgba(255, 59, 48, .32);
}

.destiny-card--keep { border-style: dashed; }
.destiny-card--keep .destiny-card__glyph { color: var(--ink-faint); }
.destiny-card--keep:hover {
  border-color: var(--ink-faint);
  box-shadow: none;
}

.destiny-card__glyph { font-size: 1.9rem; color: var(--gold); line-height: 1; }
.destiny-card__name { font-size: 1.15rem; }
.destiny-card__desc { margin: 0; font-size: .88rem; color: var(--ink-dim); }

.destiny-card__kind {
  position: absolute;
  top: 14px;
  right: 14px;
  font-size: .58rem;
  letter-spacing: .2em;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 9px;
}

/* --------------------------------------------------------------- game over */
.gameover__name { font-size: 1.5rem; margin-top: 14px; color: var(--blood-bright); }
.gameover__best { font-family: var(--font-mono); color: var(--gold); letter-spacing: .06em; }

/* --------------------------------------------------------------- footer */
.footer {
  margin-top: 40px;
  padding: 18px 16px 10px;
  border-top: 1px solid var(--line);
  color: var(--ink-dim);
  font-size: .78rem;
  text-align: center;
  /* The only text that sits straight on the dojo floor, so it carries its
   * own scrim rather than relying on whatever plank is behind it. */
  border-radius: var(--radius);
  background: linear-gradient(180deg, rgba(9, 8, 7, .34), rgba(9, 8, 7, .62));
  backdrop-filter: blur(5px);
}
.footer p { margin: 0; }

/* --------------------------------------------------------------- responsive */
@media (max-width: 760px) {
  .gear__slot { width: 22px; height: 22px; font-size: .72rem; }
  .forge { grid-template-columns: 1fr; }
  .forge__portrait { max-width: 240px; margin: 0 auto; }
  .matchup { grid-template-columns: 1fr; }
  .versus { justify-self: center; }
  .brand { font-size: 1.05rem; letter-spacing: .18em; }
  .panel { padding: 20px 16px; }
  .stats { grid-template-columns: repeat(2, 1fr); }
  .stage { height: 288px; --lunge: 62px; }
  .brawler { --body-w: 96px; --stance: 74px; }
  .brawler__shadow { width: 62px; margin-left: -31px; }
  .hud { gap: 8px; padding: 7px 8px 0; }
  .hud__name { font-size: 1rem; -webkit-text-stroke-width: .6px; }
  .hud__bar { height: 16px; border-width: 2px; }
  .portrait { width: 46px; height: 46px; flex-basis: 46px; }
  .portrait .avatar { width: 116px; left: -35px; top: -12px; }
  .fx-num { font-size: 1.2rem; }
  .fx-num--crit { font-size: 1.7rem; }
}

/* Same battle-rig sizing as the phone breakpoint above, extended to
   touch tablets (iPad): their viewport is wider than 760px so they'd
   otherwise render the full desktop-size stage on mobile-class GPU
   hardware, which is what made battles feel smooth on iPhone but janky
   on iPad. Scoped to just the stage/fighters/HUD -- other screens
   (forge, matchup, etc.) are unaffected on tablets. */
@media (min-width: 761px) and (max-width: 1194px) and (pointer: coarse) and (hover: none) {
  .stage { height: 288px; --lunge: 62px; }
  .brawler { --body-w: 96px; --stance: 74px; }
  .brawler__shadow { width: 62px; margin-left: -31px; }
  .hud { gap: 8px; padding: 7px 8px 0; }
  .hud__name { font-size: 1rem; -webkit-text-stroke-width: .6px; }
  .hud__bar { height: 16px; border-width: 2px; }
  .portrait { width: 46px; height: 46px; flex-basis: 46px; }
  .portrait .avatar { width: 116px; left: -35px; top: -12px; }
  .fx-num { font-size: 1.2rem; }
  .fx-num--crit { font-size: 1.7rem; }
}

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


/* ============================ DICE PRIZE ==============================
 * Called after a floor boss falls: guess the half of the die and take the
 * boss's own power as a Destiny.
 * ---------------------------------------------------------------------- */
/* A real cube, tumbled with a 3D transform and landed on the rolled face. */
.dice3d {
  position: relative;
  width: 124px;
  height: 124px;
  margin: 24px auto 12px;
  perspective: 760px;
}

/* Grounding shadow. It cannot live on the cube: any filter there would force
 * transform-style to flatten and collapse the die into stacked squares. */
.dice3d::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -14px;
  width: 96px;
  height: 16px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(0, 0, 0, .55), transparent 70%);
  pointer-events: none;
}

.dice3d__cube {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transform: rotateX(-22deg) rotateY(26deg);
  transition: transform 1.15s cubic-bezier(.18, .74, .22, 1);
}

.dice3d__face {
  position: absolute;
  inset: 0;
  padding: 15px;
  display: grid;
  grid-template: repeat(3, 1fr) / repeat(3, 1fr);
  gap: 5px;
  border-radius: 16px;
  border: 1px solid rgba(0, 0, 0, .18);
  background: linear-gradient(155deg, #ffffff 0%, #f2f0ec 52%, #d8d4cc 100%);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .9),
              inset 0 -12px 20px rgba(0, 0, 0, .13);
  backface-visibility: hidden;
}

/* 124px cube, so each face sits 62px out from the centre. */
.dice3d__face--1 { transform: translateZ(62px); }
.dice3d__face--6 { transform: rotateY(180deg) translateZ(62px); }
.dice3d__face--2 { transform: rotateY(90deg) translateZ(62px); }
.dice3d__face--5 { transform: rotateY(-90deg) translateZ(62px); }
.dice3d__face--3 { transform: rotateX(90deg) translateZ(62px); }
.dice3d__face--4 { transform: rotateX(-90deg) translateZ(62px); }

.dice3d__pip {
  align-self: center;
  justify-self: center;
  width: 74%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #e8564f, #c62828 58%, #8d1c1c);
  box-shadow: inset 0 -1px 2px rgba(0, 0, 0, .35), 0 1px 1px rgba(0, 0, 0, .18);
}

.dice__result { min-height: 1.4em; margin: 6px 0 14px; font-weight: 700; letter-spacing: .04em; }
.dice__result--win { color: #9fe0ac; }
.dice__result--loss { color: #ffb1b1; }

@media (prefers-reduced-motion: reduce) {
  .dice3d__cube { transition-duration: .01s; }
}

/* =========================== HALL OF FAME ============================= */
.hall { list-style: none; margin: 18px 0 22px; padding: 0; display: grid; gap: 12px; }

.hall__row {
  display: grid;
  grid-template-columns: 68px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 12px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .02);
  text-align: left;
}

.hall__row--1 { border-color: rgba(214, 173, 92, .6); background: rgba(214, 173, 92, .07); }
.hall__rank { font-family: var(--font); font-size: 1.5rem; color: var(--gold); }
.hall__avatar { width: 68px; height: 92px; }
.hall__name { font-family: var(--font); font-size: 1.06rem; }
.hall__meta { font-size: .82rem; color: var(--ink-dim); }
.hall__floor { font-family: var(--font); font-size: 1.7rem; color: var(--gold); text-align: right; }
.hall__floor small { display: block; font-size: .62rem; letter-spacing: .14em; color: var(--ink-dim); }
.hall__empty { color: var(--ink-dim); padding: 22px 0; }

@media (max-width: 560px) {
  .hall__row { grid-template-columns: 52px 1fr auto; }
  .hall__avatar { width: 52px; height: 70px; }
}

/* The weapon's stat rider, on its own line under the description. */
.weapon__bonus {
  grid-column: 2;
  justify-self: start;
  margin-top: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--gold);
  border: 1px solid rgba(214, 173, 92, .45);
  background: rgba(214, 173, 92, .12);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The stat rider a weapon on offer would bring. */
.destiny-card__rider {
  display: inline-block;
  /* The card is a flex column, which would stretch the chip edge to edge. */
  align-self: start;
  width: fit-content;
  margin: 2px 0 6px;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--gold);
  border: 1px solid rgba(214, 173, 92, .45);
  background: rgba(214, 173, 92, .12);
}


/* ========================= FIGHT CALLOUT ==============================
 * Slams onto the arena as the bout opens. It sits outside .stage__camera so
 * the world shakes under it while the word itself holds still.
 * ---------------------------------------------------------------------- */
.callout {
  position: absolute;
  left: 50%;
  top: 46%;
  z-index: 7;
  width: min(78%, 620px);
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: fight-slam var(--callout, 1s) cubic-bezier(.16, .9, .3, 1) both;
}

.callout img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, .75))
          drop-shadow(0 0 34px rgba(190, 30, 30, .5));
}

@keyframes fight-slam {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(2.9) rotate(-8deg); filter: blur(7px); }
  20%  { opacity: 1; transform: translate(-50%, -50%) scale(.92) rotate(-2.5deg); filter: blur(0); }
  30%  { transform: translate(-50%, -50%) scale(1.05) rotate(-2.5deg); }
  40%  { transform: translate(-50%, -50%) scale(1) rotate(-2.5deg); }
  74%  { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(-2.5deg); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.22) rotate(-2.5deg); }
}

@media (prefers-reduced-motion: reduce) {
  .callout { animation-duration: .4s; }
  @keyframes fight-slam {
    0% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
    30%, 74% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
  }
}


/* ========================== RANGED ATTACKS ============================
 * A bow, a wand and a throwing star are loosed, not swung. The archer plants
 * instead of lunging, so the move is all draw-and-release in the upper body.
 * ---------------------------------------------------------------------- */
.brawler.is-attacking.move-shoot .bone--armF {
  animation: shoot-armF var(--atk, .62s) cubic-bezier(.2, .85, .25, 1);
}
.brawler.is-attacking.move-shoot .bone--foreArmF {
  animation: shoot-foreF var(--atk, .62s) cubic-bezier(.2, .85, .25, 1);
}
.brawler.is-attacking.move-shoot .bone--armB {
  animation: shoot-armB var(--atk, .62s) cubic-bezier(.2, .85, .25, 1);
}
.brawler.is-attacking.move-shoot .bone--torso {
  animation: shoot-torso var(--atk, .62s) cubic-bezier(.2, .85, .25, 1);
}
.brawler.is-attacking.move-shoot .bone--head {
  animation: shoot-head var(--atk, .62s) ease-out;
}

/* Front arm holds the weapon out level; the draw and the snap live in it. */
@keyframes shoot-armF {
  0% { transform: rotate(0deg); }
  34% { transform: rotate(-56deg); }
  52% { transform: rotate(-64deg); }
  60% { transform: rotate(-50deg); }
  100% { transform: rotate(0deg); }
}
@keyframes shoot-foreF {
  0% { transform: rotate(0deg); }
  34% { transform: rotate(-24deg); }
  52% { transform: rotate(-30deg); }
  60% { transform: rotate(-12deg); }
  100% { transform: rotate(0deg); }
}
/* Back arm hauls the string, then releases. */
@keyframes shoot-armB {
  0% { transform: rotate(0deg); }
  40% { transform: rotate(-38deg); }
  52% { transform: rotate(-46deg); }
  58% { transform: rotate(4deg); }
  100% { transform: rotate(0deg); }
}
@keyframes shoot-torso {
  0%, 100% { transform: rotate(0deg); }
  46% { transform: rotate(-5deg); }
  58% { transform: rotate(3deg); }
}
@keyframes shoot-head {
  0%, 100% { transform: rotate(0deg); }
  46% { transform: rotate(-4deg); }
}

/* ------------------------------- the shot ----------------------------- */
.fx-shot {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  transform-origin: 50% 50%;
  animation: shot-fly linear forwards;
}

@keyframes shot-fly {
  from { transform: translate(-50%, -50%) scaleX(var(--dir, 1)); }
  to   { transform: translate(calc(-50% + var(--fly, 200px)),
                              calc(-50% + var(--rise, 0px))) scaleX(var(--dir, 1)); }
}

/* An arrow: shaft, head, fletching. */
.fx-shot--arrow {
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg, #6b4a2a 0%, #a9793f 62%, #d9c9a6 100%);
  box-shadow: 0 0 8px rgba(255, 210, 140, .5);
}
.fx-shot--arrow::after {
  content: '';
  position: absolute;
  right: -9px; top: 50%;
  width: 0; height: 0;
  transform: translateY(-50%);
  border-left: 10px solid #e6e9ee;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}
.fx-shot--arrow::before {
  content: '';
  position: absolute;
  left: -3px; top: 50%;
  width: 10px; height: 11px;
  transform: translateY(-50%) skewX(-22deg);
  background: #c8563f;
  border-radius: 2px;
}

/* A magic bolt. */
.fx-shot--bolt {
  width: 26px;
  height: 10px;
  border-radius: 999px;
  background: radial-gradient(circle at 70% 50%, #ffffff, #8fd8ff 40%, #3f7fd8 75%, transparent);
  box-shadow: 0 0 18px rgba(120, 200, 255, .85), 0 0 34px rgba(90, 150, 255, .5);
}

/* A thrown star keeps its own artwork, spinning as it goes. */
.fx-shot--star { width: 34px; height: 34px; }
.fx-shot--star img {
  width: 100%; height: 100%;
  display: block;
  animation: shot-spin .28s linear infinite;
  filter: drop-shadow(0 0 6px rgba(0, 0, 0, .6));
}

@keyframes shot-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .fx-shot--star img { animation: none; }
}


/* ===================== NEW BOSS POWERS ================================ */

/* Poison Touch: a sickly wash over the envenomed fighter. */
.brawler.is-poisoned .brawler__sprite { animation: venom .62s ease-out; }
@keyframes venom {
  0%, 100% { filter: none; }
  40% { filter: hue-rotate(72deg) saturate(1.7) brightness(.86); }
}
.fx-num--poison { color: #9ede6a; text-shadow: 0 0 12px rgba(120, 220, 90, .7); }

/* Phoenix Heart: back on their feet in a column of light. */
.brawler.is-reborn .brawler__sprite { animation: reborn 1.2s cubic-bezier(.2, .85, .25, 1); }
@keyframes reborn {
  0% { transform: translateY(16px) scale(.86); filter: brightness(3) saturate(0); opacity: .25; }
  30% { filter: brightness(2.6) saturate(1.4) drop-shadow(0 0 22px rgba(255, 170, 80, .95)); opacity: 1; }
  55% { transform: translateY(-8px) scale(1.06); }
  100% { transform: translateY(0) scale(1); filter: none; }
}

/* A-Rise: two fighters stand in behind their champion. */
.allies {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
}

.allies__one {
  position: absolute;
  bottom: 0;
  width: 74%;
  height: 74%;
  opacity: 0;
  /* Drained of colour, like the shadow clone: what A-Rise calls up is not two
   * more Brawlers, it is the memory of them. Grey and nothing else -- the
   * dimming, the contrast lift and the blue rim all came off, so a summon is
   * told apart by having no colour at all rather than by a glow. Dropping the
   * drop-shadow also takes a live blur off a rig that animates every frame. */
  filter: grayscale(1);
  animation: ally-in .5s cubic-bezier(.2, .9, .3, 1) forwards;
}

/* Both fall in behind their champion rather than crowding them: further back
 * and a little higher reads as depth, and keeps the fighter's silhouette
 * clear of them. */
.allies__one--0 { left: -62%; bottom: 2%; animation-delay: .04s; }
.allies__one--1 { left: -34%; bottom: 11%; width: 64%; height: 64%; animation-delay: .16s; }
.allies--enemy .allies__one--0 { left: auto; right: -62%; }
.allies--enemy .allies__one--1 { left: auto; right: -34%; }

@keyframes ally-in {
  from { opacity: 0; transform: translateY(26px) scale(.8); }
  to { opacity: .92; transform: translateY(0) scale(1); }
}

.allies__one.is-striking { animation: ally-strike .42s cubic-bezier(.2, .9, .3, 1); }
@keyframes ally-strike {
  0% { transform: translateX(0); }
  35% { transform: translateX(calc(30px * var(--dir, 1))); }
  100% { transform: translateX(0); }
}


/* ========================= MY BRAWLER / FRIEND ========================= */

/* The offer to keep a fallen Brawler, on the record screen. */
.keep {
  margin: 20px auto 4px;
  padding: 16px 18px;
  max-width: 460px;
  border-radius: var(--radius);
  border: 1px solid rgba(214, 173, 92, .4);
  background: rgba(214, 173, 92, .07);
}

.keep__text { margin: 0 0 12px; font-size: .88rem; color: var(--ink-dim); }

.btn--gold {
  border-color: rgba(214, 173, 92, .7);
  background: linear-gradient(180deg, rgba(214, 173, 92, .26), rgba(214, 173, 92, .13));
  color: var(--gold);
}
.btn--gold:hover:not(:disabled) { background: rgba(214, 173, 92, .3); }
.btn--gold:disabled { opacity: .55; }

/* The kept Brawler, shown before a bout. */
/* The roster: up to three kept Brawlers, stacked. */
.friend-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: stretch;
  text-align: left;
  margin: 20px auto;
  max-width: 520px;
}

/* One Brawler in the roster. Picking one is a real choice, so the chosen row
 * is marked rather than merely hovered. */
.friend-pick {
  display: flex;
  gap: 18px;
  align-items: center;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  padding: 12px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .02);
  transition: border-color .16s ease, background .16s ease;
}

.friend-pick:hover { border-color: var(--gold-dim); }
.friend-pick--on {
  border-color: var(--gold);
  background: rgba(217, 164, 65, .10);
  box-shadow: 0 0 0 1px rgba(217, 164, 65, .3) inset;
}
.friend-pick:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Which bout this Brawler fights, in a 3v3. */
.friend-pick__seat {
  position: absolute;
  top: 8px;
  right: 10px;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .08em;
  color: var(--gold);
  border: 1px solid rgba(217, 164, 65, .55);
  background: rgba(217, 164, 65, .14);
  border-radius: 999px;
  padding: 2px 9px;
}
.friend-pick { position: relative; }

.friend-mode { margin: 4px auto 10px; max-width: 520px; text-align: center; }
.friend-mode .btn.is-on {
  border-color: var(--gold);
  color: var(--gold);
  background: rgba(217, 164, 65, .12);
}
.friend-mode .btn[disabled] { opacity: .45; cursor: not-allowed; }

.friend-card__avatar { width: 86px; height: 118px; flex: none; }
.friend-card__name { font-family: var(--font); font-size: 1.1rem; }
.friend-card__meta { margin: 2px 0 0; font-size: .8rem; color: var(--ink-dim); }
.friend-card__empty { margin: 0; font-size: .88rem; color: var(--ink-dim); font-style: italic; }
/* The online tally sits with the stats and reads like them, one line down. */
.friend-card__record { margin-top: 6px; color: var(--gold); font-weight: 700; }
/* Chips inside a roster row sit tighter than they do on the briefing screen. */
.friend-pick .chips, .destiny-card .chips, .hall__row .chips { margin-top: 6px; gap: 4px; }
.friend-pick .chip, .destiny-card .chip, .hall__row .chip { font-size: .66rem; padding: 2px 7px; }

.friend-room { margin: 18px auto 6px; max-width: 400px; }
.friend-room__label {
  margin: 0;
  font-size: .72rem;
  letter-spacing: .18em;
  color: var(--ink-faint);
}

.friend-room__code {
  margin: 4px 0 10px;
  font-family: var(--font-mono, monospace);
  font-size: 2.6rem;
  letter-spacing: .34em;
  color: var(--gold);
  text-shadow: 0 0 22px rgba(214, 173, 92, .35);
}

.friend-room__status { margin: 0 0 14px; font-size: .86rem; color: var(--ink-dim); min-height: 1.3em; }

.field__input--code {
  text-align: center;
  font-family: var(--font-mono, monospace);
  font-size: 1.8rem;
  letter-spacing: .34em;
}

@media (max-width: 520px) {
  .friend-card { flex-direction: column; text-align: center; }
  .friend-room__code { font-size: 2rem; }
}


/* ========================== WEAPON TIERS ==============================
 * A lit outline always means the weapon was won in the tower rather than
 * forged with the Brawler, so Normal carries no glow at all.
 * ---------------------------------------------------------------------- */
/* The glow itself is an SVG <filter> emitted into each rig's <defs> by
 * assets.js -- see tierGlowDef(). It cannot live here: WebKit does not
 * reliably apply a CSS `filter` to an element inside an SVG, so on iOS the
 * lit edge never painted at all. The .wpn--* classes remain as hooks only. */

/* Godlike: the only prize that does not come off the tower's ladder. */
.outcome__note--godlike {
  color: #ff6b5e;
  text-shadow: 0 0 10px rgba(255, 59, 48, .55);
}

.badge--shadow {
  color: #cfd4dc;
  border-color: rgba(207, 212, 220, .5);
  background: rgba(207, 212, 220, .12);
}

/* The stat a weapon lends, shown beside the Brawler's own. */
.stats__lent {
  margin-left: 3px;
  font-size: .62em;
  font-weight: 700;
  color: var(--gold);
  vertical-align: super;
}

/* Tier colours on the rider chips. */
.weapon__bonus--rare,   .rider--rare   { color: #8cc6ff; border-color: rgba(90, 169, 255, .5); background: rgba(90, 169, 255, .12); }
.weapon__bonus--epic,   .rider--epic   { color: #cfa6ff; border-color: rgba(180, 119, 255, .5); background: rgba(180, 119, 255, .13); }
.weapon__bonus--legendary, .rider--legendary {
  color: #ffdc95;
  border-color: rgba(255, 200, 97, .65);
  background: rgba(255, 200, 97, .16);
}
.weapon__bonus--godlike, .rider--godlike {
  color: #ff8a7e;
  border-color: rgba(255, 59, 48, .6);
  background: rgba(255, 59, 48, .15);
}

/* ?ids=1 only: the part files the drawn Brawler is made of. */
.part-ids {
  white-space: pre-line;
  font-family: var(--font-mono);
  font-size: .72rem;
  line-height: 1.6;
  color: var(--gold-dim);
  margin-top: 8px;
}

/* ============================================================
   Pre-battle math gate: a quick addition/subtraction check
   before every "Fight & Ascend" -- keeps the tower's core loop
   tied to a small math rep instead of pure auto-battling.
   ============================================================ */
.ag-mathgate-backdrop {
  position: fixed; inset: 0; z-index: 9999; display: none;
  align-items: center; justify-content: center; padding: 20px;
  background: rgba(8, 6, 5, .68);
  backdrop-filter: blur(4px);
}
.ag-mathgate-backdrop.is-open { display: flex; }
.ag-mathgate-card {
  width: 100%; max-width: 360px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 24px 22px 20px;
  text-align: center;
  transform: scale(.94);
  transition: transform .16s ease;
}
.ag-mathgate-backdrop.is-open .ag-mathgate-card { transform: scale(1); }
.ag-mathgate-card.is-shake { animation: ag-mathgate-shake .32s ease; }
@keyframes ag-mathgate-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(7px); }
  60% { transform: translateX(-5px); }
  80% { transform: translateX(4px); }
}
.ag-mathgate-lock { font-size: 30px; line-height: 1; margin-bottom: 6px; }
.ag-mathgate-title {
  font-family: var(--font); font-weight: 600; font-size: 19px;
  color: var(--ink); margin: 0 0 4px;
}
.ag-mathgate-sub { font-size: 13px; color: var(--ink-dim); margin: 0 0 18px; }
.ag-mathgate-sum {
  font-family: var(--font); font-weight: 600; font-size: 30px;
  color: var(--ink); margin-bottom: 16px;
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.ag-mathgate-input {
  width: 88px; text-align: center;
  font-family: var(--font); font-weight: 600; font-size: 24px;
  color: var(--ink);
  background: var(--panel-2); border: 2px solid var(--line);
  border-radius: var(--radius-sm); padding: 6px 4px;
}
.ag-mathgate-input::-webkit-outer-spin-button,
.ag-mathgate-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ag-mathgate-input:focus { outline: none; border-color: var(--gold); }
.ag-mathgate-msg { min-height: 18px; font-size: 12.5px; font-weight: 700; color: var(--blood-bright); margin-bottom: 10px; }
.ag-mathgate-actions { display: flex; gap: 10px; justify-content: center; }
