/* ===========================================================================
   Switch 2 skin — layered on top of css/style.css.

   This file is a *skin*, not a rewrite: style.css still owns the layout and
   every component's structure, and this file restyles that structure to read
   like the Nintendo Switch 2 home menu. Kept as its own stylesheet (loaded
   after style.css) so the whole look is one <link> away from being dropped,
   and so the diff for "make it look like a Switch" isn't smeared through
   1200 lines of unrelated rules.

   What the Switch home menu actually looks like, and what each part maps to
   here:
     - flat near-black neutral background, no busy gradients        -> body
     - slim top bar: round user icon left, clock/wifi/battery right -> .topbar
     - a shelf of big rounded-square game icons, name underneath    -> .game-tile
     - the selected icon scales up inside a white outline ring      -> :hover etc.
     - a bottom dock bar of circular system buttons                 -> .switch-dock
   The dock is the one piece that needs a DOM node style.css never had; the
   buttons inside it are the *same elements* that used to sit in the top bar,
   relocated by js/switch-shell.js so every existing click handler still works.

   Themes (js/settings-panel.js) still work: everything here keys off the same
   --variables that file sets, this just gives them Switch-console values.
   =========================================================================== */

:root {
  /* Neutral console charcoal rather than the old blue-black — the Switch's
     home menu background is a flat dark grey, not a tinted one. */
  --bg: #16171a;
  --bg-alt: #1d1e22;
  --panel: #26272c;
  --panel-light: #303138;
  /* Joy-Con Neon Red / Neon Blue, unchanged — these are the real thing and
     every other stylesheet already keys off them. */
  --accent: #ff3c28;
  --accent2: #00c3e3;
  --accent3: #7ee81c;
  --text: #ffffff;
  --text-dim: #b9bcc6;
  --border: #3a3c44;
  /* Switch-specific tokens, only used in this file. */
  --sw-dock-h: 78px;
  --sw-select: #ffffff;
  --sw-hairline: rgba(255, 255, 255, .09);
}

/* --------------------------------------------------------------- background
   Four layers, none of which is a picture — so there's nothing to download,
   nothing to cache, and it works offline and at any window size:

     1. the base       a cool top-lit charcoal, with a vignette pulling the
                       corners down so the shelf of icons sits in the light
     2. a sheen        one very slow diagonal pass of brightness across it
     3. a colour mesh  (body::before) four soft Joy-Con-coloured fields that
                       drift against each other, plus one that follows the
                       game you're hovering (js/switch-shell.js sets --sw-hue)
     4. dust           (body::after) three tiled speck layers rising at
                       different rates, which is what actually reads as depth

   Layers 3 and 4 are pseudo-elements on <body> at negative z-index, so they
   paint above body's own background but under every piece of UI, and the light
   themes already switch both off (style.css) rather than needing new rules. */
html, body {
  font-family: "Nunito Sans", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  animation: none;
}

/* --------------------------------------------------------------- background
   Four layers, stacked bottom to top. No image, so there's nothing to
   download, it works offline, and it's sharp at any window size:

     <html>          base charcoal, a top light, and one slow diagonal sheen
     body::before    a drifting mesh of Joy-Con colour, plus a field that
                     takes the hue of whatever game you're hovering
     body::after     rising dust specks at three parallax rates, and — on top
                     of everything — the vignette that keeps the colour in check
     content         the UI

   The vignette belongs in the *last* layer, not the first: underneath the mesh
   it can't darken it, and the four overlapping colour fields turn the page into
   a wash. Above it, the same mesh reads as atmosphere.

   <body> is transparent so it doesn't cover what <html> paints; the light
   themes set their own opaque body background at higher specificity
   (html.theme-light body), so they still win. */
body { background: transparent; }

html {
  background-color: #14151a;
  background-image:
    linear-gradient(115deg, transparent 36%, rgba(255,255,255,.05) 47%, transparent 58%),
    radial-gradient(150% 105% at 50% -14%, rgba(255,255,255,.07), transparent 60%);
  background-size: 260% 260%, auto;
  background-position: 120% 0%, center;
  background-attachment: fixed;
  animation: sw-sheen 30s linear infinite;
}
@keyframes sw-sheen {
  to { background-position: -70% 0%, center; }
}

/* --sw-hue is the hue of the game icon under the cursor (js/switch-shell.js),
   so the room takes on the colour of what you're about to play — the way the
   console's home screen shifts behind a highlighted title. --sw-px/--sw-py are
   the pointer parallax, applied through `translate` so they compose with the
   drift animation's `transform` instead of fighting it for the same property. */
body::before {
  content: "";
  position: fixed;
  /* inset:0, not a negative inset: the gradient stops below are positioned in
     percentages of this element, so oversizing it pushes "12% 16%" well off
     screen and the four corner colours smear into one flat wash instead of
     sitting in their corners. The drift comes from the scale in sw-mesh
     instead, which overflows the viewport harmlessly — a fixed element adds no
     scrollable overflow. */
  inset: 0;
  overflow: hidden;
  width: auto; height: auto;
  max-width: none; max-height: none;
  border-radius: 0;
  opacity: 1;
  filter: none;
  z-index: -3;
  pointer-events: none;
  background:
    radial-gradient(40vw 40vw at 8% 6%, rgba(255, 60, 40, .26), transparent 62%),
    radial-gradient(38vw 38vw at 94% 12%, rgba(0, 195, 227, .2), transparent 62%),
    radial-gradient(40vw 40vw at 88% 98%, rgba(122, 92, 255, .22), transparent 62%),
    radial-gradient(34vw 34vw at 6% 96%, rgba(126, 232, 28, .11), transparent 58%),
    /* The hovered game's colour, and *only* when something is hovered: a
       permanent field in the middle of the screen just tints the whole page,
       and at the default hue it doubled up with the blue corner above. Its
       alpha is its own variable so the resting state is genuinely nothing. */
    radial-gradient(44vw 44vw at 50% 48%, hsl(var(--sw-hue, 190deg) 85% 55% / var(--sw-hue-a, 0)), transparent 64%);
  translate: var(--sw-px, 0px) var(--sw-py, 0px);
  transition: translate .5s cubic-bezier(.2, .8, .3, 1), background .55s ease;
  animation: sw-mesh 38s ease-in-out infinite alternate;
}
@keyframes sw-mesh {
  from { transform: scale(1) rotate(0deg); }
  to   { transform: scale(1.16) rotate(5deg); }
}

/* The canvas of tumbling blocks (js/hub-backdrop.js) sits between the colour
   mesh and the dust: the mesh is the light in the room, the blocks are in it,
   and the dust drifts in front of them. */
.hub-backdrop {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  pointer-events: none;
}
html.theme-light .hub-backdrop { display: none; }

/* Dust at three parallax rates — the drift between the three is what makes the
   colour behind them read as having depth; one layer alone is just noise.
   All background-position, so none of it ever touches layout.

   No vignette here. It was originally a fourth layer in this same element, and
   it flattened the whole mesh underneath into one uniform teal wash (checked by
   sampling real pixels: with this element disabled the mesh renders correct
   red/cyan/violet corners, with it enabled every corner came back the same
   colour). The mesh is already restrained enough not to need darkening on top
   of it, and one fewer full-screen layer is one fewer thing to composite. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  width: auto; height: auto;
  max-width: none; max-height: none;
  border-radius: 0;
  filter: none;
  opacity: 1;
  z-index: -1;
  pointer-events: none;
  /* style.css fills this pseudo-element with `background: var(--accent2)` for
     the old glow blob. Setting only `background-image` below leaves that
     *colour* in place — and with the sizing above it stops being a small blob
     and becomes a solid cyan sheet over the entire viewport, which is exactly
     what it did until this line was added. The shorthand on ::before resets it
     for free; this one has to say so. */
  background-color: transparent;
  background-image:
    radial-gradient(circle, rgba(255,255,255,.42) 1.1px, transparent 1.7px),
    radial-gradient(circle, rgba(210,230,255,.26) 1px, transparent 1.6px),
    radial-gradient(circle, rgba(160,220,255,.28) 1.5px, transparent 2.2px);
  background-size: 140px 140px, 215px 215px, 330px 330px;
  background-position: 0 0, 40px 70px, 120px 30px;
  translate: calc(var(--sw-px, 0px) * -1.6) calc(var(--sw-py, 0px) * -1.6);
  transition: translate .5s cubic-bezier(.2, .8, .3, 1);
  animation: sw-dust 90s linear infinite;
}
@keyframes sw-dust {
  to { background-position: 0 -280px, 40px -360px, 120px -650px; }
}
}

/* Room for the fixed dock so the last row of icons is never trapped under it. */
body { padding-bottom: var(--sw-dock-h); }

/* --------------------------------------------------------------------- top bar
   Switch top bar: user icon hard left, system status hard right, and nothing
   heavier than a hairline separating it from the shelf. */
.topbar {
  /* Opaque instead of blurred. This strip is fixed over the animating
     backdrop canvas, and backdrop-filter makes the compositor re-blur the
     region behind it every single frame the canvas repaints — measurably the
     hub's biggest cost, for a blur invisible behind a 96%-opaque panel. */
  background: rgba(30, 31, 36, .97);
  border-bottom: 1px solid var(--sw-hairline);
  box-shadow: none;
  padding: 9px 20px;
  gap: 10px;
}
/* The two-tone Joy-Con line under the bar was a nice nod, but the console's
   own top bar is a plain hairline — dropped so this reads as the real thing. */
.topbar::after { display: none; }

/* Three columns that hold their positions: user icon, browse controls, system
   status. style.css lets the whole bar wrap, which on a busy row drops the
   status cluster onto a second line at the *left* edge — the opposite corner
   from where a console puts its clock. The bar itself no longer wraps; the
   controls group in the middle wraps internally instead, and shrinks (min-width:0)
   rather than pushing the status off the end. */
.topbar { flex-wrap: nowrap; }
.topbar-controls { flex: 1 1 auto; min-width: 0; justify-content: flex-start; }
.sw-status { flex: 0 0 auto; }

/* The brand button becomes the console's round user icon + name. */
/* Fixed width, never squeezed: with the bar no longer wrapping, a shrinkable
   brand breaks "51 Mimi Games" onto two lines as soon as the controls get busy. */
.brand { gap: 12px; padding: 3px 12px 3px 3px; border-radius: 999px; flex: 0 0 auto; white-space: nowrap; }
.brand-icon {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: 50%;
  font-size: 1.15rem;
  /* Switch 2's own logo mark reads as a red-to-blue sweep; the user icon
     picks that up so the two obvious Joy-Con colors meet in one place. */
  background: linear-gradient(135deg, var(--accent), var(--accent2));
  box-shadow: 0 0 0 2px rgba(255,255,255,.14) inset;
  animation: none;
}
.brand h1 {
  font-size: 1.02rem;
  font-weight: 800;
  letter-spacing: .2px;
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: var(--text);
  animation: none;
}

/* Live clock / wifi / battery cluster — the right-hand end of the console's
   top bar. Markup lives in index.html; js/switch-shell.js keeps it ticking. */
.sw-status {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-left: 6px;
  font-size: .84rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: .3px;
  white-space: nowrap;
}
.sw-status svg { display: block; }
.sw-status-battery { display: flex; align-items: center; gap: 5px; }
.sw-status-batt-pct { font-size: .78rem; color: var(--text-dim); font-weight: 600; }

#search {
  background: rgba(255,255,255,.07);
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 7px 14px;
}
#search:focus { outline: none; border-color: var(--sw-select); background: rgba(255,255,255,.12); }

.cat-chip {
  background: rgba(255,255,255,.06);
  border: 1px solid transparent;
  border-radius: 999px;
  font-weight: 700;
}
.cat-chip:hover, .cat-chip.pad-hover-target {
  border-color: var(--sw-select);
  transform: none;
  box-shadow: none;
  background: rgba(255,255,255,.12);
}
.cat-chip.active {
  background: var(--sw-select);
  color: #16171a;
  border-color: var(--sw-select);
}

/* ------------------------------------------------------------------ the shelf
   80 games won't fit the console's single scrolling row, so the row wraps —
   but every icon keeps the home menu's proportions: a chunky rounded square
   with its name printed underneath, never inside. */
.menu-view { padding: 34px 30px 30px; }

.grid {
  /* The row gap is the space the names live in, so it has to clear a
     two-line title (~25px) plus the 7px it sits below the icon — anything
     tighter and a long name overlaps the next row of icons. */
  gap: 46px 20px;
  justify-content: center;
  padding-bottom: 10px;
}

.game-tile {
  flex: 0 0 auto;
  width: 132px;
  height: 132px;
  max-width: none;
  padding: 0;
  border: none;
  border-radius: 20px;
  overflow: visible;
  justify-content: center;
  gap: 0;
  /* Every icon on a real home menu is its own piece of key art, so each tile
     gets a deterministic hue off its position in the grid — the shelf reads
     as a wall of distinct games instead of 80 copies of one grey card. */
  background:
    linear-gradient(160deg, rgba(255,255,255,.14), rgba(0,0,0,.18)),
    hsl(calc(var(--hue, var(--i, 0)) * 47deg) 62% 42%);
  box-shadow: 0 6px 16px rgba(0,0,0,.45);
  transition: transform .16s cubic-bezier(.2,.9,.3,1.2), box-shadow .16s ease;
}
/* The spinning conic ring and the shimmering LED outline are a gamer-RGB
   look, not a console one — the Switch marks selection with one clean white
   ring and nothing else. */
.game-tile::before { display: none; }
.game-tile::after  { display: none; }

.game-tile:hover, .game-tile:focus-visible, .game-tile.pad-hover-target {
  transform: scale(1.13);
  border-color: transparent;
  box-shadow:
    0 0 0 4px var(--sw-select),
    0 16px 34px rgba(0,0,0,.6);
  z-index: 3;
}
.game-tile:active { transform: scale(1.04); }

.game-tile .tile-emoji {
  font-size: 3.1rem;
  filter: drop-shadow(0 3px 5px rgba(0,0,0,.4));
}
.game-tile:hover .tile-emoji,
.game-tile:focus-visible .tile-emoji,
.game-tile.pad-hover-target .tile-emoji { transform: none; }

/* The name sits *below* the icon, outside the square — the row gap above
   reserves the space, so it never overlaps the next row. */
.game-tile .tile-title {
  position: absolute;
  top: calc(100% + 7px);
  left: -12px; right: -12px;
  font-size: .74rem;
  font-weight: 700;
  color: var(--text-dim);
  line-height: 1.2;
  /* Two lines is what the row gap above budgets for; the handful of longer
     titles ellipsize rather than pushing into the next row. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: color .16s ease;
  pointer-events: none;
}
.game-tile:hover .tile-title,
.game-tile:focus-visible .tile-title,
.game-tile.pad-hover-target .tile-title { color: var(--text); }

/* Player count / category is detail the console only ever shows on the game's
   own page, not on the shelf — hidden here, still in the DOM for search. */
.game-tile .tile-meta { display: none; }

.game-tile .tile-num {
  top: 7px; left: 8px;
  background: rgba(0,0,0,.34);
  color: rgba(255,255,255,.72);
  font-weight: 700;
  border-radius: 7px;
}
.tile-fav { top: 5px; right: 6px; text-shadow: 0 2px 4px rgba(0,0,0,.5); }

/* Recently-played row: the same icons at a smaller size, on the shelf's own
   background rather than in cards. */
.section-heading { font-weight: 800; letter-spacing: .2px; }
.continue-tile {
  border: none;
  border-radius: 16px;
  background: linear-gradient(160deg, rgba(255,255,255,.14), rgba(0,0,0,.2)), var(--panel);
  box-shadow: 0 5px 14px rgba(0,0,0,.4);
}
.continue-tile:hover, .continue-tile:focus-visible, .continue-tile.pad-hover-target {
  transform: scale(1.06);
  border-color: transparent;
  box-shadow: 0 0 0 3px var(--sw-select), 0 12px 26px rgba(0,0,0,.55);
}

/* ----------------------------------------------------------- landing / search
   The three play-mode cards and the app shortcuts get the same icon-square
   language as the shelf, so stepping between screens never changes vocabulary. */
.landing-mode-btn,
.search-home-app-tile {
  border: none;
  border-radius: 22px;
  background: linear-gradient(160deg, rgba(255,255,255,.14), rgba(0,0,0,.22)), var(--panel);
  box-shadow: 0 8px 22px rgba(0,0,0,.45);
  transition: transform .16s cubic-bezier(.2,.9,.3,1.2), box-shadow .16s ease;
}
.landing-mode-btn:hover, .landing-mode-btn:focus-visible, .landing-mode-btn.pad-hover-target,
.search-home-app-tile:hover, .search-home-app-tile:focus-visible, .search-home-app-tile.pad-hover-target {
  transform: scale(1.05);
  border-color: transparent;
  box-shadow: 0 0 0 4px var(--sw-select), 0 16px 34px rgba(0,0,0,.6);
}
.landing-mode-solo    { background: linear-gradient(160deg, rgba(255,255,255,.16), rgba(0,0,0,.24)), #b52a1c; }
.landing-mode-multi   { background: linear-gradient(160deg, rgba(255,255,255,.16), rgba(0,0,0,.24)), #0b7f96; }
.landing-mode-wireless{ background: linear-gradient(160deg, rgba(255,255,255,.16), rgba(0,0,0,.24)), #4a8c1b; }
.landing-mode-label { font-weight: 800; }
.landing-mode-desc  { color: rgba(255,255,255,.78); }
.landing-tagline { font-weight: 700; }

.search-home-bar {
  border-radius: 999px;
  border: 1px solid var(--sw-hairline);
  background: rgba(255,255,255,.07);
}
.search-home-bar:focus-within { border-color: var(--sw-select); box-shadow: none; }

/* --------------------------------------------------------------- the dock bar
   The console's bottom bar: a hairline-topped strip of circular system
   buttons. These are the exact <button> elements that used to live in the top
   bar — js/switch-shell.js moves the nodes, it never re-creates them, so
   every listener bound in app.js/profiles.js/settings-panel.js still fires. */
.switch-dock {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: var(--sw-dock-h);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 26px;
  padding: 0 20px;
  /* Same reasoning as the top bar — see the note there. */
  background: rgba(30, 31, 36, .97);
  border-top: 1px solid var(--sw-hairline);
  z-index: 30;
}

/* Every dock button collapses to its glyph: font-size:0 kills the button's own
   label text node, and a per-button ::before supplies the icon. Doing it in CSS
   (rather than rewriting each button's textContent) leaves the live labels the
   JS keeps updating — "Music: On", "Music: Off" — intact underneath. */
.switch-dock .btn,
.switch-dock .download-app-wrap > .btn {
  width: 52px; height: 52px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid transparent;
  background: rgba(255,255,255,.08);
  font-size: 0;
  display: grid;
  place-items: center;
  position: relative;
  transition: transform .16s cubic-bezier(.2,.9,.3,1.2), box-shadow .16s ease, background .16s ease;
}
.switch-dock .btn::before {
  font-size: 1.42rem;
  line-height: 1;
  content: "•";
}
/* The keys button is the one dock icon that has to keep showing text — the
   balance is the whole point of it — so it opts out of the glyph-only
   treatment the others get and stays a pill. */
#keysBtn { font-size: .82rem !important; width: auto !important; border-radius: 999px !important;
           padding: 0 14px !important; font-weight: 800; letter-spacing: .3px; }
#keysBtn::before { content: none !important; }

#musicBtn::before      { content: "🎵"; }
#settingsBtn::before   { content: "⚙️"; }
#updatesBtn::before    { content: "🆕"; }
#downloadAppBtn::before{ content: "⬇️"; }
#profileBtn::before    { content: "👤"; }
#playTogetherBtn::before { content: "📡"; }
#leaderboardsBtn::before { content: "🏆"; }
#achievementsBtn::before { content: "🎖️"; }
#friendsBtn::before      { content: "👥"; }
#messagesBtn::before     { content: "💬"; }
#switch100Btn::before    { content: "🔀"; }
/* Was falling through to the generic "•" above — Feedback had never gotten
   its own glyph since joining the dock. A different icon from Messages'
   💬 on purpose: the two sit right next to each other and need to read as
   distinct at a glance. */
#feedbackBtn::before     { content: "📝"; }

.switch-dock .btn:hover:not(:disabled),
.switch-dock .btn:focus-visible,
.switch-dock .btn.pad-hover-target:not(:disabled) {
  transform: scale(1.12);
  background: rgba(255,255,255,.16);
  box-shadow: 0 0 0 3px var(--sw-select);
  filter: none;
}

/* Hovered dock icons name themselves above the bar, the way the console's do. */
.switch-dock .btn::after {
  content: attr(data-sw-label);
  position: absolute;
  bottom: calc(100% + 12px);
  left: 50%;
  transform: translateX(-50%);
  padding: 5px 11px;
  border-radius: 8px;
  background: rgba(12,13,16,.95);
  border: 1px solid var(--sw-hairline);
  font-size: .74rem;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .14s ease;
}
.switch-dock .btn:hover::after,
.switch-dock .btn:focus-visible::after,
.switch-dock .btn.pad-hover-target::after { opacity: 1; }

/* font-size:0 on the button would swallow the unread-updates dot too. */
.switch-dock .updates-badge {
  font-size: .62rem;
  top: -2px; right: -2px;
}

/* In the dock the Download menu has to open upward, not downward. */
.switch-dock .download-app-menu {
  top: auto;
  bottom: calc(100% + 14px);
  right: 50%;
  transform: translateX(50%);
  border-radius: 16px;
}

/* The search-home front page strips the bar down to Music/Settings/Profile.
   style.css does that with `.topbar.search-mode #updatesBtn` selectors that
   stop matching once the buttons move out of the top bar, so the dock mirrors
   the class (js/switch-shell.js) and re-states the rule against itself. */
.switch-dock.search-mode #updatesBtn,
.switch-dock.search-mode #playTogetherBtn,
.switch-dock.search-mode #leaderboardsBtn,
.switch-dock.search-mode #achievementsBtn,
.switch-dock.search-mode #friendsBtn,
.switch-dock.search-mode #messagesBtn,
.switch-dock.search-mode .download-app-wrap { display: none; }

.footer {
  padding: 14px 20px 18px;
  font-size: .74rem;
  color: rgba(255,255,255,.34);
}

/* ------------------------------------------------------------------ overlays
   System menus on the console are flat dark sheets with hairline-separated
   rows and one accent per screen — not glassy floating cards. */
.updates-card, .help-card, .overlay-card {
  border-radius: 22px;
  background: #24252b;
  border: 1px solid var(--sw-hairline);
  box-shadow: 0 30px 80px rgba(0,0,0,.6);
}
.updates-header, .help-header { border-bottom: 1px solid var(--sw-hairline); padding-bottom: 14px; }
.updates-header h2, .help-header h2 { font-weight: 800; }
.help-close { border-radius: 50%; }
.help-close:hover, .help-close.pad-hover-target { border-color: var(--sw-select); color: var(--text); }

/* Buttons outside the dock keep their labels but take the console's pill shape. */
.btn {
  border-radius: 999px;
  font-weight: 700;
  background: rgba(255,255,255,.08);
  border: 1px solid transparent;
}
.btn:hover:not(:disabled), .btn.pad-hover-target:not(:disabled) {
  background: rgba(255,255,255,.15);
  border-color: var(--sw-select);
  transform: none;
  box-shadow: none;
}
.btn.primary { background: var(--accent); border-color: var(--accent); }

.back-btn { border-radius: 999px; font-weight: 700; }

/* --------------------------------------------------------------- light theme
   The light presets in settings-panel.js flip --bg/--panel to bright values;
   the few hard-coded dark surfaces above have to follow or they'd stay black
   on a white page. */
html.theme-light .topbar { background: var(--panel); }
html.theme-light .switch-dock {
  background: var(--panel);
  border-top-color: rgba(0,0,0,.1);
}
html.theme-light .switch-dock .btn { background: rgba(0,0,0,.05); }
html.theme-light .switch-dock .btn::after { background: #2a2b30; }
html.theme-light .updates-card,
html.theme-light .help-card,
html.theme-light .overlay-card { background: var(--panel); }
html.theme-light .game-tile .tile-title { color: #3a3c44; }
/* The search field and category chips sit on a white-ish tint that vanishes
   against a light page — they need a real surface and edge there. */
html.theme-light #search,
html.theme-light .cat-chip {
  background: rgba(0,0,0,.05);
  border-color: rgba(0,0,0,.14);
  color: #24252b;
}
html.theme-light .cat-chip:hover,
html.theme-light .cat-chip.pad-hover-target { background: rgba(0,0,0,.1); border-color: #24252b; }
html.theme-light .cat-chip.active { background: #24252b; color: #fff; border-color: #24252b; }
html.theme-light #search:focus { border-color: #24252b; background: rgba(0,0,0,.08); }
html.theme-light .btn { background: rgba(0,0,0,.05); border-color: rgba(0,0,0,.12); }
html.theme-light .btn:hover:not(:disabled), html.theme-light .btn.pad-hover-target:not(:disabled) {
  background: rgba(0,0,0,.1);
  border-color: #24252b;
}

@media (prefers-reduced-motion: reduce) {
  .game-tile, .landing-mode-btn, .search-home-app-tile, .switch-dock .btn { transition: none; }
}

@media (max-width: 720px) {
  /* Too narrow for three columns side by side — let the bar wrap again, and
     drop the battery percentage so the cluster still fits on one line. */
  .topbar { flex-wrap: wrap; }
  .sw-status-batt-pct { display: none; }

  :root { --sw-dock-h: 68px; }
  .switch-dock { gap: 14px; }
  .switch-dock .btn, .switch-dock .download-app-wrap > .btn { width: 46px; height: 46px; }
  .grid { gap: 40px 14px; }
  .game-tile { width: 104px; height: 104px; border-radius: 17px; }
  .game-tile .tile-emoji { font-size: 2.5rem; }
  .sw-status { font-size: .78rem; gap: 8px; }
}

/* --------------------------------------------------------------- entry anim
   style.css's `tile-in` staggers each icon in with a transform, and runs it
   with `animation-fill-mode: both` — which means the animation keeps owning
   `transform` after it finishes, and the :hover/:focus scale rules above can
   never take effect. Same name, opacity only: the stagger still reads, and
   selection gets `transform` back. */
@keyframes sw-tile-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.game-tile, .continue-tile { animation-name: sw-tile-in; }
.continue-tile--last { animation-name: sw-tile-in, pulse-glow; }

/* --------------------------------------------------------------------- keys */
.keys-toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--sw-dock-h) + 18px);
  transform: translate(-50%, 16px);
  z-index: 400;
  padding: 12px 20px;
  border-radius: 999px;
  background: linear-gradient(135deg, #ffd166, #ffb02e);
  color: #241a02;
  font-weight: 800;
  box-shadow: 0 12px 30px rgba(0,0,0,.5);
  opacity: 0;
  transition: opacity .3s ease, transform .3s cubic-bezier(.2,.9,.3,1.2);
  pointer-events: none;
}
.keys-toast.is-in { opacity: 1; transform: translate(-50%, 0); }
.keys-balance { font-size: 2.4rem; font-weight: 900; margin-bottom: 14px; }
.keys-balance span { color: #ffd166; }
.keys-quest {
  border: 1px solid var(--sw-hairline);
  border-radius: 16px;
  padding: 14px 16px;
  background: rgba(255,255,255,.04);
}
.keys-quest-title { margin: 0 0 2px; font-weight: 800; }
.keys-quest-sub { margin: 0 0 10px; font-size: .84rem; color: var(--text-dim); }
.keys-bar { height: 9px; border-radius: 999px; background: rgba(255,255,255,.12); overflow: hidden; }
.keys-bar i { display: block; height: 100%; background: linear-gradient(90deg, #ffd166, #ffb02e); transition: width .3s ease; }
.keys-quest-state { margin: 9px 0 0; font-size: .84rem; }
.keys-note { margin: 14px 0 0; font-size: .84rem; color: var(--text-dim); }
.keys-note-dim { opacity: .7; font-size: .78rem; }

.keys-board { margin-top: 14px; border: 1px solid var(--sw-hairline); border-radius: 16px;
              padding: 14px 16px; background: rgba(255,255,255,.04); }
.keys-board-body { margin-top: 8px; display: flex; flex-direction: column; gap: 3px; }
.keys-row { display: flex; align-items: center; gap: 10px; padding: 6px 9px; border-radius: 9px; font-size: .88rem; }
.keys-row.is-me { background: rgba(255,209,102,.14); }
.keys-row strong { flex: 1; font-weight: 700; }
.keys-rank { width: 26px; text-align: center; font-weight: 800; color: var(--text-dim); }
.keys-score { font-weight: 800; color: #ffd166; }
.keys-dev { margin-top: 14px; }
.keys-dev:empty { display: none; }
.keys-dev-row { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.keys-dev-row input { flex: 1; min-width: 110px; padding: 8px 11px; border-radius: 10px;
                      border: 1px solid var(--border); background: rgba(255,255,255,.06); color: var(--text); font: inherit; }

/* ------------------------------------------------------------- avatar picker
   Shared by js/avatars.js; any game can call window.MimiAvatars.openPicker()
   and gets this same grid, so a second game adopting it costs no new CSS. */
.avatar-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 4px; }
.avatar-opt {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 12px 6px; border-radius: 14px; border: 2px solid var(--sw-hairline);
  background: rgba(255,255,255,.04); cursor: pointer; transition: transform .1s, border-color .15s, background .15s;
}
.avatar-opt:hover, .avatar-opt.pad-hover-target { border-color: var(--accent2); transform: translateY(-2px); }
.avatar-opt.is-selected { border-color: #ffd166; background: rgba(255,209,102,.14); }
.avatar-opt-emoji { font-size: 1.9rem; line-height: 1; }
.avatar-opt-label { font-size: .72rem; color: var(--text-dim); }

/* ------------------------------------------------------------ keyboard escape */
.ke-choices { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; min-height: 54px; }
.ke-choice {
  width: 54px; height: 54px; border-radius: 14px; border: 2px solid #9aa0b4;
  background: linear-gradient(160deg, #ffffff, #dfe3ee); color: #2b2f3a;
  font-size: 1.3rem; font-weight: 800; cursor: pointer; transition: transform .1s, border-color .15s;
}
.ke-choice:hover, .ke-choice.pad-hover-target { border-color: #ffd166; transform: translateY(-2px) scale(1.04); }
.ke-choice:active { transform: scale(0.95); }

/* -------------------------------------------------------------- feedback */
.feedback-cats { display: flex; gap: 8px; margin-bottom: 10px; }
.feedback-message {
  width: 100%; box-sizing: border-box; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: rgba(255,255,255,.06); color: var(--text);
  font: inherit; resize: vertical; margin-bottom: 10px;
}
.feedback-status { min-height: 1.2em; font-size: .84rem; color: var(--text-dim); }
.feedback-inbox { margin-top: 14px; }
.feedback-inbox-list { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; max-height: 320px; overflow-y: auto; }
.feedback-row { border: 1px solid var(--sw-hairline); border-radius: 10px; padding: 10px 12px; background: rgba(255,255,255,.04); }
.feedback-row-top { display: flex; gap: 8px; align-items: center; font-size: .78rem; color: var(--text-dim); margin-bottom: 4px; }
.feedback-row-top strong { color: var(--text); flex: 1; }
.feedback-row-msg { margin: 0; font-size: .9rem; white-space: pre-wrap; word-break: break-word; }

/* ------------------------------------------------------------ video board */
.vb-wrap { display: flex; flex-direction: column; gap: 14px; max-width: 880px; margin: 0 auto; }
.vb-tabs { display: flex; gap: 8px; justify-content: center; }
.vb-board, .vb-watch { display: flex; flex-direction: column; gap: 12px; }
.vb-board-controls { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; }
.vb-filter { display: flex; gap: 6px; }
.vb-post { display: flex; gap: 8px; flex-wrap: wrap; }
.vb-post-url { flex: 2; min-width: 200px; }
.vb-post-title { flex: 1; min-width: 140px; }
.vb-post-hint { margin: -4px 0 0; font-size: .78rem; color: var(--text-dim); min-height: 1.1em; }
.vb-post input, .vb-watch-row input {
  padding: 9px 12px; border-radius: 10px; border: 1px solid var(--border);
  background: rgba(255,255,255,.06); color: var(--text); font: inherit;
}
.vb-watch-row { display: flex; gap: 8px; }
.vb-watch-row input { flex: 1; }
.vb-watch-player { aspect-ratio: 16/9; background: #000; border-radius: 10px; overflow: hidden; }
.vb-watch-player iframe { width: 100%; height: 100%; border: 0; display: block; }
.vb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.vb-card { border: 1px solid var(--sw-hairline); border-radius: 12px; overflow: hidden; background: rgba(255,255,255,.04); }
.vb-thumb-wrap { position: relative; aspect-ratio: 16/9; cursor: pointer; background: #000; }
.vb-thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.vb-play {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 2.2rem; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,.6); background: rgba(0,0,0,.15);
  transition: background .15s;
}
.vb-thumb-wrap:hover .vb-play { background: rgba(0,0,0,.32); }
.vb-thumb-wrap.vb-playing { cursor: default; }
.vb-thumb-wrap.vb-playing iframe { width: 100%; height: 100%; border: 0; display: block; }
.vb-card-body { padding: 10px 12px; display: flex; flex-direction: column; gap: 3px; }
.vb-card-title { font-size: .92rem; }
.vb-card-by { font-size: .76rem; color: var(--text-dim); }

/* ------------------------------------------------------------------- compact
   Compact UI (Settings -> js/ui-density.js): smaller tiles fit more of the
   89 games on screen at once, a smaller dock, and a slimmer strip around
   whichever game is open so more of the window is the actual game. Pure
   sizing/spacing overrides layered on top of everything above — none of
   these touch .game-tile's entrance animation or transform property (see
   CLAUDE.md's animation-fill-mode trap: the base tile-in keyframe owns
   transform for good once `both`-fill finishes, so a hover/compact rule can
   only ever *replace* the value on the same selector switch2.css's own fix
   already targets, never reintroduce transform into the keyframe itself). */
html.compact-ui {
  --sw-dock-h: 56px;
}
html.compact-ui .menu-view { padding: 18px 18px 14px; }
html.compact-ui .grid { gap: 30px 10px; }
html.compact-ui .game-tile { width: 92px; height: 92px; border-radius: 15px; }
html.compact-ui .game-tile:hover,
html.compact-ui .game-tile:focus-visible,
html.compact-ui .game-tile.pad-hover-target { transform: scale(1.07); }
html.compact-ui .game-tile .tile-emoji { font-size: 2rem; }
html.compact-ui .game-tile .tile-title { top: calc(100% + 4px); font-size: .64rem; }

html.compact-ui .switch-dock { gap: 14px; }
html.compact-ui .switch-dock .btn,
html.compact-ui .switch-dock .download-app-wrap > .btn { width: 40px; height: 40px; }
html.compact-ui .switch-dock .btn::before { font-size: 1.12rem; }
html.compact-ui .switch-dock .btn:hover:not(:disabled),
html.compact-ui .switch-dock .btn:focus-visible,
html.compact-ui .switch-dock .btn.pad-hover-target:not(:disabled) { transform: scale(1.08); }
html.compact-ui #keysBtn { font-size: .74rem !important; padding: 0 11px !important; }

html.compact-ui .topbar { padding: 6px 14px; }

/* The strip around whichever game is open — style.css's own rules, not
   this skin's, so these need the same specificity bump the rest of this
   file uses (a plain html.compact-ui prefix) rather than a full rewrite. */
html.compact-ui .game-view { padding: 6px 14px 10px; }
html.compact-ui .game-toolbar { margin-bottom: 6px; gap: 8px; }
html.compact-ui .back-btn { padding: 5px 10px; font-size: .78rem; }
html.compact-ui #gameTitle { font-size: 1.05rem; }
html.compact-ui .game-status { font-size: .8rem; }

/* ----------------------------------------------------------------- FPS meter
   Above everything (including a game's own overlays and Kart Circuit's iframe)
   and click-through except for the click that resets its low-water mark. */
.fps-meter {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 500;
  padding: 3px 9px;
  border-radius: 8px;
  background: rgba(8, 10, 16, .72);
  color: #7ee81c;
  font: 700 12px/1.4 ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  letter-spacing: .04em;
  cursor: pointer;
  pointer-events: auto;
}
.fps-meter[data-tier="ok"] { color: #ffd166; }
.fps-meter[data-tier="bad"] { color: #ff6b6b; }
