/* ===== BrainPang Web Client — mobile-first, large text/touch targets ===== */

:root {
  /* Shared "outline text" treatment (see the BP outline text rule at the end
     of this file and D:\BrainPang_HQ\docs\web-ui-style.md). */
  --bp-outline-color: #000;
  --bp-outline-width: 0.2em;
  /* Flat drop shadow 0.06em long, falling down and slightly right: 70deg
     from horizontal, 20deg off straight down (x = 0.06em*cos70,
     y = 0.06em*sin70). em = the styled element's own font size. */
  --bp-shadow-x: 0.021em;
  --bp-shadow-y: 0.056em;
  /* Platform default typeface, used where Maplestory is not wanted (footers)
     and as the fallback after it everywhere else. */
  --bp-system-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Text outline: a text stroke --bp-outline-width wide painted under the
     fill (paint-order: stroke fill), so its outer half shows as the outline,
     and one flat drop shadow. Owner decision 2026-10-29: the 37 offset
     shadow copies used before stacked into stepped, color-fringed edges. */
  --bp-text-stroke: var(--bp-outline-width) var(--bp-outline-color);
  --bp-text-outline: var(--bp-shadow-x) var(--bp-shadow-y) 0 var(--bp-outline-color);
  --color-bg: #1b1730;
  --color-bg-alt: #241f3d;
  --color-accent: #ffb703;
  --color-accent-dark: #e09400;
  --color-text: #ffffff;
  --color-text-dim: #cfc9e8;
  --color-danger: #ff4d4d;
  --color-success: #4dff88;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* Popups stay clear of the notch / Dynamic Island and the home bar (2026-11-07: the quest close button sat under the notch). */
  --popup-safe-y: calc(var(--safe-top) + var(--safe-bottom));
  /* UI refresh palette (matches assets/ui-refresh/*.svg) and layout sizes. */
  --ui-crimson-light: #3f8df5;
  --ui-crimson: #1264d6;
  --ui-crimson-dark: #073f9b;
  --ui-crimson-shadow: #00245f;
  --ui-cta-green-mid: #63dd00;
  --ui-gold: #ffdc24;
  --ui-gold-dark: #ed7900;
  --ui-gold-highlight: #fff3a4;
  --ui-gold-depth: #bf6500;
  --ui-cream: #fff9e8;
  --ui-cream-dark: #ffe9b5;
  --ui-cream-highlight: #fffdf4;
  --ui-cream-depth: #e4b66f;
  --ui-brown: #5a2a10;
  --ui-panel-edge: 30px;
  --ui-badge-h: 52px;
  --round-btn-size: 52px;
  --lobby-side-col: 84px;
  --bottom-nav-h: calc(68px + var(--safe-bottom));
  /* Vertical space the game screen uses besides the two panes (top bar,
     board margins/padding, hint row, footer); landscape sizes panes from it. */
  --game-chrome-h: 252px;
}

* { box-sizing: border-box; }

/* Maplestory (NEXON) is the game-wide typeface, self-hosted as woff2. It has
   no CJK ideographs, so Japanese kanji fall back to the system stack below. */
@font-face {
  font-family: "Maplestory";
  src: url("../assets/fonts/maplestory-light.woff2") format("woff2");
  font-weight: 100 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Maplestory";
  src: url("../assets/fonts/maplestory-bold.woff2") format("woff2");
  font-weight: 600 900;
  font-style: normal;
  font-display: swap;
}

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: "Maplestory", var(--bp-system-font);
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
}

/* Form controls don't inherit font-family by default. */
button, input, select, textarea {
  font-family: inherit;
}

.hidden { display: none !important; }

/* ---------- Coin icon (CSS-drawn so it never falls back to a tofu/globe glyph) ---------- */
.icon-coin {
  display: inline-block;
  width: 0.95em;
  height: 0.95em;
  border-radius: 50%;
  vertical-align: -0.12em;
  background: radial-gradient(circle at 32% 28%, #fff5cc 0%, #ffd23f 35%, var(--color-accent) 70%, var(--color-accent-dark) 100%);
  box-shadow: inset 0 0 0 2px rgba(140, 90, 0, 0.55), inset 0 -2px 2px rgba(0, 0, 0, 0.25);
}

/* ---------- Loading overlay ---------- */
.loading-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: rgba(27, 23, 48, 0.72);
  backdrop-filter: blur(2px);
}

.spinner {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 4px solid rgba(255, 255, 255, 0.25);
  border-top-color: var(--color-accent);
  animation: spin 0.8s linear infinite;
}

.loading-text {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--color-text-dim);
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

.screen {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
}

/* Lobby<->game hard-cut softener — replays automatically whenever a .screen
   goes from display:none to visible (removing 'hidden'), no JS involved. */
.screen:not(.hidden) {
  animation: screen-fade-in 180ms ease;
}

@keyframes screen-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Screen-reader-only text (kept in the DOM for SEO/accessibility). */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Shared refresh UI pieces (assets/ui-refresh) ----------
   Panels and CTA buttons are 9-sliced from the refresh SVGs with
   border-image, so they stretch to any size without distorting corners. */
.ui-panel,
.stage-panel,
.game-board,
.popup-card {
  border: 0 solid transparent;
  border-image-source: url('../assets/ui-refresh/panel-cream-wide.svg');
  border-image-slice: 48 fill;
  border-image-width: var(--ui-panel-edge);
  border-image-repeat: stretch;
}

/* Gallery panel and game board: a blue-only layer extends 10px outward
   behind the shared 30px frame (gold/cream lines stay their normal size).
   z-index -1 paints it under the panel's own frame, so neither panel may
   form its own stacking context. */
.stage-panel-frame,
.game-board-frame {
  position: absolute;
  inset: -10px;
  z-index: -1;
  border-radius: 30px;
  background: linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 45%, var(--ui-crimson-dark) 100%);
  box-shadow: 0 5px 0 var(--ui-crimson-shadow);
  pointer-events: none;
}

/* Navy round button with a cream face (home / settings). */
.round-btn {
  flex: 0 0 auto;
  position: relative;
  width: var(--round-btn-size);
  height: var(--round-btn-size);
  padding: 0;
  border: 2px solid var(--ui-crimson-shadow);
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 46%, var(--ui-cream) 0 52%, var(--ui-gold) 53% 59%, transparent 60%),
    linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 45%, var(--ui-crimson-dark) 100%);
  box-shadow: 0 4px 0 var(--ui-crimson-shadow), 0 6px 12px rgba(0, 0, 0, 0.35);
  cursor: pointer;
  touch-action: manipulation;
}

.round-btn img {
  position: absolute;
  inset: 2%;
  width: 96%;
  height: 96%;
  pointer-events: none;
}

/* Lobby and in-game Options gear art sits 3px higher (Home stays put). */
.resource-bar-settings img,
#btn-game-settings img {
  transform: translateY(-3px);
}

.round-btn:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--ui-crimson-shadow), 0 3px 6px rgba(0, 0, 0, 0.3);
}

/* Glossy navy resource bar holding cream pills (coins / hearts / hints). */
.resource-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 5px 8px;
  border: 2px solid var(--ui-crimson-shadow);
  border-radius: 999px;
  background: linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 45%, var(--ui-crimson-dark) 100%);
  box-shadow:
    0 4px 0 var(--ui-crimson-shadow),
    0 8px 16px rgba(0, 0, 0, 0.35),
    inset 0 2px 0 rgba(169, 200, 245, 0.75);
}

.resource-avatar {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  margin: -6px 0 -6px -6px;
  border: 3px solid var(--ui-gold);
  border-radius: 50%;
  overflow: hidden;
  background: radial-gradient(circle at 50% 35%, #8fd3ff 0%, #2f6fd1 100%);
  box-shadow: 0 2px 0 var(--ui-crimson-shadow);
}

.resource-avatar img {
  width: 150%;
  height: 150%;
  margin: -6% 0 0 -25%;
  object-fit: cover;
  object-position: 50% 0;
}

.resource-pill {
  flex: 1 1 0;
  min-width: 0;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 10px 0 2px;
  opacity: var(--bp-layer-opacity, 1);
  border: var(--bp-layer-stroke, 2px solid var(--ui-gold-dark));
  border-radius: var(--bp-layer-radius, 999px);
  background: var(--bp-layer-fill, linear-gradient(180deg, var(--ui-cream) 0%, var(--ui-cream-dark) 100%));
  box-shadow: var(--bp-layer-shadow,
    inset 0 1px 0 var(--ui-cream-highlight),
    inset 0 -2px 0 var(--ui-cream-depth),
    inset 0 -4px 0 rgba(201, 138, 60, 0.24));
  white-space: nowrap;
}

/* Artwork nudged 2px right as a visual offset; its layout box stays put. */
.resource-icon {
  flex: 0 0 auto;
  position: relative;
  left: 2px;
  width: var(--bp-layer-size, 36px);
  height: var(--bp-layer-size, 36px);
  margin: -4px 0 -4px -6px;
  display: block;
  opacity: var(--bp-layer-opacity, 1);
  filter: var(--bp-layer-filter, none);
  transform: var(--bp-layer-transform, none);
  pointer-events: none;
}

.resource-value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
  font-size: var(--bp-layer-font-size, 16px);
  font-weight: 800;
  /* Room above and below the digits: the Maplestory glyphs and the 0.2em outline reach past a
     1em line box, and overflow (kept for the ellipsis) would cut their tops off. */
  line-height: 1.4;
  color: var(--bp-layer-color, #fff);
  opacity: var(--bp-layer-opacity, 1);
  letter-spacing: var(--bp-layer-letter-spacing, normal);
  text-shadow: var(--bp-layer-text-shadow, var(--bp-text-outline));
  -webkit-text-stroke: var(--bp-layer-text-stroke, var(--bp-text-stroke));
  paint-order: stroke fill;
  transform: var(--bp-layer-transform, none);
}

.resource-pill--coin {
  flex-grow: 1.4;
}

.resource-pill--heart .resource-value {
  color: var(--color-danger);
}

@media (max-width: 420px) {
  .resource-bar {
    gap: 4px;
    padding: 4px 6px;
  }

  .resource-avatar {
    width: 42px;
    height: 42px;
  }

  .resource-pill {
    height: 34px;
    padding: 0 6px 0 0;
    gap: 2px;
  }

  .resource-icon {
    width: var(--bp-layer-size, 30px);
    height: var(--bp-layer-size, 30px);
    margin-left: -4px;
  }

  .resource-value {
    font-size: 0.95rem;
    letter-spacing: -0.02em;
  }

  :root {
    --round-btn-size: 44px;
  }
}

/* ---------- Title screen (lobby / stage select) ----------
   The screen scrolls (the legal footer sits below the first viewport);
   body has a blanket touch-action:none for the fixed app shell, so this
   needs its own pan-y override. */
.screen-title-lobby {
  align-items: stretch;
  justify-content: flex-start;
  overflow-y: auto;
  overflow-x: hidden;
  touch-action: pan-y;
  background:
    linear-gradient(to bottom, rgba(20, 10, 5, 0.18) 0%, rgba(20, 10, 5, 0.02) 30%, rgba(20, 10, 5, 0.5) 100%),
    url('../assets/lobby/lobby-bg-detective-v1.webp') center center / cover no-repeat;
  text-align: center;
}

/* Set while a lobby popup (Options) is open so the lobby behind it can't
   scroll. */
.screen-title-lobby.lobby-modal-open {
  overflow: hidden;
}

/* One viewport tall (minus the sticky bottom nav). Grid areas place the
   resource bar, side menus, logo, stage gallery and continue button; the
   wide/landscape media query below rearranges them. */
.lobby-main {
  position: relative;
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: var(--lobby-side-col) minmax(0, 1fr) var(--lobby-side-col);
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  grid-template-areas:
    "top top top"
    "left hero right"
    "gallery gallery gallery"
    "cta cta cta";
  gap: 6px 4px;
  width: 100%;
  height: calc(100vh - var(--bottom-nav-h));
  height: calc(100dvh - var(--bottom-nav-h));
  min-height: 560px;
  padding: calc(8px + var(--safe-top)) 8px 10px;
}

.lobby-topbar {
  grid-area: top;
  display: flex;
  justify-content: center;
  min-width: 0;
}

.lobby-topbar .resource-bar {
  width: min(100%, 560px);
}

/* Content menus (mission/gift on the left, event/collection on the right).
   They live inside #screen-title, so hiding the lobby hides them too; the
   in-game rule is a second guard. */
.lobby-content-menu {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(8px, 1.6vh, 16px);
  padding-top: 4px;
  z-index: 4;
}

.lobby-content-menu--left {
  grid-area: left;
}

.lobby-content-menu--right {
  grid-area: right;
}

body.in-game .lobby-content-menu {
  display: none;
}

/* Fixed 70.4px icon on the navy round button art; the caption pill
   overlaps the bottom edge. */
.lobby-content-button {
  position: relative;
  width: 70.4px;
  height: 70.4px;
  padding: 0;
  border: var(--bp-layer-stroke, none);
  border-radius: var(--bp-layer-radius, 50%);
  background: var(--bp-layer-fill, url('../assets/ui-refresh/button-round-crimson.svg') center / contain no-repeat);
  opacity: var(--bp-layer-opacity, 1);
  filter: var(--bp-layer-shadow, drop-shadow(0 4px 6px rgba(0, 0, 0, 0.35)));
  cursor: pointer;
  touch-action: manipulation;
}

.lobby-content-button img {
  position: absolute;
  left: -7%;
  top: -13%;
  width: 114%;
  height: 114%;
  pointer-events: none;
  opacity: var(--bp-layer-opacity, 1);
  filter: var(--bp-layer-filter, none);
  transform: var(--bp-layer-transform, none);
}

.lobby-content-button:active {
  transform: translateY(2px) scale(0.97);
}

.lobby-content-button:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

/* Visible caption over the icon's bottom edge (also the accessible name).
   The box is 130% of the icon, centered, so it overhangs 15% per side;
   game.js sets --menu-label-size so the widest label of every language
   fits that box. */
.lobby-content-label {
  position: absolute;
  left: -15%;
  bottom: 0;
  width: 130%;
  padding: 2px 3px;
  transform: var(--bp-layer-transform, translateY(40%));
  border: var(--bp-layer-stroke, 2px solid var(--ui-gold));
  border-radius: var(--bp-layer-radius, 999px);
  background: var(--bp-layer-fill, linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 55%, var(--ui-crimson-dark) 100%));
  box-shadow: var(--bp-layer-shadow,
    inset 0 1px 0 var(--ui-gold-highlight),
    inset 0 -2px 0 var(--ui-gold-depth));
  text-align: center;
  white-space: pre;
  font-size: var(--bp-layer-font-size, var(--menu-label-size, 12px));
  font-weight: 700;
  line-height: 1.1;
  color: var(--bp-layer-color, #fff);
  opacity: var(--bp-layer-opacity, 1);
  letter-spacing: var(--bp-layer-letter-spacing, normal);
  text-shadow: var(--bp-layer-text-shadow, var(--bp-text-outline));
  -webkit-text-stroke: var(--bp-layer-text-stroke, var(--bp-text-stroke));
  paint-order: stroke fill;
  pointer-events: none;
}

/* Daily Mission caption is split into two annotation layers: the wrapper
   (text-panel) owns the pill box, the inner label (text) keeps typography
   from .lobby-content-label. Rendered result matches the other captions. */
#mission-daily-label-panel {
  position: absolute;
  left: -15%;
  bottom: 0;
  width: 130%;
  padding: 2px 3px;
  transform: var(--bp-layer-transform, translateY(40%));
  border: var(--bp-layer-stroke, 2px solid var(--ui-gold));
  border-radius: var(--bp-layer-radius, 999px);
  background: var(--bp-layer-fill, linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 55%, var(--ui-crimson-dark) 100%));
  box-shadow: var(--bp-layer-shadow,
    inset 0 1px 0 var(--ui-gold-highlight),
    inset 0 -2px 0 var(--ui-gold-depth));
  pointer-events: none;
}

#mission-daily-label {
  position: static;
  display: block;
  width: auto;
  padding: 0;
  transform: var(--bp-layer-transform, none);
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

/* Mission tile = Daily Goal summary (Retention Core v1). A small corner
   badge inside the icon footprint: a conic progress pie (game.js sets
   --daily-progress) that turns into a check once today is complete. */
.mission-daily-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 22px;
  height: 22px;
  box-sizing: border-box;
  border: 2px solid var(--ui-gold);
  border-radius: 50%;
  background: conic-gradient(var(--color-accent) var(--daily-progress, 0%), var(--ui-crimson-dark) 0);
  pointer-events: none;
}

.mission-daily-badge--complete {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: url('../assets/ui-refresh/premium-icons/notification-count-v1.webp') center / contain no-repeat;
}

.mission-daily-check {
  display: none;
}

.mission-daily-badge--complete .mission-daily-check {
  display: block;
  position: absolute;
  left: 50%;
  top: 50%;
  width: 4px;
  height: 9px;
  border: solid var(--color-text);
  border-width: 0 3px 3px 0;
  transform: translate(-50%, -50%) rotate(45deg);
}

/* Reusable corner notifications. Art stays separate from the CSS/HTML text
   so the green count can change without generating another image. */
.notification-badge {
  position: absolute;
  z-index: 6;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  color: var(--bp-layer-color, #fff);
  font-size: var(--bp-layer-font-size, 14px);
  font-weight: 900;
  line-height: 1;
  text-align: center;
  text-shadow: var(--bp-layer-text-shadow, var(--bp-text-outline));
  -webkit-text-stroke: var(--bp-layer-text-stroke, var(--bp-text-stroke));
  paint-order: stroke fill;
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
  pointer-events: none;
  opacity: var(--bp-layer-opacity, 1);
  transform: var(--bp-layer-transform, none);
}

.notification-badge--alert {
  top: -2px;
  right: -2px;
  background-image: url('../assets/ui-refresh/premium-icons/notification-alert-v1.webp');
}

.notification-badge--count {
  background-image: url('../assets/ui-refresh/premium-icons/notification-count-v1.webp');
}

.notification-badge--side {
  top: -2px;
  right: -2px;
}

.notification-badge--nav {
  top: 1px;
  left: calc(50% + 10px);
}

.lobby-hero {
  grid-area: hero;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
}

.brainpang-logo {
  display: block;
  height: clamp(96px, 19vh, 230px);
  height: clamp(96px, 19dvh, 230px);
  width: auto;
  max-width: 100%;
  object-fit: contain;
  filter: drop-shadow(0 8px 16px rgba(20, 10, 5, 0.45));
}

/* ---------- Stage gallery panel ---------- */
.level-gallery {
  --gallery-cols: 3;
  grid-area: gallery;
  position: relative;
  min-height: 0;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 820px;
  justify-self: center;
  margin: 26px auto 0;
  /* 19px = the frame's innermost line above the panel bottom (panel-cream-wide.svg slice 48 at 30px),
     so the progress label below ends exactly at that line. */
  padding: 30px 14px 19px;
}

.stage-panel-header {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, -52%);
  z-index: 2;
}

/* Gallery title and in-game level title: pure-CSS navy capsule
   (semicircle ends) with a gold rim and a cream inner line. */
.stage-panel-title,
.game-board-title {
  margin: 0;
  min-width: 170px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 32px;
  border: 3px solid var(--ui-gold);
  border-radius: 26px;
  background: linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 50%, var(--ui-crimson-dark) 100%);
  box-shadow:
    inset 0 0 0 2px var(--ui-cream-dark),
    0 3px 0 var(--ui-gold-dark),
    0 6px 10px rgba(0, 0, 0, 0.3);
  font-size: clamp(1.1rem, 2.6vh, 1.6rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: #fff;
}

.stage-panel-body {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The label box is the whole strip between the gallery's bottom edge and the frame line, with the
   text centred in it, so the space above and below "1/694" is equal at every size and scroll position. */
.level-gallery-progress {
  flex: 0 0 auto;
  margin: 0;
  height: 30px;
  font-size: 20px;
  font-weight: 800;
  line-height: 30px;
  color: var(--color-accent);
}

.level-gallery-grid {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(var(--gallery-cols), minmax(0, 1fr));
  /* The grid has a definite (flexed) height, so plain auto rows would only
     get the cards' minimum size (0 for overflow:hidden) and collapse. */
  grid-auto-rows: max-content;
  align-content: start;
  gap: 8px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--ui-crimson) transparent;
  padding: 6px 10px;
  border-radius: 14px;
}

/* Design 2.5.2 §11: the cards come in groups of 60 (js/game.js chunkGalleryCards), each a full-width
   row of the gallery grid with the same columns and gap. A group off screen skips style, layout and
   paint. Its stand-in height is the real one, rows x card height (column width x 410/709) plus the
   gaps, worked out from the gallery width (cqw), so the scroll height and the anchoring on the last
   played level stay exact; `auto` keeps the measured height once a group has been on screen. */
.level-gallery-grid {
  container-type: inline-size;
}

.level-gallery-chunk {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(var(--gallery-cols), minmax(0, 1fr));
  grid-auto-rows: max-content;
  gap: 8px;
}

/* Only where the stand-in height can be worked out (container units); older WebViews lay every
   card out as before. A declaration with var() never falls back to an earlier one, hence @supports. */
@supports (width: 1cqw) {
  .level-gallery-chunk {
    --chunk-rows: calc(var(--chunk-cards) / var(--gallery-cols));
    content-visibility: auto;
    contain-intrinsic-block-size: auto calc(
      var(--chunk-rows) * (100cqw - (var(--gallery-cols) - 1) * 8px) / var(--gallery-cols) * 410 / 709 + (var(--chunk-rows) - 1) * 8px);
  }
}

/* The last, partial group counts whole rows. */
@supports (width: calc(round(up, 1.5, 1) * 1cqw)) {
  .level-gallery-chunk {
    --chunk-rows: round(up, var(--chunk-cards) / var(--gallery-cols), 1);
  }
}

/* Non-interactive tail so the final rows can still be scrolled up to the
   top when anchoring the last-played level; height is set from game.js. */
.level-gallery-grid::after {
  content: '';
  display: block;
  grid-column: 1 / -1;
  height: var(--gallery-tail-space, 0px);
  pointer-events: none;
}

.level-card {
  display: block;
  position: relative;
  width: 100%;
  padding: 0;
  aspect-ratio: 709 / 410;
  border-radius: 10px;
  overflow: hidden;
  background: #fff;
  border: 3px solid #d9c49a;
  box-shadow: 0 2px 0 rgba(122, 80, 30, 0.35);
  cursor: default;
  font: inherit;
  appearance: none;
}

button.level-card--done,
button.level-card--open {
  cursor: pointer;
  touch-action: manipulation;
}

button.level-card--done:hover,
button.level-card--open:hover {
  border-color: var(--color-accent);
}

.level-card--current {
  border-color: var(--ui-gold);
  box-shadow: 0 0 0 2px var(--ui-gold-dark), 0 0 12px rgba(255, 183, 3, 0.75);
}

button.level-card--done:focus-visible,
button.level-card--open:focus-visible {
  outline: 3px solid var(--ui-crimson);
  outline-offset: 2px;
}

.level-card-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
  user-select: none;
}

.level-card--locked {
  border-color: #8f8a84;
  background: #6d6965;
}

.level-card--locked .level-card-img {
  filter: grayscale(1) brightness(0.55);
}

.level-card-number {
  position: absolute;
  left: 4px;
  top: 3px;
  font-size: 0.8rem;
  font-weight: 800;
  color: #fff;
  line-height: 1;
}

.level-card-badge {
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.62rem;
  font-weight: 900;
  line-height: 1;
}

.level-card-badge--done {
  background: #2fb85a;
  color: #12331f;
}

.level-card-badge--play {
  background: var(--color-accent);
  color: #2a1a00;
}

.level-card-lock {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.15);
}

.level-card-lock img {
  width: 36%;
  height: auto;
  max-height: 70%;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.45));
}

/* ---------- Continue button ----------
   Pure CSS, the same build as the LEVEL title panel (.stage-panel-title): gold rim, cream inner line,
   gold base and a bevel. Kept as its own rule so the layer tool styles the two separately; the face is a
   plain linear-gradient so its colours are editable in the tool's gradient editor. */
.play-row {
  grid-area: cta;
  position: relative;
  width: 100%;
  display: flex;
  justify-content: center;
  padding-top: 15px;
  padding-bottom: 15px;
}

.btn-play-hero {
  --cta-h: 92px;
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: min(360px, 78vw);
  height: var(--cta-h);
  margin-inline: auto;
  box-sizing: border-box;
  padding: 0 28px 4px;
  text-align: left;
  font-size: 2.4rem;
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #fff;
  background-color: #147800;
  background-image: linear-gradient(180deg, #c7ff19 0%, #8bea00 32%, #42ba00 68%, #147800 100%);
  border: 3px solid var(--ui-gold);
  border-radius: 15px;
  box-shadow:
    inset 0 3px 0 #ffffff88,
    inset 0 -3px 0 #00000033,
    inset 0 0 0 2px var(--ui-cream-dark),
    0 3px 0 var(--ui-gold-dark),
    0 6px 10px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  touch-action: manipulation;
}

/* The label and arrow remain separate annotation targets inside the CTA. */
.btn-play-label {
  position: relative;
  z-index: 1;
  display: inline-block;
  color: var(--bp-layer-color, #fff);
  opacity: var(--bp-layer-opacity, 1);
  font-size: var(--bp-layer-font-size, inherit);
  letter-spacing: var(--bp-layer-letter-spacing, inherit);
  text-shadow: var(--bp-layer-text-shadow, inherit);
  transform: var(--bp-layer-transform, none);
}

.btn-play-arrow {
  position: static;
  flex: 0 0 var(--bp-layer-size, calc(var(--cta-h) * 0.46));
  width: var(--bp-layer-size, calc(var(--cta-h) * 0.46));
  height: var(--bp-layer-size, calc(var(--cta-h) * 0.46));
  opacity: var(--bp-layer-opacity, 1);
  filter: var(--bp-layer-filter, none);
  transform: var(--bp-layer-transform, none);
  object-fit: contain;
}

.btn-play-hero:active {
  transform: translateY(3px);
  box-shadow:
    inset 0 3px 0 #ffffff88,
    inset 0 -3px 0 #00000033,
    inset 0 0 0 2px var(--ui-cream-dark),
    0 0 0 var(--ui-gold-dark),
    0 3px 5px rgba(0, 0, 0, 0.3);
}

.btn-play-hero:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

/* ---------- Bottom navigation (sticky to the lobby viewport bottom) ---------- */
.bottom-nav {
  position: sticky;
  bottom: 0;
  z-index: 20;
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
  height: var(--bottom-nav-h);
  padding-bottom: var(--safe-bottom);
  border-top: 3px solid var(--ui-gold);
  background: linear-gradient(180deg, var(--ui-crimson) 0%, var(--ui-crimson-dark) 100%);
  /* Beige line of the same 3px just inside the gold top outline. */
  box-shadow:
    inset 0 3px 0 var(--ui-cream-dark),
    inset 0 -5px 0 var(--ui-crimson-shadow),
    0 -1px 0 var(--ui-gold-depth),
    0 -6px 16px rgba(0, 0, 0, 0.35);
}

.bottom-nav-btn {
  position: relative;
  isolation: isolate;
  overflow: visible;
  flex: 1 1 0;
  min-width: 0;
  min-height: 48px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 1px 2px 0;
  border: none;
  background: var(--bp-layer-fill, transparent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), inset 0 -3px 0 rgba(0, 0, 0, 0.18);
  color: var(--bp-layer-color, #fff);
  opacity: var(--bp-layer-opacity, 1);
  cursor: pointer;
  touch-action: manipulation;
}

/* Dark separator starting below the gold outline and the beige line. */
.bottom-nav-divider {
  position: absolute;
  top: 6px;
  bottom: 0;
  right: 0;
  width: 2px;
  z-index: 1;
  background: rgba(10, 43, 95, 0.6);
  pointer-events: none;
}

.bottom-nav-btn:last-child .bottom-nav-divider,
.bottom-nav-btn[aria-current="page"] .bottom-nav-divider {
  display: none;
}

/* The label tucks into the art's transparent bottom so the 57px icon fits
   the 68px bar. */
.bottom-nav-btn img {
  position: relative;
  z-index: 2;
  width: var(--bp-layer-size, 57px);
  height: var(--bp-layer-size, 57px);
  margin: 0 0 -11px;
  pointer-events: none;
  opacity: var(--bp-layer-opacity, 1);
  filter: var(--bp-layer-filter, drop-shadow(0 2px 2px rgba(0, 0, 0, 0.35)));
  transform: var(--bp-layer-transform, none);
}

.bottom-nav-btn[aria-current="page"] {
  margin-top: -10px;
  border: 3px solid var(--ui-gold);
  border-bottom: none;
  border-radius: 16px 16px 0 0;
  background: linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 60%);
  box-shadow:
    inset 0 2px 0 var(--ui-gold-highlight),
    inset 0 5px 0 rgba(255, 255, 255, 0.3),
    inset 0 -3px 0 var(--ui-gold-depth),
    inset 0 -5px 0 var(--ui-crimson-shadow),
    0 -4px 0 rgba(0, 0, 0, 0.2);
}

/* Home rises above the bar only while selected; inactive tabs sit flush. */
#nav-home[aria-current="page"] {
  margin-top: -19px;
}

#nav-home:not([aria-current="page"]) {
  margin-top: 0;
  border: 0;
  border-radius: 0;
}

.bottom-nav-btn:focus-visible {
  outline: 3px solid var(--ui-gold);
  outline-offset: -4px;
}

/* Label paints above the overlapping icon art and is never clipped. A label wider than its tab is
   shrunk to fit by js/hybridLobby.js fitTabLabels (2.5.2). */
.bottom-nav-label {
  position: relative;
  z-index: 3;
  overflow: visible;
  white-space: nowrap;
  font-size: var(--bp-layer-font-size, 0.85rem);
  font-weight: 700;
  line-height: 1.1;
  color: var(--bp-layer-color, #fff);
  opacity: var(--bp-layer-opacity, 1);
  letter-spacing: var(--bp-layer-letter-spacing, normal);
  text-shadow: var(--bp-layer-text-shadow, var(--bp-text-outline));
  -webkit-text-stroke: var(--bp-layer-text-stroke, var(--bp-text-stroke));
  paint-order: stroke fill;
  transform: var(--bp-layer-transform, none);
}

/* One line tall: a label that wraps (narrow phones, below) grows upward over
   the icon art instead of pushing past the bottom of the screen. */
.bottom-nav-label {
  text-align: center;
  height: 1.1em;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
}

/* Narrowest phones (320px): labels ran into each other or under the raised
   Home tab (tr/th/vi/en/ja/id). A slightly smaller label, and a second line
   where the language allows a break (several words, Thai word boundaries);
   Japanese katakana stays on one line. Wider phones keep one line as before. */
@media (max-width: 340px) {
  .bottom-nav-label {
    max-width: calc(100% + 4px);
    font-size: 12px;
  }
  .bottom-nav-label:not(:lang(ja)) {
    white-space: normal;
    text-wrap: balance;
  }
}
.bottom-nav-btn .notification-badge--nav {
  z-index: 4;
}

@media (min-width: 600px) {
  .level-gallery {
    --gallery-cols: 5;
  }
}

/* Wide / landscape lobby: side menus flank the logo and the gallery, the
   gallery gets six columns, nav items put the label beside the icon. */
@media (orientation: landscape) and (min-width: 900px) {
  .lobby-main {
    grid-template-columns: minmax(0, 1fr) minmax(0, 820px) minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    grid-template-areas:
      "top top top"
      "left hero right"
      "left gallery right"
      ". cta .";
    padding-inline: 16px;
  }

  .lobby-content-menu {
    gap: 22px;
    padding-top: 10px;
  }

  .lobby-content-menu--left {
    justify-self: end;
    margin-right: 40px;
  }

  .lobby-content-menu--right {
    justify-self: start;
    margin-left: 40px;
  }

  .level-gallery {
    --gallery-cols: 6;
    /* Same 19px bottom as portrait: the progress label ends at the frame's innermost line. */
    padding: 32px 20px 19px;
  }

  .bottom-nav {
    justify-content: center;
  }

  .bottom-nav-btn {
    flex: 0 1 220px;
    flex-direction: row;
    gap: 10px;
  }

  .bottom-nav-btn img {
    width: 66px;
    height: 66px;
    margin: -1px 0;
  }

  .bottom-nav-label {
    font-size: 14px;
  }

  .btn-play-hero {
    --cta-h: 84px;
  }
}

/* Short screens (landscape phones): tighter logo so the gallery keeps room. */
@media (max-height: 560px) {
  .brainpang-logo {
    height: 84px;
  }
}

.btn {
  font-size: 1.2rem;
  font-weight: 700;
  border: none;
  border-radius: 16px;
  padding: 18px 28px;
  cursor: pointer;
  min-height: 60px;
  min-width: 60px;
  touch-action: manipulation;
}

.btn-huge {
  width: 100%;
  font-size: 1.6rem;
  padding: 22px 28px;
  border-radius: 20px;
}

.btn-large {
  font-size: 1.3rem;
  padding: 16px 24px;
}

.btn-small {
  font-size: 1rem;
  padding: 10px 14px;
  border-radius: 10px;
  min-height: 44px;
}

/* Glossy green action button (NEXT / OK / refill / manual). */
.btn-primary {
  background: linear-gradient(180deg, #b4ff00 0%, #63dd00 42%, #009400 100%);
  color: #fff;
  border: 3px solid var(--ui-gold);
  box-shadow: 0 5px 0 #006b00, inset 0 2px 0 rgba(255, 255, 255, 0.55);
}
.btn-primary:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 #006b00, inset 0 2px 0 rgba(255, 255, 255, 0.45);
}

/* PC-only affordance — touch devices have no hover state to speak of, so
   this never applies on phones/tablets (pointer:fine excludes touchscreens
   that merely support a mouse, e.g. touch laptops with a trackpad present). */
@media (hover: hover) and (pointer: fine) {
  .btn-play-hero:hover,
  .btn-primary:hover,
  .round-btn:hover,
  .lobby-content-button:hover,
  .bottom-nav-btn:hover,
  #btn-hint:not(:disabled):hover,
  #btn-zoom:hover {
    filter: brightness(1.08);
  }
}

.btn-secondary {
  background: #3a3260;
  color: var(--color-text);
}

/* Brown pill (sound toggle). */
.btn-ghost {
  background: linear-gradient(180deg, #b8692f 0%, #7a4320 100%);
  border: 2px solid #3d1c08;
  color: #fff;
  font-size: 1.1rem;
  padding: 12px 20px;
  border-radius: 999px;
}

.btn:focus-visible,
.lang-btn:focus-visible,
.round-btn:focus-visible,
#btn-hint:focus-visible,
#btn-zoom:focus-visible {
  outline: 3px solid var(--ui-crimson);
  outline-offset: 3px;
}

/* ---------- Language switcher (manual EN/JA/KO override, Options) ---------- */
.lang-switcher {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: center;
  padding-bottom: 6px;
}

.lang-btn {
  min-height: 44px;
  font-size: 0.95rem;
  font-weight: 700;
  padding: 8px 14px;
  border-radius: 999px;
  border: 2px solid #3d1c08;
  background: linear-gradient(180deg, #b8692f 0%, #7a4320 100%);
  color: #fff;
  cursor: pointer;
  touch-action: manipulation;
}

.lang-btn.active {
  background: linear-gradient(180deg, #fff6a1 0%, var(--color-accent) 55%, var(--color-accent-dark) 100%);
  border-color: var(--ui-gold-dark);
  color: #2a1a00;
}

/* Lobby-only footer variant — real in-flow footer below the first
   viewport, with a legal links row (Privacy/Terms/Contact) under the
   copyright line. */
.brand-footer.lobby-footer {
  position: static;
  margin-bottom: auto;
  flex-direction: column;
  gap: 8px;
  padding: 18px 16px 18px;
  background: rgba(20, 10, 5, 0.72);
}

.lobby-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
}

.lobby-footer-links a {
  color: var(--color-text-dim);
  text-decoration: underline;
}

/* reCAPTCHA v3 disclosure. Google allows hiding the floating badge (it
   covered the phone menu row) only with visibility, and only when this
   notice is shown in the user flow instead. */
.grecaptcha-badge {
  visibility: hidden !important;
}

.recaptcha-notice {
  margin: 0;
  max-width: 520px;
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--color-text-dim);
}

.recaptcha-notice a {
  color: var(--color-text-dim);
  text-decoration: underline;
}

.lobby-footer-links .sep {
  color: var(--color-text-dim);
}

/* ---------- Game screen ----------
   Glossy resource bar on top, then the cream/crimson level board holding
   the two puzzle panes, with the blue hint button overlapping its bottom
   edge. Portrait: the board fills the remaining height. Landscape: the
   board width follows the pane height so each pane keeps the image ratio. */
#screen-game {
  isolation: isolate;
  align-items: center;
  background: #2a1408;
}

/* Blurred detective-office backdrop behind the board. */
.screen-game-backdrop {
  position: absolute;
  inset: -24px;
  z-index: -1;
  background: url('../assets/lobby/lobby-bg-detective-v1.webp') center / cover no-repeat;
  filter: blur(6px) brightness(0.72);
}

.hud {
  flex: 0 0 auto;
  width: 100%;
  padding: calc(8px + var(--safe-top)) 10px 4px;
  z-index: 5;
}

.hud-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.hud-row-top {
  width: min(100%, 720px);
  margin: 0 auto;
}

.hud-row-resources {
  flex: 1 1 auto;
}

.game-board {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  width: calc(100% - 16px);
  max-width: 760px;
  margin: 34px auto 44px;
  padding: calc(var(--ui-badge-h) * 0.62) 12px 46px;
}

/* Level title capsule across the board's top edge. */
.game-board-title {
  position: absolute;
  top: 0;
  left: 50%;
  z-index: 6;
  transform: translate(-50%, -50%);
  gap: 8px;
}

/* Portrait phones (touch screens under 600px wide; a desktop window and an
   upright tablet keep the layout above): each pane's picture area is as wide as the screen (the
   pictures reach both edges, owner 2026-10-30) and 1.16 (width : height) at most as tall, or as tall
   as the height left after the slim top bar, title, buttons and copyright when that is less: a long
   phone shows a wider area than 1.16 (owner decision 2026-10-30: filling the screen comes before the
   same 1x view on every phone). Any height left over is split above and below the board. */
@media (orientation: portrait) and (pointer: coarse) and (max-width: 599px) {
  #screen-game {
    --game-action-h: 54px;
    --pg-side: 8px;          /* screen edge to a title wider than the board */
    --pg-board-px: 0px;      /* board edge to the panes, left and right: none */
    --pg-title-h: 42px;      /* the title capsule across the board's top edge */
    --pg-board-mt: calc(var(--pg-title-h) / 2 + 10px);       /* top bar to the board: half the title + 10px */
    --pg-board-pt: calc(var(--pg-title-h) / 2 + 4px);        /* lower half of the title above the panes */
    --pg-board-pb: calc(var(--game-action-h) / 2 + 6px);     /* upper half of the buttons and the hint count */
    --pg-board-mb: calc(var(--game-action-h) / 2 + 10px);    /* lower half of the buttons + 10px to the copyright */
    --pg-pane-gap: 6px;
    --pg-pane-border: 6px;   /* .pane border, top and bottom */
    --pg-pane-border-x: 0px; /* .pane border, left and right: none, the pictures touch the screen edges */
    --pg-footer-h: 21px;     /* copyright strip, without the safe area */
    /* Top bar row: the round buttons or the 46px resource bar (34px pills on phones), whichever is taller. */
    --pg-chrome-h: calc(4px + max(var(--round-btn-size), 46px) + 2px + var(--safe-top)
      + var(--pg-board-mt) + var(--pg-board-pt) + var(--pg-pane-gap) + var(--pg-board-pb) + var(--pg-board-mb)
      + var(--pg-footer-h) + var(--safe-bottom));
    /* One picture area: the full width (the 760px board at most), and 1.16 or the height left, whichever is less. */
    --pg-view-w: min(
      calc(100vw - 2 * var(--pg-board-px) - var(--pg-pane-border-x)),
      calc(760px - 2 * var(--pg-board-px) - var(--pg-pane-border-x)));
    --pg-view-h: min(
      calc(var(--pg-view-w) / 1.16),
      calc((100dvh - var(--pg-chrome-h)) / 2 - var(--pg-pane-border)));
  }

  /* A received puzzle's title ("<name>님이 보낸 문제" and the find icons) is longer than a phone is
     wide: smaller, it may take two lines (the name, then the icons), so the board leaves room
     for a 48px title. 390x844 still has the height spare; 360 and 320 lose a few pixels of pane. */
  body.in-challenge #screen-game {
    --pg-title-h: 48px;
  }

  #screen-game .hud {
    padding-top: calc(4px + var(--safe-top));
    padding-bottom: 2px;
    margin-bottom: auto;
  }

  #screen-game .game-board {
    flex: 0 0 auto;
    width: calc(var(--pg-view-w) + var(--pg-pane-border-x) + 2 * var(--pg-board-px));
    max-width: none;
    margin: var(--pg-board-mt) auto var(--pg-board-mb);
    padding: var(--pg-board-pt) var(--pg-board-px) var(--pg-board-pb);
  }

  #screen-game .game-board-title {
    height: 42px;
  }

  body.in-challenge #screen-game .game-board-title {
    width: max-content;
    max-width: calc(100vw - 2 * var(--pg-side));
    height: auto;
    min-height: 42px;
    flex-wrap: wrap;
    row-gap: 2px;
    padding: 3px 16px;
    font-size: clamp(0.85rem, 3.8vw, 0.95rem);
    white-space: normal;
    text-align: center;
  }

  body.in-challenge #screen-game .hud-level {
    max-width: 100%;
    overflow-wrap: anywhere;
  }

  body.in-challenge #screen-game .find-icon {
    width: 20px;
    height: 20px;
    font-size: 0.8rem;
  }

  /* "First challenge n/5" sits on the title capsule's top rim, right end: beside the title
     there is no room for a long word ("Herausforderung") on a phone. */
  #screen-game .first-challenge-badge {
    top: 0;
    right: 14px;
    left: auto;
    transform: translateY(-50%);
    width: max-content;
    max-width: calc(100vw - 2 * var(--pg-side));
    padding: 1px 7px;
    white-space: nowrap;
  }

  #screen-game .first-challenge-label {
    font-size: 11px;
  }

  #screen-game .game-board .pane {
    border-left-width: 0;
    border-right-width: 0;
    border-radius: 0;
  }

  #screen-game .game-board .panes {
    flex: 0 0 auto;
    height: calc((var(--pg-view-h) + var(--pg-pane-border)) * 2 + var(--pg-pane-gap));
    gap: var(--pg-pane-gap);
  }

  #screen-game .brand-footer-compact {
    margin-top: auto;
  }

  /* Smaller count badge on the slimmer button, a smaller icon, and the label kept on one line. */
  #screen-game .btn-hint {
    --hint-count-size: 28px;
    --hint-icon-w: calc(var(--cta-h) * 0.5);
    --hint-gap: 4px;
    padding-left: 4px;
  }

  #screen-game .btn-hint-icon {
    width: var(--hint-icon-w);
    height: var(--hint-icon-w);
  }

  #screen-game .btn-hint-count {
    top: -6px;
    padding: 0 5px;
    border-width: 2px;
    font-size: 0.95rem;
  }
}

@media (orientation: landscape) {
  .game-board {
    --pane-h: calc((100dvh - var(--game-chrome-h) - var(--safe-top) - var(--safe-bottom)) / 2);
    flex: 0 0 auto;
    width: min(calc(var(--pane-h) * 1.7293 + 30px), calc(100vw - 32px));
    max-width: none;
  }

  .game-board .panes {
    height: calc(var(--pane-h) * 2 + 8px);
  }
}

/* Landscape tablets: the two panes sit side by side at the full height left
   under the top bar and the button row. Touch screens only (pointer: coarse),
   so a desktop browser window keeps the stacked layout and the whole pictures. */
@media (orientation: landscape) and (pointer: coarse) and (min-height: 501px) and (min-aspect-ratio: 4/3) {
  .game-board {
    --pane-h: calc(100dvh - var(--game-chrome-h) - var(--safe-top) - var(--safe-bottom));
    width: min(calc(var(--pane-h) * 3.4586 + 38px), calc(100vw - 24px));
  }

  .game-board .panes {
    flex-direction: row;
    height: var(--pane-h);
  }
}

/* Landscape phones: the two panes sit side by side at almost the full screen
   height. The top bar becomes the left strip (lobby, coins, hints, hearts,
   options, copyright); the level title and the hint/zoom buttons become the
   right strip. Arabic mirrors the strips. zoomPan reads each pane's own rect,
   so taps, zoom and hints need nothing else. Not limited to touch screens:
   before 2.5.1 every window this short already got a side-by-side phone
   layout, so a short desktop window keeps that kind of layout (stacked panes
   at under 500px would be unusable). */
@media (orientation: landscape) and (max-height: 500px) {
  :root {
    /* Board padding above and below the panes. */
    --game-chrome-h: 24px;
    --side-w: 72px;
    --safe-left: env(safe-area-inset-left, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --round-btn-size: 42px;
    --ui-badge-h: 38px;
    --game-action-h: 58px;
  }

  .resource-pill {
    height: 32px;
  }

  #screen-game {
    display: grid;
    grid-template-columns: calc(var(--side-w) + var(--safe-left)) minmax(0, 1fr) calc(var(--side-w) + var(--safe-right));
    grid-template-rows: minmax(0, 1fr) auto;
    align-items: stretch;
  }

  /* Left strip: the top bar stacked top to bottom. */
  .game-topbar {
    grid-column: 1;
    grid-row: 1;
    min-height: 0;
    padding: calc(6px + var(--safe-top)) 2px 4px calc(2px + var(--safe-left));
  }

  .game-topbar .hud-row-top {
    flex-direction: column;
    width: 100%;
    gap: 6px;
  }

  .game-topbar .hud-row-resources {
    flex: 0 0 auto;
    flex-direction: column;
    width: 100%;
    gap: 6px;
    padding: 5px 2px;
    border-radius: 20px;
  }

  .game-topbar .resource-pill {
    flex: 0 0 auto;
    width: 100%;
    min-width: 0;
    gap: 1px;
    padding: 0 3px 0 0;
  }

  .game-topbar .resource-icon {
    width: 24px;
    height: 24px;
  }

  .game-topbar .resource-value {
    font-size: 0.75rem;
    letter-spacing: -0.03em;
  }

  #screen-game .brand-footer-compact {
    grid-column: 1;
    grid-row: 2;
    padding: 4px 2px calc(4px + var(--safe-bottom)) calc(2px + var(--safe-left));
    background: none;
    text-align: center;
  }

  /* 8px copyright text does not fit the 72px strip legibly: hidden here. */
  #screen-game .brand-footer-compact span {
    display: none;
  }

  /* 1:1 / contest play hides hints and hearts with visibility; here they would
     keep their slot and leave an empty capsule, so they leave the strip. */
  body.in-duel .game-topbar #hud-hearts,
  body.in-duel .game-topbar #hud-hints {
    display: none;
  }

  /* The board fills the middle column; the panes take its full height. */
  .game-board {
    grid-column: 2;
    grid-row: 1 / span 2;
    justify-self: center;
    --pane-h: calc(100dvh - var(--game-chrome-h) - var(--safe-top) - var(--safe-bottom));
    width: min(calc(var(--pane-h) * 3.4586 + 38px), calc(100% - 16px));
    margin: var(--safe-top) 0 var(--safe-bottom);
    padding: calc(var(--game-chrome-h) / 2) 12px;
  }

  .game-board .panes {
    flex-direction: row;
    height: var(--pane-h);
  }

  /* Right strip, top: the level title capsule stands upright. */
  #screen-game .game-board-title {
    position: fixed;
    top: calc(6px + var(--safe-top));
    right: calc(4px + var(--safe-right));
    left: auto;
    transform: none;
    width: calc(var(--side-w) - 8px);
    min-width: 0;
    height: auto;
    flex-direction: column;
    gap: 5px;
    padding: 8px 3px;
    border-radius: 18px;
    font-size: 0.78rem;
    line-height: 1.15;
    white-space: normal;
    text-align: center;
    overflow-wrap: break-word;
  }

  #screen-game .hud-level {
    max-width: 100%;
    white-space: normal;
  }

  #screen-game .hud-finds {
    flex-wrap: wrap;
    gap: 3px;
  }

  #screen-game .find-icon {
    width: 18px;
    height: 18px;
    font-size: 0.7rem;
  }

  #screen-game .hud-duel-timer {
    min-width: 0;
    width: 100%;
    padding: 2px 0;
    font-size: 15px;
  }

  #screen-game .first-challenge-badge {
    position: static;
    transform: none;
    width: 100%;
    max-width: none;
    padding: 3px 2px;
    justify-content: center;
    white-space: normal;
    text-align: center;
  }

  #screen-game .first-challenge-label {
    font-size: 0.66rem;
  }

  /* Right strip, bottom: hint and zoom stacked. */
  #screen-game .game-footer {
    position: fixed;
    top: auto;
    bottom: calc(10px + var(--safe-bottom));
    right: calc(5px + var(--safe-right));
    left: auto;
    width: calc(var(--side-w) - 10px);
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    transform: none;
  }

  #screen-game .game-action-btn {
    --cta-h: 64px;
    width: 100%;
    flex-direction: column;
    gap: 2px;
    padding: 4px 2px 6px;
    font-size: 0.7rem;
  }

  #screen-game .btn-hint-icon,
  #screen-game .btn-zoom-icon {
    width: 30px;
    height: 30px;
  }

  #screen-game .btn-hint-count {
    top: -8px;
    right: -6px;
    min-width: 24px;
    height: 24px;
    padding: 0 4px;
    border-width: 2px;
    font-size: 0.8rem;
  }

  /* The label sits under the icon here: the whole button width is its room. */
  #screen-game .btn-hint-label {
    max-width: 100%;
    font-size: clamp(11px, calc((100cqi - 2px) / (var(--hint-label-chars, 4) * 0.62)), 1em);
  }

  /* A long word ("Herausforderung") breaks rather than leave the strip. */
  #screen-game .first-challenge-label {
    overflow-wrap: anywhere;
  }

  /* Grid columns already follow the reading direction; the insets swap with them. */
  [dir="rtl"] #screen-game {
    grid-template-columns: calc(var(--side-w) + var(--safe-right)) minmax(0, 1fr) calc(var(--side-w) + var(--safe-left));
  }

  [dir="rtl"] .game-topbar {
    padding-right: calc(2px + var(--safe-right));
    padding-left: 2px;
  }

  [dir="rtl"] #screen-game .brand-footer-compact {
    padding-right: calc(2px + var(--safe-right));
    padding-left: 2px;
  }

  [dir="rtl"] #screen-game .game-board-title {
    right: auto;
    left: calc(4px + var(--safe-left));
  }

  [dir="rtl"] #screen-game .game-footer {
    right: auto;
    left: calc(5px + var(--safe-left));
  }
}

.level-goal-callout {
  position: fixed;
  left: 50%;
  top: 52%;
  transform: translate(-50%, -50%);
  z-index: 30;
  min-width: min(82vw, 320px);
  padding: 14px 22px;
  border: 2px solid rgba(255, 255, 255, 0.72);
  border-radius: 18px;
  background: linear-gradient(180deg, rgba(53, 43, 91, 0.96), rgba(27, 23, 48, 0.96));
  color: #fff;
  font-size: clamp(1.35rem, 5vw, 2rem);
  font-weight: 900;
  line-height: 1.15;
  text-align: center;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.48), inset 0 2px 0 rgba(255, 255, 255, 0.22);
  pointer-events: none;
  opacity: 1;
}

.level-goal-callout.level-goal-show {
  animation: level-goal-callout 1.5s ease-out both;
}

/* Challenge Level entry emphasis (GAME_SPEC.md §4.6 2026-09-18(2)) — reuses
   the same success-green "Challenge" identity as .popup-card--challenge so
   "3개 찾기" reads as visually distinct the moment the level starts. Shown
   only once per entry, riding the existing level-goal-show fade (no extra
   animation, no added display time). */
.level-goal-callout--challenge {
  border-color: var(--color-success);
  box-shadow:
    0 0 0 2px rgba(77, 255, 136, 0.55),
    0 0 22px rgba(77, 255, 136, 0.35),
    0 12px 28px rgba(0, 0, 0, 0.48),
    inset 0 2px 0 rgba(255, 255, 255, 0.22);
}

@keyframes level-goal-callout {
  0% { opacity: 0; transform: translate(-50%, -42%) scale(0.82); }
  18%, 68% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -58%) scale(1.04); }
}

.brain-collect-hud {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: var(--color-text);
}

.brain-collect-level {
  font-size: 0.78rem;
  font-weight: 800;
  line-height: 1;
  color: var(--color-accent);
}

.brain-letter-slots {
  display: flex;
  gap: 5px;
}

.brain-letter-slot {
  --slot-light: #ff7584;
  --slot-color: #ff4058;
  --slot-dark: #9f1730;
  position: relative;
  width: 25px;
  height: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 2px solid rgba(255, 255, 255, 0.68);
  border-radius: 50%;
  background:
    radial-gradient(circle at 32% 25%, rgba(255, 255, 255, 0.92) 0 8%, transparent 10%),
    radial-gradient(circle at 35% 30%, var(--slot-light) 0, var(--slot-color) 48%, var(--slot-dark) 100%);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 900;
  line-height: 1;
  box-shadow:
    inset -3px -4px 5px rgba(0, 0, 0, 0.28),
    inset 3px 3px 4px rgba(255, 255, 255, 0.28),
    0 3px 5px rgba(0, 0, 0, 0.38);
  opacity: 0.38;
  filter: saturate(0.72);
  transition: opacity 0.2s ease, filter 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.brain-letter-slot:nth-child(1) {
  --slot-light: #ff7584;
  --slot-color: #ff4058;
  --slot-dark: #9f1730;
}

.brain-letter-slot:nth-child(2) {
  --slot-light: #ffb15c;
  --slot-color: #ff8126;
  --slot-dark: #a83908;
}

.brain-letter-slot:nth-child(3) {
  --slot-light: #fff17a;
  --slot-color: #ffd43b;
  --slot-dark: #a57600;
}

.brain-letter-slot:nth-child(4) {
  --slot-light: #7bea9f;
  --slot-color: #35c86c;
  --slot-dark: #0d7138;
}

.brain-letter-slot:nth-child(5) {
  --slot-light: #83c8ff;
  --slot-color: #3d9bff;
  --slot-dark: #134c9c;
}

.brain-letter-slot-shine {
  position: absolute;
  top: 4px;
  left: 5px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 0 3px rgba(255, 255, 255, 0.9);
  pointer-events: none;
}

.brain-letter-slot.filled {
  opacity: 1;
  filter: saturate(1.12) brightness(1.05);
  box-shadow:
    inset -3px -4px 5px rgba(0, 0, 0, 0.3),
    inset 3px 3px 4px rgba(255, 255, 255, 0.34),
    0 0 10px color-mix(in srgb, var(--slot-color) 70%, transparent),
    0 3px 5px rgba(0, 0, 0, 0.38);
}

.brain-letter-slot.just-collected {
  animation: brain-letter-collect 0.5s ease-out;
}

/* 5th-letter arrival: light sweeps through the 5 slots in order before the
   existing brain-complete flash (GAME_SPEC.md §4.5 3단계 2026-09-18(2)).
   Delays are staggered per nth-child; last one (320ms + 300ms) sets
   BRAIN_SWEEP_MS in game.js — keep the two in sync if either changes. */
.brain-letter-slot.sweep-light {
  animation: brain-slot-sweep 0.3s ease-out both;
}
.brain-letter-slot.sweep-light:nth-child(1) { animation-delay: 0ms; }
.brain-letter-slot.sweep-light:nth-child(2) { animation-delay: 80ms; }
.brain-letter-slot.sweep-light:nth-child(3) { animation-delay: 160ms; }
.brain-letter-slot.sweep-light:nth-child(4) { animation-delay: 240ms; }
.brain-letter-slot.sweep-light:nth-child(5) { animation-delay: 320ms; }

@keyframes brain-slot-sweep {
  0% { filter: brightness(1.05) saturate(1.12); transform: scale(1); }
  50% {
    filter: brightness(1.65) saturate(1.4);
    transform: scale(1.14);
    box-shadow:
      0 0 18px 4px color-mix(in srgb, var(--slot-color) 85%, white),
      inset 0 0 6px rgba(255, 255, 255, 0.6);
  }
  100% { filter: brightness(1.05) saturate(1.12); transform: scale(1); }
}

.brain-collect-hud.brain-complete {
  animation: brain-word-complete 0.65s ease-out;
}

.brain-collect-hud.brain-level-up {
  animation: brain-level-hud-up 0.9s ease-out;
}

.brain-collect-hud.brain-level-up .brain-collect-level {
  padding: 4px 12px;
  border-radius: 999px;
  background: linear-gradient(180deg, #fff2a8, var(--color-accent));
  color: #302044;
  box-shadow: 0 0 16px rgba(255, 183, 3, 0.75), inset 0 2px 0 rgba(255, 255, 255, 0.7);
}

@keyframes brain-level-hud-up {
  0% { transform: scale(0.78); filter: brightness(1); }
  45% { transform: scale(1.16); filter: brightness(1.45); }
  100% { transform: scale(1); filter: brightness(1); }
}

@keyframes brain-letter-collect {
  0% { transform: scale(0.45) rotate(-10deg); opacity: 0.35; }
  55% { transform: scale(1.35) rotate(4deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

@keyframes brain-word-complete {
  0% { transform: scale(1); filter: brightness(1); }
  45% { transform: scale(1.08); filter: brightness(1.45); }
  100% { transform: scale(1); filter: brightness(1); }
}

@media (max-width: 390px) {
  .hud-row-top { gap: 5px; }
  .hud-row-resources { gap: 5px; }
  .brain-letter-slots { gap: 4px; }
  .brain-letter-slot {
    width: 22px;
    height: 22px;
    font-size: 0.64rem;
  }
  .brain-letter-slot-shine {
    top: 3px;
    left: 4px;
  }
}

/* Keep room for a five-digit coin balance on the 320px layout. Hint and heart
   counts stay one digit, so the coin pill gets a little more of the shared row. */
@media (max-width: 360px) {
  .hud-row-resources .resource-pill--coin { flex-grow: 2; }
}

/* Lobby and in-game top bars on phones: hearts and hints are short counts, so their
   pills keep a steady width of their own (never squeezed onto the icon, and the same
   in every language) and grow only for a longer count; the coin pill gets the rest
   (a five-digit balance fits at 320px instead of ending in an ellipsis). Without
   this the in-game hint and heart counts shrank to 7px at 320px. */
@media (max-width: 420px) {
  .lobby-topbar .resource-pill:not(.resource-pill--coin),
  .hud-row-resources .resource-pill:not(.resource-pill--coin) { flex: 0 0 auto; min-width: 62px; }
}

/* 320px with a five-digit balance and a two-digit hint count: a little less
   gap and a slightly smaller portrait keep every number whole. In game the
   top row and the pills close up instead (there is no portrait). */
@media (max-width: 340px) {
  .lobby-topbar .resource-bar,
  .hud-row-top,
  .hud-row-resources { gap: 3px; }
  .lobby-topbar .resource-avatar { width: 38px; height: 38px; }
  .lobby-topbar .resource-pill:not(.resource-pill--coin),
  .hud-row-resources .resource-pill:not(.resource-pill--coin) { min-width: 46px; }
}

.btn-lobby-icon {
  margin-left: 0;
}

.hud-item {
  font-size: 1.1rem;
  font-weight: 700;
  white-space: nowrap;
}

.hud-center {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}

.hud-level {
  font-size: inherit;
  font-weight: 800;
  letter-spacing: 0.02em;
  line-height: 1;
  color: #fff;
}

/* ---------- Find icons (replaces the old "0 / 1" counter + hearts row) ---------- */
.hud-finds {
  display: flex;
  gap: 6px;
  justify-content: center;
  margin: 0;
  line-height: 1;
}

.find-icon {
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.95rem;
  font-weight: 900;
  line-height: 1;
  border: 2px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.4);
  transition: background 0.25s ease, border-color 0.25s ease, color 0.25s ease, transform 0.25s ease;
  margin: 0;
}

.find-icon.done {
  border-color: var(--color-success);
  background: var(--color-success);
  color: #fff;
  transform: scale(1.08);
}

/* ---------- Hearts gauge (moved next to coins) ---------- */
.hud-hearts-gauge {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--color-danger);
}

.icon-heart {
  position: relative;
  display: inline-block;
  font-size: 1em;
  line-height: 1;
}

/* ---------- Hints gauge (RewardGate — GAME_SPEC.md §5.2) ---------- */
.hud-hints-gauge {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--color-accent);
}

.icon-hint {
  position: relative;
  display: inline-block;
  font-size: 1em;
  line-height: 1;
}

@keyframes heart-hit {
  0% { transform: scale(1); filter: brightness(1); }
  30% { transform: scale(1.3); filter: brightness(1.7); }
  60% { transform: scale(0.9); }
  100% { transform: scale(1); filter: brightness(1); }
}

.hud-hearts-gauge.heart-hit {
  animation: heart-hit 0.4s ease;
}

@keyframes hint-use-pop {
  0% { transform: scale(1); }
  40% { transform: scale(0.82); }
  100% { transform: scale(1); }
}

.hud-hints-gauge.hint-use-pop {
  animation: hint-use-pop 0.28s ease-out;
}

/* Free refill completion pop (GAME_SPEC.md §5.3 2026-09-18(2)) — shared
   bounce for whichever gauge (hearts or hints) just got refilled, plus a
   small light-particle burst spawned by celebrateResourceRefill() into the
   gauge's icon span (.icon-heart/.icon-hint, position:relative above). */
@keyframes refill-bounce {
  0% { transform: scale(1); }
  35% { transform: scale(1.32); }
  60% { transform: scale(0.92); }
  100% { transform: scale(1); }
}

.hud-hearts-gauge.refill-pop,
.hud-hints-gauge.refill-pop {
  animation: refill-bounce 0.45s ease;
}

.refill-particle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: radial-gradient(circle, #ffffff 0%, #fff6b0 55%, rgba(255, 246, 176, 0) 100%);
  box-shadow: 0 0 4px rgba(255, 246, 176, 0.9);
  transform: translate(-50%, -50%) rotate(var(--angle)) translateX(0);
  opacity: 1;
  pointer-events: none;
  animation: refill-particle-burst 0.48s cubic-bezier(0.15, 0.85, 0.3, 1) forwards;
}

@keyframes refill-particle-burst {
  to {
    transform: translate(-50%, -50%) rotate(var(--angle)) translateX(var(--dist));
    opacity: 0;
  }
}

#hud-coins {
  color: var(--color-accent);
}

.panes {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  gap: 8px;
  -webkit-user-select: none;
  user-select: none;
}

/* White picture frame per puzzle pane; overflow + radius clip the stage. */
.pane {
  flex: 1 1 50%;
  position: relative;
  overflow: hidden;
  min-height: 0;
  border: 3px solid #ecd3a0;
  border-radius: 14px;
  background: #fff;
  box-shadow: inset 0 2px 6px rgba(122, 80, 30, 0.18);
}

.viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  touch-action: none;
}

.stage {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
}

.stage img {
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
}

.marker-layer, .debug-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.marker {
  position: absolute;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 900;
}

.marker-correct {
  border: 4px solid var(--color-success);
  background: rgba(77, 255, 136, 0.15);
  color: var(--color-success);
  animation: marker-pop 0.45s ease-out;
}

@keyframes marker-pop {
  0% { transform: translate(-50%, -50%) scale(0.3); opacity: 0; }
  35% { transform: translate(-50%, -50%) scale(2); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}

/* Decorative "팡-팡팡" burst on top of .marker-correct (GAME_SPEC.md §4.3) —
   the ~1s correct-burst image added by spawnCorrectBurst. Wrapped so
   --counter-scale (1 / current in-app zoom, set inline by spawnCorrectBurst)
   keeps the decoration from growing along with high pinch-zoom while the
   persistent green ring above keeps its own existing zoom-proportional size. */
.marker-burst {
  position: absolute;
  inset: 0;
  transform: scale(var(--counter-scale, 1));
  pointer-events: none;
}

/* The artwork was drawn in a box 4x the ring's diameter centred on it (1 unit
   = 1% of the ring); its falling confetti spill past that box. The image is
   cropped to what is actually drawn: x -163..134, y -141..247 units from the
   ring's centre. Two classes so it outranks `.stage img` (100% x 100%). */
.marker-burst .marker-burst-img {
  position: absolute;
  left: -113%;
  top: -91%;
  width: 297%;
  height: 388%;
  max-width: none;
  pointer-events: none;
  user-select: none;
}

/* Challenge Level 1st/2nd/last tiered pop (GAME_SPEC.md §4.3 2026-09-18(2)).
   Size scaling happens via --counter-scale (spawnCorrectBurst multiplies it
   by the tier's scaleMult) — these two classes only add the "색의 강조"
   (color emphasis) layer on top. */
.marker-burst--tier1 {
  filter: brightness(0.9) saturate(0.82);
}

.marker-burst--final {
  filter: brightness(1.22) saturate(1.32);
}

.marker-wrong {
  border: 4px solid var(--color-danger);
  background: rgba(255, 77, 77, 0.2);
  color: var(--color-danger);
  animation: wrong-fade 0.6s ease-out forwards;
}

@keyframes wrong-fade {
  0% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.3); }
}

.debug-circle {
  position: absolute;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 2px dashed rgba(255, 255, 0, 0.9);
  background: rgba(255, 255, 0, 0.15);
}

/* Instant touch acknowledgement, spawned by zoomPan.js on every pointerdown —
   shows before it's known whether the gesture is a tap/pan/pinch and before
   any correct/wrong judgment (up to 300ms later via the double-tap window,
   or fully suppressed by the wrong-tap cooldown) has a chance to render. */
.tap-ripple {
  position: absolute;
  width: 34px;
  height: 34px;
  margin-left: -17px;
  margin-top: -17px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.18);
  pointer-events: none;
  z-index: 5;
  animation: tap-ripple-pop 0.28s ease-out forwards;
}

@keyframes tap-ripple-pop {
  0% { transform: scale(0.4); opacity: 0.9; }
  100% { transform: scale(1.6); opacity: 0; }
}

.hint-glow {
  position: absolute;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  border: 3px solid var(--color-accent);
  box-shadow: 0 0 20px 6px var(--color-accent);
  pointer-events: none;
}

.dim-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.75);
  pointer-events: none;
  z-index: 3;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.dim-overlay.active { opacity: 1; }

/* One-shot ripple at the hint spotlight's rim, sized/positioned inline by
   spawnHintRipple() to match the dim-overlay's radial-gradient cutout
   (GAME_SPEC.md §4.3 힌트 이펙트 진입 연출 2026-09-18(2)). Manual hint only —
   the idle nudge (§4.4 below) never spawns this. */
.hint-ripple {
  position: absolute;
  transform: translate(-50%, -50%) scale(0.85);
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 14px 2px var(--color-accent);
  opacity: 0.9;
  pointer-events: none;
  animation: hint-ripple-expand 0.6s ease-out forwards;
}

@keyframes hint-ripple-expand {
  0% { transform: translate(-50%, -50%) scale(0.85); opacity: 0.9; }
  100% { transform: translate(-50%, -50%) scale(1.18); opacity: 0; }
}

/* ---------- Idle hint nudge (GAME_SPEC.md §4.4) ---------- */
.dim-overlay.idle-blink {
  animation: idle-answer-blink 1.1s ease-in-out infinite;
}
@keyframes idle-answer-blink {
  0%, 100% { opacity: 0.15; }
  50% { opacity: 1; }
}

#btn-hint.idle-blink-btn {
  animation: idle-hint-btn-blink 1s ease-in-out infinite;
}
@keyframes idle-hint-btn-blink {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(255,183,3,0);
  }
  50% {
    box-shadow: 0 0 0 6px rgba(255,183,3,0.55), 0 0 22px 6px rgba(255,183,3,0.45);
  }
}

/* Hint (left) and zoom (right) buttons, overlapping the board's bottom edge and
   centred as a pair on the screen's vertical centre line. */
.game-footer {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  display: flex;
  justify-content: center;
  gap: 16px;
  transform: translateY(50%);
  pointer-events: none;
}

/* In-game action buttons: one shared size and the lobby play button's CSS panel
   (gold rim, cream inner line, gold base, bevel). Each colour is its own rule so
   the layer tool edits the hint and zoom buttons separately. */
.game-action-btn {
  --cta-h: var(--game-action-h, 72px);
  position: relative;
  flex: 0 0 auto;
  white-space: nowrap;
  pointer-events: auto;
  width: min(180px, 39vw);
  height: var(--cta-h);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 12px 2px;
  border: 3px solid var(--ui-gold);
  border-radius: 15px;
  box-shadow:
    inset 0 3px 0 #ffffff88,
    inset 0 -3px 0 #00000033,
    inset 0 0 0 2px var(--ui-cream-dark),
    0 3px 0 var(--ui-gold-dark),
    0 6px 10px rgba(0, 0, 0, 0.3);
  color: #ffffff;
  font-size: clamp(1rem, 2.6vh, 1.35rem);
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}
.game-action-btn:active {
  transform: translateY(3px);
  box-shadow:
    inset 0 3px 0 #ffffff88,
    inset 0 -3px 0 #00000033,
    inset 0 0 0 2px var(--ui-cream-dark),
    0 0 0 var(--ui-gold-dark),
    0 3px 5px rgba(0, 0, 0, 0.3);
}

/* Blue hint button with the live hint count. */
#btn-hint {
  background-color: #073f9b;
  background-image: linear-gradient(180deg, #5fb0ff 0%, #2f86f0 32%, #1264d6 68%, #073f9b 100%);
}
#btn-hint:disabled {
  opacity: 0.5;
}

/* Orange zoom button: 1x <-> 2x around the image centre, like a double-tap. */
#btn-zoom {
  background-color: #b84d00;
  background-image: linear-gradient(180deg, #ffd36b 0%, #ffab2e 32%, #f07a00 68%, #b84d00 100%);
}

.btn-hint-icon,
.btn-zoom-icon {
  width: calc(var(--cta-h) * 0.62);
  height: calc(var(--cta-h) * 0.62);
  flex: 0 0 auto;
  pointer-events: none;
}

/* Hint button: the count badge sits on the top-right corner and the icon and label keep left
   of it. The label's size follows its length (--hint-label-chars, set by js/game.js) and the
   room the button leaves (100cqi: the button is the size container), never under 11px; a label
   still too long ends in an ellipsis. */
.btn-hint {
  --hint-count-size: 36px;
  --hint-icon-w: calc(var(--cta-h) * 0.62);
  --hint-gap: 6px;
  container-type: inline-size;
  gap: var(--hint-gap);
  padding-left: 6px;
  padding-right: calc(var(--hint-count-size) - 4px);
}

.btn-hint .btn-hint-icon {
  width: var(--hint-icon-w);
  height: var(--hint-icon-w);
}

.btn-hint-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  /* room inside the clip for the outline and its drop shadow */
  padding: 3px 1px 4px;
  margin: -3px -1px -4px;
  font-size: clamp(11px, calc((100cqi - var(--hint-icon-w) - var(--hint-gap) - 2px) / (var(--hint-label-chars, 4) * 0.62)), 1em);
}

.btn-hint-count {
  position: absolute;
  top: -10px;
  right: -6px;
  min-width: var(--hint-count-size);
  height: var(--hint-count-size);
  padding: 0 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 3px solid var(--ui-gold);
  border-radius: 999px;
  background: linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 55%, var(--ui-crimson-dark) 100%);
  font-size: 1.1rem;
  font-weight: 800;
  color: #fff;
}

#btn-hint.is-empty .btn-hint-count {
  background: linear-gradient(180deg, #9a9a9a 0%, #5c5c5c 100%);
}

/* ---------- Zoom discoverability hint — overlays the bottom edge of the
   lower difference image. Its own surface is translucent, while the text
   remains fully opaque; it never captures a tap meant for the image. ---------- */
.zoom-hint {
  position: absolute;
  right: 12px;
  bottom: 0;
  left: 12px;
  z-index: 4;
  width: auto;
  max-width: none;
  margin: 0;
  padding: 7px 12px;
  text-align: center;
  font-size: 0.78rem;
  font-weight: 600;
  color: #eef3f9;
  background: rgba(39, 50, 68, 0.5);
  border: 1px solid rgba(177, 192, 212, 0.32);
  border-radius: 999px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.28);
  pointer-events: none;
  opacity: 0;
  transform: translateY(6px);
  animation: zoom-hint-cycle 5s ease-in-out infinite;
}

@keyframes zoom-hint-cycle {
  0%, 8% { opacity: 0; transform: translateY(6px); }
  46%, 62% { opacity: 1; transform: translateY(0); }
  86%, 100% { opacity: 0; transform: translateY(-3px); }
}

/* ---------- First-level onboarding hint (item 2) — fixed/out-of-flow so
   showing or auto-hiding it never resizes .panes (would desync zoomPan's
   cached stage size from the actual viewport). Overlays the top edge only;
   pointer-events:none so it never blocks the lobby button beneath it. ---------- */
.first-level-hint {
  position: fixed;
  left: 12px;
  right: 12px;
  top: calc(118px + var(--safe-top));
  z-index: 50;
  max-width: 420px;
  margin: 0 auto;
  padding: 10px 16px;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.82);
  color: var(--color-text);
  font-size: 0.95rem;
  font-weight: 600;
  text-align: center;
  pointer-events: none;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}

/* ---------- whyGames brand footer (shared component, GAME_SPEC.md §3.5) ----------
   Same markup + class reused on the lobby and in-game screens so future
   in-game work automatically keeps the same credit strip. */
/* Standard, understated website copyright treatment — small, light-weight,
   identical look on lobby and in-game (item 2). Only icon/text size differ
   between the two variants, to fit the in-game screen's tight vertical
   budget; weight/color/opacity stay the same on both. */
/* Footers (copyright, legal links, reCAPTCHA notice) keep the platform
   default font rather than the game's Maplestory face. */
.brand-footer {
  font-family: var(--bp-system-font);
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 6px 12px calc(6px + var(--safe-bottom));
  background: rgba(0, 0, 0, 0.2);
}

.brand-footer img {
  height: 15px;
  width: auto;
  display: block;
  opacity: 0.8;
}

.brand-footer span {
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.6);
}

/* In-game variant: a normal (non-absolute) flex child at the very bottom of
   #screen-game, right after the ad banner — kept as small as possible so it
   barely touches the already-tight canvas space. Same weight/color as the
   lobby version, just smaller. */
.brand-footer-compact {
  position: static;
  flex: 0 0 auto;
  background: var(--color-bg-alt);
  padding: 3px 12px calc(3px + var(--safe-bottom));
}

.brand-footer-compact img {
  height: 12px;
  gap: 0;
}

.brand-footer-compact span {
  font-size: 0.58rem;
}

.btn:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ---------- Popups ---------- */
.popup-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 8, 4, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: calc(20px + var(--safe-top)) calc(20px + env(safe-area-inset-right, 0px))
    calc(20px + var(--safe-bottom)) calc(20px + env(safe-area-inset-left, 0px));
}

/* Cream card with the crimson/gold frame (panel-cream-wide.svg via the
   shared .ui-panel border-image rule). Long copy inside is dark brown; short
   outlined text sets its own light color. */
.popup-card {
  position: relative;
  z-index: 2;
  color: var(--ui-brown);
  word-break: keep-all;
  overflow-wrap: anywhere;
  padding: 36px 26px 28px;
  text-align: center;
  width: 100%;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  animation: popup-card-in 180ms cubic-bezier(0.34, 1.2, 0.64, 1);
}

/* Chinese/Japanese have no spaces: keep-all made a whole sentence one
   unbreakable word, so overflow-wrap split it anywhere — "？" alone on the
   next line (ja 320 purchase title). Chinese breaks between characters with
   the strict punctuation rules; Japanese between phrases (auto-phrase), so a
   word is not split. A WebView without auto-phrase keeps "normal". */
.popup-card:lang(ja),
.popup-card:lang(zh) {
  word-break: normal;
  line-break: strict;
}

.popup-card:lang(ja) {
  word-break: auto-phrase;
}

/* Clear results keep the shared gold frame but use the desaturated blue
   interior variant so the reward content reads as a distinct game moment. */
.popup-card--clear {
  border-image-source: url('../assets/ui-refresh/panel-clear-blue.svg');
  max-height: calc(100dvh - 32px - var(--popup-safe-y));
  overflow-y: auto;
  overscroll-behavior: contain;
}

.clear-shop-opportunity {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 6px 10px;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  border: 2px solid var(--ui-gold);
  border-radius: 16px;
  background: linear-gradient(180deg, #fff8e6, #f4e2b8);
  color: var(--ui-brown);
}

.clear-shop-opportunity-art {
  grid-row: span 2;
  width: 52px;
  height: 52px;
  object-fit: contain;
  border-radius: 10px;
}

.clear-shop-opportunity-heading { grid-column: 1 / -1; margin: 0; font-size: 15px; font-weight: 900; }
.clear-shop-opportunity-copy { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.clear-shop-opportunity-name { overflow-wrap: anywhere; font-size: 16px; }
.clear-shop-opportunity-status { color: #347b2f; font-size: 13px; font-weight: 800; }
.clear-shop-opportunity-price { font-size: 13px; font-weight: 700; }
.clear-shop-opportunity-button { grid-column: 1 / -1; min-height: 42px; margin-top: 2px; }

@media (prefers-reduced-motion: reduce) {
  .popup-card--clear { scroll-behavior: auto; }
}

@keyframes popup-card-in {
  from { opacity: 0; transform: scale(0.92); }
  to { opacity: 1; transform: scale(1); }
}

.popup-card h2 {
  margin: 0;
  font-size: 1.8rem;
  color: var(--color-accent);
}

/* Challenge Level clear callout — reuses the success-green token (already
   the "correct answer" color) so it reads as a distinct identity from the
   accent-gold used everywhere else in this card (coins, Brain Level, LEVEL
   UP badge), without introducing a new color. */
.popup-card--challenge {
  filter: drop-shadow(0 0 3px rgba(77, 255, 136, 0.9)) drop-shadow(0 0 18px rgba(77, 255, 136, 0.45));
}

.popup-card--challenge h2 {
  color: var(--color-success);
}

/* Daily popup (2.5.2): long languages ran 13px past a 640px screen (es); it scrolls inside the card,
   with the overlay's 20px padding kept on each side. */
.popup-card--daily-goal {
  max-height: calc(100vh - 44px - var(--popup-safe-y));
  max-height: calc(100dvh - 44px - var(--popup-safe-y));
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Options popup (lobby) — scrolls inside the card on short screens instead
   of pushing past the viewport. */
.popup-card--options {
  max-height: calc(100vh - 40px - var(--popup-safe-y));
  max-height: calc(100dvh - 40px - var(--popup-safe-y));
  overflow-y: auto;
  overscroll-behavior: contain;
}

.options-links {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.options-link {
  display: block;
  min-height: 44px;
  box-sizing: border-box;
  padding: 11px 16px;
  border-radius: 999px;
  border: 2px solid var(--ui-gold);
  background: linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 55%, var(--ui-crimson-dark) 100%);
  color: #fff;
  font-weight: 700;
  text-decoration: none;
  touch-action: manipulation;
}

.options-note {
  margin: 0;
  padding: 0 6px;
  font-size: 12px;
  line-height: 1.4;
  color: inherit;
  opacity: 0.85;
}

.options-link:focus-visible {
  outline: 3px solid var(--ui-crimson);
  outline-offset: 2px;
}

/* Paw divider under popup titles. */
.popup-divider {
  display: block;
  width: min(240px, 70%);
  height: auto;
  margin: -8px auto -4px;
}

/* Shared coming-soon modal for content that is not built yet. */
.popup-coming-soon-art {
  width: 64px;
  height: 64px;
  margin: -8px auto -8px;
}

.popup-coming-soon-body {
  margin: 0;
  font-size: 1.1rem;
  line-height: 1.5;
  color: var(--ui-brown);
}

/* Daily Goal modal (opened by #btn-mission): one segment per daily game,
   each reusing the .xp-bar-track look; filled segments take the accent. */
.daily-goal-segments {
  display: flex;
  gap: 6px;
  width: 100%;
}

.daily-goal-segment {
  flex: 1;
  height: 14px;
}

.daily-goal-segment.is-filled {
  background: var(--color-accent);
}

.daily-goal-modal-count {
  margin: -6px 0 0;
  font-size: 1.4rem;
  font-weight: 800;
  color: var(--color-accent);
}

.daily-goal-modal-status {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.daily-goal-modal-reward {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
  color: var(--color-accent);
  overflow-wrap: anywhere;
}

/* Weekly stamps (GAME_SPEC.md §4.5.1) under today's progress: N/goal, the
   Daily claims left and the weekly reward. Text only, no new art. */
.daily-goal-weekly {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 100%;
  padding-top: 8px;
  border-top: 2px solid var(--color-bg-alt);
}

.daily-goal-weekly-title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--color-text);
}

.daily-goal-weekly-count {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--color-accent);
}

.daily-goal-weekly-status,
.daily-goal-weekly-reward {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.daily-goal-weekly-reward {
  color: var(--color-accent);
}

/* Local review only (localhost + ?retentionReview=1). */
.daily-review-group {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
}

.daily-review-group button {
  background: var(--color-bg-alt);
  color: var(--color-text);
  border: 2px solid var(--color-accent);
}

.daily-review-group button:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

/* Manual popup (lobby) — the former in-flow How to Play / Features / What's
   New text. Only .manual-content scrolls; the card stays viewport-bounded. */
.popup-card--manual {
  width: min(680px, calc(100vw - 24px));
  max-width: 100%;
  /* The overlay keeps 20px padding on each side: 40px plus a little room, or the card ran 4px past
     a 640px-tall screen (2.5.2). */
  max-height: calc(100vh - 44px - var(--popup-safe-y));
  max-height: calc(100dvh - 44px - var(--popup-safe-y));
}

.manual-content {
  max-height: calc(100vh - 140px - var(--popup-safe-y));
  max-height: calc(100dvh - 140px - var(--popup-safe-y));
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  text-align: left;
}

/* Same readable sizing the lobby text panel used (18px+ body for the 50-70
   target audience). */
.manual-content h3 {
  font-size: 1.3rem;
  color: var(--color-accent);
  margin: 28px 0 10px;
}

.manual-content h3:first-child {
  margin-top: 0;
}

.manual-content p {
  font-size: 18px;
  line-height: 1.7;
  color: var(--ui-brown);
  margin: 0 0 8px;
}

.popup-clear-subtitle {
  margin: 0;
  font-size: 0.95rem;
  color: #fff;
}

/* Universal dismiss control (item 4) — every popup can be closed without
   completing its flow. */
.popup-close-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 3px solid var(--ui-gold);
  background: linear-gradient(180deg, var(--ui-crimson-light) 0%, var(--ui-crimson) 55%, var(--ui-crimson-dark) 100%);
  box-shadow: 0 3px 0 var(--ui-crimson-shadow);
  color: #fff;
  font-size: 1.05rem;
  font-weight: 800;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
  z-index: 3;
}
.popup-close-btn:active {
  transform: translateY(2px);
}

.popup-close-btn:focus-visible {
  outline: 3px solid var(--ui-crimson);
  outline-offset: 2px;
}

#popup-clear .popup-reward {
  color: #fff;
}

.popup-reward {
  font-size: 1.3rem;
  margin: 0;
}

.brain-growth {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.popup-brain-xp-gained {
  margin: 0;
  font-size: 1rem;
  color: #fff;
}

.popup-brain-level {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--color-accent);
}

.xp-bar-track {
  width: 100%;
  height: 12px;
  border-radius: 999px;
  background: rgba(90, 42, 16, 0.22);
  box-shadow: inset 0 1px 2px rgba(90, 42, 16, 0.35);
  overflow: hidden;
}

.xp-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--color-accent);
  transition: width 0.3s ease;
}

.xp-bar-label {
  margin: 0;
  font-size: 0.85rem;
  color: #fff;
}

/* Daily Goal (Retention Core v1) — clear-popup block between Brain Growth
   and NEXT (or before OK on the last level). The bar reuses
   .xp-bar-track/.xp-bar-fill (and their reduced-motion rule); long
   translations wrap rather than clip. */
.daily-goal {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.daily-goal-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.daily-goal-title {
  margin: 0;
  min-width: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text);
  text-align: left;
  overflow-wrap: anywhere;
}

.daily-goal-count {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-accent);
  white-space: nowrap;
}

.xp-bar-fill.daily-goal-bar-fill {
  background: var(--ui-cta-green-mid);
}

.daily-goal-message {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.level-up-badge {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 800;
  color: var(--color-accent);
}

.level-up-badge.level-up-pop {
  animation: level-up-pop 0.5s ease-out;
}

@keyframes level-up-pop {
  0% { transform: scale(0.6); opacity: 0; }
  50% { transform: scale(1.15); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

.popup-brain-level.brain-level-pulse {
  animation: brain-level-pulse 0.5s ease-out;
}

@keyframes brain-level-pulse {
  0% { transform: scale(1); }
  40% { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* Brain Growth phase 2 motion — final values are always set synchronously
   regardless of this rule; this only strips the visual transition/animation. */
@media (prefers-reduced-motion: reduce) {
  .xp-bar-fill { transition: none !important; }
  .level-up-badge.level-up-pop,
  .popup-brain-level.brain-level-pulse,
  .brain-letter-slot.just-collected {
    animation: none;
    opacity: 1;
    transform: translate(-50%, 0);
  }
  .zoom-hint {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .brain-letter-slot.sweep-light,
  .brain-collect-hud.brain-complete,
  .brain-collect-hud.brain-level-up,
  .level-goal-callout.level-goal-show {
    animation: none !important;
  }
  /* Defense in depth — spawnCorrectBurst()/playBrainBeam()/playBrainSlotSweep()
     already skip creating/adding these under reduced motion; this just
     guarantees no stray instance can animate if one ever slips through. */
  .marker-burst, .brain-beam-orb {
    display: none !important;
  }
  .hint-ripple, .refill-particle {
    display: none !important;
  }
  .hud-hearts-gauge.refill-pop, .hud-hints-gauge.refill-pop {
    animation: none !important;
  }
  /* Screen/popup entrances and the tap ripple stay instantaneous rather than
     fully disabled — hiding the tap ripple outright would remove the only
     "the screen felt your touch" cue this fix adds, which reduced-motion
     users need at least as much as anyone else. */
  .screen:not(.hidden),
  .popup-card {
    animation: none !important;
    opacity: 1;
    transform: none;
  }
  .tap-ripple {
    animation: tap-ripple-fade 0.2s ease-out forwards;
    transform: scale(1);
  }
}

@keyframes tap-ripple-fade {
  from { opacity: 0.9; }
  to { opacity: 0; }
}

/* ---------- Debug panel ---------- */
/* Off-canvas drawer: collapsed by default so it never covers the game
   screen; only the toggle button is visible until the user opens it, and
   even open it's capped to a fraction of the width so both image panes
   stay comparable. */
.debug-toggle-btn {
  position: fixed;
  right: 16px;
  bottom: 16px;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.85);
  color: #0f0;
  font-size: 1.3rem;
  z-index: 101;
  cursor: pointer;
}

.debug-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(40vw, 340px);
  background: rgba(0, 0, 0, 0.92);
  color: #0f0;
  font-family: monospace;
  font-size: 0.85rem;
  padding: 10px;
  padding-bottom: 76px; /* clears the toggle button */
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform 0.25s ease;
}

.debug-panel.open {
  transform: translateX(0);
}

.debug-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.debug-row input[type="number"] {
  width: 70px;
  font-size: 1rem;
  padding: 6px;
}

#debug-overlap-list {
  color: #ff0;
}

#debug-edit-hint {
  color: #7fd6ff;
  font-size: 0.8rem;
}

#debug-export-status {
  color: #0f0;
}

.debug-circle.editable {
  pointer-events: auto;
  cursor: move;
  border-color: rgba(0, 200, 255, 0.9);
  background: rgba(0, 200, 255, 0.12);
}

.debug-resize-handle {
  position: absolute;
  left: 100%;
  top: 50%;
  width: 16px;
  height: 16px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #00c8ff;
  border: 2px solid #003a4d;
  pointer-events: auto;
  cursor: nwse-resize;
}

/* ---------- Level-clear celebration ---------- */
.popup-overlay {
  overflow: hidden;
}

.celebration-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 40;
}

/* BRAIN 슬롯 이동 연출 (GAME_SPEC.md §4.5 4단계) — fixed full-viewport layer,
   the topmost in-game decorative overlay so the beam stays visible over the
   celebration confetti, the clear popup, and the HUD (all below the loading
   overlay's z-index 200, which it never needs to exceed). */
.brain-beam-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 150;
  overflow: visible;
}

/* 2026-09-19: the previous white/yellow/orange palette had no dark contrast
   ring, so it visually disappeared against the many light/white-background
   puzzle images (a real, reported "too faint to see" bug) — this adds a dark
   ring for contrast on light art and a white ring for contrast on dark art,
   plus a more saturated magenta/purple core that's rare in the game's own
   product-illustration palette, so it reads clearly against either. */
.brain-beam-orb {
  position: absolute;
  left: 0;
  top: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: radial-gradient(circle, #ffffff 0%, #fff1a8 20%, #ff4fd8 46%, #a020f0 72%, rgba(160, 32, 240, 0) 100%);
  box-shadow:
    0 0 0 3px rgba(18, 8, 36, 0.9),
    0 0 0 6px rgba(255, 255, 255, 0.95),
    0 0 20px 8px rgba(255, 79, 216, 0.95),
    0 0 38px 16px rgba(160, 32, 240, 0.85),
    0 0 55px 22px rgba(77, 255, 136, 0.55);
  will-change: transform, opacity;
}

/* ---------- Reward celebration (GAME_SPEC.md §4.5.1) ----------
   Full-screen dark scrim over the clear flow (above popups and confetti at
   50, below the loading overlay at 200). Mobile portrait first: the icon row
   and type scale with the viewport width. Icons pop in one after another;
   sparkles are a few small CSS glints, no extra art. */
.reward-celebration {
  position: fixed;
  inset: 0;
  z-index: 120;
  background: rgba(0, 0, 0, 0.78);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(16px + var(--safe-top)) 16px calc(16px + var(--safe-bottom));
  overflow: hidden;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.reward-celebration-content {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: min(560px, 100%);
  text-align: center;
}

.reward-celebration-title {
  margin: 0;
  font-size: clamp(1.9rem, 8.5vw, 3rem);
  font-weight: 900;
  line-height: 1.15;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

.reward-celebration-subtitle {
  margin: 0;
  font-size: clamp(1.1rem, 4.6vw, 1.5rem);
  font-weight: 700;
  color: var(--color-accent);
}

.reward-celebration.is-playing .reward-celebration-title,
.reward-celebration.is-playing .reward-celebration-subtitle {
  animation: reward-rise 360ms ease-out both;
}

.reward-celebration-hero {
  width: min(56vw, 260px);
  height: auto;
  animation: reward-pop 620ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

.reward-celebration-items {
  list-style: none;
  margin: 4px 0 8px;
  padding: 0;
  display: flex;
  justify-content: center;
  gap: clamp(8px, 3vw, 20px);
  width: 100%;
}

.reward-item {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
  max-width: 170px;
  animation: reward-pop 560ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
  animation-delay: calc(var(--reward-index) * 160ms + 200ms);
}

.reward-celebration-items--many {
  flex-wrap: wrap;
  row-gap: 12px;
}

/* 4+ items: rows of up to 3 so nothing leaves the screen. */
.reward-celebration-items--many .reward-item {
  flex: 0 0 calc((100% - 2 * clamp(8px, 3vw, 20px)) / 3);
  max-width: calc((100% - 2 * clamp(8px, 3vw, 20px)) / 3);
}

.reward-item-icon {
  width: min(26vw, 140px);
  height: min(26vw, 140px);
  object-fit: contain;
}

.reward-item-label {
  font-size: clamp(1rem, 4.2vw, 1.35rem);
  font-weight: 800;
  line-height: 1.2;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

#btn-reward-collect {
  min-width: min(240px, 70vw);
  font-size: 1.35rem;
}

.reward-celebration-sparkles {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.reward-celebration-sparkles span {
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--ui-gold);
  box-shadow: 0 0 12px 4px var(--ui-gold);
  opacity: 0;
  animation: reward-sparkle 1800ms ease-in-out infinite;
}

.reward-celebration-sparkles span:nth-child(1) { left: 14%; top: 22%; animation-delay: 300ms; }
.reward-celebration-sparkles span:nth-child(2) { left: 82%; top: 18%; animation-delay: 700ms; }
.reward-celebration-sparkles span:nth-child(3) { left: 10%; top: 64%; animation-delay: 1100ms; }
.reward-celebration-sparkles span:nth-child(4) { left: 88%; top: 58%; animation-delay: 500ms; }
.reward-celebration-sparkles span:nth-child(5) { left: 30%; top: 84%; animation-delay: 900ms; }
.reward-celebration-sparkles span:nth-child(6) { left: 70%; top: 86%; animation-delay: 1300ms; }

@keyframes reward-pop {
  0% { opacity: 0; transform: scale(0.3); }
  60% { opacity: 1; transform: scale(1.12); }
  80% { transform: scale(0.96); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes reward-rise {
  0% { opacity: 0; transform: translateY(12px); }
  100% { opacity: 1; transform: none; }
}

@keyframes reward-sparkle {
  0%, 100% { opacity: 0; transform: scale(0.4); }
  50% { opacity: 0.9; transform: scale(1); }
}

/* Reduced motion: final state immediately (no pop, rise or sparkles). The
   class is set by game.js too, so the result never depends on the media
   query alone. */
.reward-celebration--static.is-playing .reward-celebration-title,
.reward-celebration--static.is-playing .reward-celebration-subtitle,
.reward-celebration--static .reward-celebration-hero,
.reward-celebration--static .reward-item {
  animation: none;
  opacity: 1;
  transform: none;
}

.reward-celebration--static .reward-celebration-sparkles {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .reward-celebration .reward-celebration-hero,
  .reward-celebration.is-playing .reward-celebration-title,
  .reward-celebration.is-playing .reward-celebration-subtitle,
  .reward-celebration .reward-item,
  .reward-celebration .reward-celebration-sparkles span {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .reward-celebration .reward-celebration-sparkles { display: none; }
}

/* ---------- Languages (js/localization.js) ----------
   Maplestory covers Basic Latin, Hangul and kana only. Languages whose letters it
   lacks (accented Latin, Arabic, Thai, Devanagari, Chinese) use the system stack for
   the whole screen, so one word never mixes two typefaces. */
html:is(:lang(de), :lang(fr), :lang(es), :lang(pt), :lang(it), :lang(pl), :lang(tr), :lang(vi), :lang(ar), :lang(th), :lang(hi), :lang(zh)),
html:is(:lang(de), :lang(fr), :lang(es), :lang(pt), :lang(it), :lang(pl), :lang(tr), :lang(vi), :lang(ar), :lang(th), :lang(hi), :lang(zh)) body,
.lang-btn:is(:lang(de), :lang(fr), :lang(es), :lang(pt), :lang(it), :lang(pl), :lang(tr), :lang(vi), :lang(ar), :lang(th), :lang(hi), :lang(zh)) {
  font-family: var(--bp-system-font);
}

/* Counts are digits only (Latin digits in every language), which Maplestory has, so the
   resource numbers keep the game face and the same width whatever the language. */
.resource-value,
.btn-hint-count {
  font-family: "Maplestory", var(--bp-system-font);
}

/* Arabic reads right to left (html dir="rtl"); the two pictures and their tap
   coordinates, the level strip, the rescue map and the dream tree strips keep their
   left-to-right order and scrolling. */
[dir="rtl"] .game-board,
[dir="rtl"] .level-gallery-grid,
[dir="rtl"] .village-map,
[dir="rtl"] .dream-tree-scroll,
[dir="rtl"] .home-banner-viewport {
  direction: ltr;
}

/* Each banner still reads right to left inside the left-to-right sliding strip. */
[dir="rtl"] .home-banner-track > * {
  direction: rtl;
}

/* The banner copy starts on the right in Arabic, so the picture, its arrows/dots and the
   fade behind the copy move to the left (css/lobbyQuest.css places them on the right). */
[dir="rtl"] .home-banner-art {
  right: auto;
  left: 4%;
}

[dir="rtl"] .home-banner-controls {
  right: auto;
  left: 8px;
}

[dir="rtl"] .home-banner-shade {
  background: linear-gradient(270deg, rgba(255, 250, 235, 0.94) 0%, rgba(255, 250, 235, 0.86) 52%, rgba(255, 250, 235, 0) 78%);
}

/* Seventeen languages: a scrolling two-column list inside the options popup. */
.popup-card .lang-switcher {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr));
  max-height: min(34vh, 232px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 2px 2px 6px;
}

/* BP outline text — the one shared treatment for short UI text: keep the
   element's own color, add a black outline (a text stroke under the fill)
   and a flat opaque black drop shadow sized in em. New short UI text takes .bp-outline-text (or is added
   here); long body copy, legal footers and the debug panel stay plain.
   Rules: D:\BrainPang_HQ\docs\web-ui-style.md. Keep this rule last. */
.bp-outline-text,
.level-gallery-progress,
.lobby-content-label,
.btn-play-hero,
.level-card-number,
.level-card-badge,
.hud-game-title,
.hud-item,
.level-goal-callout,
.zoom-hint,
#btn-hint,
#btn-zoom,
.popup-card h2,
.manual-content h3,
.popup-close-btn,
#btn-sound-toggle,
.lang-btn,
.options-link,
#btn-manual-open,
.popup-clear-subtitle,
#popup-clear .popup-reward,
.popup-brain-xp-gained,
.level-up-badge,
.popup-brain-level,
.brain-letter-slot,
.xp-bar-label,
.daily-goal-title,
.daily-goal-count,
.daily-goal-message,
.daily-goal-modal-count,
.daily-goal-modal-status,
.daily-goal-modal-reward,
.daily-review-group button,
.daily-goal-weekly-title,
.daily-goal-weekly-count,
.daily-goal-weekly-status,
.daily-goal-weekly-reward,
.reward-celebration-title,
.reward-celebration-subtitle,
.reward-item-label,
#btn-reward-collect,
#btn-next,
#btn-last-ok,
#btn-refill-confirm,
#btn-coming-soon-ok,
#btn-coming-soon-store,
#btn-daily-goal-ok,
.resource-value,
.stage-panel-title,
.bottom-nav-label,
.btn-hint-count {
  text-shadow: var(--bp-layer-text-shadow, var(--bp-text-outline));
  -webkit-text-stroke: var(--bp-layer-text-stroke, var(--bp-text-stroke));
  paint-order: stroke fill;
}

/* BP outline text on light fills — near-black labels on yellow buttons and
   badges become illegible under a black outline, so outlined text is white
   there. Colored text (accent yellow, red hearts) keeps its color. */
.btn-play-hero,
.btn-primary,
.lang-btn.active,
.level-card-badge {
  color: #fff;
}

/* Landscape phones: in-game popups taller than the screen (the clear result, the options) are shown
   smaller to fit, by js/popupFit.js (--popup-fit, half size at least; 2.5.1 (434), owner 2026-10-30).
   Their own height limits come off so the card keeps its natural layout and every button stays on it. */
@media (orientation: landscape) and (max-height: 500px) {
  .popup-overlay .popup-card {
    scale: var(--popup-fit, 1);
  }

  .popup-card--clear,
  .popup-card--options {
    max-height: none;
    overflow: visible;
  }
}
