/* level.css — page chrome for the Babylon level. Standalone page, so nothing here is
   scoped: it never loads alongside the three.js game's stylesheets. */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: #0b0d10;
  font-family: "Inter", "Segoe UI", system-ui, sans-serif;
  color: #e8e6e1;
}

#render {
  width: 100%;
  height: 100%;
  display: block;
  outline: none;
  touch-action: none;
}

/* ---- loading overlay ---- */
#loading {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  background: radial-gradient(circle at 50% 42%, #1c2530 0%, #0b0d10 70%);
  z-index: 20;
  transition: opacity 0.55s ease;
}
#loading.gone { opacity: 0; pointer-events: none; }
#loading.failed .load-bar span { animation: none; background: #8d3b32; }

.load-inner { text-align: center; }
.load-title {
  font-size: 26px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #d9c9a3;
  margin-bottom: 10px;
}
#loading-stage {
  font-size: 13px;
  letter-spacing: 0.05em;
  color: #7f8a97;
  margin-bottom: 18px;
  min-height: 18px;
}
.load-bar {
  width: 220px;
  height: 3px;
  background: rgba(255, 255, 255, 0.09);
  border-radius: 3px;
  overflow: hidden;
  margin: 0 auto;
}
.load-bar span {
  display: block;
  width: 40%;
  height: 100%;
  background: linear-gradient(90deg, transparent, #d9c9a3, transparent);
  animation: slide 1.25s ease-in-out infinite;
}
@keyframes slide {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(320%); }
}

/* ---- version panel ----
   Sits over the running scene rather than replacing it, so the thing being versioned stays
   visible behind the blur while you read what changed about it. */
#versions {
  position: fixed;
  inset: 0;
  z-index: 15;
  display: grid;
  place-items: center;
  padding: 3vh 3vw;
  /* Safety net: if the panel ever exceeds the viewport — odd zoom, large default font,
     a browser UI that eats height — the page itself cannot scroll (body is overflow:hidden),
     so without this the "Enter the clearing" button would be unreachable. */
  overflow: auto;
  background: rgba(6, 8, 11, 0.58);
  backdrop-filter: blur(9px) saturate(0.85);
  opacity: 1;
}
/* The transition lives on the hidden state only, so opening is instant and closing fades.
   A fade-IN here stalls half-finished whenever the compositor is throttled — a backgrounded
   tab, or the verification pane while hidden — leaving a permanently see-through panel with
   the level showing through the text. Measured at opacity 0.708 and stuck. */
#versions.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.22s ease, visibility 0s linear 0.22s;
}

.vp {
  width: min(1040px, 100%);
  max-height: 94svh;         /* svh, not vh: mobile toolbars make vh overshoot */
  margin: auto;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, rgba(22, 27, 34, 0.97), rgba(14, 17, 21, 0.97));
  border: 1px solid rgba(217, 201, 163, 0.22);
  border-radius: 11px;
  box-shadow: 0 26px 70px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}

/* ---- tabs ----
   Across the very top of the panel, above the title: the title is part of what the tab
   selects, so it has to sit below the thing that selects it. */
.vp-tabs {
  display: flex;
  gap: 2px;
  flex: none;
  padding: 0 14px;
  background: rgba(0, 0, 0, 0.26);
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.vp-tab {
  position: relative;
  padding: 12px 18px 11px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: #7f8a97;
  background: none;
  border: none;
  border-radius: 6px 6px 0 0;
  cursor: pointer;
  /* The underline is a pseudo-element rather than a border so the inactive tabs keep the
     exact same height — a border that appears on selection nudges the row by a pixel. */
}
.vp-tab::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: -1px;                 /* over the strip's own border, not above it */
  height: 2px;
  background: transparent;
}
.vp-tab:hover { color: #b9bfc7; background: rgba(255, 255, 255, 0.04); }
.vp-tab.is-active { color: #d9c9a3; background: rgba(217, 201, 163, 0.08); }
.vp-tab.is-active::after { background: #d9c9a3; }
.vp-tab:focus-visible { outline: 2px solid #8fa4bd; outline-offset: -3px; }

/* The header is a row: the title block on the left, the one primary action on the right.
   `align-items: start` so the button sits level with the eyebrow rather than floating in the
   middle of a three-line block — it is the first thing on that side of the panel and it
   should line up with the first thing on the other. */
.vp-head {
  display: flex;
  align-items: start;
  gap: 20px;
  padding: 20px 26px 15px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.vp-head-text { flex: 1; min-width: 0; }
.vp-start {
  flex: none;
  margin-top: 2px;
  padding: 11px 22px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: #16324a;
  background: #d9c9a3;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}
.vp-start:hover { background: #e7dbbb; }
.vp-start:focus-visible { outline: 2px solid #8fa4bd; outline-offset: 2px; }
/* Going somewhere else is a different act from resuming where you are, and the button says
   so before you read it: green for travel, bone for "carry on". */
.vp-start.is-travel {
  background: #8fc79a;
  color: #10240f;
}
.vp-start.is-travel:hover { background: #a6d8af; }
.vp-eyebrow {
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #7f8a97;
  margin-bottom: 5px;
}
.vp-title { margin: 0; font-size: 25px; font-weight: 600; color: #d9c9a3; letter-spacing: 0.02em; }
.vp-sub { margin: 5px 0 0; font-size: 12px; color: #8b95a1; }

.vp-body { display: flex; min-height: 0; flex: 1; }

.vp-list {
  width: 218px;
  flex: none;
  border-right: 1px solid rgba(255, 255, 255, 0.07);
  overflow-y: auto;
  padding: 10px 8px;
}
.vp-item {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "id title" "id date";
  gap: 0 9px;
  width: 100%;
  text-align: left;
  padding: 9px 10px;
  margin-bottom: 4px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.vp-item:hover { background: rgba(255, 255, 255, 0.045); }
.vp-item.is-active {
  background: rgba(217, 201, 163, 0.11);
  border-color: rgba(217, 201, 163, 0.34);
}
.vp-item-id {
  grid-area: id;
  align-self: center;
  font-size: 12px;
  font-weight: 700;
  color: #d9c9a3;
  font-variant-numeric: tabular-nums;
}
.vp-item-title { grid-area: title; font-size: 13px; color: #e8e6e1; }
.vp-item-date { grid-area: date; font-size: 10.5px; color: #7f8a97; }
.vp-item.is-current .vp-item-id::after {
  content: "●";
  font-size: 8px;
  color: #7fc08a;
  margin-left: 5px;
  vertical-align: middle;
}

/* ---- the Areas tab -------------------------------------------------------------------
 * The column is places and nothing else, so each row can be big: the whole strip is two
 * items and it should read as a choice between two somewheres rather than as a list. */
.vp-item.is-area-row {
  grid-template-areas: "id id" "title title" "date date";
  gap: 3px 0;
  padding: 13px 12px;
  margin-bottom: 8px;
  border-color: rgba(255, 255, 255, 0.07);
  background: rgba(255, 255, 255, 0.022);
}
.vp-item.is-area-row .vp-item-id {
  font-size: 15px;
  color: #e8e6e1;
  letter-spacing: 0.01em;
}
.vp-item.is-area-row .vp-item-title { font-size: 11.5px; line-height: 1.45; color: #8f9aa6; }
.vp-item.is-area-row .vp-item-date { color: #6d7783; }
/* Where the player actually is, marked in the list as well as on the page — the list is
   what you scan, and "which of these am I in" is the question the tab exists to answer. */
.vp-item.is-area-row.is-here .vp-item-id::before {
  content: "▸ ";
  color: #7fc08a;
}

/* ---- a place's changelog, at the foot of its own page ---------------------------------
 * Under the story, because you learn what somewhere IS before what it was. Rows rather than
 * the notes inlined: five versions of full notes and bugs is a few thousand words, and a
 * page that long buries the description and the travel button above it. */
.vp-history { margin-top: 26px; }
.vp-hrow {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  margin-bottom: 5px;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.02);
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.vp-hrow:hover {
  background: rgba(217, 201, 163, 0.08);
  border-color: rgba(217, 201, 163, 0.3);
}
.vp-hrow-line { display: flex; align-items: baseline; gap: 9px; }
.vp-hrow-id {
  font-size: 12px;
  font-weight: 700;
  color: #d9c9a3;
  font-variant-numeric: tabular-nums;
  flex: none;
}
.vp-hrow-title { font-size: 13px; color: #e8e6e1; }
.vp-hrow-date { font-size: 10.5px; color: #6d7783; margin-left: 21px; }
/* One line of why you would open it. Without it the list is five ids and five dates, which
   says a version happened and nothing about whether it is the one you are looking for. */
.vp-hrow-sum {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 5px 0 0 21px;
  font-size: 11.5px;
  line-height: 1.5;
  color: #98a1ab;
}
.vp-hrow.is-current .vp-hrow-id::after {
  content: "●";
  font-size: 8px;
  color: #7fc08a;
  margin-left: 5px;
  vertical-align: middle;
}

.vp-back {
  display: inline-block;
  margin-bottom: 12px;
  padding: 4px 10px 4px 8px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 5px;
  background: none;
  color: #98a1ab;
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
}
.vp-back:hover { color: #e8e6e1; border-color: rgba(217, 201, 163, 0.34); }

.vp-area-tagline {
  margin: 9px 0 0;
  font-size: 12px;
  letter-spacing: 0.02em;
  text-transform: lowercase;
  color: #8f9aa6;
}
.vp-badge.is-area { background: #6d7783; color: #101820; }
.vp-here {
  font-size: 10.5px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: #7fc08a;
  border: 1px solid rgba(127, 192, 138, 0.4);
  border-radius: 3px;
  padding: 1px 6px;
}

.vp-travel-row { margin: 16px 0 4px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }

/* The way to LOOK at a figure, beside the way to wear one. A link rather than a button
   because it leaves the page, and the difference between "this happens here" and "this takes
   you somewhere" is worth one element type. */
.vp-study {
  font-size: 12px;
  color: #8fa4bd;
  text-decoration: none;
  border-bottom: 1px solid rgba(143, 164, 189, 0.35);
  padding-bottom: 1px;
}
.vp-study:hover { color: #d9c9a3; border-bottom-color: rgba(217, 201, 163, 0.6); }
.vp-study:focus-visible { outline: 2px solid #8fa4bd; outline-offset: 3px; }
.vp-travel {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: #16324a;
  background: #d9c9a3;
  border: 1px solid #d9c9a3;
  border-radius: 6px;
  padding: 9px 18px;
  cursor: pointer;
}
.vp-travel:hover { background: #e8dcbd; }
/* Greyed rather than removed. A disabled control that reads "You are here" tells you
   something; a missing one just looks like the button failed to render. */
.vp-travel:disabled {
  background: none;
  color: #7f8a97;
  border-color: rgba(255, 255, 255, 0.14);
  cursor: default;
}

.vp-detail { flex: 1; min-width: 0; overflow-y: auto; padding: 20px 26px 24px; }
.vp-detail-head { display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap; }
.vp-badge {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #16324a;
  background: #d9c9a3;
  padding: 2px 8px;
  border-radius: 4px;
}
.vp-detail-title { margin: 0; font-size: 19px; font-weight: 600; color: #e8e6e1; }
.vp-date { font-size: 11.5px; color: #7f8a97; margin-left: auto; }
.vp-summary { margin: 12px 0 0; font-size: 13px; line-height: 1.65; color: #b9bfc7; }

.vp-shot {
  margin: 16px 0 4px;
  border-radius: 7px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.09);
  background: #0b0d10;
}
/* Capped, and cropped rather than letterboxed. Uncapped, the shot took 431 px of a 589 px
   panel on a 1280×640 screen and pushed every word of the notes below the fold — the panel
   is a changelog with a picture, not a picture with a caption. Capped tighter again once the
   story moved in under it: at 300 px the premise started below the fold, and a premise
   nobody scrolls to is one nobody reads. */
.vp-shot img {
  display: block;
  width: 100%;
  max-height: min(29svh, 226px);
  object-fit: cover;
  object-position: center 42%;
}
/* Character previews: a figure is shown whole, never cropped to the landscape band — the
   level's letterbox takes his head off. Taller cap too, since the image is portrait and a
   300 px band of it would be his belt.

   The card shrink-wraps the portrait rather than spanning the column: stretched full width
   it was two thirds black bars with a thin strip of character down the middle. */
.vp-shot.is-contain {
  width: fit-content;
  max-width: 100%;
  margin-left: auto;
  margin-right: auto;
  background: radial-gradient(ellipse at 50% 45%, rgba(217, 201, 163, 0.07), transparent 70%), #0b0d10;
}
.vp-shot.is-contain img {
  width: auto;
  /* Same discipline as the level shots: tall enough to read the figure, short enough that
     "What landed" is still on screen under it. At 380 px it was not. */
  height: min(36svh, 290px);
  max-width: 100%;
  max-height: none;
  object-fit: contain;
  object-position: center;
}

/* The premise, between the shot and the changelog. Set warmer and a size larger than the
   note bullets under it: it is prose to be read once, not a list to be scanned, and at the
   bullets' 12.5 px grey it looked like a fourth section of release notes. */
.vp-story {
  margin-top: 20px;
  padding: 15px 17px 14px;
  border-left: 2px solid rgba(217, 201, 163, 0.42);
  border-radius: 0 7px 7px 0;
  background: linear-gradient(90deg, rgba(217, 201, 163, 0.075), rgba(217, 201, 163, 0.015));
}
.vp-group-title.is-story { color: #d9c9a3; }
.vp-story-p {
  margin: 0 0 9px;
  font-size: 13px;
  line-height: 1.7;
  color: #cdd2d8;
}
.vp-story-p:last-of-type { margin-bottom: 0; }

.vp-goal {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin: 13px 0 0;
  padding-top: 11px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}
.vp-goal-label {
  font-size: 10.5px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  font-weight: 600;
  color: #8b95a1;
}
.vp-goal-text {
  font-size: 14px;
  font-weight: 600;
  color: #e8e6e1;
}

.vp-group { margin-top: 20px; }
.vp-group-title {
  margin: 0 0 8px;
  font-size: 10.5px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  font-weight: 600;
}
.vp-group-title.is-new { color: #9ec6a4; }
.vp-group-title.is-bug { color: #d9a68a; }
.vp-group-title.is-known { color: #8b95a1; }

.vp-bullets { margin: 0; padding-left: 17px; }
.vp-bullets li {
  font-size: 12.5px;
  line-height: 1.62;
  color: #b9bfc7;
  margin-bottom: 6px;
}
.vp-bullets li::marker { color: rgba(217, 201, 163, 0.5); }

/* ---- spellbook tab ----
   Same list-and-detail furniture as the version tabs, so switching between them does not
   redraw the panel's shape; only the row contents differ. */
.vp-list-group {
  margin: 12px 4px 5px;
  font-size: 9.5px;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  font-weight: 700;
  color: #7f8a97;
}
.vp-list-group:first-child { margin-top: 2px; }

/* The icon sits where a version id would, so it needs the size a mark reads at rather than
   the 12 px a "V3" is set in. */
.sb-row .vp-item-id,
.eb-row .vp-item-id { font-size: 16px; }
.sb-row .vp-item-title,
.eb-row .vp-item-title { font-size: 12.5px; }

/* A spell's mark is a drawn plate (spellicons.js), not a character, so the cell it sits in
   stops being text: no line-height to centre against, and the canvas carries its own size. */
.vp-item-id.is-icon { display: flex; align-items: center; font-size: 0; }
.sb-icon { display: block; border-radius: 22%; }
/* The row's plate lifts slightly on hover and on the selected row, which is the whole of the
   feedback an icon list needs — the plate is already the thing the eye is on. */
.vp-item:hover .sb-icon { filter: brightness(1.12); }
.vp-item.is-active .sb-icon { filter: brightness(1.2) saturate(1.06); }

/* The heading is now a plate and a name rather than a string, so it has to lay them out. */
.vp-detail-title { display: flex; align-items: center; gap: 11px; }
.sb-title-text { display: block; }

.sb-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.sb-tag {
  padding: 2px 9px 3px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  border: 1px solid transparent;
  border-radius: 999px;
}
.sb-tag.is-none {
  color: #7f8a97;
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.1);
}

/* Two columns rather than a bullet list: these are quantities, and quantities that share a
   left edge can be compared between two spells by flicking between them. */
.sb-stats {
  display: grid;
  grid-template-columns: minmax(96px, auto) 1fr;
  gap: 5px 16px;
  margin: 17px 0 0;
  padding: 13px 15px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 7px;
  background: rgba(0, 0, 0, 0.2);
}
.sb-stat-k {
  font-size: 10.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: #7f8a97;
  align-self: baseline;
}
.sb-stat-v {
  margin: 0;
  font-size: 13px;
  color: #e8e6e1;
  font-variant-numeric: tabular-nums;
}

.sb-negative {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin: 13px 0 0;
  padding: 10px 14px;
  font-size: 12.5px;
  line-height: 1.6;
  color: #e0c4b4;
  border-left: 2px solid rgba(217, 138, 106, 0.55);
  border-radius: 0 6px 6px 0;
  background: rgba(217, 138, 106, 0.08);
}
.sb-negative-label {
  font-size: 10px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  font-weight: 700;
  color: #d9a68a;
}

.vp-group-title.is-mile { color: #c9b6e8; }
.sb-miles { margin: 0; padding: 0; list-style: none; }
.sb-mile {
  display: grid;
  grid-template-columns: 44px 1fr;
  gap: 10px;
  align-items: baseline;
  padding: 6px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.sb-mile:first-child { border-top: none; }
.sb-mile-lv {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #16324a;
  background: rgba(201, 182, 232, 0.85);
  border-radius: 4px;
  padding: 2px 0;
  text-align: center;
}
.sb-mile-text { font-size: 12.5px; line-height: 1.55; color: #b9bfc7; }

.sb-mastery { margin: 14px 0 0; font-size: 11.5px; line-height: 1.6; color: #7f8a97; }

/* The live plates — the cast in the Spellbook (spellpreview.js) and the wolf in the bestiary
   (enemypreview.js), both standing on darkstage.js. One set of rules for both, because they
   are one plate on two pages of the same book: two sets would drift, and a book whose
   illustrations are different sizes on different pages reads as two books.

   Sized like the version shots above them rather than like a viewport: this is an illustration
   on a page, and a page with a 400 px illustration on it is a page whose text starts below
   the fold. */
.sb-preview,
.eb-preview {
  margin: 16px auto 0;
  /* Capped and centred rather than run to the full width of the pane. The subject is a 1.8 m
     figure with a 1.3 m jet in front of him, or a wolf a metre and a half long; stretched to
     a 3.5:1 band, two fifths of the picture is empty floor either side. A plate with margins
     is a book page. */
  max-width: 640px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 7px;
  overflow: hidden;
  background: #0b0c0f;
}
.sb-preview-canvas,
.eb-preview-canvas {
  display: block;
  width: 100%;
  height: min(30svh, 240px);
  /* The canvas is the picture, so it must not also be a control: without this a drag on it
     selects the text either side and a touch scroll on it does nothing. */
  touch-action: none;
  outline: none;
}
.sb-preview-note,
.eb-preview-note {
  padding: 8px 13px 9px;
  font-size: 11px;
  line-height: 1.55;
  color: #7f8a97;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  background: rgba(0, 0, 0, 0.25);
}

/* ---- "play this version" (versionpanel.js) ----
   A footnote, not a call to action: it is the last thing on a version page and it is set
   quieter than the notes above it. The command is the only part that has to be legible enough
   to copy by eye, so it is the only part in a mono face. */
.vp-play {
  margin-top: 22px;
  padding: 11px 13px 12px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.28);
}
.vp-play-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-bottom: 7px;
}
.vp-play-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: #8b95a1;
}
.vp-play-sha {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  color: #d9c9a3;
}
.vp-play-cmd {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px;
  line-height: 1.7;
  color: #b9bfc7;
  /* The two lines are one instruction; wrapping beats a scrollbar on a block this short, and
     `user-select: all` makes the whole thing one click to select. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: all;
}
.vp-play-note {
  margin: 8px 0 0;
  font-size: 11px;
  line-height: 1.55;
  color: #7f8a97;
}

/* ---- the bestiary (enemybook.js) ----
   Everything here sits beside a spellbook rule rather than replacing one: the two tabs share
   the stat grid, the chip row and the footnote, and only the furniture that has no spellbook
   equivalent — a threat rating, a list of tells, a designer's note — is its own. */

/* Threat, as pips. Five of them, filled to the rating: a number out of five gets read as a
   fraction and has to be done as arithmetic, where a row of pips is read at a glance. It
   lives in the chip row, so it is shaped like a chip. */
.eb-threat {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 2px 10px 3px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
}
.eb-threat-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: #8b95a1;
}
.eb-pips { display: inline-flex; gap: 3px; }
.eb-pip {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.14);
}
/* Warm, and the same amber the wolf's eyes are — the colour of the thing that is dangerous. */
.eb-pip.is-on { background: #e0a24a; box-shadow: 0 0 5px rgba(224, 162, 74, 0.55); }

.vp-group-title.is-tell { color: #d9a68a; }
.eb-tells { margin: 0; padding: 0; list-style: none; }
.eb-tell {
  padding: 8px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.eb-tell:first-child { border-top: none; }
/* The name on its own line above the text rather than in a gutter beside it: these are
   sentences, not stats, and a 44 px gutter would set every one of them in a column three
   words wide. */
.eb-tell-name {
  display: block;
  margin-bottom: 3px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #d9a68a;
}
.eb-tell-text { font-size: 12.5px; line-height: 1.6; color: #b9bfc7; }

/* The designer's reason. Cool where the tells are warm, because it is an aside to a different
   reader — the tells are for whoever is about to be bitten, this is for whoever is building
   the next enemy. */
.eb-why {
  margin: 18px 0 0;
  padding: 10px 14px;
  font-size: 12.5px;
  line-height: 1.6;
  color: #b7c4d2;
  border-left: 2px solid rgba(143, 164, 189, 0.5);
  border-radius: 0 6px 6px 0;
  background: rgba(143, 164, 189, 0.07);
}
.eb-why-label {
  display: block;
  margin-bottom: 3px;
  font-size: 10px;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  font-weight: 700;
  color: #8fa4bd;
}

.vp-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  background: rgba(0, 0, 0, 0.22);
}
/* A hint strip, since its button moved to the header. */
.vp-hint { font-size: 11px; color: #7f8a97; }

@media (max-width: 720px) {
  .vp-body { flex-direction: column; }
  .vp-list {
    width: 100%;
    display: flex;
    gap: 6px;
    overflow-x: auto;
    border-right: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  }
  .vp-item { width: auto; flex: none; }
  /* The list is a horizontal strip here, so a category heading becomes a divider standing
     between two runs of spells rather than a line above them. */
  .vp-list-group {
    flex: none;
    align-self: center;
    margin: 0 2px;
    writing-mode: vertical-rl;
  }
  .sb-stats { grid-template-columns: 1fr; gap: 1px 0; }
  .sb-stat-v { margin-bottom: 7px; }
}

/* ---- the corner panel ----
 * Where you are, what the keys are, and what the frame costs. It is the one part of the HUD
 * that is FOR THE PERSON MAKING THE GAME rather than for the person playing it, and it is
 * built to sit down: quieter than the command bar, quieter than the map, no bone-coloured
 * border competing with either. Glass and a hairline, with the numbers dimmest of all — a
 * debug readout that shouts is a debug readout you end up hiding, and then you stop reading
 * it at all. */
#hud {
  position: fixed;
  left: 16px;
  top: 16px;
  padding: 13px 16px 14px;
  /* Cut, not rounded, and with the light along the top edge: the same object as the command
     bar and the map, quieter. It is the third thing on screen that belongs to the game rather
     than to the page, and a rounded card among two chamfered instruments is the odd one. */
  clip-path: polygon(10px 0, calc(100% - 10px) 0, 100% 10px, 100% calc(100% - 10px),
                     calc(100% - 10px) 100%, 10px 100%, 0 calc(100% - 10px), 0 10px);
  background:
    linear-gradient(180deg, rgba(58, 62, 70, 0.72) 0%, rgba(20, 24, 30, 0.7) 5%,
                            rgba(12, 15, 19, 0.66) 60%, rgba(8, 10, 13, 0.68) 100%);
  backdrop-filter: blur(11px) saturate(0.9);
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45);
  font-size: 11.5px;
  line-height: 1.62;
  z-index: 10;
  transition: opacity 0.2s ease;
  max-width: min(84vw, 440px);
}
#hud.hidden { opacity: 0; pointer-events: none; }

/* A bronze rule under the title, the same one the tooltip carries. */
.hud-title::after {
  content: "";
  display: block;
  height: 1px;
  margin: 6px 0 0;
  background: linear-gradient(90deg, rgba(178, 138, 76, 0.6), rgba(178, 138, 76, 0));
}
.hud-title {
  color: rgba(217, 201, 163, 0.9);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-size: 9.5px;
  font-weight: 600;
  margin-bottom: 5px;
}
.hud-ver { margin-bottom: 2px; }
.hud-ver a {
  color: #8fa4bd;
  font-size: 11px;
  text-decoration: none;
  border-bottom: 1px solid rgba(143, 164, 189, 0.32);
}
.hud-ver a:hover { color: #d9c9a3; border-bottom-color: rgba(217, 201, 163, 0.5); }

/* ---- the music switch ----
 * A pill beside the version link. It has to read as OFF at a glance and not merely as
 * unlit — the score does not start itself, so this button is the only evidence in the game
 * that there is any music at all, and a control that looks the same in both states would
 * leave a player who never presses it assuming the game is silent by design. Hence the
 * filled, warm ON state against a dim outlined OFF one. */
.hud-ver { display: flex; align-items: center; gap: 9px; }
.hud-toggle {
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.04em;
  color: #7f8a97;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(143, 164, 189, 0.24);
  border-radius: 999px;
  padding: 1px 8px 2px;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.hud-toggle:hover { color: #d9c9a3; border-color: rgba(217, 201, 163, 0.45); }
.hud-toggle.is-on {
  color: #1a1408;
  background: #d9c9a3;
  border-color: #d9c9a3;
}
.hud-toggle.is-on:hover { background: #e8dcbd; border-color: #e8dcbd; }
.hud-toggle:focus-visible { outline: 1px solid #d9c9a3; outline-offset: 2px; }

.hud-keys { color: #8d97a3; font-size: 11px; margin-top: 3px; }
.hud-keys b { color: #cfd6de; font-weight: 600; }
/* Under its own rule, and dimmest of the three rows: it is a meter, not a sentence. */
#stats {
  color: #6b7480;
  font-variant-numeric: tabular-nums;
  font-size: 10.5px;
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

/* ---- the hunt ----
   What is left of the pack. Condition moved to the life globe on the command bar — a bar in
   the corner is a stat, and how badly he is hurt is not something a player should have to
   look away from the middle of the screen to find out. */
.hud-vitals {
  display: flex;
  align-items: center;
  gap: 8px;
}
.hud-vitals:empty { display: none; }
.vit-pack {
  color: #e0c26a;
  font-size: 11px;
  letter-spacing: 0.04em;
}
.vit-pack.hidden { display: none; }

/* ---- the one line that must not be missed ----
 * Bottom centre, above the command bar: a pack coming out of the treeline is not a stat, and
 * it should not have to be found among stats.
 *
 * It is a LINE, not a card. The boxed version read as a notification from the program — a
 * grey pill with a border, the shape a browser uses to tell you a download finished. This is
 * the game speaking, so it is set as text, over its own shadow, with one rule of light under
 * it and the ends of that rule faded out so it belongs to the screen rather than to a box. */
#alert {
  position: fixed;
  left: 50%;
  /* Clear of the command bar, which owns the bottom of the screen now. A line that lands on
     top of the globes is a line read as part of them. */
  bottom: 158px;
  transform: translateX(-50%) translateY(0);
  z-index: 11;
  padding: 0 8px 9px;
  max-width: min(70vw, 620px);
  text-align: center;
  color: #efe7d3;
  font-size: 15px;
  font-weight: 450;
  letter-spacing: 0.035em;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.95), 0 0 30px rgba(0, 0, 0, 0.8);
  transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.2, 0.8, 0.3, 1);
  pointer-events: none;
}
#alert::after {
  content: "";
  position: absolute;
  left: 12%;
  right: 12%;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg,
    rgba(217, 201, 163, 0), rgba(217, 201, 163, 0.5), rgba(217, 201, 163, 0));
}
#alert.gone { opacity: 0; transform: translateX(-50%) translateY(7px); }
#alert.warn { color: #f6d49a; }
#alert.warn::after {
  background: linear-gradient(90deg, rgba(226, 150, 80, 0), rgba(238, 176, 96, 0.75), rgba(226, 150, 80, 0));
}

/* ---- the command bar ------------------------------------------------------------------
 *
 * Bottom centre: life, six sockets, the roll, the ward. js/actionbar.js has the long version
 * of why it looks like this; the rules that live down here are the ones CSS is responsible
 * for keeping true, and there are three.
 *
 * ONE LIGHT. Everything is lit from above-left. A socket is a HOLE, so its top edge is in
 * shadow and its bottom inner lip catches the light; a keycap is a THING, so it is the other
 * way round. Four-sided bevels are what a stylesheet does when nobody has decided where the
 * sun is.
 *
 * ONE FRAME PER THING. The marks are drawn without their own plates (`plate: false`), so the
 * socket is the only frame in the picture.
 *
 * IT IS READ, NOT CLICKED — except the sockets, which are. The bar as a whole is
 * pointer-events: none so the middle of the screen still swings the sword; the six sockets
 * turn it back on for themselves, because a slot you cannot click is unreachable for a
 * mouse-only player and has nowhere to hang a tooltip.
 */
#actionbar {
  position: fixed;
  left: 50%;
  /* The globes' names hang below them, so the box has to carry that overhang or the labels
     sit on the very edge of the screen — one pixel of clearance, measured. */
  bottom: 14px;
  padding-bottom: 16px;
  transform: translateX(-50%);
  z-index: 10;
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}
#actionbar.hidden { opacity: 0; transform: translateX(-50%) translateY(10px); }
/* Down, and the whole instrument goes with him. */
#actionbar.is-down { filter: saturate(0.18) brightness(0.5); }

.ab-wrap {
  display: flex;
  align-items: flex-end;
  /* Negative, so the globes sink into the plate's raked ends and the whole thing reads as one
     mounted instrument. The plate is drawn wider than the deck for exactly this. */
  gap: 0;
}
.ab-wrap > .ab-globe { margin: 0 -13px 9px; z-index: 2; }

/* The scrim. Nothing on this HUD can be designed against an unknown background — the ground
   under it is sunlit sand one minute and black wood the next — so the bottom of the screen
   gets a gradient of its own and every contrast decision above is made against that. */
#hud-scrim {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 220px;
  z-index: 9;
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(4, 6, 9, 0.82) 0%, rgba(4, 6, 9, 0.58) 26%, rgba(4, 6, 9, 0.26) 58%, rgba(4, 6, 9, 0) 100%);
  transition: opacity 0.22s ease;
}
#hud-scrim.hidden { opacity: 0; }

/* ---- the globes ---- */
.ab-globe {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.66));
  transition: filter 0.3s ease;
}
.ab-globe canvas { display: block; }
.ab-globe-num {
  position: absolute;
  font-size: 24px;
  font-weight: 700;
  color: #f7f2e7;
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
  /* Cut rather than typed, exactly like the cooldown numerals: a hard black edge round bone,
     which survives being read over a bright liquid, and a soft glow behind it for the dark. */
  -webkit-text-stroke: 3px rgba(0, 0, 0, 0.75);
  paint-order: stroke fill;
  text-shadow: 0 0 18px rgba(0, 0, 0, 0.75);
}
/* Nearly gone. The one thing on this HUD allowed to move on its own — and it is allowed
   because at that point the game is telling him something every second anyway. */
.ab-globe.is-low { animation: abLow 1.05s ease-in-out infinite; }
@keyframes abLow {
  0%, 100% { filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.66)); }
  50%      { filter: drop-shadow(0 0 18px rgba(206, 46, 34, 0.85)) drop-shadow(0 8px 20px rgba(0, 0, 0, 0.66)); }
}
.ab-globe.is-ward.is-ready { filter: drop-shadow(0 0 13px rgba(214, 178, 92, 0.42)) drop-shadow(0 8px 20px rgba(0, 0, 0, 0.66)); }
.ab-globe.is-ward.is-up { filter: drop-shadow(0 0 20px rgba(232, 198, 118, 0.6)) drop-shadow(0 8px 20px rgba(0, 0, 0, 0.66)); }

/* ---- the deck ---- */
/* ---- the deck ----
 * One piece of iron with the sockets cut into it. Everything physical about it — the metal,
 * the holes, the bevels, the groove, the rivets — is drawn by js/hudart.js into the canvas
 * below; what is left here is layout and the things that move.
 *
 * That division is deliberate. CSS is very good at putting boxes in the right place and very
 * bad at making a box look like an object: gradients have no grain, box-shadows have no
 * direction, border-radius has no machining. Everything that has to look MADE is painted. */
.ab-deck {
  position: relative;
  padding: 10px 17px 8px;
}
/* Wider than its own contents at both ends, so the globes sit ON the plate rather than beside
   it: three objects in a row is a toolbar, one instrument with two dials mounted in it is a
   HUD. Its size and offset are written by actionbar.js — NOT by `right`/`bottom` here, since
   an absolutely positioned canvas with `width: auto` takes its intrinsic size and would grow
   without limit as it was measured. */
.ab-plate {
  position: absolute;
  top: 0;
  display: block;
  pointer-events: none;
  filter: drop-shadow(0 10px 22px rgba(0, 0, 0, 0.55));
}
.ab-slots {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  gap: 9px;
}
/* The gap the groove is engraved into: the six are a loadout and the roll is not one of
   them, and the eye should be told that before the keycap has been read. */
.ab-gap { width: 15px; align-self: stretch; }

/* ---- one socket ----
 * The socket element itself is a HOLE IN THE PLATE, so it has no background, no border and no
 * shadow of its own — all three are painted into the metal underneath. What lives here is the
 * mark, the wedge, the channel fill and the number, every one of them clipped to the same
 * chamfer the hole was cut with (set from JS, which knows the size).
 */
.ab-slot {
  /* The one place a socket's size is set — see makeSocket on why it is not written inline. */
  --sz: 54px;
  --tint: #8b95a1;
  --glow: #12161d;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  pointer-events: auto;
  cursor: pointer;
}
.ab-slot.is-fixed { --sz: 42px; pointer-events: none; }   /* the roll: smaller, and not a slot you can press */

.ab-socket {
  position: relative;
  width: var(--sz);
  height: var(--sz);
  /* Cut to the same shape as the hole in the plate under it, so the wedge and the channel fill
     stop at the machined edge instead of squaring off the corners. A RATIO, not a fixed
     number of pixels: the roll's socket is 32 px at the narrow breakpoint and a fixed 6 px
     chamfer took a fifth of it. */
  --k: calc(var(--sz) * 0.12);
  clip-path: polygon(var(--k) 0, calc(100% - var(--k)) 0, 100% var(--k),
                     100% calc(100% - var(--k)), calc(100% - var(--k)) 100%, var(--k) 100%,
                     0 calc(100% - var(--k)), 0 var(--k));
}
/* The frame: the generated casting, nine-sliced so its band keeps its thickness at every
   socket size. `border-image` without `fill` paints the band and nothing else, which is what
   lets it sit OVER the mark while the mark is still visible through the middle. */
.ab-frame {
  position: absolute;
  inset: -1px;
  pointer-events: none;
  border: calc(var(--sz) * 0.145) solid transparent;
  border-image: url("./textures/hud/socket.png") 26 stretch;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}

/* The light in the hole: an inset ring that answers hover, arming and the cast. Inset, so it
   is inside the chamfer clip and follows the machined edge instead of boxing it. */
.ab-socket::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 0 0 rgba(0, 0, 0, 0);
  transition: box-shadow 0.18s ease;
}

/* Edge to edge. The marks are paintings now, and a painting in a socket is cropped by the
   socket — the frame's band overlaps its border the way every icon in both reference HUDs is
   overlapped by its own frame. A drawn glyph needed the inset; a picture needs the opposite. */
.ab-art {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  transition: filter 0.2s ease, transform 0.14s cubic-bezier(0.2, 0.9, 0.3, 1.25);
}
/* The mark sits IN the hole: its own shadow on the floor of it. */
.ab-art canvas {
  display: block;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.7));
}

/* The wedge. A conic gradient, so what is drawn is the arc still to run rather than a hand
   sweeping over it — the shape itself is the number. Dark enough to be unmistakable, light
   enough to read the mark through: a sweep that hides the icon turns six sockets into six
   black squares at exactly the moment a player is scanning them for the one that is back. */
.ab-sweep {
  position: absolute;
  inset: 0;
  display: none;
  background: conic-gradient(rgba(3, 5, 8, 0.68) var(--a, 0deg), rgba(0, 0, 0, 0) 0);
}
.ab-sweep.is-on { display: block; }
/* The leading edge of it, so a long cooldown has something moving on it. */
.ab-hand {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 50%;
  height: 1.5px;
  display: none;
  transform-origin: 0 50%;
  transform: rotate(calc(var(--a, 0deg) - 90deg));
  background: linear-gradient(90deg, rgba(255, 255, 255, 0) 30%, color-mix(in srgb, var(--tint) 70%, white) 100%);
  opacity: 0.7;
}
.ab-hand.is-on { display: block; }

/* Filled from the bottom while something is being held: the fire, and the words. */
.ab-hold {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  display: none;
  background: linear-gradient(0deg,
    color-mix(in srgb, var(--tint) 50%, transparent) 0%,
    color-mix(in srgb, var(--tint) 8%, transparent) 100%);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--tint) 80%, white);
}
.ab-hold.is-on { display: block; }

/* The countdown, cut rather than typed: a black bed under bone. A soft drop shadow is a web
   heading; a hard dark edge is a number stamped into metal. */
.ab-cd {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 17px;
  font-weight: 700;
  color: #f2ead6;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  -webkit-text-stroke: 2.5px rgba(0, 0, 0, 0.82);
  paint-order: stroke fill;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.9);
}

/* Drawn metal with the label struck into it — see hudart.js's keyCap. */
.ab-key {
  display: block;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}

/* ---- states ----
 * The hole does not move — it is cut into the plate. What moves is what is IN it, which is
 * also what a finger does to a real key: the cap goes down, the panel does not. */
.ab-slot:hover:not(.is-fixed) .ab-socket::after {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tint) 55%, transparent),
              inset 0 0 14px color-mix(in srgb, var(--tint) 18%, transparent);
}
.ab-slot:hover:not(.is-fixed) .ab-art { transform: scale(1.04); }
.ab-slot.is-cooling .ab-art { filter: saturate(0.4) brightness(0.7); }
.ab-slot.is-down .ab-art { transform: translateY(1.5px) scale(0.92); }
.ab-slot.is-active .ab-socket::after {
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--tint) 85%, white),
              inset 0 0 18px color-mix(in srgb, var(--tint) 42%, transparent);
}
.ab-slot.is-active .ab-art { filter: brightness(1.2) saturate(1.12); transform: scale(1.05); }

/* Pressed and it took: one quick pop, on the mark rather than the hole. Pressed and the body
   refused: one shake, and no sound of any kind — a refusal that punished you with noise would
   be worse than silence. */
.ab-slot.is-hit .ab-art { animation: abHit 0.24s cubic-bezier(0.2, 0.9, 0.3, 1.3); }
@keyframes abHit {
  0%   { transform: translateY(2px) scale(0.9); }
  55%  { transform: translateY(-1px) scale(1.09); }
  100% { transform: none; }
}
.ab-slot.is-refused .ab-art { animation: abNo 0.26s ease-out; }
@keyframes abNo {
  0%, 100% { transform: translateX(0); }
  20%  { transform: translateX(-3px); }
  50%  { transform: translateX(3px); }
  80%  { transform: translateX(-1.5px); }
}

/* Off cooldown. One ring, once — the only thing on the bar that asks for anything rather
   than reporting on him. It sits outside the socket's clip so the ring can leave the hole. */
.ab-flash {
  position: absolute;
  top: 0;
  left: 50%;
  width: var(--sz);
  height: var(--sz);
  margin-left: calc(var(--sz) / -2);
  pointer-events: none;
  opacity: 0;
}
.ab-slot.is-ready .ab-flash { animation: abReady 0.5s ease-out; }
@keyframes abReady {
  0%   { opacity: 1; box-shadow: 0 0 0 0 rgba(246, 238, 214, 0.7), 0 0 14px rgba(246, 238, 214, 0.55); }
  100% { opacity: 0; box-shadow: 0 0 0 15px rgba(246, 238, 214, 0), 0 0 0 rgba(246, 238, 214, 0); }
}

/* ---- the tooltip ----
   Cut from the same iron as everything else: chamfered corners, a bronze rule under the name,
   and no border-radius anywhere. A rounded card here would have been the one object on the bar
   still shaped like a web component. */
.ab-tip {
  position: absolute;
  bottom: calc(100% + 13px);
  transform: translate(-50%, 6px);
  min-width: 158px;
  max-width: 258px;
  padding: 10px 13px 11px;
  clip-path: polygon(9px 0, calc(100% - 9px) 0, 100% 9px, 100% calc(100% - 9px),
                     calc(100% - 9px) 100%, 9px 100%, 0 calc(100% - 9px), 0 9px);
  background:
    linear-gradient(180deg, rgba(58, 62, 70, 0.97) 0%, rgba(30, 33, 39, 0.97) 6%,
                            rgba(17, 19, 24, 0.97) 70%, rgba(10, 12, 15, 0.97) 100%);
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.66);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.14s ease, transform 0.14s ease;
}
/* The bronze rule, under the name and across the full cut. */
.ab-tip::after {
  content: "";
  position: absolute;
  left: 13px;
  right: 13px;
  top: 30px;
  height: 1px;
  background: linear-gradient(90deg, rgba(158, 120, 64, 0), rgba(178, 138, 76, 0.65), rgba(158, 120, 64, 0));
}
.ab-tip.is-on { opacity: 1; transform: translate(-50%, 0); }
.ab-tip b {
  display: block;
  font-size: 12.5px;
  font-weight: 650;
  color: #ecdfc0;
  letter-spacing: 0.02em;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.8);
}
.ab-tip span {
  display: block;
  margin-top: 12px;
  font-size: 11px;
  line-height: 1.5;
  color: #97a1ad;
}
.ab-tip em {
  position: absolute;
  top: 11px;
  right: 13px;
  font-style: normal;
  font-size: 8.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: #d9c9a3;
}
.ab-tip b { padding-right: 42px; }

/* ---- the screen's own answer to being bitten ----
   Under half health he is looking through a red edge, and every landed bite puts a beat of it
   on top. The hero has no flinch of his own, so this is the only thing that says a hit landed
   while the player is watching the middle of the screen. */
#hurt-vignette {
  position: fixed;
  inset: 0;
  z-index: 8;
  pointer-events: none;
  opacity: 0;
  background:
    radial-gradient(ellipse at 50% 50%,
      rgba(120, 0, 0, 0) 38%, rgba(126, 12, 8, 0.5) 76%, rgba(96, 6, 4, 0.9) 100%);
  /* NO transition. Its opacity is written every frame from the hero's condition, and a
     transition on a property that is assigned sixty times a second never finishes one — it
     restarts, and the value on screen lags behind the game by however long the transition is.
     (It also stalls outright in the verification browser, where the compositor is throttled:
     measured as a computed opacity of 0 while the inline style said 0.21.) The easing that
     matters is already in the numbers this is derived from. */
}

/* ---- minimap ----
   Fixed scale and north-up. The window scrolls over a map baked once at load; nothing here
   rotates, because a rotating minimap is harder to read and would fight the fixed camera. */
#minimap-wrap {
  position: fixed;
  right: 16px;
  top: 16px;
  width: 232px;
  height: 168px;
  z-index: 10;
  /* Cut like the command bar, not rounded like a card: the map and the bar are the two things
     on screen that belong to the game rather than to the page, and they are the same object. */
  clip-path: polygon(11px 0, calc(100% - 11px) 0, 100% 11px, 100% calc(100% - 11px),
                     calc(100% - 11px) 100%, 11px 100%, 0 calc(100% - 11px), 0 11px);
  border: 1px solid rgba(0, 0, 0, 0.75);
  box-shadow:
    inset 0 0 0 1px rgba(217, 201, 163, 0.16),
    inset 0 0 30px rgba(0, 0, 0, 0.55),
    0 10px 30px rgba(0, 0, 0, 0.55);
  background: #11150f;
  transition: opacity 0.2s ease;
}
#minimap-wrap.hidden { opacity: 0; pointer-events: none; }
/* A scrim inside the top edge so the N and the frame's light never sit on bright ground. */
#minimap-wrap::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 22px;
  background: linear-gradient(to bottom, rgba(6, 8, 10, 0.6), rgba(6, 8, 10, 0));
  pointer-events: none;
}

#minimap {
  display: block;
  width: 232px;
  height: 168px;
}

.mm-n {
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: rgba(217, 201, 163, 0.75);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  pointer-events: none;
}

/* A key for the one thing the map is actually asked: can I walk there. */
.mm-key {
  position: absolute;
  left: 6px;
  bottom: 4px;
  display: flex;
  gap: 9px;
  font-size: 9px;
  letter-spacing: 0.04em;
  color: rgba(232, 230, 225, 0.82);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95);
  pointer-events: none;
}
.mm-key i { display: inline-block; width: 7px; height: 7px; margin-right: 3px; border-radius: 1px; }
.mm-key .open { background: #7d9450; }
.mm-key .shut { background: #262a34; outline: 1px solid rgba(255,255,255,0.22); }

/* The bar shrinks rather than wrapping, in two steps. Six sockets, the roll and two globes
   in ONE ROW is the shape — a command bar on two lines is two HUDs, and a player looking for
   slot 4 in a fight should never have to find out which line it went to. The globes are
   canvases sized in JS, so they are scaled here rather than resized; the sockets are CSS all
   the way down and shrink honestly. */
@media (max-width: 1080px) {
  #actionbar .ab-globe { transform: scale(0.9); transform-origin: bottom center; }
  #actionbar .ab-slot { --sz: 48px; }
  #actionbar .ab-slot.is-fixed { --sz: 38px; }
  #actionbar .ab-key { transform: scale(0.92); }
}
@media (max-width: 780px) {
  #actionbar { bottom: 9px; }
  #actionbar .ab-globe { transform: scale(0.7); }
  #actionbar .ab-slots { gap: 6px; }
  #actionbar .ab-slot { --sz: 42px; gap: 5px; }
  #actionbar .ab-slot.is-fixed { --sz: 32px; }
  #actionbar .ab-key { transform: scale(0.82); }
  #actionbar .ab-gap { width: 10px; }
  #hud-scrim { height: 170px; }
  #alert { bottom: 132px; }
}
@media (max-width: 700px) {
  #minimap-wrap, #minimap { width: 156px; height: 112px; }
}


/* ---- speaking to somebody (js/talk.js) -------------------------------------------------
 * Two states of one surface, in the HUD's own materials.
 *
 * NO BOX. The first cut of this was a black rectangle with the text inside it, and against
 * bright village dirt it read as a debug overlay that had been left switched on — a shape the
 * game does not use anywhere else. Everything else in this HUD that speaks to the player (the
 * alert line, the loading card) darkens the ground it needs and fades out, so this does too:
 * a soft wash with no edge to find, one gold hairline above it, and the words carrying
 * themselves on their own shadow. What makes it readable is contrast, not a border.
 */
#talk {
  position: fixed;
  left: 50%;
  /* Above the command bar, with air. The bar owns the bottom 150 px and a line that touches
     it is read as part of it. */
  bottom: 182px;
  transform: translateX(-50%);
  z-index: 12;
  width: min(76vw, 620px);
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.talk-cap {
  display: block;
  width: 19px;
  height: 17px;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.9));
}

/* The offer. It rises half a centimetre as it arrives — a prompt that pops reads as a
   notification, a prompt that rises reads as something the world just offered you. */
.talk-prompt {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 24px 12px;
  color: #f0e7d0;
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: 0.045em;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 1), 0 0 26px rgba(0, 0, 0, 0.95);
  /* 50% vertically, for the reason spelled out over .talk-panel. */
  background: radial-gradient(56% 50% at 50% 50%,
    rgba(6, 8, 6, 0.82) 0%, rgba(6, 8, 6, 0.5) 54%, rgba(6, 8, 6, 0) 100%);
  transition: opacity 0.28s ease, transform 0.28s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.talk-prompt.gone { opacity: 0; transform: translateY(7px); pointer-events: none; }

/* The conversation. */
.talk-panel {
  position: relative;
  width: 100%;
  padding: 20px 30px 32px;
  /* Two washes, and the VERTICAL RADIUS OF BOTH IS AT MOST 50% — which is the whole trick.
     A radial gradient sized past the box is cut off by it, and the cut is a straight edge
     across the screen: the first version of this had a soft left and right and a hard top,
     which is worse than a box because it looks like a mistake rather than a decision. At 50%
     the wash reaches zero exactly at the element's own edge and there is nothing to see. */
  background:
    radial-gradient(64% 50% at 50% 50%, rgba(5, 7, 5, 0.92) 0%, rgba(5, 7, 5, 0.58) 52%, rgba(5, 7, 5, 0) 100%),
    radial-gradient(104% 50% at 50% 50%, rgba(5, 7, 5, 0.46) 0%, rgba(5, 7, 5, 0) 100%);
  transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.talk-panel.gone { opacity: 0; transform: translateY(10px); pointer-events: none; }
/* One rule above the name, brightest in the middle — the alert line's own hairline, which is
   what ties this to the rest of the HUD rather than to a dialogue box. */
.talk-panel::before {
  content: "";
  position: absolute;
  left: 16%;
  right: 16%;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg,
    rgba(232, 200, 135, 0) 0%, rgba(232, 200, 135, 0.55) 50%, rgba(232, 200, 135, 0) 100%);
}

.talk-who {
  color: #e8c887;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin-bottom: 7px;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 1);
}

.talk-line {
  margin: 0;
  /* Two lines' worth, held open so the panel does not jump as a line wraps or does not. */
  min-height: 2.9em;
  color: #f2ead7;
  font-size: 16px;
  font-weight: 420;
  line-height: 1.46;
  letter-spacing: 0.012em;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 1), 0 0 24px rgba(0, 0, 0, 0.9);
}

/* "go on", bottom right, arriving a beat after the line finishes — see SETTLE in talk.js. */
.talk-go {
  position: absolute;
  right: 24px;
  bottom: 6px;
  display: flex;
  align-items: center;
  gap: 8px;
  color: rgba(222, 208, 178, 0.78);
  font-size: 11.5px;
  letter-spacing: 0.09em;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 1);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.talk-go.gone { opacity: 0; transform: translateX(5px); }

@media (max-width: 1080px) {
  #talk { bottom: 168px; }
}
@media (max-width: 780px) {
  #talk { bottom: 146px; width: min(90vw, 540px); }
  .talk-line { font-size: 15px; }
}
