:root {
  /* Melniboné palette: obsidian and old iron with a violet-black undertone (the Chaos void),
     ruby-red as the blade of the theme (blood, the Ruby Throne, combat), imperial violet as
     the sorcery/chaos accent, and pale albino-ivory for headline text. Kept as CSS variables
     so the whole app can be retuned from one place. */
  --bg: #07060c;
  --bg-gradient: radial-gradient(ellipse 900px 600px at 15% -10%, #1c1130 0%, transparent 60%),
    radial-gradient(ellipse 900px 700px at 100% 0%, #230d1a 0%, transparent 55%),
    radial-gradient(ellipse at bottom, #0c0a16 0%, #05040a 100%);
  --panel: rgba(21, 17, 30, 0.72);
  --panel-solid: #14111d;
  --panel-raised: #1c1728;
  --border: #2a2338;
  --border-bright: #4a3568;
  --bone: #cac2dc;
  --bone-bright: #f1ecfa;
  --blood: #7d0f22;
  --blood-bright: #e91f42;
  --blood-glow: rgba(233, 31, 66, 0.35);
  --text: #e2dcef;
  --text-muted: #8e83a6;
  --danger: #8f1d1d;
  --danger-bright: #ff4f4f;
  --success: #3f9d6d;
  --warn: #d19a3c;
  --spirit: #8f7bec;
  --divine: #d4af37;
  --sorcery: #38c7c7;
  --chaos: #6a2fb0;
  --chaos-bright: #a562f0;
  --chaos-glow: rgba(165, 98, 240, 0.35);
  --hidden: #6a2a54;
  --radius: 16px;
  --radius-sm: 10px;
  --shadow: 0 10px 34px rgba(0, 0, 0, 0.55), 0 1px 0 rgba(255, 255, 255, 0.02) inset;
  --font-display: "Cinzel", "Iowan Old Style", Georgia, serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* A small spacing scale so panel/card padding and gaps read as one consistent rhythm across
     the app, rather than each page picking its own rem value. */
  --space-1: 0.4rem;
  --space-2: 0.7rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.25rem;
  --sidebar-width: 248px;
  font-size: 16px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background:
    repeating-linear-gradient(115deg, rgba(255, 255, 255, 0.01) 0px, rgba(255, 255, 255, 0.01) 1px, transparent 1px, transparent 3px),
    var(--bg-gradient) fixed,
    var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--chaos); color: #fff; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-bright); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: var(--chaos); }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.035em;
  color: var(--bone-bright);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
  margin: 0 0 0.55em;
}
h2 { font-size: 1.25em; }
h3 { font-size: 1.02em; text-transform: uppercase; letter-spacing: 0.09em; font-weight: 600; color: var(--bone); }
h3 .badge, summary .badge { text-transform: none; letter-spacing: normal; }

a { color: var(--blood-bright); text-decoration: none; }
a:hover { text-decoration: underline; }

code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.1em 0.4em;
  font-size: 0.9em;
}

button {
  font: inherit;
  font-family: var(--font-body);
  cursor: pointer;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel-raised);
  color: var(--text);
  padding: 0.6em 1.1em;
  min-height: 44px;
  font-weight: 500;
  transition: border-color 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease, background 0.15s ease;
}
button:hover { border-color: var(--chaos-bright); color: var(--bone-bright); }
button:active { transform: translateY(1px); }
button:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
button:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--chaos-glow); }

button.primary {
  background: linear-gradient(155deg, var(--blood-bright), var(--blood) 80%);
  color: #fff3f1;
  font-weight: 600;
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 4px 16px var(--blood-glow);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
button.primary:hover { box-shadow: 0 4px 22px var(--blood-glow), 0 0 0 1px var(--blood-bright) inset; border-color: transparent; }
button.danger { background: linear-gradient(155deg, #b3273a, var(--danger) 80%); border: none; color: #fff; }
button.ghost { background: transparent; border-color: var(--border); }
button.ghost:hover { background: rgba(165, 98, 240, 0.08); }
button.small { padding: 0.4em 0.8em; min-height: auto; font-size: 0.85em; border-radius: 8px; }

input, select, textarea {
  font: inherit;
  font-family: var(--font-body);
  background: rgba(6, 5, 10, 0.6);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.55em 0.75em;
  width: 100%;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--chaos-bright);
  box-shadow: 0 0 0 3px var(--chaos-glow);
}
textarea { min-height: 4.5em; resize: vertical; }
select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%), linear-gradient(135deg, var(--text-muted) 50%, transparent 50%); background-position: calc(100% - 18px) center, calc(100% - 13px) center; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 2rem; }

/* ---------- App shell: a persistent left sidebar (nav + identity) beside the content column,
   built for a tablet/laptop screen with real width to work with - not a phone-first layout
   stretched wide. Below ~900px the sidebar collapses into a top bar + bottom tab bar (the old
   mobile layout's shape) via the media query at the bottom of this section, so the app still
   works on a narrow screen without needing a second navigation system built from scratch. ---------- */

#app-shell {
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
}

.sidebar {
  width: var(--sidebar-width);
  flex: 0 0 var(--sidebar-width);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, rgba(20, 14, 26, 0.92) 0%, rgba(12, 9, 16, 0.92) 100%);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-right: 1px solid var(--border);
  z-index: 5;
}
.sidebar::after {
  /* A living rune-line down the sidebar's trailing edge: crimson bleeding into chaos-violet. */
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: -1px;
  width: 2px;
  background: linear-gradient(180deg, transparent, var(--blood-bright) 15%, var(--chaos-bright) 50%, var(--blood-bright) 85%, transparent);
  opacity: 0.7;
}

.sidebar-brand {
  padding: var(--space-4) var(--space-3) var(--space-3);
  font-family: var(--font-display);
  color: var(--bone-bright);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 1em;
  display: flex;
  align-items: center;
  gap: 0.5em;
}
.sidebar-brand::before {
  /* The eight-arrowed Chaos sigil - a small nod to the tone the app is going for. */
  content: "";
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  display: inline-block;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cg fill='none' stroke='%23e91f42' stroke-width='4.5' stroke-linecap='round'%3E%3Cpath d='M32 4v16M32 44v16M4 32h16M44 32h16M12.7 12.7l11.3 11.3M40 40l11.3 11.3M51.3 12.7L40 24M24 40L12.7 51.3'/%3E%3C/g%3E%3Ccircle cx='32' cy='32' r='7' fill='%23a562f0'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  filter: drop-shadow(0 0 4px var(--blood-glow));
}

.sidebar-identity {
  padding: 0 var(--space-3) var(--space-3);
  border-bottom: 1px solid var(--border);
  font-size: 0.82em;
  color: var(--text-muted);
  display: flex;
  flex-direction: column;
  gap: 0.35em;
}
.sidebar-identity a { color: var(--text-muted); border-bottom: 1px dotted var(--border-bright); }
.sidebar-identity a:hover { color: var(--chaos-bright); text-decoration: none; }

.sidebar-nav {
  display: flex;
  flex-direction: column;
  padding: var(--space-2);
  gap: 0.2em;
}
.sidebar-nav button {
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  padding: 0.65em 0.8em;
  font-size: 0.88em;
  font-weight: 600;
  letter-spacing: 0.01em;
  min-height: 0;
  display: flex;
  align-items: center;
  gap: 0.65em;
  text-align: left;
}
.sidebar-nav button::before {
  font-family: var(--font-body);
  font-size: 1.15em;
  line-height: 1;
  width: 1.2em;
  text-align: center;
}
.sidebar-nav button[data-tab="character"]::before { content: "\2694"; }
.sidebar-nav button[data-tab="equipment"]::before { content: "\1F392"; }
.sidebar-nav button[data-tab="magic"]::before { content: "\1F52E"; }
.sidebar-nav button[data-tab="gear"]::before { content: "\1F6E1"; }
.sidebar-nav button[data-tab="bestiary"]::before { content: "\1F409"; }
.sidebar-nav button[data-tab="combat"]::before { content: "\1F5E1"; }
.sidebar-nav button[data-tab="campaign"]::before { content: "\1F4DC"; }
.sidebar-nav button:hover { color: var(--bone); background: rgba(255, 255, 255, 0.04); }
.sidebar-nav button.active {
  color: var(--bone-bright);
  background: linear-gradient(90deg, rgba(233, 31, 66, 0.16), rgba(165, 98, 240, 0.08));
  box-shadow: 2px 0 0 var(--blood-bright) inset;
}

.page { padding: var(--space-4) var(--space-4) var(--space-5); max-width: 1200px; }

/* Below this width, the sidebar becomes a compact top bar (brand + identity) plus a bottom tab
   bar - the same shape the app used before the sidebar existed, kept as a fallback rather than a
   fully separate design. */
@media (max-width: 900px) {
  /* The base rule's `align-items: flex-start` is a *desktop* choice (top-align the sidebar and
     page columns sitting side by side) - it doesn't get reset just because flex-direction flips
     to column here, and in a column flex container align-items controls the CROSS axis, which is
     now horizontal. flex-start there means "don't stretch, size each child to its own content"
     instead of "fill the screen width," so .page silently sizes itself to whatever's widest
     inside it and the whole document ends up wider than the viewport. `stretch` (the normal flex
     default) is what a single mobile column actually needs. This was the root cause behind every
     "page scrolls sideways" symptom found in the mobile audit - screens further down the cascade
     (list rows, buttons) were never the real problem, just the first things wide enough to expose
     it once .page stopped clamping its own children to 375px. */
  #app-shell { flex-direction: column; align-items: stretch; min-height: 0; }
  .sidebar {
    position: sticky;
    width: auto;
    flex: 0 0 auto;
    height: auto;
    overflow: visible;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    border-right: none;
    border-bottom: 1px solid var(--border);
    padding: 0.7em 0.9em;
    /* backdrop-filter on an ancestor creates a new containing block for position:fixed
       descendants (same as transform/filter/perspective) - left in place, .sidebar-nav's own
       "position: fixed; bottom: 0" below would resolve against *this* box instead of the real
       viewport, pinning the tab bar to the bottom of the header instead of the screen. .sidebar-nav
       has its own backdrop-filter for the same frosted-glass look, so nothing is lost here. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .sidebar::after { display: none; }
  .sidebar-brand { padding: 0; font-size: 0.92em; }
  .sidebar-identity {
    padding: 0;
    border-bottom: none;
    flex-direction: row;
    gap: 0.9em;
    flex-wrap: wrap;
    font-size: 0.76em;
  }
  .sidebar-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    flex-direction: row;
    background: rgba(15, 12, 20, 0.9);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-top: 1px solid var(--border);
    padding: 0 0 env(safe-area-inset-bottom, 0);
    z-index: 10;
    /* 8 tabs (Character/Equipment/Magic/Cults/Gear/Bestiary/Combat/Campaign) at readable label
       widths add up to ~460px - wider than a phone screen. `flex: 1` on the buttons below used to
       force an even 8-way split instead, which flexbox can't do below each button's natural
       content width, so the row silently overflowed and later tabs fell off the visible edge
       (Campaign, sometimes Combat) with no way to reach them. Scrolling sideways here is the
       standard mobile pattern for "more tabs than fit," and it keeps every label fully spelled
       out rather than truncating "Equipment"/"Bestiary"/"Campaign" into something unreadable. */
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .sidebar-nav::-webkit-scrollbar { display: none; }
  .sidebar-nav button {
    flex: 0 0 auto;
    min-width: 4.4em;
    flex-direction: column;
    gap: 0.25em;
    padding: 0.6em 0.4em 0.5em;
    font-size: 0.68em;
    text-transform: uppercase;
    text-align: center;
    border-radius: 0;
  }
  .sidebar-nav button.active { box-shadow: none; }
  .sidebar-nav button.active::after {
    content: "";
    position: absolute;
    top: 0;
    left: 24%;
    right: 24%;
    height: 2px;
    border-radius: 0 0 4px 4px;
    background: linear-gradient(90deg, var(--blood-bright), var(--chaos-bright));
    box-shadow: 0 0 8px var(--blood-glow);
  }
  /* This clears the fixed bottom tab bar by a flat rem value, but the tab bar itself grows taller
     on phones with a home-indicator safe area (it adds env(safe-area-inset-bottom) to its own
     padding - see .sidebar-nav above). Without matching that here, the last bit of scrollable page
     content ends up sitting exactly where the taller bar actually is on those devices. The
     .dice-fab equivalent of this fix lives with .dice-fab's own rules further down the file, not
     here - see the comment there for why. */
  .page { padding: var(--space-3); padding-bottom: calc(5.5rem + env(safe-area-inset-bottom, 0px)); max-width: none; }
}

/* Independent sections that can sit side by side once there's room, instead of one long stack -
   used selectively per page (Character, Equipment, Combat, Campaign), not a global page wrapper. */
.page-columns { display: flex; flex-direction: column; gap: var(--space-3); }
.page-columns .col { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
@media (min-width: 900px) {
  .page-columns { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; gap: var(--space-4); }
}

/* A simple header bar for the screens that sit *outside* the sidebar shell (there's no campaign
   yet to have a sidebar for) - just the Campaign Picker today. Kept minimal and separate from
   .sidebar-brand rather than shared, since this is a one-line top strip, not a persistent nav. */
.topbar {
  padding: var(--space-3) var(--space-4);
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  max-width: 1200px;
}
.topbar .brand {
  font-family: var(--font-display);
  color: var(--bone-bright);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.98em;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
}
.topbar .brand::before {
  content: "";
  width: 18px;
  height: 18px;
  display: inline-block;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cg fill='none' stroke='%23e91f42' stroke-width='4.5' stroke-linecap='round'%3E%3Cpath d='M32 4v16M32 44v16M4 32h16M44 32h16M12.7 12.7l11.3 11.3M40 40l11.3 11.3M51.3 12.7L40 24M24 40L12.7 51.3'/%3E%3C/g%3E%3Ccircle cx='32' cy='32' r='7' fill='%23a562f0'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  filter: drop-shadow(0 0 4px var(--blood-glow));
}
.topbar .sub { color: var(--text-muted); font-size: 0.8em; }
.topbar .sub a { color: var(--text-muted); border-bottom: 1px dotted var(--border-bright); }
.topbar .sub a:hover { color: var(--chaos-bright); text-decoration: none; }

.card {
  background: linear-gradient(165deg, rgba(28, 23, 40, 0.75) 0%, rgba(12, 10, 17, 0.85) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
  box-shadow: var(--shadow);
  /* backdrop-filter's GPU compositing cost scales with blur radius, and .card is used for nearly
     every content panel app-wide - a single busy tab can have 7+ of these blurring
     simultaneously. Chrome/Blink's backdrop-filter compositing is well-documented to be heavier
     than Safari/WebKit's, so a page full of these can feel sluggish on Chrome specifically even
     though nothing is actually broken. 2px keeps the frosted-glass look while cutting most of
     that cost - reported as felt lag on hover/click in Chrome vs Safari on the same machine. */
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  position: relative;
}
.card::before {
  /* A hairline rune-rule under the top edge of every card, crimson fading to violet - kept
     subtle (a texture, not a highlight) so it doesn't compete with a page full of cards. */
  content: "";
  position: absolute;
  top: 0;
  left: var(--space-3);
  right: var(--space-3);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--blood-bright) 15%, var(--chaos-bright) 50%, var(--blood-bright) 85%, transparent);
  opacity: 0.32;
}
.card h3 { margin-bottom: 0.65em; }

.card.gm-zone {
  border-color: var(--chaos);
  background: linear-gradient(165deg, rgba(38, 22, 56, 0.75) 0%, rgba(16, 11, 22, 0.85) 100%);
}
.card.gm-zone h3 { color: var(--chaos-bright); }
.card.gm-zone::before { background: linear-gradient(90deg, transparent, var(--chaos-bright) 30%, var(--chaos-bright) 70%, transparent); }

/* Collapsible cards (native <details>/<summary>, no JS needed) - used for setup-time sections
   like Manage Combatants and the Encounter Log, which don't need to stay open and eat scroll
   space during round-by-round play. */
details.card summary {
  font-size: 1.02em;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-weight: 600;
  color: var(--bone);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5em;
}
details.card summary::-webkit-details-marker { display: none; }
details.card summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--text-muted);
  font-size: 0.85em;
  transition: transform 0.15s ease;
}
details.card:not([open]) summary::after { transform: rotate(-90deg); }
details.card[open] summary { margin-bottom: 0.75em; }
details.card.gm-zone summary { color: var(--chaos-bright); }

.row { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.row.between { justify-content: space-between; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; }
@media (min-width: 640px) {
  .grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.6rem; }
}
.grid-4 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }

.stat {
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.65em 0.7em;
  text-align: center;
  transition: border-color 0.15s ease;
}
.stat .label { font-size: 0.7em; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.stat .value { font-size: 1.45em; font-weight: 700; color: var(--blood-bright); font-family: var(--font-display); }
button.stat.char-btn:hover { border-color: var(--chaos-bright); }

.list-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.6em 0.15em;
  border-bottom: 1px solid var(--border);
  gap: 0.5em;
  /* Shared by every weapon/skill/spell/inventory row across the app. Without flex-wrap, and with
     flex children defaulting to min-width:auto (refusing to shrink below their own content
     width), a name+details block on the left plus an actions row on the right that together
     exceed the viewport just push the whole page into horizontal scroll instead of stacking -
     the single highest-leverage mobile-overflow bug found in the audit, since fixing it here
     fixes it everywhere .list-item is used. */
  flex-wrap: wrap;
}
.list-item > * { min-width: 0; }
.list-item:last-child { border-bottom: none; }
.list-item .name { font-weight: 600; color: var(--bone-bright); }
.list-item .pct { color: var(--blood-bright); font-weight: 700; min-width: 3.5em; text-align: right; font-family: var(--font-display); }

/* ---------- Magic reference: a spell entry (name + stats + full description), taller than a
   .list-item since it always shows its description rather than hiding it behind a click. ---------- */
.spell-entry { padding: 0.7em 0.15em; border-bottom: 1px solid var(--border); }
.spell-entry:last-child { border-bottom: none; }
.spell-entry .name { font-weight: 600; color: var(--bone-bright); }
.spell-entry p { margin: 0.35em 0 0; }

.creature-entry { padding: 0.7em 0.15em; border-bottom: 1px solid var(--border); }
.creature-entry:last-child { border-bottom: none; }
.creature-entry .name { font-weight: 600; color: var(--bone-bright); }
.creature-entry p { margin: 0.35em 0 0; }
.creature-stat-block { margin-top: 0.5em; }
.creature-stat-block table { border-collapse: collapse; width: 100%; margin: 0.35em 0; font-size: 0.9em; overflow-x: auto; display: block; }
.creature-stat-block th, .creature-stat-block td { border: 1px solid var(--border); padding: 0.3em 0.5em; text-align: left; color: var(--bone); }
.creature-stat-block th { color: var(--bone-bright); background: rgba(255, 255, 255, 0.03); }

/* 7 columns (Location/Roll/HP/AP/Armour/ENC/State) don't fit a phone width - same
   display:block + overflow-x:auto treatment already used for .creature-stat-block table, so the
   table scrolls sideways on its own instead of pushing the whole page into horizontal scroll. */
.hit-locations-table { width: 100%; border-collapse: collapse; font-size: 0.92em; display: block; overflow-x: auto; }
.hit-locations-table th {
  text-align: left;
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  padding: 0.3em 0.5em;
  border-bottom: 1px solid var(--border-bright);
}
.hit-locations-table td { padding: 0.55em 0.5em; border-bottom: 1px solid var(--border); vertical-align: top; }
.hit-locations-table tr.list-item-row:last-of-type td { border-bottom: 1px solid var(--border); }
.hit-locations-table tr.armour-edit-row td { padding: 0 0.5em; border-bottom: none; }
.hit-locations-table tr.armour-edit-row:has(.armour-edit:empty) td { padding: 0; }
.hit-locations-table tr.armour-edit-row .armour-edit:not(:empty) { padding: 0.5em 0 0.8em; }

/* ---------- Skill categories (nested collapsibles inside the Skills card) ---------- */

details.skill-category summary {
  font-size: 0.85em;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
  color: var(--bone);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.25em 0;
  border-bottom: 1px solid var(--border);
}
details.skill-category summary::-webkit-details-marker { display: none; }
details.skill-category summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--text-muted);
  font-size: 0.85em;
  transition: transform 0.15s ease;
}
details.skill-category:not([open]) summary::after { transform: rotate(-90deg); }
details.skill-category[open] summary { margin-bottom: 0.4em; }

/* ---------- Manage Members (nested collapsible inside a Campaign list item) ---------- */

details.manage-members {
  margin: 0 0 0.6em;
  padding-bottom: 0.6em;
  border-bottom: 1px solid var(--border);
}
details.manage-members summary {
  font-size: 0.85em;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
  color: var(--bone);
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.25em 0;
}
details.manage-members summary::-webkit-details-marker { display: none; }
details.manage-members summary::after {
  content: "▾";
  margin-left: auto;
  color: var(--text-muted);
  font-size: 0.85em;
  transition: transform 0.15s ease;
}
details.manage-members:not([open]) summary::after { transform: rotate(-90deg); }
details.manage-members .member-section { margin-top: 0.5em; }
details.manage-members .member-section + .member-section { margin-top: 0.9em; }

/* ---------- Vital Signs: knight-templar body diagram health monitor ---------- */

.health-monitor {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: center;
}
.health-monitor-svg {
  width: 100%;
  max-width: 200px;
  height: auto;
  flex-shrink: 0;
}
.hm-ground { fill: rgba(0, 0, 0, 0.45); }
.hm-region {
  stroke: rgba(0, 0, 0, 0.5);
  stroke-width: 2;
  transition: fill 0.3s ease;
}
.hm-healthy { fill: #3ddc71; }
.hm-wounded { fill: #e8a33d; }
.hm-critical { fill: var(--blood-bright); }
.hm-down { fill: #2a2733; stroke: var(--blood-bright); }
.hm-unknown { fill: var(--panel-raised); stroke: var(--border); }
.hm-visor { fill: rgba(0, 0, 0, 0.55); }
.hm-plume { fill: var(--blood-bright); }
.hm-emblem { stroke: rgba(0, 0, 0, 0.4); }
.hm-readout {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  fill: #fff;
  paint-order: stroke;
  stroke: rgba(0, 0, 0, 0.75);
  stroke-width: 3px;
  pointer-events: none;
}
.health-monitor-legend {
  display: flex;
  flex-direction: column;
  gap: 0.4em;
  font-size: 0.82em;
  color: var(--text-muted);
}
.health-monitor-legend span { display: flex; align-items: center; gap: 0.5em; }
.hm-swatch {
  display: inline-block;
  width: 0.8em;
  height: 0.8em;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.5);
}
.hm-swatch.hm-healthy { background: #3ddc71; }
.hm-swatch.hm-wounded { background: #e8a33d; }
.hm-swatch.hm-critical { background: var(--blood-bright); }
.hm-swatch.hm-down { background: #2a2733; border-color: var(--blood-bright); }

.badge {
  display: inline-block;
  font-size: 0.7em;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 0.2em 0.6em;
  border-radius: 999px;
  background: var(--panel-raised);
  border: 1px solid var(--border-bright);
  color: var(--text-muted);
}
.badge.spirit { color: var(--spirit); border-color: var(--spirit); box-shadow: 0 0 8px -2px var(--spirit); }
.badge.divine { color: var(--divine); border-color: var(--divine); box-shadow: 0 0 8px -2px var(--divine); }
.badge.sorcery { color: var(--sorcery); border-color: var(--sorcery); box-shadow: 0 0 8px -2px var(--sorcery); }
.badge.custom, .badge.chaos { color: var(--chaos-bright); border-color: var(--chaos); box-shadow: 0 0 8px -2px var(--chaos); }

.why {
  margin-top: 0.5em;
  background: rgba(5, 4, 9, 0.6);
  border: 1px dashed var(--border-bright);
  border-radius: var(--radius-sm);
  padding: 0.65em 0.8em;
  font-size: 0.88em;
  color: var(--text-muted);
}
.why .line { display: flex; justify-content: space-between; padding: 0.15em 0; }
.why .line b { color: var(--text); }
.why .warning { color: var(--warn); margin-top: 0.3em; }
.why .blocking { color: var(--danger-bright); font-weight: 600; margin-top: 0.3em; }

.timeline-track { margin-top: 0.5rem; }
.timeline-track-label {
  font-size: 0.78em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.timeline-track:first-of-type .timeline-track-label { color: var(--blood-bright); }

.timeline {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 0.3rem;
  margin: 0.4rem 0 0.75rem;
}
.timeline .sr {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 7px;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  font-weight: 700;
  font-size: 0.85em;
  color: var(--text-muted);
  position: relative;
  transition: box-shadow 0.15s ease;
}
.timeline .sr.past { opacity: 0.5; }
.timeline .sr.current { border-color: var(--chaos-bright); color: var(--bone-bright); box-shadow: 0 0 0 2px var(--chaos-bright) inset, 0 0 10px var(--chaos-glow); }
.timeline .sr.has-own { background: linear-gradient(155deg, #3a0e1c, #22091a); color: var(--blood-bright); border-color: var(--blood); }
.timeline .sr.has-enemy::after {
  content: "";
  position: absolute;
  top: 2px;
  right: 2px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--danger-bright);
  box-shadow: 0 0 4px var(--danger-bright);
}

.search-results { margin-top: 0.75em; }
.search-cat { color: var(--chaos-bright); font-size: 0.72em; text-transform: uppercase; letter-spacing: 0.08em; margin: 0.9em 0 0.2em; font-weight: 700; }

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(4, 3, 7, 0.8);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 50;
  padding: 0;
}
@media (min-width: 640px) {
  .modal-backdrop { align-items: center; padding: 1rem; }
}
.modal {
  background: var(--panel-solid);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius) var(--radius) 0 0;
  width: 100%;
  max-width: 480px;
  max-height: 92vh;
  overflow-y: auto;
  padding: 1.35rem;
  box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.7);
}
@media (min-width: 640px) {
  .modal { border-radius: var(--radius); }
}

.modal.attack {
  border-color: var(--danger-bright);
  box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.7), 0 0 0 1px var(--danger) inset;
}
.modal.attack h2 { color: #ff9a9a; }

.field { margin-bottom: 0.85em; }
.field label { display: block; margin-bottom: 0.32em; color: var(--text-muted); font-size: 0.83em; letter-spacing: 0.02em; }

.toast {
  position: fixed;
  top: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  background: var(--panel-solid);
  border: 1px solid var(--chaos);
  color: var(--text);
  padding: 0.65em 1.1em;
  border-radius: var(--radius-sm);
  z-index: 100;
  box-shadow: var(--shadow), 0 0 16px var(--chaos-glow);
  max-width: 90vw;
}
.toast.error { border-color: var(--danger-bright); color: #ff9a9a; box-shadow: var(--shadow), 0 0 16px var(--blood-glow); }

.muted { color: var(--text-muted); }
.center { text-align: center; }
.mt { margin-top: 1rem; }
.spacer { height: 1rem; }
.small-note { font-size: 0.82em; margin-top: -0.3em; }

/* ---------- Vitals (HP / Fatigue / Magic) ---------- */

.vitals-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
}
@media (min-width: 640px) {
  .vitals-grid { grid-template-columns: repeat(3, 1fr); }
}
.vital {
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.65em 0.75em;
}
.vital-label { font-size: 0.74em; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 0.4em; }
.vital-bar {
  height: 10px;
  border-radius: 999px;
  background: rgba(5, 4, 8, 0.8);
  border: 1px solid var(--border);
  overflow: hidden;
  margin-bottom: 0.5em;
}
.vital-bar.small { height: 6px; margin: 0.3em 0; }
.vital-fill { height: 100%; border-radius: 999px; transition: width 0.25s ease; }
.vital-fill.ok { background: linear-gradient(90deg, #2f8358, #4fce8f); }
.vital-fill.warn { background: linear-gradient(90deg, var(--warn), #eec46a); }
.vital-fill.danger { background: linear-gradient(90deg, var(--danger), var(--danger-bright)); box-shadow: 0 0 8px var(--danger-bright); }
.vital-inputs { display: flex; align-items: center; gap: 0.35em; }
.vital-inputs input { width: 4.5em; text-align: center; padding: 0.35em; }
.vital-calc { display: flex; align-items: center; justify-content: space-between; gap: 0.5em; margin-top: 0.5em; }
.vital-calc button { padding: 0.25em 0.6em; font-size: 0.85em; }

/* ---------- Inline add/edit forms (skills, weapons, spells, inventory, combatants) ---------- */

.inline-form {
  background: rgba(8, 6, 12, 0.55);
  border: 1px dashed var(--border-bright);
  border-radius: var(--radius-sm);
  padding: 0.8em 0.9em;
  margin: 0.65em 0;
}
.inline-form .field { margin-bottom: 0.6em; }
.inline-form .field:last-of-type { margin-bottom: 0.4em; }
.checkbox-field {
  display: flex;
  align-items: center;
  gap: 0.45em;
  color: var(--text-muted);
  font-size: 0.9em;
  margin-bottom: 0.5em;
}
.checkbox-field input { width: auto; }

.skill-edit, .weapon-edit, .spell-edit, .item-edit, .combatant-manage { margin-top: 0.4em; }

/* ---------- Games Master combatant management ---------- */

.combatant-block { border-bottom: 1px solid var(--border); padding-bottom: 0.35em; margin-bottom: 0.35em; }
.combatant-block:last-child { border-bottom: none; }
.custom-stat-row { margin-bottom: 0.4em; align-items: center; }
.custom-stat-row input { flex: 1; }

.enemy-declare-row {
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.65em 0.75em;
  margin-bottom: 0.6em;
}

/* ---------- Leaderboard ---------- */

.leaderboard-header, .leaderboard-row {
  display: flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.6em 0.2em;
  border-bottom: 1px solid var(--border);
}
.leaderboard-row:last-child { border-bottom: none; }
.leaderboard-header {
  color: var(--text-muted);
  font-size: 0.72em;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: 1px solid var(--border-bright);
}
.lb-rank { width: 2.2em; text-align: center; font-size: 1.1em; font-family: var(--font-display); }
.lb-name { flex: 1; font-weight: 600; color: var(--bone-bright); }
.lb-stat { width: 4.5em; text-align: right; font-weight: 700; color: var(--blood-bright); font-family: var(--font-display); }
.leaderboard-header .lb-stat { color: var(--text-muted); font-weight: inherit; font-family: inherit; }
.leaderboard-row:first-child {
  background: linear-gradient(90deg, rgba(212, 175, 55, 0.14), transparent);
  border-radius: 8px;
}
.leaderboard-row:first-child .lb-rank { color: var(--divine); text-shadow: 0 0 8px rgba(212, 175, 55, 0.5); }

/* ---------- Login screen ---------- */

.login-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  position: relative;
}
.login-screen::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle 500px at 50% 20%, rgba(106, 47, 176, 0.16), transparent),
    radial-gradient(circle 400px at 50% 90%, rgba(125, 15, 34, 0.14), transparent);
  pointer-events: none;
}
.login-card { width: 100%; max-width: 380px; text-align: center; position: relative; }
.login-card::after {
  content: "";
  display: block;
  width: 34px;
  height: 34px;
  margin: -0.3em auto 0.9em;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cg fill='none' stroke='%23e91f42' stroke-width='4' stroke-linecap='round'%3E%3Cpath d='M32 4v16M32 44v16M4 32h16M44 32h16M12.7 12.7l11.3 11.3M40 40l11.3 11.3M51.3 12.7L40 24M24 40L12.7 51.3'/%3E%3C/g%3E%3Ccircle cx='32' cy='32' r='7' fill='%23a562f0'/%3E%3C/svg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  filter: drop-shadow(0 0 6px var(--blood-glow));
  order: -1;
}
.login-card h1 { font-size: 1.55em; text-transform: uppercase; }
.login-card .field { text-align: left; }
.role-choice { display: flex; gap: 0.5em; }
.role-choice .role-btn { flex: 1; }
.role-btn.active {
  background: linear-gradient(155deg, var(--chaos-bright), var(--chaos) 80%);
  color: #fff;
  border-color: var(--chaos-bright);
  box-shadow: 0 4px 16px var(--chaos-glow);
}

/* ---------- Character tab: GM roster picker ---------- */

#gm-char-picker { margin-top: 0.3em; }

.char-block { display: flex; flex-direction: column; }
.char-block .char-btn { flex: 1; }
.char-block .char-edit-btn { margin-top: 0.3em; width: 100%; }

/* ---------- Dice roller (the "roll the bones" floating button + modal) ---------- */

.dice-fab {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 20;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5em;
  line-height: 1;
  background: linear-gradient(155deg, var(--chaos-bright), var(--chaos));
  border: 1px solid rgba(255, 255, 255, 0.15);
  box-shadow: 0 6px 20px var(--chaos-glow);
  color: #fff;
  padding: 0;
  min-height: 0;
}
.dice-fab:hover { border-color: rgba(255, 255, 255, 0.3); }
/* This has to live after the base .dice-fab rule above, not up with the rest of the mobile
   breakpoint's overrides - a rule inside a matching @media block doesn't out-rank an unconditional
   rule of equal specificity, only source order does, and the unconditional `bottom: 1.25rem` above
   used to come later in the file than the old mobile override. That meant the "mobile" bottom
   offset was silently never applying at any width - the FAB sat at 1.25rem (behind/under the fixed
   bottom tab bar) on phones the whole time, which is what read as the dice roller "colliding with
   the bottom bar." Also accounts for env(safe-area-inset-bottom): the tab bar itself grows taller
   on phones with a home-indicator safe area (see .sidebar-nav), so a flat rem offset alone isn't
   enough on those devices either. */
@media (max-width: 900px) {
  .dice-fab { bottom: calc(5.5rem + env(safe-area-inset-bottom, 0px)); }
}

.modal.dice-roller { text-align: center; max-width: 420px; }
.modal.dice-roller h2 { text-align: left; }

.dice-picker { display: flex; flex-wrap: wrap; gap: 0.4em; justify-content: center; margin: 0.75em 0 1em; }
.dice-picker button.active {
  background: linear-gradient(155deg, var(--blood-bright), var(--blood));
  color: #fff3f1;
  border-color: transparent;
}

.dice-stage {
  min-height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 1rem 0;
  position: relative;
}
.dice-stage.shaking { animation: dice-stage-tense 2.6s ease-in-out; }
@keyframes dice-stage-tense {
  0% { transform: scale(1); }
  50% { transform: scale(1.015); }
  100% { transform: scale(1); }
}

/* The centrepiece: a Jumanji-board-style brass-ringed window with roiling mist behind it and
   the result burning/carving itself into view in the middle - reskinned in the app's own
   blood/chaos palette instead of jungle green. */
.dice-orb {
  position: relative;
  width: 210px;
  height: 210px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(circle at 50% 42%, #050308 0%, #0a0612 55%, #150a1e 100%);
  box-shadow:
    0 0 0 2px #000 inset,
    0 0 0 8px var(--panel-solid) inset,
    0 0 0 10px var(--border-bright) inset,
    0 0 0 13px #000 inset,
    0 10px 34px rgba(0, 0, 0, 0.65),
    0 0 40px 4px var(--chaos-glow);
}
.dice-stage.shaking .dice-orb { animation: dice-orb-breathe 2.6s ease-in-out; }
@keyframes dice-orb-breathe {
  0%, 100% { box-shadow:
    0 0 0 2px #000 inset, 0 0 0 8px var(--panel-solid) inset, 0 0 0 10px var(--border-bright) inset,
    0 0 0 13px #000 inset, 0 10px 34px rgba(0, 0, 0, 0.65), 0 0 40px 4px var(--chaos-glow); }
  50% { box-shadow:
    0 0 0 2px #000 inset, 0 0 0 8px var(--panel-solid) inset, 0 0 0 10px var(--border-bright) inset,
    0 0 0 13px #000 inset, 0 10px 34px rgba(0, 0, 0, 0.65), 0 0 62px 14px var(--chaos-glow); }
}
.dice-orb::before {
  /* Carved tick marks around the brass ring, like the board's engraved rim. */
  content: "";
  position: absolute;
  inset: -13px;
  border-radius: 50%;
  background: repeating-conic-gradient(rgba(255, 255, 255, 0.09) 0deg 2deg, transparent 2deg 12deg);
  -webkit-mask: radial-gradient(circle, transparent 63%, #000 64%, #000 76%, transparent 77%);
  mask: radial-gradient(circle, transparent 63%, #000 64%, #000 76%, transparent 77%);
  pointer-events: none;
}

.dice-mist {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  pointer-events: none;
}
.dice-mist span {
  position: absolute;
  width: 140%;
  height: 140%;
  left: -20%;
  top: -20%;
  border-radius: 50%;
  filter: blur(18px);
  opacity: 0.55;
  animation: dice-mist-drift 9s ease-in-out infinite;
}
.dice-mist span:nth-child(1) { background: radial-gradient(circle at 30% 35%, var(--chaos-glow), transparent 60%); animation-duration: 8s; }
.dice-mist span:nth-child(2) { background: radial-gradient(circle at 70% 60%, var(--blood-glow), transparent 60%); animation-duration: 11s; animation-delay: -3s; }
.dice-mist span:nth-child(3) { background: radial-gradient(circle at 50% 75%, rgba(56, 199, 199, 0.28), transparent 60%); animation-duration: 13s; animation-delay: -6s; }
@keyframes dice-mist-drift {
  0%, 100% { transform: translate(0, 0) rotate(0deg) scale(1); }
  33% { transform: translate(4%, -3%) rotate(8deg) scale(1.08); }
  66% { transform: translate(-3%, 4%) rotate(-6deg) scale(0.96); }
}
.dice-stage.shaking .dice-mist span {
  /* Ethereal, not frantic - the fog thickens and slows to a deep roll while the answer forms. */
  animation-duration: 4.5s;
  opacity: 0.8;
  filter: blur(22px);
}

.dice-readout {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 0.05em;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 3em;
  line-height: 1;
  color: var(--divine);
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.8), 0 0 18px rgba(212, 175, 55, 0.65), 0 0 36px rgba(212, 175, 55, 0.35);
}
.dice-readout .rune-char {
  display: inline-block;
  opacity: 0;
  filter: blur(6px);
  transform: translateY(6px) scale(0.7);
}
.dice-readout.materialise .rune-char {
  animation: dice-rune-materialise 1.3s cubic-bezier(0.16, 0.8, 0.24, 1) both;
}
@keyframes dice-rune-materialise {
  0% { opacity: 0; filter: blur(16px); transform: translateY(14px) scale(0.55); }
  45% { opacity: 0.55; filter: blur(9px); }
  100% { opacity: 1; filter: blur(0); transform: translateY(0) scale(1); }
}
.dice-readout .cycling {
  color: var(--text-muted);
  text-shadow: 0 0 12px rgba(180, 170, 255, 0.25);
  filter: blur(1.5px);
  animation: dice-cycle-breathe 2.1s ease-in-out infinite;
}
@keyframes dice-cycle-breathe {
  0%, 100% { opacity: 0.35; filter: blur(2.5px); }
  50% { opacity: 0.85; filter: blur(0.5px); }
}

.dice-flash {
  position: absolute;
  inset: -30px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--chaos-glow), transparent 70%);
  opacity: 0;
  pointer-events: none;
}
.dice-flash.burst { animation: dice-flash-burst 1.8s ease-out; }
@keyframes dice-flash-burst {
  0% { opacity: 0; transform: scale(0.3); }
  35% { opacity: 0.5; }
  100% { opacity: 0; transform: scale(1.8); }
}

.dice-breakdown { color: var(--text-muted); font-size: 0.9em; min-height: 1.2em; margin-top: 0.75rem; }

/* ---------- Incoming attack warning (a beast lunges at the little knight before the reaction
   modal opens - a beat of dread before "Incoming Attack" gives you the choice to parry/dodge) ---------- */

.attack-warning-backdrop {
  animation: attack-warning-vignette-in 0.4s ease-out both;
}
@keyframes attack-warning-vignette-in {
  0% { background: rgba(0, 0, 0, 0); }
  100% { background: rgba(0, 0, 0, 0.75); }
}

.attack-warning-scene {
  position: relative;
  width: min(92vw, 440px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.attack-warning-svg { width: 100%; height: auto; overflow: visible; }

.aw-knight-group {
  transform-box: fill-box;
  transform-origin: center;
  animation: aw-knight-flinch 2.6s ease-in-out both;
}
@keyframes aw-knight-flinch {
  0%, 60% { transform: translate(0, 0) rotate(0deg) scale(0.85); }
  68% { transform: translate(-12px, 3px) rotate(-7deg) scale(0.85); }
  78% { transform: translate(-5px, 0) rotate(-2deg) scale(0.85); }
  100% { transform: translate(-7px, 0) rotate(-2deg) scale(0.85); }
}

.aw-knight-shield {
  transform-box: fill-box;
  transform-origin: center;
  animation: aw-shield-raise 2.6s ease-in-out both;
}
@keyframes aw-shield-raise {
  0%, 55% { transform: scale(1) rotate(0deg); }
  68% { transform: scale(1.14) rotate(-9deg); }
  100% { transform: scale(1.06) rotate(-4deg); }
}

.aw-beast-group {
  transform-box: fill-box;
  transform-origin: center;
  animation: aw-beast-lunge 2.6s cubic-bezier(0.5, 0, 0.2, 1) both;
}
@keyframes aw-beast-lunge {
  0% { transform: translateX(170px) scale(0.7) rotate(5deg); opacity: 0; }
  18% { opacity: 1; }
  60% { transform: translateX(0) scale(1.28) rotate(-4deg); }
  74% { transform: translateX(-8px) scale(1.16) rotate(1deg); }
  100% { transform: translateX(0) scale(1.18) rotate(0deg); }
}

.aw-beast-eye { animation: aw-eye-glow 1.6s ease-in-out infinite; }
@keyframes aw-eye-glow {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

.attack-warning-impact-flash {
  fill: #3a7d1e;
  filter: blur(16px);
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  pointer-events: none;
  animation: aw-impact-flash 2.6s ease-out both;
}
@keyframes aw-impact-flash {
  0%, 57% { opacity: 0; transform: scale(0.4); }
  65% { opacity: 0.85; transform: scale(1.2); }
  100% { opacity: 0; transform: scale(1.7); }
}

.attack-warning-text {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 1.5em;
  text-align: center;
  color: var(--blood-bright);
  text-shadow: 0 0 6px rgba(0, 0, 0, 0.8), 0 0 22px var(--blood-glow);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0;
  filter: blur(8px);
  animation: aw-text-in 0.8s ease-out 1.5s both;
}
@keyframes aw-text-in {
  0% { opacity: 0; filter: blur(10px); transform: scale(0.85); }
  100% { opacity: 1; filter: blur(0); transform: scale(1); }
}

.attack-warning-subtext {
  color: var(--text-muted);
  font-size: 0.9em;
  text-align: center;
  opacity: 0;
  animation: aw-text-in 0.7s ease-out 2s both;
}

/* Green ogre/troll/hulk palette - swapped out from the earlier generic dark-violet beast so it
   reads unmistakably as a hulking green brute, tusks and all, looming over a small noble knight. */
.aw-beast-body { fill: #16240d; stroke: #5fa62e; stroke-width: 2; }
.aw-beast-belly { fill: #2b4318; }
.aw-beast-horn { fill: #f2ead2; } /* tusks */
.aw-beast-claw { fill: #0d1608; stroke: #3a7d1e; stroke-width: 1.5; }
.aw-beast-eye { fill: #ffcf3d; filter: drop-shadow(0 0 5px rgba(255, 207, 61, 0.7)); }
.aw-knight-body { fill: #4a4d5c; stroke: var(--divine); stroke-width: 1.5; }
.aw-knight-head { fill: #cfd3de; stroke: var(--divine); stroke-width: 1.5; }
.aw-knight-plume { fill: var(--blood-bright); }
.aw-knight-crown { fill: var(--divine); }
.aw-knight-cape { fill: #5a1f2b; stroke: var(--divine); stroke-width: 1; }
.aw-knight-shield-shape { fill: #2a2440; stroke: var(--divine); stroke-width: 2; }
.aw-knight-shield-sigil { fill: var(--divine); }
.aw-ground-shadow { fill: rgba(0, 0, 0, 0.5); }

/* ---------- Pass/Fail reveal after a parry/dodge roll (manual or virtual) ---------- */

.rr-outcome-wrap { text-align: center; padding: 0.5rem 0 0.25rem; }
.rr-outcome {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 2.6em;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0;
  filter: blur(10px);
  transform: scale(0.55);
  animation: rr-outcome-in 0.55s cubic-bezier(0.2, 1.4, 0.4, 1) forwards;
}
.rr-outcome.pass { color: #3ddc71; text-shadow: 0 0 4px rgba(0, 0, 0, 0.8), 0 0 14px rgba(61, 220, 113, 0.55), 0 0 30px rgba(61, 220, 113, 0.3); }
.rr-outcome.fail { color: var(--blood-bright); text-shadow: 0 0 4px rgba(0, 0, 0, 0.8), 0 0 14px var(--blood-glow), 0 0 30px var(--blood-glow); }
@keyframes rr-outcome-in {
  0% { opacity: 0; filter: blur(14px); transform: scale(0.5); }
  60% { opacity: 1; filter: blur(0); transform: scale(1.1); }
  100% { opacity: 1; filter: blur(0); transform: scale(1); }
}
.rr-outcome-detail { color: var(--text-muted); font-size: 0.9em; margin-top: 0.5rem; }

/* ---------- Guided character creation wizard ---------- */

.wizard-steps { display: flex; flex-wrap: wrap; gap: 0.35em 0.9em; margin-top: 0.6em; }
.wizard-steps span { opacity: 0.5; }
.wizard-steps span.active { opacity: 1; color: var(--blood-bright); font-weight: 600; }
.wizard-steps span.done { opacity: 0.85; text-decoration: line-through; text-decoration-color: var(--border-bright); }

.culture-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
.culture-card { border: 1px solid var(--border); border-radius: 10px; padding: 0.7em; cursor: pointer; }
.culture-card.selected { border-color: var(--chaos-bright); box-shadow: 0 0 0 1px var(--chaos-bright) inset; }
.culture-card .name { font-weight: 700; color: var(--bone-bright); margin-bottom: 0.25em; }

.char-roll-row { display: flex; align-items: center; gap: 0.6em; padding: 0.5em 0; border-bottom: 1px solid var(--border); }
.char-roll-row:last-child { border-bottom: none; }
.char-roll-row .key { width: 2.6em; font-weight: 700; color: var(--blood-bright); font-family: var(--font-display); }
.char-roll-row .formula { flex: 1; color: var(--text-muted); font-size: 0.85em; }
.char-roll-row input[type="number"] { width: 4.5em; margin: 0; }

.exp-row { display: flex; align-items: center; gap: 0.5em; padding: 0.4em 0; border-bottom: 1px solid var(--border); }
.exp-row:last-child { border-bottom: none; }
.exp-row .name { flex: 1; }
.exp-row .base { font-size: 0.85em; width: 4.5em; text-align: right; }
.exp-row input[type="number"] { width: 4em; margin: 0; }

/* ---------- Battle Screen: FF7-style retro battle view (replaces the Jumanji board) ----------
   A 16:10 "screen" with a pixel-art arena behind it, sprites in two diagonal formations, blue
   bevelled status windows along the bottom and a strike-rank track above them. Everything inside
   is sized in container-query units (cqw) so the whole screen scales as one picture from a phone
   to a propped-up tablet - no media queries needed. Animations are stepped (steps()) so they read
   as sprite frames, not smooth tweens. */

:root { --font-pixel: "Press Start 2P", "Courier New", monospace; }

.ff-card h2 { margin-bottom: 0; }
.ff-toolbar { flex-wrap: wrap; gap: 0.6rem; }
.ff-toolbar-right { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.ff-toolbar-right select, .ff-toolbar-right button, .ff-toolbar-right input { margin: 0; }
.ff-tool { padding: 0.35rem 1.8rem 0.35rem 0.6rem; font-size: 0.85em; }
.ff-tool-inline { display: inline-block; vertical-align: middle; padding: 0.15rem 1.6rem 0.15rem 0.5rem; font-size: 0.85em; margin: 0 0.2rem 0 0; }
button.ff-tool-inline { padding: 0.15rem 0.5rem; }
.ff-gm-controls { display: inline-flex; gap: 0.3rem; align-items: center; padding-left: 0.5rem; border-left: 1px solid var(--border); }
.ff-gm-controls input[type="number"] { width: 3.4em; padding: 0.3rem 0.4rem; }
.ff-card-screen { padding: 0.75rem; }
.ff-embedded { padding: 0; }
.battle-screen-card > summary { cursor: pointer; list-style: none; display: flex; gap: 0.6rem; align-items: baseline; }
.battle-screen-card > summary::-webkit-details-marker { display: none; }
.battle-screen-card > summary h3 { margin: 0; }
.battle-screen-card > summary::before { content: "▸"; color: var(--text-muted); }
.battle-screen-card[open] > summary::before { content: "▾"; }
.battle-screen-card[open] > summary { margin-bottom: 0.6rem; }

.ff-screen {
  position: relative;
  width: 100%;
  max-width: 960px;
  margin: 0 auto;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 6px;
  border: 3px solid #14141f;
  box-shadow: 0 0 0 2px #3a3a55, 0 18px 40px rgba(0, 0, 0, 0.6);
  background: #000;
  container-type: inline-size;
  font-family: var(--font-pixel);
  color: #fff;
  user-select: none;
  -webkit-user-select: none;
}
.ff-bg {
  position: absolute; inset: 0;
  /* Oversized and bottom-anchored so the backdrop's ground plane (the lower ~40% of every
     arena image) rises into the field instead of hiding behind the status windows - the
     horizon lands around 40% down the screen and the actors' feet sit on the ground below it. */
  background-size: auto 140%; background-position: center bottom;
  image-rendering: pixelated; image-rendering: crisp-edges;
}
.ff-bg::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.16) 0 1px, transparent 1px 3px),
    radial-gradient(ellipse at center, transparent 55%, rgba(0, 0, 0, 0.45) 100%);
}
.ff-flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; z-index: 30; }

/* ----- the field and its actors ----- */
.ff-field { position: absolute; inset: 21% 0 26% 0; }
.ff-actor {
  position: absolute; width: 17%; height: 40%;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  transform-origin: 50% 100%;
}
.ff-sprite { height: 84%; display: flex; align-items: flex-end; justify-content: center; animation: ff-idle 1.1s steps(2, jump-none) infinite; }
.ff-sprite img {
  height: calc(100% * var(--scale, 1)); width: auto; max-width: none;
  image-rendering: pixelated; image-rendering: crisp-edges;
  transform-origin: 50% 100%;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, 0.35));
  transition: filter 0.4s steps(3), opacity 0.6s steps(4), transform 0.5s steps(4);
}
.ff-foes .ff-sprite img { transform: scaleX(-1); }
.ff-foes .ff-actor:nth-child(odd) .ff-sprite { animation-delay: -0.55s; }
.ff-party .ff-actor:nth-child(even) .ff-sprite { animation-delay: -0.55s; }
.ff-name {
  margin-top: 0.3cqw; font-size: 1.05cqw; line-height: 1.3; color: #fff;
  text-shadow: 0.15cqw 0.15cqw 0 #000, -0.1cqw 0 0 #000, 0 -0.1cqw 0 #000; white-space: nowrap; max-width: 130%;
  overflow: hidden; text-overflow: ellipsis;
}
.ff-type-companion .ff-name, .ff-type-npc .ff-name { color: #d6ffe1; }
.ff-type-enemy .ff-name { color: #ffd6d6; }

.ff-cursor {
  position: absolute; top: -0.4cqw; left: 50%; width: 0; height: 0; margin-left: -1.1cqw; opacity: 0;
  border-left: 1.1cqw solid transparent; border-right: 1.1cqw solid transparent; border-top: 1.8cqw solid #ffe14d;
  filter: drop-shadow(0 0.2cqw 0 #6b4d00) drop-shadow(0 0 0.3cqw #000);
}
.ff-active .ff-cursor { opacity: 1; animation: ff-bounce 0.5s steps(2, jump-none) infinite alternate; }
.ff-target::after {
  content: ""; position: absolute; inset: 12% 22% 14% 22%; pointer-events: none;
  border: 0.3cqw dashed #ff4d4d; border-radius: 50%; animation: ff-blink 0.7s steps(1) infinite;
}
.ff-inactive { opacity: 0.55; }
.ff-fled { opacity: 0.35; }
.ff-fled .ff-sprite { animation: none; }
.ff-ko .ff-sprite { animation: none; }
.ff-party .ff-ko .ff-sprite img { transform: rotate(90deg) translate(18%, -22%); filter: grayscale(1) brightness(0.55); }
.ff-foes .ff-ko { opacity: 0.18; }
.ff-foes .ff-ko .ff-sprite img { transform: scaleX(-1) translateY(6%); filter: grayscale(1) brightness(0.4); }
.ff-ko .ff-name { color: #bbb; }

@keyframes ff-idle { from { transform: translateY(0); } to { transform: translateY(-0.5cqw); } }
@keyframes ff-bounce { from { transform: translateY(0); } to { transform: translateY(-0.7cqw); } }
@keyframes ff-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
@keyframes ff-pulse { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.5); } }

/* ----- the windows: classic blue gradient with a bevelled light border ----- */
.ff-window {
  background: linear-gradient(180deg, #2a3ad2 0%, #1a24a4 45%, #0b1266 100%);
  border: 0.35cqw solid #e6e6f6;
  box-shadow: inset 0 0 0 0.25cqw #70709a, inset 0 0 0 0.45cqw #05052a, 0 0 0 0.15cqw #000;
  border-radius: 0.5cqw;
  color: #fff;
  text-shadow: 0.12cqw 0.12cqw 0 #000;
  padding: 0.9cqw 1.2cqw;
  box-sizing: border-box;
}
.ff-msg {
  position: absolute; top: 1.8%; left: 50%; transform: translateX(-50%); z-index: 20;
  min-width: 42%; max-width: 84%; text-align: center; font-size: 1.3cqw; line-height: 1.6; padding: 0.6cqw 1.2cqw;
}
.ff-track { position: absolute; left: 1.2%; right: 1.2%; top: 12.5%; z-index: 15; padding: 0.5cqw 1cqw 0.4cqw; }
.ff-track-head { display: flex; justify-content: space-between; font-size: 1.1cqw; color: #ffe14d; margin-bottom: 0.4cqw; }
.ff-track-note { font-size: 1cqw; color: #cfd6ff; }
.ff-cells { display: grid; grid-template-columns: repeat(10, 1fr); gap: 0.2cqw; }
.ff-cell { position: relative; height: 4.6cqw; background: rgba(0, 0, 20, 0.45); border: 0.15cqw solid rgba(255, 255, 255, 0.18); border-radius: 0.3cqw; }
.ff-cell.done { background: rgba(255, 255, 255, 0.08); opacity: 0.55; }
.ff-cell.current { background: linear-gradient(180deg, #ffe14d, #d19a3c); border-color: #fff7c9; animation: ff-pulse 0.9s steps(2, jump-none) infinite; box-shadow: 0 0 0.8cqw rgba(255, 225, 77, 0.8); }
.ff-cell-num { position: absolute; left: 0; right: 0; bottom: 0.2cqw; text-align: center; font-size: 1cqw; color: #aeb6ff; }
.ff-cell.current .ff-cell-num { color: #2a1a03; text-shadow: none; }
.ff-cell.done .ff-cell-num { color: #7d84c0; }
.ff-cell-heads { position: absolute; left: 0; right: 0; bottom: 1.5cqw; display: flex; justify-content: center; align-items: flex-end; gap: 0.1cqw; flex-wrap: wrap-reverse; }
.ff-mini { height: 3cqw; width: auto; image-rendering: pixelated; image-rendering: crisp-edges; }
.ff-mini.ff-mirror { transform: scaleX(-1); }
.ff-mini.pending { filter: drop-shadow(0 0 0.3cqw #ffe14d) drop-shadow(0 0 0.15cqw #fff); }
.ff-mini.acted { opacity: 0.35; filter: grayscale(1); }
.ff-mini.idle { opacity: 0.8; }

.ff-bottom {
  position: absolute; left: 0; right: 0; bottom: 0; height: 26%; z-index: 15;
  display: grid; grid-template-columns: 1fr 1.7fr; gap: 0.5cqw; padding: 0.5cqw 1.2% 1.2%;
  box-sizing: border-box;
}
.ff-foelist, .ff-status { overflow-y: auto; font-size: 1.15cqw; line-height: 1.5; scrollbar-width: thin; }
.ff-row { display: grid; align-items: center; gap: 0.8cqw; padding: 0.25cqw 0; }
.ff-foe-row { grid-template-columns: minmax(0, 1fr) auto auto; }
.ff-status-row { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.2fr) minmax(0, 1fr); }
.ff-row.down { opacity: 0.5; }
.ff-row-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ff-row-extra { display: inline-flex; align-items: center; gap: 0.5cqw; }
.ff-hp { display: inline-flex; align-items: center; gap: 0.5cqw; min-width: 0; }
.ff-hp-num { font-size: 1cqw; white-space: nowrap; color: #e6ffe9; }
.ff-dim { color: #8f97d6; font-size: 1cqw; }
.ff-bar { display: inline-block; flex: 1; min-width: 3cqw; height: 0.9cqw; background: #05052a; border: 0.15cqw solid #9a9ac0; box-sizing: content-box; overflow: hidden; }
.ff-bar i { display: block; height: 100%; }
.ff-hpbar.ok i { background: #3ef06a; }
.ff-hpbar.warn i { background: #ffd23f; }
.ff-hpbar.danger i { background: #ff4a3d; animation: ff-blink 0.8s steps(1) infinite; }
.ff-atb { display: inline-flex; align-items: center; gap: 0.5cqw; min-width: 0; }
.ff-atb-label { font-size: 0.95cqw; white-space: nowrap; color: #cfd6ff; min-width: 4.2cqw; }
.ff-gauge i { background: linear-gradient(90deg, #35b9ff, #d8f4ff); }
.ff-gauge.ready i { background: #ffe14d; animation: ff-pulse 0.5s steps(2, jump-none) infinite; }
.ff-gauge.idle-ready i { background: #9aa8ff; }
.ff-gauge.acted i { background: #5b5f8c; }
.ff-tag { font-size: 0.95cqw; color: #cfd6ff; }
.ff-tag.dead { color: #ff4a3d; }
.ff-sprite-pick { font-family: var(--font-pixel); font-size: 0.9cqw; padding: 0.1cqw 1.4cqw 0.1cqw 0.3cqw; background-position: calc(100% - 0.6cqw) center, calc(100% - 0.35cqw) center; background-size: 0.3cqw 0.3cqw; border-radius: 0.2cqw; margin: 0; width: auto; }
.ff-row .muted { color: #8f97d6; }

/* ----- one-shot effects ----- */
.ff-pop { position: absolute; z-index: 25; pointer-events: none; font-size: 2.6cqw; color: #fff; text-shadow: 0.25cqw 0.25cqw 0 #000, -0.12cqw 0 0 #000, 0 -0.12cqw 0 #000; white-space: nowrap; }
.ff-pop.crit { color: #ffe14d; font-size: 3.2cqw; }
.ff-pop.miss { color: #cfd8ff; font-size: 1.5cqw; }
.ff-pop.dead { color: #ff4a3d; font-size: 1.8cqw; }
.ff-banner {
  position: absolute; left: 50%; top: 42%; z-index: 26; pointer-events: none;
  font-size: 4cqw; color: #ffe14d; text-shadow: 0.35cqw 0.35cqw 0 #000, 0 0 1.2cqw rgba(255, 225, 77, 0.8); white-space: nowrap;
}
.ff-outcome {
  position: absolute; inset: 0 0 26% 0; z-index: 22; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1cqw;
  background: rgba(0, 0, 20, 0.45);
}
.ff-outcome-title { font-size: 5cqw; color: #fff; text-shadow: 0.4cqw 0.4cqw 0 #000; animation: ff-pulse 1.2s steps(2, jump-none) infinite; }
.ff-outcome.win .ff-outcome-title { color: #ffe14d; }
.ff-outcome.lose .ff-outcome-title { color: #ff4a3d; }
.ff-outcome-sub { font-size: 1.3cqw; color: #cfd6ff; text-shadow: 0.12cqw 0.12cqw 0 #000; }
.ff-legend { line-height: 1.8; }

/* ----- Character tab: battle sprite picker ----- */
.sprite-pick { margin-top: 0.6rem; }
.sprite-pick-current { display: flex; align-items: center; gap: 0.8rem; }
.sprite-frame {
  display: inline-flex; align-items: flex-end; justify-content: center; width: 64px; height: 64px; flex: 0 0 auto;
  background: linear-gradient(180deg, #1c2cbf 0%, #0a1470 100%); border: 2px solid #d8d8e8; box-shadow: inset 0 0 0 2px #05052a; border-radius: 4px;
  font-family: var(--font-pixel); color: #cfd6ff; font-size: 18px; padding-bottom: 4px; box-sizing: border-box;
}
.sprite-frame img { height: 52px; width: auto; image-rendering: pixelated; image-rendering: crisp-edges; animation: ff-idle-px 1.1s steps(2, jump-none) infinite; }
@keyframes ff-idle-px { from { transform: translateY(0); } to { transform: translateY(-2px); } }
.sprite-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 0.5rem; margin-top: 0.7rem; }
.sprite-choice { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; padding: 0.5rem 0.3rem; background: var(--panel-raised); }
.sprite-choice.selected { border-color: var(--divine); box-shadow: 0 0 0 2px rgba(212, 175, 55, 0.35); }
.sprite-choice-label { font-size: 0.72em; color: var(--text-muted); text-align: center; line-height: 1.2; }
.sprite-choice.selected .sprite-choice-label { color: var(--bone-bright); }


.skill-tick { margin: 0 .4rem 0 0; font-size: .8rem; white-space: nowrap; }
