/* ANGELUS - fonts, bundled so they work from file:// with no network.
   Cinzel is cut from first-century Roman inscriptional capitals - carved
   stone letterforms, which is exactly what a game about a spire and its
   bells wants. Spectral is a screen-first serif for prose. The existing
   monospace stays for the log and any column of numbers, where alignment
   matters more than character. */

@font-face { font-family: 'Cinzel'; font-style: normal; font-weight: 400;
  src: url('../assets/fonts/cinzel-400.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'Cinzel'; font-style: normal; font-weight: 600;
  src: url('../assets/fonts/cinzel-600.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'Spectral'; font-style: normal; font-weight: 300;
  src: url('../assets/fonts/spectral-300.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'Spectral'; font-style: normal; font-weight: 400;
  src: url('../assets/fonts/spectral-400.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'Spectral'; font-style: italic; font-weight: 400;
  src: url('../assets/fonts/spectral-400i.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'Spectral'; font-style: normal; font-weight: 600;
  src: url('../assets/fonts/spectral-600.woff2') format('woff2'); font-display: swap; }

:root {
  --font-display: 'Cinzel', 'Trajan Pro', Georgia, serif;
  --font-body: 'Spectral', Georgia, serif;
  --font-mono: 'Iosevka', 'DejaVu Sans Mono', ui-monospace, monospace;

  /* The HUD is position:absolute over the stage, so nothing else can learn
     its height by being laid out next to it. Declared ONCE here and read by
     both #hud and the touch cluster, which has to sit clear of it - the two
     were 192 and 116 in different places, and a cluster that guessed either
     would land on the Grace globe the moment the other applied. */
  --hud-h: 192px;
}

/* ANGELUS - style.css
   Diablo's HUD was carved stone and gold leaf on black. This one is the same
   silhouette, lit from the other side: pale stone plates, indigo recesses. */

:root {
  --dawn: #F4EDDF;
  --limestone: #DCD0BA;
  --worn: #B9A98E;
  --kindled: #F2C14E;
  --ember: #E08A2E;
  --vault: #4A4270;
  --deep: #2A2545;
  --void: #14122A;
  --grace: #7FD4C8;
  --corruption: #C4453A;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: #08070F;
  color: var(--limestone);
  font-family: "Iosevka", "DejaVu Sans Mono", ui-monospace, monospace;
  height: 100%;
  overflow: hidden;
}

#frame {
  position: relative;
  width: 100%;
  /* 100vh on a phone is the LARGEST viewport - the one with the browser
     chrome hidden - so at rest the bottom of the frame sits underneath the
     address bar and whatever is down there is simply cut off. That is why
     the HUD's lower row was clipped in portrait. dvh is the live value and
     tracks the chrome sliding in and out; the vh line stays first as the
     fallback for anything that does not know it. */
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: #08070F;
}

#stage {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  background: var(--void);
  border-bottom: none;
}

#view {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: auto;
  cursor: crosshair;
}

#frame:fullscreen { background: #08070F; }

#tierlabel {
  position: absolute;
  top: 10px; left: 14px;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(244, 237, 223, 0.5);
  display: flex;
  flex-direction: column;
  gap: 2px;
  pointer-events: none;
}
#tierlabel b { color: var(--kindled); font-weight: 500; }
#tierlabel i { font-style: normal; color: rgba(244, 237, 223, 0.72); }

/* ------------------------------------------------------------- overlays */

.overlay {
  position: absolute;
  inset: 0;
  background: rgba(11, 10, 24, 0.93);
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  text-align: center;
  z-index: 10;

  /* A CENTRED FLEXBOX THAT OVERFLOWS LOSES BOTH ENDS, AND CANNOT SCROLL.
     This is why character creation could not be finished on a phone: the
     panel is taller than a 620px screen, `justify-content: center` split the
     excess evenly above and below, and `overflow: visible` meant there was
     nothing to scroll. The title went off the top and BEGIN THE ASCENT went
     off the bottom, both unreachable, with no scrollbar to suggest otherwise.

     `safe center` is the fix for the top half specifically: plain `center`
     will push content past the START edge, which no amount of scrolling can
     reach. With `safe`, the moment it would overflow the box falls back to
     flex-start and everything stays reachable. Browsers that do not know the
     keyword keep the `center` declared above, so they lose nothing they had.

     overscroll-behavior stops a flick at the end of the panel from scrolling
     the page behind it, which on a phone reads as the game jumping. */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  justify-content: safe center;
  padding: 18px 12px;
}
/* or a long panel squashes its own children instead of scrolling */
.overlay > * { flex-shrink: 0; }
.overlay.show { display: flex; }

/* Char, Pack and Rites are opened mid-climb, so they sit over the world
   rather than replacing it - the loop keeps drawing while paused (only
   `update` is gated), so there is a live scene back there to see. The inner
   plates carry their own dark ground, which is what keeps the text legible
   over whatever happens to be on screen. */
#sheet, #inv, #rites {
  background: rgba(11, 10, 24, 0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.overlay h1 {
  margin: 0 0 18px;
  font-size: 68px;
  font-weight: 400;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  color: var(--kindled);
  text-shadow: 0 0 34px rgba(242, 193, 78, 0.45);
}
.overlay h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--dawn);
}
.overlay ul { list-style: none; margin: 0; padding: 0; }
.overlay li {
  font-size: 24px;
  letter-spacing: 0.16em;
  padding: 9px 20px;
  color: var(--worn);
  cursor: pointer;
  transition: color 0.12s;
}
.overlay li:hover { color: var(--dawn); }
.overlay li.dim { opacity: 0.32; pointer-events: none; }
.overlay li.dim.on { opacity: 1; pointer-events: auto; }

.menu-foot {
  margin-top: 22px;
  font-size: 12px;
  letter-spacing: 0.1em;
  color: rgba(185, 169, 142, 0.5);
}

/* Sized for reading, not for fitting. This panel was 13px body and 14px in
   the key table, on a dark ground, in a face with a small x-height - which is
   under the 16px a browser defaults to for body text and well under what a
   low-contrast palette wants. Anything asking to be READ rather than glanced
   at gets 16 here; the HUD can stay small because nobody reads the HUD. */
#help dl {
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: 10px 20px;
  max-width: 600px;
  text-align: left;
  font-size: 16px;
  line-height: 1.55;
  margin: 10px 0 0;
}
#help dt { color: var(--kindled); }
#help dd { margin: 0; color: var(--limestone); }
#help p {
  max-width: 620px;
  font-size: 16px;
  line-height: 1.75;
  /* was 0.7 alpha on a dark plate - contrast is half the legibility problem
     and it costs nothing to fix at the same time */
  color: rgba(228, 217, 197, 0.86);
}
#fallen p {
  max-width: 500px;
  font-size: 14px;
  line-height: 1.7;
  color: rgba(220, 208, 186, 0.78);
}

.overlay button {
  margin-top: 10px;
  background: var(--deep);
  border: 1px solid var(--vault);
  color: var(--limestone);
  font: inherit;
  font-size: 14px;
  letter-spacing: 0.12em;
  padding: 9px 22px;
  cursor: pointer;
}
.overlay button:hover { border-color: var(--kindled); color: var(--dawn); }
.overlay button:focus-visible { outline: 2px solid var(--kindled); outline-offset: 2px; }

/* ------------------------------------------------------------------ HUD */

#hud {
  /* Diablo's panel sat over the world rather than beside it, and taking the
     full width made ours read as a browser toolbar. Three-quarters, centred,
     with capped ends. */
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: min(82%, 1300px);
  flex: none;
  height: var(--hud-h);   /* tall enough that no head or wing is cropped */
  overflow: hidden;
  border-radius: 8px 8px 0 0;
  transition: transform 0.22s ease, opacity 0.18s ease;
  /* carved panel: a lit top edge, a shadowed underside, and a shallow
     inner shadow so the whole bar sits below the world rather than on it */
  box-shadow:
    inset 0 1px 0 rgba(225, 216, 200, 0.35),
    inset 0 2px 14px rgba(30, 24, 46, 0.42),
    0 -6px 18px rgba(8, 7, 15, 0.55);
  /* cut stone: block seams, a mortar line, and a brass rail along the top */
  background:
    linear-gradient(180deg,
      #C8971E 0px, #8A6410 3px,
      rgba(0,0,0,0.45) 4px, rgba(0,0,0,0) 9px),
    repeating-linear-gradient(90deg,
      rgba(0,0,0,0.18) 0 1px,
      rgba(255,255,255,0.02) 1px 2px,
      rgba(0,0,0,0) 2px 74px),
    repeating-linear-gradient(180deg,
      rgba(255,255,255,0.035) 0 1px,
      rgba(0,0,0,0.14) 1px 2px,
      rgba(0,0,0,0) 2px 34px),
    linear-gradient(180deg, #37305C 0%, #2A2447 40%, #191531 100%);
  border: 2px solid #4A340A;
  border-bottom: none;
  display: grid;
  /* plates | statue | globe | log | globe | statue | plates
     The log used to run the full width between the globes, which pushed them
     hard against the buttons. Narrowing it opens a column either side. */
  /* plates | mount (angel + orb) | log | mount | plates */
  grid-template-columns: 130px 256px minmax(0, 1fr) 256px 130px;
  /* stretch, not centre: the centre column sets its own height from the
     belt plus the log, and centring let the log overrun the panel */
  align-items: stretch;
  padding: 9px 14px;
  gap: 10px;
}

.side { display: flex; flex-direction: column; justify-content: center; gap: 4px; }
.side.right { align-items: flex-end; }

.plate {
  font-family: var(--font-display);
  font-weight: 700;
  -webkit-text-stroke: 0.4px currentColor;
  background:
    linear-gradient(180deg,
      #F0D089 0%, #C8971E 18%, #9A7014 52%, #6E4E0C 82%, #C8971E 100%);
  border: 1px solid #4A340A;
  border-radius: 4px;
  outline: 1px solid rgba(232, 184, 74, 0.4);
  outline-offset: 1px;
  color: #37280A;
  /* NOT `font: inherit` - the shorthand wipes font-family, which was silently
     dropping these back to the mono face at weight 400. The vellum theme only
     looked better because a later rule happened to re-set it. */
  font-size: 15px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 8px 0;
  width: 118px;
  cursor: pointer;
  text-shadow: 0 1px 0 rgba(255, 240, 190, 0.75);
  /* a thicker bevel and a deeper underside, so they read as cast metal
     rather than as coloured rectangles */
  box-shadow:
    inset 0 2px 0 rgba(255, 246, 214, 0.85),
    inset 0 -3px 4px rgba(58, 42, 6, 0.55),
    inset 0 0 0 1px rgba(88, 62, 10, 0.5),
    0 3px 4px rgba(8, 7, 15, 0.65);
  transition: filter 0.12s, transform 0.06s;
}
.plate:hover { filter: brightness(1.14); }
.plate:active { transform: translateY(1px); filter: brightness(0.94); }
.plate:focus-visible { outline: 2px solid var(--kindled); outline-offset: 2px; }

/* globes */
.globe {
  position: relative;
  align-self: center;
  width: 108px;
  height: 108px;
  border-radius: 50%;
  overflow: hidden;
  background: radial-gradient(circle at 50% 62%, #1D1A38 0%, #0C0B1B 100%);
  border: 3px solid #8A6410;
  justify-self: center;
  /* the ring is brass, the ball sits proud of it */
  box-shadow:
    0 0 0 1px #4A340A,
    0 0 0 4px #2A2447,
    0 0 0 5px rgba(200, 151, 30, 0.35),
    0 3px 8px rgba(8, 7, 15, 0.75);
}

/* glass over the well: a highlight above, a darkening below */
.globe::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  z-index: 3;
  /* a sphere: a tight specular near the top-left, and the limb darkening
     that makes a circle read as a ball rather than a disc */
  /* The broad second highlight ran straight across the middle of the globe
     and read as a shaded bar sitting on top of the liquid. Keep the tight
     specular and the limb darkening; drop the band. */
  background:
    radial-gradient(circle 14px at 33% 22%, rgba(255,255,255,0.5), rgba(255,255,255,0) 100%),
    radial-gradient(circle at 50% 50%, rgba(8,7,15,0) 58%, rgba(8,7,15,0.5) 100%);
}
.globe .fill {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 100%;
  transition: height 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
  z-index: 1;
}
.globe .fill::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 3px;
  background: rgba(255, 250, 240, 0.55);
  box-shadow: 0 2px 5px rgba(255, 250, 240, 0.28);
}
.vit .fill { background: linear-gradient(180deg, #F2C14E 0%, #B87A16 100%); }
.grace .fill { background: linear-gradient(180deg, #EFFAF8 0%, #7FD4C8 100%); }

.orb-label {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  color: #FFFAF0;
  text-shadow: 0 0 4px rgba(11, 10, 24, 0.95), 0 1px 2px rgba(11, 10, 24, 0.9);
}
.orb-name {
  position: absolute;
  z-index: 4;
  bottom: 7px; left: 0; right: 0;
  text-align: center;
  font-size: 8px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  /* sits over pale teal liquid, so it needs a real dark halo, not a glow */
  color: rgba(255, 252, 246, 0.95);
  font-weight: 700;
  text-shadow:
    0 1px 2px rgba(8, 7, 15, 0.95),
    0 0 5px rgba(8, 7, 15, 0.9),
    0 0 10px rgba(8, 7, 15, 0.7);
}

/* center column */
.center { display: flex; flex-direction: column; gap: 6px; min-height: 0; }

.belt { display: flex; gap: 4px; justify-content: center; }
.slot {
  position: relative;
  width: 40px; height: 40px;
  border-radius: 2px;
  /* colour and image kept separate: the icon is set inline on
     background-image, and without an explicit colour underneath, losing the
     gradient let the panel stone show straight through the socket */
  background-color: #0C0A1A;
  background-image: linear-gradient(180deg, #0A0914 0%, #16132A 100%);
  background-size: 32px 32px;
  background-position: center;
  background-repeat: no-repeat;
  border: 2px solid #6E4E0C;
  box-shadow:
    inset 0 2px 7px rgba(0, 0, 0, 0.98),
    inset 0 0 0 1px rgba(200, 151, 30, 0.22),
    0 1px 2px rgba(8, 7, 15, 0.6);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  color: rgba(220, 208, 186, 0.35);
}
.slot b {
  position: absolute;
  bottom: 1px; right: 3px;
  font-size: 9px;
  font-weight: 400;
  color: rgba(220, 208, 186, 0.4);
}
.slot.filled {
  border-color: var(--kindled);
  color: var(--kindled);
  cursor: pointer;
  /* a filled socket glows a little - a stocked belt should look stocked */
  box-shadow:
    inset 0 2px 5px rgba(8, 7, 15, 0.7),
    inset 0 0 10px rgba(242, 193, 78, 0.18),
    0 0 11px rgba(242, 193, 78, 0.42);
}

.log {
  flex: 1;
  background: linear-gradient(180deg, #08070F 0%, #100E22 100%);
  border: 2px solid #6E4E0C;
  border-radius: 2px;
  box-shadow:
    inset 0 3px 10px rgba(0, 0, 0, 0.95),
    inset 0 0 0 1px rgba(200, 151, 30, 0.18),
    0 1px 2px rgba(8, 7, 15, 0.6);
  padding: 5px 10px;
  font-size: 11.5px;
  line-height: 1.55;
  overflow: hidden;
  display: flex;
  flex-direction: column-reverse;
  color: rgba(220, 208, 186, 0.72);
}
.log div:first-child { color: var(--dawn); }

.skills { display: flex; gap: 5px; margin-top: 4px; align-self: flex-end; }

.skill {
  width: 54px; height: 54px;
  border-radius: 2px;
  background: linear-gradient(180deg, #1C1936 0%, #0C0B1B 100%);
  border: 2px solid #6E4E0C;
  box-shadow:
    inset 0 2px 6px rgba(8, 7, 15, 0.85),
    inset 0 0 0 1px rgba(200, 151, 30, 0.18);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(220, 208, 186, 0.45);
}
.skill.ready {
  border-color: var(--kindled);
  color: var(--kindled);
  box-shadow:
    inset 0 2px 6px rgba(8, 7, 15, 0.6),
    0 0 12px rgba(242, 193, 78, 0.35);
}
.skill small { font-size: 12px; }

#hint {
  position: absolute;
  bottom: 10px; left: 14px;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(244, 237, 223, 0.32);
  pointer-events: none;
  text-align: left;
  line-height: 1.6;
}

.bells { display: flex; gap: 5px; margin: 3px 0 1px; }
.bell-pip {
  width: 9px; height: 9px;
  border: 1px solid rgba(242, 193, 78, 0.55);
  border-radius: 50%;
}
.bell-pip.rung { background: var(--kindled); border-color: var(--kindled); }
#objective { color: rgba(242, 193, 78, 0.6); }
#objective.open { color: rgba(244, 237, 223, 0.85); }

/* ------------------------------------------------ character creation */

.field {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--worn);
}
.field input {
  background: #100E22;
  border: 1px solid var(--vault);
  color: var(--dawn);
  font: inherit;
  font-size: 15px;
  letter-spacing: 0.08em;
  text-transform: none;
  padding: 8px 12px;
  width: 300px;
}
.field input:focus {
  outline: none;
  border-color: var(--kindled);
}
.field input::placeholder { color: rgba(185, 169, 142, 0.35); }
.field.seed input { width: 220px; }

.field-error {
  font-size: 12px;
  color: var(--corruption);
  min-height: 15px;
  letter-spacing: 0.06em;
}

/* WRAPS. `width: 232px` below is a wish, not a measurement: the card's own
   content gives it a min-width of about 359px, and flex-shrink cannot go
   under that. So three cards need ~1100px and overflowed silently on
   anything narrower - a landscape phone at 844px lost two of them off the
   sides with no horizontal scroll to reach them. Wrapping costs desktop
   nothing (they still fit one row at 1100px) and is the difference between
   three pickable classes and one everywhere else. */
.classes {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: 6px 0 4px;
  justify-content: center;
  max-width: 100%;
}

.class-card {
  width: 232px;
  min-width: 0;          /* or the content's own width wins and it never wraps */
  display: flex;
  flex-direction: column;
  background: #14122A;
  border: 1px solid var(--vault);
  border-radius: 0;
  padding: 16px 16px 18px;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
}
.class-card:hover { border-color: var(--worn); }
.class-card.on { border-color: var(--accent, #F2C14E); background: #1B1834; }
.class-card:focus-visible { outline: 2px solid var(--kindled); outline-offset: 2px; }

.class-card h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--accent, #F2C14E);
}
.class-card .tag {
  display: block;
  margin: 3px 0 10px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}
.class-card p {
  flex: 1;
  margin: 0 0 14px;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.6;
  color: rgba(220, 208, 186, 0.78);
}
.class-card .bars { display: flex; flex-direction: column; gap: 5px; }
.class-card .bar {
  display: grid;
  grid-template-columns: 54px 1fr;
  align-items: center;
  gap: 8px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.65);
}
.class-card .track { display: block; height: 5px; background: #2A2545; }
.class-card .fillbar { display: block; height: 5px; background: var(--accent, #F2C14E); }

.row { display: flex; gap: 12px; margin-top: 6px; }
.overlay button.primary {
  background: var(--kindled);
  border-color: var(--kindled);
  color: #2A2545;
}
.overlay button.primary:hover { background: #FFD873; color: #2A2545; }

/* ------------------------------------------------------ character sheet */

.sheet-sub {
  margin: 0;
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(242, 193, 78, 0.75);
}
#sheet dl {
  display: grid;
  grid-template-columns: auto auto;
  gap: 9px 44px;
  margin: 14px 0 4px;
  font-size: 14px;
  text-align: left;
}
#sheet dt { color: rgba(185, 169, 142, 0.75); }
#sheet dd { margin: 0; color: var(--dawn); text-align: right; }

/* --------------------------------------------------------- level bar */

.levelbar {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  display: flex;
  align-items: center;
}
.levelbar .xptrack {
  flex: 1;
  height: 4px;
  background: #16142B;
  display: block;
}
/* Bone, not the Grace teal.
   XP and Grace are unrelated systems and they were sharing one colour, so the
   bar read as if it were tracking the Grace globe two inches away from it.
   Levelling is not a resource - nothing is spent, nothing is gathered - so it
   gets a neutral that competes with neither globe and sits with the HUD's own
   stonework. */
.levelbar .xpfill {
  display: block;
  height: 4px;
  background: var(--limestone);
  width: 0;
  transition: width 0.25s ease-out;
}
.levelbar .lvl {
  position: absolute;
  left: 14px; top: 7px;
  font-size: 11px;
  letter-spacing: 0.14em;
  color: rgba(220, 208, 186, 0.5);
}
.levelbar .lvl::before { content: 'LVL '; }
.levelbar .lvl.unspent { color: var(--kindled); }
.levelbar .lvl.unspent::after { content: ' •'; }

/* ------------------------------------------------- attributes on sheet */

.sheet-body {
  display: grid;
  /* portrait | attributes | stats */
  grid-template-columns: 210px 400px auto;
  gap: 28px;
  align-items: start;
  margin: 16px 0 4px;
  text-align: left;
}

/* The pilgrim's own face beside their numbers. Cropped to head and shoulders
   at this size - a whole figure 210px wide is a smudge - and faded at the
   bottom so it sits into the panel rather than ending on a hard edge. */
.sheet-portrait {
  height: 300px;
  border: 1px solid rgba(200, 151, 30, 0.35);
  border-radius: 3px;
  background-color: rgba(14, 12, 28, 0.94);
  background-size: cover;
  background-position: 50% 12%;
  background-repeat: no-repeat;
  box-shadow:
    inset 0 1px 0 rgba(200, 151, 30, 0.14),
    inset 0 -40px 46px -20px rgba(14, 12, 28, 0.95),
    0 2px 6px rgba(8, 7, 15, 0.65);
}

@media (max-width: 1180px) {
  .sheet-body { grid-template-columns: 160px 340px auto; gap: 20px; }
  .sheet-portrait { height: 250px; }
}

.xp-row {
  display: grid;
  grid-template-columns: 84px 1fr auto;
  align-items: center;
  gap: 10px;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.7);
}
.xp-track { display: block; height: 6px; background: #2A2545; }
.xp-fill { display: block; height: 6px; background: var(--limestone); }
.xp-num { color: var(--dawn); letter-spacing: 0.06em; }

.points-line {
  margin: 12px 0 10px;
  font-size: 13px;
  color: rgba(185, 169, 142, 0.7);
}
.points-line b { color: var(--kindled); font-weight: 400; }

.attrs { display: flex; flex-direction: column; gap: 10px; }
.attr { border-left: 2px solid var(--vault); border-radius: 0; padding: 2px 0 2px 12px; }
.attr-head { display: flex; align-items: center; gap: 10px; }
.attr-name {
  font-size: 14px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dawn);
  flex: 1;
}
.attr-val { font-size: 16px; color: var(--kindled); min-width: 22px; text-align: right; }
.attr-plus:hover:not(:disabled) { border-color: var(--kindled); color: var(--kindled); }
.attr-table .attr-plus:disabled { opacity: 0.28; cursor: default; }
.attr-plus:focus-visible { outline: 2px solid var(--kindled); outline-offset: 2px; }

/* ------------------------------------------------------------- roster */

.roster-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 640px;
  max-height: 330px;
  overflow-y: auto;
  margin: 6px 0 2px;
}
.roster-empty {
  font-size: 13px;
  color: rgba(185, 169, 142, 0.55);
  padding: 26px 0;
}
.pilgrim {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 14px;
  background: #14122A;
  border: 1px solid var(--vault);
  border-radius: 0;
  padding: 11px 14px;
  text-align: left;
}
.pilgrim:hover { border-color: var(--worn); }
.pilgrim .who { display: flex; flex-direction: column; gap: 3px; }
.pilgrim .pname {
  font-size: 16px;
  letter-spacing: 0.1em;
  color: var(--dawn);
}
.pilgrim .pmeta {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}
.pilgrim button {
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.12em;
  padding: 7px 16px;
  background: var(--deep);
  border: 1px solid var(--vault);
  color: var(--limestone);
  cursor: pointer;
}
.pilgrim button.go { background: var(--kindled); border-color: var(--kindled); color: #2A2545; }
.pilgrim button:hover { border-color: var(--kindled); }
.pilgrim button.del:hover { border-color: var(--corruption); color: var(--corruption); }
.pilgrim button:focus-visible { outline: 2px solid var(--kindled); outline-offset: 2px; }

.roster-note {
  font-size: 12px;
  color: var(--text-warning, #E0A02E);
  min-height: 16px;
}

/* ---------------------------------------------------------- dev panel */

.dev-grid {
  display: grid;
  grid-template-columns: auto auto;
  gap: 10px 14px;
  align-items: center;
  margin-bottom: 14px;
}
.dev-grid .field input { width: 110px; }
.dev-row { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
#dev-panel button, .dev-grid button {
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.12em;
  padding: 8px 16px;
  background: var(--deep);
  border: 1px solid var(--vault);
  color: var(--limestone);
  cursor: pointer;
}
#dev-panel button:hover, .dev-grid button:hover { border-color: var(--kindled); color: var(--dawn); }
.dev-tiers {
  margin: 16px auto 4px;
  max-width: 560px;
  font-size: 11.5px;
  line-height: 1.8;
  color: rgba(185, 169, 142, 0.6);
}

.volume {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}
.volume input { width: 190px; accent-color: var(--kindled); }
#mute-hint.off { color: rgba(196, 69, 58, 0.75); }

/* Theme picker, beside the volume. Two buttons rather than one toggle: a
   toggle labelled with the theme you are not in is a riddle every time, and
   this sits in front of a player who may only ever visit it once. */
.theme-pick, .chant-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}

/* These live inside .overlay, which styles every button as a gold plate far
   later in this file and at the same weight - so a plain `.theme-pick button`
   rule here loses every property they share, silently. Hence the extra class.
   Matching the plate is the right answer regardless: a control that does not
   look like the rest of the menu reads as broken. The chosen theme is the lit
   plate and the other is dimmed, which is legible without reading either
   label. The chant toggle is listed alongside for exactly the same reason -
   adding `.chant-pick` to these selectors rather than writing its own rule is
   what keeps it from losing to `.overlay button` the way the first attempt at
   the theme buttons did. */
.menu-bar .theme-pick button,
.menu-bar .chant-pick button {
  margin-top: 0;
  font-size: 12px;
  padding: 6px 15px;
}
.menu-bar .theme-pick button:not(.on),
.menu-bar .chant-pick button:not(.on) {
  filter: brightness(0.5) saturate(0.45);
}
.menu-bar .theme-pick button:not(.on):hover,
.menu-bar .chant-pick button:not(.on):hover {
  filter: brightness(0.78) saturate(0.7);
}
.menu-bar .theme-pick button.on,
.menu-bar .chant-pick button.on {
  outline: 2px solid rgba(255, 246, 214, 0.6);
  outline-offset: 2px;
}

/* ------------------------------------------------------------- the pack */

.inv-wrap {
  display: grid;
  /* the doll column is wider than the old worn-list it replaced, and the
     slots sit outside the figure - at 190px they overhung the pack grid */
  grid-template-columns: 248px auto 260px;
  gap: 30px;
  align-items: start;
  margin: 14px 0 6px;
  text-align: left;
}

.worn { display: flex; flex-direction: column; gap: 7px; }
.worn-slot {
  background: #14122A;
  border: 1px solid var(--vault);
  border-radius: 0;
  padding: 8px 11px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.worn-slot.has { cursor: pointer; }
.worn-slot:hover { background: #1B1834; }
.worn-label {
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.5);
}
.worn-name { font-size: 13px; color: rgba(220, 208, 186, 0.55); }

.pack-grid {
  display: grid;
  grid-template-columns: repeat(6, 62px);
  gap: 5px;
}
.cell {
  height: 46px;
  background: #100E22;
  border: 1px solid var(--vault);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  letter-spacing: 0.06em;
  color: rgba(185, 169, 142, 0.3);
  cursor: pointer;
  text-align: center;
  padding: 2px;
}
.cell.has { background: #16142B; }
.cell:hover { background: #221E3C; }
.pack-foot {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  white-space: nowrap;
  margin-top: 8px;
  font-size: 11px;
  letter-spacing: 0.08em;
  color: rgba(185, 169, 142, 0.5);
}

.item-detail {
  background: #14122A;
  border: 1px solid var(--vault);
  padding: 14px 16px;
  min-height: 190px;
}
.detail-name { font-size: 16px; letter-spacing: 0.08em; color: var(--dawn); }
.detail-sub {
  font-size: 10.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.55);
  margin-top: 3px;
}
.detail-blurb { font-size: 12.5px; color: rgba(220, 208, 186, 0.75); margin-top: 10px; }
.detail-affixes { list-style: none; margin: 12px 0 0; padding: 0; }
.detail-affixes li { font-size: 13px; color: var(--kindled); padding: 2px 0; }
.detail-affixes li.none { color: rgba(185, 169, 142, 0.4); }
.detail-vs { margin-top: 12px; font-size: 11.5px; color: rgba(185, 169, 142, 0.6); }
.detail-hint { margin-top: 12px; font-size: 11px; color: rgba(185, 169, 142, 0.42); }

/* ------------------------------------------------------------ paper doll */
/* Worn gear sits on a figure instead of in a list, so where a thing goes is
   shown rather than named. The silhouette is drawn in CSS - no art needed for
   it, and it stays legible in both themes. */
.doll {
  position: relative;
  width: 248px; height: 340px;
  background: rgba(11, 10, 24, 0.6);
  border: 1px solid var(--vault);
}
/* The painted figure. It sits behind the slots, so it is dimmed hard - at
   full strength the robe's widest part is brighter than the slot backgrounds
   and the shoulder slots start to disappear into it. */
.doll-fig.painted {
  position: absolute; left: 50%; top: 16px;
  width: 148px; height: 308px; margin-left: -74px;
  background-position: center; background-repeat: no-repeat;
  background-size: contain;
  opacity: 0.34;
  clip-path: none;
  -webkit-mask: none; mask: none;
}
.doll-fig {
  position: absolute; left: 50%; top: 26px;
  width: 96px; height: 250px; margin-left: -48px;
  background:
    radial-gradient(ellipse 26px 30px at 50% 30px, rgba(122,106,74,0.5) 99%, transparent 100%),
    linear-gradient(rgba(122,106,74,0.42), rgba(122,106,74,0.42));
  -webkit-mask:
    radial-gradient(ellipse 26px 30px at 50% 30px, #000 99%, transparent 100%),
    linear-gradient(#000, #000);
  clip-path: polygon(
    38% 0%, 62% 0%, 70% 9%, 88% 17%, 96% 46%, 84% 50%, 78% 34%,
    76% 62%, 66% 100%, 54% 100%, 50% 70%, 46% 100%, 34% 100%, 24% 62%,
    22% 34%, 16% 50%, 4% 46%, 12% 17%, 30% 9%);
}
.doll-slot {
  position: absolute;
  width: 54px; height: 54px;
  display: grid; place-items: center;
  background: rgba(20, 18, 42, 0.86);
  border: 1px solid var(--vault);
  border-radius: 3px;
  cursor: pointer;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.doll-slot .doll-label {
  font-family: var(--font-display);
  font-size: 8px; letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.4);
}
.doll-slot:hover { border-color: var(--worn); }
/* Kept inside the panel, not hung off its edges - at 10px the weapon slot
   was half outside the border and clipped by the column beside it. */
.slot-weapon  { left: 16px;  top: 118px; }
.slot-ward    { right: 16px; top: 118px; }
.slot-raiment { left: 50%; margin-left: -27px; top: 176px; }
.slot-sigil   { right: 16px; top: 30px; }
/* only the slot a dragged item could actually go in lights up */
.doll.dropping .doll-slot { opacity: 0.4; }
.doll.dropping .doll-slot.ok {
  opacity: 1;
  border-color: var(--kindled);
  box-shadow: 0 0 12px rgba(242, 193, 78, 0.5);
}
.doll.dropping .doll-slot.ok.over {
  border-color: var(--dawn);
  box-shadow: 0 0 18px rgba(242, 193, 78, 0.9);
}
.pack-grid .cell[draggable="true"] { cursor: grab; }

/* -------------------------------------------------------- character plate */
.sheet-plate {
  /* Three columns, and nothing scrolls. Stacking the readout under the
     attributes pushed Back off the bottom of the screen; side by side it all
     fits on one page, which is what it was before and what it should be. */
  max-width: 1030px;
  margin: 8px auto 12px;
  padding: 16px 20px 14px;
  background: rgba(11, 10, 24, 0.72);
  border: 1px solid var(--worn);
  box-shadow: inset 0 0 40px rgba(4, 3, 10, 0.55);
  text-align: left;
}
.sheet-top {
  display: grid;
  grid-template-columns: 158px minmax(0, 1fr) minmax(0, 0.92fr);
  gap: 26px;
  align-items: start;
}
.sheet-attrs { font-family: var(--font-display); }

.plate-rows { display: grid; gap: 2px; margin-bottom: 10px; }
.plate-row { display: flex; align-items: baseline; gap: 10px; }
.plate-row .pl {
  flex: 1;
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.62);
}
.plate-row .pv { font-size: 15px; color: var(--dawn); }
.plate-row .pv.big { font-size: 22px; color: var(--kindled); }

.xp-track.wide {
  display: block; height: 5px; margin: 0 0 14px;
  background: rgba(20, 18, 42, 0.9); border: 1px solid var(--vault);
}
.xp-track.wide .xp-fill { display: block; height: 100%; background: var(--kindled); }

/* Attributes as a table: name, base, and the button to raise it. Everything
   lines up in a column with everything it should be compared against. */
.attr-table { display: grid; gap: 3px; }
.attr-head-row, .attr-row {
  display: grid; grid-template-columns: 1fr 56px 44px;
  align-items: center; gap: 8px;
}
.attr-table { grid-template-columns: 1fr; }
.attr-head-row span {
  font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.45); text-align: center;
}
.attr-row { padding: 5px 0 3px; border-bottom: none; }
/* The blurb is back on the page. It was moved to a tooltip to save height and
   that was the wrong trade - it is the only thing that says what a point in
   Devotion actually buys you, and nobody hovers to find out. */
.attr-note {
  grid-column: 1 / -1;
  margin: 0 0 5px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(122, 106, 74, 0.22);
  font-family: var(--font-body);
  font-size: 11.5px;
  line-height: 1.42;
  letter-spacing: 0;
  text-transform: none;
  color: rgba(185, 169, 142, 0.62);
}
.attr-note b { color: rgba(242, 193, 78, 0.75); font-weight: 400; }
.attr-row .attr-name {
  font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(220, 208, 186, 0.8);
}
.attr-row .attr-val {
  text-align: center; font-size: 16px; color: var(--dawn);
  background: rgba(20, 18, 42, 0.8); border: 1px solid var(--vault); padding: 1px 0;
}
/* `.overlay button` is defined near the end of this file and styles every
   button as a gold plate. A single-class rule here loses to it at equal-ish
   weight, so this carries its parent. Third time this trap has bitten. */
/* Big enough to hit without aiming, and it repeats while held - see the
   handler in ui.js. Twenty points should not be twenty clicks. */
.attr-table .attr-plus {
  width: 40px; height: 34px; padding: 0; margin: 0;
  font-size: 22px; font-weight: 700; line-height: 1;
  user-select: none; -webkit-user-select: none;
  color: #FFE9C6; background: linear-gradient(#C4453A, #7E241D);
  border: 1px solid #E8695C; border-radius: 2px; cursor: pointer;
}
.attr-table .attr-plus:disabled {
  opacity: 0.22; cursor: default;
  background: rgba(60, 52, 44, 0.6); border-color: var(--vault); color: var(--vault);
}
.points-line {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid rgba(122, 106, 74, 0.3);
  font-family: var(--font-display); font-size: 11px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.62);
}
.points-line b { font-size: 20px; color: rgba(185, 169, 142, 0.5); }
.points-line.hot b { color: #E8695C; }

/* Now a column of its own beside the attributes, not a block beneath them. */
.sheet-stats {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 14px; margin: 0; padding: 0 0 0 22px; width: 100%;
  border-top: none;
  border-left: 1px solid rgba(122, 106, 74, 0.3);
}
.sheet-stats dt {
  font-family: var(--font-display); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.55);
}
.sheet-stats dd {
  margin: 0; text-align: right; font-size: 13px; color: rgba(220, 208, 186, 0.9);
}

/* ------------------------------------------------------------- level up */
/* Bright enough to catch the eye mid-fight, and it does not go away on its
   own - it clears when the point is actually spent, which is the thing it is
   asking you to do. */
/* `.levelbar .lvl` is absolutely positioned at the left of this bar, so a
   button sitting in the flex row landed underneath it. This takes the free
   right end instead. */
/* Above the panel, on the world, not inside the frame. It is an interruption
   and it should look like one. */
#levelup {
  /* Sits on the world above the panel, not inside it. #hud is overflow:hidden
     so anything parented there and pushed above its top edge is simply
     clipped away - which is exactly what happened to two earlier attempts. */
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 200px;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px 7px 16px;
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.2em;
  color: #FFF0DC;
  background: linear-gradient(#C4453A, #7E241D);
  border: 1px solid #E8695C;
  border-radius: 2px;
  cursor: pointer;
  box-shadow: 0 0 12px rgba(196, 69, 58, 0.55);
}
#levelup[hidden] { display: none; }
#levelup:hover { transform: translateX(-50%) scale(1.04); }
#levelup b {
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  color: #7E241D;
  background: #FFE9C6;
  border-radius: 2px;
}

#levelup.beat { animation: levelbeat 0.5s ease-out 3; }
@keyframes levelbeat {
  0%, 100% { box-shadow: 0 0 12px rgba(196, 69, 58, 0.55); }
  50%      { box-shadow: 0 0 26px rgba(232, 105, 92, 0.95); }
}

/* ----------------------------------------------------------- item faces */
/* The picture is set inline, not through a custom property - url() in a
   custom property resolves against the stylesheet, which would look for
   css/assets/item_blade.png. Same trap as the relic and rite sockets. */
.item-face {
  display: block;
  width: 100%;
  height: 100%;
  background-size: 86%;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 3px;
}
.item-face.small { width: 46px; height: 46px; flex: 0 0 46px; }

/* --------------------------------------------------------------- credits */
.credits-cols {
  display: grid; grid-template-columns: 1fr 1fr; gap: 34px;
  max-width: 840px; margin: 0 auto; text-align: left;
}
.credits-cols h3 {
  margin: 20px 0 8px; font-family: var(--font-display);
  font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--kindled);
}
.credits-cols section > h3:first-child { margin-top: 0; }
/* The Chapter House. Long names in a narrow column wrap into a mess in the
   two-up credit-line layout, so these stack: role above, name below, and the
   name gets the full width. */
.chapter-line {
  margin: 0 0 10px;
  padding-bottom: 8px;
  border-bottom: 1px dotted rgba(122, 106, 74, 0.28);
}
.chapter-line span {
  display: block;
  font-family: var(--font-display);
  font-size: 9.5px; letter-spacing: 0.18em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.55);
  margin-bottom: 2px;
}
.chapter-line b {
  display: block;
  font-family: var(--font-display);
  font-size: 14px; letter-spacing: 0.02em;
  color: var(--dawn); font-weight: 400;
}

.credit-tools {
  margin: 0; font-family: var(--font-body); font-size: 13px; line-height: 1.7;
  color: rgba(220, 208, 186, 0.8);
}

/* ----------------------------------------------------------------- lobby */
.lobby-panel {
  width: min(92%, 720px); margin: 0 auto; padding: 28px 34px 24px;
  background: rgba(11, 10, 24, 0.92);
  border: 1px solid var(--worn);
  box-shadow: 0 20px 70px rgba(4, 3, 10, 0.8);
  text-align: left;
}
.lobby-sub {
  margin: -6px 0 20px; font-family: var(--font-display);
  font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}
.lobby-row {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: center; gap: 14px;
  padding: 11px 0; border-bottom: 1px solid rgba(122, 106, 74, 0.22);
}
.lobby-name { font-family: var(--font-display); font-size: 15px; color: var(--dawn); }
.lobby-meta {
  font-family: var(--font-display); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.55);
}
.lobby-empty {
  padding: 22px 0; font-family: var(--font-body); font-size: 14px;
  color: rgba(185, 169, 142, 0.6);
}
.lobby-field {
  display: flex; align-items: center; gap: 10px; margin: 6px 0;
}
.lobby-field span {
  width: 96px; font-family: var(--font-display); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}
.lobby-field input {
  flex: 1; padding: 7px 10px; font: inherit; font-size: 14px;
  color: var(--dawn); background: #14122A;
  border: 1px solid var(--vault); border-radius: 2px;
}
.lobby-err {
  min-height: 18px; margin: 12px 0 4px;
  font-family: var(--font-body); font-size: 13px; color: #E8695C;
}
.lobby-actions { display: flex; gap: 12px; justify-content: flex-end; }
.lobby-code {
  font-family: var(--font-display); font-size: 26px; letter-spacing: 0.3em;
  color: var(--kindled); padding: 6px 0 2px;
}
.lobby-hr { height: 1px; background: rgba(122,106,74,0.3); margin: 18px 0 14px; }

/* the little roster that sits on the HUD during a shared climb */
#party {
  position: absolute; left: 14px; top: 74px; z-index: 5;
  display: flex; flex-direction: column; gap: 4px;
  pointer-events: none;
}
#party[hidden] { display: none; }
.party-row {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--font-display); font-size: 11px;
  letter-spacing: 0.12em;
  color: rgba(220, 208, 186, 0.8);
  text-shadow: 0 1px 3px #000;
}
.party-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--kindled); }
.party-row.lagging { color: rgba(185,169,142,0.45); }
.party-row.lagging .party-dot { background: rgba(185,169,142,0.4); }

/* ---------------------------------------------------------------- ending */
.ending-panel {
  max-width: 620px; margin: 0 auto; padding: 34px 40px 30px;
  background: rgba(11, 10, 24, 0.9);
  border: 1px solid var(--worn);
  box-shadow: 0 20px 70px rgba(4, 3, 10, 0.8);
}
.ending-verdict {
  margin: 4px 0 26px;
  font-family: var(--font-display);
  font-size: 16px; line-height: 1.65;
  color: rgba(226, 214, 190, 0.9);
}
.ending-tally { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.ending-tally div { display: flex; flex-direction: column; gap: 4px; }
.ending-tally b { font-family: var(--font-display); font-size: 30px; color: var(--kindled); }
.ending-tally span {
  font-family: var(--font-display); font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}

/* ----------------------------------------------------------------- intro */
/* The film plays over black with nothing else on screen. It is the one place
   in the game where the UI gets out of the way entirely. */
/* The film sits over the game rather than replacing it, the same as Char,
   Pack and Rites - you can see where you are behind it. */
#intro.overlay {
  background: rgba(11, 10, 24, 0.72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  gap: 12px;
}
.intro-frame {
  position: relative;
  width: min(88%, 1180px);
  /* The hairline is painted background showing through 1px of padding, not a
     border. A video's height is almost always fractional - here 664.625px -
     so a 1px border on the bottom edge lands on a half pixel and antialiases
     to about half intensity, while the top edge sits on a whole pixel and
     draws solid. That is why the line appeared on three sides and not the
     fourth. A background fills fractional edges completely. */
  padding: 1px;
  background: var(--worn);
  box-shadow: 0 24px 70px rgba(4, 3, 10, 0.85);
  line-height: 0;
}
#intro-video { display: block; width: 100%; height: auto; background: #000; }
.intro-hint {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: rgba(226, 214, 190, 0.9);
  background: rgba(11, 10, 24, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s;
}
.intro-hint.show { opacity: 1; }
.intro-bar {
  display: flex;
  align-items: center;
  gap: 22px;
  width: min(88%, 1180px);
  justify-content: flex-end;
}
.intro-keys {
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.5);
}
.intro-skip {
  padding: 7px 18px;
  font-family: var(--font-display);
  font-size: 12px; letter-spacing: 0.22em; text-transform: uppercase;
  color: rgba(220, 208, 186, 0.55);
  background: rgba(11, 10, 24, 0.6);
  border: 1px solid rgba(122, 106, 74, 0.5);
  border-radius: 2px;
  cursor: pointer;
}
.intro-skip:hover { color: var(--kindled); border-color: var(--kindled); }

/* ------------------------------------------------------------------ boon */
/* Offered over the Landing you are standing on, like the opening - you should
   be able to see the safe ground while you choose what to carry off it. */
#boon.overlay { background: rgba(11, 10, 24, 0.62); backdrop-filter: blur(2px); }
.boon-panel {
  max-width: 760px; margin: 0 auto; padding: 26px 34px 30px;
  background: rgba(11, 10, 24, 0.86);
  border: 1px solid var(--worn);
  box-shadow: 0 18px 60px rgba(4, 3, 10, 0.75);
}
.boon-sub {
  margin: -4px 0 22px; font-family: var(--font-display);
  font-size: 12px; letter-spacing: 0.2em; text-transform: uppercase;
  color: rgba(185, 169, 142, 0.6);
}
.boon-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.boon-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 18px 16px; text-align: left; cursor: pointer;
  background: #14122A; border: 1px solid var(--vault);
  transition: border-color 0.12s, transform 0.12s, box-shadow 0.12s;
}
.boon-card:hover {
  border-color: var(--kindled); transform: translateY(-2px);
  box-shadow: 0 0 18px rgba(242, 193, 78, 0.22);
}
.boon-name {
  font-family: var(--font-display); font-size: 17px;
  letter-spacing: 0.06em; color: var(--kindled);
}
.boon-blurb {
  font-family: var(--font-body); font-size: 13.5px; line-height: 1.5;
  color: rgba(220, 208, 186, 0.82);
}

/* --------------------------------------------------------------- opening */
/* The opening sits over the village rather than replacing it - you can see
   where you are standing while you are told why. Hence the panel rather than
   a full-bleed screen, and hence the transparency. */
#opening.overlay {
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#opening .opening-panel {
  max-width: 660px;
  margin: 0 auto;
  padding: 30px 40px 26px;
  background: rgba(11, 10, 24, 0.82);
  border: 1px solid var(--worn);
  box-shadow: 0 0 0 1px rgba(11, 10, 24, 0.8),
              0 18px 60px rgba(4, 3, 10, 0.75),
              inset 0 0 60px rgba(4, 3, 10, 0.5);
}
#opening h2 { margin-top: 0; }
.opening-body { text-align: left; }
/* All of it in the display face, not the mono body face. */
.opening-body p {
  margin: 0 0 15px;
  font-family: var(--font-display);
  font-size: 17px;
  line-height: 1.66;
  letter-spacing: 0.012em;
  color: rgba(226, 214, 190, 0.9);
}
.opening-body .opening-last {
  margin: 22px 0 0;
  color: var(--kindled);
  font-style: italic;
}

/* ----------------------------------------------------------------- rites */

.rite-list {
  display: grid;
  /* wider than they were, to pay for the roundels. The text column actually
     comes out roomier than before despite the icon, because the card grew
     more than the socket did. */
  grid-template-columns: repeat(2, 366px);
  gap: 10px;
  margin: 14px 0 4px;
  text-align: left;
}
.rite {
  background: #14122A;
  border: 1px solid var(--vault);
  padding: 12px 14px;
}
.rite.locked { opacity: 0.42; }

/* The icon sits beside the text rather than above it: the card is only three
   lines tall, so a socket on the left costs no height at all. Cards without
   art keep the plain single-column layout, which is what lets the page still
   read with an empty assets folder. */
.rite.has-icon {
  display: grid;
  grid-template-columns: 112px 1fr;
  gap: 14px;
  align-items: center;
}
.rite-icon {
  width: 112px;
  height: 112px;
  /* round, not square. The roundels are circles with transparent corners, so
     a square socket drew a box around a disc and the two shapes argued. The
     socket is now the same shape as the thing in it and reads as a window. */
  border-radius: 50%;
  border: 1px solid var(--vault);
  /* an explicit colour under the icon, or the panel stone shows through a
     transparent PNG and washes it out - the relic sockets had this exact bug */
  background-color: #0B0A18;
  background-size: 100%;
  background-position: center;
  background-repeat: no-repeat;
  box-shadow: 0 0 14px rgba(242, 193, 78, 0.13),
              inset 0 0 10px rgba(8, 7, 15, 0.55);
}
body.theme-vellum .rite-icon { background-color: #E4DAC0; }
.rite-head { display: flex; justify-content: space-between; align-items: baseline; }
.rite-name { font-size: 15px; letter-spacing: 0.1em; color: var(--dawn); }
.rite-cost {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(242, 193, 78, 0.75);
}
.rite-blurb { font-family: var(--font-body); font-size: 14px; line-height: 1.55; color: rgba(220, 208, 186, 0.7); margin-top: 6px; }
.rite-bind { display: flex; gap: 6px; margin-top: 10px; }
.rite-bind button {
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.12em;
  padding: 5px 14px;
  background: var(--deep);
  border: 1px solid var(--vault);
  color: rgba(220, 208, 186, 0.7);
  cursor: pointer;
}
.rite-bind button.on { background: var(--kindled); border-color: var(--kindled); color: #2A2545; }
.rite-bind button:hover { border-color: var(--kindled); }

/* ---------------------------------------------------------------- quests */

.quest-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 620px;
  max-height: 400px;
  overflow-y: auto;
  margin: 14px 0 4px;
  text-align: left;
}
.quest {
  background: #14122A;
  border-left: 2px solid var(--vault);
  border-radius: 0;
  padding: 10px 14px;
}
.quest.done { opacity: 0.45; border-left-color: var(--kindled); }
.quest-head { display: flex; justify-content: space-between; align-items: baseline; }
.quest-title { font-size: 14.5px; letter-spacing: 0.08em; color: var(--dawn); }
.quest-count {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(242, 193, 78, 0.8);
}
.quest-desc { font-family: var(--font-body); font-size: 13.5px; color: rgba(220, 208, 186, 0.65); margin: 5px 0 8px; }
.quest-track { display: block; height: 4px; background: #2A2545; }
.quest-fill { display: block; height: 4px; background: var(--kindled); }

#saved {
  position: absolute;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(127, 212, 200, 0.85);
  opacity: 0;
  transition: opacity 0.35s;
  pointer-events: none;
}
#saved.show { opacity: 1; }
#saved.warn { color: var(--corruption); }

.menu-save {
  margin: 20px 0 0;
  font-size: 12px;
  letter-spacing: 0.06em;
  color: rgba(127, 212, 200, 0.7);
}
.menu-save.warn { color: var(--corruption); }

/* ---------------------------------------------------------------- shop */

#purse { color: rgba(242, 193, 78, 0.55); }
#purse b { color: var(--kindled); font-weight: 400; }

/* How many you have let go. Deliberately COOLER than the purse - bells are
   what you took out of the spire, and this is what you left in it. The two
   should not read as the same kind of score. */
#spared { color: rgba(214, 226, 232, 0.42); }
#spared b { color: rgba(226, 238, 246, 0.92); font-weight: 400; }

.shop-wrap {
  display: grid;
  grid-template-columns: 380px 380px;
  gap: 22px;
  margin: 12px 0 4px;
  text-align: left;
}
.shop-col { display: flex; flex-direction: column; gap: 8px; }
.shop-head {
  display: flex;
  justify-content: space-between;
  font-size: 10.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.55);
}
.shop-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
  height: 318px;
  overflow-y: auto;
  padding-right: 6px;
}
.shop-list::-webkit-scrollbar { width: 6px; }
.shop-list::-webkit-scrollbar-track { background: #100E22; }
.shop-list::-webkit-scrollbar-thumb { background: var(--vault); }
.trade {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 12px;
  background: #14122A;
  border: 1px solid var(--vault);
  padding: 8px 12px;
  cursor: pointer;
}
.trade:hover { background: #1B1834; border-color: var(--worn); }
.trade.cant { opacity: 0.4; cursor: default; }
.trade.cant:hover { background: #14122A; border-color: var(--vault); }
.trade > span:first-child { display: block; min-width: 0; }
.trade .tname { display: block; font-size: 13.5px; }
.trade .tprops {
  display: block;
  font-size: 11px;
  line-height: 1.5;
  color: rgba(185, 169, 142, 0.6);
  margin-top: 3px;
}
.trade .tprice {
  font-size: 13px;
  color: var(--kindled);
  white-space: nowrap;
}
.trade.empty {
  color: rgba(185, 169, 142, 0.35);
  font-size: 12px;
  cursor: default;
  justify-items: center;
  grid-template-columns: 1fr;
  text-align: center;
}
.restock {
  font: inherit;
  font-size: 11.5px;
  letter-spacing: 0.12em;
  padding: 7px 0;
  background: var(--deep);
  border: 1px solid var(--vault);
  color: var(--limestone);
  cursor: pointer;
}
.restock:hover { border-color: var(--kindled); color: var(--dawn); }
.shop-note {
  min-height: 18px;
  font-size: 12px;
  color: rgba(242, 193, 78, 0.8);
  margin-top: 6px;
}

/* ------------------------------------------------------- the title menu */

.menu-screen {
  justify-content: flex-start;
  padding-top: 17vh;          /* the bell hangs in the space above */
  gap: 0;
  /* almost nothing - the drawn scene behind is the background */
  background: rgba(11, 10, 24, 0.10);
}

.title-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 11px;
  margin-bottom: 28px;
}

.title-rule {
  width: 340px;
  height: 1px;
  background: linear-gradient(90deg,
    rgba(242,193,78,0) 0%,
    rgba(242,193,78,0.55) 22%,
    rgba(242,193,78,0.55) 78%,
    rgba(242,193,78,0) 100%);
}

.menu-screen h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 74px;
  font-weight: 700;
  letter-spacing: 0.30em;
  text-indent: 0.34em;
  line-height: 1;
  color: #FFE9A8;
  text-shadow:
    0 0 8px rgba(242, 193, 78, 0.55),
    0 0 40px rgba(242, 193, 78, 0.35),
    0 0 90px rgba(224, 138, 46, 0.28);
}

.title-sub {
  margin: 0;
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.22em;
  text-transform: none;
  text-transform: uppercase;
  color: rgba(220, 208, 186, 0.5);
}

.menu-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.menu-list li {
  position: relative;
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  padding: 7px 44px;
  -webkit-text-stroke: 0.35px currentColor;
  color: rgba(185, 169, 142, 0.85);
  cursor: pointer;
  transition: color 0.14s, letter-spacing 0.14s;
}

/* a bell-mouth marker either side of the item under the cursor */
.menu-list li::before,
.menu-list li::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -3.5px;
  border-radius: 50%;
  background: var(--kindled);
  opacity: 0;
  transition: opacity 0.14s, transform 0.14s;
  transform: scale(0.4);
}
.menu-list li::before { left: 14px; }
.menu-list li::after  { right: 14px; }

.menu-list li:hover,
.menu-list li.on:hover,
.menu-list li.sel {
  color: #FFF6DE;
  letter-spacing: 0.24em;
}
.menu-list li:hover::before, .menu-list li:hover::after,
.menu-list li.sel::before,   .menu-list li.sel::after {
  opacity: 1;
  transform: scale(1);
}

.menu-list li.dim {
  opacity: 0.26;
  pointer-events: none;
}
.menu-list li.dim.on {
  opacity: 1;
  pointer-events: auto;
}

/* In flow, so the whole title stack centres as one block instead of hanging
   from the top with the bar pinned far below it. */
.menu-bar {
  margin-top: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 40px;
  padding: 0 30px;
}

.menu-screen .volume { margin-top: 0; }
.menu-screen .menu-foot {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.38);
}
.menu-screen .menu-save { margin-top: 34px; }

@media (max-height: 760px) {
  .menu-screen { padding-top: 12vh; }
  .menu-screen h1 { font-size: 54px; }
  .title-block { margin-bottom: 20px; }
  .menu-list li { font-size: 17px; padding: 5px 40px; }
}

/* A title screen should not have a health bar on it. */
#frame.titlemode #hud,
#frame.titlemode #tierlabel,
#frame.titlemode #hint,
#frame.titlemode #saved { display: none; }

#frame.titlemode #stage { border-bottom: none; }



/* --------------------------------------------------------- vellum theme */
/* The whole UI runs off these variables, so a theme is a variable swap
   rather than a second stylesheet. */

body.theme-vellum {
  --dawn: #3A2E1C;
  --limestone: #4A3B24;
  --worn: #6B5836;
  --kindled: #A8791A;
  --ember: #B4691C;
  --vault: #C6B896;
  --deep: #EDE5D0;
  --void: #F4EEDF;
  --grace: #2F7A6E;
  --corruption: #A83A2E;
  background: #EFE8D6;
}

body.theme-vellum #frame { background: #EFE8D6; }
body.theme-vellum #stage { background: #F4EEDF; border-color: #C6B896; }

body.theme-vellum #hud {
  background: linear-gradient(180deg, #EFE7D2 0%, #E2D8BE 100%);
  border-color: #C6B896;
}
body.theme-vellum .plate {
  font-family: var(--font-display);
  font-weight: 700;
  -webkit-text-stroke: 0.4px currentColor;
  background: linear-gradient(180deg, #6B5836 0%, #4A3B24 100%);
  border-color: #3A2E1C;
  color: #F4EEDF;
}
body.theme-vellum .plate:hover {
  background: linear-gradient(180deg, #8A7247 0%, #6B5836 100%);
}
body.theme-vellum .globe { background: #E2D8BE; border-color: #B9A87F; }
body.theme-vellum .vit .fill { background: linear-gradient(180deg, #D89A22 0%, #9A6408 100%); }
body.theme-vellum .grace .fill { background: linear-gradient(180deg, #7FD4C8 0%, #2F7A6E 100%); }
body.theme-vellum .orb-label {
  color: #2E2412;
  text-shadow: 0 0 4px rgba(250,246,236,0.95), 0 1px 2px rgba(250,246,236,0.9);
}
body.theme-vellum .orb-name { color: rgba(46,36,18,0.75); text-shadow: 0 0 4px rgba(250,246,236,0.9); }
body.theme-vellum .slot { background: #EDE5D0; border-color: #C6B896; color: rgba(74,59,36,0.45); }
body.theme-vellum .log { background: #F7F2E4; border-color: #C6B896; color: rgba(58,46,28,0.85); }
body.theme-vellum .log div:first-child { color: #2E2412; }
body.theme-vellum .skill { background: #EDE5D0; border-color: #C6B896; color: rgba(74,59,36,0.55); }
body.theme-vellum .levelbar .xptrack { background: #DCD0B2; }
body.theme-vellum .levelbar .lvl { color: rgba(74,59,36,0.65); }

body.theme-vellum .overlay { background: rgba(244, 238, 223, 0.94); }
body.theme-vellum .menu-screen { background: rgba(244, 238, 223, 0.12); }
body.theme-vellum .menu-screen h1 {
  color: #7A5410;
  text-shadow: 0 0 10px rgba(200, 150, 40, 0.35), 0 0 46px rgba(180, 120, 30, 0.22);
}
body.theme-vellum .title-rule {
  background: linear-gradient(90deg,
    rgba(122,84,16,0) 0%, rgba(122,84,16,0.5) 22%,
    rgba(122,84,16,0.5) 78%, rgba(122,84,16,0) 100%);
}
body.theme-vellum .title-sub { color: rgba(74,59,36,0.6); }
body.theme-vellum .menu-list li { color: rgba(74,59,36,0.85); }
body.theme-vellum .menu-list li:hover,
body.theme-vellum .menu-list li.sel { color: #2E2412; }

body.theme-vellum .class-card,
body.theme-vellum .pilgrim,
body.theme-vellum .trade,
body.theme-vellum .rite,
body.theme-vellum .quest,
body.theme-vellum .item-detail,
body.theme-vellum .worn-slot,
body.theme-vellum .cell { background: #EDE5D0; border-color: #C6B896; }
body.theme-vellum .cell.has { background: #E5DAC0; }
body.theme-vellum .field input,
body.theme-vellum .shop-list { background: #F7F2E4; border-color: #C6B896; color: #2E2412; }
body.theme-vellum .overlay button {
  background: #E2D8BE; border-color: #B9A87F; color: #3A2E1C;
}
body.theme-vellum .overlay button.primary,
body.theme-vellum .pilgrim button.go {
  background: #C8971E; border-color: #C8971E; color: #2E2412;
}
body.theme-vellum #saved { color: #2F7A6E; }

/* ------------------------------------- vellum: the same panel, in stone */

body.theme-vellum #hud {
  background:
    linear-gradient(180deg,
      rgba(255,255,255,0.5) 0%,
      rgba(255,255,255,0) 3px),
    linear-gradient(180deg, #F2EBD8 0%, #E4DAC0 45%, #CFC2A2 100%);
  border-color: #B9A87F;
  box-shadow:
    inset 0 1px 0 rgba(255, 253, 246, 0.9),
    inset 0 2px 12px rgba(122, 100, 60, 0.16),
    0 -6px 18px rgba(122, 100, 60, 0.18);
}

body.theme-vellum .plate {
  font-family: var(--font-display);
  font-weight: 700;
  -webkit-text-stroke: 0.4px currentColor;
  background: linear-gradient(180deg, #7E6A44 0%, #61512F 46%, #4A3B24 100%);
  border-color: #3A2E1C;
  color: #F7F2E4;
  text-shadow: 0 1px 0 rgba(20, 14, 6, 0.55);
  box-shadow:
    inset 0 1px 0 rgba(226, 210, 176, 0.42),
    inset 0 -1px 0 rgba(20, 14, 6, 0.4),
    0 1px 2px rgba(122, 100, 60, 0.35);
}

body.theme-vellum .globe {
  background: radial-gradient(circle at 50% 62%, #EFE7D2 0%, #DCD0B2 100%);
  border-color: #B9A87F;
  box-shadow:
    inset 0 6px 14px rgba(122, 100, 60, 0.34),
    inset 0 -3px 8px rgba(255, 253, 246, 0.5),
    0 2px 6px rgba(122, 100, 60, 0.28),
    0 0 0 1px rgba(122, 100, 60, 0.35);
}
body.theme-vellum .globe::after {
  background:
    radial-gradient(ellipse 62% 40% at 50% 14%, rgba(255,255,255,0.6), rgba(255,255,255,0) 70%),
    radial-gradient(ellipse 90% 70% at 50% 108%, rgba(122,100,60,0.28), rgba(122,100,60,0) 60%);
}
body.theme-vellum .globe .fill::before { background: rgba(255, 253, 246, 0.75); }

body.theme-vellum .slot {
  background: linear-gradient(180deg, #DCD0B2 0%, #EDE5D0 100%);
  border-color: #B9A87F;
  box-shadow:
    inset 0 2px 5px rgba(122, 100, 60, 0.34),
    inset 0 -1px 0 rgba(255, 253, 246, 0.7);
}
body.theme-vellum .log {
  background: linear-gradient(180deg, #FAF6EA 0%, #F2EBD8 100%);
  border-color: #C6B896;
  box-shadow:
    inset 0 2px 8px rgba(122, 100, 60, 0.2),
    inset 0 -1px 0 rgba(255, 253, 246, 0.8);
}
body.theme-vellum .skill {
  background: linear-gradient(180deg, #EDE5D0 0%, #DCD0B2 100%);
  border-color: #C6B896;
  box-shadow: inset 0 2px 6px rgba(122, 100, 60, 0.22);
}
body.theme-vellum .skill.ready {
  box-shadow:
    inset 0 2px 6px rgba(122, 100, 60, 0.16),
    0 0 12px rgba(168, 121, 26, 0.45);
}
body.theme-vellum .levelbar .xpfill { background: #2F7A6E; }

/* the theme rule above outranks .slot.filled, so restate it */
body.theme-vellum .slot.filled {
  border-color: #A8791A;
  color: #7A5410;
  background: linear-gradient(180deg, #F0E2BC 0%, #E4D3A6 100%);
  box-shadow:
    inset 0 2px 5px rgba(122, 100, 60, 0.22),
    0 0 9px rgba(168, 121, 26, 0.4);
}
body.theme-vellum .skill.ready { border-color: #A8791A; color: #7A5410; }

/* A pause menu sits over a lit world, so it needs to actually cover it. */
.menu-screen.over-world {
  background: rgba(11, 10, 24, 0.90);
  backdrop-filter: blur(3px);
}
body.theme-vellum .menu-screen.over-world {
  background: rgba(58, 46, 28, 0.90);
}
body.theme-vellum .menu-screen.over-world h1 {
  color: #E8B84A;
  text-shadow: 0 0 10px rgba(232, 184, 74, 0.4), 0 0 46px rgba(200, 150, 40, 0.25);
}
body.theme-vellum .menu-screen.over-world .menu-list li { color: rgba(228, 214, 180, 0.85); }
body.theme-vellum .menu-screen.over-world .menu-list li:hover,
body.theme-vellum .menu-screen.over-world .menu-list li.sel { color: #FFF6DE; }
body.theme-vellum .menu-screen.over-world .title-sub,
body.theme-vellum .menu-screen.over-world .menu-foot { color: rgba(228, 214, 180, 0.55); }

/* ------------------------------------------------------- the help panel */

.help-scroll {
  width: min(1000px, 92vw);
  max-height: 70vh;
  overflow-y: auto;
  padding: 4px 14px 4px 2px;
  margin: 16px 0 6px;
}
.help-scroll::-webkit-scrollbar { width: 8px; }
.help-scroll::-webkit-scrollbar-track { background: rgba(16,14,34,0.6); }
.help-scroll::-webkit-scrollbar-thumb { background: var(--vault); border-radius: 4px; }

.help-cols {
  column-count: 2;
  column-gap: 20px;
}
@media (max-width: 900px) { .help-cols { column-count: 1; } }

.help-card {
  break-inside: avoid;
  background: rgba(20, 18, 42, 0.55);
  border-left: 2px solid var(--kindled);
  padding: 12px 16px 14px;
  margin: 0 0 14px;
  text-align: left;
}
.help-card h3 {
  margin: 0 0 9px;
  font-size: 13.5px;
  font-weight: 600;
  font-family: var(--font-display);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--kindled);
}
.help-card p {
  margin: 0 0 8px;
  font-family: var(--font-body);
  font-size: 14.5px;
  line-height: 1.65;
  color: rgba(220, 208, 186, 0.82);
  max-width: none;
}
.help-card p:last-child { margin-bottom: 0; }
.help-card b { color: var(--dawn); font-weight: 400; }
.help-card i { color: rgba(244, 237, 223, 0.9); font-style: italic; }

.help-card dl {
  display: grid;
  grid-template-columns: 92px 1fr;
  gap: 6px 14px;
  margin: 0;
  font-size: 13px;
  max-width: none;
}
.help-card dt { color: var(--kindled); }
.help-card dd { margin: 0; color: rgba(220, 208, 186, 0.82); }

body.theme-vellum .help-card { background: rgba(74, 59, 36, 0.10); }
body.theme-vellum .help-card p,
body.theme-vellum .help-card dd { color: rgba(58, 46, 28, 0.88); }

/* vellum restatement of the brass-and-stone panel */
body.theme-vellum #hud {
  background:
    linear-gradient(180deg,
      #8A6410 0px, #6E4E0C 3px,
      rgba(74,52,10,0.35) 4px, rgba(0,0,0,0) 9px),
    repeating-linear-gradient(90deg,
      rgba(90,72,40,0.16) 0 1px,
      rgba(255,255,255,0.22) 1px 2px,
      rgba(0,0,0,0) 2px 74px),
    repeating-linear-gradient(180deg,
      rgba(255,255,255,0.28) 0 1px,
      rgba(90,72,40,0.12) 1px 2px,
      rgba(0,0,0,0) 2px 34px),
    linear-gradient(180deg, #C7B694 0%, #B6A47F 42%, #9A8865 100%);
  border-color: #6E4E0C;
}
body.theme-vellum .globe {
  border-color: #6E4E0C;
  box-shadow:
    0 0 0 1px #3A2A06,
    0 0 0 4px #B6A47F,
    0 0 0 5px rgba(110, 78, 12, 0.5),
    0 3px 8px rgba(90, 72, 40, 0.5);
}
body.theme-vellum .globe::after {
  background:
    radial-gradient(circle 16px at 34% 24%, rgba(255,255,255,0.75), rgba(255,255,255,0) 100%),
    radial-gradient(ellipse 70% 46% at 42% 18%, rgba(255,255,255,0.3), rgba(255,255,255,0) 72%),
    radial-gradient(circle at 50% 50%, rgba(90,72,40,0) 52%, rgba(90,72,40,0.4) 100%);
}
body.theme-vellum .slot,
body.theme-vellum .skill,
body.theme-vellum .log { border-color: #8A6410; }

/* corner readouts need dark ink on sand, not pale text */
body.theme-vellum #tierlabel { color: rgba(58, 46, 28, 0.75); }
body.theme-vellum #tierlabel i { color: rgba(58, 46, 28, 0.9); }
body.theme-vellum #tierlabel b { color: #7A5410; }
body.theme-vellum #purse { color: rgba(122, 84, 16, 0.75); }
body.theme-vellum #spared { color: rgba(58, 70, 84, 0.7); }
body.theme-vellum #spared b { color: rgba(38, 48, 60, 0.95); }
body.theme-vellum #purse b { color: #7A5410; }
body.theme-vellum #objective { color: rgba(122, 84, 16, 0.8); }
body.theme-vellum #objective.open { color: rgba(58, 46, 28, 0.9); }
body.theme-vellum #hint { color: rgba(58, 46, 28, 0.45); }
body.theme-vellum .bell-pip { border-color: rgba(122, 84, 16, 0.6); }
body.theme-vellum .bell-pip.rung { background: #A8791A; border-color: #A8791A; }


/* ------------------------------------------------ marble, from assets/ */
/* The panel and every full screen share one stone sheet. It is greyscale,
   so a background-blend-mode tints it per theme instead of needing a second
   image. Swap assets/firmament_wall.png to reskin all of it at once. */

#hud {
  /* The panel was polished marble, which reads as clean. Diablo's is cut
     rough. undercroft_wall is the weathered block sheet - same trick, far
     more tooth, and a second overlay pass to deepen the pitting. */
  background-image:
    linear-gradient(180deg,
      rgba(200,151,30,0.9) 0px, rgba(110,78,12,0.9) 3px,
      rgba(0,0,0,0.55) 4px, rgba(0,0,0,0) 11px),
    url('../assets/undercroft_wall.webp'),
    url('../assets/undercroft_wall.webp'),
    /* lightened to sit with the pale limestone statues - the old indigo made
       them look like cut-outs pasted onto a different material */
    linear-gradient(180deg, #8C8497 0%, #736C82 45%, #4E495F 100%);
  background-size: auto, 260px 260px, 620px 620px, auto;
  background-repeat: no-repeat, repeat, repeat, no-repeat;
  background-blend-mode: normal, multiply, overlay, normal;
}

/* The hidden state is no longer reachable by key - kept because showHud()
   clears it on every tier entry, which un-sticks any old save that has it. */
#frame.hud-hidden #hud {
  transform: translateX(-50%) translateY(105%);
  opacity: 0;
  pointer-events: none;
}

/* end caps, so the panel reads as a carved slab rather than a cut-off bar */
#hud::before,
#hud::after {
  content: '';
  position: absolute;
  bottom: 0;
  width: 18px;
  height: 100%;
  pointer-events: none;
  z-index: 5;
}
#hud::before {
  left: 0;
  background: linear-gradient(90deg, rgba(8,7,15,0.5), rgba(8,7,15,0));
  border-left: 1px solid rgba(200,151,30,0.25);
}
#hud::after {
  right: 0;
  background: linear-gradient(270deg, rgba(8,7,15,0.5), rgba(8,7,15,0));
  border-right: 1px solid rgba(200,151,30,0.25);
}

/* ---- the full screens get the same stone ---- */
.overlay:not(.menu-screen) {
  background-image:
    url('../assets/firmament_wall.webp'),
    linear-gradient(180deg, #1E1A38 0%, #14122A 60%, #0D0B1C 100%);
  background-size: 420px 420px, auto;
  background-repeat: repeat, no-repeat;
  background-blend-mode: multiply, normal;
  background-color: #14122A;
}

/* cards on the stone read better with a brass edge */
.class-card, .pilgrim, .trade, .rite, .quest,
.item-detail, .worn-slot, .cell, .help-card, .shop-list {
  box-shadow: inset 0 1px 0 rgba(200,151,30,0.10), 0 2px 5px rgba(8,7,15,0.5);
}

/* ---- vellum restatement ---- */
body.theme-vellum #hud {
  background-image:
    linear-gradient(180deg,
      rgba(138,100,16,0.9) 0px, rgba(110,78,12,0.9) 3px,
      rgba(74,52,10,0.35) 4px, rgba(0,0,0,0) 11px),
    url('../assets/undercroft_wall.webp'),
    url('../assets/undercroft_wall.webp'),
    linear-gradient(180deg, #D8C9A6 0%, #C3B08A 45%, #A08D68 100%);
  background-size: auto, 260px 260px, 620px 620px, auto;
  background-blend-mode: normal, multiply, overlay, normal;
  border-color: #6E4E0C;
}
body.theme-vellum .overlay:not(.menu-screen) {
  background-image:
    url('../assets/firmament_wall.webp'),
    linear-gradient(180deg, #E4DAC0 0%, #D3C4A2 60%, #BCAA84 100%);
  background-size: 420px 420px, auto;
  background-blend-mode: multiply, normal;
  background-color: #D3C4A2;
}

/* A full field of stone with content floating in the middle reads as empty.
   Darken the edges so the eye lands where the content is. */
.overlay:not(.menu-screen)::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 70% 62% at 50% 50%,
    rgba(8,7,15,0) 0%, rgba(8,7,15,0.35) 62%, rgba(8,7,15,0.82) 100%);
}
body.theme-vellum .overlay:not(.menu-screen)::before {
  background: radial-gradient(ellipse 70% 62% at 50% 50%,
    rgba(90,72,40,0) 0%, rgba(90,72,40,0.22) 62%, rgba(90,72,40,0.6) 100%);
}
.overlay > * { position: relative; z-index: 1; }

/* --------------------------------------------------- front menu extras */

.menu-list li.soon {
  color: rgba(185, 169, 142, 0.30);
  cursor: default;
}
.menu-list li.soon:hover { color: rgba(185, 169, 142, 0.42); letter-spacing: 0.16em; }
.menu-list li.soon:hover::before,
.menu-list li.soon:hover::after { opacity: 0; }
.menu-list li.soon::after {
  content: 'soon';
  position: absolute;
  right: -46px;
  top: 50%;
  transform: translateY(-50%) scale(1);
  width: auto; height: auto;
  margin: 0;
  border-radius: 0;
  background: none;
  opacity: 0.5;
  font-family: var(--font-body);
  font-size: 10px;
  font-style: italic;
  letter-spacing: 0.14em;
  text-transform: lowercase;
  color: rgba(185, 169, 142, 0.5);
}

.menu-save.warn { color: var(--corruption); }

.version {
  position: absolute;
  left: 20px; bottom: 18px;
  font-family: var(--font-display);
  font-size: 11px;
  letter-spacing: 0.2em;
  color: rgba(185, 169, 142, 0.3);
}

/* an image wordmark replaces the text one when assets/wordmark.png exists */
#wordmark.has-art {
  /* THE BOX HUGS THE ART, and the art has changed shape twice now. The first
     wordmark was 3:1, the second 6.3:1, and this one is 3.1:1 - so the box has
     to move with it. Left in the wrong box, `contain` fits the art to whichever
     side runs out first and leaves a band of empty box on the other, which
     shoves the menu list down for nothing.

     SIZED TO MATCH THE LETTER HEIGHT of the one it replaces, not its width.
     Both say ANGELUS, but the old art was heavily letterspaced and this one is
     dense - at the same cap height it is half as wide (424px against 860).
     Matching width instead would double the height to 277px and push the menu
     into it. To go wider anyway: 620px is about as far as it can grow before
     the two collide. */
  width: min(424px, 40vw);
  aspect-ratio: 3.1 / 1;
  height: auto;
  text-indent: -9999px;
  overflow: hidden;
  /* The art sits over a soft dark pool of its own.
     The wordmark lands on the brightest part of the painted backdrop - the
     light coming through the belfry - and this artwork is DARK BRONZE with no
     glow of its own, because it was generated on black. Bronze on bright light
     reads grey and washed. The pool underneath gives it something to be metal
     against, and is invisible on a dark backdrop or none at all. */
  background:
    url('../assets/wordmark.webp') center/contain no-repeat,
    radial-gradient(ellipse 74% 104% at 50% 50%,
      rgba(11,10,24,0.78) 0%,
      rgba(11,10,24,0.55) 45%,
      rgba(11,10,24,0.18) 72%,
      rgba(11,10,24,0) 100%);
  text-shadow: none;
}

/* likewise a painted backdrop replaces the drawn scene */
/* Only while the title is up. Without the titlemode gate this painted layer
   sits over the canvas for the whole game. */
#frame.titlemode #stage.has-menu-art::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    /* the painting is bright at the top; the menu list needs the bottom
       held down or the type fights the stonework */
    linear-gradient(180deg,
      rgba(11,10,24,0) 0%,
      rgba(11,10,24,0.15) 38%,
      rgba(11,10,24,0.62) 62%,
      rgba(11,10,24,0.86) 100%),
    url('../assets/menu_bg.webp') center/cover no-repeat;
}
/* the painted bell replaces the drawn one, so the wordmark moves down
   clear of it */
/* With the painting, the wordmark sits over the dark mouth of the bell and
   the list runs down the shadow beneath it. Eight entries is a lot to fit,
   so everything below the title tightens. */
#frame.has-art .menu-screen {
  justify-content: center;
  padding-top: 14vh;   /* bias downward, clear of the painted bell */
}
#frame.has-art #wordmark.has-art { width: min(760px, 70vw); }
#frame.has-art .title-block { margin-bottom: 14px; gap: 6px; }
#frame.has-art .title-rule { width: 300px; }
#frame.has-art .title-sub { font-size: 12.5px; }
#frame.has-art .menu-list li { font-size: 18px; padding: 5px 44px; }
#frame.has-art .menu-save { margin-top: 16px; font-size: 11px; }

@media (max-height: 800px) {
  #frame.has-art .menu-screen { padding-top: 10vh; }
  #frame.has-art #wordmark.has-art { width: min(500px, 50vw); }
  #frame.has-art .menu-list li { font-size: 16px; padding: 3px 40px; }
}

.credits-body {
  /* The list scrolls, not the page. Back is a sibling below this, so it stays
     on screen however long the credits get - it was falling off the bottom at
     every window height, and adding names only made it worse. Same fix as the
     character sheet. */
  width: min(880px, 92vw);
  max-height: calc(100vh - 190px);
  overflow-y: auto;
  margin: 16px 0 6px;
  text-align: left;
}
.credit-line {
  /* Two names in one slot is normal here, so the label is allowed to be
     narrow and the names get the room. Flex with space-between let a long
     pairing squeeze the label until it wrapped. */
  display: grid;
  grid-template-columns: minmax(72px, auto) 1fr;
  align-items: baseline;
  gap: 14px;
  margin: 0 0 10px;
  font-family: var(--font-body);
  font-size: 15px;
  color: rgba(185, 169, 142, 0.7);
  border-bottom: 1px dotted rgba(185, 169, 142, 0.2);
  padding-bottom: 8px;
}
.credit-line b {
  text-align: right;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--dawn);
}
.credit-note {
  margin: 20px 0 0;
  font-family: var(--font-body);
  font-style: italic;
  font-size: 14px;
  line-height: 1.75;
  color: rgba(185, 169, 142, 0.55);
}


/* ------------------------------------------------- class portraits */
/* The figure sits left of the text once the art exists. Without it the card
   keeps its original single-column layout, so nothing depends on the art. */

.class-figure { display: none; }

.class-card.has-figure {
  width: 428px;
  flex-direction: row;
  gap: 14px;
  align-items: stretch;
}
.class-card.has-figure .class-figure {
  display: block;
  flex: 0 0 168px;
  align-self: stretch;
  /* the portraits are full-length, so crop into the head and shoulders -
     at this size a whole figure is just a smudge */
  background-position: 50% 5%;
  background-size: auto 150%;
  background-repeat: no-repeat;
  border-right: 1px solid rgba(200, 151, 30, 0.18);
  /* fade the portrait into the card rather than cutting it off square */
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 72%, transparent 100%);
  mask-image: linear-gradient(90deg, #000 0%, #000 72%, transparent 100%);
}
.class-card.has-figure .class-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}
.class-card.has-figure .class-body p { flex: 1; }

@media (max-width: 1460px) {
  .class-card.has-figure { width: 356px; }
  .class-card.has-figure .class-figure { flex-basis: 132px; background-size: auto 145%; }
}
@media (max-width: 1180px) {
  .class-card.has-figure { width: 300px; }
  .class-card.has-figure .class-figure { flex-basis: 104px; background-size: auto 140%; }
}

/* ============================================================== mobile */
/* Everything above is sized in fixed pixels for a desktop window. On a phone
   the same numbers are proportionally tiny - a 21px menu item on a 1400px
   screen is comfortable, on a 390px one it is small print. These scale the
   type and the panel up rather than reflowing anything, because the layout
   itself works fine at narrow widths. */

@media (max-width: 820px) {
  /* two 366px cards are 742px wide and a phone is 390 - one column, and a
     smaller socket so the roundel does not crowd out the blurb */
  .rite-list { grid-template-columns: 1fr; }
  .rite.has-icon { grid-template-columns: 84px 1fr; gap: 11px; }
  .rite-icon { width: 84px; height: 84px; }

  .menu-screen h1 { font-size: 44px; letter-spacing: 0.22em; }
  #frame.has-art #wordmark.has-art { width: min(86vw, 420px); }
  #frame.has-art .menu-screen { padding-top: 8vh; justify-content: flex-start; }
  .title-rule { width: 62vw; }
  .title-sub { font-size: 13px; letter-spacing: 0.16em; }

  .menu-list li,
  #frame.has-art .menu-list li {
    font-size: 22px;
    padding: 12px 34px;      /* a real tap target, not a mouse target */
    letter-spacing: 0.12em;
  }
  .menu-list li.soon::after { right: -34px; font-size: 11px; }

  .menu-bar { flex-direction: column; gap: 12px; margin-top: 22px; }
  .menu-screen .menu-foot { font-size: 10px; }
  .volume input[type=range] { width: 190px; height: 26px; }
  .version { font-size: 10px; left: 12px; bottom: 10px; }

  /* the panel needs the whole width on a phone */
  #hud {
    width: 100%;
    border-radius: 0;
    border-left: none;
    border-right: none;
    height: 124px;
  }
  #hud::before, #hud::after { display: none; }
  .plate { width: 88px; font-size: 12px; padding: 7px 0; letter-spacing: 0.1em; }
  /* LEFT AT 76. `.mount .globe` is two classes and beats a bare `.globe`
     whatever the order, which is why an earlier attempt at 84 silently did
     nothing. Once the selector was fixed and 84 actually applied, it turned
     out 76 was right all along: at 84 the globes overlap the belt and the
     message log, and the right one is clipped by the panel edge. The original
     rule was sized to the space, not guessed. */
  .orb-label { font-size: 15px; }
  .orb-name { font-size: 8px; }
  .slot { width: 32px; height: 32px; }
  .skill { width: 44px; height: 44px; font-size: 10px; }
  .log { font-size: 12px; }

  .overlay h2 { font-size: 22px; }
  .overlay p, .help-card p { font-size: 15px; }
  .help-cols { column-count: 1; }
  .help-scroll { max-height: 66vh; }
  .class-card, .class-card.has-figure { width: min(92vw, 420px); }
  #tierlabel { font-size: 13px; }
  #hint { display: none; }        /* keyboard hints mean nothing on a phone */
}

@media (max-width: 820px) and (orientation: portrait) {
  /* a tall thin window shows very little of an isometric room; pull the
     camera back so the player can still see what is coming */
  :root { --hud-h: 116px; }   /* the cluster follows it - see --hud-h */
  .plate { width: 76px; font-size: 11px; }
}

@media (max-width: 820px) {
  /* The painting is bright through its middle, which on a tall thin screen is
     exactly where the menu list lands. Hold it down much harder than on
     desktop - the type has to win. */
  #frame.titlemode #stage.has-menu-art::after {
    background:
      linear-gradient(180deg,
        rgba(11,10,24,0.10) 0%,
        rgba(11,10,24,0.45) 30%,
        rgba(11,10,24,0.86) 52%,
        rgba(11,10,24,0.94) 100%),
      url('../assets/menu_bg.webp') center/cover no-repeat;
  }
  .menu-screen h1 { text-shadow: 0 0 8px rgba(0,0,0,0.9), 0 0 30px rgba(242,193,78,0.4); }
  .title-sub { color: rgba(244,237,223,0.75); text-shadow: 0 1px 6px rgba(0,0,0,0.9); }
  .menu-list li { text-shadow: 0 1px 8px rgba(0,0,0,0.85); }
}


/* ------------------------------------------------- mobile HUD layout */
/* The desktop panel is five fixed columns totalling 500px, which simply does
   not fit a 390px phone - it overflowed and buried the log behind a globe.
   On narrow screens it becomes two rows: the two globes flanking the log,
   with the plates and belt underneath. */

@media (max-width: 900px), (max-height: 520px) {
  #hud {
    /* AUTO, both. `height: 168px` with a pinned `84px` first row, and
       `align-items: center`, meant the centre column - belt plus log, 146px
       of content - was centred inside an 84px row and hung 31px out of each
       end. The belt's first line ended up outside the panel, on the game
       world, which is why only the last few slots looked like they existed.
       Sizing to content puts everything inside and leaves no dead stone. */
    height: auto;
    grid-template-columns: 76px minmax(0, 1fr) 76px;
    grid-template-rows: auto auto auto;
    /* THE BELT GETS THE WHOLE WIDTH. Stuck in the middle column it had 210px
       for eight slots, which is 23px each - too small for a thumb - or two
       rows, which reads as a grid rather than a belt. Across the panel it has
       354px, so all eight fit in ONE row at 38px, larger than they were.
       `.center` becomes `display: contents` below so its two children become
       grid items here; the wrapper stays in the HTML for desktop. */
    grid-template-areas:
      "belt belt belt"
      "vit  log  grace"
      "left left right";
    gap: 6px 6px;
    padding: 10px 10px 8px;
    align-items: center;
  }
  #hud .globe.vit   { grid-area: vit; }
  #hud .globe.grace { grid-area: grace; }
  /* display: contents dissolves the wrapper so `.belt` and `.log` become
     direct grid items of the panel and can take their own areas. The element
     stays in the markup, so desktop - which never sees this block - is
     untouched. */
  #hud .center      { display: contents; }
  #hud .belt        { grid-area: belt; }
  #hud .log         { grid-area: log; }
  #hud .side.left   { grid-area: left; }
  #hud .side.right  { grid-area: right; }
  .statue { display: none; }   /* no room for ornament at 390px */
  /* four plates on the left, two on the right - give the row proportional
     space so nothing gets clipped */
  #hud .side.left  { flex: 2 1 0; }
  #hud .side.right { flex: 1 1 0; }     /* RITES fits where SPELLS did not */

  /* plates run across the bottom instead of stacking */
  #hud .side {
    flex-direction: row;
    justify-content: flex-start;
    gap: 6px;
    flex-wrap: nowrap;
  }
  #hud .side.right { justify-content: flex-end; }
  .plate { width: auto; flex: 1 1 0; min-width: 0; padding: 9px 2px; font-size: 10px;
           letter-spacing: 0.02em; overflow: hidden; text-overflow: clip; }

  .globe { width: 76px; height: 76px; }
  .orb-label { font-size: 14px; }
  .orb-name { font-size: 7px; bottom: 4px; }

  .center { gap: 4px; }
  /* WRAPS rather than scrolling sideways. The strip was `overflow-x: auto`
     with 30px slots - which works, but a horizontal scroll with no visible
     affordance is invisible on a phone, so the slots past the edge may as
     well not exist. Two rows of four at a thumb-sized 36px shows all eight. */
  /* ONE ROW, and bigger than before. `flex: 1 1 0` lets the eight share the
     panel evenly and shrink together on a narrower phone rather than the last
     ones dropping off the end. */
  .belt { gap: 4px; justify-content: center; flex-wrap: nowrap; }
  .slot { width: auto; height: 38px; flex: 1 1 0; max-width: 44px; min-width: 0;
          font-size: 9px; }
  .log { font-size: 11.5px; line-height: 1.35; padding: 4px 7px; max-height: 66px; overflow: hidden; }

  /* the skills sit with the right-hand plates */
  #hud .skills { display: none; }
  .levelbar { top: 3px; }
}


@media (max-width: 820px) {
  /* SAVED was landing on top of the tier name */
  #saved { top: auto; bottom: 8px; right: 12px; left: auto; transform: none; font-size: 10px; }
  #tierlabel { font-size: 12px; line-height: 1.5; }
}


/* --------------------------------------------- angel and orb, as one */
/*
 * The angel HOLDS the orb, the way Diablo's supporters do, rather than
 * standing beside it. That only works if the art and the engine agree on
 * exactly where the orb goes, so the geometry is fixed here and the prompt
 * is written to match it:
 *
 *   image           512 x 328, transparent
 *   orb centre      x = 80.3% left / 19.7% right, y = 67.0% in both
 *   orb diameter    192px in the source, 96px on screen
 *
 * The right angel IS the left one mirrored. Two separately generated
 * sculptures held their hands at different heights relative to their heads,
 * so aligning the orbs left one figure sitting visibly lower than the other.
 * A mirror cannot drift: same size, same head height, same cradle.
 *
 * The image aspect MUST be 512:328. The mount is stretched to exactly that
 * ratio and the art is drawn at 100% 100%, so a percentage in the source is
 * the same percentage on screen. With `contain` it is not - the image letter-
 * boxes inside the mount and every offset drifts.
 * Full spec and prompts in assets/STATUE_ART.md.
 */

.mount {
  position: relative;
  align-self: end;
  width: 256px;
  height: 164px;          /* 256:164 == 512:328, the source aspect */
  background-position: center;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  filter: drop-shadow(0 2px 3px rgba(10, 8, 20, 0.55));
}
.mount.left  { background-image: url('../assets/statue_left.webp'); }
.mount.right { background-image: url('../assets/statue_right.webp'); }

/* the orb sits where the hands are */
.mount .globe {
  position: absolute;
  width: 96px;
  height: 96px;
  margin: 0;
  transform: translate(-50%, -50%);
  top: 67%;
}
.mount.left  .globe { left: 80.3%; }
.mount.right .globe { left: 19.7%; }   /* 100 - 80.3, because it is a mirror */

/* ------------------------------------------------- liquid, moving */
/* A flat line across a globe reads as a gauge. Two waves at different speeds
   read as something with a surface, which is what a sphere full of light
   should look like.
   
   These are background-images with an animated background-position, not
   masks with an animated transform. Translating the element carried its mask
   along with it, so the pattern never moved relative to the globe; and
   mask-position turned out not to animate here at all. background-position
   does, everywhere. */

.globe .fill::before,
.globe .fill::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  pointer-events: none;
}

.globe .fill::before {
  top: -9px;
  height: 16px;
  background-image: var(--wave-a);
  background-repeat: repeat-x;
  background-size: 88px 16px;
  animation: swell 6s linear infinite;
  filter: drop-shadow(0 1px 3px rgba(255, 250, 240, 0.25));
}

.globe .fill::after {
  top: -6px;
  height: 13px;
  background-image: var(--wave-b);
  background-repeat: repeat-x;
  background-size: 62px 13px;
  animation: swell2 9.5s linear infinite;
}

:root {
  /* shallow - a tall wave reads as a logo, not a liquid */
  /* One long crest reads as a surface. Short wavelengths at this size looked
     like two flat shaded bars stacked on each other. */
  --wave-a: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='88' height='16' preserveAspectRatio='none'%3E%3Cpath d='M0 9 Q 22 1 44 9 T 88 9 V16 H0 Z' fill='rgba(255,252,244,0.72)'/%3E%3C/svg%3E");
  --wave-b: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='62' height='13' preserveAspectRatio='none'%3E%3Cpath d='M0 8 Q 15.5 3 31 8 T 62 8 V13 H0 Z' fill='rgba(255,252,244,0.28)'/%3E%3C/svg%3E");
}

@keyframes swell  { from { background-position: 0 0; }  to { background-position: 88px 0; } }
@keyframes swell2 { from { background-position: 0 0; }  to { background-position: -62px 0; } }

/* a full globe has no surface to move */
.globe .fill.brimming::before,
.globe .fill.brimming::after { opacity: 0; }

@keyframes swell2 {
  from { -webkit-mask-position: 0 0;    mask-position: 0 0; }
  to   { -webkit-mask-position: 33px 0; mask-position: 33px 0; }
}


/* No angel art yet? The orb centres itself and the mount is just a slot. */
.mount:not(.has-art) { background-image: none; }
.mount:not(.has-art) .globe { left: 50%; top: 50%; width: 108px; height: 108px; }

/* Same breakpoint as the rest of the phone rules - narrow OR short. At the
   old 820 a landscape phone (844 wide, 390 tall) kept both 256px statue
   plates, which is 512px of a 844px screen spent on decoration. */
@media (max-width: 900px), (max-height: 520px) {
  .mount { width: auto; height: auto; background-image: none; }
  /* RELATIVE, not static. `position: static` here took the globe out of being
     a containing block, so `.fill` - which is position:absolute - resolved
     against an ancestor OUTSIDE the globe's `overflow: hidden`. An absolutely
     positioned descendant whose containing block sits outside the clip is not
     clipped by it, so the gold fill escaped the circle and painted its own
     square. On a full Vitality globe that is a large yellow box with a ball
     drawn on top of it, which is exactly how it looked.
     The transform still has to go, or it would offset from its own corner. */
  .mount .globe {
    position: relative;
    /* top/left MUST be cleared with it. The desktop rule places the orb on
       the statue with `top: 67%` and `left: 80.3%`, and `static` was ignoring
       both - switching to `relative` woke them up as offsets and shoved the
       globe over the belt and the message log. Static was hiding two bugs at
       once: the escaping fill, and these. */
    transform: none;
    width: 76px; height: 76px;
  }
  /* .mount.left .globe and .mount.right .globe are THREE classes and outrank
     a two-class rule whatever the order - so clearing `left` above did
     nothing and the orb stayed shoved 80% across its mount, on top of the
     belt. Matched selector, matched weight. This is the trap START-HERE
     names: grep the class and read the LAST match, and count the classes. */
  .mount.left .globe, .mount.right .globe {
    top: auto; left: auto; right: auto; bottom: auto;
  }
}


/* ============================== full screens, on the same stone ========= */
/* Char, Pack, Rites, Quests and the shop were flat panels on a plain field.
   They now use the panel's own stone so the whole interface reads as one
   fitting - and because stone is busier than a flat colour, every surface
   that carries text gets an opaque backing rather than a translucent one. */

.overlay:not(.menu-screen) {
  background-image:
    url('../assets/undercroft_wall.webp'),
    url('../assets/undercroft_wall.webp'),
    linear-gradient(180deg, #6E6780 0%, #55506A 45%, #332E46 100%);
  background-size: 300px 300px, 700px 700px, auto;
  background-repeat: repeat, repeat, no-repeat;
  background-blend-mode: multiply, overlay, normal;
  background-color: #3A3450;
}

/* headings and buttons in the panel's own face */
.overlay:not(.menu-screen) h2 {
  font-family: var(--font-display);
  font-weight: 700;
  color: #FFE9A8;
  text-shadow: 0 2px 3px rgba(8,7,15,0.9), 0 0 14px rgba(242,193,78,0.3);
}
.overlay:not(.menu-screen) .sheet-sub,
.overlay:not(.menu-screen) .tag {
  color: rgba(255, 240, 205, 0.8);
  text-shadow: 0 1px 3px rgba(8,7,15,0.9);
}

/* The selected class card, restated at a specificity that survives the block
   below. `.class-card.on` is defined ~2100 lines earlier and loses outright to
   `.overlay:not(.menu-screen) .class-card`, so the class was being applied and
   drawing nothing - you could pick a calling and get no sign that you had. */
.overlay:not(.menu-screen) .class-card.on {
  border-color: var(--kindled) !important;
  background-color: #221C42 !important;
  box-shadow: 0 0 0 1px var(--kindled), 0 0 18px rgba(242, 193, 78, 0.25);
}

/* every card gets a solid dark ground - text on bare stone is unreadable */
.overlay:not(.menu-screen) .class-card,
.overlay:not(.menu-screen) .pilgrim,
.overlay:not(.menu-screen) .trade,
.overlay:not(.menu-screen) .rite,
.overlay:not(.menu-screen) .quest,
.overlay:not(.menu-screen) .item-detail,
.overlay:not(.menu-screen) .worn-slot,
.overlay:not(.menu-screen) .cell,
.overlay:not(.menu-screen) .help-card,
.overlay:not(.menu-screen) .shop-list,
.overlay:not(.menu-screen) .attr,
.overlay:not(.menu-screen) .field input {
  background-color: rgba(14, 12, 28, 0.94);
  background-image: none;
  border-color: rgba(200, 151, 30, 0.35);
  box-shadow:
    inset 0 1px 0 rgba(200,151,30,0.12),
    0 2px 6px rgba(8,7,15,0.65);
}
.overlay:not(.menu-screen) .cell.has { background-color: rgba(28, 24, 50, 0.96); }

/* the stat column sat straight on the stone and was unreadable */
.overlay:not(.menu-screen) #sheet-stats,
.overlay:not(.menu-screen) #shop-list,
.overlay:not(.menu-screen) .credits-body,
.overlay:not(.menu-screen) .belt-note {
  background-color: rgba(14, 12, 28, 0.94);
  border: 1px solid rgba(200, 151, 30, 0.28);
  border-radius: 3px;
  padding: 12px 16px;
  box-shadow: inset 0 1px 0 rgba(200,151,30,0.1), 0 2px 6px rgba(8,7,15,0.65);
}
.overlay:not(.menu-screen) #sheet-stats dt { color: rgba(238, 230, 214, 0.72); }
.overlay:not(.menu-screen) #sheet-stats dd { color: #FFF3D6; }

/* The attribute + buttons are not plates. They used to be kept small, which
   was right when a point was a rare click; now they repeat while held and are
   the most-used control on the page, so they are sized to be hit without
   aiming. This rule is late in the file and beats the earlier one - the
   earlier one is dead, and was quietly winning against two attempts to
   enlarge these. */
.overlay button.attr-plus {
  font-size: 22px;
  font-weight: 700;
  padding: 0;
  width: 40px;
  height: 34px;
  line-height: 1;
  letter-spacing: 0;
  border-radius: 3px;
  box-shadow:
    inset 0 1px 0 rgba(255, 246, 214, 0.8),
    inset 0 -2px 3px rgba(58, 42, 6, 0.5),
    0 2px 3px rgba(8, 7, 15, 0.6);
}

/* buttons in the overlays match the HUD plates */
.overlay button {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 9px 22px;
  color: #37280A;
  background:
    linear-gradient(180deg,
      #F0D089 0%, #C8971E 18%, #9A7014 52%, #6E4E0C 82%, #C8971E 100%);
  border: 1px solid #4A340A;
  border-radius: 4px;
  outline: 1px solid rgba(232, 184, 74, 0.4);
  outline-offset: 1px;
  text-shadow: 0 1px 0 rgba(255, 240, 190, 0.75);
  box-shadow:
    inset 0 2px 0 rgba(255, 246, 214, 0.85),
    inset 0 -3px 4px rgba(58, 42, 6, 0.55),
    0 3px 4px rgba(8, 7, 15, 0.65);
  transition: filter 0.12s, transform 0.06s;
}
.overlay button:hover { filter: brightness(1.14); }
.overlay button:active { transform: translateY(1px); filter: brightness(0.94); }

/* prose on stone needs weight and a shadow it did not need on flat colour */
.overlay:not(.menu-screen) .help-card p,
.overlay:not(.menu-screen) .class-card p,
.overlay:not(.menu-screen) .rite-blurb,
.overlay:not(.menu-screen) .quest-desc,
.overlay:not(.menu-screen) .help-card dd {
  color: rgba(238, 230, 214, 0.94);
}
.overlay:not(.menu-screen) .item-detail b,
.overlay:not(.menu-screen) .stats b { color: #FFE9A8; }


@media (min-width: 821px) {
  /* the loose line under the pack grid was floating on bare stone */
  .overlay:not(.menu-screen) .inv-note,
  .overlay:not(.menu-screen) .hint-line,
  .overlay:not(.menu-screen) > p {
    text-shadow: 0 1px 3px rgba(8,7,15,0.95), 0 0 8px rgba(8,7,15,0.8);
    color: rgba(238, 230, 214, 0.9);
  }
}





/* ------------------------------------------------------- talking to people */

/* A band sitting on top of the HUD, not a box in the middle of the screen.
   A centred modal covers the person who is talking, which is the one thing
   you want to keep looking at - and it stops the world reading as something
   that carries on around the conversation.

   #speak is an .overlay and those centre their contents, so the panel is
   pushed to the bottom here and lifted clear of the 192px panel. The overlay
   itself keeps pointer events so a click anywhere lands on Go on. */
/* .overlay is `flex-direction: column` with `justify-content: center` and a
   near-opaque background, so the two things that matter here are overridden:
   the world stays visible behind the band, and flex-end is on JUSTIFY, not
   align - align-items on a column overlay controls the horizontal axis and
   centring there is what we want. Getting those the wrong way round put the
   panel in the middle of a blacked-out screen. */
#speak {
  background: transparent;
  justify-content: flex-end;
  text-align: left;
}

.speak-panel {
  width: min(82%, 1300px);      /* the HUD's own width, so they line up */
  margin-bottom: 200px;         /* HUD is 192 tall; 8px of air above it */
  padding: 20px 30px 18px;

  /* ONE piece of vellum, stretched to fit. Not tiled - a repeat is readable as
     a repeat however clean the seam is.
     The asset is authored at 2600x358, which is this band's own 7.26:1, so
     `100% 100%` is a 1:1 blit rather than a stretch. It also carries alpha at
     the torn top edge, so the world shows through the tear instead of the
     sheet ending on a hard line. If this band's HEIGHT changes, the asset has
     to be re-cut to match or the tear thickens. */
  background-image: url('../assets/vellum_band.webp');
  background-repeat: no-repeat;
  background-size: 100% 100%;

  border: none;
  border-radius: 2px;
  /* the parchment has its own edge; a drop shadow is what lifts it off the
     grass rather than a border drawn around it */
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.55);
}

/* The body face, not the mono default. Everything a player READS in this
   game is Spectral; the mono is for numbers and the in-world [E] prompts.
   Speech in a monospace face reads as a terminal, which is the one thing a
   villager should not sound like. */
/* Ink, not light. The parchment's middle measures 221 luma, so the near-white
   this used on a dark plate would have been pale text on pale skin. Iron-gall
   brown rather than black - black on vellum reads as printed, and this is
   supposed to be someone talking. */
#speak .speak-panel h3 {
  margin: 0 0 10px;
  font-family: 'Cinzel', Georgia, serif;
  font-size: 16px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  /* Solved against the ASSET, every time the asset changes. The parchment is
     0.75 luma where this line sits - darker than the sheet it replaced, so the
     previous #302210 dropped to 4.2:1 and had to come down again. Small
     uppercase with wide tracking is the least forgiving text on a page. */
  color: #281C0D;
}
/* Scoped to #speak so nothing later in the file can outrank it - `.overlay p`
   is redefined more than once further down, and a plain class selector loses
   to those at equal weight without saying so. */
#speak .speak-panel p {
  margin: 0;
  min-height: 2.9em;          /* so the band does not jump between lines */
  font-family: 'Spectral', Georgia, serif;
  font-size: 24px;
  line-height: 1.5;
  color: #282014;   /* 4.8:1 on this sheet's 0.81 luma; see the note above */
}
#speak-count { color: rgba(90, 72, 44, 0.55) !important; }
.speak-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 12px;
}
#speak-count {
  font-size: 11px;
  letter-spacing: 0.18em;
  color: rgba(185, 169, 142, 0.45);
}
/* .overlay button is a gold plate defined far later in this file; matching it
   is the right answer rather than fighting it. */
.speak-panel button { margin-top: 0; font-size: 12px; padding: 7px 18px; }


/* --------------------------------------------------------------- the book */

/* One panel, five pages. It replaced a help screen and a separate credits
   overlay, and it exists to be a container - the lore keeps arriving and
   bolting another section onto a help screen was how it used to go. */
.book-tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin: 10px 0 2px;
}
/* .overlay button is a gold plate defined far later in this file at the same
   weight, so these need the extra class to survive - the trap that already
   caught the theme picker and the chant toggle. */
.book-tabs button {
  margin-top: 0;
  font-family: 'Cinzel', Georgia, serif;
  font-size: 12px;
  padding: 7px 16px;
}
.book-tabs button:not(.on) { filter: brightness(0.52) saturate(0.45); }
.book-tabs button:not(.on):hover { filter: brightness(0.8) saturate(0.7); }
.book-tabs button.on {
  outline: 2px solid rgba(255, 246, 214, 0.55);
  outline-offset: 2px;
}

/* Only the open page is in the flow. `display:none` rather than visibility,
   so a hidden page does not hold the scroll height of the tallest one. */
.book-page { display: none; }
.book-page.on { display: block; }
.help-cols.book-page.on { display: block; }

/* The songs. Latin gets the italic face and the gloss sits under it at a
   smaller size - a translation set at the same weight as the text competes
   with it, and the Latin is the thing being sung. */
.song-card .latin {
  font-family: 'Spectral', Georgia, serif;
  font-style: italic;
  font-size: 16px;
  line-height: 1.7;
  color: var(--kindled);
  margin: 12px 0 2px;
}
.song-card .gloss {
  font-size: 14px;
  line-height: 1.6;
  color: rgba(210, 198, 176, 0.72);
  margin: 0 0 4px;
}
.song-card h3 small {
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: none;
  color: rgba(185, 169, 142, 0.6);
}
.book-chant { justify-content: flex-start; margin-top: 12px; }
.book-chant button { margin-top: 0; font-size: 12px; padding: 6px 15px; }
.book-chant button:not(.on) { filter: brightness(0.52) saturate(0.45); }
.book-chant button.on {
  outline: 2px solid rgba(255, 246, 214, 0.55);
  outline-offset: 2px;
}


/* ------------------------------------------------- the book, page plumbing */

/* Id-scoped so nothing later in the file can outrank them. `.book-page.on` is
   (0,2,0) and loses to anything like `.overlay:not(.menu-screen) .thing`,
   which is (0,3,0) and appears more than once further up. */
#help .book-page { display: none; }
#help .book-page.on { display: block; }

/* SINGLE COLUMN inside the book.
   `.help-cols` is `column-count: 2`, and multicol inside a height-capped
   scroll container is the single most fragile layout in this file - it is the
   likeliest reason a page renders with nothing in it while its markup is
   perfectly present. Prose also reads better in one column than in two narrow
   ones, so there is nothing lost. */
#help .help-cols.book-page { column-count: 1; }

/* The credits used to be their own overlay, where `.credits-body` was the
   scroller. Inside the book it is a scroller INSIDE a scroller, and a nested
   height-capped overflow box is the other way a page collapses to nothing.
   The book's own scroll is the only one that should exist. */
#help .credits-body {
  max-height: none;
  overflow: visible;
  width: auto;
  margin: 0;
}
#help .credits-cols { column-count: 1; }

/* A definition list inside multicol can be dropped by some engines. With the
   single column above this is belt and braces, but it costs nothing. */
#help .book-page dl { break-inside: avoid; }

/* The opening lives on the story page now, as a card with a button in it
   rather than a menu line. It keeps the `menu-opening` id, because probeIntro
   swaps that element's text and data-act when the film is found beside the
   marker - and it does that by getElementById, not through the cache. */
#help .book-open {
  margin-top: 4px;
  font-family: 'Cinzel', Georgia, serif;
  font-size: 12px;
  padding: 8px 18px;
}
#help .song-missing {
  padding: 14px 4px;
  color: rgba(185, 169, 142, 0.6);
  font-size: 14px;
  cursor: default;
}
#help .song-missing code {
  color: var(--kindled);
  font-size: 13px;
}

/* The transport. Five buttons in a row with the play button larger than the
   rest - a row of identical controls makes you read the glyphs before you can
   press the one you want. */
#help .player-now {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
}
#help .player-now #song-title {
  font-family: 'Cinzel', Georgia, serif;
  font-size: 17px;
  color: var(--kindled);
}
#help .player-bar { gap: 8px; }
#help .player-btn {
  width: 40px;
  min-width: 40px;
  padding: 8px 0;
  font-size: 13px;
}
#help .player-main {
  width: 54px;
  min-width: 54px;
  padding: 10px 0;
  font-size: 15px;
}
#help .player-time { margin-left: auto; }

/* One lyric block at a time. They all live in the markup so the panel can
   never come up empty; only which is visible is decided at runtime. */
#help .song-words { display: none; }
#help .song-words.on { display: block; }


/* ------------------------------------------------- the songs page, layout */

/* List left, words right. Stacked, the sixteen rows pushed the Latin a full
   screen below the transport, so choosing a track and reading it were never
   visible at the same time. */
.song-split {
  display: grid;
  grid-template-columns: minmax(280px, 0.9fr) minmax(0, 1.4fr);
  gap: 14px;
  align-items: start;
}
@media (max-width: 900px) {
  .song-split { grid-template-columns: 1fr; }
}
#help .song-col { padding-top: 14px; }

/* The seek bar. Tall enough to hit - a 3px target is fine to look at and
   miserable to click - with the visible line drawn inside it. */
#help .player-seek {
  position: relative;
  height: 16px;
  display: flex;
  align-items: center;
  cursor: pointer;
  margin: 12px 0 2px;
}
#help .player-seek::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 3px;
  background: rgba(74, 66, 112, 0.55);
}
#help #song-fill {
  position: relative;
  height: 3px;
  background: var(--kindled);
  width: 0;
}

/* The list. `list-style: none` is the whole reason this block matters - the
   rows came up as a numbered ol with the three spans run together. */
#help .song-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 46vh;
  overflow-y: auto;
}
#help .song-list li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 8px 6px;
  border-bottom: 1px solid rgba(74, 66, 112, 0.28);
  cursor: pointer;
  color: rgba(220, 208, 186, 0.72);
  text-align: left;
}
#help .song-list li:last-child { border-bottom: none; }
#help .song-list li:hover { color: rgba(240, 232, 214, 0.95); }
#help .song-list li.on { color: var(--kindled); }
#help .song-n {
  font-family: 'Cinzel', Georgia, serif;
  font-size: 11px;
  letter-spacing: 0.1em;
  color: rgba(185, 169, 142, 0.45);
  min-width: 20px;
}
#help .song-t {
  flex: 1;
  font-family: 'Spectral', Georgia, serif;
  font-size: 15px;
}
#help .song-x {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.4);
  white-space: nowrap;
}
/* the one actually sounding, as against merely chosen */
#help .song-list li.sounding .song-n::after {
  content: ' \25B8';
  color: var(--kindled);
}

/* The words. `pre` keeps the verse breaks the score wrote, with no markup
   needed in the lyric itself. */
#help .latin-block {
  margin: 12px 0 0;
  font-family: 'Spectral', Georgia, serif;
  font-style: italic;
  font-size: 15px;
  line-height: 1.8;
  color: var(--kindled);
  white-space: pre-wrap;
  word-break: break-word;
  text-align: left;
}
#help .song-words .gloss {
  font-size: 14px;
  line-height: 1.7;
  color: rgba(210, 198, 176, 0.72);
  margin: 0;
  text-align: left;
}
#help #song-words { max-height: 56vh; overflow-y: auto; }
#help .song-missing {
  padding: 14px 4px;
  color: rgba(185, 169, 142, 0.6);
  font-size: 14px;
  cursor: default;
}

/* The gentler-road offer. Hidden until a third fall on one tier, shown once
   per pilgrim, and set apart from the Rise button so it never looks like the
   default action - taking it is a decision, not a reflex. */
.fallen-offer { display: none; }
.fallen-offer.on {
  display: block;
  max-width: 460px;
  margin: 6px auto 2px;
  padding: 14px 18px 6px;
  border: 1px solid rgba(74, 66, 112, 0.8);
  border-radius: 3px;
  background: rgba(16, 14, 34, 0.6);
}
#fallen-easier-note {
  margin: 0 0 4px;
  font-size: 14px;
  line-height: 1.6;
  color: rgba(220, 208, 186, 0.8);
}

/* ------------------------------------------------ the difficulty picker */

/* Three plates on the creation screen. Each carries the name, the plain word
   under it, and one line saying what changes - because a clever name is worth
   nothing to somebody who cannot tell which is which. */
.difficulty {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  width: min(720px, 92vw);
  margin: 4px auto 2px;
}
@media (max-width: 700px) { .difficulty { grid-template-columns: 1fr; } }

/* NOT A BRASS PLATE ANY MORE.
   These inherit `.overlay button`, which is a bright gold gradient - and
   three lines of small type on bright textured gold is the least readable
   pairing available, whichever ink is used on it. Dark ink was an
   improvement and still not comfortable.
   The class cards eighteen pixels above are dark panels with light text and
   they read perfectly at a glance. This is the same choice, so it should
   look the same: dark ground, limestone text, and gold spent on the BORDER
   of the one that is picked rather than on all three at once. Which also
   makes the selection obvious, where before it was one gold plate among
   three gold plates. */
#create .difficulty button {
  margin-top: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 14px;
  text-align: left;
  line-height: 1.4;
  background: #14122A;
  background-image: none;
  border: 1px solid var(--vault);
  box-shadow: none;
  text-shadow: none;
}
/* WAS brightness(0.52) saturate(0.45), which took the unpicked settings to
   roughly a quarter contrast against their own plate - unreadable on a phone
   and, as it turns out, never comfortable on a monitor either. The unpicked
   ones still have to recede, but a choice you cannot read is not a choice. */
/* The unpicked ones recede by OPACITY, not by a brightness filter - a filter
   dimmed the text and its ground together, so the contrast between them never
   improved however far it was wound back. */
#create .difficulty button:not(.on) { filter: none; opacity: 0.62; }
#create .difficulty button:not(.on):hover { opacity: 0.85; border-color: var(--worn); }
#create .difficulty button.on {
  border-color: var(--accent, #F2C14E);
  background: #1B1834;
  opacity: 1;
}
#create .difficulty button.on {
  outline: 2px solid rgba(255, 246, 214, 0.55);
  outline-offset: 2px;
}
/* DARK INK ON GOLD. These sit on a bright brass plate, and the text was
   inheriting the pale limestone used everywhere else in the game - pale on
   gold is the lowest-contrast pairing in the palette, which is why it has
   never been comfortable on a monitor either. Near-black instead, and the
   sizes up a step: 14/10/12 was set for a plate read at arm's length. */
#create .diff-name {
  font-family: 'Cinzel', Georgia, serif;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent, #F2C14E);
}
#create .diff-plain {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--worn, #B9A98E);
  opacity: 0.85;
}
#create .diff-blurb {
  font-family: 'Spectral', Georgia, serif;
  font-size: 14px;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--limestone, #DCD0BA);
  opacity: 0.92;
}

/* --------------------------------------------------- threads in the log */

/* People at the top, counters underneath. The group headings exist so the two
   are not mistaken for each other - one is something to go and do, the other
   is a record of what is already done. */
#quests .quest-group {
  margin: 14px 0 8px;
  font-family: 'Cinzel', Georgia, serif;
  font-size: 12px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(185, 169, 142, 0.55);
  text-align: left;
}
#quests .quest-group:first-child { margin-top: 0; }

#quests .quest.thread { border-left: 2px solid rgba(74, 66, 112, 0.9); }
#quests .quest.thread .quest-title {
  font-family: 'Cinzel', Georgia, serif;
  color: var(--limestone);
}
/* the one you can go and close right now */
#quests .quest.thread.ready { border-left-color: var(--kindled); }
#quests .quest.thread.ready .quest-title { color: var(--kindled); }
#quests .quest.thread.ready .quest-count { color: var(--kindled); }


/* ---------------------------------------------------------------------
   CHARACTER CREATION ON A PHONE

   `.classes` is a flex ROW of three 232px cards. Measured on a 390px
   screen it laid out 1104px wide and centred, which put the Warder at
   -357 and the Exorcist at 388 - two of the three classes entirely off
   the screen, with no horizontal scroll. You could only ever pick the
   middle one, and nothing said so.

   Wrapping turns the row into a column, which makes the panel much
   taller - fine, because the overlay scrolls now. The two fixes only
   work together.
   --------------------------------------------------------------------- */
@media (max-width: 900px), (max-height: 520px) {
  #create .classes {
    flex-wrap: wrap;
    gap: 10px;
    width: 100%;
  }
  #create .class-card {
    width: 100%;
    max-width: 420px;
    padding: 12px 14px 14px;
  }
  /* A phone shows one card at a time, so the picked one has to be obvious
     without a second card beside it to compare against. */
  #create .class-card.on { box-shadow: 0 0 0 1px var(--accent, #F2C14E); }
  #create .class-card p { font-size: 13px; line-height: 1.5; }
  #create .class-card h3 { font-size: 17px; }

  #create h2 { font-size: 20px; margin-bottom: 2px; }
  #create .field input { width: min(100%, 320px); }
  #create .field.seed input { width: min(100%, 320px); }
  #create .difficulty { flex-wrap: wrap; gap: 8px; }
  #create .row { flex-wrap: wrap; justify-content: center; }
  #create .row button { flex: 1 1 auto; min-width: 140px; }
}


/* =====================================================================
   TOUCH CONTROLS
   ---------------------------------------------------------------------
   Five keys a phone cannot press: E, the two rite slots, right-mouse
   held, and shift. Tapping to move and to attack already worked, so
   this is the whole of what was missing.

   Laid out for ONE THUMB, bottom right, because that is the hand a
   phone gets opened with. ABSOLVE is the largest and the lowest -
   nearest the thumb's resting arc - because it is the thing the game
   is about and it has to be held, not tapped.

   The container takes NO pointer events. Only the buttons do. Without
   that, an invisible box over the bottom third of the screen would
   swallow every tap meant for the floor behind it, and walking would
   stop working in exactly the corner you walk toward most.
   ===================================================================== */
#touch { display: none; }
/* Only while a run is actually being played. It was showing on the main menu,
   over the title art, because nothing ever told it not to - the cluster is a
   sibling of the canvas and the overlays sit above it, so it was visible in
   every gap between them. `body.playing` is set when a run starts and cleared
   whenever an overlay takes the screen. */
body.touch.playing #touch {
  /* ONE ROW along the bottom. The arc used two rows of the screen's height,
     which is the axis a phone has least of - and the gaps between the
     buttons were dead space you could not walk through either, since a tap
     that lands between two circles still lands on the world behind them but
     nowhere useful.
     Order is left to right: DASH, RUN, MEND, KIND, ACT - with ACT largest
     and furthest right, nearest the thumb. */
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 10px;
  /* ABSOLUTE, inside #stage - not fixed to the viewport. #stage is the play
     area and is flex:1, so anchoring here keeps the cluster clear of the HUD
     no matter how tall the HUD gets. Fixed positioning put ABSOLVE on top of
     the Grace globe and the rites on top of the message log. */
  position: absolute;
  left: 0; right: 0;
  /* clear of the HUD, which floats OVER the stage rather than sitting under
     it - so the stage's own bottom edge is no guide at all. */
  bottom: calc(var(--hud-h) + 8px);
  width: auto; height: auto;
  z-index: 9;                 /* under the overlays (10), over the canvas */
  pointer-events: none;
  /* clear of the home bar and the rounded corner on a modern phone */
  padding: 0 calc(10px + env(safe-area-inset-right)) calc(10px + env(safe-area-inset-bottom)) 0;
}

.tbtn {
  position: static;   /* laid out by the row, not placed by hand */
  flex: none;
  pointer-events: auto;
  border-radius: 50%;
  border: 1px solid rgba(242, 193, 78, 0.55);
  background: rgba(20, 18, 42, 0.72);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  color: var(--limestone, #DCD0BA);
  font-family: var(--font-display), serif;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0;
  /* no 300ms tap delay, no double-tap zoom, no text selection on a hold */
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.tbtn:active { background: rgba(242, 193, 78, 0.30); }

/* 84px, not 64. The absolve is HELD for up to two seconds while the room
   keeps moving, so it is the one button a thumb must not slip off. */
.tbig   { width: 78px; height: 78px; font-size: 12px; }
.trite  { width: 58px; height: 58px; font-size: 14px; }
.tsmall { width: 50px; height: 50px; font-size: 10px; }
/* DOM order is dash, run, rite-b, rite-a, absolve - already left to right. */

/* Held-run is a TOGGLE on touch. Holding a button down with one thumb while
   tapping the floor with the same thumb is not a thing a hand can do. */
#t-run[aria-pressed="true"] {
  background: rgba(242, 193, 78, 0.34);
  border-color: var(--kindled, #F2C14E);
  color: #FFF3D6;
}

/* A rite you do not have yet, or cannot pay for, should look unavailable
   rather than broken when nothing happens. */
.tbtn.spent { opacity: 0.34; }

/* Mercy is available RIGHT NOW. Same gold as the rim the faltering creature
   wears, so the two read as one signal rather than two. */
.tbtn.ready {
  border-color: var(--kindled, #F2C14E);
  box-shadow: 0 0 14px rgba(242, 193, 78, 0.45);
  color: #FFF3D6;
}

/* Landscape is short: pull the cluster in so it does not eat the play area,
   and keep it clear of the HUD's bottom row. */
/* Landscape is short: the row is the same shape, just smaller. */
@media (max-height: 480px) {
  body.touch.playing #touch { gap: 8px; }
  .tbig   { width: 64px; height: 64px; font-size: 11px; }
  .trite  { width: 48px; height: 48px; font-size: 12px; }
  .tsmall { width: 44px; height: 44px; font-size: 9px; }
}


/* =====================================================================
   THE PANELS ON A PHONE
   Same media query the class cards use: narrow OR short, because a
   landscape phone at 844px is above every width breakpoint and needs
   this just as much.
   ===================================================================== */
@media (max-width: 900px), (max-height: 520px) {

  /* --- PACK ---------------------------------------------------------
     .inv-wrap is a three-column grid: 248px doll, the pack, 260px belt -
     about 786px laid out and centred, so on a 390px screen the equipped
     figure fell off the left and the belt off the right. Only the middle
     column was ever visible, which is exactly what you saw.

     One column, in reading order: what you are wearing, then what you
     are carrying, then the belt. The overlay scrolls, so height is
     free and width is not. */
  #inv .inv-wrap {
    grid-template-columns: 1fr;
    gap: 16px;
    justify-items: center;
    width: 100%;
  }
  #inv .doll { width: min(100%, 248px); }

  /* The pack is six fixed 62px cells - 453px laid out, against 366 of usable
     width. FIXED columns cannot shrink, so it overflowed however the wrapper
     was arranged. Fluid cells instead, still six across so the pack holds the
     same shape and nothing about capacity changes. */
  #inv .pack-grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    width: 100%;
    max-width: 330px;
  }
  #inv .cell { height: 42px; }

  /* `white-space: nowrap` on a two-part footer that says "4 carried" and
     "Click or drag to equip - right-click to drop". It cannot wrap, so it
     pushed the whole panel wide. It also says CLICK and RIGHT-CLICK, neither
     of which a phone has. */
  #inv .pack-foot { white-space: normal; flex-wrap: wrap; gap: 4px 12px; max-width: 330px; }

  #inv .item-detail { width: min(100%, 330px); }

  /* --- CHARACTER ----------------------------------------------------
     The attribute rows measured 510px against 390 available. */
  /* --- CHARACTER ----------------------------------------------------
     The stat list is `grid-template-columns: auto auto` with a 44px column
     gap. `auto` sizes to CONTENT, so the grid is as wide as its widest pair
     wants to be and simply overhangs the plate it sits in - the values were
     landing at x=432..510 on a 390px screen, outside the panel and outside
     the window, with nothing to scroll to reach them.

     A fluid first column and a smaller gap keeps the pairs together and
     inside the plate. */
  #sheet dl {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 7px 14px;
    width: 100%;
  }
  /* .sheet-top is a GRID, not a flex row - `flex-direction` on it did
     nothing, which is why the attribute table stayed beside the portrait and
     ran 12px past the right edge. One column stacks it properly. */
  #sheet .sheet-top { display: grid; grid-template-columns: 1fr; justify-items: center; gap: 12px; }
  #sheet .sheet-plate { width: 100%; max-width: 360px; }
  #sheet .attr-table { width: min(100%, 300px); }
  /* the portrait column was 158px; stacked, it stretched to the full plate
     and the figure went to a sliver. Hold its own size. */
  #sheet .sheet-portrait { width: 158px; flex: none; }

  /* --- QUESTS -------------------------------------------------------
     .quest-list is a fixed 620px, centred - so on a 390px screen it ran
     from -115 to 505 and lost a chunk off BOTH sides. It reads fine
     because the text is centred and the middle survived, which is why
     this one looked all right at a glance. */
  #quests .quest-list { width: min(100%, 620px); max-width: 100%; }
  #quests .quest, #quests .quest-head { max-width: 100%; }

  /* --- THE GLOBES ---------------------------------------------------
     .globe is a fixed 108px sitting inside .mount, which is a 256x164
     statue plate. In portrait the HUD squeezes the mount to 76px and the
     globe simply overflowed it - a round ball on a squashed frame, with
     the fill spilling past the brass. That is the wonkiness.

     The statues go on a phone. They are decoration, they are 256px wide
     apiece, and at this size they are two smudges either side of a ball.
     The globes shrink to match what the HUD actually gives them. */
  .mount { width: auto; height: auto; background-image: none; }
  .mount.left, .mount.right { background-image: none; }
  .globe { width: 72px; height: 72px; border-width: 2px; }
  .orb-label { font-size: 12px; }
  .orb-name  { font-size: 8px; letter-spacing: 0.1em; }

  /* --- THE BOOK -----------------------------------------------------
     Credits are a two-column grid of label and name; at this width the
     name column wrapped under a label that had already gone narrow. One
     column, label above name, reads cleanly. */
  #help .book-credits .credit-line {
    grid-template-columns: 1fr;
    gap: 0;
    margin-bottom: 12px;
  }
  #help .book-credits .credit-line b { text-align: left; }

  /* The songs page is a wide player with sixteen rows of Latin, English
     and a seek bar - it cannot be made to work at 390px, and it does not
     need to: the whole score is on the landing page, playable, with the
     translations beside it. Hiding the TAB as well as the page, so there
     is no button that leads somewhere broken. */
  #help #book-songs { display: none; }
  #help .book-page[data-page="songs"] { display: none !important; }
}


/* One hint at a time. `body.touch` is set once at boot from the same check
   that decides whether to take the screen, so this needs no media query -
   a small laptop is still a mouse, and a big tablet is still a finger. */
.hint-touch { display: none; }
body.touch .hint-mouse { display: none; }
body.touch .hint-touch { display: inline; }

/* the cell being held, so a long press looks like it is doing something
   rather than like a tap that failed */
.cell.holding { border-color: var(--kindled, #F2C14E); background: #1B1834; }


/* --- HUD on a phone, second pass ------------------------------------- */
@media (max-width: 900px), (max-height: 520px) {
  /* The panel is min(82%, 1300px). 82% of 390 is 320px, and seven 40px belt
     slots plus their gaps need 304 of it before the two globes are placed -
     so the slots were being squeezed to 30x30 and the globes to 76. Give the
     panel the width a phone actually has. */
  /* Edge to edge. `min(82%, 1300px)` is right for a monitor and leaves a
     stripe of world showing either side of the panel on a phone, which reads
     as the HUD having failed to load rather than as a design. */
  #hud { width: 100%; }

  /* THE KEEPER'S STALL. Two fixed 380px columns - 782px with the gap - on a
     390px screen. For sale and For trade sit one above the other now, and the
     lists lose their fixed 318px height so two of them do not fill the
     screen twice over. */
  #shop .shop-wrap { grid-template-columns: 1fr; gap: 14px; width: 100%; }
  #shop .shop-col { width: 100%; min-width: 0; }
  #shop .shop-list { height: auto; max-height: 34vh; }
  /* LEFT AT 76. `.mount .globe` is two classes and beats a bare `.globe`
     whatever the order, which is why an earlier attempt at 84 silently did
     nothing. Once the selector was fixed and 84 actually applied, it turned
     out 76 was right all along: at 84 the globes overlap the belt and the
     message log, and the right one is clipped by the panel edge. The original
     rule was sized to the space, not guessed. */
  .orb-label { font-size: 14px; }
  /* The name under the number is 8px and unreadable, and it is telling you
     what the colour already says - gold is vitality, blue is grace. Dropping
     it gives the number the room instead. Both are spelled out in full on the
     character sheet. */
  .orb-name { display: none; }

  /* The belt lives in the centre column, which the HUD's grid leaves 190px
     wide - seven 40px slots need 304. They were being squeezed to 30x30.
     Wrapping to two rows lets each slot keep a thumb-sized 36px instead of
     seven unreadable ones in a line.
     flex: none, or they would just shrink again inside the new rows. */
  /* The centre column measured 170px tall inside a 168px HUD, so it spilled
     BOTH ways: the belt's first row went off the top of the panel - which is
     why only 6, 7 and 8 were visible - and the log came down 25px onto the
     CHAR/QUESTS buttons.
     The panel gets the height its own contents need. Two rows of four slots
     shows all eight. */
  /* HEIGHT: AUTO, not a number I picked. 206px was a guess at what the
     contents need, and it was wrong in both directions at once: the centre
     column still started 19px ABOVE the panel, so the belt's first row sat
     out on the game world, and the plates ended 39px short of the bottom,
     leaving a band of dead stone.
     Letting the grid size to its own rows puts every child inside the panel
     and makes the panel exactly as tall as it has to be. */
  /* "137 / 137" was wrapping to two lines inside the globe. */
  .orb-label { font-size: 13px; white-space: nowrap; }
}


/* --- the prayer on the fallen screen ---------------------------------- */
/* Quieter than the note above it. The Latin carries the weight; the gloss
   sits under it small and unemphatic, the way the book sets the chants. */
.fallen-prayer {
  margin: 14px 0 4px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  line-height: 1.5;
}
.fallen-prayer .latin {
  font-family: var(--font-display), serif;
  font-size: 26px;
  letter-spacing: 0.09em;
  color: var(--kindled, #F2C14E);
  opacity: 0.9;
}
.fallen-prayer .gloss {
  font-family: var(--font-body), serif;
  font-style: italic;
  font-size: 17px;
  letter-spacing: 0.02em;
  color: var(--worn, #B9A98E);
  opacity: 0.75;
}
@media (max-width: 900px), (max-height: 520px) {
  .fallen-prayer .latin { font-size: 21px; }
  .fallen-prayer .gloss { font-size: 15px; }
}

/* --- the fallen screen reads at a distance ----------------------------
   It was 28/14/14 on a screen with nothing else on it - the one moment in
   the game with the whole window to itself and the smallest type in it.
   The heading and the note were inheriting the sizes used inside busy
   panels, where 14px is a reasonable compromise; here it is just small. */
#fallen h2 { font-size: 38px; }
#fallen #fallen-note { font-size: 19px; line-height: 1.6; }
#fallen button { font-size: 17px; padding: 12px 26px; }
#fallen #fallen-easier-note { font-size: 16px; }
@media (max-width: 900px), (max-height: 520px) {
  #fallen h2 { font-size: 27px; }
  #fallen #fallen-note { font-size: 16px; }
  #fallen button { font-size: 15px; }
  #fallen #fallen-easier-note { font-size: 14px; }
}

/* --- panel body text, everywhere --------------------------------------
   14px was the base for every paragraph inside an overlay. It is legible
   and it is not comfortable, which is the complaint that keeps coming
   back in different clothes - the difficulty plates, the stat list, this
   screen. 16px across the board; the panels were remeasured after and
   nothing overflows at any size. */
.overlay p { font-size: 16px; }
.overlay button { font-size: 15px; }



/* --- LEVEL UP, clear of the touch row ---------------------------------
   `bottom: 200px` was chosen when the bottom right of the screen was empty.
   The touch cluster now sits at `var(--hud-h) + 8px` and its tallest button
   is 78px, so 200px lands the badge squarely behind ACT and MEND - which is
   exactly what the screenshot shows.

   Stacked above the row rather than moved to the top of the screen. The top
   left is the tier and bell status, the top right is the minimap, and the
   badge is something you want to notice while fighting - putting it at the
   far end of the screen from your thumb makes it easier to miss, not harder.
   --------------------------------------------------------------------- */
@media (max-width: 900px), (max-height: 520px) {
  #levelup { bottom: calc(var(--hud-h) + 96px); }
}
/* Landscape runs a smaller cluster - 64px instead of 78 - so it needs less
   clearance, and vertical space is the thing it has least of. */
@media (max-height: 480px) {
  #levelup { bottom: calc(var(--hud-h) + 82px); }
}


/* =====================================================================
   PANELS OVER THE WORLD, not instead of it
   ---------------------------------------------------------------------
   .overlay was inset:0 with a 93%-opaque wash: a full screen that
   happened to be drawn on top. Reading the book that is right. Opening
   your pack it is not - it takes you out of the room you are standing in.

   Three shapes now:
     .as-left    CHAR      slides against the left edge
     .as-right   INV RITES slides against the right edge
     .as-card    KEEPER QUESTS  a card in the middle
   Everything else - the book, the map, the fallen screen, the boon, the
   ending, the menu - is untouched and stays full screen. Those are
   reading and moments, not tools.

   The material is the panel's own stone, which the interface already
   uses; this only changes the shape and what you can see past it.
   ===================================================================== */

#sheet.as-left, #inv.as-right, #rites.as-right, #shop.as-card, #quests.as-card {
  /* The wash goes. A slight darkening keeps the panel legible against a
     lit floor without hiding what is walking toward you - which is the
     whole reason these do not pause any more. */
  background-color: rgba(11, 10, 24, 0.34);
  background-image: none;
  align-items: stretch;
  justify-content: stretch;
  padding: 0;
}

/* The frame itself. One rule, three positions. */
.panel-sheet {
  position: absolute;
  top: 0; bottom: 0;
  width: min(430px, 92vw);
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px 22px;
  overflow-y: auto;
  overscroll-behavior: contain;
  text-align: center;

  /* the same stone the HUD is cut from, so the interface reads as one
     fitting rather than as a website over a game */
  background-color: #14122A;
  background-image:
    linear-gradient(rgba(20, 18, 42, 0.86), rgba(20, 18, 42, 0.86)),
    url('../assets/undercroft_wall.webp');
  background-size: auto, 420px auto;
  box-shadow: 0 0 44px rgba(0, 0, 0, 0.75);
}
/* The border is the tell that this is a fitting in a building: a bronze
   hairline, a gold rule inside it, and the edge that faces the room
   carrying the weight. */
#sheet .panel-sheet {
  left: 0;
  border-right: 1px solid rgba(242, 193, 78, 0.42);
  box-shadow: 3px 0 0 rgba(140, 106, 52, 0.35), 0 0 44px rgba(0, 0, 0, 0.75);
}
#inv .panel-sheet, #rites .panel-sheet {
  right: 0;
  border-left: 1px solid rgba(242, 193, 78, 0.42);
  box-shadow: -3px 0 0 rgba(140, 106, 52, 0.35), 0 0 44px rgba(0, 0, 0, 0.75);
}

/* KEEPER and QUESTS: a card, still paused, still centred. */
#shop.as-card, #quests.as-card { align-items: center; justify-content: safe center; padding: 18px 12px; }
#shop .panel-sheet, #quests .panel-sheet {
  position: relative;
  top: auto; bottom: auto;
  width: min(720px, 94vw);
  max-height: 88vh;
  border: 1px solid rgba(242, 193, 78, 0.42);
  border-radius: 2px;
  box-shadow: 0 0 0 3px rgba(140, 106, 52, 0.30), 0 18px 60px rgba(0, 0, 0, 0.7);
}

/* On a phone the panel IS the screen, but it keeps the desktop shape -
   still anchored to its edge, still framed, with a sliver of world left
   showing so you can see it is still there and tap out of it. */
@media (max-width: 900px), (max-height: 520px) {
  .panel-sheet { width: 88vw; padding: 16px 14px; }
  #shop .panel-sheet, #quests .panel-sheet { width: 94vw; max-height: 84vh; }
}


#quests.as-card .quest-list { width: 100%; max-width: 100%; }
#shop.as-card .shop-wrap { grid-template-columns: 1fr 1fr; }
@media (max-width: 900px), (max-height: 520px) {
  #shop.as-card .shop-wrap { grid-template-columns: 1fr; }
}


/* =====================================================================
   THE SLIDE-OUTS STOP ABOVE THE CONTROLS
   ---------------------------------------------------------------------
   They were top:0 bottom:0, so on desktop a 430px panel sat over the
   left third of the HUD - the vitality globe and half the belt behind a
   character sheet.

   Worse on a phone, and this one was mine: the panel covered the touch
   cluster. The buttons were still DISPLAYED, because the live panels
   deliberately do not count as covering the game - but the panel is
   z-index 10 and the cluster is 9, so a tap where ABSOLVE is landed on
   a pack cell instead. Visible and unreachable is the worst of both.

   So the live panels get the play area and nothing else. --hud-h is
   measured at runtime, so this follows the panel however tall it gets.
   ===================================================================== */
#sheet.as-left .panel-sheet,
#inv.as-right .panel-sheet,
#rites.as-right .panel-sheet {
  bottom: calc(var(--hud-h) + 6px);
}
@media (max-width: 900px), (max-height: 520px) {
  /* and clear of the touch row too - same 96px the LEVEL UP badge uses,
     so the two agree about where the play area ends */
  #sheet.as-left .panel-sheet,
  #inv.as-right .panel-sheet,
  #rites.as-right .panel-sheet {
    bottom: calc(var(--hud-h) + 96px);
  }
}
@media (max-height: 480px) {
  #sheet.as-left .panel-sheet,
  #inv.as-right .panel-sheet,
  #rites.as-right .panel-sheet {
    bottom: calc(var(--hud-h) + 82px);
  }
}

/* =====================================================================
   CHAR AND PACK, CHUNKIER
   ---------------------------------------------------------------------
   These two are read at a glance mid-fight now that the world keeps
   running behind them, which is a different job from being read at
   leisure on a paused screen. Bigger and heavier, and the portrait goes
   - it was 300px of the panel's height spent on something you already
   chose and cannot change.
   ===================================================================== */
#sheet.as-left .sheet-portrait { display: none; }

#sheet.as-left h2 { font-size: 24px; }
#sheet.as-left .sheet-sub { font-size: 14px; letter-spacing: 0.16em; }
#sheet.as-left dt { font-size: 14px; font-weight: 600; letter-spacing: 0.08em; }
#sheet.as-left dd { font-size: 17px; font-weight: 700; }
#sheet.as-left .attr-row { font-size: 17px; }
#sheet.as-left .attr-name { font-size: 17px; font-weight: 700; letter-spacing: 0.1em; }
#sheet.as-left .attr-val  { font-size: 19px; font-weight: 700; }
#sheet.as-left .attr-note { font-size: 13.5px; line-height: 1.5; }
#sheet.as-left .attr-plus { font-size: 18px; min-width: 40px; min-height: 40px; }

#inv.as-right h2 { font-size: 24px; }
#inv.as-right .cell { height: 48px; }
#inv.as-right .pack-foot { font-size: 13px; }
#inv.as-right .detail-name { font-size: 17px; font-weight: 700; }
#inv.as-right .detail-sub { font-size: 14px; }
#inv.as-right .detail-affixes { font-size: 14px; line-height: 1.55; }
#inv.as-right .doll-label { font-size: 12px; letter-spacing: 0.12em; }


/* The live overlays PAINT their dimming but catch nothing. The panel stops
   above the controls, but the overlay itself is still inset:0 - so a tap
   where ABSOLVE is was landing on a transparent sheet of #inv rather than on
   the button, and the world behind could not be clicked either. A panel the
   game runs behind has to let you reach the game.
   Only the three live ones. The Keeper and Quests are paused and centred, and
   their overlay should absorb a stray click. */
#sheet.as-left, #inv.as-right, #rites.as-right { pointer-events: none; }
#sheet.as-left .panel-sheet,
#inv.as-right .panel-sheet,
#rites.as-right .panel-sheet { pointer-events: auto; }


/* =====================================================================
   CLICK THE WORLD TO CLOSE, AND STILL REACH THE BUTTONS
   ---------------------------------------------------------------------
   The live overlays catch clicks again - that is how clicking outside
   dismisses them. Which brings back the problem that pointer-events:none
   was solving: the overlay is inset:0 and would sit over the touch
   cluster.

   So the cluster goes ABOVE the overlays instead. It is only ever
   displayed while body.playing is set, and the panels that should block
   it - the book, the menu, the fallen screen - clear that flag, so
   raising it cannot leak into a screen where it does not belong.
   ===================================================================== */
#sheet.as-left, #inv.as-right, #rites.as-right { pointer-events: auto; }
body.touch #touch { z-index: 11; }
/* the world showing past the panel is a dismiss target, so say so */
#sheet.as-left, #inv.as-right, #rites.as-right { cursor: pointer; }
.panel-sheet { cursor: default; }




/* =====================================================================
   WIDE PANELS ON DESKTOP
   ---------------------------------------------------------------------
   The first version was 430px and I spent four passes shrinking the
   contents to fit it - smaller icons, shorter blurbs, a squashed
   figure. That was the wrong end of the problem. The panels are wide
   enough for their own layouts now and the contents go back to the size
   they were designed at.

   820px holds what these were built for: the rite list at its real
   two-column 366px, the pack's figure beside its grid and belt, the
   sheet's attributes beside its stat list. All three then fit in the
   frame with no scrolling at 1280x800.
   ===================================================================== */
#sheet.as-left .panel-sheet,
#inv.as-right .panel-sheet,
#rites.as-right .panel-sheet {
  width: min(820px, 64vw);
  padding: 18px 22px;
}

/* CHAR: attributes beside the stat list. The portrait column is gone, so
   two columns rather than the three this grid was written for. */
/* Two columns, and the attributes get the larger share. Measured three
   ways: an even split gave the notes a 180px measure and they wrapped to
   four lines each (500px tall); stacking them was worse still (+343);
   1.5fr against 1fr lets every note sit on one or two lines and puts the
   sixteen-row stat list beside it rather than under it. */
#sheet.as-left .sheet-top {
  display: grid;
  grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr);
  gap: 20px;
  align-items: start;
  justify-items: stretch;
}
#sheet.as-left .sheet-plate { width: 100%; max-width: none; }
#sheet.as-left .attr-table { width: 100%; }
/* the three boxes across the top stay - they were the good part */
#sheet.as-left .plate-rows {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 0 0 8px;
}
#sheet.as-left .plate-rows > * {
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 2px;
  background: #100E22;
  border: 1px solid var(--vault);
  padding: 6px 4px 5px;
  font-size: 10px;
  letter-spacing: 0.12em;
  text-align: center;
}
#sheet.as-left .plate-rows b,
#sheet.as-left .plate-rows span:last-child { font-size: 22px; font-weight: 700; }
/* Both halves go two-across rather than one-down. The attribute table was
   440px of four stacked rows and the stat list 535px of sixteen; paired up
   they are about 220 and 270, and the taller of the two columns then fits
   the frame with room. Nothing shrinks - it just stops queueing. */
/* Two attributes across, inside the wider half. Measured: one column of
   four is 440px, two of two is about 230 - the notes do wrap at this
   measure, but wrapping two rows is cheaper than stacking four. */
#sheet.as-left .attr-table {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 18px;
  align-items: start;
}
#sheet.as-left .sheet-top > dl {
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto;
  gap: 4px 14px;
}

/* PACK: back to the figure beside the grid beside the belt. */
#inv.as-right .inv-wrap {
  grid-template-columns: 248px minmax(0, 1fr) 260px;
  gap: 20px;
  justify-items: stretch;
}
/* KEEP THE HEIGHT. `.doll` is 248x340 and every slot inside it is
   absolutely positioned, so `height: auto` collapsed it to 2px and the
   painted figure - which is a background on a child - spilled out over the
   pack grid and the belt. It looked like a z-index problem and was not. */
#inv.as-right .doll { width: 248px; height: 340px; }
#inv.as-right .pack-grid { grid-template-columns: repeat(6, 62px); width: auto; max-width: none; }
#inv.as-right .cell { height: 46px; }
#inv.as-right .pack-foot { white-space: nowrap; max-width: none; }
#inv.as-right .item-detail { width: 100%; max-height: none; }

/* RITES: two columns of three, at the size the cards were drawn for. */
#rites.as-right .rite-list { grid-template-columns: repeat(2, 366px); gap: 8px; justify-content: center; }
#rites.as-right .rite { padding: 9px 12px; }
/* 112px was right when the list had the whole window. In a 820px panel at
   1280x800 three rows of it overhang by 85, and the icon is the cheapest
   place to find that - 96 still reads as a roundel rather than a bullet. */
#rites.as-right .rite-icon { width: 88px; height: 88px; }
#rites.as-right .rite-blurb { font-size: 13.5px; line-height: 1.45; }
#rites.as-right .rite.locked .rite-blurb { display: block; }

/* =====================================================================
   ON A PHONE THE PANEL IS THE PAGE
   ---------------------------------------------------------------------
   Full width, and it scrolls - there is no arrangement of a character
   sheet that fits a 390px screen, so pretending otherwise just makes the
   type too small to read. The narrow single-column layouts live here,
   where the constraint is real.
   ===================================================================== */
@media (max-width: 900px), (max-height: 520px) {
  #sheet.as-left .panel-sheet,
  #inv.as-right .panel-sheet,
  #rites.as-right .panel-sheet { width: 100vw; padding: 16px 14px; }

  #sheet.as-left .sheet-top { grid-template-columns: 1fr; gap: 12px; justify-items: center; }
  #sheet.as-left .sheet-plate { max-width: 360px; }
  #sheet.as-left .attr-table { width: min(100%, 320px); }

  #inv.as-right .inv-wrap { grid-template-columns: 1fr; gap: 16px; justify-items: center; }
  #inv.as-right .doll { width: min(100%, 248px); height: 340px; }
  #inv.as-right .pack-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); width: 100%; max-width: 330px; }
  #inv.as-right .cell { height: 42px; }
  #inv.as-right .pack-foot { white-space: normal; flex-wrap: wrap; gap: 4px 12px; max-width: 330px; }

  #rites.as-right .rite-list { grid-template-columns: 1fr; }
  #rites.as-right .rite-icon { width: 92px; height: 92px; }
}


/* The last 117px on an 800px-tall screen, taken from chrome rather than
   from content: the panel's own padding, the heading, the boxes at the top
   and the leading on the notes. Nothing that carries a number gets smaller. */
#sheet.as-left .panel-sheet { padding: 12px 20px; gap: 6px; }
#sheet.as-left h2 { font-size: 20px; margin: 0; }
#sheet.as-left .sheet-sub { font-size: 12px; margin: 0; }
#sheet.as-left .sheet-plate { padding: 10px 12px; }
#sheet.as-left .plate-rows { gap: 6px; margin: 0 0 6px; }
#sheet.as-left .plate-rows > * { padding: 4px 4px 3px; }
#sheet.as-left .plate-rows b,
#sheet.as-left .plate-rows span:last-child { font-size: 20px; }
#sheet.as-left .attr-row { padding: 2px 0; }
#sheet.as-left .attr-note { line-height: 1.3; margin: 1px 0 0; }
#sheet.as-left .points-line { margin: 2px 0 0; }
#rites.as-right .panel-sheet { padding: 12px 20px; gap: 4px; }
#rites.as-right h2 { margin: 0; }


/* --- three things the first fitted version got wrong ------------------ */

/* 1. The Back button came back when I stripped the compression rules. The
      three live panels are dismissed by clicking the world. */
#sheet.as-left > .panel-sheet > button,
#inv.as-right > .panel-sheet > button,
#rites.as-right > .panel-sheet > button { display: none; }

/* 2. Values were landing on top of the next label. The four-track grid
      used `auto` for the value columns, and `auto` will not shrink below
      its content - so a long one (the spire seed is eight digits) pushed
      straight through its neighbour. minmax(0, max-content) lets it give,
      and right-aligning pins each value to its own label. */
#sheet.as-left .sheet-top > dl {
  grid-template-columns: minmax(0, 1fr) minmax(0, max-content) minmax(0, 1fr) minmax(0, max-content);
  gap: 4px 10px;
  align-items: baseline;
}
#sheet.as-left .sheet-top > dl dd {
  text-align: right;
  overflow-wrap: anywhere;
  min-width: 0;
}
#sheet.as-left .sheet-top > dl dt { min-width: 0; }

/* 3. The BASE heading is a row, not a cell - it was taking the first slot
      of the two-across attribute grid and leaving a hole beside it. */
#sheet.as-left .attr-head-row { grid-column: 1 / -1; }


/* TWO tracks, not four. The stat list sits in the narrower half of the
   panel - about 215px - and two label/value pairs across that is 50px a
   cell, which is why "Archangel" and "Principality at 10" were sitting on
   top of each other. Sixteen rows down a single column is 350px, and the
   panel had 130px of unused height going spare, so it costs nothing. */
#sheet.as-left .sheet-top > dl {
  grid-template-columns: minmax(0, 1fr) minmax(0, max-content);
  gap: 3px 12px;
}


/* PACK needs more width than the other two, and it is arithmetic rather
   than taste: 248px figure + 260px belt + 20px gaps leaves 252 for a pack
   grid that is six 62px cells and its gaps - 397. So the belt was landing
   on top of the grid. 1000px gives the three columns the 945 they add up
   to, with a little over. */
#inv.as-right .panel-sheet { width: min(1000px, 78vw); }
#inv.as-right .inv-wrap { grid-template-columns: 248px minmax(397px, 1fr) 260px; }
#inv.as-right .pack-foot { white-space: normal; }


/* --- PACK on desktop: the belt goes under the squares ------------------
   Three columns across put the belt detail out on the right where the eye
   has to travel for it. Under the grid it reads in the order you use it -
   figure, then what you are carrying, then what is on the belt - and the
   panel comes back down to 820px with the other two. */
#inv.as-right .panel-sheet { width: min(820px, 64vw); }
#inv.as-right .inv-wrap {
  grid-template-columns: 248px minmax(397px, 1fr);
  grid-template-rows: auto auto;
  gap: 16px 20px;
  align-items: start;
}
#inv.as-right .doll { grid-column: 1; grid-row: 1 / span 2; }
#inv.as-right .inv-wrap > div:not(.doll):not(.item-detail) { grid-column: 2; grid-row: 1; }
#inv.as-right .item-detail { grid-column: 2; grid-row: 2; width: 100%; }

/* =====================================================================
   ON A PHONE THE PANEL IS A PAGE
   ---------------------------------------------------------------------
   Full width AND full height - over the HUD and the touch row, because
   there is no world left beside it to reach anyway. Which means it needs
   a way out that is not "tap the world", so the Back button comes back
   here and only here.
   ===================================================================== */
@media (max-width: 900px), (max-height: 520px) {
  #sheet.as-left .panel-sheet,
  #inv.as-right .panel-sheet,
  #rites.as-right .panel-sheet {
    left: 0; right: 0;
    width: 100vw;
    top: 0;
    bottom: 0;                    /* over the HUD, not stopping above it */
    border: none;
    box-shadow: none;
    padding: 16px 14px 20px;
  }

  /* the way out. Hidden on desktop, where clicking the world does it. */
  #sheet.as-left > .panel-sheet > button,
  #inv.as-right > .panel-sheet > button,
  #rites.as-right > .panel-sheet > button {
    display: block;
    width: min(100%, 320px);
    margin: 14px auto 0;
    padding: 12px 20px;
    font-size: 16px;
  }

  /* single column, everything stacked, and it scrolls */
  #inv.as-right .inv-wrap {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    justify-items: center;
  }
  #inv.as-right .doll { grid-column: 1; grid-row: auto; }
  #inv.as-right .inv-wrap > div:not(.doll):not(.item-detail) { grid-column: 1; grid-row: auto; }
  #inv.as-right .item-detail { grid-column: 1; grid-row: auto; max-width: 360px; }
}


/* --- and the phone resets, LAST so they win ---------------------------
   The desktop grids were appended after the earlier mobile block, so they
   were still applying at 390px: attributes two across, the stat list in
   two tracks with max-content values, and a three-box header. All of it
   wider than the screen. Order matters more than intent in one stylesheet
   that has been appended to all day - so these go at the end. */
@media (max-width: 900px), (max-height: 520px) {
  #sheet.as-left .sheet-top { grid-template-columns: 1fr; }
  #sheet.as-left .attr-table { display: block; }
  #sheet.as-left .sheet-plate { max-width: none; width: 100%; }
  #sheet.as-left .plate-rows { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
  #sheet.as-left .plate-rows > * { padding: 5px 2px; font-size: 9px; }
  #sheet.as-left .plate-rows b,
  #sheet.as-left .plate-rows span:last-child { font-size: 17px; }
  #sheet.as-left .sheet-top > dl {
    grid-template-columns: minmax(0, 1fr) minmax(0, auto);
    gap: 3px 10px;
  }
  #sheet.as-left .sheet-top > dl dd { text-align: right; overflow-wrap: anywhere; }

  #rites.as-right .rite-list { grid-template-columns: 1fr; }
  #rites.as-right .rite-icon { width: 88px; height: 88px; }
}


/* =====================================================================
   THE SLIDE — desktop only
   ---------------------------------------------------------------------
   A transition needs a starting state to run from, and these overlays go
   from `display: none` to `display: flex` in one frame - the browser has
   nothing to animate between. So the three live panels stay DISPLAYED at
   all times and are hidden with visibility and opacity instead, which are
   both animatable. `visibility: hidden` also removes them from hit
   testing, so an idle panel cannot catch a click on the world behind it.

   Not on a phone: there the panel is a full page, and a page that slides
   in from the side is a transition pretending to be a place. The media
   query is the inverse of the phone one, so the two cannot both apply.
   ===================================================================== */
@media (min-width: 901px) and (min-height: 521px) {
  #sheet.as-left, #inv.as-right, #rites.as-right {
    display: flex;
    visibility: hidden;
    opacity: 0;
    /* the delay on visibility holds the panel around long enough to slide
       back out, rather than vanishing the instant the class comes off */
    transition: opacity 150ms ease, visibility 0s linear 200ms;
  }
  #sheet.as-left.show, #inv.as-right.show, #rites.as-right.show {
    visibility: visible;
    opacity: 1;
    transition: opacity 130ms ease, visibility 0s;
  }

  #sheet.as-left .panel-sheet,
  #inv.as-right .panel-sheet,
  #rites.as-right .panel-sheet {
    /* out and back, slightly faster leaving than arriving - a panel should
       feel like it is being put away rather than dragged */
    transition: transform 200ms cubic-bezier(0.22, 0.7, 0.28, 1);
    will-change: transform;
  }
  #sheet.as-left:not(.show) .panel-sheet { transform: translateX(-100%); }
  #inv.as-right:not(.show) .panel-sheet,
  #rites.as-right:not(.show) .panel-sheet { transform: translateX(100%); }
  #sheet.as-left.show .panel-sheet,
  #inv.as-right.show .panel-sheet,
  #rites.as-right.show .panel-sheet { transform: translateX(0); }
}

/* Anyone who has asked their system not to animate things gets the panel
   without the journey. */
@media (prefers-reduced-motion: reduce) {
  #sheet.as-left .panel-sheet,
  #inv.as-right .panel-sheet,
  #rites.as-right .panel-sheet { transition: none; transform: none; }
  #sheet.as-left, #inv.as-right, #rites.as-right { transition: none; }
}
