/* ***************************************************************
 * MireGameKit - portrait first dark shell theme
 * One stylesheet for every game in the arcade: the splash veil, the
 * letterboxed portrait stage, the branded dialog cards, the full mf- ui
 * class inventory, and the generated arcade hub page. Portrait is native;
 * desktop simply centers the portrait column with neutral gutters. There is
 * no landscape concept anywhere in this file.
 * *************************************************************** */

/* ***************************************************************
 * Theme tokens (dom chrome only, per game palettes affect the canvas)
 * *************************************************************** */

:root {
  --mf-bg: #10131c;
  --mf-bg-2: #1c2230;
  --mf-surface: rgba(28, 34, 48, 0.86);
  --mf-surface-flat: rgba(16, 19, 28, 0.4);
  --mf-ink: #eef1f7;
  --mf-ink-dim: rgba(255, 255, 255, 0.55);
  --mf-faint: rgba(255, 255, 255, 0.12);
  --mf-accent: #b8a8d8;
  --mf-accent-soft: rgba(184, 168, 216, 0.14);
  --mf-danger: #f6465d;
  --mf-up: #0ecb81;
  --mf-card-border: rgba(184, 168, 216, 0.18);
  /* The crown / medal / trophy / favorite-star semantic gold. A token so day
     mode can swap in a deeper, more legible gold on the light parchment without
     touching the night value, which stays this exact bright gold. */
  --mf-gold: #ffd75e;
  --mf-brand-ink: #eef1f7;
  --mf-radius: 14px;
  --mf-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;
}

/* ***************************************************************
 * Day mode token overrides (OPTIONAL warm parchment theme)
 * Selected by data-theme="day" on the <html> root. Absence or any unknown
 * value stays NIGHT (the unconditional dark tokens above). The majority of the
 * chrome reads these var(--mf-*) tokens, so re-pointing them here re-themes most
 * of the dialog cards, buttons, lists and hub to the light parchment palette in
 * one place; the hardcoded values that do NOT follow tokens are overridden in
 * the day-override block further down.
 * *************************************************************** */

:root[data-theme="day"] {
  --mf-bg: #faf9f5;
  --mf-bg-2: #efe9d9;
  --mf-surface: #ffffff;
  --mf-surface-flat: #f1eee7;
  --mf-ink: #2c2a26;
  --mf-ink-dim: #5c5850;
  --mf-faint: #e6e2d9;
  --mf-accent: #880e4f;
  --mf-accent-soft: rgba(136, 14, 79, 0.10);
  --mf-danger: #c62828;
  --mf-up: #4e7d2f;
  --mf-card-border: #e6e2d9;
  /* a deeper, saturated gold so crowns / medals / trophies / stars stay clearly
     legible on the light parchment, where the bright night gold washes out */
  --mf-gold: #e0a400;
}

/* ***************************************************************
 * Reset and base page (no scroll bounce inside the Telegram webview)
 * *************************************************************** */

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100dvh;
  width: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  font-family: var(--mf-font);
  font-size: 16px;
  color: var(--mf-ink);
  background: linear-gradient(160deg, var(--mf-bg) 0%, var(--mf-bg-2) 100%);
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* The single visibility switch used across every ui module */
.mf-hidden {
  display: none !important;
}

/* The outermost mount must carry the full viewport height so the percentage
 * height chain reaches the shell and the canvas; without this the shell
 * collapses to its content and the stage only fills the top of the screen */
#app-root {
  position: relative;
  width: 100%;
  height: 100%;
}

/* The splash veil overlays the whole app while present and never takes layout
 * space once the kit has removed it */
#splash-root {
  position: absolute;
  inset: 0;
  z-index: 50;
}

#splash-root:empty {
  display: none;
  pointer-events: none;
}

/* ***************************************************************
 * Shell - the portrait stage column
 * Fills the viewport on mobile; on desktop the column is capped so it never
 * stretches much past a 9:16 portrait frame and centers with dark gutters.
 * *************************************************************** */

.mf-shell {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100dvh;
  margin: 0 auto;
  overflow: hidden;
  background: linear-gradient(160deg, var(--mf-bg) 0%, var(--mf-bg-2) 100%);
  /* Expose the device safe-area as variables for the UI control layers only.
   * The shell and canvas stay full-bleed to all four edges so the game art
   * reaches the screen edges with no dark bars; only the chrome (pause button,
   * menu, overlays) insets. Putting the inset back here as padding shrank the
   * canvas and let the dark shell background show as bars on any device with a
   * real notch or in Telegram fullscreen, which was the black-edge bug. */
  --mf-safe-top: env(safe-area-inset-top);
  --mf-safe-right: max(0px, env(safe-area-inset-right));
  --mf-safe-bottom: env(safe-area-inset-bottom);
  --mf-safe-left: max(0px, env(safe-area-inset-left));
  /* The ONE anchor of the floating pause control and of everything pinned to
   * its line: the lives hearts, the corner stat and the in-run jeton pill. The
   * top is the device safe area PLUS a fixed clearance so the control sits below
   * Telegram's fullscreen close / collapse / menu chrome in the top corners.
   * Every rule below reads these tokens instead of copying the calc, so moving
   * the pause button is a ONE LINE change here that drags the whole family with
   * it. They are declared on the shell, not on :root, because the safe-area vars
   * they read are declared here and a custom property resolves its own var()
   * against the element it is declared on. */
  --mf-pause-top: calc(max(12px, var(--mf-safe-top, 0px)) + 44px);
  --mf-pause-right: max(12px, var(--mf-safe-right, 0px));
  --mf-pause-size: 48px;
}

/* The portrait column cap is a DESKTOP-centering device only and is gated by
 * viewport WIDTH, not by pointer type. A pointer:coarse gate was unreliable
 * inside the Telegram WebView, so the cap stayed live on phones and squeezed
 * every game into a narrow column with side gutters - the standard width gap the
 * owner saw in all games. A phone viewport is always far under 700px, so it now
 * always fills the full width with no gutters; only a genuinely wide desktop or
 * tablet viewport centers the portrait column with neutral side gutters. */
@media (min-width: 700px) {
  .mf-shell {
    max-width: calc(100vh * 0.5625);
  }
}

/* The stage canvas the engine draws into, never zoomable or scrollable */
.mf-canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}

/* The game shell box the canvas and overlays stack inside */
#game-shell {
  position: relative;
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  overflow: hidden;
}

/* ***************************************************************
 * THE STACKING SCALE - read this before writing another z-index
 * ***************************************************************
 *
 * Every layer in the kit shell belongs to one of these tiers. The numbers were
 * picked one at a time over a long stretch and ended up scattered across 1, 2, 5,
 * 10, 20, 21, 25, 30, 40, 50, 59, 60, 61, 70 and 100 with nothing saying what any
 * of them meant - which is how the in-run jeton pill (20) came to sit ON TOP of the
 * leaderboard panel (10) that was supposed to have replaced it on screen. Name the
 * tier you are adding to and reuse its number; do not invent a value between two.
 *
 *    1-5   IN-CARD DECORATION. Nested pieces inside one card or panel that only
 *          stack against their own siblings: a card above its dim, a badge over a
 *          row, a panel's own sticky header. Never compared against anything below.
 *
 *     10   THE MENU LAYER (#menu-root). The resting menu and every panel raised
 *          into it: main, leaderboard, how-to, goals, invite, wardrobe.
 *
 *     20   THE IN-RUN HUD LAYER. The readouts that belong to a LIVE RUN and to
 *          nothing else: the pause button, the lives row, the corner stat and the
 *          jeton pill. This tier sits ABOVE the menu layer on purpose, because
 *          during play there is no menu under it - which is exactly why it must be
 *          taken DOWN, not out-stacked, whenever a screen is raised over the run.
 *          The mf-run-hud-off rule below is what does that; see app-host's
 *          setRunHudVisible for why hiding the elements alone is not enough.
 *
 *  21-25   IN-RUN TRANSIENTS. Short-lived notices that must clear the HUD they
 *          appear beside: the rewarded-ad status line (21), the wave/level banner
 *          (25). They belong to the run and go down with the HUD layer.
 *
 *     30   THE OVERLAY LAYER (#overlay-root). Screens raised OVER a run: the
 *          paused card, the revive offer, the game over card.
 *
 *     40   MODAL EXTRAS OVER AN OVERLAY. A picker or sheet opened from a card in
 *          the overlay layer, which must clear the card that opened it.
 *
 *     50   THE APP FRAME. Chrome that is outside the game shell entirely and
 *          outlives any single screen.
 *
 *  59-70   THE DEV/EDIT SURFACE. The browser layout editor and its handles. Above
 *          everything the player ever sees, and shipped disabled.
 *
 *    100   THE SPLASH. The brand entry veil, which by definition covers all of it.
 */

/* Menu and overlay mount points cover the stage */
#menu-root,
#overlay-root {
  position: absolute;
  inset: 0;
  z-index: 10;
}

#overlay-root {
  z-index: 30;
  pointer-events: none;
}

/* Overlay children take pointer events back when present */
#overlay-root > * {
  pointer-events: auto;
}

/* Empty mount points must not block the canvas */
#menu-root:empty,
#overlay-root:empty {
  pointer-events: none;
}

/* The menu root centers its single visible panel over the stage, kept clear of
 * the device notch and the Telegram fullscreen header by the safe-area insets */
#menu-root {
  display: flex;
  padding: calc(16px + var(--mf-safe-top, 0px))
    calc(16px + var(--mf-safe-right, 0px))
    calc(16px + var(--mf-safe-bottom, 0px))
    calc(16px + var(--mf-safe-left, 0px));
}

/* ***************************************************************
 * Short screen flag (set by the app host from measured pixels)
 * *************************************************************** */

body.mf-short .mf-card {
  padding: 14px 16px;
  gap: 9px;
}

body.mf-short .mf-title {
  font-size: 20px;
}

body.mf-short .mf-subtitle {
  font-size: 13px;
}

/* a short viewport has no room to spare, so the mascot keeps a fixed compact box
   and opts out of the viewport scaling the tall layout uses */
body.mf-short .mf-mascot {
  width: 104px;
  height: clamp(72px, 16vh, 110px);
}

body.mf-short .mf-brand-strip {
  height: 11px;
}

/* ***************************************************************
 * Splash + loading veil (the brand entry, doubles as the load veil)
 * *************************************************************** */

.mf-splash {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  /* A neutral dark veil, never the per game palette and never the day theme: the
     brand lockup uses the dark-background variant, so the splash stays dark in day
     mode too. Hardcoded (not the themed --mf-bg, which day flips to a light cream)
     so the brand mark never washes out on a light splash. */
  background: linear-gradient(160deg, #10131c 0%, #1c2230 100%);
  opacity: 1;
}

/* The centered icon over wordmark stack, the only content on the splash */
.mf-splash-logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 24px;
}

/* The brand icon breathes gently while the game loads behind the veil */
.mf-splash-logo img:first-child {
  width: 84px;
  height: 84px;
  animation: mf-pulse 2.2s ease-in-out infinite;
}

/* The wordmark keeps its wide ratio, sized by height */
.mf-splash-logo img:last-child {
  height: 22px;
  width: auto;
  opacity: 0.9;
}

/* The two-tone splash wordmark rendered as text rather than an image, so the
 * Mire bordeaux and Games grey read true. The .mf-brand-mire and
 * .mf-brand-games spans inside it carry the colors (those rules already exist),
 * this rule just sets the bold size and slight tracking of the lockup */
.mf-splash-word {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 800;
  font-size: 28px;
  letter-spacing: 0.4px;
  line-height: 1;
}

/* The single fade out, kept under the spec cap of four tenths of a second */
.mf-splash-fade {
  opacity: 0;
  transition: opacity 380ms ease;
}

/* Soft scale and opacity pulse for the brand icon */
@keyframes mf-pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.7;
  }
  50% {
    transform: scale(1.08);
    opacity: 1;
  }
}

/* ***************************************************************
 * Brand strip + cta (the quiet repetition on resting surfaces)
 * *************************************************************** */

/* The brand lockup strip pinned at the top of every dialog card: the icon and
 * the Mire wordmark side by side, centered and quiet, never the bare word */
/* The studio wordmark sits at the FOOT of every card, not the head. It used to take
 * the single most valuable line on the panel, directly above the game's own name and
 * squeezed between the two corner buttons, where it read as a cramped page heading
 * rather than as a mark. A publisher signature belongs under the content: the top of
 * a title screen is for the game's identity, and moving the strip down lets the title
 * and the mascot rise into the space it was holding. The card is a flex column, so a
 * high order value is all it takes; the markup order is untouched, which keeps the
 * brand early in the reading order for a screen reader. */
.mf-brand-strip {
  flex: 0 0 auto;
  order: 99;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  height: 14px;
  margin-top: 2px;
  opacity: 0.92;
}

/* The brand icon, a square mark that scales with the strip height */
.mf-brand-icon {
  height: 100%;
  aspect-ratio: 1 / 1;
  background: url("assets/brand/trademire-icon-dark.svg") center / contain
    no-repeat;
}

/* The Mire Games wordmark, two-tone text so the brand colors read true: a flat
 * single-color svg could not show Mire in bordeaux and Games in grey */
.mf-brand-text {
  height: 100%;
  display: flex;
  align-items: center;
  gap: 4px;
  font-weight: 800;
  font-size: 13px;
  letter-spacing: 0.2px;
  line-height: 1;
}

/* The wordmark reuses the logo icon's exactly two colors so the mark and the
 * text read as one lockup: Mire takes the accent-circle burgundy, Games takes
 * the figure grey. Both hex values are lifted straight from the icon svg. */
.mf-brand-mire {
  color: #d96f8a;
}
.mf-brand-games {
  color: #b8b0c2;
}

/* When the strip carries an explicit img instead of a background */
.mf-brand-strip-img {
  height: 13px;
  width: auto;
  opacity: 0.55;
}

/* The single game over call to action row, quiet by design */
.mf-brand-cta {
  margin-top: 4px;
  text-align: center;
  font-size: 12px;
  color: var(--mf-ink-dim);
}

.mf-brand-cta a {
  color: var(--mf-accent);
  text-decoration: none;
}

.mf-brand-cta a:hover {
  text-decoration: underline;
}

/* ***************************************************************
 * Cards and typography
 * *************************************************************** */

.mf-card {
  position: relative;
  margin: auto;
  width: min(420px, 100%);
  max-height: 100%;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow-y: auto;
  background: var(--mf-surface);
  border: 1px solid var(--mf-card-border);
  border-radius: var(--mf-radius);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(8px);
}

/* The flexible middle slot inside a card stacks its content vertically */
.mf-card-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.mf-title {
  margin: 0;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: var(--mf-ink);
  text-align: center;
}

/* Secondary line under the title. Readability is lifted a touch with a larger
 * size and a clearer ink that still reads as secondary, not as a heading */
.mf-subtitle {
  margin: 0;
  font-size: 15px;
  line-height: 1.45;
  color: rgba(238, 241, 247, 0.85);
  text-align: center;
}

/* ***************************************************************
 * Buttons (touch targets stay at or above forty eight pixels)
 * *************************************************************** */

.mf-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding: 12px 16px;
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  color: var(--mf-ink);
  background: rgba(184, 168, 216, 0.1);
  border: 1px solid var(--mf-card-border);
  border-radius: 11px;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.05s ease;
}

.mf-btn:hover {
  background: rgba(184, 168, 216, 0.18);
}

.mf-btn:active {
  transform: scale(0.98);
}

.mf-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Primary call to action carries the accent emphasis. A subtle vertical
 * gradient gives the fill depth and a soft purple glow lifts it off the card,
 * without changing the layout or size of the button */
.mf-btn-primary {
  color: var(--mf-bg);
  background: linear-gradient(180deg, #c8bbe4 0%, var(--mf-accent) 100%);
  border-color: transparent;
  box-shadow: 0 8px 22px rgba(184, 168, 216, 0.4),
    0 0 18px rgba(184, 168, 216, 0.28);
}

.mf-btn-primary:hover {
  background: linear-gradient(180deg, #d4c9ec 0%, #c8bbe4 100%);
}

/* Watch-ad continue button: the single standard rewarded-ad action across every
 * game (on-death revive, the-claw +seconds, the game-over double-coins, the
 * wardrobe's costume lease). A gold fill plus a film-strip marker so players
 * always read it as "free by watching an ad". One definition here drives all
 * games. This block used to be written TWICE, a hundred lines apart, and the
 * second copy silently won on every property it repeated; the values kept below
 * are that winner (the #ffd75e->#f0b429 fill, the 700 weight, the #2a1d10 ink),
 * so merging the pair changes nothing on screen and leaves one place to edit. */
.mf-btn-ad {
  color: #2a1d10;
  background: linear-gradient(180deg, #ffd75e, #f0b429);
  border-color: transparent;
  font-weight: 700;
  box-shadow: 0 8px 22px rgba(240, 180, 41, 0.32);
}

.mf-btn-ad:hover {
  background: linear-gradient(180deg, #ffe9a8 0%, #ffce3a 100%);
  filter: brightness(1.06);
}

/* the classic film-strip marker prefixed inside the button; a pseudo-element with
 * an inline-SVG film strip (dark frame, gold sprocket holes) so it reads as a
 * rewarded VIDEO ad and survives the overlay re-setting the button's textContent.
 * Sized at 1.25em rather than the old 1.7em: on the full-width overlay buttons the
 * difference is cosmetic, but in a wardrobe row the button is one fixed 116px
 * column, and a 1.7em glyph plus its margin ate a third of that column and pushed
 * the label out of the 48px touch target. */
.mf-btn-ad::before {
  content: '';
  display: inline-block;
  width: 1.25em;
  height: 1.25em;
  margin-right: 6px;
  vertical-align: middle;
  background: center / contain no-repeat
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><rect x='2.5' y='3' width='19' height='18' rx='3' fill='%232a2300'/><g fill='%23ffd75e'><rect x='4' y='5.2' width='1.7' height='1.7' rx='.4'/><rect x='4' y='9' width='1.7' height='1.7' rx='.4'/><rect x='4' y='12.8' width='1.7' height='1.7' rx='.4'/><rect x='4' y='16.6' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='5.2' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='9' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='12.8' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='16.6' width='1.7' height='1.7' rx='.4'/><path d='M10 8.5 L10 15.5 L15.5 12 Z'/></g></svg>");
}

/* The watching state drops the film strip: the loading label is the longest text
 * this button ever carries (German "Werbung wird geladen...") and it needs the
 * whole column, while the ad it announces is about to cover the screen anyway. */
.mf-btn-ad.mf-btn-wait::before {
  content: none;
}

/* Row holding the single mock ad offer above the action buttons */
.mf-ad-row {
  display: flex;
  margin-top: 10px;
}

.mf-ad-row > .mf-btn {
  flex: 1 1 0;
}

/* Row of buttons sharing one line, each grows to fill its share */
.mf-btn-row {
  display: flex;
  gap: 10px;
}

.mf-btn-row:empty {
  display: none;
}

.mf-btn-row > .mf-btn {
  flex: 1 1 0;
}

/* Game-over actions can run to four buttons (watch-ad / play again / share /
 * exit). Let the row wrap and give the wide gold watch-ad button its own full
 * line so the long "watch ad: 2x coins" label never wraps to three lines and
 * crushes its neighbours. Scoped to the game-over card so the revive overlay's
 * own .mf-btn-ad (a two-button row) is untouched. */
.mf-gameover-actions {
  flex-wrap: wrap;
}

.mf-gameover-actions > .mf-btn-ad {
  flex: 1 1 100%;
}

/* Compact per-level reward screen: stack the two buttons full-width with a small
 * gap so the gold Nx coin grab leads and the continue button sits right under it. */
.mf-gameover-compact {
  gap: 10px;
}

.mf-gameover-compact > .mf-btn {
  flex: 1 1 100%;
}

/* Quiet text button for secondary navigation links. It lays out an optional
 * leading icon and a label on one centered row, the icon sitting just left of
 * the text with a small gap */
.mf-link-btn {
  flex: 1 1 calc(50% - 4px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 48px;
  padding: 11px 10px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--mf-accent);
  background: rgba(184, 168, 216, 0.07);
  border: 1px solid var(--mf-card-border);
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s ease;
}

.mf-link-btn:hover {
  background: rgba(184, 168, 216, 0.15);
}

/* A slightly more prominent secondary button for the two feature links
 * (Leaderboard and All Games). It keeps the quiet link footprint but reads
 * one step louder with an accent-tinted fill and a clearer accent border */
.mf-link-btn-feature {
  color: var(--mf-ink);
  background: rgba(184, 168, 216, 0.14);
  border-color: rgba(184, 168, 216, 0.4);
}

.mf-link-btn-feature:hover {
  background: rgba(184, 168, 216, 0.22);
}

/* A coming-soon link: the feature is not live yet, so the entry stays visible
 * but reads as inert. It dims, drops any hover lift and shows a not-allowed
 * cursor so a tap obviously does nothing. Listed for hover too so it overrides
 * the feature link's brighter hover when a shop link is both feature and soon */
.mf-link-btn-soon,
.mf-link-btn-soon:hover {
  opacity: 0.45;
  cursor: not-allowed;
  background: rgba(184, 168, 216, 0.08);
  border-color: rgba(184, 168, 216, 0.18);
}

/* Row that wraps the panel link buttons and the mute toggle */
.mf-links-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* The three standard links - board, wardrobe, goals - share ONE row, three across.
 * They are the same three marks the hub card carries in the same order, so they must
 * read as one set rather than as a 2x2 grid that splits them up. Three captions across
 * a phone card is tight, so the trio stacks its icon over its label and drops a size;
 * a game that ships no wardrobe simply splits the row between the two it has.
 *
 * The 25% basis is what RESERVES the row for the set. A zero basis takes no width in
 * the wrap calculation, so any half-width link declared after the trio - the second
 * progress ladder a game like merge-manor ships - joined the trio's line and squeezed
 * three captions into a quarter of the card. At a quarter each the three claim three
 * quarters of the line, so the next half-width link cannot fit and wraps; the grow
 * factor still spreads them across the full width, so a game with two or three trio
 * links looks exactly as it did. */
.mf-link-btn-trio {
  flex: 1 1 25%;
  min-width: 0;
  flex-direction: column;
  gap: 4px;
  padding: 11px 4px;
  font-size: 12px;
  line-height: 1.2;
  text-align: center;
}

/* How to play takes its own full width row beneath the trio: it is the one link that
 * is not part of the set, and giving it the whole line keeps the trio unambiguous. */
.mf-link-btn-full {
  flex: 1 0 100%;
}

/* The more games cross promotion button, an anchor styled as a link button.
 * It resets the anchor underline and centers its label like the other link
 * buttons so it sits cleanly among them and on the game over card */
.mf-more-btn {
  text-decoration: none;
  color: var(--mf-accent);
}

.mf-more-btn:hover {
  text-decoration: none;
}

/* The standalone more games row under the game over actions, one centered
 * link button that fills the card width like the action buttons above it */
.mf-more-row {
  display: flex;
}

.mf-more-row > .mf-more-btn {
  flex: 1 1 auto;
}

/* ***************************************************************
 * Toggle group (difficulty and mode pickers)
 * *************************************************************** */

.mf-toggle-group {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: rgba(16, 19, 28, 0.5);
  border: 1px solid var(--mf-faint);
  border-radius: 11px;
}

.mf-toggle {
  flex: 1 1 0;
  min-height: 40px;
  padding: 9px 4px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--mf-ink-dim);
  background: transparent;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.mf-toggle:hover {
  color: var(--mf-ink);
}

/* Selected segment stands out in the accent */
.mf-toggle-active {
  color: var(--mf-bg);
  background: var(--mf-accent);
}

.mf-toggle-active:hover {
  color: var(--mf-bg);
}

/* The small inline icon (a globe, target, trophy, and so on) sitting left of a
 * link button label. It is sized to about a quarter of a square and inherits
 * the button color through currentColor so it always matches its label */
.mf-link-icon {
  flex: 0 0 auto;
  display: block;
  width: 16px;
  height: 16px;
  color: currentColor;
}

/* A compact icon-only button used for wordless menu controls like the mute
 * toggle; the svg inside inherits the button color through currentColor */
.mf-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  color: var(--mf-ink-dim);
  background: rgba(184, 168, 216, 0.07);
  border: 1px solid var(--mf-card-border);
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.mf-icon-btn:hover {
  color: var(--mf-ink);
  background: rgba(184, 168, 216, 0.15);
}

/* The mute toggle pinned to the top-right corner of the main menu card */
.mf-mute-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
}

/* The how-to shortcut pinned to the top-left corner of the paused card */
.mf-howto-btn {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
}

/* The back-to-hub control pinned to the top-left corner of the main menu card,
 * mirroring the mute on the right so a thumb never lands on both at once. It
 * gives every platform a visible way back to the arcade even where there is no
 * native back chrome (the Android and iOS wrappers). */
.mf-back-btn {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
}

/* The two TOP-CORNER controls of the MAIN MENU card - back to the hub on the
 * left, mute on the right - are the ones a player reaches for without looking,
 * so they get a much bigger tap target than the compact base size. Two things
 * keep the growth from leaking. It sits on these two classes and never on
 * .mf-icon-btn, so the how-to shortcut stays 36px; and it is scoped to
 * #menu-root, because the paused card carries its own .mf-mute-btn and every
 * control on that card must keep the size it has.
 * 64px, not 72: a headless sweep of the menu card across phone viewports showed
 * 72px squares reaching into the centred title and subtitle on a 360px wide
 * screen, which is a very common cheap-Android size. 64px clears both there and
 * matches the .mf-icon-btn-lg pause controls, so the two big sizes agree. */
#menu-root .mf-back-btn,
#menu-root .mf-mute-btn {
  width: 64px;
  height: 64px;
  border-radius: 14px;
}

/* Grow the glyph with the button. The shared .mf-link-icon rule pins every menu
 * icon at 16px, which reads as a lost speck inside a 64px square. */
#menu-root .mf-back-btn .mf-link-icon,
#menu-root .mf-mute-btn .mf-link-icon {
  width: 32px;
  height: 32px;
}

/* A larger icon button for the pause menu controls, brighter than the subtle
 * menu toggles since these are the main interaction on the paused card */
.mf-icon-btn-lg {
  width: 64px;
  height: 64px;
  border-radius: 14px;
  color: var(--mf-ink);
}

/* The safe primary control (resume) carries the accent fill so it stands out */
.mf-icon-btn-primary {
  color: var(--mf-bg);
  background: var(--mf-accent);
  border-color: var(--mf-accent);
}

.mf-icon-btn-primary:hover {
  color: var(--mf-bg);
  background: var(--mf-accent);
}

/* The centered row of icon controls on the paused card. It grows to fill the
 * button row so justify-content can truly center the icons rather than letting
 * the shrink-to-content row hug the left edge */
.mf-pause-actions {
  display: flex;
  flex: 1 1 auto;
  justify-content: center;
  gap: 18px;
  flex-wrap: wrap;
}

/* The inline how-to text revealed by the corner icon on the paused card */
.mf-pause-howto {
  text-align: left;
}

.mf-pause-howto p {
  margin: 7px 0;
}

/* Size every pause icon uniformly regardless of its own svg attributes */
.mf-pause-actions svg {
  width: 30px;
  height: 30px;
}

/* The paused card stacks its controls in two tiers, so the action group runs
 * as a column: resume on its own row above the quieter restart and menu row */
.mf-pause-actions {
  flex-direction: column;
  gap: 12px;
}

/* One tier of the paused controls. The primary tier holds resume alone and the
 * secondary tier splits the two destructive choices evenly across the width */
.mf-pause-row {
  display: flex;
  gap: 12px;
}

/* The secondary tier now carries up to four choices - restart, end run, exit to
   menu and, inside the hub, exit to the arcade - so it wraps instead of squeezing
   four captions into a phone-width card. The basis is the width one action needs to
   read on the narrowest phone: three still share one line on a normal card, and a
   fourth drops to a second line of two rather than clipping every label. */
.mf-pause-row-secondary {
  justify-content: center;
  flex-wrap: wrap;
}

.mf-pause-row-secondary .mf-icon-btn-action {
  flex: 1 1 84px;
  min-width: 84px;
}

/* Each action button stacks its glyph over a short caption, so a bare arrow or
 * home glyph always reads with a plain word under it. It grows to share its row
 * with any siblings and keeps a comfortable thumb-sized minimum height */
.mf-icon-btn-action {
  flex: 1 1 0;
  flex-direction: column;
  gap: 6px;
  width: auto;
  min-height: 72px;
  padding: 12px 8px;
  border-radius: 14px;
  color: var(--mf-ink);
}

/* The resume tier reads as the loud default, taller than the secondary pair */
.mf-pause-row-primary .mf-icon-btn-action {
  min-height: 84px;
}

/* The glyph slot keeps the icon centered above the caption in the column */
.mf-action-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The caption under the glyph names the action in plain words */
.mf-action-label {
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
}

/* The inline confirm step shown over the action tiers before a destructive
 * pause action. It is a tight centered column with the prompt above its row */
.mf-pause-confirm {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: stretch;
}

/* The confirm prompt sits a touch bolder than a plain subtitle so the question
 * reads as the focus of this step */
.mf-pause-confirm-title {
  font-weight: 600;
  color: var(--mf-ink);
}

/* Cancel and the bold accept share one row, each filling half its width */
.mf-pause-confirm-row {
  display: flex;
  gap: 10px;
}

.mf-pause-confirm-row > .mf-btn {
  flex: 1 1 0;
}

/* ***************************************************************
 * Language picker
 * *************************************************************** */

/* The drop-down list of shipped languages, opened from the language link
 * button; a simple full width column that flows under the links row */
.mf-lang-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  padding: 8px;
  background: rgba(16, 19, 28, 0.5);
  border: 1px solid var(--mf-faint);
  border-radius: 11px;
}

/* One language option, each labeled in its own native script */
.mf-lang-option {
  min-height: 44px;
  padding: 10px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  color: var(--mf-ink-dim);
  background: rgba(184, 168, 216, 0.07);
  border: 1px solid var(--mf-card-border);
  border-radius: 9px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.mf-lang-option:hover {
  color: var(--mf-ink);
  background: rgba(184, 168, 216, 0.15);
}

/* The currently active language stands out in the accent */
.mf-lang-active {
  color: var(--mf-bg);
  background: var(--mf-accent);
}

.mf-lang-active:hover {
  color: var(--mf-bg);
  background: var(--mf-accent);
}

/* ***************************************************************
 * How to scoring guide
 * *************************************************************** */

/* Heading above the scoring guide grid */
.mf-howto-score-title {
  margin-top: 6px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--mf-ink-dim);
}

/* Column of scoring rows, each an icon, a name and its points */
.mf-howto-scores {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* One scoring row: icon on the left, name in the middle, points on the right */
.mf-howto-score-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: rgba(16, 19, 28, 0.5);
  border: 1px solid var(--mf-faint);
  border-radius: 10px;
}

/* The sprite icon canvas, a fixed square that never shrinks */
.mf-howto-score-icon {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
}

/* The item name fills the remaining width. min-width 0 lets the flex item shrink
   below its content width, and overflow-wrap breaks a long word, so a long name
   wraps onto the next line instead of overflowing the row sideways. */
.mf-howto-score-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: break-word;
  font-size: 14px;
  font-weight: 600;
  color: var(--mf-ink);
}

/* The point value reads in the gain green so the reward pops */
.mf-howto-score-value {
  flex: 0 0 auto;
  font-size: 15px;
  font-weight: 800;
  color: var(--mf-up);
}

/* A worthless or penalty item dims its value in the danger tone */
.mf-howto-score-zero {
  color: var(--mf-danger);
}

/* A non-scoring item shows a localized effect phrase instead of a short number,
   so its slot must be allowed to shrink and WRAP to the next line rather than
   overflow the row sideways. This overrides the fixed value slot above (declared
   later so it wins) only for the effect label. */
.mf-howto-score-effect {
  flex: 0 1 auto;
  min-width: 0;
  white-space: normal;
  overflow-wrap: break-word;
  text-align: right;
  font-size: 12px;
  font-weight: 700;
  color: var(--mf-ink-dim);
}

/* ***************************************************************
 * Stats row
 * *************************************************************** */

.mf-stats-row {
  display: flex;
  gap: 8px;
}

.mf-stat {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 10px 6px;
  background: var(--mf-surface-flat);
  border: 1px solid var(--mf-faint);
  border-radius: 10px;
}

/* First line of a stat is its big value, the label is the dim second line */
.mf-stat strong {
  font-size: 20px;
  font-weight: 700;
  color: var(--mf-ink);
}

.mf-stat span {
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--mf-ink-dim);
}

/* The all-time podium cell that took the retired coin slot on the entry panel. It is
 * a normal stat cell, so it keeps the strip's two-up rhythm and its trailing dim label,
 * but its body is three compact standings instead of one big number. The rows are laid
 * out tight and centred so three of them stand roughly as tall as the single 18px value
 * in the Best cell beside it and the strip stays level. */
.mf-stat-podium {
  justify-content: center;
  gap: 4px;
  /* the podium carries three names and three scores where its neighbours carry one
   * number each, so it claims a larger share of the strip. Without this a game that
   * also shows a Level cell squeezes the podium to a third of the row and the names
   * clip to three characters, which reads as noise rather than as a leaderboard. */
  flex-grow: 1.9;
}

/* The scope caption, sitting ABOVE the three names so the player reads which board
 * they are looking at before the names, not after them. It is also the tap target
 * that walks the three time windows, so it carries a pointer cursor. */
.mf-podium-scope {
  order: -1;
}

/* The whole podium cell is tappable, so it shows the same affordance a button does. */
.mf-stat-podium {
  cursor: pointer;
  user-select: none;
}

/* The stack of three standings. */
.mf-podium-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
}

/* One standing: place, then the name taking the slack, then the score. The row is a
 * grid rather than a flex line so the place column and the score column hold the same
 * width on all three rows and the names cannot push the scores out of alignment. */
.mf-podium-row {
  display: grid;
  grid-template-columns: 11px 1fr auto;
  align-items: baseline;
  gap: 6px;
  line-height: 1.35;
}

/* The three row spans must UNDO the stat-cell caption styling. The `.mf-stat span`
 * rule above turns every span in a stat cell into a 10px uppercase letter-spaced
 * label, which is right for a caption like BEST and very wrong for a player name:
 * it shouted the names in capitals and squeezed them to caption size. These rules
 * are written with the .mf-stat prefix on purpose so they outrank it. */
.mf-stat .mf-podium-rank,
.mf-stat .mf-podium-name,
.mf-stat .mf-podium-score {
  font-size: 12px;
  text-transform: none;
  letter-spacing: 0;
}

/* The standing number, dim so the name and the score lead the row. */
.mf-stat .mf-podium-rank {
  color: var(--mf-ink-dim);
  font-weight: 700;
  text-align: right;
}

/* The player name, in the casing the player actually chose. It is untrusted text of
 * any length, so it clips with an ellipsis rather than wrapping and pushing the cell
 * taller than the Best cell beside it. */
.mf-stat .mf-podium-name {
  color: var(--mf-ink);
  font-weight: 500;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The score, the one number in the row that carries weight. Tabular figures keep the
 * three scores right-aligned on a common digit grid instead of drifting. */
.mf-stat .mf-podium-score {
  color: var(--mf-ink);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* A tiny dim glyph sitting above the stat value (a crown on Best, a coin on
 * Coins). It is quiet so the number still leads the cell, and it tints itself
 * through currentColor to match the dim label tone */
.mf-stat-icon {
  display: block;
  width: 19px;
  height: 19px;
  color: var(--mf-ink-dim);
}

/* size the inner svg to the cell, a notch larger than the shared link-icon
 * default so the crown and currency tokens read clearly above the value */
.mf-stat-icon svg {
  width: 19px;
  height: 19px;
}

/* the premium gold lift for the crown and star marquee icons. Scoped under
 * .mf-stat so it outranks the .mf-stat span label color rule that also catches
 * this span; the plain single class would tie on specificity and lose */
.mf-stat .mf-stat-icon-gold,
.mf-stat .mf-stat-icon-gold svg {
  color: var(--mf-gold);
}

/* a real collectible art token sits a touch larger than the flat glyph so the
 * sprite detail reads, and contains itself so it never distorts */
.mf-stat-icon-art {
  width: 22px;
  height: 22px;
}

.mf-stat-icon-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* ***************************************************************
 * Lists (buyable, progress, leaderboard, howto rows)
 * *************************************************************** */

.mf-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}



/* The board list shows ten rows at most; the rest scrolls inside the card so
   the header, tabs and back action stay put while the player browses ranks */
.mf-board-list {
  max-height: 552px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Slim on-brand scrollbar for the kit's scrolling surfaces. The thumb derives
   from the accent token, so the day and night themes both stay on palette
   instead of falling back to the platform's default chrome */
.mf-card,
.mf-board-list {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--mf-accent) 38%, transparent) transparent;
}

.mf-card::-webkit-scrollbar,
.mf-board-list::-webkit-scrollbar {
  width: 6px;
}

.mf-card::-webkit-scrollbar-track,
.mf-board-list::-webkit-scrollbar-track {
  background: transparent;
}

.mf-card::-webkit-scrollbar-thumb,
.mf-board-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--mf-accent) 38%, transparent);
  border-radius: 999px;
}

.mf-card::-webkit-scrollbar-thumb:hover,
.mf-board-list::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--mf-accent) 60%, transparent);
}

/* The wardrobe standardised on eight rows per game, and at the old 12px padding
 * that list ran past the bottom of a phone: the player had to scroll a panel whose
 * whole job is "look at all your costumes at once". Everything here is tightened
 * just enough that eight rows, the coin line and the back button fit one screen -
 * the padding, the gap and the art slot - while the trailing control keeps its
 * 48px touch height, which is the one measurement that must not shrink. */
.mf-list-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--mf-surface-flat);
  border: 1px solid var(--mf-faint);
  border-radius: 11px;
}

/* Every row ends in ONE column of the same size, whatever the row's unlock kind:
 * a coin buy, an ad lease, an invite counter and a state chip all share this box,
 * so a wardrobe of mixed rows reads as one list with one right edge instead of
 * four ragged ones. Without it the control was flex: 0 1 auto and simply took the
 * width of its own label, which made the ad button about four times the width of
 * the Equip button next to it. 116px is what the longest shipped label needs
 * ("Equipaggia", "Echipează") at the 14px below, and it still leaves the 46px
 * preview and the label block their room on a 320px phone. The 48px floor is the
 * touch target every button already had, repeated so the chips match it exactly. */
.mf-list-row > .mf-btn,
.mf-list-row > .mf-active-chip {
  flex: 0 0 116px;
  width: 116px;
  min-height: 48px;
}

/* Row buttons drop from the 16px chrome size to the row's own 14px and give back
 * six pixels of side padding: inside a fixed column the label has to fit, and it
 * reads better next to the 14px item name than the full-width overlay size does.
 * Nowrap plus a clip keeps a long label from wrapping to a second line, which
 * would push the row past the 48px height the whole list is aligned on. */
.mf-list-row > .mf-btn {
  padding: 12px 10px;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
}

/* The row main label grows to fill the space between art and trailing control */
.mf-list-label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--mf-ink);
}

/* Stacked label plus hint wrapper inside a row */
.mf-list-desc {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.mf-list-desc .mf-list-label {
  flex: 0 0 auto;
}

/* Secondary hint text, also used for right aligned price and status notes */
.mf-list-hint {
  font-size: 12px;
  color: var(--mf-accent);
}

/* Small square art slot for a buyable row preview */
.mf-item-preview {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: rgba(16, 19, 28, 0.5);
  border: 1px solid var(--mf-faint);
  overflow: hidden;
}

/* The sprite the shop draws into the preview box, scaled to fill the slot */
.mf-item-preview-art {
  display: block;
  width: 100%;
  height: 100%;
}

/* A locked preview dims until the item is owned */
.mf-item-locked {
  filter: grayscale(1);
  opacity: 0.5;
}

/* The equipped or maxed state chip. It fills the same fixed column as the row
 * buttons above, so it is centered on both axes rather than sitting as a small
 * inline pill; at that size the old 10px text looked lost inside the box, so the
 * chip reads at 11px with the padding grown to fill the column. */
.mf-active-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 9px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  text-align: center;
  color: var(--mf-up);
  background: rgba(14, 203, 129, 0.14);
  border: 1px solid rgba(14, 203, 129, 0.35);
  border-radius: 999px;
}

/* The chip on a costume worn under a watched-ad lease, showing how much of the
 * day is left on it. Tinted in the gold ad tone, not the green owned tone, so a
 * borrowed costume never reads as one the coin shop sold. */
.mf-chip-timed {
  color: #f0b429;
  background: rgba(240, 180, 41, 0.14);
  border-color: rgba(240, 180, 41, 0.4);
  /* the chip carries a time span ("23 hr", "23 sa", "45 min"), not a state word, so
     it drops the chips' uppercase shout: an upper-cased unit reads as an acronym */
  font-size: 12px;
  letter-spacing: 0.2px;
  text-transform: none;
}

/* The counter on an invite item still short of its friends goal. It is a locked
 * gate, not an earned state, so it drops the green for the quiet dim ink; the
 * sentence that explains the goal rides the row's hint line and the chip's
 * aria-label, leaving the box to the count alone. */
.mf-chip-invite {
  color: var(--mf-ink-dim);
  background: rgba(184, 168, 216, 0.08);
  border-color: var(--mf-faint);
  /* a bare count, so the same normal-case treatment the lease chip takes */
  font-size: 12px;
  letter-spacing: 0.2px;
  text-transform: none;
}

/* Progress row state icon, a hollow circle until the goal is reached */
.mf-progress-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--mf-ink-dim);
  opacity: 0.5;
}

.mf-progress-icon svg {
  width: 22px;
  height: 22px;
}

/* A completed progress row fills its circle in the up green */
.mf-progress-done {
  color: var(--mf-gold);
  opacity: 1;
}

/* Tier pip container for tiered buyables */
.mf-dots {
  display: flex;
  gap: 5px;
}

.mf-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid var(--mf-ink-dim);
}

/* An owned tier fills the pip with the accent */
.mf-dot-filled {
  background: var(--mf-accent);
  border-color: var(--mf-accent);
}

/* ***************************************************************
 * Leaderboard board rows
 * *************************************************************** */

.mf-board-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  font-size: 14px;
  background: var(--mf-surface-flat);
  border: 1px solid var(--mf-faint);
  border-radius: 10px;
}

/* Rank number leads the row */
.mf-board-rank {
  flex: 0 0 24px;
  font-weight: 700;
  color: var(--mf-ink-dim);
}

/* Round avatar between the rank and the name: the profile photo layers over a
   colored initial, so a missing or broken image falls back to the letter */
.mf-board-avatar {
  position: relative;
  flex: 0 0 30px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  color: #fff;
}

.mf-board-avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Player name fills the middle and ellipsizes */
.mf-board-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--mf-ink);
}

/* Score trails the row */
.mf-board-score {
  flex: 0 0 auto;
  font-weight: 700;
  color: var(--mf-ink);
  font-variant-numeric: tabular-nums;
}

/* Small level badge between the name and the score, for games with a level ladder.
   The up-triangle drawn before the number reads it as the player's climbed level. */
.mf-board-level {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--mf-accent-soft);
  color: var(--mf-accent);
  border: 1px solid var(--mf-accent);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.mf-board-level::before {
  content: '';
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 6px solid currentColor;
}

/* The signed in player's own row is highlighted in the accent */
.mf-board-me {
  background: var(--mf-accent-soft);
  border-color: var(--mf-accent);
}

.mf-board-me .mf-board-rank {
  color: var(--mf-accent);
}

/* ***************************************************************
 * Leaderboard watch-a-run control
 * *************************************************************** */

/* The watch control that closes a row whose run the relay STILL HOLDS a replay for.
   Almost no row carries one, so it is styled as a quiet outline rather than a second
   score-weight element: it must not compete with the figure it sits next to. It is
   deliberately narrow (24px) because the row already carries a rank, an avatar, a name
   and a score; the name is the only flexible element left and ellipsizes, so on a 320px
   phone the row stays on one line instead of wrapping. */
.mf-board-watch {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--mf-accent);
  background: transparent;
  color: var(--mf-accent);
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* A play triangle looks left-heavy when centred by its bounding box, so it is nudged a
   hair right to sit optically centred inside the circle. */
.mf-board-watch-glyph {
  display: block;
  transform: translateX(1px);
}

/* Held down the outline fills, so the tap is acknowledged before the read returns */
.mf-board-watch:active {
  background: var(--mf-accent-soft);
}

/* Reading the proof: the control is disabled (a replay can be a few hundred kilobytes
   and a second tap would start a second read) and its glyph is cleared so the ring below
   shows in its place. A control that just sat there disabled would read as dead. */
.mf-board-watch:disabled {
  cursor: default;
}

.mf-board-watch-busy::after {
  content: '';
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: mf-board-watch-spin 0.7s linear infinite;
}

@keyframes mf-board-watch-spin {
  to {
    transform: rotate(360deg);
  }
}

/* A player who asked for less motion still has to see that something is happening, so
   the ring stays and only the spin goes, fading in place instead. The fade keyframes are
   declared here rather than reusing the shell's mf-pulse because this rule set also ships
   standalone to the hub, where mf-pulse is not defined. */
@media (prefers-reduced-motion: reduce) {
  .mf-board-watch-busy::after {
    animation: mf-board-watch-fade 1.2s ease-in-out infinite;
  }
}

@keyframes mf-board-watch-fade {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}

/* The board title's inline trophy. SINGLE SOURCE: these three rules are the
   canonical leaderboard-title styling, kept byte-identical with the hub copy in
   player/styles/hub.css (.arcade-hub .mf-board-title*) and kit/leaderboard-shared.css.
   Without them here the in-game board (which only links this shell.css) falls back
   to the panel's white ink with the stroke="currentColor" trophy sitting unaligned
   at the top-left; with them the in-game header reads exactly like the hub board:
   a gold trophy inline right before the centered game-name title. */
.mf-board-title {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.mf-board-title-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  color: var(--mf-gold);
}

.mf-board-title-icon svg {
  display: block;
  width: 22px;
  height: 22px;
}

/* ***************************************************************
 * Overlays (pause, revive, game over)
 * *************************************************************** */

.mf-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(16px + var(--mf-safe-top, 0px))
    calc(16px + var(--mf-safe-right, 0px))
    calc(16px + var(--mf-safe-bottom, 0px))
    calc(16px + var(--mf-safe-left, 0px));
}

/* Dim backdrop behind an overlay card */
.mf-dim {
  position: absolute;
  inset: 0;
  background: rgba(8, 10, 16, 0.72);
  backdrop-filter: blur(3px);
}

/* The overlay card floats above the dim and reuses the menu card look */
.mf-overlay .mf-card {
  position: relative;
  z-index: 1;
  margin: 0;
}

/* Floating pause control anchored to the top right of the stage. It no longer
 * spells out its own offsets: the shared --mf-pause-* tokens on .mf-shell own
 * the anchor, so this rule and every control hanging off it move together. */
.mf-pause-btn {
  position: absolute;
  top: var(--mf-pause-top);
  right: var(--mf-pause-right);
  z-index: 20;
  width: var(--mf-pause-size);
  height: var(--mf-pause-size);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  letter-spacing: 1px;
  color: var(--mf-ink);
  background: rgba(16, 19, 28, 0.6);
  border: 1px solid var(--mf-card-border);
  border-radius: 50%;
  cursor: pointer;
  backdrop-filter: blur(4px);
}

.mf-pause-btn:active {
  background: rgba(16, 19, 28, 0.85);
}

/* THE LEFT CLUSTER on the pause button's line: one flex row pinned to the EXACT
   same anchor tokens as the pause button but on the left, so everything it holds
   always sits on the pause button's line, guaranteed, even when mobile Telegram
   pushes the chrome down. Reading --mf-pause-top rather than repeating its calc
   is what makes moving the pause button carry the whole cluster along.

   It exists because the two readouts that live here - the lives hearts and the
   corner stat - each used to pin this corner themselves, so the two games that
   show BOTH (petal-trail, lasso-sheep) drew the stat straight on top of the
   hearts. Order is fixed in css rather than by mount order, so the hearts lead
   whichever readout mounted first. */
.mf-left-hud {
  position: absolute;
  top: var(--mf-pause-top);
  left: max(12px, var(--mf-safe-left, 0px));
  height: var(--mf-pause-size);
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}

/* The shared lives readout (hearts), a row inside the cluster above. */
.mf-lives-hud {
  order: 1;
  height: 100%;
  display: flex;
  align-items: center;
  gap: 5px;
  pointer-events: none;
}

.mf-lives-hud .mf-life {
  display: block;
}

.mf-lives-hud .mf-lives-count {
  font: 800 26px system-ui, -apple-system, sans-serif;
  color: currentColor;
  margin-right: 3px;
}

.mf-lives-hud.mf-hidden {
  display: none;
}

/* ***************************************************************
 * The in-run HUD layer switch
 * *************************************************************** */

/* Blocks the WHOLE in-run HUD layer (tier 20 plus its transients) while a screen
   is raised over the run: the paused card, the revive offer, the game over card,
   or any menu panel. The app host sets this class on the shell through its
   setRunHudVisible helper, which owns the same list on the script side.

   WHY A CLASS ON THE SHELL AND NOT JUST THE HIDDEN FLAG. Hiding these nodes one by
   one does not hold, because the jeton pill re-reveals ITSELF: the shared score HUD
   pushes the live count from inside the game's own draw hook and drops the hidden
   class whenever it finds it, and draw keeps running behind a raised screen - one
   frozen frame after a pause, and every frame of the attract demo that plays behind
   the menu. So the pill was hidden and back on top a frame later, floating over the
   leaderboard it was meant to be under. A display rule the HUD's class juggling
   cannot reach is what actually ends that race.

   It uses display rather than a lower z-index deliberately: these readouts are
   FURNITURE FOR A LIVE RUN, and a stale count dimmed to 28% behind a card is still
   a wrong number on screen. Two tier neighbours are deliberately NOT listed: the
   pause button, which the host already hides directly on every one of these
   transitions, and the rewarded-ad status line, which exists precisely to caption an
   ad started FROM one of these raised cards and so must stay legible over them. */
#game-shell.mf-run-hud-off .mf-lives-hud,
#game-shell.mf-run-hud-off .mf-wave-banner,
#game-shell.mf-run-hud-off .mf-corner-stat,
#game-shell.mf-run-hud-off .mf-coin-hud {
  display: none;
}

/* The in-run JETON (coin) readout, a DOM pill hung directly UNDER the pause
   button and anchored to the same tokens, so the pill follows the button
   wherever it moves. It used to be painted on the canvas in the centre column
   under the big score, where it competed with the score for the eye and drifted
   with the per-device canvas scale. The look deliberately mirrors .mf-pause-btn
   - same translucent dark fill, same card border, same blur - so the two read as
   one corner cluster; only the radius softens to a pill instead of a circle. */
.mf-coin-hud {
  position: absolute;
  top: calc(var(--mf-pause-top) + var(--mf-pause-size) + 8px);
  right: var(--mf-pause-right);
  z-index: 20;
  height: 30px;
  padding: 0 10px 0 8px;
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--mf-ink);
  background: rgba(16, 19, 28, 0.6);
  border: 1px solid var(--mf-card-border);
  border-radius: 15px;
  backdrop-filter: blur(4px);
  pointer-events: none;
  line-height: 1;
}

/* The branded gold M jeton mark leading the pill, sized to the pill height */
.mf-coin-hud .mf-coin-mark {
  display: block;
  width: 20px;
  height: 20px;
}

/* The count beside the mark, gold and haloed so it stays legible over both the
   dark night chrome and any bright game art showing through the translucency */
.mf-coin-hud .mf-coin-count {
  font: 800 18px system-ui, -apple-system, sans-serif;
  color: var(--mf-gold);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}

.mf-coin-hud.mf-hidden {
  display: none;
}

/* The shared top-left corner stat (a DOM text readout), a row inside the left
   cluster, so a game's headline stat (e.g. hold-snake's distance) always sits on
   the pause button's line on every host - mobile and web Telegram alike -
   instead of drifting like a canvas-drawn number. It follows the hearts rather
   than sharing their corner: a game may show both. */
.mf-corner-stat {
  order: 2;
  height: 100%;
  display: flex;
  align-items: center;
  gap: 3px;
  pointer-events: none;
  line-height: 1;
}

.mf-corner-stat .mf-corner-stat-value {
  font: 800 30px system-ui, -apple-system, sans-serif;
}

.mf-corner-stat .mf-corner-stat-unit {
  font: 700 18px system-ui, -apple-system, sans-serif;
  color: #9cc3aa;
  opacity: 0.9;
}

/* compact variant: a quieter, smaller sub-stat (opt-in per game, e.g. miro-flight
   distance) - hold-snake and other games keep the bold headline default above */
.mf-corner-stat--compact {
  height: 40px;
}

.mf-corner-stat--compact .mf-corner-stat-value {
  font: 600 23px system-ui, -apple-system, sans-serif;
}

.mf-corner-stat--compact .mf-corner-stat-unit {
  font: 600 14px system-ui, -apple-system, sans-serif;
  color: inherit;
  opacity: 0.7;
}

.mf-corner-stat.mf-hidden {
  display: none;
}

/* The in-run watch-ad status line (engine/hud/ad-status.ts): the WORDS that go with
   the canvas watch-ad badge. The badge itself is language free - a film frame and a
   "+1" - which is what makes it cheap in thirty locales but also what makes it mute,
   so the sentence that explains the offer, the loading state and a watch that paid
   nothing lands here. DOM rather than canvas for the same reason as the lives row and
   the jeton pill: it is pinned to the pause row's own anchor tokens, so it sits on the
   same line on mobile Telegram, web Telegram and the native shell alike.

   WHERE IT SITS, AND WHY IT IS THE ONLY PLACE IT CAN SIT. It used to hang one row
   under the jeton pill, centred, on the theory that the row below the pill is free.
   It is not: that row is already the top of the BOARD. Measured headless at 360x640,
   the card landed at y 150-260 and covered merge-manor's entire top row (board top
   155), the drop column inside gumball-drop's jar, wall-breaker's first brick rows
   (field top 92), acorn-sweeper's top rows and nectar-sort's vase rims. Gumball-drop
   had already moved its own ad chips OUT of the jar for exactly that reason (see
   game-types.ts helperChipY) - the chips moved, this card did not. Four independent
   audits reported the same overlap.

   So it now lives in the ONE band the KIT owns in all seventy-six games: the pause
   line. Every game lays its board out under that line - the shared score HUD reads
   the real pause button and puts the big score on it (engine/hud/score-hud.ts
   pauseLineLogicalY), and the boards start under the score - so the strip can never
   be over a play field, whatever the per-game screenTop / screenBottom fill is. Three
   properties do the work:

     BOTTOM ANCHORED, not top anchored. translateY(-100%) pins the BOTTOM edge to the
     bottom of the pause row, so a three-line German or Russian sentence grows UP into
     the kit's own chrome instead of DOWN into the board. The old top anchor did the
     opposite: the longer the language, the further it reached into the game.

     FULL WIDTH, not shrink-to-fit. left/right insets replace the old left:50% +
     translateX(-50%), which silently capped the box at HALF the shell width (an
     absolutely positioned box with only `left` gets the space from left to the right
     edge) - that is why one sentence wrapped to five lines and grew 110px tall.

     RIGHT INSET CLEARS THE PAUSE BUTTON, so the one control the player may need
     during the line is never covered. The jeton pill sits on the row below and is
     never covered either.

   What it DOES cover for the two or three seconds it is up is the kit's own top
   strip - the big score, the best/rank line, the lives row - and that is the trade
   made on purpose: those are the arcade's own readouts, they come straight back, and
   the alternative is a tile the player cannot reach. */
.mf-ad-status {
  position: absolute;
  top: calc(var(--mf-pause-top) + var(--mf-pause-size));
  left: max(12px, var(--mf-safe-left, 0px));
  right: calc(var(--mf-pause-right) + var(--mf-pause-size) + 10px);
  transform: translateY(-100%);
  z-index: 21;
  padding: 7px 12px 7px 10px;
  display: flex;
  align-items: center;
  gap: 7px;
  border-radius: 14px;
  background: rgba(8, 11, 20, 0.9);
  border: 1px solid rgba(255, 215, 94, 0.45);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  color: #f3f5fb;
  font: 700 15px/1.25 system-ui, -apple-system, sans-serif;
  text-align: left;
  text-wrap: balance;
  pointer-events: none;
}

/* The same inline-SVG film strip the DOM ad buttons wear, as a pseudo-element so the
   line never needs innerHTML: one mark for every watch-ad surface in the arcade, DOM
   and canvas alike. */
.mf-ad-status::before {
  content: '';
  flex: 0 0 auto;
  display: block;
  width: 18px;
  height: 18px;
  background: center / contain no-repeat
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><rect x='2.5' y='3' width='19' height='18' rx='3' fill='%232a2300'/><g fill='%23ffd75e'><rect x='4' y='5.2' width='1.7' height='1.7' rx='.4'/><rect x='4' y='9' width='1.7' height='1.7' rx='.4'/><rect x='4' y='12.8' width='1.7' height='1.7' rx='.4'/><rect x='4' y='16.6' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='5.2' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='9' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='12.8' width='1.7' height='1.7' rx='.4'/><rect x='18.3' y='16.6' width='1.7' height='1.7' rx='.4'/><path d='M10 8.5 L10 15.5 L15.5 12 Z'/></g></svg>");
}

/* The four tones. Only the rim and the text colour move, so the line stays one shape
   the eye already knows: gold for the offer, dim for loading, green for a granted
   reward, red for a watch that paid nothing. */
.mf-ad-status--info {
  border-color: rgba(255, 215, 94, 0.5);
}

.mf-ad-status--wait {
  border-color: rgba(255, 255, 255, 0.22);
  color: #cfd6e6;
}

.mf-ad-status--good {
  border-color: rgba(69, 224, 111, 0.6);
  color: #d6ffe4;
}

.mf-ad-status--bad {
  border-color: rgba(255, 118, 118, 0.6);
  color: #ffdede;
}

.mf-ad-status.mf-hidden {
  display: none;
}

/* The transient centre wave/level banner: the game's menu mascot over a big label
   that pops in and fades out when the value increments. Cosmetic, no pointer. */
.mf-wave-banner {
  position: absolute;
  top: 38%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 25;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  text-align: center;
}

.mf-wave-banner .mf-wave-mascot {
  width: 116px;
  height: auto;
  display: block;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
}

.mf-wave-banner .mf-wave-label {
  font: 900 54px system-ui, -apple-system, sans-serif;
  color: currentColor;
  letter-spacing: 1px;
  text-shadow: 0 3px 12px rgba(0, 0, 0, 0.55);
}

.mf-wave-banner.mf-hidden {
  display: none;
}

.mf-wave-banner.mf-wave-banner-play {
  display: flex;
  animation: mf-wave-pop 1.8s ease forwards;
}

@keyframes mf-wave-pop {
  0% {
    opacity: 0;
    transform: translate(-50%, -42%) scale(0.85);
  }
  14% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.05);
  }
  26% {
    transform: translate(-50%, -50%) scale(1);
  }
  76% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -54%) scale(1);
  }
}

/* ***************************************************************
 * Game over card
 * *************************************************************** */

/* New record banner, celebratory in the up green */
.mf-newbest {
  align-self: center;
  padding: 6px 16px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--mf-up);
  background: rgba(14, 203, 129, 0.14);
  border: 1px solid rgba(14, 203, 129, 0.35);
  border-radius: 999px;
}

/* Grade band chip, neutral accent fill with the band letter as its text */
.mf-grade-chip {
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 56px;
  padding: 8px 18px;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: 1px;
  color: var(--mf-bg);
  background: var(--mf-accent);
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(184, 168, 216, 0.35);
}

/* Chip shown when the player used a revive this run */
.mf-revived-chip {
  align-self: center;
  padding: 4px 12px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--mf-accent);
  background: var(--mf-accent-soft);
  border: 1px solid var(--mf-card-border);
  border-radius: 999px;
}

/* The offline coin note under the summary rows. Borrows the revived chip's pill
   shape so the card keeps one vocabulary, but it carries a SENTENCE rather than a
   two word badge: the uppercasing and the letter spacing are dropped so a long
   locale (Ukrainian, Romanian, Bengali) stays readable, and it is allowed to wrap
   onto a second line instead of overflowing the card. Neutral ink rather than the
   accent, because it is an explanation and not an award. */
.mf-coin-offline-note {
  max-width: 100%;
  text-align: center;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
  color: var(--mf-ink-dim);
  background: var(--mf-surface-flat);
  white-space: normal;
}

/* The summary block on the game over card */
.mf-summary {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 14px;
  background: var(--mf-surface-flat);
  border: 1px solid var(--mf-faint);
  border-radius: 11px;
}

.mf-summary-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 14px;
  color: var(--mf-ink-dim);
}

/* Right aligned tally value on each summary line */
.mf-summary-value {
  font-size: 16px;
  font-weight: 700;
  color: var(--mf-ink);
  font-variant-numeric: tabular-nums;
}

/* The soft-currency value grouped with the branded jeton mark on the row's right
   edge: the amount, then the M jeton just to its right. */
.mf-summary-coinval {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.mf-summary-coin-icon {
  display: inline-flex;
}

.mf-summary-coin-icon svg {
  width: 15px;
  height: 15px;
  display: block;
}

/* The wardrobe/upgrade buy button's price mark: the SAME branded gold M jeton
   as .mf-summary-coin-icon above, reused so a cost reads identically whether it
   is quoted on the game-over card or on a buy button. Sized and blocked the
   same way; the button (.mf-btn) is already an inline-flex row with its own
   gap, so this rule only has to stop the svg from stretching the pill or
   drifting off the label's baseline. */
.mf-btn-coin-icon {
  display: inline-flex;
  flex: 0 0 auto;
}

.mf-btn-coin-icon svg {
  width: 15px;
  height: 15px;
  display: block;
}

/* ***************************************************************
 * Revive bar (countdown)
 * *************************************************************** */

.mf-revive-bar-track {
  width: 100%;
  height: 8px;
  background: rgba(16, 19, 28, 0.6);
  border: 1px solid var(--mf-faint);
  border-radius: 999px;
  overflow: hidden;
}

.mf-revive-bar {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--mf-accent), var(--mf-up));
  border-radius: 999px;
}

/* When this flag is added the fill drains over the revive window */
.mf-revive-bar-shrink {
  width: 0%;
  transition: width 5000ms linear;
}

/* ***************************************************************
 * Toast (one snackbar slot above the thumb zone)
 * *************************************************************** */

.mf-toast-root {
  position: absolute;
  left: 50%;
  bottom: max(16px, env(safe-area-inset-bottom));
  z-index: 40;
  transform: translateX(-50%);
  display: flex;
  justify-content: center;
  width: 100%;
  pointer-events: none;
  padding: 0 16px;
}

.mf-toast {
  max-width: 320px;
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 600;
  text-align: center;
  color: var(--mf-ink);
  background: rgba(16, 19, 28, 0.92);
  border: 1px solid var(--mf-card-border);
  border-radius: 999px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

/* The enter and visible state of a toast */
.mf-toast-show {
  opacity: 1;
  transform: translateY(0);
}

/* ***************************************************************
 * Mascot (optional menu art slot)
 * *************************************************************** */

/* The title screen's top band is a toolbar: the two 64px corner buttons live in it,
 * absolutely positioned, and the game title is centred between them. Before the studio
 * wordmark moved to the foot, that strip plus a smaller title happened to clear the
 * buttons; with the strip gone the heading rose into them and the tagline ran straight
 * underneath both. Rather than pad the whole card down by the button height and lose
 * that space, the heading now OWNS the band: it stands as tall as a button, and it
 * reserves a button-width gutter on each side so a long name wraps inside the gap
 * instead of sliding under a control. The title also scales with the viewport so a
 * three-word name still fits the narrower gutter on a small phone.
 * The gutter is 76px: a 64px button pushed in by a 12px inset. */
.mf-card-main .mf-title {
  min-height: 64px;
  padding: 0 76px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(19px, 5.6vw, 26px);
}

/* A short viewport keeps the same band, since the buttons do not shrink with it. */
body.mf-short .mf-card-main .mf-title {
  min-height: 64px;
  font-size: clamp(17px, 5vw, 20px);
}

/* The menu mascot is the panel's emotional hook and the one thing on the card that
 * is purely the game's own. It was a fixed 132px, sized for the worst case, so on a
 * modern tall phone it floated in the middle of a half empty card. It now scales with
 * the viewport between a floor that still reads on a small screen and a ceiling that
 * keeps it from swallowing the panel, with a viewport-height cap so a short-but-wide
 * window cannot push the Play button under the fold. */
.mf-mascot {
  display: block;
  width: clamp(150px, 66vw, 300px);
  /* FIXED slot height: the card must be the same height in all games, so the
   * slot may not depend on the sprite's aspect ratio. contain letterboxes each
   * mascot inside the box keeping its aspect - nothing is stretched. */
  height: clamp(180px, 28vh, 250px);
  object-fit: contain;
  margin: 0 auto;
  image-rendering: auto;
}

/* The game-over mascot is standardized to one fixed square box across all 16
   games so a tall or wide mascot webp no longer renders larger or smaller than
   the others. object-fit contain scales each mascot to fit the box keeping its
   aspect, so none is stretched and all read at the same size. */
.mf-gameover-mascot {
  width: 120px;
  height: 120px;
  object-fit: contain;
}

body.mf-short .mf-gameover-mascot {
  width: 96px;
  height: 96px;
}

/* The paused card reuses the menu mascot at a smaller size so it sits neatly
 * under the Paused title without crowding the action buttons below it. */
.mf-pause-mascot {
  width: 96px;
  height: 96px;
  margin: 2px auto 10px;
}

/* ***************************************************************
 * Arcade hub page (generated dist/index.html only)
 * *************************************************************** */

.mf-hub {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  /* Always reserve the scrollbar gutter so toggling Favorites (which shortens
   * the grid and would otherwise drop the scrollbar) never widens the content
   * box and zooms the cards and heading. Favorites is an in-place filter, not a
   * separate page, so the layout must stay rock steady across the toggle. */
  scrollbar-gutter: stable;
  padding: 24px 16px max(24px, env(safe-area-inset-bottom));
  background: linear-gradient(160deg, var(--mf-bg) 0%, var(--mf-bg-2) 100%);
}

.mf-hub-title {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  margin-bottom: 22px;
  text-align: center;
}

/* The brand lockup: the icon next to the Mire wordmark on one row */
.mf-hub-lockup {
  display: flex;
  align-items: center;
  gap: 9px;
}

.mf-hub-title img {
  width: auto;
}

/* The colored brand mark, the hero icon of the landing */
.mf-hub-mark {
  height: 30px;
}

/* The Mire Games wordmark beside the mark, now two-tone live text so it matches
 * the splash and dialog lockups and reads the brand colors true */
.mf-hub-word {
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.3px;
}

.mf-hub-title h1 {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--mf-ink);
}

/* The search + favorites toolbar above the game grid, pinned to the top so it
   stays reachable while the long game list scrolls under it */
.mf-hub-tools {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  gap: 10px;
  max-width: 720px;
  margin: 0 auto 16px;
  padding: 12px 0;
  align-items: center;
  background: var(--mf-bg);
  box-shadow: 0 8px 14px -10px rgba(0, 0, 0, 0.7);
}

/* The back control closes the arcade webview, sitting at the start of the bar */
.mf-hub-back {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  color: var(--mf-ink);
  background: var(--mf-surface);
  border: 1px solid var(--mf-card-border);
  border-radius: 12px;
  cursor: pointer;
}

.mf-hub-back:active {
  background: var(--mf-bg-2);
}

.mf-hub-search {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 42px;
  padding: 10px 14px;
  font-family: inherit;
  font-size: 15px;
  color: var(--mf-ink);
  background: var(--mf-surface);
  border: 1px solid var(--mf-card-border);
  border-radius: 12px;
  outline: none;
}

.mf-hub-search::placeholder {
  color: var(--mf-ink-dim);
}

.mf-hub-search:focus {
  border-color: var(--mf-accent);
}

.mf-hub-favchip {
  flex: 0 0 auto;
  min-height: 42px;
  padding: 0 14px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  color: var(--mf-ink-dim);
  background: var(--mf-surface);
  border: 1px solid var(--mf-card-border);
  border-radius: 12px;
  cursor: pointer;
}

.mf-hub-favchip.on {
  color: var(--mf-gold);
  border-color: var(--mf-gold);
}

/* The footer meta row keeps the favorite star and crown progress off the banner
   art, so neither collides with the game name baked into the image */
.mf-hub-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 28px;
  margin-top: 2px;
}

.mf-hub-card-art .mf-hub-meta {
  margin: 0 14px 8px;
}

/* The favorite star toggle anchors the left of the footer meta row */
.mf-hub-fav {
  order: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: -4px;
  padding: 0;
  color: var(--mf-ink-dim);
  background: transparent;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.12s ease, background 0.12s ease;
}

.mf-hub-fav.on {
  color: var(--mf-gold);
}

/* Hover styling only on real pointer devices, so a phone tap never leaves a
   sticky hover that overrides the gold favorited state */
@media (hover: hover) {
  .mf-hub-fav:hover {
    color: var(--mf-ink);
    background: rgba(255, 255, 255, 0.06);
  }

  .mf-hub-fav.on:hover {
    color: var(--mf-gold);
  }
}

.mf-hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  max-width: 720px;
  margin: 0 auto;
}

.mf-hub-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px;
  text-decoration: none;
  background: var(--mf-surface);
  border: 1px solid var(--mf-card-border);
  border-left: 3px solid var(--mf-accent);
  border-radius: var(--mf-radius);
  transition: transform 0.1s ease, background 0.15s ease, box-shadow 0.2s ease;
}

.mf-hub-card:hover {
  background: rgba(28, 34, 48, 0.96);
  transform: translateY(-2px);
}

.mf-hub-card strong {
  font-size: 15px;
  font-weight: 700;
  color: var(--mf-ink);
}

.mf-hub-card span {
  font-size: 12px;
  line-height: 1.4;
  color: var(--mf-ink-dim);
}

/* Art cards carry the hero banner edge to edge above the text block */
.mf-hub-card-art {
  padding: 0;
  gap: 0;
  overflow: hidden;
}

.mf-hub-card-art img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-bottom: 1px solid var(--mf-card-border);
}

.mf-hub-card-art strong {
  margin: 10px 14px 2px;
}

.mf-hub-card-art span {
  margin: 10px 14px 6px;
}

/* The overlaid, localizable game name on an art hub card. It replaces the title
 * that used to be painted into the hero webp, so the name now comes from the
 * string tables (i18n) and can be translated or restyled without redrawing art.
 * Pinned top-left over the hero, white with the game accent underline so it
 * reads like the old baked stamp. width:max-content keeps the underline tight
 * to the text. The more specific selector overrides the art-card strong margin. */
.mf-hub-card-art .mf-hub-card-title {
  position: absolute;
  top: 8px;
  left: 12px;
  margin: 0;
  padding: 0 0 4px;
  width: max-content;
  max-width: calc(100% - 24px);
  font-size: 15px;
  font-weight: 800;
  line-height: 1.15;
  color: #fff;
  letter-spacing: 0.2px;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.75), 0 0 2px rgba(0, 0, 0, 0.6);
  border-bottom: 3px solid var(--mf-card-accent, #fff);
  z-index: 2;
  pointer-events: none;
}

/* A soft dark scrim only behind cards that carry the overlaid title, so the
 * white name stays legible over a bright hero. Art-integral cards (no overlay)
 * are left untouched. :has keeps the scrim scoped to titled cards. */
.mf-hub-card-art:has(.mf-hub-card-title)::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 46%;
  background: linear-gradient(180deg, rgba(8, 11, 18, 0.62) 0%, rgba(8, 11, 18, 0) 100%);
  z-index: 1;
  pointer-events: none;
}

/* The crown progress reads as light gold text on the right of the footer meta
   row, no heavy pill so it stops crowding the card */
.mf-hub-crowns {
  order: 2;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 11px;
  font-weight: 800;
  color: var(--mf-gold);
}

/* A fully crowned game glows gold so a maxed game stands out in the grid */
.mf-hub-card-crowned {
  border-color: var(--mf-gold);
  box-shadow: 0 0 18px rgba(255, 215, 94, 0.45);
}

/* ***************************************************************
 * Invite panel
 * *************************************************************** */

/* The headline count of friends joined toward the reward */
.mf-invite-progress {
  font-size: 16px;
  font-weight: 800;
  color: var(--mf-ink);
}

/* The reward state line, dim while locked and gold once unlocked */
.mf-invite-reward {
  font-size: 13px;
  font-weight: 600;
  color: var(--mf-ink-dim);
}

.mf-invite-reward-on {
  color: var(--mf-gold);
}

/* The invite link box, selectable and wrapping so the full link is readable */
.mf-invite-link {
  padding: 10px 12px;
  font-size: 12px;
  word-break: break-all;
  user-select: all;
  color: var(--mf-accent);
  background: rgba(16, 19, 28, 0.5);
  border: 1px solid var(--mf-faint);
  border-radius: 10px;
}

/* ***************************************************************
 * Responsive (portrait only; desktop just keeps the centered column)
 * *************************************************************** */

/* Phones: let the menu card use the full width and scroll */
@media (max-width: 560px) {
  .mf-card {
    width: 100%;
    border-radius: 16px;
  }

  #menu-root {
    padding: 12px;
    align-items: stretch;
  }

  .mf-title {
    font-size: 21px;
  }
}

/* Very short stages: trim card padding so it never clips off screen */
@media (max-height: 420px) {
  .mf-card {
    padding-top: 14px;
    padding-bottom: 14px;
  }
}

/* ***************************************************************
 * Day mode hardcoded overrides (OPTIONAL warm parchment theme)
 * These rules patch the values that do NOT read the --mf-* tokens and so would
 * look broken on the light parchment background. Every selector here is gated
 * under :root[data-theme="day"] so NIGHT stays byte-identical; placed at the end
 * of the file so each override wins on source order against the matching dark
 * rule of equal specificity. The gold watch-ad button (.mf-btn-ad) and every
 * gold #ffd75e crown/medal/trophy semantic are intentionally left UNCHANGED.
 * *************************************************************** */

/* Subtitle was near-white (rgba(238,241,247,0.85)) - invisible on a light card */
:root[data-theme="day"] .mf-subtitle {
  color: #5c5850;
}

/* Primary CTA was a fixed lavender gradient; day reads as white-on-burgundy. The
   text color stays var(--mf-bg) (now near-white) which is correct on burgundy. */
:root[data-theme="day"] .mf-btn-primary {
  background: linear-gradient(135deg, #a8324f 0%, #880e4f 100%);
  box-shadow: 0 8px 20px rgba(136, 14, 79, 0.28);
}

:root[data-theme="day"] .mf-btn-primary:hover {
  background: linear-gradient(135deg, #b53d5a 0%, #98185c 100%);
}

/* Overlay scrim was a heavy dark veil; day uses a soft warm-grey wash */
:root[data-theme="day"] .mf-dim {
  background: rgba(44, 42, 38, 0.40);
}

/* Card drop shadow was a deep black; day uses a soft warm shadow */
:root[data-theme="day"] .mf-card {
  box-shadow: 0 10px 30px rgba(80, 60, 30, 0.12),
    0 2px 8px rgba(80, 60, 30, 0.06);
}

/* Corner-stat unit was a fixed mint green; day uses a warm dim grey */
:root[data-theme="day"] .mf-corner-stat .mf-corner-stat-unit {
  color: #716a60;
}

/* The watch-ad status line keeps its dark ground in day mode ON PURPOSE: it is the
   twin of the canvas badge, which is dark-on-any-board by design, and a parchment
   pill would break the pair apart. Only the shadow softens to the day palette. */
:root[data-theme="day"] .mf-ad-status {
  box-shadow: 0 6px 18px rgba(80, 60, 30, 0.18);
}

/* Toggle group track was a dark fill; day uses a flat parchment fill */
:root[data-theme="day"] .mf-toggle-group {
  background: #f1eee7;
  border-color: #e6e2d9;
}

/* New-best banner used fixed neon-green tints; day uses the muted day up-green */
:root[data-theme="day"] .mf-newbest {
  background: rgba(78, 125, 47, 0.12);
  border-color: rgba(78, 125, 47, 0.30);
}

/* Revive bar track was a dark fill; day uses a faint warm-grey track */
:root[data-theme="day"] .mf-revive-bar-track {
  background: rgba(44, 42, 38, 0.10);
}

/* Brand wordmark colors were tuned for dark; day uses readable-on-white tones */
:root[data-theme="day"] .mf-brand-mire {
  color: #d96f8a;
}

:root[data-theme="day"] .mf-brand-games {
  color: #b8b0c2;
}

/* Faint lavender washes on the icon and link buttons; day matches the burgundy */
:root[data-theme="day"] .mf-icon-btn,
:root[data-theme="day"] .mf-link-btn {
  background: rgba(136, 14, 79, 0.06);
}

/* The faint icon-btn wash above (specificity 0,3,0) would otherwise defeat the
   base .mf-icon-btn-primary accent fill (0,1,0) and wash out the Resume action.
   Restore the solid burgundy fill + white glyph for the primary, placed after the
   wash so equal-specificity source order wins. */
:root[data-theme="day"] .mf-icon-btn-primary,
:root[data-theme="day"] .mf-icon-btn-primary:hover {
  color: var(--mf-bg);
  background: var(--mf-accent);
  border-color: var(--mf-accent);
}

/* ***************************************************************
 * Day mode Step-4 audit overrides (additional hardcoded breakages)
 * Hardcoded dark fills / near-white text used on surfaces visible in day mode,
 * found by scanning the whole file; each repainted to the day palette. Gold
 * #ffd75e semantics
 * are intentionally excluded.
 * *************************************************************** */

/* Equipped/maxed chip used fixed neon-green tints; align to the day up-green */
:root[data-theme="day"] .mf-active-chip {
  background: rgba(78, 125, 47, 0.12);
  border-color: rgba(78, 125, 47, 0.30);
}

/* The lease and invite chips carry their own semantics (gold "borrowed for the
   day", dim "still locked"), but they are .mf-active-chip too, so the day rule
   just above outranks their own fill and border and would repaint both green.
   Restoring them here, after it, is what keeps a gold chip gold in day mode. */
:root[data-theme="day"] .mf-chip-timed {
  color: var(--mf-gold);
  background: rgba(240, 180, 41, 0.16);
  border-color: rgba(240, 180, 41, 0.45);
}

:root[data-theme="day"] .mf-chip-invite {
  background: rgba(52, 44, 66, 0.06);
  border-color: var(--mf-faint);
}

/* Item preview slot bg was a dark fill; day uses a flat parchment fill */
:root[data-theme="day"] .mf-item-preview {
  background: #f1eee7;
}

/* The floating pause button lives over the GAMEPLAY canvas, which is still dark in
   day mode (no in-game day repaint), so a bright white pill was glaring on the dark
   scene. Keep it a calm dark translucent pill with a light glyph in day too - the
   same comfortable treatment night uses over the same dark game. The icon color is
   forced light here because --mf-ink is dark in day and would vanish otherwise. */
:root[data-theme="day"] .mf-pause-btn {
  background: rgba(18, 22, 30, 0.5);
  color: #eef1f7;
  border-color: rgba(255, 255, 255, 0.16);
}

:root[data-theme="day"] .mf-pause-btn:active {
  background: rgba(18, 22, 30, 0.66);
}

/* The jeton pill hangs off the same button over the same still-dark gameplay
   canvas, so it takes the identical calm dark treatment in day mode rather than
   flipping to a bright parchment chip nothing else around it matches. */
:root[data-theme="day"] .mf-coin-hud {
  background: rgba(18, 22, 30, 0.5);
  border-color: rgba(255, 255, 255, 0.16);
}

/* Lang picker list + drop bg were dark fills; day uses the flat parchment fill */
:root[data-theme="day"] .mf-lang-list {
  background: #f1eee7;
}

:root[data-theme="day"] .mf-lang-option {
  background: rgba(136, 14, 79, 0.06);
}

/* How-to score rows had a dark fill; day uses the flat parchment fill */
:root[data-theme="day"] .mf-howto-score-row {
  background: #f1eee7;
}

/* Toast snackbar was a dark pill with a black shadow; day uses a light pill */
:root[data-theme="day"] .mf-toast {
  background: rgba(255, 255, 255, 0.96);
  box-shadow: 0 6px 18px rgba(80, 60, 30, 0.18);
}

/* Invite link box bg was a dark fill; day uses the flat parchment fill */
:root[data-theme="day"] .mf-invite-link {
  background: #f1eee7;
}

/* Hub tools bar shadow was a heavy black; soften for the light page */
:root[data-theme="day"] .mf-hub-tools {
  box-shadow: 0 8px 14px -10px rgba(80, 60, 30, 0.45);
}

/* Hub card hover bg was a fixed dark slate; day lifts toward warm parchment */
:root[data-theme="day"] .mf-hub-card:hover {
  background: #fbfaf6;
}

/* Short phones. The eight-row wardrobe fits a 892px screen at the sizes above, but
 * a 667px one still ran over by about a hundred and fifty pixels, and a costume
 * list you have to scroll defeats the point of showing eight at once. Below this
 * height everything gives a little more: the art slot, the row padding, the card's
 * own padding and gap, and the trailing control drops to a 44px touch target -
 * still above the 44px floor Apple and Android both publish, so nothing becomes
 * hard to hit. */
@media (max-height: 800px) {
  .mf-card {
    padding: 14px;
    gap: 10px;
  }

  .mf-list {
    gap: 5px;
  }

  .mf-list-row {
    padding: 4px 8px;
    gap: 8px;
  }

  .mf-item-preview {
    width: 32px;
    height: 32px;
  }

  .mf-list-row > .mf-btn,
  .mf-list-row > .mf-active-chip {
    min-height: 44px;
  }

  .mf-list-row > .mf-btn {
    padding: 8px 10px;
  }
}

/* Very short phones (a 667px iPhone SE class screen and below). Even compressed,
 * eight rows plus their one-line flavour text overrun this height, so the flavour
 * text is what goes: the costume NAME, its price and its button are what the panel
 * exists to show, and a description the player must scroll to reach is worth less
 * than seeing the whole wardrobe at once. Everything else tightens one more step. */
@media (max-height: 700px) {
  .mf-card {
    padding: 10px;
    gap: 6px;
  }

  .mf-list {
    gap: 3px;
  }

  .mf-list-row {
    padding: 3px 8px;
  }

  .mf-list-row .mf-list-desc .mf-list-hint {
    display: none;
  }

  .mf-item-preview {
    width: 30px;
    height: 30px;
  }

  .mf-list-row > .mf-btn,
  .mf-list-row > .mf-active-chip {
    min-height: 42px;
  }
}

