/* =========================================================================
   Design tokens
   Everything derives from --brand-primary, which each tenant sets, so a
   tenant's colour flows through gradients, highlights and accents rather
   than sitting in one lonely header bar.
   ========================================================================= */

:root {
  --brand-primary: #2563eb;
  --brand-accent: #0ea5e9;

  --bg: #f6f7fb;
  --surface: #ffffff;
  --surface-2: #f0f2f8;
  --border: #e6e9f2;

  --text: #10121a;
  --text-2: #5b6178;
  --text-3: #8b91a6;

  --radius-sm: 10px;
  --radius: 16px;
  --radius-lg: 22px;

  --shadow-sm: 0 1px 2px rgba(16, 18, 26, 0.05), 0 1px 3px rgba(16, 18, 26, 0.04);
  --shadow: 0 4px 16px rgba(16, 18, 26, 0.07), 0 1px 3px rgba(16, 18, 26, 0.05);
  --shadow-lg: 0 12px 32px rgba(16, 18, 26, 0.13);

  --gold: #f5b93b;
  --silver: #b9c0d4;
  --bronze: #cf8a4e;

  --ease: cubic-bezier(0.22, 0.9, 0.3, 1);
}

/* One dark palette, one selector. "auto" is resolved to a concrete light/dark
   value in JS (see applyTheme in app.js) rather than with a second copy of these
   tokens behind a prefers-color-scheme query — that duplication drifted, and it
   also left color-scheme forced to dark for an auto tenant on a light device,
   which painted the UA's own form controls and scrollbars dark on a light page. */
:root[data-theme='dark'] {
  color-scheme: dark;
  --bg: #0e1016;
  --surface: #171a23;
  --surface-2: #1f232f;
  --border: #272c3a;
  --text: #f2f4f9;
  --text-2: #a8b0c4;
  --text-3: #767f96;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
}

:root[data-corners='soft'] {
  --radius-sm: 7px;
  --radius: 10px;
  --radius-lg: 14px;
}

:root[data-corners='sharp'] {
  --radius-sm: 2px;
  --radius: 3px;
  --radius-lg: 4px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  height: 100%;
}

body {
  margin: 0;
  width: 100%;
  height: 100%;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

#shell-root {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
}

#shell-root.hidden {
  display: none;
}

/* =========================================================================
   Tab bar

   Real tabs rather than a pill segmented control: full-width, equal-width,
   icon + label, with an underline that slides between them. This is the
   convention players already know from offerwall and store UIs, and it scales
   to more sections later without the pill getting cramped.

   The tenant's name is intentionally absent — this runs embedded inside the
   tenant's own page, which already shows their branding, so repeating it cost
   vertical space the games need.
   ========================================================================= */

#shell-header {
  flex-shrink: 0;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

#shell-nav {
  position: relative;
  display: flex;
  max-width: 560px;
  margin: 0 auto;
}

#shell-nav button {
  flex: 1;
  position: relative;
  appearance: none;
  border: none;
  background: transparent;
  color: var(--text-3);
  padding: 12px 10px 13px;
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  transition: color 200ms var(--ease);
  -webkit-tap-highlight-color: transparent;
}

#shell-nav button svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 240ms var(--ease);
}

/* The Games icon reads better filled than outlined at this size. */
#shell-nav button[data-view='game-list'] svg rect {
  fill: currentColor;
  stroke: none;
}

#shell-nav button:hover {
  color: var(--text-2);
}

#shell-nav button.active {
  color: var(--brand-primary);
}

#shell-nav button.active svg {
  transform: translateY(-1px) scale(1.06);
}

#nav-indicator {
  position: absolute;
  bottom: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: var(--brand-primary);
  transition: transform 300ms var(--ease), width 300ms var(--ease);
  pointer-events: none;
}

/* Phones put the tab bar at the bottom by default: it is where thumbs actually
   reach on a tall screen, and it is the convention players know from the apps
   this sits inside. column-reverse moves it without duplicating any markup.
   A tenant with a bottom-anchored ad sets data-nav="top" (see applyAdAnchor)
   so the bar does not end up flush against their ad. */
@media (max-width: 640px) {
  :root[data-nav='bottom'] #shell-root {
    flex-direction: column-reverse;
  }
  :root[data-nav='bottom'] #shell-header {
    border-bottom: none;
    border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  :root[data-nav='bottom'] #nav-indicator {
    bottom: auto;
    top: 0;
    border-radius: 0 0 3px 3px;
  }
  #shell-nav button {
    flex-direction: column;
    gap: 3px;
    font-size: 11px;
    padding: 8px 6px 9px;
  }
  #shell-nav button svg {
    width: 20px;
    height: 20px;
  }
}

/* =========================================================================
   Views
   ========================================================================= */

#shell-content {
  flex: 1;
  position: relative;
  min-height: 0;
}

.view {
  /* Declared as a variable because .catalog-sticky has to cancel exactly this
     value to reach the edges. Hard-coding it in both places is how a sticky
     header ends up with a visible seam after someone retunes the padding. */
  --view-pad: 18px;
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--view-pad);
  -webkit-overflow-scrolling: touch;
}

.view.hidden {
  display: none;
}

.view-enter {
  animation: view-in 260ms var(--ease);
}

@keyframes view-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.section-title {
  margin: 0 0 14px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
}

/* =========================================================================
   Catalogue controls — search, random pick, category filters
   ========================================================================= */

/* Pinned to the top of the list, and full-bleed: the bar needs an opaque
   background across the whole width or cards show through beside it as they
   pass underneath, so it cancels the view's padding and re-applies it inside.
   The .view owns overflow-y, so it is the scrollport this sticks to. */
/* The catalogue view drops its own top padding and the sticky header supplies
   that space as its own padding instead. The previous approach had the header
   cancel the view's padding with a negative top margin and add it back — the two
   have to agree exactly, and any disagreement shows up as a gap above the search
   bar, which is what happened. With nothing to cancel there is nothing to
   disagree about. Horizontal bleed still needs negative margins, but a
   horizontal mismatch cannot open a vertical gap. */
#view-game-list { padding-top: 0; }

.catalog-sticky {
  position: sticky;
  top: 0;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin: 0 calc(var(--view-pad) * -1) 10px;
  padding: var(--view-pad) var(--view-pad) 10px;
  background: var(--bg);
  transition: transform 220ms var(--ease), opacity 200ms var(--ease);
}

/* The wrapper's gap and padding own the spacing now, so the chips' own bottom
   margin would double it. */
.catalog-sticky .category-chips { margin-bottom: 0; }

/* Tucked away while the player is heading down the list, and back the instant
   they scroll up. A permanently pinned bar costs a row of cards on a phone for
   controls that are mostly not in use; scrolling up is the gesture that means
   "I want the controls". Sticky elements keep their space in flow, so this
   translate moves it without shifting the grid. */
.catalog-sticky.is-tucked {
  transform: translateY(calc(-100% - 4px));
  opacity: 0;
  pointer-events: none;
}

.catalog-tools {
  display: flex;
  gap: 8px;
  max-width: 560px;
  margin-inline: auto;
}

.search-wrap {
  position: relative;
  flex: 1;
  min-width: 0;
}

.search-input {
  width: 100%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: 999px;
  padding: 10px 16px;
  font-size: 14px;
  font-family: inherit;
  box-shadow: var(--shadow-sm);
  -webkit-appearance: none;
}

.search-input::placeholder { color: var(--text-3); }

/* flex-shrink: 0 so the search field yields instead, and a max-width sized to
   the longest label so changing order does not resize the row. */
.sort-select {
  flex-shrink: 0;
  max-width: 134px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  border-radius: 999px;
  padding: 10px 12px;
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  -webkit-appearance: none;
  appearance: none;
  text-align: center;
}

.sort-select:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--brand-primary) 55%, var(--border));
}

.search-input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--brand-primary) 55%, var(--border));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-primary) 16%, transparent);
}

/* Deliberately a peer of the search field rather than buried in a menu: it is
   the fastest path to playing something when a player has no specific game in
   mind, which is most of them. */
.lucky-btn {
  flex-shrink: 0;
  width: 44px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 999px;
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: transform 200ms var(--ease), border-color 200ms var(--ease);
}

.lucky-btn:hover {
  border-color: color-mix(in srgb, var(--brand-primary) 55%, var(--border));
  transform: rotate(-12deg) scale(1.06);
}

.lucky-btn:active { transform: rotate(6deg) scale(0.96); }

.category-chips {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 4px;
  margin-bottom: 12px;
  max-width: 560px;
  margin-inline: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.category-chips::-webkit-scrollbar { display: none; }

.category-chips button {
  flex-shrink: 0;
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  border-radius: 999px;
  padding: 6px 13px;
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: all 160ms var(--ease);
}

.category-chips button.active {
  background: linear-gradient(140deg, var(--brand-primary), color-mix(in srgb, var(--brand-accent) 65%, #000));
  color: #fff;
  border-color: transparent;
}

/* =========================================================================
   Game cards

   Single column by default: this is a mobile-first surface, and a full-width
   card gives room for real artwork, the player's own numbers and an
   unmissable call to action. Wider viewports get multiple columns of the same
   card rather than a different component.
   ========================================================================= */

.game-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  max-width: 560px;
  margin: 0 auto;
}

.game-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 220ms var(--ease), box-shadow 220ms var(--ease);
}

.game-card:hover {
  box-shadow: var(--shadow);
}

/* Shorter than a 16:9 banner: this is a dense catalogue, and a squatter
   letterbox keeps more games above the fold without starving the artwork. */
.game-art {
  position: relative;
  aspect-ratio: 8 / 3;
  cursor: pointer;
  overflow: hidden;
  background: #0b0620;
}

/* Generated key art fills the banner; the icon sits on top of it. */
.game-banner {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.game-body {
  padding: 10px 12px 11px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.game-name {
  margin: 0;
  font-size: 15.5px;
  font-weight: 800;
  letter-spacing: -0.3px;
  color: var(--text);
}

.stat-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.stat-chip {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 5px 10px;
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.stat-value {
  font-size: 14px;
  font-weight: 800;
  letter-spacing: -0.3px;
  font-variant-numeric: tabular-nums;
  color: var(--brand-primary);
}

.stat-chip.is-muted .stat-value {
  color: var(--text-3);
}

.stat-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
}

.play-btn {
  appearance: none;
  border: none;
  width: 100%;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  background: linear-gradient(140deg, var(--brand-primary), color-mix(in srgb, var(--brand-accent) 70%, #000));
  color: #fff;
  font-size: 14.5px;
  font-weight: 700;
  font-family: inherit;
  letter-spacing: -0.1px;
  cursor: pointer;
  box-shadow: 0 4px 14px color-mix(in srgb, var(--brand-primary) 35%, transparent);
  transition: transform 160ms var(--ease), box-shadow 200ms var(--ease), filter 160ms var(--ease);
}

.play-btn:hover {
  filter: brightness(1.06);
  box-shadow: 0 6px 20px color-mix(in srgb, var(--brand-primary) 45%, transparent);
}

.play-btn:active {
  transform: translateY(1px) scale(0.995);
}

/* Play is the primary action; the leaderboard sits beside it as a peer rather
   than hidden in a menu, because "how am I doing at this?" is the second thing
   a player wants from a tile. */
.card-actions {
  display: flex;
  gap: 8px;
}

.card-actions .play-btn { flex: 1; }

.lb-link {
  flex-shrink: 0;
  width: 44px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all 160ms var(--ease);
}

.lb-link svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.lb-link:hover {
  color: var(--brand-primary);
  border-color: color-mix(in srgb, var(--brand-primary) 50%, var(--border));
}

/* Leaderboard picker — the escape hatch for boards outside your own games. */
.lb-more {
  border-style: dashed !important;
}

.lb-picker {
  max-width: 560px;
  margin: 0 auto;
}

.lb-picker-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
}

.lb-picker-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  text-align: left;
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 10px 13px;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
}

.lb-picker-item small {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text-3);
  white-space: nowrap;
}

.lb-picker-item:hover { border-color: color-mix(in srgb, var(--brand-primary) 50%, var(--border)); }

.lb-picker-item.active {
  border-color: var(--brand-primary);
  background: color-mix(in srgb, var(--brand-primary) 8%, var(--surface));
}

.lb-prompt {
  max-width: 560px;
  margin: 16px auto 0;
  text-align: center;
}

.lb-prompt p {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-3);
}

.lb-prompt .play-btn { max-width: 280px; margin: 0 auto; }

/* Roadmap titles with no build yet: visibly present but clearly not playable,
   so a catalogue preview never dead-ends in a 404. */
.game-card.is-upcoming {
  opacity: 0.72;
}

.game-card.is-upcoming .game-art {
  filter: saturate(0.45);
  cursor: default;
}

.game-soon {
  margin: 0;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-3);
}

.play-btn:disabled {
  background: var(--surface-2);
  color: var(--text-3);
  box-shadow: none;
  cursor: default;
}

/* Wider screens: same card, more columns. */
@media (min-width: 720px) {
  .game-list {
    grid-template-columns: repeat(2, 1fr);
    max-width: 880px;
  }
}

@media (min-width: 1100px) {
  .game-list {
    grid-template-columns: repeat(3, 1fr);
    max-width: 1200px;
  }
}

/* =========================================================================
   Leaderboard
   ========================================================================= */

.lb-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.leaderboard-tabs {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 2px;
  scrollbar-width: none;
}

.leaderboard-tabs::-webkit-scrollbar {
  display: none;
}

.leaderboard-tabs button {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  border-radius: 999px;
  padding: 7px 14px;
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  transition: all 180ms var(--ease);
}

.leaderboard-tabs button:hover {
  border-color: color-mix(in srgb, var(--brand-primary) 40%, var(--border));
}

.leaderboard-tabs button.active {
  background: linear-gradient(140deg, var(--brand-primary), color-mix(in srgb, var(--brand-primary) 62%, #000));
  color: #fff;
  border-color: transparent;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--brand-primary) 38%, transparent);
}

/* ---- Podium for the top three ---- */

.podium {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: end;
  gap: 10px;
  margin-bottom: 16px;
}

.podium-slot {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 8px 12px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  box-shadow: var(--shadow-sm);
  animation: podium-rise 420ms var(--ease) backwards;
}

.podium-slot[data-place='1'] {
  order: 2;
  padding-top: 20px;
  border-color: color-mix(in srgb, var(--gold) 55%, var(--border));
  box-shadow: var(--shadow), 0 0 0 1px color-mix(in srgb, var(--gold) 30%, transparent);
  animation-delay: 60ms;
}

.podium-slot[data-place='2'] {
  order: 1;
  animation-delay: 0ms;
}

.podium-slot[data-place='3'] {
  order: 3;
  animation-delay: 120ms;
}

@keyframes podium-rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.podium-medal {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.5px;
  padding: 3px 9px;
  border-radius: 999px;
  color: #4a3a10;
  background: color-mix(in srgb, var(--gold) 30%, #fff);
}

.podium-slot[data-place='2'] .podium-medal {
  color: #3a3f4d;
  background: color-mix(in srgb, var(--silver) 40%, #fff);
}

.podium-slot[data-place='3'] .podium-medal {
  color: #533217;
  background: color-mix(in srgb, var(--bronze) 30%, #fff);
}

.podium-name {
  font-size: 12.5px;
  font-weight: 700;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.podium-score {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -0.3px;
  font-variant-numeric: tabular-nums;
}

.podium-sub {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text-3);
}

/* ---- Ranked rows ---- */

.lb-list {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.lb-row {
  display: grid;
  grid-template-columns: 30px auto 1fr auto;
  align-items: center;
  gap: 11px;
  padding: 9px 13px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

.lb-rank {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.lb-name {
  font-size: 13.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lb-values {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.lb-value {
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.2px;
}

.lb-value small {
  display: block;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-3);
}

.lb-row.is-you {
  border-color: color-mix(in srgb, var(--brand-primary) 55%, transparent);
  background: color-mix(in srgb, var(--brand-primary) 6%, var(--surface));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--brand-primary) 22%, transparent), var(--shadow-sm);
}

.you-badge {
  display: inline-block;
  margin-left: 7px;
  padding: 2px 7px;
  border-radius: 999px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  background: var(--brand-primary);
  color: #fff;
  vertical-align: middle;
}

.avatar {
  display: grid;
  place-items: center;
  border-radius: 999px;
  color: #fff;
  font-weight: 800;
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25);
}

/* ---- Empty / loading states ---- */

.lb-empty {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-3);
  font-size: 13.5px;
  font-weight: 500;
}

.skeleton {
  height: 52px;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--surface-2) 25%, #e9ecf4 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}

@keyframes shimmer {
  from { background-position: 100% 50%; }
  to { background-position: 0 50%; }
}

/* =========================================================================
   Player handle
   ========================================================================= */

.handle-bar {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  padding: 10px 13px;
  margin-bottom: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 13px;
  box-shadow: var(--shadow-sm);
}

.handle-label {
  color: var(--text-3);
  font-weight: 500;
}

.handle-name {
  font-weight: 700;
}

.handle-edit,
.handle-form button {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  border-radius: 999px;
  padding: 5px 13px;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: all 160ms var(--ease);
}

.handle-edit:hover {
  border-color: color-mix(in srgb, var(--brand-primary) 45%, var(--border));
  color: var(--text);
}

.handle-edit[hidden],
.handle-form[hidden] {
  display: none;
}

.handle-form {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}

.handle-form input {
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 13px;
  font-size: 13px;
  font-family: inherit;
  min-width: 150px;
  background: var(--surface-2);
  color: var(--text);
}

.handle-form input:focus {
  outline: 2px solid color-mix(in srgb, var(--brand-primary) 45%, transparent);
  outline-offset: 1px;
  background: var(--surface);
}

.handle-form button[type='submit'] {
  background: var(--brand-primary);
  color: #fff;
  border-color: transparent;
}

.handle-error {
  color: #d3453c;
  font-size: 12px;
  font-weight: 600;
  flex-basis: 100%;
}

/* =========================================================================
   Game player
   ========================================================================= */

.view-game-player {
  padding: 0;
  display: flex;
  flex-direction: column;
}

.game-player-bar {
  flex-shrink: 0;
  padding: 9px 14px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.game-player-bar button {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  border-radius: 999px;
  padding: 6px 15px;
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: all 160ms var(--ease);
}

.game-player-bar button:hover {
  border-color: color-mix(in srgb, var(--brand-primary) 45%, var(--border));
  color: var(--text);
}

#active-game-frame {
  flex: 1;
  min-height: 0;
  width: 100%;
  border: none;
  display: block;
}

/* =========================================================================
   Error state
   ========================================================================= */

#shell-error {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 28px;
  background: var(--bg);
  color: var(--text-2);
  font-size: 14px;
  font-weight: 500;
}

#shell-error.hidden {
  display: none;
}

/* =========================================================================
   Responsive
   ========================================================================= */

@media (max-width: 380px) {
  /* Sets the variable, not padding: .catalog-sticky cancels --view-pad to reach
     the edges, so overriding padding alone leaves it cancelling 18px against a
     14px gutter and the header no longer sits flush. */
  .view { --view-pad: 14px; }
}

/* Short viewports (landscape phones): the shell's chrome must not eat the
   space a game needs. */
@media (max-height: 480px) {
  #shell-nav button { padding: 7px 10px 8px; font-size: 12px; }
  #shell-nav button svg { width: 15px; height: 15px; }
  .game-player-bar { padding: 5px 10px; }
  .game-player-bar button { padding: 4px 12px; font-size: 12px; }
  .view { --view-pad: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------------------
   Game player bar: back button leading, immersive toggle trailing — the layout
   a video player uses, because that is where a thumb already expects it.
   --------------------------------------------------------------------------- */

/* A grid with equal side columns rather than flex, so the middle column is
   centred on the bar. Under flex the title had flex: 1 and therefore spanned the
   gap between the two controls — and since '← Games' is wider than the
   fullscreen icon, the centre of that span sat right of the centre of the bar,
   which is what skewed the label. 1fr auto 1fr cannot skew: the side columns are
   equal by construction whatever they contain. */
.game-player-bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
}

.game-back-btn { justify-self: start; }

.game-player-actions {
  display: flex;
  align-items: center;
  justify-self: end;
  gap: 6px;
}

/* Centred between the two controls, and allowed to shrink to an ellipsis: a
   long title must never push the back button off the bar. */
.game-player-title {
  min-width: 0;
  text-align: center;
  padding: 0 8px;
  font-size: 13.5px;
  font-weight: 650;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.game-fs-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  flex-shrink: 0;
}

.game-fs-btn svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Real fullscreen. The bar rides along so the way out is never hidden. */
#view-game-player:fullscreen {
  background: var(--bg);
}

/* Expanded play, which is what the fullscreen button does: reclaim the shell's
   own chrome and nothing else, so the game fills the embed while the tenant's
   page around it — ad slots included — is untouched. .view-game-player already
   carries padding: 0, so hiding the header is the whole change, and leaving
   immersive mode puts the player exactly where they were. */
:root[data-immersive='on'] #shell-header { display: none; }

/* ---------------------------------------------------------------------------
   Confirmation dialog
   --------------------------------------------------------------------------- */

.confirm-backdrop {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 22px;
  background: rgba(6, 8, 20, 0.62);
  backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity 160ms var(--ease);
}

.confirm-backdrop.visible { opacity: 1; }

.confirm-box {
  width: 100%;
  max-width: 340px;
  padding: 20px 20px 16px;
  border-radius: 18px;
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--border);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
  transform: translateY(10px) scale(0.98);
  transition: transform 200ms var(--ease);
}

.confirm-backdrop.visible .confirm-box { transform: none; }

.confirm-title {
  margin: 0 0 7px;
  font-size: 16.5px;
  font-weight: 700;
  color: var(--text);
}

.confirm-body {
  margin: 0 0 18px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-3);
}

.confirm-actions {
  display: flex;
  gap: 9px;
}

.confirm-actions button {
  flex: 1;
  appearance: none;
  border-radius: 999px;
  padding: 11px 14px;
  font-size: 13.5px;
  font-weight: 650;
  font-family: inherit;
  cursor: pointer;
  transition: all 160ms var(--ease);
}

/* Staying is the safe answer, so it gets the solid treatment and the leading
   slot; leaving is deliberately the quieter of the two. */
.confirm-cancel {
  border: none;
  color: #fff;
  background: linear-gradient(135deg, var(--brand-primary), var(--brand-accent));
}

.confirm-ok {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-3);
}

.confirm-ok:hover {
  border-color: color-mix(in srgb, #f43f5e 50%, var(--border));
  color: #fb7185;
}
