/* ============================================================
   NEW ERIDU ARCHIVE — official game art layer
   ============================================================
   Loaded AFTER zzz.css and pages.css. This file owns every
   surface that is built on a real in-game asset:

     · agent portraits        assets/img/agent/*.webp
     · agent splash art       assets/img/splash/*.webp
     · agent medallions       assets/img/icon/*.webp
     · element emblems        assets/img/element/*.webp
     · specialty emblems      assets/img/spec/*.webp
     · attack-type emblems    assets/img/atk/*.webp
     · faction crests         assets/img/faction/*.webp
     · drive-disc covers      assets/img/disc/*.webp
     · W-Engine renders       assets/img/wengine/*.webp
     · Bangboo renders        assets/img/bangboo/*.webp

   It deliberately re-declares the placeholder dossier-card rules
   it replaces, so nothing from the earlier pass leaks through.
   ============================================================ */

/* ------------------------------------------------------------
   0. ART PRIMITIVES
   Inline emblems inherit their box from the text they sit in, so
   a single `.ic` size rule keeps chips, tables and prose aligned.
   ------------------------------------------------------------ */
.ic {
  display: inline-block;
  vertical-align: -0.14em;
  object-fit: contain;
  flex: none;
}
.ic-el  { width: 1.05em; height: 1.05em; }
.ic-sp  { width: 0.95em; height: 0.95em; }
.ic-atk { width: 0.95em; height: 0.95em; }
.ic-fac { width: 1.2em;  height: 1.2em; }
.ic-sm  { width: 14px; height: 14px; }
.ic-md  { width: 18px; height: 18px; }
.ic-lg  { width: 26px; height: 26px; }
.ic-xl  { width: 40px; height: 40px; }

/* Emblems carry their own colour, so they need a touch of
   contrast separation on the ink surfaces. */
.ic-el, .ic-sp, .ic-atk {
  filter: drop-shadow(0 0 6px rgba(0, 0, 0, .55));
}
.ic-fac { filter: drop-shadow(0 0 8px rgba(0, 0, 0, .6)); }

/* Missing-art fallback: a cut-corner monogram plate. Used by the
   handful of sets whose icons are not published yet — it reads as
   a deliberate "classified" stamp rather than a broken image. */
.a-fallback {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background:
    repeating-linear-gradient(135deg,
      rgba(255, 255, 255, .035) 0 6px, transparent 6px 12px),
    var(--ink-850);
  border: 1.5px solid var(--ink-600);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%);
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: .02em;
  color: var(--ac, var(--fluoro));
}
.a-fallback::after {
  content: 'ICON PENDING';
  position: absolute;
  left: 0; right: 0; bottom: 6px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .16em;
  color: var(--mute-400);
}

/* ------------------------------------------------------------
   1. THE AGENT CARD
   A portrait cut-out floated on an attribute-coloured stage, with
   the element emblem ghosted behind the head — the same read as
   the in-game agent select grid.
   ------------------------------------------------------------ */
.agent { display: block; text-decoration: none; }

.agent-frame {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--ink-900);
  border: 1.5px solid var(--ink-600);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%);
  transition: border-color .3s ease, transform .45s var(--ease-out);
  isolation: isolate;
}

/* attribute wash — sits under the art, keeps the card legible
   even while the portrait file is still loading */
.agent-frame::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(78% 62% at 50% 104%, var(--ac), transparent 68%),
    linear-gradient(180deg, var(--ink-850) 0%, var(--ink-900) 62%, #000 100%);
  opacity: .38;
  transition: opacity .4s ease;
}
/* diagonal stripe texture — the print-finish signature */
.agent-frame::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: repeating-linear-gradient(135deg,
    rgba(255, 255, 255, .028) 0 1px, transparent 1px 7px);
  mix-blend-mode: screen;
}

/* the ghosted element emblem behind the head */
.a-elbg {
  position: absolute;
  z-index: 1;
  top: 6%;
  right: -8%;
  width: 74%;
  height: 74%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  opacity: .17;
  transform: rotate(-8deg);
  pointer-events: none;
  transition: opacity .4s ease, transform .6s var(--ease-out);
}

/* the portrait itself */
.agent-art {
  position: absolute;
  z-index: 2;
  inset: 0;
  background-repeat: no-repeat;
  background-position: 50% 4%;
  background-size: 116% auto;
  transform: scale(1.02);
  transform-origin: 50% 100%;
  transition: transform .6s var(--ease-out), filter .4s ease;
  pointer-events: none;
}
/* an <img> variant so the browser can lazy-load 60 portraits */
img.agent-art {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
  padding: 2% 5% 0;
}

.a-halftone {
  position: absolute;
  z-index: 3;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(rgba(0, 0, 0, .5) 1px, transparent 1.4px);
  background-size: 4px 4px;
  opacity: .28;
  mix-blend-mode: multiply;
  mask-image: linear-gradient(180deg, transparent 46%, #000 86%);
  -webkit-mask-image: linear-gradient(180deg, transparent 46%, #000 86%);
}

.a-shine {
  position: absolute;
  z-index: 5;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(115deg,
    transparent 38%, rgba(255, 255, 255, .17) 47%, transparent 56%);
  transform: translateX(-120%);
  transition: transform .78s var(--ease-out);
}

.a-scan {
  position: absolute;
  z-index: 5;
  left: 0; right: 0; top: 0;
  height: 26%;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(232, 255, 35, .1), transparent);
  opacity: 0;
}

/* --- HUD: rarity row + tier chip --- */
.a-top {
  position: absolute;
  z-index: 6;
  top: 10px; left: 10px; right: 10px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}
.a-rare {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 7px 3px 5px;
  background: rgba(8, 8, 10, .74);
  border: 1px solid var(--ink-600);
  backdrop-filter: blur(3px);
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .16em;
  color: var(--gold, #FFC93C);
}
.a-rare b {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0;
  line-height: 1;
}
.a-rare.is-a { color: #A78BFA; }

.agent-tier {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  background: var(--tc, var(--fluoro));
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: 10px;
  line-height: 1.25;
  letter-spacing: .02em;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 5px 100%, 0 calc(100% - 5px));
}

/* --- name plate --- */
.a-plate {
  position: absolute;
  z-index: 6;
  left: 0; right: 0; bottom: 0;
  padding: 26px 12px 12px;
  background: linear-gradient(180deg, transparent, rgba(8, 8, 10, .82) 42%, rgba(8, 8, 10, .96));
}
.a-plate-top {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 7px;
}
.a-plate-top .a-el {
  width: 20px;
  height: 20px;
  margin-left: auto;
  opacity: .95;
}
.a-meta {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--mute-300);
  white-space: nowrap;
  overflow: hidden;
}
.a-meta .ic { opacity: .9; }
.a-meta .sep { color: var(--mute-400); }

.agent-name {
  font-family: var(--font-display);
  font-size: 13.5px;
  line-height: 1.04;
  letter-spacing: -.01em;
  text-transform: uppercase;
  color: var(--paper);
  overflow-wrap: anywhere;
}
.a-idx {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .12em;
  color: var(--mute-400);
}

/* legacy inner pieces from the placeholder pass: neutralised */
.agent-mono, .agent-slash, .agent-grid-lines, .agent-flag,
.agent-halftone, .agent-scan, .agent-sheen, .agent-bot, .agent-sub {
  display: none !important;
}
.agent-art.placeholder { display: none; }

/* --- hover / focus --- */
.agent:hover .agent-frame,
.agent:focus-visible .agent-frame {
  border-color: var(--ac);
  transform: translate(-3px, -3px);
}
.agent:hover .agent-frame::before { opacity: .62; }
.agent:hover .agent-art { transform: scale(1.075); }
.agent:hover .a-elbg { opacity: .3; transform: rotate(-2deg) scale(1.06); }
.agent:hover .a-shine { transform: translateX(120%); }
.agent:hover .a-scan { animation: a-scan 1.05s var(--ease-out); }
@keyframes a-scan {
  0%   { opacity: 0; transform: translateY(-30%); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(320%); }
}

/* upcoming agents get a visibly different frame */
.agent.is-upcoming .agent-frame { border-style: dashed; }
.agent.is-upcoming .agent-art { filter: saturate(.82); }

/* smaller density used by the tier board and related strips */
.g-6 .agent-name { font-size: 12px; }
.g-6 .a-meta { font-size: 7.5px; letter-spacing: .08em; }
.tier-band-c .agent-name { font-size: 12px; }
.tier-band-c .a-meta { display: none; }

/* ------------------------------------------------------------
   2. ROSTER ROW (related-agent strips, team cross-links)
   ------------------------------------------------------------ */
.agent-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 12px;
  border: 1px solid var(--ink-700);
  background: var(--ink-850);
  transition: border-color .24s, transform .28s var(--ease-out), background .24s;
}
.agent-row:hover { border-color: var(--ac); transform: translateX(4px); background: var(--ink-800); }
.agent-row-badge {
  position: relative;
  width: 42px;
  height: 42px;
  flex: none;
  background:
    radial-gradient(60% 60% at 50% 108%, var(--ac), transparent 72%),
    var(--ink-900);
  border: 1px solid var(--ink-600);
  overflow: hidden;
}
.agent-row-badge img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
  transform: scale(1.22);
  transform-origin: 50% 100%;
}
.agent-row-n {
  font-family: var(--font-display);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: -.01em;
}
.agent-row-m {
  margin-top: 3px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--mute-400);
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ------------------------------------------------------------
   3. ICON CHIPS
   Every attribute / specialty / attack-type / faction reference
   in the site becomes emblem + label.
   ------------------------------------------------------------ */
.chip-attr, .chip-spec, .chip-atk, .chip-fac {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.chip-attr { border-color: var(--ac); color: var(--ac); background: rgba(255, 255, 255, .02); }
.chip-atk  { color: var(--tc, var(--paper-dim)); }

/* a stack of all four, used on the detail hero */
.a-ids {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}

/* ------------------------------------------------------------
   4. AGENT DETAIL — the splash-art hero
   ------------------------------------------------------------ */
.ahero {
  position: relative;
  overflow: hidden;
  border-bottom: var(--border);
  min-height: 460px;
  background: var(--ink-900);
}
.ahero::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(64% 84% at 26% 42%, var(--ac), transparent 66%),
    linear-gradient(180deg, var(--ink-850), var(--ink-900) 70%, #050506);
  opacity: .34;
}
.ahero-grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, .035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .035) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: linear-gradient(180deg, #000 20%, transparent 88%);
  -webkit-mask-image: linear-gradient(180deg, #000 20%, transparent 88%);
}
.ahero-stripes {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(135deg,
    rgba(255, 255, 255, .022) 0 1px, transparent 1px 9px);
}
.ahero-emblem {
  position: absolute;
  right: 4%;
  top: 50%;
  width: 420px;
  height: 420px;
  margin-top: -230px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  opacity: .085;
  transform: rotate(-10deg);
}
.ahero-art {
  position: absolute;
  right: 0;
  bottom: 0;
  top: 0;
  width: 54%;
  pointer-events: none;
}
.ahero-art img {
  position: absolute;
  right: 38%;
  bottom: 0;
  height: 100%;
  width: auto;
  max-width: none;
  object-fit: contain;
  object-position: right bottom;
  filter: drop-shadow(0 24px 50px rgba(0, 0, 0, .62));
}
.ahero-art::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--ink-900) 0%, transparent 34%),
              linear-gradient(180deg, transparent 62%, var(--ink-900) 100%);
}
.ahero-in {
  position: relative;
  z-index: 3;
  padding: 42px 28px 54px;
}
.ahero-kicker {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ac);
}
.ahero-name {
  font-family: var(--font-display);
  font-size: clamp(38px, 6vw, 74px);
  line-height: .92;
  letter-spacing: -.03em;
  text-transform: uppercase;
  margin: 12px 0 0;
}
.ahero-real {
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mute-300);
}
.ahero-stars { margin-top: 14px; display: flex; align-items: center; gap: 14px; }
.ahero-lede {
  max-width: 560px;
  margin-top: 18px;
  font-size: 14.5px;
  line-height: 1.74;
  color: var(--paper-dim);
}
.ahero-acts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 24px; }

/* stat rail under the hero */
.arail {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0;
  border: 1px solid var(--ink-700);
  border-top: none;
  background: var(--ink-850);
}
.arail-cell {
  padding: 15px 17px;
  border-right: 1px solid var(--ink-700);
}
.arail-cell:last-child { border-right: none; }
.arail-k {
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--mute-400);
}
.arail-v {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 7px;
  font-family: var(--font-condensed);
  font-size: 20px;
  letter-spacing: .02em;
  line-height: 1;
  color: var(--paper);
}
.arail-v .ic { width: 22px; height: 22px; }

/* ------------------------------------------------------------
   5. DISC / ENGINE / BANGBOO TILES
   Real cover art, uniform frames, hover lift.
   ------------------------------------------------------------ */
.dtile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 11px;
  background: var(--ink-850);
  border: 1.5px solid var(--ink-700);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%);
  transition: border-color .26s, transform .34s var(--ease-out), background .26s;
}
.dtile:hover {
  border-color: var(--ac, var(--fluoro));
  background: var(--ink-800);
  transform: translate(-3px, -3px);
}
.dtile-art {
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(72% 72% at 50% 96%, var(--ac, var(--fluoro)), transparent 74%),
    var(--ink-900);
  overflow: hidden;
}
.dtile-art::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(135deg,
    rgba(255, 255, 255, .03) 0 1px, transparent 1px 7px);
}
.dtile-art > img,
.dtile-art > .a-fallback,
.dtile-art > .tile-glyph {
  position: relative;
  z-index: 1;
  width: 78%;
  height: 78%;
  object-fit: contain;
  transition: transform .42s var(--ease-out);
}
.dtile:hover .dtile-art > img { transform: scale(1.07); }

/* the emblem motif used when a set's own cover art is not published yet */
.tile-glyph {
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  opacity: .92;
  filter: drop-shadow(0 10px 26px rgba(0, 0, 0, .6));
}
.tile-glyph-tag {
  position: absolute;
  left: 50%;
  bottom: -2px;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .16em;
  white-space: nowrap;
  color: var(--mute-400);
}

.dtile-head {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}
.dtile-n {
  font-family: var(--font-display);
  font-size: 14px;
  line-height: 1.12;
  text-transform: uppercase;
  letter-spacing: -.01em;
  overflow-wrap: anywhere;
}
.dtile-k {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--mute-400);
}
.dtile-tag {
  margin-left: auto;
  flex: none;
  padding: 3px 7px;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .12em;
  text-transform: uppercase;
  border: 1px solid var(--ac, var(--orange));
  color: var(--ac, var(--orange));
}
.dtile-body {
  font-size: 12.5px;
  line-height: 1.64;
  color: var(--mute-300);
}
.dtile-eff {
  display: grid;
  gap: 8px;
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid var(--ink-700);
}
.dtile-eff-row {
  display: grid;
  grid-template-columns: 30px 1fr;
  gap: 9px;
  align-items: baseline;
  font-size: 12px;
  line-height: 1.58;
  color: var(--paper-dim);
}
.dtile-eff-row b {
  font-family: var(--font-display);
  font-size: 10px;
  color: var(--ac, var(--fluoro));
}
.dtile-eff-row.is-4 b { color: var(--fluoro); }

/* Disc / W-Engine / Bangboo tiles.
 *
 * A `height: 118px` frame rule and the `.dtile.is-wide` rule below had been
 * merged into one selector list by accident, so all four of these grids
 * inherited the short band — while the matching image rule was written for
 * `.is-wide` only. The result: a 307x118 band holding a 256px square cover with
 * `object-fit: contain` drew every disc at 92px, a postage stamp with 107px of
 * empty background either side, inside a tile 331px wide.
 *
 * They cannot simply go back to the base 1:1 box either: a W-Engine render is
 * 220px and sits in a 440px column, which is a 1.6x upscale. The band is
 * therefore sized per asset class, off the softest file in each group, and the
 * image fills it by height. Every one of the three is a square render with
 * transparency, so a square cover now uses the band instead of a letterbox, and
 * nothing is ever drawn larger than it exists. */
#disc-grid .dtile-art { aspect-ratio: auto; height: 200px; }  /* 256px covers */
#eng-perm .dtile-art,
#eng-sig  .dtile-art { aspect-ratio: auto; height: 156px; }   /* 2 renders ship at 156 */
#bb-body  .dtile-art { aspect-ratio: auto; height: 156px; }   /* 6 renders ship at 178 */
#disc-grid .dtile-art > img, #disc-grid .dtile-art > .a-fallback,
#eng-perm  .dtile-art > img, #eng-perm  .dtile-art > .a-fallback,
#eng-sig   .dtile-art > img, #eng-sig   .dtile-art > .a-fallback,
#bb-body   .dtile-art > img, #bb-body   .dtile-art > .a-fallback {
  width: auto;
  height: 100%;
  max-width: 100%;
}

/* Signature-engine tiles sit two-up, so each was ~590px wide and its art box
   was a 4:3 square-on-stilts — the render inside it measured 482px across from
   a 156px file, a 3.1x upscale, the softest artwork anywhere on the site. As a
   short full-width banner the engine is drawn at its own resolution and the
   tile is 300px shorter, which is the difference between the W-Engine
   catalogue being an 11-viewport page and a 7-viewport one. */
.dtile.is-wide .dtile-art { aspect-ratio: auto; height: 118px; }
.dtile.is-wide .dtile-art > img { width: auto; height: 100%; max-width: 100%; }

/* the tile's effect block gets a labelled pair of rows */
.dtile-eff-row.is-2 b { color: var(--fluoro); }

/* tiny inline disc / engine reference used inside agent dossiers */
.mini-disc {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px;
  border: 1px solid var(--ink-700);
  background: var(--ink-850);
}
.mini-disc img, .mini-disc .a-fallback {
  width: 38px;
  height: 38px;
  flex: none;
  object-fit: contain;
}
.mini-disc .a-fallback { font-size: 11px; }
.mini-disc .a-fallback::after { display: none; }

/* ------------------------------------------------------------
   6. TEAM SLOTS
   ------------------------------------------------------------ */
.slot { position: relative; }
.slot-pic {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background:
    radial-gradient(64% 64% at 50% 104%, var(--ac), transparent 74%),
    var(--ink-900);
  border: 1px solid var(--ink-600);
}
/* The frame-filling rules below are scoped to the portrait by class on
   purpose. A bare `.slot-pic img` also matches the element emblem that sits
   in the same box, and being more specific than `.slot-el` it wins — which
   stretches a 60px emblem to 236px and paints it over the character. */
.slot-pic img.slot-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
  transform: scale(1.16);
  transform-origin: 50% 100%;
}
.slot-pic .slot-el {
  position: absolute;
  z-index: 2;
  top: 3px; right: 3px;
  width: 14px; height: 14px;
}
.slot-r {
  position: absolute;
  z-index: 2;
  left: 3px; bottom: 3px;
  padding: 1px 5px;
  background: rgba(8, 8, 10, .82);
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ac);
}
.slot-n {
  font-family: var(--font-display);
  font-size: 10.5px;
  text-transform: uppercase;
  margin-top: 6px;
  line-height: 1.15;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------
   7. HOME HERO — key art + dossier
   ------------------------------------------------------------ */
/* The right-hand column. On the stacked layout (≤980px) it is a real box
   carrying the Agent and their dossier beneath it — the two halves of the same
   idea. On the desktop grid it dissolves (display: contents, see pages.css):
   the dossier moves under the identity block and only the art remains here. */
.hero-side {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  min-width: 0;
}
/* The hero draws the WHOLE splash — head to feet. The old half-body bust crop
   was retired on request: visitors want the full figure, and the column has
   the vertical room for it now that the dossier card lives in the left column
   and the art owns the right column alone.

   The window takes an explicit height (--full-h, pages.css) rather than an
   aspect ratio, because splash canvases vary from 420 to 1736px wide against
   a fixed 1080px height. The img is sized by HEIGHT alone (height:100%,
   width:auto), anchored to the window floor and centred:

   - narrow canvases (58 of 62) fit inside the width exactly as contain would;
   - the three wide ones (remielle 1736, pyrois 1145, zhao 1029) used to be
     squeezed by object-fit:contain down to 0.4x and left sitting at the
     bottom under a huge void — the one thing visitors read as "misplaced".
     Bleeding them off both edges instead keeps the figure at the cast-wide
     scale, feet on the floor, wings running past the frame;
   - --z (set per Agent from app.js's ART_ZOOM) reframes a canvas whose FIGURE
     starts unusually low — Claret's head sits 16.7% down (the cast norm is
     3–8%), so at 1x she read as sunk in her own frame. 1.14 trims the thin
     scythe shaft above her head and lands it at the 5% the others hold. */
.hero-art {
  position: relative;
  width: 100%;
  height: var(--full-h);
  max-height: var(--full-h);
  /* overflow:hidden clipped Remielle's left wing at the container edge
     (her source canvas is wing-edge-to-wing-edge). clip-path keeps the
     right/bottom crop identical but lets the left wing breathe into the
     empty gap between the headline and the art column. */
  overflow: visible;
  clip-path: inset(0 0 0 -45%);
  pointer-events: none;
}
.hero-art img {
  position: absolute;
  /* --x (per-Agent, from app.js's ART_SHIFT) nudges the whole figure without
     touching the transform: the idle-motion keyframes own that property, so a
     horizontal offset must ride on `left` instead or the animation would
     overwrite it every frame. Set on the .hero-art container and inherited. */
  left: calc(50% + var(--x, 0px));
  bottom: 0;
  height: calc(100% * var(--z, 1));
  width: auto;
  max-width: none;
  transform: translateX(-50%);
  filter: drop-shadow(0 30px 60px rgba(0, 0, 0, .7));
  transition: opacity .5s ease, transform .7s var(--ease-out);
}

/* The attribute emblem behind the figure is a 128px file. At 340px it was
   being drawn at 2.7x its own resolution, so the most prominent graphic on the
   home page was also the softest thing on it. 150px keeps a margin over the
   source and the watermark is a texture, not a picture — nothing is lost. */
.hero-art-emblem {
  position: absolute;
  left: 50%;
  top: 30%;
  width: 150px;
  height: 150px;
  margin: -75px 0 0 -75px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  opacity: .1;
  transform: rotate(-12deg);
  transition: opacity .5s ease;
}
/* --- geometry behind the figure -------------------------------------------
   A bare splash on a near-black panel reads as a floating sticker. This set
   gives the window some composed depth: five printed shapes in the site's own
   print language (thin strokes, one halftone), fluoro as the base colour and
   the Agent's attribute colour for the one filled triangle, so the dressing
   shifts temperament with the character. All shapes sit UNDER the figure and
   bleed off the window edges on purpose — overflow: hidden crops them, which
   is what keeps them a backdrop rather than a second subject.

   app.js hashes the Agent's slug to one of four [data-v] arrangements, so the
   composition changes as the rail rotates. */
.hero-art-geo {
  position: absolute;
  inset: 0;
  pointer-events: none;
  transition: opacity .5s ease;
}
.hero-art-geo i { position: absolute; display: block; }
/* the big ring — the anchor shape, upper register */
.hero-art-geo .g-ring {
  width: 46%;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 2px solid rgba(232, 255, 51, .26);
}
/* dashed inner ring rides on the big one, slowly turning */
.hero-art-geo .g-ring::after {
  content: '';
  position: absolute;
  inset: 9%;
  border-radius: 50%;
  border: 1px dashed rgba(232, 255, 51, .30);
  animation: geoSpin 46s linear infinite;
}
@keyframes geoSpin { to { transform: rotate(360deg); } }
/* the one filled shape — attribute colour, very low, reads as a glow wedge */
.hero-art-geo .g-tri {
  width: 30%;
  aspect-ratio: 1.08;
  background: var(--ac, rgba(232, 255, 51, .8));
  opacity: .13;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
/* halftone patch — the site's print texture, dotted */
.hero-art-geo .g-grid {
  width: 27%;
  height: 24%;
  background-image: radial-gradient(rgba(232, 255, 51, .38) 1.5px, transparent 1.6px);
  background-size: 13px 13px;
}
/* small cross / registration mark */
.hero-art-geo .g-cross { width: 22px; height: 22px; }
.hero-art-geo .g-cross::before,
.hero-art-geo .g-cross::after {
  content: '';
  position: absolute;
  background: rgba(232, 255, 51, .42);
}
.hero-art-geo .g-cross::before { left: 50%; top: 0; width: 2px; height: 100%; margin-left: -1px; }
.hero-art-geo .g-cross::after { top: 50%; left: 0; height: 2px; width: 100%; margin-top: -1px; }
/* one thin vertical rule */
.hero-art-geo .g-bar { width: 2px; height: 30%; background: rgba(232, 255, 51, .30); }

/* four arrangements. v0 is the base; the rest move every shape so the
   composition re-reads as a different frame, not the same one slid about. */
.hero-art-geo[data-v='0'] .g-ring { right: 4%; top: 7%; }
.hero-art-geo[data-v='0'] .g-tri  { left: 1%; bottom: 5%; }
.hero-art-geo[data-v='0'] .g-grid { left: 9%; top: 7%; }
.hero-art-geo[data-v='0'] .g-cross { right: 11%; bottom: 15%; }
.hero-art-geo[data-v='0'] .g-bar  { right: 17%; top: 0; }

.hero-art-geo[data-v='1'] .g-ring { left: 5%; top: 5%; width: 40%; }
.hero-art-geo[data-v='1'] .g-tri  { right: 2%; bottom: 7%; transform: rotate(12deg); }
.hero-art-geo[data-v='1'] .g-grid { right: 7%; top: 9%; }
.hero-art-geo[data-v='1'] .g-cross { left: 9%; bottom: 13%; }
.hero-art-geo[data-v='1'] .g-bar  { left: 15%; top: 5%; }

.hero-art-geo[data-v='2'] .g-ring { right: 9%; bottom: 12%; width: 52%; }
.hero-art-geo[data-v='2'] .g-tri  { left: 4%; top: 9%; transform: rotate(180deg); }
.hero-art-geo[data-v='2'] .g-grid { right: 6%; top: 6%; }
.hero-art-geo[data-v='2'] .g-cross { left: 8%; bottom: 9%; }
.hero-art-geo[data-v='2'] .g-bar  { left: 24%; bottom: 0; }

.hero-art-geo[data-v='3'] .g-ring { left: 12%; bottom: 7%; width: 44%; }
.hero-art-geo[data-v='3'] .g-tri  { right: 5%; top: 7%; transform: rotate(-14deg); }
.hero-art-geo[data-v='3'] .g-grid { left: 7%; bottom: 11%; }
.hero-art-geo[data-v='3'] .g-cross { right: 8%; top: 11%; }
.hero-art-geo[data-v='3'] .g-bar  { right: 9%; top: 9%; }

.hero-art-geo[data-v='4'] .g-ring { right: 14%; top: 4%; width: 38%; }
.hero-art-geo[data-v='4'] .g-tri  { left: 3%; bottom: 12%; transform: rotate(-8deg); }
.hero-art-geo[data-v='4'] .g-grid { right: 5%; bottom: 6%; }
.hero-art-geo[data-v='4'] .g-cross { left: 7%; top: 10%; }
.hero-art-geo[data-v='4'] .g-bar  { left: 4%; top: 0; }

/* --- idle motion: six per-Agent "alive" cues -------------------------------
   A splash art is ONE bitmap, so true re-posing/expression changes would need
   AI re-drawn frames — which flicker between poses and cost megabytes. The
   cheap, lossless equivalent: each featured Agent gets her own micro-motion
   loop shaped by the pose she already holds. All cues are transform-only
   (compositor thread, zero layout/paint), sub-pixel-to-3px in amplitude and
   3.8–6.4s long, so they read as breathing / hovering / swaying — motion the
   eye feels rather than watches. Nothing is downloaded: the "animation" is
   ~60 lines of CSS on the art that is already there.

   Rules of thumb baked into the keyframes:
   - transform-origin 50% 100% — the feet are the weight line;
   - translateX(-50%) is re-declared in every keyframe: it IS the horizontal
     centring of the img, so every keyframe must carry it;
   - Remielle (1736px wing-to-wing) gets NO rotation: half a degree would sweep
     her wingtips ~8px. She floats instead. */
.hero-art img {
  will-change: transform;
  transform-origin: 50% 100%;
}
/* Claret — the scythe rests on her shoulder; she leans into it, slightly */
.hero-art img[data-motion='claret'] { animation: idleClaret 5.2s ease-in-out infinite; }
/* Remielle — angelic hover, wings held level */
.hero-art img[data-motion='remielle'] { animation: idleRemielle 6.4s ease-in-out infinite; }
/* Miyabi — blade-drawn stillness: a slow breath, nothing else */
.hero-art img[data-motion='miyabi'] { animation: idleMiyabi 4.6s ease-in-out infinite; }
/* Aria — the lively one: a quicker double-bob rhythm */
.hero-art img[data-motion='aria'] { animation: idleAria 3.8s ease-in-out infinite; }
/* Ye Shunguang — poised to move: weight shifts heel to toe */
.hero-art img[data-motion='ye-shunguang'] { animation: idleYe 5.6s ease-in-out infinite; }
/* Astra Yao — sways with a tempo only she hears */
.hero-art img[data-motion='astra-yao'] { animation: idleAstra 4.2s ease-in-out infinite; }

@keyframes idleClaret {
  0%, 100% { transform: translateX(-50%) rotate(0deg); }
  35%      { transform: translateX(calc(-50% + 1.5px)) rotate(.45deg); }
  70%      { transform: translateX(calc(-50% - 1px)) rotate(-.3deg); }
}
@keyframes idleRemielle {
  0%, 100% { transform: translateX(-50%) translateY(0) scale(1); }
  50%      { transform: translateX(-50%) translateY(-3px) scale(1.004); }
}
@keyframes idleMiyabi {
  0%, 100% { transform: translateX(-50%) translateY(0) scaleY(1); }
  50%      { transform: translateX(-50%) translateY(-2px) scaleY(1.003); }
}
@keyframes idleAria {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  28%      { transform: translateX(-50%) translateY(-2.5px); }
  52%      { transform: translateX(-50%) translateY(0); }
  76%      { transform: translateX(-50%) translateY(-1.5px); }
}
@keyframes idleYe {
  0%, 100% { transform: translateX(calc(-50% - 2px)) rotate(-.35deg); }
  50%      { transform: translateX(calc(-50% + 2px)) rotate(.2deg); }
}
@keyframes idleAstra {
  0%, 100% { transform: translateX(-50%) rotate(0deg); }
  25%      { transform: translateX(calc(-50% + 1px)) rotate(.55deg); }
  75%      { transform: translateX(calc(-50% - 1px)) rotate(-.55deg); }
}
/* The swap cut overrides the idle loop: while .is-swapping holds, the loop is
   suspended so the scripted sink-and-fade transform wins. When the class is
   removed the loop restarts from 0 — invisible at these amplitudes, under a
   500ms opacity ramp. */
.hero-art.is-swapping img { animation: none; }

.hero-art.is-swapping img,
.hero-art.is-swapping .hero-art-emblem,
.hero-art.is-swapping .hero-art-geo { opacity: 0; }
.hero-art.is-swapping img { transform: translateX(-50%) translateY(14px) scale(.985); }

/* visitors who opt out of motion get the static portrait back */
@media (prefers-reduced-motion: reduce) {
  .hero-art img { animation: none !important; }
}

/* the hero identity block owns the left column now — the grid sets its width */
.hero-inner { min-width: 0; }

/* --- featured dossier rail: the rotation control, built from real
       medallion art so the home page is a character showcase --- */
/* The card is the width of the column. It used to be capped at 500px and
   centred, which left it 70px in from the left edge of the key-art window
   above it and 71px short of the right — two stacked rectangles sharing
   neither edge, which reads as a mistake rather than a composition. */
.hero-feat {
  width: 100%;
  border: 1px solid var(--ink-700);
  background: rgba(8, 8, 10, .66);
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
}
.hero-feat-body { display: flex; align-items: center; gap: 13px; padding: 15px 14px; }
/* Two lines, not one. Name and fields were sharing a row with the button,
   which squeezed the name against three chips and left the card looking like
   a toolbar. Identity stacks here; the button moves to the far edge. */
.hero-feat-id {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.hero-feat-n {
  font-family: var(--font-display);
  font-size: clamp(18px, 1.7vw, 22px);
  line-height: 1;
  letter-spacing: -.015em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The record's fields, stated rather than boxed: a swatch in the attribute's
   colour, then attribute, specialty and tier as one tracked line. */
.hero-feat-t {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--mute-300);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hero-feat-t .hm-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 8px;
  vertical-align: middle;
  position: relative;
  top: -1px;
}
.hero-feat-t b { font-weight: 700; }
.hero-feat-go {
  margin-left: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--ink-600);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--paper);
  white-space: nowrap;
  transition: color .24s, border-color .24s;
}
.hero-feat-go .arw { transition: transform .3s var(--ease-out); }
.hero-feat-go:hover { color: var(--ac, var(--fluoro)); border-color: var(--ac, var(--fluoro)); }
.hero-feat-go:hover .arw { transform: translateX(3px); }

/* The rail used to be six `flex: 1 1 0` items at a fixed 50px *height* in a
   620px card — 95px wide against a 142px square source, so `object-fit: cover`
   scaled by width and cropped the square down to a band across the eyes. Every
   thumbnail was a pair of eyebrows. Fixed 52px squares keep the aspect the art
   was cut at, and the space they free up goes to the roster link. */
.hero-rail { display: flex; align-items: center; gap: 6px; padding: 0 14px 14px; }
.hero-rail button {
  position: relative;
  flex: none;
  width: 52px;
  height: 52px;
  padding: 0;
  border: 1px solid var(--ink-700);
  background: var(--ink-900);
  overflow: hidden;
  transition: border-color .24s, transform .28s var(--ease-out);
}
/* .55 opacity over .55 grayscale, sitting under a page-wide vignette, made the
   whole row read as one grey smear — you could not tell who any of the five
   unselected Agent were. The active tile still has to lead, but only by being
   fully lit, not by the others being unreadable. */
.hero-rail button img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: .78;
  filter: grayscale(.4);
  transition: opacity .26s, filter .26s;
}
.hero-rail button::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--ac, var(--fluoro));
  transform: scaleX(0);
  transition: transform .3s var(--ease-out);
}
.hero-rail button:hover { border-color: var(--ac); transform: translateY(-2px); }
.hero-rail button:hover img { opacity: .94; filter: none; }
.hero-rail button.on { border-color: var(--ac); }
.hero-rail button.on::after { transform: scaleX(1); }
.hero-rail button.on img { opacity: 1; filter: none; }
.hero-rail-all {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--mute-300);
  white-space: nowrap;
  transition: color .24s;
}
.hero-rail-all .arw { transition: transform .3s var(--ease-out); }
.hero-rail-all:hover { color: var(--fluoro); }
.hero-rail-all:hover .arw { transform: translateX(3px); }

/* roster wall: a compact strip of every playable agent's medallion */
.wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: 5px;
}
.wall a {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background:
    radial-gradient(60% 60% at 50% 106%, var(--ac), transparent 74%),
    var(--ink-900);
  border: 1px solid var(--ink-700);
  transition: transform .24s var(--ease-out), border-color .22s;
}
.wall a img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 96%;
  transform: scale(1.24);
  transform-origin: 50% 100%;
}
.wall a:hover { transform: translateY(-4px); border-color: var(--ac); z-index: 2; }
.wall a.is-upcoming { border-style: dashed; opacity: .72; }
/* the emblem motif, sized to sit inside a wall cell instead of a tile */
.wall a .tile-glyph {
  position: absolute;
  inset: 9%;
  width: auto;
  height: auto;
  opacity: .78;
  filter: none;
}
.wall a .tile-glyph-tag { display: none; }

/* the wall also runs as a bordered panel with a caption + counters */
.wall-panel {
  display: grid;
  grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
  gap: 24px;
  align-items: center;
  padding: 18px;
  border: 1px solid var(--ink-700);
  background: var(--ink-850);
}
.wall-panel-t {
  font-family: var(--font-display);
  font-size: 19px;
  line-height: 1.08;
  text-transform: uppercase;
  letter-spacing: -.015em;
}
.wall-panel-p {
  margin-top: 9px;
  font-size: 12px;
  line-height: 1.62;
  color: var(--mute-300);
}
/* These two labels were 8px in --ink-500 (#3A3A45 on an #0F0F12 panel) — a
   contrast ratio of about 1.4:1, which reads as a rendering fault rather than
   as small type. Sized up and moved onto the muted ramp so they are legible. */
.wall-counts { display: flex; gap: 16px; row-gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.wall-counts b {
  display: block;
  font-family: var(--font-display);
  font-size: 22px;
  line-height: 1;
  color: var(--fluoro);
}
.wall-counts span {
  display: block;
  margin-top: 5px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--mute-300);
}

@media (max-width: 640px) {
  /* The name, the fields and the link no longer share one row at 320px: the
     name would ellipsis down to nothing to keep the link at the far edge. */
  .hero-feat-body { flex-wrap: wrap; gap: 11px; }
  .hero-feat-go { margin-left: 0; }
  /* six squares plus the roster link do not fit 320px: let the link fall to
     its own line rather than shrink the portraits back into eyebrow strips */
  .hero-rail { flex-wrap: wrap; gap: 5px; padding: 0 10px 10px; }
  .hero-rail button { width: 44px; height: 44px; }
  .hero-rail-all { margin-left: 0; }
  .wall-panel { grid-template-columns: 1fr; gap: 16px; }
  .wall { grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 4px; }
}

/* ------------------------------------------------------------
   9. COMPLETE ROSTER LISTS
   Built so a 103-entry W-Engine list and a 41-entry Bangboo list
   stay scannable: dense rows, art thumb, one number worth reading.
   ------------------------------------------------------------ */
.lst-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}
.lst-tabs { display: flex; flex-wrap: wrap; gap: 1px; }
.lst-tabs button {
  padding: 7px 13px;
  border: 1px solid var(--ink-700);
  background: var(--ink-850);
  color: var(--mute-300);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .13em;
  text-transform: uppercase;
  transition: color .2s, border-color .2s, background .2s;
}
.lst-tabs button:hover { color: var(--paper); border-color: var(--mute-400); }
.lst-tabs button.on {
  background: var(--fluoro);
  border-color: var(--fluoro);
  color: var(--ink-900);
  font-weight: 700;
}
.lst-count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--mute-400);
}
.lst-count b { color: var(--fluoro); font-family: var(--font-display); font-size: 12px; }

/* --- dense art row (W-Engines) --- */
.elist { display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: 9px; }
.erow {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 11px;
  border: 1px solid var(--ink-700);
  background: var(--ink-850);
  transition: border-color .22s, transform .26s var(--ease-out), background .22s;
}
.erow:hover { border-color: var(--ac, var(--fluoro)); background: var(--ink-800); transform: translateY(-2px); }
.epic {
  position: relative;
  width: 46px;
  height: 46px;
  flex: none;
  border: 1px solid var(--ink-600);
  background:
    radial-gradient(64% 64% at 50% 100%, var(--ac, var(--fluoro)), transparent 76%),
    var(--ink-900);
  overflow: hidden;
}
.epic img {
  position: absolute;
  inset: 3px;
  width: calc(100% - 6px);
  height: calc(100% - 6px);
  object-fit: contain;
  transition: transform .34s var(--ease-out);
}
.erow:hover .epic img { transform: scale(1.09); }
.enm {
  font-family: var(--font-display);
  font-size: 12.5px;
  line-height: 1.14;
  text-transform: uppercase;
  letter-spacing: -.005em;
  overflow-wrap: anywhere;
}
.emt {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 7.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--mute-400);
}
.emt .rnk { padding: 1px 4px; border: 1px solid var(--ink-500); color: var(--mute-300); }
.emt .rnk.s { border-color: #FFC93C; color: #FFC93C; }
.estat { margin-left: auto; flex: none; text-align: right; }
.eatk { font-family: var(--font-display); font-size: 16px; line-height: 1; color: var(--fluoro); }
.esub {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--mute-400);
  max-width: 74px;
  line-height: 1.3;
}

/* --- Bangboo codex tiles --- */
.blist { display: grid; grid-template-columns: repeat(auto-fill, minmax(154px, 1fr)); gap: 11px; }
.boo {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1.5px solid var(--ink-700);
  background: var(--ink-850);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%);
  transition: border-color .24s, transform .3s var(--ease-out), background .24s;
}
.boo:hover { border-color: var(--ac, var(--fluoro)); background: var(--ink-800); transform: translate(-2px, -2px); }
.boo-pic {
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(70% 70% at 50% 92%, var(--ac, var(--fluoro)), transparent 76%),
    var(--ink-900);
  overflow: hidden;
}
.boo-pic::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(135deg,
    rgba(255, 255, 255, .028) 0 1px, transparent 1px 7px);
}
/* Bangboo renders ship at 200px, and six of the forty-one at 178. On a phone
   the tier band collapses to one wide column and hands this box 267px, so 84%
   of the box drew a 200px file at 224px and the browser invented the
   difference. The cap is the resolution of the softest asset in the group, so
   the width of the column can never enlarge the picture. */
.boo-pic img {
  position: relative;
  z-index: 1;
  width: min(84%, 178px);
  height: min(84%, 178px);
  object-fit: contain;
  transition: transform .4s var(--ease-out);
}
.boo:hover .boo-pic img { transform: scale(1.07); }
.boo-rk {
  position: absolute;
  z-index: 2;
  top: 7px;
  left: 7px;
  padding: 2px 7px;
  background: rgba(8, 8, 10, .8);
  border: 1px solid var(--ink-600);
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .14em;
  color: var(--mute-300);
}
.boo-rk.s { color: #FFC93C; border-color: rgba(255, 201, 60, .5); }
.boo-in { padding: 10px 11px 13px; }
.boo-n {
  font-family: var(--font-display);
  font-size: 13px;
  line-height: 1.12;
  text-transform: uppercase;
  letter-spacing: -.005em;
  overflow-wrap: anywhere;
}
.boo-c {
  margin-top: 3px;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--mute-400);
}
.boo-d {
  margin-top: 8px;
  font-size: 11px;
  line-height: 1.52;
  color: var(--mute-400);
}

/* ------------------------------------------------------------
   10. MISC ART UPGRADES
   ------------------------------------------------------------ */
/* filter chips: emblem + label, so an attribute reads as an icon too */
.fopt { display: inline-flex; align-items: center; gap: 6px; }
.fopt img { opacity: .85; }
.fopt.on img { opacity: 1; }

/* the disc / engine pick inside an Agent dossier: real cover art, and the
   circular decorative wash from the placeholder pass is retired */
.disc-card {
  display: flex;
  align-items: stretch;
  gap: 15px;
  padding: 15px;
}
.disc-card::before { display: none; }
.disc-card-art {
  position: relative;
  width: 92px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(70% 70% at 50% 96%, var(--fluoro), transparent 76%),
    var(--ink-900);
  border: 1px solid var(--ink-600);
  overflow: hidden;
}
.disc-card-art img,
.disc-card-art .a-fallback,
.disc-card-art .tile-glyph { width: 84%; height: 84%; object-fit: contain; position: relative; z-index: 1; }
.disc-card-art .a-fallback { font-size: 15px; }
.disc-card-art .a-fallback::after { display: none; }
.disc-card-in { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.disc-card-e { margin-top: auto; padding-top: 12px; }

/* the signature W-Engine callout inside the dossier */
.sigw { display: flex; align-items: center; gap: 14px; margin-top: 12px; }
.sigw-art {
  position: relative;
  width: 74px;
  height: 74px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(66% 66% at 50% 98%, var(--warn, #FFC93C), transparent 76%),
    var(--ink-900);
  border: 1px solid var(--ink-600);
  overflow: hidden;
}
.sigw-art img,
.sigw-art .a-fallback { width: 84%; height: 84%; object-fit: contain; position: relative; z-index: 1; }
.sigw-art .a-fallback { font-size: 13px; }
.sigw-art .a-fallback::after { display: none; }
.sigw b {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: -.01em;
}
.sigw span { display: block; margin-top: 6px; font-size: 12.5px; line-height: 1.6; color: var(--mute-300); }

/* a horizontal strip of set covers — used as a "what to farm" row */
.disc-strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 10px; }
.ds {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 9px 11px;
  border: 1px solid var(--ink-700);
  background: var(--ink-850);
  text-decoration: none;
  transition: border-color .22s, transform .26s var(--ease-out);
}
.ds:hover { border-color: var(--fluoro); transform: translateY(-3px); }
.ds > img,
.ds > .a-fallback,
.ds > .tile-glyph {
  width: 100%;
  aspect-ratio: 4 / 3;
  height: auto;
  object-fit: contain;
  background: radial-gradient(68% 68% at 50% 96%, rgba(232, 255, 35, .16), transparent 76%);
}
.ds .a-fallback { font-size: 13px; }
.ds .a-fallback::after { display: none; }
.ds .tile-glyph-tag { display: none; }
.ds span {
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: .09em;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--mute-300);
  overflow-wrap: break-word;
}
.ds:hover span { color: var(--paper); }

/* same-faction roster rows inside an Agent dossier */
.roster-rows { display: grid; gap: 8px; margin-top: 14px; }
.roster-rows .agent-row-badge { width: 36px; height: 36px; }
.roster-rows .agent-row-n { font-size: 11.5px; }
.roster-rows .agent-row-m { font-size: 7.5px; }
.roster-rows .agent-tier { font-size: 9px; padding: 2px 6px; }

/* search results: a real thumbnail instead of a colour dot. Every row keeps
   the same 34px slot so the list height does not jump between hit types. */
.ov-row { min-height: 56px; }
.ov-dot { display: none; }
.ov-thumb {
  position: relative;
  width: 34px;
  height: 34px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ink-600);
  background:
    radial-gradient(64% 64% at 50% 100%, var(--oc, var(--fluoro)), transparent 76%),
    var(--ink-900);
  overflow: hidden;
}
.ov-thumb img { width: 84%; height: 84%; object-fit: contain; position: relative; z-index: 1; }
.ov-thumb.is-flat::after {
  content: '';
  width: 9px;
  height: 9px;
  background: var(--oc, var(--fluoro));
  transform: rotate(45deg);
}

/* Guide thumbnails are composed in four layers, back to front:
     crest watermark -> the fronting Agent's portrait -> glyph -> label.
   Each layer owns a different corner so nothing collides:
     crest top-left, portrait bottom-right, glyph bottom-left, name top-right. */
.guide-thumb-crest {
  position: absolute;
  left: -10%;
  top: -16%;
  width: 62%;
  height: 62%;
  background-repeat: no-repeat;
  background-position: left top;
  background-size: contain;
  opacity: .13;
  transform: rotate(-8deg);
  transition: opacity .4s ease, transform .6s var(--ease-out);
}
.guide-card:hover .guide-thumb-crest { opacity: .24; transform: rotate(-3deg) scale(1.05); }

/* the fronting Agent's portrait — oversized into the corner on purpose, so the
   crop reads as a deliberate composition rather than a shrunken sticker */
.guide-thumb-art {
  position: absolute;
  right: -4%;
  bottom: -12%;
  width: 62%;
  height: 116%;
  object-fit: contain;
  object-position: 100% 100%;
  filter: drop-shadow(-12px 8px 24px rgba(0, 0, 0, .62));
  transition: transform .6s var(--ease-out);
}
.guide-card:hover .guide-thumb-art { transform: translateY(-7px) scale(1.03); }

.guide-thumb-by {
  position: absolute;
  right: 12px;
  top: 13px;
  max-width: 46%;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--mute-300);
}

/* the oversized outlined category letters move to the opposite corner now that
   the portrait owns the bottom-right */
.guide-thumb-glyph { right: auto; left: 14px; }

/* the guide detail page carries the same Agent as a corner watermark */
.article-head-art {
  position: absolute;
  right: 0;
  bottom: 0;
  top: 0;
  width: min(38%, 500px);
  z-index: 0;
  pointer-events: none;
}
.article-head-art img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 100% 100%;
  opacity: .9;
  filter: drop-shadow(-18px 10px 34px rgba(0, 0, 0, .66));
}
/* the copy must sit above the watermark */
.article-head > .wrap { position: relative; z-index: 1; }
/* A long headline reaches into the art and the last word ends up sitting on the
   character's face. Reserve the art's column for the text instead. Gated on
   min-width because the art is hidden below 1025px, where the headline needs
   the full measure back. */
@media (min-width: 1025px) {
  .article-head:has(.article-head-art) > .wrap { padding-right: min(40%, 520px); }
}

@media (max-width: 1024px) {
  /* the headline needs the full width back on small screens */
  .article-head-art { display: none; }
}

/* related-agent mini cards gain a portrait medallion */
.mini-grid { grid-template-columns: repeat(auto-fill, minmax(238px, 1fr)); }
.mini { padding: 10px 12px; gap: 11px; }
.mini-m { display: flex; align-items: center; gap: 6px; }
.mini-pic {
  position: relative;
  width: 40px;
  height: 40px;
  flex: none;
  border: 1px solid var(--ink-600);
  background:
    radial-gradient(62% 62% at 50% 104%, var(--mc), transparent 74%),
    var(--ink-900);
  overflow: hidden;
}
.mini-pic img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
  transform: scale(1.2);
  transform-origin: 50% 100%;
}

/* team slots: the picture carries the recognition, the name anchors it.
   The frame moves from the card onto the picture so the art is the card. */
.slot {
  display: block;
  border: none;
  background: none;
  padding: 0;
  text-align: left;
  text-decoration: none;
  transition: transform .28s var(--ease-out);
}
.slot:hover { border-color: transparent; transform: translateY(-3px); }
.slot-pic { border: 1.5px solid var(--ink-600); transition: border-color .22s; }
a.slot:hover .slot-pic { border-color: var(--ac, var(--fluoro)); }
.slot-n { transition: color .22s; }
a.slot:hover .slot-n { color: var(--ac, var(--fluoro)); }

/* the roster-matrix table gains an art column */
.matrix td .rt {
  display: flex;
  align-items: center;
  gap: 8px;
}
.matrix td .rt-pic {
  position: relative;
  width: 28px;
  height: 28px;
  flex: none;
  border: 1px solid var(--ink-700);
  background: var(--ink-900);
  overflow: hidden;
}
.matrix td .rt-pic img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
  transform: scale(1.2);
  transform-origin: 50% 100%;
}
.matrix td .mi {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* attribute / specialty legend rows gain their emblem */
.side-row .k { display: inline-flex; align-items: center; gap: 8px; }
.side-row .k img { width: 17px; height: 17px; }

/* tier-band heading gets the tier colour as a hard rule */
.tier-band .tier-band-l { position: relative; }

@media (max-width: 640px) {
  .elist { grid-template-columns: 1fr; }
  .blist { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 9px; }
  .lst-count { margin-left: 0; width: 100%; }
}

/* ------------------------------------------------------------
   8. RESPONSIVE
   ------------------------------------------------------------ */
@media (max-width: 1080px) {
  .ahero-in { max-width: 100%; padding-bottom: 34px; }
  .ahero-art { position: relative; width: 100%; height: 300px; order: -1; }
  .ahero-art img { position: relative; right: auto; height: 300px; margin: 0 auto; display: block; }
  .ahero-art::after { background: linear-gradient(180deg, transparent 48%, var(--ink-900)); }
  .ahero { display: flex; flex-direction: column; }
  .ahero-emblem { width: 260px; height: 260px; right: -40px; margin-top: -130px; }
  /* Stacked, the art is in normal flow and no longer needs bounding, so the
     hero goes back to content height. The dossier also drops below the art, so
     the phone hero reads: masthead, counters, the Agent, their dossier. */
  .hero-main { min-height: 0; padding-bottom: 30px; }
  .hero-cols { grid-template-columns: 1fr; gap: 28px; }
  /* Stacked, the art window spans the full wrap — 968px at 1024 — and the
     figure is drawn 455px wide inside it, so a third of the window is a
     character-shaped hole. Capping the column caps the art *and* the dossier
     card inside it, so the two still share both edges the way they do on
     desktop. Below 640px this does nothing: the phone is narrower already. */
  /* An explicit width, not a max-width: with a max-width this column is a
     grid item centred by `justify-self`, so it sizes to its own content — and
     what its content measures is the rail's min-content, which is a function
     of how many Agents happen to be featured. Fine today (484px), a landmine
     tomorrow. */
  .hero-side { width: min(100%, 620px); justify-self: center; }
  /* Stacked, the desktop clamp would hand a 544px-tall window to a 390px
     phone. The height now tracks the column instead: 60vw keeps the full
     figure in proportion to the width it has, between a 300px floor and a
     460px ceiling. */
  .hero { --full-h: clamp(300px, 60vw, 460px); }
}
@media (max-width: 640px) {
  .ahero-art { height: 240px; }
  .ahero-art img { height: 240px; }
  .ahero-in { padding: 26px 20px 30px; }
  .arail { grid-template-columns: repeat(2, 1fr); }
  .arail-cell:nth-child(2n) { border-right: none; }
  .arail-cell { border-bottom: 1px solid var(--ink-700); }
  .dtile-art > img, .dtile-art > .a-fallback { width: 76%; height: 76%; }
  /* The masthead stack on a phone is badge + title + lead + three CTAs.
     Nothing is removed here — the three CTAs are kept full width and the lead
     keeps its text — the space comes out of the margins. The art needs no rule
     of its own: at 350px of column the aspect above draws a 227px figure, all
     of it, which is the smallest it is ever allowed to be drawn. */
  .hero-badge { padding: 5px 9px; }
  .hero-title { margin-top: 14px; }
  .hero-title .l3 { margin-top: 12px; gap: 10px; letter-spacing: .24em; }
  .hero-title .l3::before { width: 22px; }
  .hero-desc { margin-top: 14px; font-size: 13.5px; line-height: 1.58; }
  /* The comment above promised full-width CTAs and the rules below never did
     it: `flex-wrap` on a 390px phone left the three buttons at 200/190/186px
     on three ragged lines. A single-column grid makes good on it. */
  .hero-actions { margin-top: 18px; gap: 8px; display: grid; grid-template-columns: 1fr; }
  .hero-actions .btn { width: 100%; justify-content: center; }
  .hero-actions .btn { padding-top: 11px; padding-bottom: 11px; }
  .hero-feat { margin-top: 14px; }
  .hero-feat-id { row-gap: 8px; }
}

/* ------------------------------------------------------------
   9. ABOUT — THE ASSET SHEET
   The About page claims every asset here is the game's own art,
   so it shows one specimen per class plus complete symbol
   families.

   Sizing notes, all measured against the actual files rather
   than assumed:
     - splash art is PORTRAIT (0.57-0.85), everything else in the
       build is essentially square (drive disc covers 1.16,
       W-Engine renders 0.75-1.00, Bangboo 0.89-1.00, emblems ~1.0);
     - so a 16/10 "hero" plate was the wrong shape for every asset
       it could ever hold, and an aspect-ratio-based row was wrong
       in principle: two plates in one row only line up if their
       ratios happen to agree, and they cannot, because the column
       gap is a fixed 10px while the columns scale with the viewport.
   The sheet therefore uses one uniform auto-row height. Every plate
   in every row is then exactly the same height by construction, at
   every viewport width, with no ratio arithmetic to keep in sync.
   ------------------------------------------------------------ */
.sheet {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 410px;
  gap: 10px;
  margin: 22px 0 12px;
}

/* The showcase lives outside .prose, so it carries its own heading and lead
   set to the prose scale rather than inheriting it. */
.sheet-h {
  font-family: var(--font-display);
  font-size: 24px;
  line-height: 1.14;
  letter-spacing: -.02em;
  text-transform: uppercase;
  color: var(--paper);
  margin: 46px 0 0;
}
.sheet-lead {
  font-size: 15.5px;
  line-height: 1.78;
  color: var(--paper-dim);
  max-width: 820px;
  margin: 14px 0 0;
}
@media (max-width: 900px) {
  .sheet-h { font-size: 20px; }
  .sheet-lead { font-size: 15px; line-height: 1.72; }
}
.sheet-plate {
  position: relative;
  margin: 0;
  height: 100%;
  grid-column: span 4;
  display: flex;
  flex-direction: column;
  border: 1.5px solid var(--ink-700);
  background:
    repeating-linear-gradient(45deg, rgba(255,255,255,.014) 0 1px, transparent 1px 7px),
    var(--ink-850);
  overflow: hidden;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 15px), calc(100% - 15px) 100%, 0 100%);
  transition: border-color .35s var(--ease-out), transform .35s var(--ease-out);
}
/* Display-case glow. A portrait splash in a near-square plate leaves real
   margin either side; without something behind it that margin reads as a
   sizing mistake rather than as a mount. */
.sheet-plate::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: radial-gradient(62% 52% at 50% 40%, rgba(232,255,35,.06), transparent 72%);
  pointer-events: none;
}
.sheet-plate:hover { border-color: var(--fluoro-deep); transform: translateY(-3px); }
.sheet-plate > img {
  position: relative;
  z-index: 1;
  /* flex-basis 0 rather than auto: the art then always grows to exactly fill
     whatever the caption leaves, so no specimen can be clipped by its own
     placard the way a fixed height plus an overlay caption did. */
  flex: 1 1 0;
  min-height: 0;
  width: 100%;
  object-fit: contain;
  padding: 14px;
  box-sizing: border-box;
}
/* The medallion is a 142px head-and-shoulders roundel; shown at plate width it
   looks like a mistake, so it is inset to roughly the scale it has in the UI. */
/* The medallion is a 142px head-and-shoulders roundel. It was being inset with
   percentage padding, which left it drawn at 266px from a 142px file — nearly
   2x, and visibly soft on the page that exists to show the artwork is real.
   Sized at its own resolution instead of at a proportion of the plate. */
.sheet-plate.is-round > img {
  padding: 0;
  width: auto;
  height: auto;
  max-width: min(142px, 100%);
  max-height: 142px;
  margin: auto;
  align-self: center;
}
/* The icon-sized classes. Best case 256px (disc card), but the ceiling is set
   by the *worst* file in the group, not the best: three W-Engines ship at
   156/156/166 and six Bangboo at 178. Any cap above 156 puts those on screen
   larger than they exist and the browser invents the difference — which is
   exactly what "the images look soft" means. 156 is the shortest side of the
   softest asset, so the group can never be enlarged. */
.sheet-plate.is-tight > img {
  padding: 0;
  width: auto;
  height: auto;
  max-width: min(156px, 100%);
  max-height: 156px;
  margin: auto;
  align-self: center;
}

/* Placard. A static footer rather than a gradient overlay: an overlay covers
   the bottom of the specimen, which on a square Agent bust means the chest,
   and on a portrait splash means the whole lower half of the character. */
.sheet-cap {
  position: relative;
  z-index: 2;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 9px 13px 10px;
  background: var(--ink-800);
  border-top: 1px solid var(--ink-750);
}
.sheet-tag {
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--fluoro);
}
.sheet-sub {
  font-size: 10.5px;
  line-height: 1.3;
  color: var(--mute-300);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Symbol families — full sets, one row each */
.sheet-rows {
  border: 1.5px solid var(--ink-700);
  background: var(--ink-850);
  margin-bottom: 30px;
}
.sheet-row {
  display: grid;
  grid-template-columns: 168px 1fr 52px;
  align-items: center;
  gap: 16px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--ink-800);
}
.sheet-row:last-child { border-bottom: 0; }
.sheet-row-k {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mute-300);
}
.sheet-row-v { display: flex; flex-wrap: wrap; gap: 11px 16px; }
.sheet-sym { display: flex; align-items: center; gap: 7px; }
.sheet-sym .sheet-ic {
  /* 30px, deliberately under the 40px ceiling the QA pass treats as an
     oversized emblem — a larger glyph here reads as a layout bug. */
  width: 30px;
  height: 30px;
  flex: none;
  object-fit: contain;
  transition: transform .3s var(--ease-out);
}
.sheet-sym:hover .sheet-ic { transform: scale(1.14); }
.sheet-sym-n { font-size: 10.5px; color: var(--mute-300); white-space: nowrap; }
.sheet-sym:hover .sheet-sym-n { color: #ECECF0; }
.sheet-row-n {
  font-family: var(--font-display);
  font-size: 19px;
  line-height: 1;
  text-align: right;
  color: var(--fluoro);
}

@media (max-width: 900px) {
  /* Two up, uniform rows. The desktop span (3 of 3 per row) would make each
     plate too narrow to read a splash in, and a per-plate span override would
     need one rule per plate class — so the grid itself is 2 columns here and
     every plate just takes one. `.sheet .sheet-plate` ties on specificity with
     `.sheet-plate` above and wins on source order. */
  .sheet { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 260px; }
  .sheet .sheet-plate { grid-column: span 1; }
  .sheet-row { grid-template-columns: 1fr auto; row-gap: 9px; }
  .sheet-row-k { grid-column: 1; }
  .sheet-row-n { grid-column: 2; grid-row: 1; }
  .sheet-row-v { grid-column: 1 / -1; gap: 10px 14px; }
}
@media (max-width: 560px) {
  .sheet { grid-auto-rows: 230px; }
  .sheet-sym .sheet-ic { width: 26px; height: 26px; }
  .sheet-sym-n { font-size: 10px; }
}
