/* ---------------------------------------------------------------------------
   Fonts — self-hosted (see fonts/LICENSE-fonts.txt: both are Google Fonts
   under the SIL Open Font License, free for commercial use, no attribution
   required). Metamorphous is the dramatic display face (title, win/lose
   banner); MedievalSharp is used for buttons and other UI chrome. Regular
   body/number text stays in a plain readable font on purpose — a decorative
   face on card numbers or status text would hurt quick readability during
   play.
--------------------------------------------------------------------------- */
@font-face {
  font-family: 'Metamorphous';
  src: url('fonts/Metamorphous-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'MedievalSharp';
  src: url('fonts/MedievalSharp-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------------------
   Design tokens — simplistic, flat, dark theme with one warm accent color.
   Change values here to re-theme the whole game.
--------------------------------------------------------------------------- */
:root {
  --bg: #16161a;
  --surface: #1e1e23;
  --surface-hover: #28282e;
  --border: #3a3a42;
  --text: #f0f0f0;
  --text-muted: #9a9aa4;
  --accent: #d9a441;
  --accent-hover: #e6b75d;
  --danger: #c0392b;
  --success: #6fae52;

  --card-bg: #f5f2e9;
  --card-text: #1a1a1a;
  --card-text-red: #b3283d;
  /* "R, G, B" components for use in rgba()/glow effects on the card border
     (see the .card--tier-N rules). Monsters get darker/gloomier as their
     tier goes up instead of a single fixed color — see the
     .card--monster.card--tier-N overrides below — so there's no single
     --monster-rgb here. Weapons default to white but that's set per-card
     via card.glowRgb (js/cards.js) applied as an inline style
     (applyGlowColor() in js/ui.js), not a fixed token — this is just the
     class-level fallback for a weapon card that somehow has none. */
  --weapon-rgb: 255, 255, 255;
  --potion-rgb: 179, 40, 61;
  --shield-rgb: 122, 147, 168;
  --mana-rgb: 61, 133, 224;

  --radius: 0.5rem;

  --font-body: system-ui, sans-serif;
  --font-display: 'Metamorphous', serif;
  --font-ui: 'MedievalSharp', cursive;

  /* Responsive tokens (mobile/default values — redefined per breakpoint
     below). --card-scale multiplies the card's base 100x140px design size;
     --weapon-slot-scale shrinks the weapon slot a bit further relative to
     room cards (it's a supporting element, not the main focus) so it takes
     up less vertical space. --stack-gap is the spacing between the page's
     main sections. */
  --card-scale: 0.72;
  --weapon-slot-scale: 0.75;
  --stack-gap: 0.5rem;
}

/* ---------------------------------------------------------------------------
   Responsive scaling — two things change together per breakpoint:
   1) the root font-size, which scales everything sized in rem (buttons,
      text, HP bar, toggle, banner) — phone is the 1x baseline, desktop
      reaches a full 1.5x (24px / 16px) as requested.
   2) --card-scale / --stack-gap, which size the room/weapon cards and the
      gaps between page sections directly in px via calc(), independent of
      the root font-size. Cards need their own control because they have a
      hard constraint text doesn't: 4 of them must fit in one row without
      wrapping, and together with everything else they must fit one screen
      with NO scrolling.

   Each tier below also requires a minimum viewport HEIGHT, not just width —
   measured against this page's actual content height at that tier (with a
   safety margin), so a wide-but-short window (e.g. a 1280x720 laptop) simply
   doesn't qualify for a bigger tier and the cascade falls through to the
   next one that fits, instead of forcing a size that would cause scrolling.
   Rules are listed smallest-to-largest tier on purpose, so on a wide+tall
   screen the later (bigger) rules correctly override the earlier ones.
--------------------------------------------------------------------------- */
html {
  font-size: 16px; /* phone (default) */
}

@media (min-width: 640px) {
  html {
    font-size: 18px; /* tablet, e.g. iPad portrait — content needs ~660px */
  }
  :root {
    --card-scale: 1; /* original 100x140 card size */
    --stack-gap: 0.75rem;
  }
}

/* Small/mid desktop — content at this tier needs ~870px, so require some
   margin above that. */
@media (min-width: 1024px) and (min-height: 900px) {
  html {
    font-size: 22px;
  }
  :root {
    --card-scale: 1.3;
    --stack-gap: 0.9rem;
  }
}

/* Full desktop, 1.5x the original size — content at this tier needs
   ~1000px, so require real margin above that before using it. */
@media (min-width: 1280px) and (min-height: 1040px) {
  html {
    font-size: 24px; /* desktop — 1.5x the original 16px */
  }
  :root {
    --card-scale: 1.5;
    --stack-gap: 1.1rem;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
}

body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  text-align: center;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--stack-gap);
  padding: 0.5rem 1rem;
}

h1 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 2.6rem;
  letter-spacing: 0.04em;
  margin: 0;
}

/* --- buttons (dungeon-styled: carved-stone gradient, torchlight glow on
   hover, a light "sheen" sweep, and a satisfying press) ------------------ */

button {
  font-family: var(--font-ui);
  font-size: 1.05rem;
  letter-spacing: 0.03em;
  padding: 0.55rem 1.3rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: linear-gradient(180deg, var(--surface-hover), var(--surface));
  color: var(--text);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 2px 4px rgba(0, 0, 0, 0.4);
  transition: transform 0.15s ease, box-shadow 0.2s ease, border-color 0.2s ease,
    background 0.2s ease;
}

/* Diagonal light sheen that sweeps across the button on hover. */
button::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 -60%;
  width: 40%;
  background: linear-gradient(115deg, transparent, rgba(255, 255, 255, 0.16), transparent);
  transform: skewX(-20deg);
  transition: transform 0.55s ease;
  pointer-events: none;
  z-index: 1;
}

button:hover:not(:disabled)::before {
  transform: translateX(480%) skewX(-20deg);
}

button:hover:not(:disabled) {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
    0 0 12px 1px rgba(217, 164, 65, 0.4), 0 4px 8px rgba(0, 0, 0, 0.45);
}

button:active:not(:disabled) {
  transform: translateY(0) scale(0.96);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.45);
}

button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

button:disabled::before {
  content: none;
}

#new-game-btn,
#play-again-btn,
#room-start-btn,
#start-new-game-btn {
  background: linear-gradient(180deg, var(--accent-hover), var(--accent));
  border-color: var(--accent);
  color: #1a1408;
  font-weight: 600;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 2px 6px rgba(0, 0, 0, 0.4);
}

#new-game-btn:hover:not(:disabled),
#play-again-btn:hover:not(:disabled),
#room-start-btn:hover:not(:disabled),
#start-new-game-btn:hover:not(:disabled) {
  background: linear-gradient(180deg, #f0cd7a, var(--accent-hover));
  border-color: #f0cd7a;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 0 18px 3px rgba(217, 164, 65, 0.6), 0 4px 10px rgba(0, 0, 0, 0.45);
}

/* --- controls / deck count / hamburger menu button ------------------------ */

#controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem 1rem;
}

#deck-count {
  color: var(--text-muted);
  font-size: 0.9rem;
}

#menu-btn {
  padding: 0.5rem 0.7rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.hamburger-icon {
  position: relative;
  z-index: 2; /* stays above the button's ::before hover sheen */
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  width: 1.3rem;
}

.hamburger-icon span {
  display: block;
  height: 2px;
  background: var(--text);
  border-radius: 1px;
  transition: background 0.2s ease;
}

#menu-btn:hover .hamburger-icon span {
  background: var(--accent);
}

/* --- menu popup (New Game + rules) -----------------------------------------
   #gallery-overlay (Champions/Weapons/Monsters galleries, see further down)
   and #champion-select-overlay (the "pick a champion" screen shown whenever
   a new game starts) share this exact chrome — same dimmed backdrop, panel,
   close button, and title treatment — so they're styled via the same
   selectors rather than duplicated. Only their inner content differs. */

#menu-overlay,
#gallery-overlay,
#champion-select-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  z-index: 300;
  padding: 3rem 1rem;
  overflow-y: auto;
}

#menu-overlay.hidden,
#gallery-overlay.hidden,
#champion-select-overlay.hidden {
  display: none;
}

#menu-panel,
#gallery-panel,
#champion-select-panel {
  position: relative;
  background: linear-gradient(180deg, var(--surface), #17171b);
  border: 1px solid var(--border);
  box-shadow: 0 0 0 1px rgba(217, 164, 65, 0.15), 0 10px 30px rgba(0, 0, 0, 0.6);
  border-radius: 12px;
  padding: 1.5rem;
  width: min(30rem, 100%);
  max-height: 85vh;
  overflow-y: auto;
  animation: menu-in 0.25s ease;
}

/* Champion-select gets a bit more room than the menu/gallery panels since
   its tiles carry a full description line, not just a name + rank. */
#champion-select-panel {
  width: min(38rem, 100%);
}

@keyframes menu-in {
  from {
    opacity: 0;
    transform: translateY(-12px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

#menu-close-btn,
#gallery-close-btn,
#champion-select-close-btn {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  padding: 0.3rem 0.65rem;
  font-size: 0.9rem;
}

#menu-title,
#gallery-title,
#champion-select-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.6rem;
  margin: 0 0 1rem;
  text-align: center;
}

#menu-panel #new-game-btn,
#menu-panel #back-to-menu-btn {
  display: block;
  width: 100%;
  margin: 0 0 0.6rem;
}

#menu-panel #new-game-btn.hidden {
  display: none;
}

#menu-panel #back-to-menu-btn {
  margin-bottom: 1.5rem;
}

#rules {
  text-align: left;
  border-top: 1px solid var(--border);
  padding-top: 0.75rem;
}

#rules h3 {
  font-family: var(--font-ui);
  color: var(--accent);
  font-size: 1rem;
  margin: 1rem 0 0.35rem;
}

#rules h3:first-child {
  margin-top: 0;
}

#rules p,
#rules li {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-muted);
}

#rules p {
  /* Reset the browser's default paragraph margin — it was stacking on top
     of #rules h3's own margin-bottom, making the gap under each title look
     much bigger than the 0.35rem actually set there. */
  margin: 0 0 0.5rem;
}

#rules ul {
  margin: 0 0 0.5rem 1.1rem;
  padding: 0;
}

#rules strong {
  color: var(--text);
}

/* --- main/start screen ------------------------------------------------------
   Shown on page load instead of the game screen (#game-screen, wrapping
   everything that used to be body's direct children) — startNewGame() in
   main.js swaps to #game-screen; the hamburger menu's "Main Menu" button
   (#back-to-menu-btn) swaps back. Both screens are direct children of body,
   which is still the flex column doing the centering — only one is ever in
   flow at a time since the other gets display:none via .hidden. */

#start-screen,
#game-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--stack-gap);
}

#start-screen.hidden,
#game-screen.hidden,
#loading-screen.hidden {
  display: none;
}

/* --- loading screen (js/preload.js) ---------------------------------------
   Shown first, before #start-screen; body's flex centering (see body rule
   above) centers this the same way it centers the other top-level screens,
   no layout of its own needed beyond the column/gap here. */
#loading-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.2rem;
}

#loading-bar {
  width: 14rem;
  max-width: 60vw;
  height: 0.55rem;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  overflow: hidden;
}

#loading-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  /* Matches the progress-bar-style updates in js/preload.js, not a fixed
     duration, since real progress arrives in irregular bursts. */
  transition: width 0.15s ease;
}

#loading-text {
  font-family: var(--font-ui);
  color: var(--text-muted);
  font-size: 0.9rem;
}

#start-screen {
  gap: calc(var(--stack-gap) * 1.5);
}

/* Shared with #loading-logo (js/preload.js's loading screen) so the two
   screens feel like one continuous brand moment rather than a jump cut. */
#start-logo,
#loading-logo {
  font-size: 3.2rem;
  color: var(--accent);
  text-shadow: 0 0 24px rgba(217, 164, 65, 0.35);
}

#start-tagline {
  font-family: var(--font-ui);
  color: var(--text-muted);
  font-size: 0.95rem;
  margin: -0.75rem 0 0;
}

#start-new-game-btn {
  font-size: 1.2rem;
  padding: 0.7rem 2.2rem;
}

/* Always a fixed grid, never flex-wrap. flex-wrap only wraps once a row
   actually runs out of room, so on a wide-but-short laptop (plenty of
   width, but failing the html-font-size tier's min-height gate a bit
   further up this file) it used to happily fit all 6 buttons on one row
   instead of the intended 3x2/2x3 grid, since nothing about button width
   depends on that height gate. A grid with an explicit column count is
   controlled by width alone, so it can't make that mistake regardless of
   viewport height. Deliberately its own width-only breakpoint below, not
   reusing the html-font-size tier's min-width+min-height media query above
   (that gate exists to stop room/weapon cards overflowing vertically, an
   unrelated concern from how many nav buttons fit per row). */
#start-nav {
  display: grid;
  grid-template-columns: repeat(2, auto);
  justify-content: center;
  gap: 0.6rem;
  margin-top: 0.5rem;
}

/* All 6 buttons the same size, sized to fit the widest label ("How to
   Play") rather than each hugging its own text. min-width (not width) so a
   future 7th button with an even longer label just grows this instead of
   truncating/wrapping. In rem so it scales with the root font-size same as
   everything else (see "Responsive sizing" in CLAUDE.md). */
#start-nav button {
  min-width: 9rem;
}

/* 3 columns (2 rows) once there's room for them: 3 * the 9rem button
   min-width + 2 gaps comfortably fits under 500px even at the phone tier's
   16px root font-size (a bigger root font-size only makes buttons wider,
   never narrower, so this keeps holding at every larger tier too), so
   500px is a safe, purely-width threshold. Below it (a phone in portrait)
   the plain 2-column grid above applies, giving the requested 3 rows x 2
   columns fallback "sobald notwendig". */
@media (min-width: 500px) {
  #start-nav {
    grid-template-columns: repeat(3, auto);
  }
}

/* --- gallery popup (Champions/Weapons/Monsters, opened from the start
   screen) — reuses #menu-overlay/#menu-panel chrome, see above. ----------- */

#gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
  gap: 0.75rem;
}

/* Light card-colored background (not --surface-hover) is deliberate: the
   monster/weapon artwork itself is a solid black silhouette (see the
   "Monster/Weapon artwork" sections in CLAUDE.md) meant to sit on the
   cream .card background it normally appears on — on a dark background it
   would be nearly invisible. */
.gallery-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.5rem;
  border-radius: var(--radius);
  background: var(--card-bg);
  cursor: pointer;
  border: 1px solid transparent;
  transition: transform 0.15s ease, border-color 0.2s ease;
}

.gallery-item:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
}

.gallery-item-portrait {
  width: 100%;
  max-width: 3.6rem;
  height: 3.6rem;
}

.gallery-item-portrait img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Fallback for anything without artwork yet (currently every Champion —
   see js/champion-icons.js/fillPortrait() in js/ui.js). A plain letter
   monogram rather than an emoji glyph: an earlier emoji badge (🛡) didn't
   render on every system/font tested, so letters are the safer default
   here too. Reuses the weapon slot's dashed-outline look
   (.weapon-slot-empty) so an "art not ready yet" placeholder reads
   consistently across the whole game. */
.portrait-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  background: var(--card-bg);
  font-family: var(--font-display);
  font-size: 1.6rem;
  color: var(--text-muted);
}

/* Fixed to a 2-line-tall box (not just line-clamped) regardless of whether
   the name actually wraps ("Wolf" vs "Armored Skeleton") so every tile's
   rank number lines up at the same height, both within a grid row and
   across rows — matters because #gallery-grid's rows can end up different
   heights depending on which names in that row wrapped. */
.gallery-item-name {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(0.68rem * 1.25 * 2);
  font-size: 0.68rem;
  line-height: 1.25;
  color: var(--card-text);
  text-align: center;
}

.gallery-item-rank {
  font-family: var(--font-ui);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--card-text);
  opacity: 0.85;
}

/* --- gallery item detail popup (opened by clicking a weapon/monster tile) -
   Sits on top of #gallery-overlay (z-index 300), so it needs a higher one
   of its own — everything else reuses the exact same dimmed-backdrop +
   panel + close-button chrome as #menu-overlay/#gallery-overlay, just with
   its own selectors since it isn't in that shared group (its panel is
   narrower and has image/rank rows the other two don't). */
#gallery-detail-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 310;
  padding: 1.5rem;
}

#gallery-detail-overlay.hidden {
  display: none;
}

#gallery-detail-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  background: linear-gradient(180deg, var(--surface), #17171b);
  border: 1px solid var(--border);
  box-shadow: 0 0 0 1px rgba(217, 164, 65, 0.15), 0 10px 30px rgba(0, 0, 0, 0.6);
  border-radius: 12px;
  padding: 1.5rem;
  width: min(20rem, 100%);
  animation: menu-in 0.25s ease;
}

#gallery-detail-close-btn {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  padding: 0.3rem 0.65rem;
  font-size: 0.9rem;
}

/* A div now (was a plain <img>), filled by fillPortrait() in js/ui.js so it
   can fall back to a .portrait-placeholder when there's no artwork yet
   (currently every Champion). */
#gallery-detail-image {
  width: 7rem;
  height: 7rem;
  padding: 0.5rem;
  border-radius: var(--radius);
  background: var(--card-bg);
}

#gallery-detail-image img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

#gallery-detail-image .portrait-placeholder {
  border: none;
  font-size: 2.4rem;
}

#gallery-detail-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.3rem;
  margin: 0;
  text-align: center;
}

#gallery-detail-rank {
  font-family: var(--font-ui);
  color: var(--accent);
  font-size: 0.9rem;
  margin: 0;
}

#gallery-detail-text {
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1.4;
  text-align: center;
  margin: 0;
}

/* --- champion-select screen (shown whenever a new game starts) ------------
   Sits inside #champion-select-panel, which reuses the shared #menu-overlay/
   #gallery-overlay chrome (see above). Each tile shows its full ability text
   inline rather than opening a separate detail popup (only 4 champions
   exist), and clicking one starts the game immediately — see the delegated
   click handler in main.js. */

#champion-select-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.9rem;
}

.champion-select-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 1rem 0.75rem;
  border-radius: var(--radius);
  background: var(--card-bg);
  cursor: pointer;
  border: 1px solid transparent;
  transition: transform 0.15s ease, border-color 0.2s ease;
}

.champion-select-item:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
}

.champion-select-portrait {
  width: 5rem;
  height: 5rem;
}

.champion-select-portrait img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.champion-select-portrait .portrait-placeholder {
  font-size: 2rem;
}

.champion-select-name {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--card-text);
}

.champion-select-desc {
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--card-text);
  opacity: 0.75;
  text-align: center;
}

/* --- empty room state (before the first game / after the dungeon ends) ---- */

#room-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
}

#room-empty-tagline {
  font-family: var(--font-display);
  color: var(--text-muted);
  font-size: 1.1rem;
  letter-spacing: 0.02em;
}

/* --- HP bar / weapon / message -------------------------------------------- */

#hp-bar-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}

/* The champion portrait sits left of the bar, inside the same flex row/gap
   as the bar and the "20 / 20 HP" text — #hp-bar's width below is shortened
   by exactly this badge's width plus one gap (1.75rem + 0.75rem = 2.5rem,
   13.75rem -> 11.25rem) so the whole badge+bar group takes up the same
   total width the bar alone used to. Filled via fillPortrait() (js/ui.js),
   same null-image fallback pattern as everywhere else — see
   renderChampionBadge() in js/ui.js. */
#champion-badge {
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: 50%;
  overflow: hidden;
  background: var(--card-bg);
  border: 1px solid var(--border);
  box-shadow: 0 0 0 1px rgba(217, 164, 65, 0.18);
}

#champion-badge img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

#champion-badge .portrait-placeholder {
  width: 100%;
  height: 100%;
  border: none;
  border-radius: 50%;
  font-size: 0.9rem;
}

/* Stacks the HP bar and the champion-ability bar directly beneath it in a
   column, both the same width (11.25rem, matching #hp-bar) — kept as its
   own wrapper rather than putting the ability bar inside #hp-bar-container
   so that container's 50%/50% floating-number anchor (see
   #hp-float-container below) stays centered on the HP bar alone, not on
   the taller combined column. */
#hp-bar-column {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* Positioning anchor for the floating +N / -N numbers (#hp-float-container),
   which need to sit centered above the bar regardless of its own size. */
#hp-bar-container {
  position: relative;
}

/* Carved-stone recessed look: dark inset well with a thin gold hairline
   ring, so the bar reads as part of the dungeon UI rather than a plain web
   progress bar. Width shortened to make room for #champion-badge — see the
   comment there. */
#hp-bar {
  width: 11.25rem;
  height: 0.875rem;
  background: linear-gradient(180deg, #0c0c0f, #1c1c21);
  border: 1px solid var(--border);
  border-radius: 0.4375rem;
  overflow: hidden;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(217, 164, 65, 0.18);
}

#hp-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(180deg, #e0564a, #a5281b);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 0 8px rgba(192, 57, 43, 0.55);
  transition: width 0.4s ease, box-shadow 0.3s ease;
}

/* Pulses when HP drops to 25% or below (toggled in renderHp() in ui.js) —
   a quiet "danger" cue instead of just a short red bar. */
#hp-bar.hp-bar--low #hp-fill {
  animation: hp-pulse 1s ease-in-out infinite;
}

@keyframes hp-pulse {
  0%,
  100% {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 0 8px rgba(192, 57, 43, 0.55);
  }
  50% {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 0 16px rgba(192, 57, 43, 0.9);
  }
}

#hp-text {
  font-size: 0.9rem;
  color: var(--text-muted);
  min-width: 4.375rem;
  text-align: left;
}

/* Thin per-champion progress bar directly under the HP bar, same width
   (11.25rem, via #hp-bar-column above) — shows progress toward whichever
   champion's passive triggers repeatedly during a run (Paladin's every-5th-
   kill heal, Rogue's flee streak, Herbalist's potions-per-room). Segment
   count varies by champion (buildChampionAbilitySegments() in js/ui.js);
   hidden entirely for a champion without one of these (Berserker) or no
   champion picked. Segments are always green when filled — deliberately
   not tied to the card-tier color system, this is a simple fill gauge. */
#champion-ability-bar {
  display: flex;
  gap: 2px;
  width: 11.25rem;
  height: 0.25rem;
}

.ability-segment {
  flex: 1;
  border-radius: 0.125rem;
  background: rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
  transition: background 0.25s ease, box-shadow 0.25s ease;
}

.ability-segment--filled {
  background: var(--success);
  box-shadow: 0 0 4px rgba(46, 204, 113, 0.7);
}

/* Paladin's full-bar heal celebration (see renderChampionAbilityBar()'s
   animateHeal option in js/ui.js): scoped to this state only, so the bar's
   normal instant-ish segment updates elsewhere (0.25s, above) aren't slowed
   down — only the post-heal drain from 5/5 back to empty fades over ~1s. */
#champion-ability-bar.ability-bar--draining .ability-segment {
  transition: background 1s ease, box-shadow 1s ease;
}

/* --- floating "+7" / "-4" combat numbers ------------------------------- */

#hp-float-container {
  position: absolute;
  left: 50%;
  top: 50%;
  pointer-events: none;
}

.hp-float {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 1.15rem;
  white-space: nowrap;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

.hp-float--heal {
  color: var(--success);
  animation: hp-float-up 1.1s ease-out forwards;
}

.hp-float--damage {
  color: var(--danger);
  animation: hp-float-down 1.1s ease-out forwards;
}

@keyframes hp-float-up {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.75);
  }
  15% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.15);
  }
  30% {
    transform: translate(-50%, -50%) scale(1);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, calc(-50% - 2.4rem)) scale(1);
  }
}

@keyframes hp-float-down {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.75);
  }
  15% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.15);
  }
  30% {
    transform: translate(-50%, -50%) scale(1);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, calc(-50% + 2.4rem)) scale(1);
  }
}

/* Same floating combat-number treatment as .hp-float, but for a single card
   (currently: a monster weakened by the Electric weapon effect). Appended
   to <body> and positioned via getBoundingClientRect (see showCardDamage()
   in ui.js) rather than as a child of the card itself, and `position: fixed`
   (not absolute) to match — the card element it points at gets replaced by
   renderRoom() well before the 1.1s animation finishes, so it can't be
   anchored to that element's own box. Reuses the hp-float-up/down keyframes
   above so the two feel identical. */
.card-float {
  position: fixed;
  transform: translate(-50%, -50%);
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 1rem;
  white-space: nowrap;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
  pointer-events: none;
  z-index: 50;
}

.card-float--heal {
  color: var(--success);
  animation: hp-float-up 1.1s ease-out forwards;
}

.card-float--damage {
  color: var(--danger);
  animation: hp-float-down 1.1s ease-out forwards;
}

/* Herbalist's ability heal burst — a handful of these "+" marks spawned by
   showAbilityHealBurst() (js/ui.js) at random positions around
   #ability-btn, each fading up and out on its own short timer. `left`/`top`
   are set per-mark (inline, as a % of #ability-wrap) rather than fixed
   here, so every mark ends up scattered instead of stacked in one spot. */
.ability-heal-particle {
  position: absolute;
  transform: translate(-50%, -50%);
  color: var(--success);
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 0.95rem;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
  pointer-events: none;
  z-index: 2;
  animation: ability-heal-particle-float 0.75s ease-out forwards;
}

@keyframes ability-heal-particle-float {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.5);
  }
  20% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.1);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, calc(-50% - 0.9rem)) scale(0.9);
  }
}

/* Capped to 2 lines (with ellipsis) so a long fight/flee message can never
   push the page taller and cause scrolling — height stays predictable. */
#message {
  min-height: 1rem;
  max-width: min(90vw, 26rem);
  font-size: 0.85rem;
  line-height: 1.3;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.message--won {
  color: var(--success);
  font-weight: 600;
}

.message--lost {
  color: var(--danger);
  font-weight: 600;
}

/* --- Room / cards -------------------------------------------------------- */

#room {
  display: flex;
  justify-content: center;
  gap: calc(0.5rem + 0.5rem * var(--card-scale));
  flex-wrap: nowrap; /* always keep the 4 room cards on one row, sized to fit */
}

/* Card size is driven by --card-scale (see the responsive breakpoints
   above) rather than rem, so it can be tuned independently of general
   text/button scaling — it has its own hard constraint (4 must fit in one
   row, and the whole page must fit one screen with no scrolling). */
/* Card strength is shown as a border treatment instead of the old flat
   top color bar: --edge-rgb comes from the card's type (monster/weapon/
   potion, see below), and --card-border-width / --card-border-alpha /
   --card-glow come from its strength tier (.card--tier-1 weakest through
   .card--tier-5 strongest, see further down) — a weak card gets a thin,
   faint edge, a strong one gets a thick, saturated, glowing edge. */
.card {
  --card-border-width: 1px;
  --card-border-alpha: 0.25;
  --card-glow: 0 0 0 0 rgba(0, 0, 0, 0);
  --card-glow-inset: inset 0 0 0 rgba(0, 0, 0, 0);
  position: relative;
  width: calc(100px * var(--card-scale));
  height: calc(140px * var(--card-scale));
  border-radius: var(--radius);
  background: var(--card-bg);
  color: var(--card-text);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: calc(8px * var(--card-scale)) 0;
  border: var(--card-border-width) solid rgba(var(--edge-rgb, 26, 26, 26), var(--card-border-alpha));
  box-shadow: var(--card-glow), var(--card-glow-inset), 0 2px 6px rgba(0, 0, 0, 0.35);
  cursor: pointer;
  flex-shrink: 0;
  transition: transform 0.2s ease, opacity 0.2s ease, box-shadow 0.2s ease,
    border-color 0.2s ease, border-width 0.2s ease;
}

/* Small corner badge showing a weapon's rolled effect (see
   WEAPON_EFFECTS in js/weapon-effects.js and fillCardFace() in ui.js).
   Sits above the card's own artwork/glow layers; the full name and
   description are in its title (tooltip) and in #weapon-status. */
.card-effect-badge {
  position: absolute;
  top: calc(4px * var(--card-scale));
  left: calc(4px * var(--card-scale));
  width: calc(20px * var(--card-scale));
  height: calc(20px * var(--card-scale));
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: calc(11px * var(--card-scale));
  line-height: 1;
  color: #fff;
  z-index: 1;
}

.card:hover {
  box-shadow: var(--card-glow), var(--card-glow-inset), 0 4px 10px rgba(0, 0, 0, 0.45);
  transform: translateY(-3px);
}

/* Played by main.js right when a card is clicked; the room re-renders once
   this transition has had time to finish (see CARD_ANIMATION_MS in main.js). */
.card--resolved {
  transform: translateY(24px);
  opacity: 0;
  pointer-events: none;
}

/* Minimal shake for a monster hit indirectly (currently: the Electric
   weapon effect weakening it) rather than fought directly. Small
   translateX-only wobble — deliberately not scale/rotate, same reasoning as
   .card--aura: a whole-card translate doesn't drag the value label out of
   place the way scaling from the center would. Plays once; the room
   re-renders shortly after anyway (see CARD_ANIMATION_MS in main.js). */
@keyframes card-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-3px);
  }
  50% {
    transform: translateX(3px);
  }
  75% {
    transform: translateX(-2px);
  }
}

.card--shake {
  animation: card-shake 0.3s ease-in-out;
}

/* Rogue's Backstab targeting mode: every monster in the room wiggles
   continuously (renderRogueTargeting() in js/ui.js toggles this class on
   #room .card--monster while state.rogueTargeting is true) so it's obvious
   a target needs to be picked. Reuses card-shake's translateX-only
   approach (never rotate/scale — see that keyframe's own comment for why:
   it keeps the bottom value label from drifting), just looped instead of
   playing once. */
@keyframes card-target-wiggle {
  0%,
  100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-3px);
  }
  75% {
    transform: translateX(3px);
  }
}

.card--targetable {
  animation: card-target-wiggle 0.5s ease-in-out infinite;
}

/* Shield shatter: played by animateShieldShatter() (js/ui.js) when a shield
   breaks. A fixed-position container sits exactly over the (now-hidden)
   shield slot; each .shield-shard is a full clone of the slot clipped to
   one pie slice via clip-path (set inline, per-shard, in JS) so together
   they reconstruct the whole card, then fly outward and fade — the
   --shatter-* custom properties (also set inline per shard) are each
   shard's own direction/rotation, computed from its slice's angle. */
.shield-shatter-container {
  position: fixed;
  z-index: 80;
  pointer-events: none;
}

.shield-shard {
  position: absolute;
  inset: 0;
  margin: 0;
  animation: shield-shard-fly 0.55s cubic-bezier(0.3, 0.6, 0.4, 1) forwards;
}

@keyframes shield-shard-fly {
  0% {
    transform: translate(0, 0) rotate(0deg);
    opacity: 1;
  }
  100% {
    transform: translate(var(--shatter-dx), var(--shatter-dy)) rotate(var(--shatter-rot));
    opacity: 0;
  }
}

/* Suit color (matches normal playing-card convention) */
.card[data-suit='clubs'],
.card[data-suit='spades'] {
  color: var(--card-text);
}

.card[data-suit='diamonds'],
.card[data-suit='hearts'] {
  color: var(--card-text-red);
}

/* Type color (monster/weapon/potion), used for the border/glow below.
   Monsters use a dark purple that gets progressively more saturated at
   higher tiers — see the .card--monster.card--tier-4/5 overrides further
   down — while an inward dark vignette (--card-glow-inset, kept as its own
   token so it can stay unanimated — see the .card--aura keyframes further
   down for why that matters) is what gives the strongest ones their
   "gloomy" read. Weapons default to white here, but that's normally
   overridden per-card via card.glowRgb (applied as an inline style in
   ui.js) — see js/cards.js. */
.card--monster {
  --edge-rgb: 100, 35, 140;
}

.card--weapon {
  --edge-rgb: var(--weapon-rgb);
}

.card--potion {
  --edge-rgb: var(--potion-rgb);
}

.card--shield {
  --edge-rgb: var(--shield-rgb);
}

/* --- strength tiers ---------------------------------------------------
   Set via cardTier(rank) in ui.js: 2-4 -> tier 1 (weakest) ... 14 -> tier 5
   (strongest — only the Ace/Cthulhu monster reaches it; weapons/potions
   cap at 10, so they top out around tier 3). Each tier ramps up the border
   width, how solid/saturated its color is, and how much it glows.
   --card-glow is the OUTER glow only; --card-glow-inset (default invisible)
   is a separate token so .card--aura's pulse can vary the outer glow's
   numbers smoothly while leaving the inset shadow's value completely
   untouched between keyframes — mixing an inset and non-inset shadow at
   the same list position is not smoothly interpolatable and was causing a
   visible white flash mid-pulse. */
.card--tier-1 {
  --card-border-width: 1px;
  --card-border-alpha: 0.3;
}

.card--tier-2 {
  --card-border-width: 1.5px;
  --card-border-alpha: 0.55;
  --card-glow: 0 0 6px 0 rgba(var(--edge-rgb), 0.3);
}

.card--tier-3 {
  --card-border-width: 2px;
  --card-border-alpha: 0.85;
  --card-glow: 0 0 11px 1px rgba(var(--edge-rgb), 0.55);
}

.card--tier-4 {
  --card-border-width: 2.5px;
  --card-border-alpha: 1;
  --card-glow: 0 0 16px 2px rgba(var(--edge-rgb), 0.7);
}

.card--tier-5 {
  --card-border-width: 3px;
  --card-border-alpha: 1;
  --card-glow: 0 0 22px 4px rgba(var(--edge-rgb), 0.85);
}

/* Only monsters ever reach tier 4/5 (weapons/potions cap at rank 10 / tier
   3) — for them specifically, the color gets more saturated as the tier
   goes up, and an inward dark vignette is layered in via --card-glow-inset,
   so the strongest monsters read as gloomy/ominous *and* still clearly
   visible, rather than just fading into a dark, low-contrast color. */
.card--monster.card--tier-4 {
  --edge-rgb: 85, 22, 120;
  --card-glow: 0 0 16px 2px rgba(var(--edge-rgb), 0.75);
  --card-glow-inset: inset 0 0 12px rgba(0, 0, 0, 0.45);
}

.card--monster.card--tier-5 {
  --edge-rgb: 70, 12, 100;
  --card-glow: 0 0 24px 4px rgba(var(--edge-rgb), 0.9);
  --card-glow-inset: inset 0 0 16px rgba(0, 0, 0, 0.55);
}

/* Potions get a gentle "life" pulse on top of the tier system instead of
   sitting there as a static red outline — a slow breathing glow that gets
   more noticeable the stronger the potion is (every potion pulses, since
   none of them ever reach tier 4/5 — they cap at rank 10 / tier 3).

   Implementation note: this does NOT animate box-shadow directly — that
   forces the browser to recompute blur/spread every frame (an expensive,
   non-GPU-accelerated repaint), which looked janky/stuttery with several
   cards pulsing at once. Instead, a ::after the same size as the card
   holds a fixed, pre-computed peak-glow box-shadow, and only its
   *opacity* is animated — opacity is compositor-only and animates
   perfectly smoothly regardless of how many cards are doing it at once. */
.card--potion {
  position: relative;
}

.card--potion::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 10px 1px rgba(var(--edge-rgb), 0.35);
  opacity: 0;
  pointer-events: none;
  animation: card-glow-fade 2.6s ease-in-out infinite;
}

.card--potion.card--tier-2::after {
  box-shadow: 0 0 15px 2px rgba(var(--edge-rgb), 0.55);
}

.card--potion.card--tier-3::after {
  box-shadow: 0 0 20px 3px rgba(var(--edge-rgb), 0.7);
}

/* --- aura: the very strongest cards get more than "just a glow" ---------
   J/Q/K/A monsters (rank 11+) and the 3 strongest weapons (rank 8+) get
   .card--aura added in ui.js (hasAura()): the card's outer glow flares
   gently brighter and back, instead of sitting at a single static glow.
   (A few things were tried and dropped here: a rotating gradient ring,
   which wasn't noticeable in practice; scaling the card up/down, which
   visibly dragged the value label at the bottom up and down since scale
   grows the box from its center; and animating box-shadow's own
   blur/spread directly, which was janky/stuttery for the same reason
   described in the potion pulse above — this uses the same fixed-shadow +
   opacity-fade ::after technique instead.) This sits on top of the tier
   system above, not instead of it (the tier border/glow still applies
   underneath, unanimated). */
.card--aura {
  position: relative;
}

.card--aura::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 18px 3.5px rgba(var(--edge-rgb), 0.6);
  opacity: 0;
  pointer-events: none;
  animation: card-glow-fade 2.8s ease-in-out infinite;
}

@keyframes card-glow-fade {
  0%,
  100% {
    opacity: 0;
  }
  50% {
    opacity: 1;
  }
}

.card-suit-symbol {
  font-size: calc(33.6px * var(--card-scale));
  line-height: 1;
}

/* The card's numeric value, always a plain number (2-14) — never a suit
   letter like J/Q/K/A — shown at the bottom of the card instead of the old
   "MONSTER"/"WEAPON"/"POTION" type word. */
.card-value-label {
  font-family: var(--font-ui);
  font-size: calc(22px * var(--card-scale));
  font-weight: 600;
  opacity: 0.85;
}

.card-image {
  width: 100%;
  height: calc(78px * var(--card-scale));
  object-fit: contain;
}

.card-monster-icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* --- weapon/shield slots / toggle ------------------------------------------- */

#weapon-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(0.3rem + 0.3rem * var(--card-scale));
}

/* Shield slot sits to the left of the weapon slot, same size, same gap idea
   as the rest of the page's sizing. Shields currently have no gameplay
   effect (see equipShield() in js/state.js) — this is purely the equip
   animation + display, functionality is a planned follow-up. */
#equipment-slots {
  display: flex;
  align-items: center;
  gap: calc(0.3rem + 0.3rem * var(--card-scale));
}

/* Both slots reuse .card sizing/layout, but at a smaller scale than room
   cards (--weapon-slot-scale on top of --card-scale) — they're supporting
   elements, not the main focus, so shrinking them a bit saves space. When
   empty each is a plain dashed outline with an icon; when filled it looks
   like a normal card (see fillCardFace() in ui.js, which reuses the same
   card-face markup/classes, overridden smaller below). */
#weapon-slot-card,
#shield-slot-card {
  width: calc(100px * var(--card-scale) * var(--weapon-slot-scale));
  height: calc(140px * var(--card-scale) * var(--weapon-slot-scale));
  padding: calc(8px * var(--card-scale) * var(--weapon-slot-scale)) 0;
  border-top-width: calc(4px * var(--card-scale) * var(--weapon-slot-scale));
}

#weapon-slot-card .card-suit-symbol,
#shield-slot-card .card-suit-symbol {
  font-size: calc(33.6px * var(--card-scale) * var(--weapon-slot-scale));
}

#weapon-slot-card .card-value-label,
#shield-slot-card .card-value-label {
  font-size: calc(22px * var(--card-scale) * var(--weapon-slot-scale));
}

#weapon-slot-card .card-image,
#shield-slot-card .card-image {
  height: calc(78px * var(--card-scale) * var(--weapon-slot-scale));
}

.weapon-slot-empty,
.shield-slot-empty {
  background: transparent;
  border: 2px dashed var(--border);
  box-shadow: none;
  cursor: default;
}

.weapon-slot-empty:hover,
.shield-slot-empty:hover {
  box-shadow: none;
  transform: none;
}

.sword-icon,
.shield-icon {
  font-size: calc(36.8px * var(--card-scale) * var(--weapon-slot-scale));
  color: var(--text-muted);
}

/* Champion active-ability button — sits to the right of the weapon slot,
   inside #equipment-slots. Deliberately circular rather than another card
   shape, so it reads as a distinct "ability" affordance instead of a third
   equip slot. #ability-wrap exists purely to size/position the circular
   mana-ring gauge (#mana-ring, see below) relative to the button without
   disturbing #equipment-slots' own flex layout — --ability-size/
   --ring-thickness are custom properties set here and read by both the
   button and the ring (custom properties inherit to descendants), so the
   two always stay concentric. Everything is still driven off the same
   --card-scale/--weapon-slot-scale tokens the weapon/shield slots use, so
   the whole group scales together at every breakpoint. Icon artwork is
   filled in by renderAbilityButton() (js/ui.js) from js/ability-icons.js;
   the mana ring by renderManaRing() (js/ui.js) — see "Champion Active
   Abilities" in CLAUDE.md. The ability's actual gameplay effect is still a
   planned follow-up — only the mana-gating/reset plumbing exists so far. */
#ability-wrap {
  --ability-size: calc(100px * var(--card-scale) * var(--weapon-slot-scale));
  --ring-thickness: calc(10px * var(--card-scale) * var(--weapon-slot-scale));
  position: relative;
  width: calc(var(--ability-size) + 2 * var(--ring-thickness));
  height: calc(var(--ability-size) + 2 * var(--ring-thickness));
  flex-shrink: 0;
  transition: transform 0.2s ease;
}

/* The hover lift is applied to the whole wrap, not just #ability-btn —
   #mana-ring and #ability-btn are two separate absolutely-positioned
   children, so transforming the button alone used to slide it up away from
   a ring that stayed put, briefly exposing the ring's full circle (not
   just its rim) in the gap left behind. Moving the shared parent instead
   keeps the button centered on the ring at every point of the lift, so the
   button-covers-the-center illusion holds throughout the hover animation,
   not just at rest. renderManaRing() (js/ui.js) mirrors #ability-btn's
   .ability-btn--disabled onto the wrap as .ability-wrap--disabled so this
   selector can gate the lift on ability-readiness without a :has(). */
#ability-wrap:hover:not(.ability-wrap--disabled) {
  transform: translateY(-2px);
}

/* Sized to fill #ability-wrap (button + ring-thickness on every side) and
   painted with a segmented conic-gradient by renderManaRing() — sitting
   behind the button (z-index 0 vs. the button's 1), so only the ring's
   outer rim ever shows past the button's own opaque circular background,
   like a washer. The constant glow reads as "this is a resource gauge",
   matching the pulsing glows used elsewhere for cards (see CLAUDE.md's
   "Pulsing glows" note) without needing per-segment box-shadows, which a
   single gradient background can't do. */
#mana-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  z-index: 0;
  filter: drop-shadow(0 0 0.25rem rgba(var(--mana-rgb), 0.55));
}

#ability-btn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: var(--ability-size);
  height: var(--ability-size);
  z-index: 1;
  border-radius: 50%;
  background: var(--card-bg);
  border: calc(2px * var(--card-scale) * var(--weapon-slot-scale)) solid var(--accent);
  padding: calc(14px * var(--card-scale) * var(--weapon-slot-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.25s ease, opacity 0.25s ease;
}

/* Only the glow lives here — the actual lift is #ability-wrap's (see
   above), so the ring rises together with the button instead of the button
   sliding off on its own. */
#ability-wrap:hover:not(.ability-wrap--disabled) #ability-btn {
  box-shadow: 0 0 0.6rem rgba(217, 164, 65, 0.5);
}

/* Not enough mana yet to use the ability — same grayscale-and-dim treatment
   as .weapon-slot-inactive below, and not clickable-looking.
   renderManaRing() (js/ui.js) toggles this class off again the instant mana
   reaches the champion's ABILITY_MANA_COST. */
#ability-btn.ability-btn--disabled {
  filter: grayscale(1);
  opacity: 0.45;
  cursor: default;
}

/* Rogue's Backstab cancel badge — a small ✕ pinned to #ability-wrap's
   top-right corner (not #ability-btn itself, same reasoning as the golden
   glow above: keep anything that needs to sit outside a real <button>'s
   own box off of that button). Shown only while state.rogueTargeting is
   true (toggled by renderRogueTargeting() in js/ui.js) as the one way to
   back out of targeting mode without spending mana. Every property here is
   overridden from the generic `button` rule further up in this file
   (padding, size, shape, color, hover) since that rule's carved-stone
   rectangular-button look doesn't fit a tiny round badge. */
#ability-cancel-btn {
  position: absolute;
  top: -0.3rem;
  right: -0.3rem;
  width: 1.2rem;
  height: 1.2rem;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.35);
  background: var(--danger);
  color: #fff;
  font-family: var(--font-body);
  font-size: 0.7rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

#ability-cancel-btn.hidden {
  display: none;
}

#ability-cancel-btn:hover:not(:disabled) {
  transform: none;
  background: #d64a3a;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

#ability-cancel-btn::before {
  content: none;
}

/* Ability info badge — a small blue "i" circle pinned to #ability-wrap's
   bottom-right corner (mirrors #ability-cancel-btn's placement/reasoning
   above, just the opposite corner so the two never collide: Backstab's ✕
   only shows during targeting, this "i" is always available). A plain div,
   not a <button> — it's a hover/focus target, not something you click to
   trigger an action, so it deliberately skips the generic `button` rule's
   carved-stone look and hover-lift entirely. Colored with --mana-rgb (the
   same blue as the mana ring) since it's specifically explaining the
   mana-costed ability, not a generic "help" affordance. */
#ability-info-btn {
  position: absolute;
  bottom: -0.3rem;
  right: -0.3rem;
  width: 1.2rem;
  height: 1.2rem;
  border-radius: 50%;
  background: rgb(var(--mana-rgb));
  color: #fff;
  font-family: var(--font-body);
  font-weight: 700;
  font-style: italic;
  font-size: 0.7rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  cursor: help;
  z-index: 3;
  user-select: none;
}

#ability-info-btn.hidden {
  display: none;
}

#ability-info-btn:hover,
#ability-info-btn:focus {
  background: #4f95ef;
  outline: none;
}

/* The popup itself: hidden by default (opacity/visibility, not display, so
   the fade transition can run), shown by the adjacent-sibling hover/focus
   selector below. Anchored by its own right edge (not centered via
   left: 50%) because #ability-wrap is the rightmost item in #equipment-slots
   and sits close to the right edge of the page on narrow screens — a
   centered popup would risk overflowing off-screen there, but one that only
   grows leftward from a fixed right edge stays on-screen regardless of
   #ability-wrap's own position. Positioned above the wrap (bottom: 100%),
   not below, since #equipment-slots already sits low on the page. */
#ability-info-popup {
  position: absolute;
  bottom: calc(100% + 0.5rem);
  right: 0;
  width: max-content;
  max-width: min(13rem, 60vw);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0.4rem;
  padding: 0.5rem 0.65rem;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
  font-family: var(--font-body);
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--text);
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  pointer-events: none;
  z-index: 50;
}

#ability-info-name {
  font-family: var(--font-ui);
  color: var(--accent);
  margin-bottom: 0.2rem;
  font-size: 0.8rem;
}

/* :hover/:focus on the badge shows the popup; :hover on the popup itself
   keeps it open too, so moving the mouse from the badge into the popup
   (e.g. to read a longer description) doesn't immediately close it. */
#ability-info-btn:hover + #ability-info-popup,
#ability-info-btn:focus + #ability-info-popup,
#ability-info-popup:hover {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

/* Golden "still active" pulse — shown while a champion's active ability has
   an ongoing effect running (currently just Paladin's 3-hit damage
   resistance, see paladinResistCharges in js/state.js and
   renderAbilityActiveGlow() in js/ui.js; add a future champion's own
   ongoing-effect flag to that function's condition the same way). Follows
   the codebase's standing "animate opacity on a ::after, never box-shadow
   directly" rule for pulsing glows (see CLAUDE.md's "Pulsing glows" note
   under Monster artwork) — a fixed peak-glow box-shadow lives on the
   pseudo-element and only its opacity animates, so the pulse stays
   compositor-only/smooth instead of forcing a repaint every frame.
   Deliberately lives on #ability-wrap, not #ability-btn: #ability-btn is a
   real <button>, and the generic `button` selector (further up in this
   file) sets `overflow: hidden` on every button to clip its own hover
   "sheen" sweep. Putting the glow's ::after there too meant it either got
   silently clipped (with the default hidden), or — the first attempted
   fix — overriding that button's `overflow: visible` also un-clipped its
   sheen sweep, which then showed up as a stray bar sliding out from the
   button on hover. #ability-wrap is a plain div with no such baggage, so
   the glow sits there instead, sized/centered to match the button
   (--ability-size, inherited from #ability-wrap itself) rather than using
   `inset: 0` (which would size it to the whole wrap, ring included). */
.ability-wrap--active::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--ability-size);
  height: var(--ability-size);
  transform: translate(-50%, -50%);
  border-radius: 50%;
  box-shadow: 0 0 0.9rem rgba(217, 164, 65, 0.9), 0 0 1.5rem rgba(217, 164, 65, 0.55);
  opacity: 0;
  animation: ability-active-glow 1.4s ease-in-out infinite;
  pointer-events: none;
}

@keyframes ability-active-glow {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}

.ability-icon {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Applied whenever the "Using weapon" toggle is off, so the equipped weapon
   visibly looks "switched off" while you're fighting bare-handed. */
.weapon-slot-inactive {
  filter: grayscale(1);
  opacity: 0.45;
}

#weapon-status {
  font-size: 0.85rem;
  color: var(--text-muted);
  min-height: 1.1rem;
}

.toggle-wrapper {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-ui);
  font-size: 1rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  user-select: none;
}

.toggle-wrapper span {
  transition: color 0.2s ease, text-shadow 0.2s ease;
}

.toggle-wrapper:hover span {
  color: var(--accent-hover);
}

.toggle-wrapper input[type='checkbox'] {
  appearance: none;
  width: 2.375rem;
  height: 1.3125rem;
  margin: 0;
  background: linear-gradient(180deg, var(--surface), var(--surface-hover));
  border: 1px solid var(--border);
  border-radius: 0.6875rem;
  position: relative;
  cursor: pointer;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
  transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

.toggle-wrapper:hover input[type='checkbox'] {
  border-color: var(--accent);
}

.toggle-wrapper input[type='checkbox']::before {
  content: '';
  position: absolute;
  top: 0.125rem;
  left: 0.125rem;
  width: 0.9375rem;
  height: 0.9375rem;
  border-radius: 50%;
  background: linear-gradient(180deg, #d4d4dc, var(--text-muted));
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.2s ease;
}

.toggle-wrapper input[type='checkbox']:checked {
  background: linear-gradient(180deg, var(--accent-hover), var(--accent));
  border-color: var(--accent);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35), 0 0 8px 1px rgba(217, 164, 65, 0.5);
}

.toggle-wrapper:hover input[type='checkbox']:checked {
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35), 0 0 12px 3px rgba(217, 164, 65, 0.7);
}

.toggle-wrapper input[type='checkbox']:checked::before {
  transform: translateX(1.0625rem);
  background: linear-gradient(180deg, #fff3d6, #f0cd7a);
}

/* Clone created by animateWeaponToSlot() in ui.js while a weapon card
   flies from the room into the weapon slot. */
.weapon-flying {
  position: fixed;
  margin: 0;
  z-index: 50;
  pointer-events: none;
  cursor: default;
  transition: transform 0.38s ease, opacity 0.38s ease;
}

/* --- Victory / Defeat banner ------------------------------------------------
   Original, simplistic design (angled banner shape via clip-path, flat
   color + a soft pulsing glow) — not an image asset, so it's easy to
   re-theme later just by editing colors below. --------------------------- */

#gameover-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2.5rem;
  z-index: 200;
}

#gameover-overlay.hidden {
  display: none;
}

#gameover-banner {
  --glow-color: 217, 164, 65; /* matches --accent, as r,g,b for use in rgba() */
  padding: 1.4rem 4rem;
  background: linear-gradient(180deg, #26221a, #17140f);
  border: 1px solid rgb(var(--glow-color));
  clip-path: polygon(6% 0%, 94% 0%, 100% 50%, 94% 100%, 6% 100%, 0% 50%);
  animation: banner-in 0.45s ease, banner-glow 2.4s ease-in-out infinite 0.45s;
}

#gameover-overlay.gameover-lost #gameover-banner {
  --glow-color: 192, 57, 43; /* matches --danger */
}

#gameover-text {
  display: block;
  font-family: var(--font-display);
  font-size: 2.1rem;
  font-weight: 400;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: rgb(var(--glow-color, 217, 164, 65));
  text-shadow: 0 0 12px rgba(var(--glow-color, 217, 164, 65), 0.6);
}

#gameover-banner {
  color: rgb(var(--glow-color));
}

@keyframes banner-in {
  from {
    opacity: 0;
    transform: scale(0.75) translateY(24px);
  }
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

@keyframes banner-glow {
  0%, 100% {
    box-shadow: 0 0 14px 2px rgba(var(--glow-color), 0.3);
  }
  50% {
    box-shadow: 0 0 26px 6px rgba(var(--glow-color), 0.55);
  }
}

/* --- Tutorial (custom addition, see js/tutorial.js) ------------------------
   A guided walkthrough that runs on the real game engine with a fixed
   scripted deck instead of a shuffle. Rather than a full-page dimming
   overlay with a cutout (fragile to get right against the existing
   weapon-attack/shield-shatter z-index choreography), every non-target
   interactive element is dimmed and made unclickable directly via
   body.tutorial-active, and the one element the current step wants clicked
   gets .tutorial-target layered on top to both restore its clickability
   (higher specificity wins) and draw the eye to it. */

body.tutorial-active #room .card,
body.tutorial-active #flee-btn,
body.tutorial-active #ability-wrap,
body.tutorial-active .toggle-wrapper {
  opacity: 0.3;
  pointer-events: none;
  filter: grayscale(0.4);
  transition: opacity 0.2s ease, filter 0.2s ease;
}

/* #menu-btn is deliberately NOT in the dimmed list above, unlike every
   other interactive element, so the hamburger menu (and its "Back to Menu"
   button) stays reachable throughout the tutorial as an always-available
   way out, same spirit as #tutorial-skip-btn. See the #new-game-btn/
   #back-to-menu-btn listeners in js/tutorial.js, which end the tutorial
   (endTutorial()) the moment either is used from within it. */

.tutorial-target {
  opacity: 1 !important;
  pointer-events: auto !important;
  filter: none !important;
  outline: 3px solid rgba(217, 164, 65, 0.9);
  outline-offset: 4px;
  border-radius: var(--radius);
  animation: tutorial-target-pulse 1.2s ease-in-out infinite;
  position: relative;
  z-index: 30;
}

@keyframes tutorial-target-pulse {
  0%, 100% {
    outline-color: rgba(217, 164, 65, 0.9);
  }
  50% {
    outline-color: rgba(217, 164, 65, 0.35);
  }
}

/* The coachmark bubble: positioned in JS (positionCoachmarkNear()/
   positionCoachmarkCenter() in js/tutorial.js) via plain left/top, since its
   anchor (a room card, or screen-center for an info-only step) moves every
   step and isn't known ahead of time. */
#tutorial-coachmark {
  position: fixed;
  /* Below #gameover-overlay (200) and #menu-overlay/#gallery-overlay/
     #champion-select-overlay (300) on purpose, so opening the hamburger
     menu during the tutorial (see #menu-btn's exemption above) covers the
     coachmark cleanly instead of it floating on top of the menu panel. */
  z-index: 150;
  max-width: min(20rem, 85vw);
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: 0.6rem;
  padding: 0.75rem 1rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  transition: left 0.25s ease, top 0.25s ease;
}

/* This project doesn't use one shared global .hidden utility class — every
   hideable element gets its own `#id.hidden { display: none }` rule (see
   e.g. #start-screen.hidden/#game-screen.hidden above) so a plain `.hidden`
   selector never has to out-specificity an element's own ID-scoped layout
   rules. #tutorial-next-btn already has its own further down; these two
   need the same. */
#tutorial-coachmark.hidden,
#tutorial-skip-btn.hidden {
  display: none;
}

#tutorial-coachmark-text {
  margin: 0 0 0.5rem;
  font-family: var(--font-body);
  font-size: 0.85rem;
  line-height: 1.4;
  color: var(--text);
}

#tutorial-coachmark-text:last-child {
  margin-bottom: 0;
}

#tutorial-next-btn.hidden {
  display: none;
}

#tutorial-skip-btn {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 150; /* see #tutorial-coachmark's z-index comment above */
  padding: 0.4rem 0.75rem;
  font-size: 0.8rem;
  color: var(--text-muted);
}

#tutorial-skip-btn:hover {
  color: var(--text);
}

/* --- very short viewport safety net --------------------------------------
   Deliberately placed at the very END of the file: it needs to override
   the base h1/button/body rules above (same selector specificity, so
   normal cascade order applies — last one in the file wins), not just the
   --card-scale tokens. Covers e.g. a phone rotated to landscape, where the
   window may be wide enough to otherwise qualify for the tablet/desktop
   tiers, but doesn't have the vertical room for their fixed-size title and
   buttons. -------------------------------------------------------------- */
@media (max-height: 480px) {
  html {
    font-size: 16px;
  }

  :root {
    --card-scale: 0.45;
    --weapon-slot-scale: 0.5;
    --stack-gap: 0.2rem;
  }

  body {
    padding: 0.3rem 1rem;
  }

  h1 {
    font-size: 1.5rem;
  }

  button {
    padding: 0.3rem 0.9rem;
    font-size: 0.85rem;
  }

  #message {
    font-size: 0.75rem;
  }

  #start-logo {
    font-size: 1.8rem;
  }

  #start-tagline {
    margin-top: 0;
  }

  #start-new-game-btn {
    font-size: 0.95rem;
    padding: 0.4rem 1.4rem;
  }
}
