/* =========================================================================
   DragonInit design system — CORE
   Namespaced (di-*) tokens + component primitives. SAFE TO LOAD GLOBALLY:
   contains no bare-element selectors and no `body` background, so it can sit
   next to Bootstrap Slate without touching un-migrated pages. Plain CSS with
   :root custom properties (this repo has no SCSS toolchain).
   Reusable across the whole site; the living reference is /style-guide.
   ========================================================================= */

:root{
  /* surfaces */
  --obsidian:#16131F; --obsidian-2:#1C1830; --surface:#221D33;
  --surface-hi:#2A2440; --line:#332C4A; --line-soft:#2A2540;
  /* app surfaces (combat tracker + app-wide re-skin — used by di-app.css + di-tracker.css).
     --app-bg matches --obsidian so app pages share the landing's background (DADBT-60). */
  --app-bg:#16131F; --panel:#211C30; --panel-2:#1B1728;
  --row:#221D32; --row-hi:#2B2540; --row-line:#2E2842;
  /* text */
  --vellum:#EDE7DA; --vellum-dim:#C9C1D8; --muted:#8E86A6;
  /* brand */
  --ember:#E9A23B; --ember-hi:#F6BE5B; --arcane:#8A7CF0;
  /* health spectrum — from the live tracker */
  --hp-full:#4ADE80; --hp-high:#A3E635; --hp-mid:#FACC15;
  --hp-low:#FB923C; --hp-crit:#EF4444; --hp-dead:#4A4A4A; --hp-unknown:#9CA3AF;
  /* allegiance parity (matches site.css .friend/.foe) */
  --friend:#F5BD1F; --foe:#ce43fd;
  /* rank medals (Stats2 leaderboard — kept metallic + recognizable) */
  --rank-gold:#FFD700; --rank-silver:#C0C0C0; --rank-bronze:#cd7f32;
  /* ACTION CATEGORIES (DADBT-84 turn signature). A new dimension — "what kind of turn was that?" —
     that none of the existing semantics covered, so three tokens were added DELIBERATELY and their
     meaning is written into /style-guide in the same commit (the house rule). Spells reuse --arcane
     because that already means "an ability/spell"; "other" is --muted and "not recorded" is --line,
     because neither is a thing you DID.
     Chosen by MEASUREMENT, not by eye (the S11 ΔE lesson): every one of these sits ≥34 ΔE from every
     other palette token, so no two wedges can read as the same colour at donut scale. */
  --act-melee:#BF6A4A;    /* copper — a weapon in your hand */
  --act-ranged:#5FC7C0;   /* steel-teal — reach and distance */
  --act-utility:#B3B36B;  /* olive — everything else a turn can be spent on */
  /* layout */
  --maxw:1160px; --radius:14px; --radius-sm:9px;

  /* Data-dense type scale (grids, charts, timelines). THREE sizes, on purpose - the Battle Report
     drifted to 13/12/11px for the same thing (a combatant's name) across three charts before this
     existed. Constrain the handful of roles that repeat across every data view; do NOT try to
     specify every component. See /style-guide. */
  --fs-rowname:12px;   /* a row's subject: a combatant, a party member, a metric name */
  --fs-value:11px;     /* the number in a cell, a tally, a summary figure */
  --fs-axis:10px;      /* column headers, round labels, chips, legends */

  /* semantic aliases (intent -> primitive; ease the page-by-page migration) */
  --bg:var(--obsidian); --text:var(--vellum); --text-dim:var(--vellum-dim);
  --border:var(--line); --brand:var(--ember); --focus:var(--arcane);
}

/* ---------- layout helper ---------- */
.di-wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px}

/* ---------- honeypot (DADBT-55) ----------
   Off-screen anti-bot field on the Register form. Deliberately NOT display:none -- that is a
   stronger tell to a bot that inspects computed style; off-screen + aria-hidden + tabindex=-1
   (set in the markup) is invisible to sighted users, unreachable by keyboard, unannounced by
   screen readers, and uninteresting to autofill. A real submit leaves it empty.
   Class is `.di-honeypot`, NOT `.di-hp`: this rule loads GLOBALLY (di-core.css) and the combat
   tracker already uses `.di-hp` for its HP number (di-tracker.css) -- naming it `.di-hp` here
   off-screened the tracker's HP number on every party row (regression, fixed 2026-07-20). */
.di-honeypot{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden}

/* ---------- typography helpers (class-based; no global element restyle) ---------- */
.di-font-display{font-family:"Grenze Gotisch","Hanken Grotesk",serif;font-weight:700;line-height:1.02}
.di-font-body{font-family:"Hanken Grotesk",system-ui,-apple-system,sans-serif}
.di-mono{font-family:"Space Mono",ui-monospace,monospace}
.di-eyebrow{font-family:"Space Mono",monospace;font-size:12px;letter-spacing:.22em;text-transform:uppercase;color:var(--ember)}

/* ---------- buttons ---------- */
.di-btn{
  display:inline-flex;align-items:center;gap:.55em;
  font-family:"Hanken Grotesk",system-ui,sans-serif;font-weight:700;font-size:16px;
  padding:13px 22px;border-radius:11px;cursor:pointer;border:1px solid transparent;
  transition:transform .16s ease, box-shadow .16s ease, background .16s ease, border-color .16s ease;
  line-height:1;text-decoration:none;
}
.di-btn-primary{
  background:linear-gradient(180deg,var(--ember-hi),var(--ember));
  color:#241703;
  box-shadow:0 1px 0 rgba(255,255,255,.35) inset, 0 12px 28px -12px rgba(233,162,59,.75);
}
.di-btn-primary:hover{transform:translateY(-2px);box-shadow:0 1px 0 rgba(255,255,255,.4) inset,0 18px 34px -12px rgba(233,162,59,.85)}
.di-btn-ghost{background:transparent;color:var(--vellum);border-color:var(--line)}
.di-btn-ghost:hover{border-color:var(--muted);background:rgba(255,255,255,.03);transform:translateY(-2px)}
.di-btn-lg{font-size:18px;padding:16px 30px}
.di-btn-sm{font-size:14px;padding:8px 15px}
.di-btn:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
.di-btn:disabled,.di-btn[aria-disabled="true"]{opacity:.5;cursor:not-allowed;transform:none}

/* ---------- section header + toolbar (shared: combat tracker + app pages) ---------- */
/* Replaces the old `h5 + <hr> + .btn-toolbar` pattern: a title on the left, a row of
   .di-icon-btn actions on the right, and a single bottom border instead of <hr> rules. */
.di-toolbar{
  display:flex;align-items:center;gap:10px 14px;flex-wrap:wrap;
  padding:6px 2px 12px;margin:2px 0 14px;border-bottom:1px solid var(--line-soft);
}
.di-toolbar-title{
  font-family:"Grenze Gotisch","Hanken Grotesk",serif;font-weight:700;font-size:21px;line-height:1.05;
  color:var(--vellum);display:flex;align-items:center;gap:9px;margin:0;
}
.di-toolbar-actions{display:flex;align-items:center;gap:7px;margin-left:auto;flex-wrap:wrap}
/* in-battle cue: ember-tinted bottom border (replaces the old red <hr>) */
.di-toolbar.di-in-battle{border-bottom-color:rgba(233,162,59,.55)}

/* compact ghost icon button — the icon-only companion to .di-btn */
.di-icon-btn{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:36px;height:36px;padding:0;border-radius:9px;cursor:pointer;
  background:rgba(255,255,255,.03);border:1px solid var(--line);color:var(--vellum-dim);
  font-size:15px;line-height:1;text-decoration:none;
  transition:background .14s,border-color .14s,color .14s,transform .14s;
}
.di-icon-btn:hover{background:var(--surface-hi);border-color:var(--muted);color:var(--vellum);transform:translateY(-1px)}
.di-icon-btn:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
.di-icon-btn.di-danger{color:#ff9d9d;border-color:rgba(239,68,68,.4)}
.di-icon-btn.di-danger:hover{background:rgba(239,68,68,.14);border-color:rgba(239,68,68,.6);color:#ffb3b3}
/* A strip of icon buttons that belong together (a kebab + its steppers). Replaces Bootstrap's
   .btn-toolbar > .btn-group nesting, which sized its children differently from .di-icon-btn and so
   produced a row of three buttons at two heights and three widths. */
.di-btn-row{display:inline-flex;align-items:center;gap:6px}

/* ---------- nav (shared chrome + landing) ---------- */
.di-nav{
  position:sticky;top:0;z-index:1030;
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  background:linear-gradient(180deg,rgba(22,19,31,.92),rgba(22,19,31,.66));
  border-bottom:1px solid var(--line-soft);
}
.di-nav-inner{display:flex;align-items:center;justify-content:space-between;gap:12px 20px;flex-wrap:wrap;min-height:66px}
.di-brand{display:flex;align-items:center;gap:11px;font-family:"Grenze Gotisch",serif;font-weight:700;font-size:23px;letter-spacing:.01em;color:var(--vellum);text-decoration:none}
.di-brand .di-mark{width:30px;height:30px;flex:none}
.di-brand b{color:var(--ember);font-weight:700}
.di-nav-links{display:flex;align-items:center;gap:26px}
.di-link{
  color:var(--vellum-dim);font-weight:600;font-size:15px;transition:color .15s;
  text-decoration:none;background:none;border:0;cursor:pointer;font-family:inherit;padding:0;
}
.di-link:hover{color:var(--vellum)}
.di-link:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:5px}
.di-nav-cta{display:flex;align-items:center;gap:14px}
.di-badge{
  font-family:"Space Mono",monospace;font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ember);border:1px solid rgba(233,162,59,.45);border-radius:999px;padding:2px 9px;
}
/* Same pill, no shout. Ember is the attention colour, so a passive state ("Historical") must not
   wear it or it competes with the things that actually want the eye. */
.di-badge-quiet{color:var(--muted);border-color:var(--line)}

/* user menu via native <details> (no Bootstrap JS) */
.di-menu{position:relative}
.di-menu>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;color:var(--vellum-dim)}
.di-menu>summary::-webkit-details-marker{display:none}
.di-menu>summary:hover{color:var(--vellum)}
.di-menu>summary:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:6px}
.di-menu-panel{
  position:absolute;right:0;top:calc(100% + 10px);min-width:190px;z-index:1040;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-sm);
  padding:6px;box-shadow:0 24px 48px -24px rgba(0,0,0,.85);
}
.di-menu-head{padding:8px 12px 6px;color:var(--muted);font-size:13px}
.di-menu-item{
  display:block;width:100%;text-align:left;padding:9px 12px;border-radius:7px;
  color:var(--vellum-dim);text-decoration:none;background:none;border:0;cursor:pointer;
  font-family:inherit;font-size:15px;
}
.di-menu-item:hover{background:var(--surface-hi);color:var(--vellum)}
/* destructive entry - same red the danger icon button uses, so "delete" looks the same everywhere */
.di-menu-item.di-menu-danger{color:#ff9d9d}
.di-menu-item.di-menu-danger:hover{background:rgba(239,68,68,.14);color:#ffb3b3}
/* KEBAB variant (S12 D8-D9): a di-menu whose summary IS the icon button. Bootstrap's dropdown
   toggle was a <button> inside a .btn-group; <details> needs the trigger to be the <summary>, so
   the summary wears .di-icon-btn and this restores the box the flex/inline-flex summary rule
   would otherwise flatten. */
.di-kebab>summary.di-icon-btn{justify-content:center;width:36px;height:36px}
.di-kebab>summary::marker{content:""}
/* the browse-list rows are narrow; anchor that panel to the right edge so it cannot overflow */
.di-kebab-end .di-menu-panel{right:0;left:auto}
/* Set by di-menu.js when the default right-anchored panel would run off the left of the screen (a
   kebab at the START of a table row - the tracker's party rows) or off the bottom. Measured and
   applied on open; see the comment in that file. Do NOT hand-apply these - if a menu is misplaced,
   the placement rule is what needs fixing, not the one call site. */
.di-menu-start>.di-menu-panel{left:0;right:auto}
.di-menu-up>.di-menu-panel{top:auto;bottom:calc(100% + 10px)}

/* ---------- surfaces / panels ---------- */
.di-panel{
  background:linear-gradient(180deg,var(--surface),var(--obsidian-2));
  border:1px solid var(--line);border-radius:18px;
  box-shadow:0 40px 80px -40px rgba(0,0,0,.8), 0 0 0 1px rgba(138,124,240,.06);
  overflow:hidden;
}
.di-panel-top{
  display:flex;align-items:center;justify-content:space-between;
  padding:14px 18px;border-bottom:1px solid var(--line-soft);background:rgba(0,0,0,.18);
}
.di-panel-title{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--vellum)}
.di-panel-title .di-d20{width:18px;height:18px}
.di-round-chip{
  font-family:"Space Mono",monospace;font-size:12px;color:var(--vellum-dim);
  background:rgba(138,124,240,.13);border:1px solid rgba(138,124,240,.3);
  padding:4px 10px;border-radius:999px;letter-spacing:.04em;
}
.di-round-chip b{color:var(--arcane)}

/* ---------- empty / zero state (DADBT-56) ----------
   The app's first shared empty-state primitive. Pairs with .di-panel for a branded
   card: <div class="di-panel di-empty">. Reusable — the battle/party empty lists can
   adopt it next. Bare tokens (--vellum/--muted/--ember); there is no --di-* namespace. */
.di-empty{
  max-width:520px;margin:32px auto;padding:48px 32px;
  display:flex;flex-direction:column;align-items:center;text-align:center;
}
.di-empty .di-d20{width:44px;height:44px;margin-bottom:12px}
.di-empty-title{
  font-family:"Grenze Gotisch","Hanken Grotesk",serif;font-weight:700;line-height:1.05;
  font-size:26px;color:var(--vellum);margin:6px 0 8px;
}
.di-empty-text{color:var(--muted);max-width:42ch;margin:0 auto 22px;line-height:1.5}
/* inline add-round "+" inside the round pill (distinguishes it from the standalone add-combatant "+") */
.di-round-chip .di-round-add{margin-left:9px;background:transparent;border:0;color:var(--arcane);cursor:pointer;font-size:12px;line-height:1;padding:0 2px}
.di-round-chip .di-round-add:hover{color:var(--ember-hi)}
.di-round-chip .di-round-add:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:4px}

/* ---------- data table (DADBT-82, S10) ----------
   A Bootstrap-free grid primitive. Built for the stats grid but deliberately generic — the
   remaining `.table table-sm` pages adopt it as DADBT-31 reaches them, which is why it lives in
   di-core (global) rather than a page-scoped sheet.
   NOTE the wrapper is not optional: the stats grid is 11 columns wide and WILL overflow a phone.
   Scrolling has to happen inside .di-table-wrap, never on <body>. */
.di-table-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}
.di-table{width:100%;border-collapse:collapse;font-size:14px;color:var(--vellum-dim)}
.di-table th,.di-table td{padding:8px 12px;text-align:center;white-space:nowrap;border-bottom:1px solid var(--row-line)}
.di-table tbody tr:last-child td{border-bottom:0}
.di-table thead th{
  position:sticky;top:0;z-index:1;
  background:var(--obsidian-2);color:var(--muted);
  font-family:"Space Mono",monospace;font-size:11px;font-weight:400;
  letter-spacing:.08em;text-transform:uppercase;white-space:normal;
  border-bottom:1px solid var(--line);
}
/* numbers line up only with tabular figures — without this the medal columns visibly jitter */
.di-table td{font-variant-numeric:tabular-nums}
/* first column is the name: left-aligned and the one column allowed to be wide */
.di-table th:first-child,.di-table td:first-child{text-align:left;white-space:normal;min-width:9rem}
.di-table tbody tr:hover td{background:var(--surface-hi)}
/* rank medals are applied inline (computeStyle -> var(--rank-gold|silver|bronze) + black text),
   so :hover must not repaint over them */
.di-table tbody tr:hover td[style*="--rank"]{background:inherit}

/* ---------- scope picker (DADBT-82, S10) ----------
   Battle <-> whole-campaign selector, built on the native <details> menu the nav already uses
   (.di-menu) so it carries NO Bootstrap JS. Awards.cshtml still drives the same control through
   Bootstrap's dropdown; it can adopt this when DADBT-31 reaches that page. */
.di-scope>summary{
  padding:5px 12px;border:1px solid var(--line);border-radius:999px;
  background:var(--surface);color:var(--vellum-dim);font-size:13px;gap:8px;
}
.di-scope>summary:hover{border-color:var(--line-soft);color:var(--vellum)}
.di-scope>summary::after{content:"\25BE";font-size:10px;color:var(--muted)}
.di-scope[open]>summary{border-color:var(--ember);color:var(--vellum)}
.di-scope .di-menu-panel{max-height:min(60vh,340px);overflow-y:auto;min-width:16rem}

/* ---------- signature combat tracker ---------- */
.di-combatant{
  display:grid;grid-template-columns:34px 1fr auto;gap:13px;align-items:center;
  padding:11px 18px;border-bottom:1px solid var(--line-soft);
}
.di-combatant:last-child{border-bottom:0}
.di-combatant.di-active{background:linear-gradient(90deg,rgba(233,162,59,.10),transparent 70%)}
.di-init{
  font-family:"Hanken Grotesk",system-ui,sans-serif;font-weight:800;font-size:15px;color:var(--vellum);font-variant-numeric:tabular-nums;
  text-align:center;background:rgba(255,255,255,.04);border:1px solid var(--line-soft);
  border-radius:8px;padding:5px 0;
}
.di-combatant.di-active .di-init{color:var(--ember);border-color:rgba(233,162,59,.45);background:rgba(233,162,59,.08)}
.di-c-name{font-weight:600;font-size:15.5px;display:flex;align-items:baseline;gap:8px;min-width:0;color:var(--vellum)}
.di-c-name .di-tag{font-family:"Space Mono",monospace;font-size:11px;color:var(--muted);letter-spacing:.02em;white-space:nowrap}
.di-c-name.di-gone{color:var(--muted);text-decoration:line-through;text-decoration-color:var(--hp-dead)}
.di-hpwrap{display:flex;align-items:center;gap:10px;min-width:158px;justify-content:flex-end}
.di-bar{width:112px;height:9px;border-radius:6px;background:rgba(255,255,255,.07);overflow:hidden}
.di-bar>span{display:block;height:100%;border-radius:6px;width:var(--w);animation:di-fill 1.1s cubic-bezier(.2,.7,.2,1) both}
.di-hpnum{font-family:"Space Mono",monospace;font-size:12px;color:var(--vellum-dim);width:42px;text-align:right}
.di-hpnum.di-hidden{color:var(--hp-unknown)}
@keyframes di-fill{from{width:0}to{width:var(--w)}}

.di-legend{display:flex;flex-wrap:wrap;gap:14px 18px;margin-top:16px;padding:0 4px}
.di-legend span{display:flex;align-items:center;gap:7px;font-family:"Space Mono",monospace;font-size:11.5px;color:var(--muted)}
.di-legend i{width:11px;height:11px;border-radius:3px;display:inline-block}

/* ---------- footer (shared chrome + landing) ---------- */
.di-footer{border-top:1px solid var(--line-soft);padding:34px 0;color:var(--muted);background:rgba(0,0,0,.16)}
.di-foot-inner{display:flex;flex-wrap:wrap;gap:16px 24px;justify-content:space-between;align-items:center}
.di-footer .di-brand{font-size:19px}
.di-foot-links{display:flex;flex-wrap:wrap;gap:22px;font-size:14px}
.di-foot-links a{color:var(--muted);text-decoration:none}
.di-foot-links a:hover{color:var(--vellum)}
.di-foot-links a:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:4px}
.di-foot-meta{width:100%;display:flex;flex-wrap:wrap;gap:8px 18px;justify-content:space-between;align-items:center;
  border-top:1px solid var(--line-soft);margin-top:6px;padding-top:14px;font-size:12.5px}
.di-foot-attr a{color:var(--vellum-dim);text-decoration:underline}

/* ---------- scroll reveal ---------- */
/* Starts hidden, fades in on scroll (landing.js). No-JS fallback: _LandingLayout
   ships a <noscript> style that forces .di-reveal visible, so content never
   depends on the script to appear. */
.di-reveal{opacity:0;transform:translateY(18px);transition:opacity .6s ease,transform .6s ease}
.di-reveal.di-in{opacity:1;transform:none}

/* ---------- di-modal: the dialog primitive (DADBT-31, S12 D8-D9) ---------- */
/* Behaviour lives in js/components/di-modal.js - focus trap, Esc, backdrop click, scroll lock,
   return-focus-on-close. This file is only what it LOOKS like. The `hidden` attribute is the
   single source of truth for visibility, so a page with JS disabled shows nothing rather than an
   un-closable dialog stuck over the content. */
.di-modal[hidden]{display:none}
.di-modal{position:fixed;inset:0;z-index:1050;display:flex;align-items:center;justify-content:center;
  padding:24px 16px}
.di-modal-backdrop{position:absolute;inset:0;background:rgba(10,8,16,.72);backdrop-filter:blur(2px)}
.di-modal-panel{position:relative;width:min(680px,100%);max-height:calc(100vh - 48px);
  display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:0 30px 70px -20px rgba(0,0,0,.75);
  animation:di-modal-in .16s ease-out}
.di-modal-panel:focus{outline:none}
.di-modal-panel:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
.di-modal-head{display:flex;align-items:center;gap:12px;padding:15px 18px;
  border-bottom:1px solid var(--line)}
.di-modal-title{font-family:"Grenze Gotisch",serif;font-weight:700;font-size:21px;color:var(--vellum);
  margin:0;flex:1 1 auto;min-width:0}
.di-modal-x{background:transparent;border:0;color:var(--muted);font-size:26px;line-height:1;
  cursor:pointer;padding:0 4px;border-radius:6px}
.di-modal-x:hover{color:var(--vellum)}
.di-modal-x:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
/* DADBT-31 (S13 D6): the tracker's converted dialogs keep Bootstrap's three-div nesting
   (.di-modal > .di-modal-panel > .di-modal-inner), because collapsing a level would have meant
   re-indenting ~900 lines of Simple.cshtml in the same commit as a behaviour change. The inner wrapper
   is the flex column and the scroll container, which also covers the several tracker dialogs whose
   content was never wrapped in a body at all. */
.di-modal-inner{display:flex;flex-direction:column;min-height:0;overflow-y:auto;flex:1 1 auto}
/* Size modifiers, matching what the Bootstrap versions asked for (modal-lg / max-width overrides). */
.di-modal-panel.di-modal-lg{width:min(1000px,100%)}
.di-modal-panel.di-modal-sm{width:min(420px,100%)}
/* The body scrolls, never the page behind it - the same rule the wide vizes follow. */
.di-modal-body{padding:16px 18px;overflow-y:auto;color:var(--vellum-dim);line-height:1.55}
.di-modal-foot{display:flex;justify-content:flex-end;gap:8px;padding:13px 18px;
  border-top:1px solid var(--line)}
/* Set by the script while any dialog is open; padding-right compensates for the scrollbar it
   removes, so the page underneath does not jump sideways as the dialog appears. */
.di-modal-lock{overflow:hidden}
@keyframes di-modal-in{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}

/* ---------- save state (DADBT-101, S14) ----------
   The answer to "did that save?", which the app could not give before there was a write queue to
   ask. Created by di-writes.js on the first write, so no page carries markup for it.

   COLOUR: --ember throughout, including the failure. This is the app talking ABOUT ITSELF, and the
   style guide's semantic-use rule reserves the health spectrum for game outcomes -- a red "Not
   saved" would have read as damage on the one screen where everything red already means damage.
   Fixed to the bottom-right rather than dropped in a toolbar: the tracker's party list is long, and
   an indicator you have to scroll to is not an indicator. */
.di-savestate{
  position:fixed;right:16px;bottom:16px;z-index:900;
  padding:6px 12px;border-radius:999px;
  font-family:"Hanken Grotesk",system-ui,-apple-system,sans-serif;
  font-size:var(--fs-axis);letter-spacing:.06em;text-transform:uppercase;
  background:var(--panel);border:1px solid var(--line);color:var(--muted);
  pointer-events:none;transition:opacity .25s ease,color .25s ease,border-color .25s ease;
}
.di-savestate-busy{color:var(--ember);border-color:var(--ember)}
.di-savestate-done{color:var(--muted)}
.di-savestate-error{color:var(--ember-hi);border-color:var(--ember-hi);background:var(--surface-hi)}
/* Faded, not emptied. The word stays so the last thing that happened is still readable -- and so a
   test can assert on it. */
.di-savestate-idle{opacity:0}

/* ---------- reduced motion (component-scoped) ---------- */
@media (prefers-reduced-motion:reduce){
  .di-reveal{opacity:1;transform:none;transition:none}
  .di-bar>span{width:var(--w)!important;animation:none}
  .di-modal-panel{animation:none}
  .di-btn{transition:none}
  .di-savestate{transition:none}
}

