/* ==========================================================================
   css/layout.css — the TPPC-style three-column shell + the shell agent's views
   --------------------------------------------------------------------------
   Owner: frontend-shell-agent (phase 2: ui-compact-agent). Structure (built by js/ui/layout.js):

     #app (grid)
       #topbar   game title, online count, drawer toggles (< 900px)
       #left     trainer card (#trainer-card) + navigation (#nav)
       #main     the active view (scrollable)
       #right    Party / Players here / Chat / Notifications panels

   Below 900px the sidebars become slide-over drawers toggled from the top bar.
   The second half of this file styles the views owned by the shell agent
   (auth, starter, map, location, profile, trade, stats page). Battle/team/...
   views are styled in css/game.css by the frontend-game-agent.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Skip link + boot splash
   -------------------------------------------------------------------------- */
.skip-link {
  position: absolute; left: -999px; top: 8px; z-index: 2000;
  background: var(--accent); color: var(--accent-text); padding: 8px 12px; border-radius: var(--radius-sm);
}
.skip-link:focus { left: 8px; }

/* Boot splash sits on the light intro artwork (.app--solo), so it uses the dark navy of the login page. */
.boot-splash {
  min-height: 60vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  color: #1c2740;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8), 0 0 14px rgba(255, 255, 255, 0.9);
  font-family: 'Nunito', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-weight: 700;
}
.boot-splash .spinner { border-color: rgba(28, 39, 64, 0.18); border-top-color: #1c2740; }
/* The loading screen's ball and bar (css/base.css, 25 September) on the light artwork: a navy track. */
.boot-splash .loader-ball { width: 56px; height: 56px; }
.boot-splash .loading-bar {
  background:
    linear-gradient(90deg, transparent, var(--accent) 30%, #ff8a80 50%, var(--accent) 70%, transparent) no-repeat,
    rgba(28, 39, 64, 0.16);
  background-size: 45% 100%, 100% 100%;
}

/* --------------------------------------------------------------------------
   App grid
   -------------------------------------------------------------------------- */
#app, .app {
  display: grid;
  /* Owner feedback (phase 2): sidebars a few px wider, less margin at the screen edges. */
  grid-template-columns: 224px minmax(0, 1fr) 264px;
  /* The header (#topbar) is position:fixed and out of the grid flow; the grid reserves
     exactly its height (--header-h). Keep these in THIS rule: an #id selector beats any
     class-based override further down. */
  grid-template-rows: minmax(0, 1fr);
  grid-template-areas: "left main right";
  /* Owner feedback: the shell fills the window and the left menu runs into the screen edge
     and into the header. The columns carry their own padding, so nothing shifts when one of
     them gets a scrollbar. */
  gap: 0;
  padding: var(--header-h, 52px) 0 0;   /* exactly the header's height: no gap, no overlap */
  height: 100vh;
  height: 100dvh;
}
/* Auth / starter screens: one centred column, no sidebars, no fixed header.
   Owner's intro artwork (assets/ui/intro/, source: _IMG_20260920/login pagina intro.psd)
   fills the screen like the title screen of the games; a soft dark gradient keeps the
   login card readable. The tiny blurred copy paints first while the big JPG loads. */
#app.app--solo, .app.app--solo {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  grid-template-areas: "main";
  padding: 0;
  max-width: none;
  background-color: #cfe6f5;
  /* The artwork (owner, 26 September, DECISIONS 428: the new painting of the trainer and Charmander
     above the bay). New file names on purpose: images are cached a year (DECISIONS 381). Phones get
     the 1280 px version below; the tiny blurred copy paints first on both. */
  --intro-art: url("../assets/ui/intro/intro_wallpaper2.webp");
  /* Layers, top to bottom: white haze behind the centre (the card area), a white fade at
     the bottom for the feature row, the artwork, the tiny blurred copy that paints first. */
  background-image:
    radial-gradient(ellipse 34% 60% at 50% 46%, rgba(255, 255, 255, 0.62) 0%, rgba(255, 255, 255, 0.28) 55%, rgba(255, 255, 255, 0) 100%),
    /* white fades in the top corners so the strap line (left) and PRPG.nl (right) stay readable over the leaves/sky */
    radial-gradient(ellipse 520px 170px at 0% 0%, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.55) 45%, rgba(255, 255, 255, 0) 100%),
    radial-gradient(ellipse 320px 130px at 100% 0%, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 100%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 25%, rgba(255, 255, 255, 0) 62%, rgba(255, 255, 255, 0.86) 100%),
    var(--intro-art),
    url("../assets/ui/intro/intro_wallpaper2_blur.webp");
  background-size: cover, cover, cover, cover, cover, cover;
  background-position: center, left top, right top, center, center 40%, center 40%;
  background-repeat: no-repeat;
}
/* Up to 1280 px wide (phones, tablets, small laptops): the 1280 px version, a third of the bytes.
   PLAN-BANDBREEDTE §7 with DECISIONS 428 chose 900 px; the owner widened it to 1280 on 29 September
   (bandbreedte-fixlijst 9, DECISIONS 525): on those screens the picture is drawn at 1280 or less anyway.
   Only the artwork layer changes. */
@media (max-width: 1280px) {
  #app.app--solo, .app.app--solo { --intro-art: url("../assets/ui/intro/wallpaper2-1280.webp"); }
}

/* While the shell is mounted the document itself must not scroll: the columns below the
   pinned header are the scroll containers (js/ui/layout.js mountLayout adds the class). */
body.shell { height: 100vh; height: 100dvh; overflow: hidden; }
/* The `min-height: 100vh` of css/base.css beat the 100dvh above: on a phone 100vh is the height
   WITHOUT the address bar, so with the bar in view the page was ~56px taller than the screen, and
   the browser (keyboard, focus) could scroll the whole game under the pinned header (DECISIONS 272). */
body.shell { min-height: 0; }
#left, .sidebar-left { grid-area: left; }
#main, .main { grid-area: main; }
#right, .sidebar-right { grid-area: right; }

.main {
  min-height: 0;
  /* The column itself does not scroll any more: .view-scroll below it does, so the
     scrollbar starts under the red view bar instead of beside it (DECISIONS 127). */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Views may bleed into the side padding (the battle arena) — clip it instead of
     scrolling sideways, so the artwork runs up to the sidebars. */
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* The columns touch, so the breathing room lives here — but NOT at the top: the view bar
     has to start exactly at the header, so the top spacing is given to the first child
     instead (see below). `--main-pad` keeps the two in step. */
  --main-pad: 8px;
  padding: 0;
}
/* The scrolling body of the main column. It carries the side padding the column used to
   have, so nothing shifted for the views. */
.view-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  padding: 0 var(--main-pad, 8px) var(--main-pad, 8px);
}
/* The page scrollbar lay white-on-sky-blue at 25 % (the global rule in css/base.css) and was all but
   invisible (owner, 26 September): a soft dark track in the panel colour and a clearer thumb, brighter
   while the mouse is over the page. Chrome 121+ and Firefox read the standard properties (Chrome then
   ignores the -webkit- rules, which stay for Safari). The PC page keeps its wide bar (css/pc-boxes.css). */
.view-scroll { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.55) rgba(18, 26, 44, 0.30); }
.view-scroll:hover { scrollbar-color: rgba(255, 255, 255, 0.75) rgba(18, 26, 44, 0.30); }
.view-scroll::-webkit-scrollbar { width: 10px; }
.view-scroll::-webkit-scrollbar-track { background: rgba(18, 26, 44, 0.30); }
.view-scroll::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.55); border-radius: 999px; }
/* The login / starter screens keep a plain scrolling #main (no scroller inside). */
.main--solo { overflow-y: auto; }
/* Owner (22 September): under the red bar the same 8 px as at the sides - not 16 (the bar used
   to add its own 8 px), and the scroller itself never carries it (it did as the first child of
   the column, which put 16 px of page gradient above the battle). The battle alone starts
   flush: its arena touches the header. The battle rule needs the same (0,3,0) weight. */
.main > *:first-child:not(.view-head),
.view-scroll > *:first-child:not(.view-head) { margin-top: var(--main-pad); }
.main > .view-scroll:first-child { margin-top: 0; }
.view-scroll > .battle:first-child { margin-top: 0; }
.main:focus, .view-scroll:focus { outline: none; }
.main > * + *, .view-scroll > * + * { margin-top: 12px; }
.main--solo { display: flex; flex-direction: column; align-items: center; justify-content: center; }

.sidebar {
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 2px;
  /* The scrollbar lives inside the column and its space is always reserved, so opening a
     long menu never nudges the page content sideways. */
  scrollbar-gutter: stable;
}
/* The main menu is flush with the left edge of the screen and with the header. */
#left, .sidebar-left {
  padding: 0;
  gap: 0;
  /* The column itself carries the menu colour, so the strip the scrollbar sits in is part of
     the menu instead of a light gap next to it (owner feedback). */
  background: var(--panel, rgba(18, 26, 44, 0.88));
}
#left .panel, .sidebar-left .panel { border-radius: 0; border-top-width: 0; border-left-width: 0; box-shadow: none; }
#left .nav, .sidebar-left .nav { min-height: 100%; }
/* The right column is flush too: one continuous surface against the screen edge and the
   header, with the panels as sections separated by a hairline instead of by gaps. */
#right, .sidebar-right {
  padding: 0;
  gap: 0;
  background: var(--panel, rgba(18, 26, 44, 0.88));
}
#right .panel, .sidebar-right .panel {
  border-radius: 0;
  border-width: 0;
  box-shadow: none;
  padding: 8px 10px 10px;
}
#right .panel + .panel, .sidebar-right .panel + .panel { border-top: 1px solid rgba(255, 255, 255, 0.14); }
/* Each section's title gets a rule under it, so the sections read as separate blocks.
   Same quiet label style as the menu group titles on the left (.nav-group-title). */
#right .panel-title, .sidebar-right .panel-title,
#right .panel-head, .sidebar-right .panel-head {
  margin: 0 0 7px;
  padding-bottom: 5px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
#right .panel-title, .sidebar-right .panel-title,
#right .panel-head .panel-title, .sidebar-right .panel-head .panel-title {
  font-size: 8.5px;
  font-weight: 600;
  letter-spacing: 0.12em;
  line-height: 13px;
  color: var(--muted);
  opacity: 0.6;
}
#right .panel-title svg, .sidebar-right .panel-title svg { width: 12px; height: 12px; }
#right .panel-title .badge-count, .sidebar-right .panel-title .badge-count { min-width: 15px; height: 15px; padding: 0 4px; font-size: 9.5px; }
.sidebar-right { scrollbar-width: thin; scrollbar-color: #4a5568 transparent; }
.sidebar-right::-webkit-scrollbar { width: 8px; }
.sidebar-right::-webkit-scrollbar-track { background: transparent; }
.sidebar-right::-webkit-scrollbar-thumb { background: #4a5568; border-radius: 4px; }
.sidebar-right::-webkit-scrollbar-thumb:hover { background: #5d6c82; }
.sidebar .panel { flex: none; }
.sidebar .panel.grow { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }

/* --------------------------------------------------------------------------
   Top bar
   -------------------------------------------------------------------------- */
.topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  /* A constant, not --header-h: that variable is the measured result and would feed back
     into its own input. */
  min-height: 52px;
}
/* Owner feedback: the game header is pinned to the top over the full viewport width,
   without the page's side margins or rounded corners. The grid below leaves room for it
   (see #app padding-top). Only the in-game header (#topbar), not the stats page's. */
#topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  /* Not a fixed height: on a phone with a notch the bar also carries the status-bar strip
     (viewport-fit=cover + env(safe-area-inset-top)), and a long name can make it taller.
     js/ui/layout.js measures what it really is and publishes that as --header-h, which is
     what the grid below reserves - otherwise the header lies over the content (owner,
     21 September, DECISIONS 118). */
  height: auto;
  min-height: 52px;
  /* Owner, 21 September: centred in the bar. This #id rule beats `.topbar { padding: 8px 12px }`,
     so a bare env() here made the top padding 0 on desktop (the notch inset is 0 there) while
     the bottom kept its 8px: the Poké Ball, the location, "n online" and the trainer card all
     sat ~4px too high. The 8px design padding is added back on top of the inset (DECISIONS 143). */
  padding-top: calc(8px + env(safe-area-inset-top, 0px));
  margin: 0;
  border-radius: 0;
  border-left: 0; border-right: 0; border-top: 0;
}
/* The left trainer card moved into the top bar (profile menu); hidden but kept in the DOM. */
#trainer-card { display: none; }

/* Profile menu, far right */
/* display:flex: as a plain block the button stood on a text line and sat 0.4px high (DECISIONS 225). */
.topbar-profile { position: relative; flex: none; display: flex; }
.topbar-profile-btn {
  display: inline-flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 8px 0 4px;
  border: var(--panel-border); border-radius: 999px;
  background: rgba(255, 255, 255, 0.06); color: var(--text);
  cursor: pointer; max-width: 300px;
}
.topbar-profile-btn:hover { background: rgba(255, 255, 255, 0.12); }
/* Owner, 21 September: centred in the bar. `.avatar` (css/base.css) bottom-aligns its image because
   the big standing trainer on the Trainer Card should stand on the floor of its circle; in the
   28px header circle that left the gif hanging at the bottom, so only here it sits in the vertical
   middle. Scoped to the header button: the menu and player-list avatars keep the base rule. */
.topbar-profile-btn .tp-avatar .avatar { width: 28px; height: 28px; align-items: center; }
/* The holder is a plain <span>, so the round avatar stood on a text line with room for
   descenders under it: the box was 33px, the circle sat at the top, 2.4px above the middle of
   the button with the arrow (owner, 23 September, DECISIONS 225). As a flex box it is exactly
   the circle and the button centres it. */
.topbar-profile-btn .tp-avatar { display: inline-flex; }
.topbar-profile-btn .tp-avatar .avatar img { object-position: center; }
.topbar-profile-btn .tp-text { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; line-height: 1.1; }
.topbar-profile-btn .tp-name { font-weight: 700; font-size: 0.82rem; max-width: 160px; }
.topbar-profile-btn .tp-meta { display: inline-flex; align-items: center; gap: 5px; font-size: 0.7rem; color: var(--muted); max-width: 220px; }
.topbar-profile-btn .tp-money { color: var(--accent-light, #f08c8c); font-weight: 600; }
.topbar-profile-btn .tp-loc { max-width: 140px; }
.topbar-profile-btn .tp-chevron { display: inline-flex; color: var(--muted); }
.topbar-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 70;
  min-width: 180px; padding: 6px; display: flex; flex-direction: column; gap: 2px;
}
/* `display: flex` above would beat the browser's [hidden] rule, so restate it (bug: menu could not close). */
.topbar-menu[hidden] { display: none; }
.topbar-menu-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 7px 9px; border: 0; border-radius: var(--radius-xs);
  background: transparent; color: var(--text); font-size: 0.85rem; text-align: left; text-decoration: none; cursor: pointer;
}
.topbar-menu-item:hover { background: rgba(255, 255, 255, 0.1); text-decoration: none; }
.topbar-menu-item svg { width: 15px; height: 15px; opacity: 0.8; }
/* "3 new" on Messages in the trainer menu sits at the right edge of the row. */
.topbar-menu-item .tag { margin-left: auto; }
.topbar-menu-item .tag[hidden] { display: none; }
.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  text-decoration: none;
}
.brand:hover { text-decoration: none; color: var(--accent); }
.brand-ball {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  background: linear-gradient(to bottom, #ef4444 0 45%, #1b1b1b 45% 55%, #f4f6fb 55% 100%);
  position: relative;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.15);
}
.brand-ball::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 8px; height: 8px;
  transform: translate(-50%, -50%); border-radius: 50%;
  background: #fff; box-shadow: 0 0 0 2px #1b1b1b;
}
.topbar .spacer { flex: 1 1 auto; }
.online-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.08); border: var(--panel-border);
  font-size: 0.8rem; color: var(--muted); white-space: nowrap;
}
.online-pill .dot { animation: pulse-dot 2.4s ease-in-out infinite; }
/* "17 online" is a link to who they are (owner, 24 September, DECISIONS 290): the Online now page
   in the game, the ladder's [Online only] on stats.html. No underline; a green edge on hover. */
a.online-pill { text-decoration: none; cursor: pointer; transition: color 0.15s ease, border-color 0.15s ease; }
a.online-pill:hover, a.online-pill:focus-visible { color: var(--text); border-color: rgba(76, 217, 100, 0.5); text-decoration: none; }
.topbar-loc { font-size: 0.85rem; color: var(--muted); display: inline-flex; align-items: center; gap: 6px; }
.topbar-loc svg { width: 14px; height: 14px; color: var(--accent); }
.topbar-toggle { display: none; position: relative; }
/* The speaker (js/ui/layout.js paintSound, DECISIONS 471): all sound off / on. Not on a phone: the
   bar is full there (~374 of 390 px), so the name menu carries "Sound: on / off" instead. */
.topbar-sound { flex: none; color: var(--muted); }
.topbar-sound:hover, .topbar-sound:focus-visible { color: var(--text); }
.topbar-toggle .badge-count { position: absolute; top: -4px; right: -4px; }
/* Something is waiting for an answer (DECISIONS 205): a quiet accent ring around the button
   that opens the notifications, plus a slow pulse on the counter. Loud enough to see on a
   phone, calm enough to ignore while you walk. */
.topbar-toggle.has-pending { border-color: rgba(214, 69, 69, 0.55); box-shadow: 0 0 0 2px rgba(214, 69, 69, 0.22); color: var(--accent-light); }
.topbar-toggle.has-pending .badge-count { animation: pending-pulse 1.8s ease-in-out infinite; }
@keyframes pending-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.18); } }
@media (prefers-reduced-motion: reduce) { .topbar-toggle.has-pending .badge-count { animation: none; } }

/* --------------------------------------------------------------------------
   Trainer card (left) — like the Trainer Card in the games: avatar, name,
   money, badges, current location
   -------------------------------------------------------------------------- */
.trainer-card { display: flex; flex-direction: column; gap: 6px; padding: 10px; }
.trainer-card .tc-top { display: flex; align-items: center; gap: 8px; }
.trainer-card .tc-top .avatar.md { width: 44px; height: 44px; }
.trainer-card .tc-name { font-weight: 800; font-size: 0.95rem; line-height: 1.15; }
.trainer-card .tc-money { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; font-size: 0.85rem; }
.trainer-card .tc-bottom { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
.trainer-card .tc-loc { display: flex; align-items: center; gap: 5px; font-size: 0.75rem; color: var(--muted); min-width: 0; }
.trainer-card .tc-loc svg { width: 12px; height: 12px; color: var(--accent); flex: none; }
.badge-dots { display: flex; gap: 4px; flex-wrap: wrap; flex: none; }
.badge-dot {
  width: 11px; height: 11px; border-radius: 3px; transform: rotate(45deg);
  background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.15);
}
.badge-dot.earned { background: var(--accent); border-color: #ffffff; box-shadow: 0 0 6px rgba(214, 69, 69, 0.6); }
.badge-dots.large .badge-dot { width: 18px; height: 18px; border-radius: 4px; margin: 4px; }

/* --------------------------------------------------------------------------
   Navigation (left) — TPPC-style groups (ARCHITECTURE.md 11.5): a tiny
   uppercase group title followed by 30 px icon + label rows. The active row
   gets a soft accent fill + 3 px accent left border (no solid fill). `soon`
   rows are dimmed placeholders that open the "coming soon" view.
   -------------------------------------------------------------------------- */
.nav { padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.nav .panel-title { padding: 0 6px; margin-bottom: 2px; }
.nav-group { display: flex; flex-direction: column; gap: 1px; }
/* "PRPG <version>" under the last group: GAME_VERSION from js/util.js (DECISIONS 313), as quiet as the group titles. */
.nav-version { padding: 4px 9px 0; font-size: 10px; font-weight: 600; color: var(--muted); opacity: 0.6; text-align: center; }
/* Group titles are just labels (owner feedback): smaller and quieter than the items. */
.nav-group-title {
  margin: 2px 0 1px; padding: 0 9px;
 font-size: 8.5px; font-weight: 600; letter-spacing: 0.01em;
  color: var(--muted); line-height: 13px; opacity: 0.6;
}
.nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 27px;   /* was 30: the menu sits closer together; 24 and 26 were too thin (owner, 23 September) */
  padding: 0 8px 0 9px;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--text);
  font-weight: 600;
  font-size: 12.5px;
  line-height: 1;
  text-align: left;
  text-decoration: none;
  transition: background-color 0.15s, color 0.15s, opacity 0.15s;
}
.nav-item > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-item svg { width: 15px; height: 15px; flex: none; opacity: 0.8; }
.nav-item:hover { background: rgba(255, 255, 255, 0.08); text-decoration: none; }
/* Active row = a lighter background only, no coloured edge (owner feedback). */
.nav-item.active { background: rgba(255, 255, 255, 0.14); color: #fff; }
.nav-item.active svg { opacity: 1; }
.nav-item .badge-count { margin-left: auto; }
.nav-item .tag { margin-left: auto; }
.nav-item.soon { opacity: 0.55; height: 23px; font-weight: 500; }
.nav-item.soon:hover { opacity: 0.8; }
.nav-item.disabled { opacity: 0.4; cursor: not-allowed; }
.nav-item.disabled:hover { background: transparent; }
.nav-item.mobile-only { display: none; }
.nav-group.cols-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px; }
.nav-group.cols-2 .nav-group-title { grid-column: 1 / -1; }
.nav-sep { height: 1px; background: rgba(255, 255, 255, 0.08); margin: 4px 4px; }
/* The left column scrolls (thin bar) when the menu is taller than the window. */
/* Dark grey bar inside the menu, always the same width so nothing shifts when it appears. */
.sidebar-left { scrollbar-width: thin; scrollbar-color: #4a5568 transparent; }
.sidebar-left::-webkit-scrollbar { width: 8px; }
.sidebar-left::-webkit-scrollbar-track { background: transparent; }
.sidebar-left::-webkit-scrollbar-thumb { background: #4a5568; border-radius: 4px; }
.sidebar-left::-webkit-scrollbar-thumb:hover { background: #5d6c82; }
/* --------------------------------------------------------------------------
   Party panel (right) — compact SV party list rows (34 px): 28 px icon,
   name + Lv. on one line, a 6 px HP bar with the numbers to its right.
   -------------------------------------------------------------------------- */
.party-list { display: flex; flex-direction: column; gap: 3px; }
.party-row {
  display: flex; align-items: center; gap: 6px;
  width: 100%; min-height: 34px; padding: 2px 6px;
  border: 1px solid transparent; border-radius: var(--radius-xs);
  background: var(--panel-2); color: inherit; text-align: left;
  transition: background-color 0.15s, border-color 0.15s;
}
.party-row:hover, .party-row:focus-visible { background: var(--panel-3); border-color: rgba(255, 255, 255, 0.14); }
.party-row .party-icon { width: 28px; height: 28px; flex: none; }
.party-row .party-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.party-row .party-name { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; font-size: 0.78rem; font-weight: 700; line-height: 1.1; }
.party-row .party-name span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.party-row .party-lv { color: var(--muted); font-weight: 600; font-size: 0.68rem; white-space: nowrap; }
.party-row .party-hpline { display: flex; align-items: center; gap: 6px; }
.party-row .party-hpline .hpbar { flex: 1 1 auto; height: 6px; }
.party-row .party-hp { font-size: 0.57rem; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; line-height: 1; min-width: 34px; text-align: right; }
.party-row .party-exp { height: 2px; }
.party-row.fainted .party-icon { filter: grayscale(1) brightness(0.6); }
.party-row.fainted .party-name { color: var(--muted); }
.party-row .variant-mark { font-size: 0.65rem; margin-left: 2px; }
#party-panel { padding: 10px 10px 8px; }
#party-panel .panel-title { margin-bottom: 6px; }
/* --------------------------------------------------------------------------
   Players here (right + location view)
   -------------------------------------------------------------------------- */
/* Every trainer name in the game is a link to that trainer's card (trainerLink()
   in js/ui/players.js, DECISIONS 136): white and bold like the text around it,
   accent-coloured and underlined the moment you point at it. */
.trainer-link { color: inherit; font-weight: 700; text-decoration: none; border-bottom: 1px solid transparent; cursor: pointer; }
.trainer-link:hover, .trainer-link:focus-visible { color: var(--accent-light); border-bottom-color: currentColor; }

.player-row { padding: 4px 8px; }
.player-row .player-name { font-weight: 700; font-size: 0.88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.player-row .player-name a { color: inherit; }
.player-row .player-sub { font-size: 0.72rem; color: var(--muted); }
.player-row .actions .btn-sm { padding: 3px 7px; min-height: 26px; }
/* Sidebar variant: 30 px rows, 22 px avatar, name + status on one line, icon buttons. */
#players-panel { padding: 10px 10px 8px; }
#players-panel .panel-title { margin-bottom: 6px; }
/* Players here and Notifications fold to their title line while they have nothing to show ("Nobody
   else is here", "Nothing pending") and open again by themselves the moment someone arrives or
   something comes in - js/ui/players.js and notifications.js toggle .is-empty on every render. No
   button: there is nothing to open. The chat keeps its own collapse (owner, 26 September). */
#players-panel.is-empty #players-list, #notifications-panel.is-empty #notifications-list { display: none; }
#players-panel.is-empty, #notifications-panel.is-empty { padding-bottom: 8px; }
#players-panel.is-empty .panel-title, #notifications-panel.is-empty .panel-title { margin-bottom: 0; padding-bottom: 0; border-bottom: 0; }
#players-panel .list { gap: 3px; }
#players-panel .player-row { min-height: 30px; padding: 2px 6px; gap: 6px; border-radius: var(--radius-xs); }
#players-panel .player-row .avatar { width: 22px; height: 22px; }
#players-panel .player-row .grow { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
#players-panel .player-row .player-name { font-size: 0.78rem; }
#players-panel .player-row .player-sub { font-size: 0.64rem; white-space: nowrap; }
#players-panel .player-row .actions { gap: 3px; }
#players-panel .player-row .actions .btn-sm { padding: 0 5px; min-height: 22px; min-width: 22px; border-radius: 5px; }
#players-panel .player-row .actions .btn-sm svg { width: 12px; height: 12px; }
#players-panel .empty.small { padding: 8px; font-size: 0.75rem; }

/* --------------------------------------------------------------------------
   Chat panel (right sidebar + full page on mobile)
   -------------------------------------------------------------------------- */
.chat-panel { display: flex; flex-direction: column; min-height: 0; }
.chat-panel .panel-body { display: flex; flex-direction: column; gap: 5px; min-height: 0; flex: 1 1 auto; }
.chat-panel.collapsed .panel-body { display: none; }
.chat-panel .collapse-btn svg { transition: transform 0.2s; }
#chat-panel { padding: 8px 10px 10px; }
#chat-panel .panel-head { margin-bottom: 6px; }
#chat-panel.collapsed .panel-head { margin-bottom: 0; }
#chat-panel .collapse-btn { min-width: 20px; min-height: 20px; padding: 2px; }
/* "Open the big chat" (DECISIONS 303): right next to the collapse arrow, not in the middle of the head. */
#chat-panel .chat-big-btn { min-width: 20px; min-height: 20px; padding: 2px; margin-left: auto; }
/* The chat's head is the one right-column head with buttons in it (owner, 3 October: "de chat in de rechter shell is
   niet goed uitgelijnd", DECISIONS 537). The right column's rule gives every .panel-title its own margin, padding and
   rule underneath: inside this head that lifted "Chat" above the buttons with a stray 40 px line under it. Here the
   rule runs under the whole head only, the title sits on the buttons' middle, and the four buttons (wiki, Discord,
   big chat, collapse) are one size instead of 32 px and 22 px side by side. */
#chat-panel .panel-head .panel-title { margin: 0; padding-bottom: 0; border-bottom: 0; }
#chat-panel .panel-head .btn-icon { width: 26px; height: 26px; min-width: 26px; min-height: 26px; padding: 0; }
/* "Wiki" in words, like the button at the bottom of the left menu (owner, 4 October, DECISIONS 549): the same 26 px
   as the icon buttons beside it, a hairline so it reads as a button and not as a second title. */
.chat-wiki-btn { gap: 4px; border-color: rgba(255, 255, 255, 0.14); }
.chat-wiki-btn .chat-wiki-icon { display: inline-flex; }
#chat-panel .panel-head .chat-wiki-btn { height: 26px; min-height: 26px; padding: 0 8px; font-size: 0.72rem; }
#chat-panel .panel-head .chat-wiki-btn svg { width: 13px; height: 13px; }
.chat-panel.collapsed .collapse-btn svg { transform: rotate(-90deg); }
.chat-messages {
  flex: 1 1 auto;
  min-height: 96px;
  max-height: 200px;
  overflow: auto;
  overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 2px;
  padding: 4px 5px;
  background: rgba(0, 0, 0, 0.22);
  border-radius: var(--radius-sm);
  font-size: 0.74rem;
  scroll-behavior: smooth;
}
.chat-panel.mode-page .chat-messages { max-height: none; min-height: 0; }
/* The bar under the sidebar list (DECISIONS 549): drag it, or use the arrow keys, to give the chat more lines. The
   height the reader chose rides on --chat-h (js/ui/chat.js setHeight); a double click clears it. Desktop only: in
   the phone drawer the chat already takes all the height there is (media query further down). */
.chat-resize { position: relative; flex: 0 0 auto; height: 10px; margin: -3px 0; cursor: ns-resize; touch-action: none; border-radius: 4px; }
.chat-resize::before {
  content: ""; position: absolute; left: 50%; top: 50%; width: 36px; height: 4px; margin: -2px 0 0 -18px;
  border-radius: 2px; background: rgba(255, 255, 255, 0.16);
}
.chat-resize:hover::before, .chat-resize:focus-visible::before, .chat-panel.resizing .chat-resize::before { background: var(--accent-light); }
.chat-resize:focus-visible { outline: none; }
.chat-panel.resizing { user-select: none; }
.chat-panel.resizing .chat-messages { scroll-behavior: auto; }
@media (min-width: 900px) {
  #chat-panel.sized .chat-messages { height: var(--chat-h); max-height: none; flex: 0 0 auto; }
}
/* The full-page chat (#chat, also "Open the big chat" in the sidebar): the scroller becomes a column,
   the panel fills it exactly and only the message list scrolls - tabs on top, the input pinned at
   the bottom. Phones had this since DECISIONS 143; on desktop the panel only had a min-height
   (calc(100dvh - 120px)), so with a full chat it grew with every message and the input sat below
   all of them: a long scroll down to type (owner, 25 September, DECISIONS 351). 240px keeps the list
   usable in a very low window; the scroller scrolls then. */
.view-scroll:has(> .chat-panel.mode-page) { display: flex; flex-direction: column; }
/* The same without `:has()` (DECISIONS 310): Firefox before 121, Samsung Internet before 20 and
   older Android WebViews drop the rule above. js/router.js marks the scroller with the view name.
   Its own rule: one unknown selector in a list would throw away the whole rule. */
.view-scroll[data-view="chat"] { display: flex; flex-direction: column; }
.chat-panel.mode-page { flex: 1 1 auto; min-height: 240px; }
.chat-msg { line-height: 1.3; word-break: break-word; padding: 1px 4px; border-radius: 4px; }
.chat-msg:hover { background: rgba(255, 255, 255, 0.04); }
.chat-msg .chat-time { color: rgba(169, 179, 199, 0.55); font-size: 0.64rem; margin-right: 4px; font-variant-numeric: tabular-nums; }
.chat-msg .chat-user { color: var(--accent-light); font-weight: 600; margin-right: 3px; cursor: pointer; }
.chat-msg .chat-user:hover { text-decoration: underline; }
.chat-msg.mine .chat-user { color: #9df0ab; }
/* The chat sprite (Trainer Shop, DECISIONS 472, docs/PLAN-CHAT-SPRITE.md look A - the owner's pick): a 20 px
   square with the sender's trainer in front of the name. The line stays one line of text (it grows from 15 to
   20 px); the whole figure, feet on the bottom edge like .avatar in css/base.css, so all 50 avatars fit.
   The Prestige tier rings it in its colour (.prestige-1..5 set --prestige further down). */
.chat-sprite {
  display: inline-flex; align-items: flex-end; justify-content: center; overflow: hidden;
  width: 20px; height: 20px; margin-right: 4px; vertical-align: -6px;
  border-radius: 5px; background: rgba(255, 255, 255, 0.08);
}
.chat-sprite img { width: 100%; height: 100%; object-fit: contain; object-position: center bottom; image-rendering: pixelated; }
.chat-sprite[class*="prestige-"] { box-shadow: 0 0 0 1.5px var(--prestige); }
.chat-msg.system { color: var(--muted); font-style: italic; }
/* A line from the game itself, e.g. "X opened a raid on Zapdos …" (DECISIONS 340): small, dark and
   half see-through with a little flag, so the trainers' own messages stand out between them. Full
   strength again under the mouse. */
.chat-msg.game-line {
  display: flex; align-items: baseline; gap: 5px;
  margin: 1px 0; padding: 1px 6px;
  border-left: 2px solid rgba(169, 179, 199, 0.18);
  font-size: 0.66rem; font-style: normal;
  color: rgba(169, 179, 199, 0.55);
  opacity: 0.8;
  transition: opacity 150ms ease, color 150ms ease;
}
.chat-msg.game-line:hover { opacity: 1; color: var(--muted); }
.chat-msg.game-line .chat-game-icon { flex: none; display: inline-flex; color: rgba(255, 214, 102, 0.45); translate: 0 1px; }
/* The clan's own game lines (Clan Lands, Headquarters, level, medals; DECISIONS 585): a neutral steel-blue notice in
   the Clan tab - full strength and a touch bigger than the dimmed Global game lines, yet not the members' white. */
.chat-msg.game-line.clan-news {
  margin: 2px 0; padding: 2px 7px;
  border-left-color: rgba(122, 160, 204, 0.7);
  border-radius: 0 4px 4px 0;
  background: rgba(122, 160, 204, 0.1);
  font-size: 0.7rem;
  color: #9fb6d3;
  opacity: 1;
}
.chat-msg.game-line.clan-news:hover { color: #b9cbe2; }
.chat-msg.game-line.clan-news .chat-game-icon { color: rgba(159, 182, 211, 0.85); }
.chat-form { display: flex; gap: 5px; }
.chat-form input { min-height: 28px; padding: 4px 8px; font-size: 0.76rem; }
.chat-form .btn { min-height: 28px; padding: 4px 10px; font-size: 0.76rem; }
/* "New messages ↓" (js/ui/chat.js, js/ui/battlechat.js, DECISIONS 310): shown when lines come in
   while the reader is scrolled up. It is the last child of the message list and sticks to the
   list's bottom edge, so it needs no wrapper around the list; a tap jumps to the newest line. */
.chat-new {
  position: sticky; bottom: 4px; z-index: 1;
  flex: none; align-self: center;
  display: inline-flex; align-items: center;
  margin-top: 4px; padding: 4px 12px; min-height: 28px;
  border: 0; border-radius: 999px;
  background: var(--accent-strong); color: var(--accent-text);
  font-size: 0.72rem; font-weight: 700; line-height: 1.2; white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  cursor: pointer;
}
.chat-new[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Notifications panel
   -------------------------------------------------------------------------- */
.notif-item { display: flex; flex-direction: column; gap: 4px; padding: 6px 8px; align-items: stretch; }
.notif-item .notif-text { font-size: 0.8rem; line-height: 1.3; }
.notif-item .notif-text b { color: var(--accent-light); }
.notif-item .notif-sub { font-size: 0.68rem; color: var(--muted); }
.notif-item .actions .btn-sm { padding: 2px 8px; min-height: 24px; font-size: 0.74rem; }
#notifications-panel { padding: 10px 10px 8px; }
#notifications-panel .panel-title { margin-bottom: 6px; }
#notifications-panel .list { gap: 4px; }
#notifications-panel .empty.small { padding: 8px; font-size: 0.75rem; }
.notif-item .actions { justify-content: flex-end; }
.notif-item.incoming { border-left: 3px solid var(--accent); }
.notif-item.outgoing { border-left: 3px solid rgba(255, 255, 255, 0.2); }
/* Dismiss / put back (owner, 25 September): a small × in the card's corner; dismissed cards wait,
   still working, in a folded "Dismissed (N)" block under the list. */
.notif-item.hideable { position: relative; padding-right: 26px; }
.notif-item .notif-hide { position: absolute; top: 3px; right: 3px; width: 22px; height: 22px; min-height: 0; padding: 0; color: var(--muted); }
.notif-item .notif-hide:hover, .notif-item .notif-hide:focus-visible { color: var(--text); }
.notif-dismissed { margin-top: 2px; }
.notif-dismissed > summary { cursor: pointer; font-size: 0.72rem; color: var(--muted); padding: 4px 2px; user-select: none; }
.notif-dismissed > summary:hover { color: var(--text); }
.notif-dismissed[open] > summary { margin-bottom: 4px; }
.notif-dismissed > .notif-item { opacity: 0.8; margin-bottom: 4px; }
.notif-dismissed > .notif-item:hover { opacity: 1; }
.notif-poke { display: inline-flex; align-items: center; gap: 4px; }
.notif-poke img { width: 28px; height: 28px; }

/* --------------------------------------------------------------------------
   Battle banner (sticky, shown when a battle is active but not on screen)
   -------------------------------------------------------------------------- */
.battle-banner {
  position: fixed;
  left: 50%;
  top: 12px;
  transform: translateX(-50%);
  z-index: 900;
  display: flex; align-items: center; gap: 12px;
  padding: 8px 10px 8px 14px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-weight: 700;
  box-shadow: 0 8px 24px rgba(229, 72, 77, 0.45);
  animation: pop-in 0.2s ease;
  max-width: calc(100vw - 24px);
}
.battle-banner[hidden] { display: none; }
.battle-banner .dot { background: #fff; animation: pulse-dot 1.2s ease-in-out infinite; }
.battle-banner .btn { background: #fff; color: var(--danger); border-color: #fff; }
.battle-banner .btn:hover { background: #ffe9ea; }

/* --------------------------------------------------------------------------
   Drawer backdrop (mobile)
   -------------------------------------------------------------------------- */
.drawer-backdrop {
  display: none;
  position: fixed; inset: 0; z-index: 790;
  background: rgba(6, 10, 20, 0.55);
  animation: fade-in 0.15s ease;
}
.drawer-backdrop.open { display: block; }

/* --------------------------------------------------------------------------
   Responsive: < 900px sidebars become slide-over drawers
   -------------------------------------------------------------------------- */
@media (max-width: 899px) {
  #app, .app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    grid-template-areas: "main";
    gap: 10px;
    padding: var(--header-h, 52px) 0 0;   /* below the fixed header; .main carries the padding */
  }
  #app.app--solo, .app.app--solo { grid-template-rows: minmax(0, 1fr); grid-template-areas: "main"; padding: 10px; }
  .topbar-toggle { display: inline-flex; }
  .topbar-loc { display: none; }
  .topbar-profile-btn .tp-text { display: none; }   /* phones: avatar only */
  .sidebar {
    position: fixed;
    top: var(--header-h, 52px); bottom: 0;
    width: min(86vw, 320px);
    padding: 12px;
    z-index: 800;
    background: rgba(12, 18, 32, 0.96);
    box-shadow: var(--shadow);
    transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
  .sidebar .panel { box-shadow: none; }
  /* No glass blur on the panels inside the right drawer (DECISIONS 310, same call as 286): the
     drawer itself is 88 % opaque and blurs the game already, so the panels blurred that blur once
     more - invisible, but redone on every frame while the drawer or the chat list scrolls. */
  .sidebar-right .panel { -webkit-backdrop-filter: none; backdrop-filter: none; }
  .sidebar-left { left: 0; transform: translateX(-105%); border-radius: 0 var(--radius) var(--radius) 0; }
  .sidebar-right { right: 0; transform: translateX(105%); border-radius: var(--radius) 0 0 var(--radius); }
  .sidebar.open { transform: none; }
  .nav-item.mobile-only { display: flex; }
  .chat-messages { max-height: 240px; }
  .chat-resize { display: none; }
  /* Owner (21 September, DECISIONS 143): on the phone the chat sits at the BOTTOM of the right
     drawer, so the party rows, the "Players here" buttons and the Accept / Decline buttons keep
     their place while messages come in. Party, players and notifications keep their own height
     at the top (DECISIONS 125). The open chat takes whatever height is left, at least 40vh so it
     stays usable when the panels above it are tall (the drawer then scrolls); only the message
     list grows and it scrolls inside the panel, with the input under it. A collapsed chat is just
     its title row. */
  .sidebar-right .chat-panel, .sidebar-right .panel.grow { flex: 0 0 auto; }
  .sidebar-right .chat-panel:not(.collapsed) { flex: 1 1 auto; min-height: 40vh; }
  .sidebar-right .chat-messages { flex: 1 1 auto; min-height: 0; max-height: none; }
  /* A swipe that reaches the end of the message list carries on into the scroller around it
     (DECISIONS 310). The party, "Players here" and the notifications are usually taller than the
     drawer minus the chat's 40vh, so the drawer scrolls and the chat sat half under its bottom edge,
     right under the thumb. The list already stood at its newest line and `overscroll-behavior:
     contain` (the desktop rule above, so the mouse wheel does not move the column) swallowed the
     swipe: nothing moved, and the rest of the chat could not be pulled into view. The post-battle
     chat box is small inside a long result page: a swipe on it could not reach Continue. The
     drawer and the page scroller keep `contain` themselves, so the swipe stops there. */
  .sidebar-right .chat-messages, .battle-chat-messages { overscroll-behavior-y: auto; }
  /* The input sticks to the bottom edge of the drawer while the drawer itself is scrolled, and
     clears the home indicator of phones without a bottom bezel (env(safe-area-inset-bottom)). */
  #right .chat-panel { padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); }
  .sidebar-right .chat-form {
    position: sticky;
    bottom: env(safe-area-inset-bottom, 0px);
    padding-top: 5px;
    background: #121a2c;   /* the drawer colour, so a stuck input never shows messages through it */
  }
  /* The full-page chat (#chat, the "Chat" row in the phone menu) fills the scroller at every width
     (the rules above the media queries, DECISIONS 143, 351). On a phone it may shrink all the way
     (the keyboard takes half the screen) and its input clears the home indicator. */
  .chat-panel.mode-page { min-height: 0; padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
}

@media (max-width: 480px) {
  #app, .app { padding: var(--header-h, 52px) 0 0; gap: 0; }
  #app.app--solo, .app.app--solo { padding: 8px; }
  .topbar { padding: 6px 8px; min-height: 48px; }
  /* Owner, 21 September: centred in the bar. Same as the #topbar rule above, with this size's
     6px design padding on top of the notch inset. The bottom is set here too (owner,
     23 September, DECISIONS 225): `.panel { padding: 12px }` a few lines down also hits the
     header (it is `panel topbar`), and it made the bottom 12px against 6px on top. Every icon,
     the logo, "17 online" and the trainer button sat 3.5px above the middle on a phone. The
     id rule wins over that class rule; the sides keep the 12px they had. 5px + the 1px line
     under the bar = the 6px on top. */
  #topbar { padding-top: calc(6px + env(safe-area-inset-top, 0px)); padding-bottom: 5px; }
  /* MASTER LY-01 (bugronde 28-09): logo + "PRPG" + pill + flag + avatar + menu button needed ~393 px, so on a
     360-375 px phone the party/chat button and its red badge (the only sign of a challenge or a trade on a
     phone) fell off the right edge. The word goes, the ball stays. */
  .brand span.brand-text { display: none; }
  .online-pill { padding: 3px 8px; font-size: 0.72rem; }
  .topbar-sound { display: none; }   /* the name menu's "Sound: on / off" instead (DECISIONS 471) */
  .main > * + *, .view-scroll > * + * { margin-top: 8px; }
  .panel { padding: 12px; }
  .battle-banner { top: auto; bottom: 12px; }
  .toast-container { bottom: 64px; }
  /* MASTER LY-03: in the red bar of a gym town the place name is what you need on a phone; the tags (Kanto,
     Town, Wild Pokémon, Gym) repeat what the column under the map says, and the buttons keep their icon. */
  .scene-head .tag { display: none; }
  .scene-head .btn .lbl { display: none; }
  /* MASTER KZ-30: the town name takes the card's full width on a phone ("Flintridge …" was cut at 62 %); the
     leader's portrait sits behind it (z-index -1) and the text shadow keeps it readable. */
  .gym-card-town { max-width: none; }
  /* MASTER LY-04: "Jouer maintenant — c'est gratuit" ran off the wiki's top bar on a phone (page 450 px wide);
     the button may wrap and never grows past 45 % of the bar. */
  .wiki-top .wiki-play { white-space: normal; max-width: 45%; padding: 4px 8px; font-size: 0.78rem; line-height: 1.2; text-align: center; }
}

/* ==========================================================================
   Shell-owned views
   ========================================================================== */

/* --------------------------------------------------------------------------
   Generic view header (title + subtitle + actions)
   -------------------------------------------------------------------------- */
.view-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.view-head h2 { margin: 0; font-size: 1.35rem; display: flex; align-items: center; gap: 10px; }
.view-head h2 svg { width: 22px; height: 22px; color: var(--accent); }
.view-head .sub { color: var(--muted); font-size: 0.9rem; margin-top: 2px; }
.view-head .actions { display: flex; gap: 6px; flex-wrap: wrap; }

.error-panel { border-color: rgba(229, 72, 77, 0.5); }
.error-panel h3 { color: #ff9a9d; }

/* "Reconnecting…" (js/poll.js, MASTER AC-10): a fixed strip under the top bar from the second failed poll until
   one succeeds - a toast at the third failure was gone before anyone read it. Above the toasts, below modals. */
#reconnect-bar {
  position: fixed; left: 0; right: 0; top: var(--header-h, 52px); z-index: 60;
  padding: 6px 12px; text-align: center; font-size: 0.85rem; font-weight: 600;
  color: #fff; background: rgba(229, 72, 77, 0.92); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

/* --------------------------------------------------------------------------
   Static text pages (about.html, legal.html — DECISIONS 115): one readable column with the
   game's panel and top bar, numbered steps, a small footer line of links. No JavaScript.
   -------------------------------------------------------------------------- */
.text-page { max-width: 760px; margin: 0 auto; padding: 16px 16px 32px; display: flex; flex-direction: column; gap: 14px; }
.text-page > main { padding: 22px 26px; line-height: 1.6; }
.text-page h1 { font-size: 1.5rem; margin-bottom: 0.5em; }
.text-page h2 { font-size: 1.05rem; margin: 1.5em 0 0.45em; color: var(--accent-light); }
.text-page .lead { font-size: 1.02rem; }
.text-page ol { list-style: decimal; padding-left: 1.5em; margin: 0 0 0.75em; }
.text-page ol li { margin: 0.3em 0; }
.text-page main a { text-decoration: underline; text-underline-offset: 2px; }
/* The wiki (wiki.html): a wider column, a pill row that jumps to the sections, small tables. */
.text-page.wiki { max-width: 860px; }
.wiki-nav { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 10px; }
.text-page main .wiki-nav a { text-decoration: none; padding: 4px 11px; border-radius: 999px; font-size: 0.84rem; font-weight: 600; background: rgba(255, 255, 255, 0.07); border: 1px solid rgba(255, 255, 255, 0.12); color: var(--text); }
.text-page main .wiki-nav a:hover { background: rgba(255, 255, 255, 0.14); }
.text-page h3 { font-size: 0.95rem; margin: 1.1em 0 0.3em; }
.text-page ul { list-style: disc; padding-left: 1.3em; margin: 0 0 0.75em; }
.text-page ul li { margin: 0.3em 0; }
.wiki-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; margin: 0.4em 0 0.9em; }
.wiki-table th, .wiki-table td { text-align: left; vertical-align: top; padding: 6px 8px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
.wiki-table th { color: var(--muted); font-weight: 600; font-size: 0.82rem; }
/* "Latest changes": a version a day makes a long list, so a smaller letter (owner, 3 October). */
.wiki-changes { font-size: 0.86rem; line-height: 1.45; }
.wiki-changes li { margin-bottom: 0.35em; }
/* The pictures on the wiki (owner, 23 September: "not just dull text"): the game's own sprites,
   a battle scene from the battle backdrops, the rare forms through css/game.css filters. */
.wiki .sprite { image-rendering: pixelated; }
.wiki-parade { display: flex; justify-content: center; align-items: flex-end; flex-wrap: wrap; gap: 4px 14px; padding: 6px 8px 2px; }
.wiki-parade .sprite { height: 72px; width: auto; max-width: 96px; object-fit: contain; }
.wiki-parade.safari { margin: 4px 0 8px; padding: 10px 8px 6px; border-radius: 12px; background: linear-gradient(180deg, rgba(60, 140, 80, 0.28), rgba(30, 90, 50, 0.18)); }
.wiki-figure { float: right; width: 236px; margin: 4px 0 10px 18px; border-radius: 12px; overflow: hidden; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); }
.wiki-figure figcaption { padding: 8px 10px 9px; font-size: 0.8rem; line-height: 1.4; color: var(--muted); }
.wiki-figure figcaption b { color: var(--text); }
.wiki-figure figcaption .variant-label { font-size: 9.5px; vertical-align: middle; }
/* The twenty-seven starters as on the game's starter screen (DECISIONS 349; Kalos .. Paldea since
   docs/PLAN-GEN6-9.md C2): a column per region (one <span>), Grass, Fire and Water top to bottom.
   Nine columns do not fit the 236 px float, so this figure spans the text: one row of nine on a
   computer; on a phone Kanto .. Unova fill a row and Kalos .. Paldea stand centred under them. */
.wiki-figure.wiki-starters-fig { float: none; width: auto; margin: 4px 0 12px; }
.wiki-starters { display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-end; gap: 10px 14px; padding: 14px 8px 6px; }
.wiki-starters > span { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.wiki-starters .sprite { height: 52px; width: auto; }
@media (max-width: 760px) { .wiki-starters { column-gap: 0; } .wiki-starters > span { flex: 0 0 20%; } }
@media (max-width: 480px) { .wiki-starters .sprite { height: 40px; } }
.wiki-daily { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 16px 8px 8px; }
.wiki-daily .sprite { height: 64px; width: auto; }
.wiki-boss-art { height: 150px; background-size: cover; background-position: center; display: flex; align-items: center; justify-content: center; }
.wiki-boss-art .sprite { height: 96px; width: auto; filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.55)); }
.wiki-map { display: block; width: 100%; height: auto; border-radius: 12px; margin: 6px 0 12px; border: 1px solid rgba(255, 255, 255, 0.1); }
/* The battle scene: the 256 x 152 backdrop scaled up, platforms and sprites placed in percent. */
.wiki-arena { position: relative; width: 100%; max-width: 560px; aspect-ratio: 256 / 152; margin: 6px auto 14px; border-radius: 12px; overflow: hidden; background: #0b1020; border: 1px solid rgba(255, 255, 255, 0.12); }
.wiki-arena img { position: absolute; image-rendering: pixelated; }
.wiki-arena-bg { inset: 0; width: 100%; height: 100%; }
.wiki-plat.opp { right: 5%; top: 40%; width: 42%; }
.wiki-plat.you { left: -6%; bottom: 3%; width: 66%; }
.wiki-mon.opp { right: 13%; top: 8%; width: 27%; }
.wiki-mon.you { left: 9%; bottom: 8%; width: 36%; }
.wiki-hud { position: absolute; min-width: 40%; padding: 5px 10px 7px; border-radius: 8px; background: rgba(18, 26, 44, 0.86); border: 1px solid rgba(255, 255, 255, 0.16); color: #fff; font-size: 0.78rem; line-height: 1.3; }
.wiki-hud b { font-weight: 700; }
.wiki-hud span { float: right; color: rgba(255, 255, 255, 0.75); }
.wiki-hud small { display: block; text-align: right; font-size: 0.7rem; color: rgba(255, 255, 255, 0.75); }
.wiki-hud.opp { left: 4%; top: 6%; }
.wiki-hud.you { right: 4%; bottom: 20%; }
.wiki-bar { height: 6px; margin-top: 4px; border-radius: 3px; background: rgba(255, 255, 255, 0.16); overflow: hidden; }
.wiki-bar i { display: block; height: 100%; border-radius: 3px; background: #4cd964; }
.wiki-arena-log { position: absolute; left: 0; right: 0; bottom: 0; padding: 5px 10px; background: rgba(18, 26, 44, 0.9); color: #fff; font-size: 0.74rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The battle on the wiki is the real battle screen (DECISIONS 209): wiki.html writes the same
   markup js/ui/battle.js builds, so css/game.css draws the status boxes, the green HP bars, the
   blue EXP bar, the text panel, the attack bar with Bag / Pokemon / Run and the party strip.
   Only what belongs to the game shell is undone here. (The .wiki-arena / .wiki-hud / .wiki-bar
   rules above drew the old hand-made mock-up and are no longer used by any page.) */
/* Wider than the article column (owner, 23 September): in the 860 px column the six party
   slots were cramped and the fourth attack dropped to a line of its own. The battle breaks out
   of the column and centres itself - a widescreen screenshot - so the attack bar keeps its four
   cards side by side and the party strip its six slots, exactly like the game. */
.wiki-fight {
  width: min(1120px, calc(100vw - 40px));   /* 40 px: room for the scrollbar, so the page never scrolls sideways */
  margin: 10px 0 18px 50%;
  transform: translateX(-50%);
}
.wiki-fight .battle {
  --main-pad: 0;            /* the arena bleeds into the column padding in the game; not here */
  --arena-h: 300px;
  --msg-overlap-collapsed: 18px;
  --msg-w: min(380px, 32%);
  /* In the game the panels are 88 % opaque over the green page gradient. On the wiki the block
     sticks out of the article column, so that last 12 % showed the column border running straight
     through the attack bar and the party strip (owner, 23 September). Everything is solid here:
     one dark frame with solid panels on it. */
  --panel: #1e2840;
  background: #0d1524;
  padding: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
/* Four moves, always on one row (the game lets them wrap with the window; here the picture
   must show what the attack bar looks like). The phone block below puts them back in a 2 x 2. */
.wiki-fight .move-bar { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.wiki-fight .battle-arena { margin: 0; border-radius: var(--radius); }
/* Nothing here is clickable except the moves and the three buttons, so no "you can press me" cues. */
.wiki-fight .battle-msg, .wiki-fight .battle-msg-log, .wiki-fight .party-slot { cursor: default; }
.text-page main .wiki-fight a { text-decoration: none; }
/* The caption stays in the article column, not across the whole wide battle (owner, 25 September:
   the text ran outside the column in the middle). The figure is centred on that column, so a
   caption as wide as the column's text - 860 px minus 2 x 16 px page padding and 2 x 26 px panel
   padding = 776 px, or the window minus those 84 px on a narrower screen - lines up with it. */
.wiki-fight figcaption {
  max-width: min(776px, calc(100vw - 84px));
  margin: 10px auto 0;
  font-size: 0.82rem; line-height: 1.5; color: var(--muted);
}
.wiki-fight figcaption b { color: var(--text); }
@media (max-width: 600px) {
  /* In the game the battle fills the screen and the part under the arena scrolls on its own;
     inside an article it simply flows with the page. */
  .wiki-fight { width: 100%; margin: 10px 0 14px; transform: none; }
  .wiki-fight .battle { height: auto; min-height: 0; --arena-h: 220px; }
  .wiki-fight .battle-below { overflow: visible; padding: 8px 0 0; }
  /* Same specificity trick as the rule above, the other way round: 2 x 2 like the game. */
  .wiki-fight .move-bar { grid-template-columns: 1fr 1fr; }
}
/* The exp split and the rare forms as rows of little cards. */
.wiki-split { display: grid; grid-template-columns: 1fr 1fr 1.6fr; gap: 8px; margin: 4px 0 10px; }
.wiki-share { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 10px 6px 8px; border-radius: 12px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); text-align: center; }
.wiki-share.ko { background: rgba(76, 217, 100, 0.1); border-color: rgba(76, 217, 100, 0.35); }
.wiki-share .sprite { height: 56px; width: auto; }
.wiki-share b { font-size: 1.1rem; margin-top: 2px; }
.wiki-share span { font-size: 0.76rem; color: var(--muted); }
/* The Exp. Share section of the wiki (DECISIONS 259): the holder's cell in gold, the item under its sprite. */
.wiki-share.holder { background: rgba(246, 201, 69, 0.1); border-color: rgba(246, 201, 69, 0.4); }
.wiki-share-item { width: 26px; height: 26px; margin-top: -8px; image-rendering: pixelated; }
.wiki-item { width: 30px; height: 30px; vertical-align: -7px; image-rendering: pixelated; }
.wiki-bench { display: flex; gap: 2px; align-items: flex-end; height: 56px; }
.wiki-bench .sprite { height: 40px; width: auto; }
.wiki-forms { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; margin: 4px 0 12px; text-align: center; }
.wiki-form { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 10px 4px 9px; border-radius: 12px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); }
.wiki-form .sprite { width: 84px; height: 72px; object-fit: contain; }
.wiki-form-name { font-size: 0.78rem; font-weight: 600; }
.wiki-item { width: 22px; height: 22px; vertical-align: middle; margin-right: 2px; image-rendering: pixelated; }
.wiki-items { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 8px; }
.wiki-items img { width: 30px; height: 30px; image-rendering: pixelated; padding: 3px; border-radius: 8px; background: rgba(255, 255, 255, 0.06); }
.wiki-trainers { display: flex; justify-content: center; align-items: flex-end; flex-wrap: wrap; gap: 6px 14px; margin: 4px 0 10px; }
.wiki-trainers figure { margin: 0; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.wiki-trainers img { height: 80px; width: auto; image-rendering: pixelated; }
.wiki-trainers figcaption { font-size: 0.74rem; color: var(--muted); }
.wiki-trainers figure.boss figcaption { color: #ffd45e; font-weight: 600; }
.wiki-badges { display: flex; justify-content: center; flex-wrap: wrap; gap: 6px; margin: 4px 0 6px; }
.wiki-badges img { width: 44px; height: 44px; image-rendering: pixelated; filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5)); }
@media (max-width: 700px) {
  .wiki-figure { float: none; width: 100%; margin: 6px 0 10px; }
  .wiki-forms { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .wiki-split { grid-template-columns: 1fr 1fr; }
  .wiki-share.bench { grid-column: 1 / -1; }
  .wiki-hud { font-size: 0.68rem; padding: 3px 7px 5px; }
  .wiki-hud.you { bottom: 22%; }
}
.page-footer {
  display: flex; justify-content: center; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 4px 0; font-size: 0.82rem; color: var(--muted);
}
@media (max-width: 480px) {
  .text-page { padding: 8px 8px 24px; gap: 10px; }
  .text-page > main { padding: 16px 16px 18px; }
}

/* --------------------------------------------------------------------------
   Auth view (login / register)
   -------------------------------------------------------------------------- */
/* Login / register screen after the owner's intro design (light theme on the artwork;
   the rest of the game stays dark). Font: Pocket Monk (project root → assets/fonts). */
@font-face {
  font-family: 'Pocket Monk';
  /* woff2 first (the same glyphs in a smaller box, fixlijst 14, DECISIONS 525); the ttf for a browser without woff2. */
  src: url('../assets/fonts/PocketMonk.woff2') format('woff2'), url('../assets/fonts/PocketMonk.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
/* Body font of the intro page: Nunito (rounded, friendly — the look of the design),
   self-hosted variable font (weights 200-1000, OFL licence). */
@font-face {
  font-family: 'Nunito';
  src: url('../assets/fonts/Nunito-Variable.woff2') format('woff2'), url('../assets/fonts/Nunito-Variable.ttf') format('truetype');
  font-weight: 200 1000;
  font-style: normal;
  font-display: swap;
}
.main--solo:has(.auth-page) { align-items: stretch; justify-content: flex-start; }
/* Same for the starter screens: `justify-content: center` pushed the top of a panel taller than
   the window out of reach (owner, 23 September, step 2 with 50 outfits). .starter-view centres
   itself with margin: auto, which falls back to the top when it does not fit. */
.main--solo:has(.starter-view) { justify-content: flex-start; }
.auth-page {
  --ink: #1c2740;               /* dark navy used for text on the light page */
  --ink-soft: #52607a;
  --auth-blue: #2f6fe4;
  --auth-yellow: #f6c542;
  --auth-yellow-2: #f0b400;
  position: relative;
  width: 100%;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  color: var(--ink);
  padding: 22px 24px 18px;
  font-family: 'Nunito', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
.auth-topnav {
  position: absolute; top: 20px; left: 28px;
  display: flex; align-items: center; gap: 12px;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.16em; color: var(--ink-soft);
}
.auth-topnav i { font-style: normal; opacity: 0.45; font-size: 9px; }
/* Every strap-line word links into about.html (DECISIONS 115). They must not read as links:
   no underline and no link colour, just the strap-line grey that darkens on hover. */
.auth-topnav-link { color: inherit; text-decoration: none; }
.auth-topnav-link:visited { color: inherit; }
.auth-topnav-link:hover, .auth-topnav-link:focus-visible { color: var(--ink); text-decoration: none; }
.auth-topright {
  position: absolute; top: 16px; right: 28px;
  font-size: 1.1rem; font-weight: 800; color: var(--ink); text-decoration: none;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
}
.auth-topright:hover { text-decoration: none; color: var(--auth-blue); }
/* Under PRPG.nl one row: the language flag, then "Wiki" (DECISIONS 440). z-index keeps the open
   language menu above the login card (its backdrop-filter makes it a layer of its own). */
.auth-corner {
  position: absolute; top: 46px; right: 28px; z-index: 3;
  display: flex; align-items: center; gap: 8px;
}
/* "Wiki" (DECISIONS 209): a small white pill in the light style of the intro page - the dark .btn
   of the shell would sit on the wallpaper like a patch. Same 30 px as the flag pill next to it. */
.auth-wiki-btn {
  display: inline-flex; align-items: center; gap: 5px;
  height: 30px; padding: 0 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.86);
  border: 1px solid rgba(28, 39, 64, 0.16);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
  color: var(--ink); font-size: 0.82rem; font-weight: 700; letter-spacing: 0.02em; text-decoration: none;
}
.auth-wiki-btn:hover, .auth-wiki-btn:focus-visible { background: #fff; color: var(--auth-blue); text-decoration: none; }
.auth-wiki-btn svg { width: 14px; height: 14px; }
.auth-center { margin: auto 0; display: flex; flex-direction: column; align-items: center; width: 100%; padding-top: 28px; }
.auth-logo { text-align: center; margin-bottom: 14px; }
.auth-brand {
  margin: 0;
  font-family: 'Pocket Monk', 'Segoe UI Black', 'Arial Black', sans-serif;
  font-weight: 400;
  font-size: clamp(64px, 11vw, 118px);
  line-height: 0.95;
  letter-spacing: 0.02em;
  color: var(--ink);
  transform: rotate(-2deg);
  /* White outline drawn behind the fill, plus a soft drop shadow, like the design. */
  -webkit-text-stroke: 8px #fff;
  paint-order: stroke fill;
  text-shadow: 0 6px 0 rgba(255, 255, 255, 0.9), 0 14px 30px rgba(28, 39, 64, 0.22);
}
.auth-tagline {
  margin: 8px 0 0;
  font-size: clamp(0.95rem, 1.3vw, 1.05rem);
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
  text-shadow: 0 0 14px rgba(255, 255, 255, 0.85);
}
.auth-card {
  width: min(430px, 100%);
  padding: 24px 28px 20px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.74);
  border: 1px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 24px 60px rgba(20, 40, 80, 0.20), inset 0 1px 0 #fff;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.auth-title { margin: 0 0 16px; text-align: center; font-size: 1.28rem; font-weight: 700; letter-spacing: -0.01em; color: var(--ink); }
.auth-field { margin-bottom: 10px; }
.auth-field .hint { color: var(--ink-soft); }
.auth-field .error { color: #c62828; }
.auth-input { position: relative; display: flex; align-items: center; }
.auth-input-icon { position: absolute; left: 14px; display: inline-flex; color: #7b879c; pointer-events: none; }
.auth-input input {
  width: 100%; height: 46px;
  padding: 0 44px 0 42px;
  border-radius: 14px;
  border: 1px solid #dfe4ec;
  background: #f1f3f7;
  color: var(--ink);
  font-size: 0.95rem;
}
.auth-input input::placeholder { color: #8a94a6; }
.auth-input input:focus { outline: none; border-color: var(--auth-yellow-2); box-shadow: 0 0 0 3px rgba(246, 197, 66, 0.35); background: #fff; }
.auth-field.invalid .auth-input input { border-color: #e04b4b; }
.auth-eye {
  position: absolute; right: 8px; width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 10px; background: transparent; color: #7b879c; cursor: pointer;
}
.auth-eye:hover { background: rgba(28, 39, 64, 0.06); color: var(--ink); }
.auth-submit {
  width: 100%; height: 48px; margin-top: 6px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 0; border-radius: 14px; cursor: pointer;
  background: linear-gradient(180deg, var(--auth-yellow) 0%, var(--auth-yellow-2) 100%);
  color: var(--ink); font-weight: 800; font-size: 1.02rem; letter-spacing: 0.01em;
  box-shadow: 0 8px 18px rgba(240, 180, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.6);
  transition: transform 0.12s, box-shadow 0.12s, filter 0.12s;
}
.auth-submit:hover { filter: brightness(1.04); transform: translateY(-1px); }
.auth-submit:active { transform: translateY(0); }
.auth-submit.loading { opacity: 0.7; pointer-events: none; }
.auth-submit-arrow { font-size: 1.3rem; line-height: 1; margin-top: -2px; }
.auth-alert { margin-bottom: 10px; padding: 8px 10px; border-radius: 10px; background: rgba(224, 75, 75, 0.12); color: #a12626; font-size: 0.85rem; }
.auth-alert.hidden { display: none; }
.auth-switch { margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(28, 39, 64, 0.1); text-align: center; }
.auth-switch-q { font-size: 0.82rem; font-weight: 500; color: var(--ink-soft); }
.auth-switch-link { display: inline-block; margin-top: 4px; font-size: 0.95rem; font-weight: 700; color: var(--auth-blue); text-decoration: none; }
.auth-switch-link:hover { text-decoration: underline; }
.auth-bottom { width: 100%; margin-top: 18px; display: flex; flex-direction: column; align-items: center; gap: 12px; }
/* Feature strip: four glass pills on the white fade, evenly spaced, quiet typography. */
.auth-features { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 14px; }
.auth-feature {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 18px 6px 6px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.8);
  box-shadow: 0 6px 16px rgba(20, 40, 80, 0.08);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.auth-feature-icon {
  width: 38px; height: 38px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
}
.auth-feature-icon svg { width: 18px; height: 18px; }
.auth-feature-icon.tone-green { background: #dff2e3; color: #2e7d32; }
.auth-feature-icon.tone-blue  { background: #e1ebfb; color: #2f6fe4; }
.auth-feature-icon.tone-red   { background: #fbe1e1; color: #d64545; }
.auth-feature-icon.tone-gold  { background: #fcefc8; color: #c98f00; }
.auth-feature-text { display: flex; flex-direction: column; line-height: 1.2; }
.auth-feature-title { font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em; color: var(--ink); }
.auth-feature-sub { font-size: 0.74rem; font-weight: 500; color: var(--ink-soft); }
.auth-footer-line {
  width: min(720px, 100%); padding-top: 10px;
  border-top: 1px solid rgba(28, 39, 64, 0.14);
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  font-size: 0.82rem; font-weight: 500; color: var(--ink-soft);
}
.auth-domain { font-weight: 800; color: var(--ink); }
.auth-version { font-size: 0.7rem; font-weight: 600; opacity: 0.7; }   /* GAME_VERSION from js/util.js (DECISIONS 313) */
.auth-stats-link { font-size: 0.74rem; color: var(--ink-soft); }
.auth-footer-links { display: flex; align-items: center; gap: 8px; }
.auth-footer-sep { font-size: 0.7rem; opacity: 0.55; }
@media (max-width: 899px) {
  .auth-topnav { position: static; margin-bottom: 8px; justify-content: center; flex-wrap: wrap; }
  .auth-topright { top: 12px; right: 16px; font-size: 0.95rem; }
  .auth-center { padding-top: 6px; }
  .auth-brand { -webkit-text-stroke: 5px #fff; }
  .auth-card { padding: 20px 18px 16px; }
  .auth-features { gap: 10px 18px; }
}
/* Phones (owner, 22 September). Two things had to give: the strap line ran under PRPG.nl in
   the corner, and the four pills pushed the closing line below the fold. The strap line is
   smaller and keeps clear of the domain; the pills become a tight 2 x 2 block. */
@media (max-width: 600px) {
  .auth-page { padding: 16px 14px 14px; }
  .auth-topnav {
    width: 100%; padding-right: 88px; gap: 7px;
    font-size: 10px; letter-spacing: 0.08em; margin-bottom: 4px;
  }
  .auth-topnav i { font-size: 7px; }
  .auth-topright { top: 10px; right: 12px; font-size: 0.85rem; }
  /* In the flow on the right, but still `relative`: the anchor of the language menu (with `static`
     it opened below the whole page, owner 24 September). */
  .auth-corner { position: relative; top: auto; right: auto; z-index: 5; align-self: flex-end; gap: 6px; margin: 0 0 4px; }
  .auth-wiki-btn { height: 28px; padding: 0 10px; font-size: 0.74rem; }
  .auth-logo { margin-bottom: 10px; }
  .auth-tagline { margin-top: 4px; font-size: 0.88rem; line-height: 1.3; }
  .auth-bottom { margin-top: 12px; gap: 8px; }
  .auth-features { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; width: 100%; }
  .auth-feature { padding: 4px 10px 4px 4px; gap: 7px; }
  .auth-feature-icon { width: 28px; height: 28px; }
  .auth-feature-icon svg { width: 14px; height: 14px; }
  .auth-feature-title { font-size: 0.62rem; letter-spacing: 0.08em; }
  .auth-feature-sub { font-size: 0.66rem; }
  .auth-footer-line { padding-top: 8px; font-size: 0.74rem; }
  .auth-stats-link { font-size: 0.7rem; }
}
/* The narrowest phones drop the one-liners under the four titles. */
@media (max-width: 420px) {
  .auth-feature-sub { display: none; }
  .auth-feature { padding: 3px 8px 3px 3px; }
}
@media (prefers-reduced-motion: reduce) { .auth-submit { transition: none; } }
/* (The dark-theme .auth-alert / .auth-footer of the first login screen used to follow here and
   silently overrode the light-card colours above: pink error text on the white card.) */

/* --------------------------------------------------------------------------
   Starter view ("Professor Oak's lab") + avatar picker
   -------------------------------------------------------------------------- */
.starter-view { width: 100%; max-width: 860px; margin: auto; }
.starter-intro { text-align: center; padding: 20px 18px; }
.starter-intro h1 { font-size: 1.7rem; }
.starter-intro p { color: var(--muted); max-width: 560px; margin: 0 auto; }
/* The trainer's name in the greeting stands out: bold and white (owner, 23 September). */
.starter-intro .starter-trainer { color: #fff; font-weight: 700; }
/* Twenty-seven starters in one view (owner, 24 September, DECISIONS 349; Kalos .. Paldea joined with
   docs/PLAN-GEN6-9.md C2): a column per region - Kanto to Paldea - with the rows Grass, Fire and
   Water, like the three Poké Balls on the table in every game. The cards are small (sprite + name);
   the picked one shows its types, level and blurb in the box above the table. On a computer the nine
   columns stand in one row, so nothing needs scrolling. */
.starter-table { --starter-gap: 8px; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--starter-gap); margin-top: 12px; }
.starter-col { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.starter-col h4 { margin: 0; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.starter-card {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 6px 4px 8px; text-align: center; cursor: pointer; color: inherit;
  border: 2px solid transparent; border-radius: var(--radius);
  background: var(--panel-2);
  transition: transform 0.15s, border-color 0.15s, background-color 0.15s;
}
.starter-card:hover { transform: translateY(-2px); background: var(--panel-3); }
.starter-card.selected { border-color: var(--accent); background: var(--accent-soft); }
.starter-card .sprite { width: 68px; height: 68px; }   /* a notch up from 56 (owner, 24 September) */
.starter-card .starter-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.8rem; font-weight: 700; }
/* The picked one up close. It keeps its height while empty, so the table never jumps. */
.starter-detail {
  display: flex; align-items: center; gap: 12px; min-height: 92px; max-width: 560px;
  margin: 12px auto 0; padding: 8px 14px; text-align: left;
  border-radius: var(--radius); background: var(--panel-2);
}
.starter-detail.is-empty { justify-content: center; gap: 8px; color: var(--muted); }
.starter-detail .sprite { width: 84px; height: 84px; flex: none; }
.starter-detail .starter-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.starter-detail .starter-head { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; }
.starter-detail h3 { margin: 0; font-size: 1.05rem; }
.starter-detail .blurb { font-size: 0.8rem; color: var(--muted); line-height: 1.35; }
.starter-detail .types { display: flex; gap: 4px; }
/* Same size as the badges in the party list (game.css .team-row), a notch under the base size. */
.starter-detail .type-badge { font-size: 9.5px; padding: 1px 7px; }
.starter-actions { display: flex; justify-content: center; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
/* Step 2: the 50 outfits scroll inside their own box, so the heading and the button stay in view,
   and the trainers are a size up from the Trainer Card picker (56 px there, 76 px here). */
.starter-avatars { text-align: left; max-height: min(52vh, 480px); overflow-y: auto; padding: 4px; }
.starter-avatars .avatar-grid { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); }
.starter-avatars .avatar-option img { width: 76px; height: 76px; }

.avatar-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; }
.avatar-option {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 4px; border: 2px solid transparent; border-radius: var(--radius-sm);
  background: var(--panel-2); color: inherit; cursor: pointer;
  transition: border-color 0.15s, background-color 0.15s, transform 0.15s;
}
.avatar-option:hover { background: var(--panel-3); transform: translateY(-2px); }
.avatar-option.selected { border-color: var(--accent); background: var(--accent-soft); }
.avatar-option img { width: 56px; height: 56px; object-fit: contain; object-position: bottom; image-rendering: pixelated; }
/* Bought in the Trainer Shop (DECISIONS 489): first in the list, with a gold edge. */
.avatar-option.bought { border-color: rgba(246, 201, 69, 0.55); }
.avatar-option.bought.selected { border-color: var(--accent); }
.avatar-shop-link { margin: 12px 0 0; text-align: center; }
.avatar-option span { font-size: 0.7rem; color: var(--muted); text-align: center; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

/* Nine columns of 80 px cards need about 840 px of screen. Narrower, Kanto .. Unova fill a row and
   Kalos .. Paldea stand centred under them (flex, so the short row can centre); the button stays
   above the table (owner, 23 September). */
@media (max-width: 860px) {
  .starter-table { display: flex; flex-wrap: wrap; justify-content: center; }
  .starter-col { flex: 0 0 calc((100% - 4 * var(--starter-gap)) / 5); }
}
/* Phones: the same five columns, a size smaller (five cards of about 64 px fit a 390 px screen). */
@media (max-width: 640px) {
  .starter-table { --starter-gap: 4px; }
  .starter-col { gap: 4px; }
  .starter-col h4 { font-size: 0.6rem; letter-spacing: 0.04em; }
  .starter-card { padding: 4px 2px 6px; }
  .starter-card .sprite { width: 50px; height: 50px; }
  .starter-card .starter-name { font-size: 0.66rem; white-space: normal; line-height: 1.15; }   /* two lines: "Charmander" fits (MASTER WR-10) */
  .starter-detail { gap: 10px; padding: 8px 10px; }
  .starter-detail .sprite { width: 64px; height: 64px; }
}

/* --------------------------------------------------------------------------
   Town map view (Sword/Shield style flat map with node overlay)
   -------------------------------------------------------------------------- */
:root {
  --map-town: #ffb74d;
  --map-route: #8bc34a;
  --map-forest: #43a047;
  --map-cave: #a1887f;
  --map-water: #4fc3f7;
  --map-gym: #ba68c8;
}
/* The map must always be fully visible without scrolling (owner feedback): the SVG is
   scaled by the available height (viewport minus top bar, view bar and paddings) while
   keeping its 2:1 aspect ratio; the legend and the location info float over the map. */
.map-view { padding: 8px; }
.map-view .map-wrap {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: linear-gradient(160deg, #a5d6a7, #81c784 60%, #66bb6a);
  border: var(--panel-border-strong);
  box-shadow: var(--shadow-sm);
  /* 2:1, the world.svg canvas (1400x700); a region drawn on another canvas sets --map-ratio
     (js/ui/map.js applyCanvas). The height cap only bites on a short, wide window;
     normally the map simply fills the column edge to edge. */
  width: min(100%, calc((100dvh - 150px) * var(--map-ratio, 2)));
  margin: 0 auto;
}
.map-svg { display: block; width: 100%; height: auto; user-select: none; -webkit-tap-highlight-color: transparent; }
/* Zoom (review, 21 September): on a phone the 2:1 canvas is 180 px tall and its labels 3 px, so
   the map opens zoomed to 2.4x in a pannable window centred on where you stand; the Zoom button
   in the view bar toggles it (desktop too). The legend and the info card stay put on the wrap. */
.map-view .map-scroll { overflow: hidden; }
.map-view .map-wrap.zoomed .map-scroll {
  overflow: auto; max-height: calc(100dvh - 150px); overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch; scrollbar-width: thin; touch-action: pan-x pan-y;
}
.map-view .map-wrap.zoomed .map-svg { width: 240%; max-width: none; }
.main > .view-head .btn.active { background: rgba(255, 255, 255, 0.34); border-color: rgba(255, 255, 255, 0.6); }
.map-view .map-legend.overlay {
  position: absolute; left: 8px; bottom: 8px; z-index: 3;
  padding: 4px 8px; border-radius: var(--radius-sm);
  background: rgba(18, 26, 44, 0.78); color: #dfe4ee; font-size: 0.7rem; gap: 6px 10px;
  backdrop-filter: blur(3px);
}
.map-view .map-info.overlay {
  position: absolute; left: 8px; top: 8px; z-index: 3; max-width: 280px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  background: rgba(18, 26, 44, 0.86); border: var(--panel-border); box-shadow: var(--shadow-sm);
  font-size: 0.82rem; backdrop-filter: blur(3px);
  flex-direction: column; align-items: flex-start; gap: 6px;
}
.map-view .map-info.overlay .map-info-players { font-size: 0.75rem; }
@media (max-width: 899px) {
  .map-view .map-wrap { width: 100%; }
  .map-view .map-info.overlay { position: static; max-width: none; margin-top: 8px; }
  /* MASTER LY-02 (bugronde 28-09): the legend stayed absolute and lay over the info card's buttons
     (Ferry, View location, Details, Travel here) once the card went static on a phone. */
  .map-view .map-legend.overlay { position: static; margin-top: 8px; }
  /* MASTER KZ-23: Kalos, Alola, Galar and Paldea have their names in the picture; ours doubled them here. */
  .map-view .map-svg.labels-baked .node-label { display: none; }
}
/* Compact one-line view bar: title, hint and actions on a single row. */
.view-head.compact { margin-bottom: 6px; min-height: 32px; }
.view-head.compact h2 { font-size: 1rem; gap: 8px; }
.view-head.compact h2 svg { width: 18px; height: 18px; }
.view-head.compact .sub { display: inline; margin: 0 0 0 10px; font-size: 0.78rem; font-weight: 400; }
.view-head.compact .actions .btn { padding: 4px 10px; font-size: 0.8rem; }

/* View bar: every view's header is a slim solid accent strip that runs from sidebar to
   sidebar, sticks under the fixed top header and stays over the content while it scrolls.
   The negative margin cancels .main's padding exactly, so it has no margin on any side.
   Only a header that is a direct child of the main column becomes a bar — a `.view-head`
   inside a panel (the PC's box switcher) keeps its normal look. */
.main > .view-head {
  position: static;
  z-index: 20;
  /* It sits above the scrolling body now, so it needs no sticky and no negative margins:
     it simply spans the full width of the column (DECISIONS 127). */
  flex: none;
  margin: 0;   /* flush on the content below (owner, 22 September; was --main-pad) */
  padding: 2px 10px;
  min-height: 30px;
  align-items: center;
  flex-wrap: nowrap;
  background: var(--accent-strong, #b83232);
  box-shadow: 0 4px 12px rgba(8, 12, 24, 0.3);
  color: #fff;
}
/* No gap between the bar and the scroller: the generic sibling spacing (.main > * + *, 12px)
   used to stack under it and the first panel sat 28px under the bar. */
.main > .view-head + .view-scroll { margin-top: 0; }
/* A wrapper around title + subtitle (the shop) lines up on the same row. */
.main > .view-head > div:first-child { display: flex; align-items: center; gap: 8px; min-width: 0; }   /* center, not baseline: the icon in the h2 skews its baseline (owner, 22 September) */
/* flex-shrink: 0 (MASTER LY-03, bugronde 28-09): the h2 is a flex box itself, so its text never got the "…" and the
   title was simply cut ("Tidecre", "Pol", "Gy"). Now the title keeps its width; the subtitle and the tags give way
   and the tools wrap under it. */
.main > .view-head h2 { color: #fff; flex-shrink: 0; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 0.88rem; gap: 7px; }
/* Fixed icon box + fixed gap: the title starts on the same pixel on every page. */
.main > .view-head h2 svg { color: #fff; flex: none; display: block; width: 16px; height: 16px; }
.main > .view-head .scene-count { color: rgba(255, 255, 255, 0.85); font-size: 0.72rem; }
.main > .view-head .scene-count::before { content: none; }
.main > .view-head .sub { display: inline; margin: 0; font-size: 0.72rem; font-weight: 400; color: rgba(255, 255, 255, 0.85); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.main > .view-head .muted, .main > .view-head .small { color: rgba(255, 255, 255, 0.85); }
.main > .view-head .view-tools, .main > .view-head .actions { flex: none; gap: 6px; align-items: center; }
.main > .view-head .tag { background: rgba(255, 255, 255, 0.2); color: #fff; border-color: transparent; }
.main > .view-head .btn,
.main > .view-head .actions .btn,
.main > .view-head .view-tools .btn {
  min-height: 22px;
  padding: 1px 8px;
  font-size: 0.72rem;
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.32);
  color: #fff;
  box-shadow: none;
}
.main > .view-head .btn:hover { background: rgba(255, 255, 255, 0.28); }
/* A call to action in the bar (Gym) is white on red so it still reads as the main button. */
.main > .view-head .btn-primary { background: #fff; border-color: #fff; color: var(--accent-strong, #b83232); font-weight: 700; }
.main > .view-head .btn-primary:hover { background: #ffe9e9; }
.main > .view-head .btn svg { width: 13px; height: 13px; }
/* Narrow screens: the hint drops out so the bar always stays one line. */
@media (max-width: 899px) { .main > .view-head .sub { display: none; } }
/* The Pokémon summary on a phone (bug round 28 September, PK-01): Lead / Follow me / Send to PC / Release
   did not fit beside "‹ Back to Team" and the ‹ › arrows, the tools row (flex: none) slid over the arrows
   and a tap on › made a new lead or opened the Trainer Shop. The tools take a second row of the bar. */
@media (max-width: 480px) {
  .main > .view-head.pkm-head { flex-wrap: wrap; row-gap: 4px; padding-bottom: 4px; }
  .main > .view-head.pkm-head > div:first-child { flex: 1 1 100%; }
  .main > .view-head.pkm-head .view-tools { flex: 1 1 100%; justify-content: flex-end; flex-wrap: wrap; }
}
.map-svg text { font-family: var(--font); pointer-events: none; }
.map-edge { stroke: rgba(255, 255, 255, 0.55); stroke-width: 6; stroke-linecap: round; stroke-dasharray: 2 12; fill: none; }
.map-edge.is-open { stroke: var(--accent); stroke-dasharray: none; stroke-width: 5; opacity: 0.95; }
.map-node { cursor: pointer; transition: transform 0.15s; transform-box: fill-box; transform-origin: center; }
.map-node:hover .node-shape, .map-node:focus-visible .node-shape { filter: brightness(1.12); }
.map-node:focus { outline: none; }
.map-node:focus-visible .node-shape { stroke: #fff; stroke-width: 3; }
.map-node .node-shape { stroke: rgba(20, 30, 50, 0.55); stroke-width: 2; transition: filter 0.15s; }
.map-node.is-far .node-shape { opacity: 0.85; }
.map-node.is-locked .node-shape { opacity: 0.6; }
.map-node.is-locked { cursor: not-allowed; }
.map-node.is-neighbor .node-shape { stroke: var(--accent); stroke-width: 3; }
.map-node.is-current .node-shape { stroke: #fff; stroke-width: 3; }
.map-node .node-label {
  font-size: 13px; font-weight: 700; fill: #fff; text-anchor: middle;
  paint-order: stroke; stroke: rgba(20, 30, 50, 0.85); stroke-width: 3px; stroke-linejoin: round;
}
.map-node .node-icon { fill: none; stroke: #1b2233; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.map-node .node-num { font-size: 14px; font-weight: 800; fill: #1b2233; text-anchor: middle; dominant-baseline: central; }
.map-node .node-lock { fill: #1b2233; }
.map-node .node-players text { font-size: 10px; font-weight: 700; fill: #fff; text-anchor: middle; dominant-baseline: central; }
.map-node .node-players .count-bubble { fill: var(--panel); stroke: #fff; stroke-width: 1.5; }
.map-node .node-players .mini-avatar { fill: #5aa9ff; stroke: #fff; stroke-width: 1.5; }
.map-node .node-players .mini-avatar.f { fill: #f95587; }
.map-node .node-players .mini-avatar.g { fill: #8bc34a; }
/* Current node: a white pulsing ring plus a small accent dot (phase 2 accent swap). */
.map-pulse { fill: none; stroke: #fff; stroke-width: 3; opacity: 0.9; transform-box: fill-box; transform-origin: center; animation: map-pulse 1.8s ease-out infinite; }
.map-you-dot { fill: var(--accent); stroke: #fff; stroke-width: 2; }
@keyframes map-pulse { 0% { transform: scale(0.7); opacity: 0.95; } 100% { transform: scale(1.9); opacity: 0; } }
.map-you-img { image-rendering: pixelated; pointer-events: none; }

.map-legend { display: flex; flex-wrap: wrap; gap: 8px 14px; font-size: 0.78rem; color: var(--muted); align-items: center; }
.map-legend .lg { display: inline-flex; align-items: center; gap: 6px; }
.map-legend .sw { width: 14px; height: 14px; border-radius: 50%; border: 2px solid rgba(20, 30, 50, 0.55); display: inline-block; }
.map-legend .sw.pill { border-radius: 4px; width: 20px; height: 12px; }
.map-legend .sw.diamond { transform: rotate(45deg); border-radius: 2px; width: 12px; height: 12px; }
.map-legend .sw.ring { border-color: var(--accent); border-width: 3px; background: transparent; }
.map-legend .sw.you { border-color: #fff; border-width: 3px; background: var(--accent); }
.map-legend .sw.locked { background: #777; opacity: 0.6; }
.map-legend .sw.badge { background: radial-gradient(circle at 35% 30%, #ffe08a, #d69a1f); border-color: rgba(255, 255, 255, 0.6); }
/* Port town (owner, 25 September): the town swatch with the anchor the map node wears. */
.map-legend .sw.port { background: var(--map-town); border-radius: 4px; display: inline-flex; align-items: center; justify-content: center; color: #1b2233; }

/* Owner (21 September, DECISIONS 128): the map has to answer two questions without reading -
   where am I, and where is a gym. */
.map-badge { filter: grayscale(1) brightness(0.32) contrast(1.5) drop-shadow(0 1px 1px rgba(0, 0, 0, 0.6)); }
.map-badge.earned { filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.55)); }
.map-here-ring { fill: none; stroke: var(--accent); stroke-width: 3; opacity: 0.95; }
.map-here rect { fill: var(--accent-strong, #b83232); stroke: rgba(255, 255, 255, 0.85); stroke-width: 1.5; }
.map-here text { fill: #fff; font-size: 11px; font-weight: 800; text-anchor: middle; paint-order: stroke; }
/* Everything you cannot reach yet sinks into the background, so the road you can walk stands out. */
.map-node.is-locked { opacity: 0.62; }
.map-node.is-locked .node-label { fill: #93a0b8; }
.map-node.is-far { opacity: 0.88; }

.map-info { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.map-info .map-info-name { font-weight: 700; }
.map-info .map-info-players { color: var(--muted); font-size: 0.85rem; }

/* Regions (owner, 22 September): Kanto .. Paldea are maps of their own, one tab per region in
   the view bar next to the title (the actions keep the right edge); the hint makes way for
   them. They used to scroll sideways out of sight when the bar ran out of room; now a tab that
   does not fit (a long translation) wraps instead, and below 1700 px the dropdown takes over. */
.main > .view-head .map-regions { display: flex; flex: 1 1 0; flex-wrap: wrap; gap: 4px; min-width: 0; }
.main > .view-head .map-regions[hidden] { display: none; }
.main > .view-head:has(.map-regions:not([hidden])) .sub { display: none; }
.map-region-tab { white-space: nowrap; flex: none; }
/* A region the ferry does not sail to yet (DECISIONS 250): quieter, with a lock; still clickable to look. */
.map-region-tab.locked, .gym-tab.locked { opacity: 0.7; }
.map-region-tab.locked svg, .gym-tab.locked svg { vertical-align: -1px; }
/* The white dot on the tab of the region you stand in. */
.map-region-tab .map-tab-you { display: inline-block; width: 6px; height: 6px; margin-left: 6px; border-radius: 50%; background: #fff; box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35); vertical-align: middle; }
/* On a phone six tabs did not fit the bar: the dropdown twin (map.js renderTabs) takes their
   place, the strip hides (owner, 22 September). base.css gives a <select> 100 % width and 38 px.
   Ten tabs since Gen 6-9 (27 September) need some 820 px beside the title and the actions: a main
   column of ~1220 px, a 1700 px window with both sidebars (the owner's 1536 x 731 at 125 % has 741).
   Below that the dropdown takes their place as well, so the bar keeps its one line (map.js: the
   map is never pushed below the fold) instead of wrapping to three rows. */
.main > .view-head .map-region-select { display: none; flex: 0 1 auto; width: auto; min-width: 0; margin-right: auto; min-height: 26px; padding: 2px 6px; border-radius: 8px; font-size: 0.8rem; font-weight: 600; background: rgba(255, 255, 255, 0.18); color: #fff; border: 1px solid rgba(255, 255, 255, 0.35); }
@media (max-width: 1700px) {
  .main > .view-head .map-regions { display: none; }
  .main > .view-head .map-region-select:not([hidden]) { display: block; }
}
@media (max-width: 700px) {
  /* Zoom and Where I am keep their icons only, so the dropdown gets the room. */
  .main > .view-head .map-btn-label { display: none; }
}
/* Sea lanes: a dashed line from a port to the edge of its map with a "Ferry" flag, red like an
   open road while you stand on that port. The wide transparent twin is the click target. */
.map-ferry { cursor: pointer; }
.map-ferry:focus { outline: none; }
.map-ferry .map-ferry-hit { stroke: transparent; stroke-width: 24; fill: none; }
.map-ferry .map-ferry-lane { stroke: rgba(227, 242, 253, 0.85); stroke-width: 4; stroke-linecap: round; stroke-dasharray: 10 8; fill: none; }
.map-ferry.is-open .map-ferry-lane { stroke: var(--accent); }
.map-ferry .map-ferry-label rect { fill: rgba(18, 26, 44, 0.82); stroke: rgba(255, 255, 255, 0.7); stroke-width: 1; }
.map-ferry.is-open .map-ferry-label rect { fill: var(--accent-strong, #b83232); }
.map-ferry:hover .map-ferry-label rect, .map-ferry:focus-visible .map-ferry-label rect { stroke: #fff; stroke-width: 2; }
.map-ferry .map-ferry-label text { fill: #fff; font-size: 11px; font-weight: 800; text-anchor: middle; dominant-baseline: central; }
/* Two lines since 25 September: "⛴ Ferry to" over the destination ("Halcyon", "5 regions"). */
.map-ferry .map-ferry-label .map-ferry-dest { font-size: 12px; fill: #ffe08a; }
/* The info card's line under a port: "The road ends here. The ferry sails to ...". */
.map-info .map-info-ferry { font-size: 0.85rem; margin-top: 2px; }
.map-view .map-info.overlay .map-info-ferry { font-size: 0.75rem; }
/* The boat on a port node (bottom-right, opposite the lock). */
.map-node .node-boat circle { fill: #1b2233; stroke: #fff; stroke-width: 1.5; }
.map-node .node-boat path { fill: #fff; }
.map-legend .sw.ferry { width: 18px; height: 0; border-radius: 0; border: 0; border-top: 3px dashed #e3f2fd; background: transparent; }
/* The picker: "Sail to Kanto — Pallet Town", one button per boat, floating top-right like the
   info card floats top-left; on a phone it drops under the map as the info card does. */
.map-view .map-ferry-menu {
  position: absolute; right: 8px; top: 8px; z-index: 4; min-width: 220px; max-width: 300px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  background: rgba(18, 26, 44, 0.92); border: var(--panel-border); box-shadow: var(--shadow-sm);
  font-size: 0.82rem; backdrop-filter: blur(3px);
}
.map-ferry-menu .map-ferry-list { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.map-ferry-menu .map-ferry-list .btn { justify-content: flex-start; }
@media (max-width: 899px) {
  .map-view .map-ferry-menu { position: static; max-width: none; margin-top: 8px; }
}

/* --------------------------------------------------------------------------
   Location view
   -------------------------------------------------------------------------- */
.loc-hero { display: flex; gap: 14px; align-items: flex-start; }
.loc-hero .loc-icon {
  flex: none; width: 56px; height: 56px; border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--map-route); color: #1b2233; box-shadow: var(--shadow-sm);
}
.loc-hero .loc-icon svg { width: 30px; height: 30px; }
.loc-hero.type-town .loc-icon { background: var(--map-town); }
.loc-hero.type-route .loc-icon { background: var(--map-route); }
.loc-hero.type-forest .loc-icon { background: var(--map-forest); color: #fff; }
.loc-hero.type-cave .loc-icon { background: var(--map-cave); color: #fff; }
.loc-hero.type-water .loc-icon { background: var(--map-water); }
.loc-hero.type-gym .loc-icon { background: var(--map-gym); color: #fff; }
.loc-hero.type-safari .loc-icon { background: var(--map-safari, #8d9b3a); color: #fff; }   /* token: css/safari.css */
.loc-hero h2 { margin: 0 0 2px; font-size: 1.4rem; }
.loc-hero .loc-desc { color: var(--muted); margin-top: 6px; }
.loc-hero .loc-meta { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }

.action-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
.action-card {
  display: flex; align-items: center; gap: 12px;
  padding: 12px; border-radius: var(--radius-sm);
  background: var(--panel-2); border: var(--panel-border);
  color: inherit; text-align: left; cursor: pointer;
  transition: background-color 0.15s, transform 0.15s, border-color 0.15s;
}
.action-card:hover:not(:disabled) { background: var(--panel-3); transform: translateY(-2px); border-color: rgba(255, 255, 255, 0.18); }
.action-card:disabled { opacity: 0.5; cursor: not-allowed; }
.action-card .ac-icon {
  flex: none; width: 40px; height: 40px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center; color: #1b2233;
}
.action-card .ac-icon svg { width: 22px; height: 22px; }
.action-card .ac-icon.grass { background: var(--map-route); }
.action-card .ac-icon.heal { background: #ff8a80; }
.action-card .ac-icon.shop { background: #64b5f6; }
.action-card .ac-icon.gym { background: var(--map-gym); color: #fff; }
.action-card .ac-icon.map { background: var(--accent); }
.action-card .ac-text { min-width: 0; }
.action-card .ac-title { font-weight: 700; font-size: 0.95rem; }
.action-card .ac-sub { font-size: 0.78rem; color: var(--muted); }
.action-card.primary { border-color: var(--accent); }

.trainer-row .trainer-name { font-weight: 700; }
.trainer-row .trainer-kind { font-size: 0.75rem; color: var(--muted); }
.travel-row .travel-type { width: 10px; height: 10px; border-radius: 50%; flex: none; border: 1px solid rgba(0, 0, 0, 0.3); }
.travel-row .travel-name { font-weight: 600; }
.travel-row .travel-req { font-size: 0.75rem; color: #ffd97a; display: inline-flex; align-items: center; gap: 4px; }
.travel-row.locked { opacity: 0.7; }
.type-dot-town { background: var(--map-town); }
.type-dot-route { background: var(--map-route); }
.type-dot-forest { background: var(--map-forest); }
.type-dot-cave { background: var(--map-cave); }
.type-dot-water { background: var(--map-water); }
.type-dot-gym { background: var(--map-gym); }
.type-dot-safari { background: var(--map-safari, #8d9b3a); }

.two-col { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; }
@media (max-width: 700px) { .two-col { grid-template-columns: minmax(0, 1fr); } }

/* --------------------------------------------------------------------------
   Profile view (Trainer Card)
   -------------------------------------------------------------------------- */
.profile-card { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
.profile-card .avatar { background: rgba(255, 255, 255, 0.08); }
.profile-card .profile-name { font-size: 1.5rem; font-weight: 800; margin: 0; }
.profile-card .profile-sub { color: var(--muted); font-size: 0.85rem; }
/* A Trainer Card background from the Trainer Shop (DECISIONS 489, picture 04 look A of docs/PLAN-TRAINERSHOP.md): the
   picture fills the whole head of the card; a dark wash from the left keeps the name and the lines readable. */
.panel.has-card-bg { overflow: hidden; }
.profile-card.card-bg {
  position: relative; isolation: isolate; margin: -14px; padding: 18px 20px; min-height: 150px;
  background-size: cover; background-position: center 65%;
}
.profile-card.card-bg::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(90deg, rgba(10, 14, 26, 0.88) 0%, rgba(10, 14, 26, 0.62) 45%, rgba(10, 14, 26, 0.12) 100%);
}
.profile-card.card-bg .avatar { background: rgba(10, 14, 26, 0.45); box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.55); }
.profile-card.card-bg .profile-name, .profile-card.card-bg .profile-sub { text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); }
.profile-card.card-bg .profile-sub { color: #d7deec; }
@media (max-width: 600px) {
  .profile-card.card-bg::before { background: linear-gradient(180deg, rgba(10, 14, 26, 0.35), rgba(10, 14, 26, 0.85)); }
}
.stat-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; }
.stat-tile { padding: 10px 12px; border-radius: var(--radius-sm); background: var(--panel-2); border: var(--panel-border); }
.stat-tile .stat-label { font-size: 0.7rem; letter-spacing: 0.01em; color: var(--muted); font-weight: 700; }
.stat-tile .stat-value { font-size: 1.3rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.2; }
.stat-tile .stat-value.accent { color: var(--accent-light); }
.stat-tile .stat-value.shiny { color: var(--variant-shiny); }
.stat-tile .stat-value.dark { color: var(--variant-dark); }
.stat-tile .stat-value.golden { color: var(--variant-golden); }
.stat-tile .stat-value.crystal { color: var(--variant-crystal); }
/* Compact record tiles: the label matters more than a huge number (owner feedback). */
.stat-tiles.compact { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 6px; }
.stat-tiles.compact .stat-tile { padding: 5px 8px; }
.stat-tiles.compact .stat-label { font-size: 0.62rem; letter-spacing: 0.08em; }
.stat-tiles.compact .stat-value { font-size: 0.95rem; font-weight: 700; }
/* A tile two columns wide, for a label that does not fit one ("Pokémon caught in Poké Balls",
   owner, DECISIONS 375). The compact grid keeps at least two columns above ~230 px, so it never overflows. */
.stat-tiles .stat-tile.wide { grid-column: span 2; }

/* Badge case, after the trainer card on tppcrpg.net: every badge of a region next to each
   other, earned in colour, the rest greyed, and the regions still to come listed below. */
/* Badge case on the trainer card (DECISIONS 119, reshaped in 123).
   Owner: only the shapes, small, in a row - region name on the left, whitespace, then the
   badges. Earned in colour, everything you still have to win (including the regions that are
   not in the game yet) as a dark silhouette. Names and dates live in the tooltip. */
.badge-region {
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  align-items: center;
  gap: 14px;
  padding: 7px 0;
}
.badge-region + .badge-region { border-top: 1px solid rgba(255, 255, 255, 0.07); }
.badge-region-name { font-size: 0.74rem; font-weight: 700; letter-spacing: 0.01em; }
.badge-region.locked .badge-region-name { color: var(--muted); font-weight: 600; }
.badge-case { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
.badge-chip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid transparent;
}
.badge-chip.earned { background: rgba(214, 69, 69, 0.14); border-color: rgba(214, 69, 69, 0.45); }
.badge-chip.locked { border-style: dashed; border-color: rgba(255, 255, 255, 0.16); }
.badge-chip.early { opacity: 0.55; }   /* won before its region opened: off the ladder (MASTER AC-12) */
.badge-chip-art { width: 26px; height: 26px; object-fit: contain; }
.badge-chip.locked .badge-chip-art { filter: grayscale(1) brightness(0.22) contrast(1.5); opacity: 0.95; }
/* Prestige (DECISIONS 234): Bronze .. Diamond as a ring in the tier's colour plus a small star,
   on the trainer card's chips, the Gyms page strips and cards and the Hall of Fame slots; the
   same colour fills the "Prestige ★N" buttons and the "★2 Silver" tags. Colours = data/prestige.json. */
.prestige-1, .btn-prestige.p1, .prestige-tag.p1 { --prestige: #cd7f32; }
.prestige-2, .btn-prestige.p2, .prestige-tag.p2 { --prestige: #c0c7d0; }
.prestige-3, .btn-prestige.p3, .prestige-tag.p3 { --prestige: #f6c945; }
.prestige-4, .btn-prestige.p4, .prestige-tag.p4 { --prestige: #9fe3d6; }
.prestige-5, .btn-prestige.p5, .prestige-tag.p5 { --prestige: #8fd3ff; }
.badge-chip[class*="prestige-"] { position: relative; border-color: var(--prestige); box-shadow: 0 0 0 2px var(--prestige); }
.badge-chip.prestige-5 { box-shadow: 0 0 0 2px var(--prestige), 0 0 10px var(--prestige); }
/* The tier as a small "★2" label half over the badge, in the tier's colour (owner, 23 September,
   DECISIONS 247): the ring says "prestige", the label says which one. */
.badge-chip[class*="prestige-"]::after {
  position: absolute; right: -8px; bottom: -6px;
  padding: 0 3px; border-radius: 6px;
  background: var(--prestige); color: #14161c;
  font-size: 9px; font-weight: 800; line-height: 13px; letter-spacing: -0.02em; white-space: nowrap;
  box-shadow: 0 0 0 1.5px rgba(12, 18, 32, 0.95);
}
.badge-chip.prestige-1::after { content: "★1"; }
.badge-chip.prestige-2::after { content: "★2"; }
.badge-chip.prestige-3::after { content: "★3"; }
.badge-chip.prestige-4::after { content: "★4"; }
.badge-chip.prestige-5::after { content: "★5"; }
/* A little more room between the chips once a label hangs over the edge, so it never touches the next badge. */
.badge-case:has(.badge-chip[class*="prestige-"]) { column-gap: 11px; }
.badge-case .badge-slot[class*="prestige-"] { box-shadow: 0 0 0 1.5px var(--prestige); }
.gym-card[class*="prestige-"] { border-color: var(--prestige); box-shadow: var(--shadow-sm), inset 0 3px 0 var(--prestige); }
.btn.btn-prestige { background: var(--prestige); border-color: var(--prestige); color: #14161c; font-weight: 700; }
.btn.btn-prestige:hover:not(:disabled) { filter: brightness(1.08); }
.prestige-tag {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px; vertical-align: middle;
  background: var(--prestige); color: #14161c; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.01em; white-space: nowrap;
}
/* The tier still to win: the same tag outlined (owner, 23 September, DECISIONS 238). */
.prestige-tag.next { background: rgba(10, 15, 28, 0.55); color: var(--prestige); box-shadow: inset 0 0 0 1.5px var(--prestige); text-shadow: none; }
/* The gym card's status line is the tags, not a sentence. */
.gym-status.gym-prestige { flex-wrap: wrap; row-gap: 3px; max-width: 70%; }
.gym-status.gym-prestige .prestige-tag { margin-left: 0; text-shadow: none; }
/* "Challenge for prestige ★1 Bronze": a quiet button that carries the coloured tag. */
.btn.btn-prestige-go { gap: 4px; }
.btn.btn-prestige-go .prestige-tag { margin-left: 4px; }
.btn.btn-prestige-go > .prestige-tag:first-child { margin-left: 0; }
@media (max-width: 600px) {
  .badge-region { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .badge-chip { width: 28px; height: 28px; }
  .badge-chip-art { width: 23px; height: 23px; }
}
/* The pill-shaped .badge-slot of the first trainer card and the flex .badge-region that went with
   it used to live here. Both are gone: the flex rule silently overrode the grid .badge-region
   above (the DECISIONS 126 layout), and the pill's padding left the 15 px .badge-slot circles of
   the stats page (`.hall-card .badge-case .badge-slot` below) no room for the badge picture. */
/* The old pill version of .badge-chip (gym list, phase 2) used to live here. It is gone: the
   trainer card and the Battle Hub draw the round badge chips of DECISIONS 126 above, and the
   later pill rule silently overrode their padding and background (found by agent B). */

/* --------------------------------------------------------------------------
   Trade views (modal picker + trades page)
   -------------------------------------------------------------------------- */
/* A party row that can evolve (DECISIONS 255): a small gold sparkle after the name. */
.party-evo { display: inline-flex; margin-left: 4px; vertical-align: -1px; color: #f6c945; }
.party-evo svg { width: 12px; height: 12px; }
/* The Pokémon you chose to follow you (Trainer Shop, DECISIONS 489): footsteps and a blue edge. */
.party-follow { display: inline-flex; margin-left: 4px; vertical-align: -1px; color: #7cc4ff; }
.party-follow svg { width: 12px; height: 12px; }
.party-row.is-follower { box-shadow: inset 3px 0 0 #5aa9ff; }
.btn.btn-follow.on { border-color: rgba(90, 169, 255, 0.7); background: rgba(90, 169, 255, 0.16); color: #cfe4ff; }
/* The Exp. Share (DECISIONS 259): the holder's mark in the party list, and the gift popup. */
.party-held { width: 15px; height: 15px; margin-left: 3px; vertical-align: -3px; image-rendering: pixelated; }
.gift-pop { display: flex; gap: 14px; align-items: flex-start; }
.item-card.item-focus { box-shadow: 0 0 0 2px var(--accent); animation: item-focus-pulse 1.1s ease-out 3; }   /* #bag?focus= (DECISIONS 262) */
@keyframes item-focus-pulse { 0% { box-shadow: 0 0 0 6px rgba(214, 69, 69, 0.45); } 100% { box-shadow: 0 0 0 2px var(--accent); } }
.item-held { font-size: 0.75rem; font-weight: 700; color: var(--accent); margin-bottom: 2px; }   /* "Held by …" in the Bag (DECISIONS 262) */
.gift-pop p { margin: 0 0 8px; }
.gift-pop-art { flex: none; width: 60px; height: 60px; padding: 6px; border-radius: 12px; background: var(--panel-2); image-rendering: pixelated; }
.trade-steps { display: flex; gap: 6px; margin-bottom: 12px; font-size: 0.75rem; }
.trade-steps .step { flex: 1; padding: 4px 8px; border-radius: 999px; background: var(--panel-2); color: var(--muted); text-align: center; font-weight: 600; }
.trade-steps .step.active { background: var(--accent); color: var(--accent-text); }
.trade-steps .step.done { color: #9df0ab; }
.poke-pick-list { display: flex; flex-direction: column; gap: 6px; max-height: 50vh; overflow: auto; padding-right: 2px; }
.poke-pick-list .list-item img { width: 40px; height: 40px; flex: none; }
.poke-pick-list .pp-name { font-weight: 700; font-size: 0.9rem; }
.poke-pick-list .pp-sub { font-size: 0.75rem; color: var(--muted); display: flex; gap: 4px; align-items: center; flex-wrap: wrap; }
.trade-summary { display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: center; }
.trade-summary .trade-side { text-align: center; padding: 10px; background: var(--panel-2); border-radius: var(--radius-sm); border: var(--panel-border); }
.trade-summary .trade-side img { width: 72px; height: 72px; }
.trade-summary .trade-side .who { font-size: 0.72rem; color: var(--muted); letter-spacing: 0.01em; }
.trade-summary .trade-side .what { font-weight: 700; }
.trade-summary .trade-arrow { color: var(--accent-light); }
.trade-summary .trade-arrow svg { width: 26px; height: 26px; }
@media (max-width: 420px) { .trade-summary { grid-template-columns: 1fr; } .trade-summary .trade-arrow { transform: rotate(90deg); text-align: center; } }

/* --------------------------------------------------------------------------
   Public league page (stats.html) — DECISIONS 135
   --------------------------------------------------------------------------
   The page wears the game's own three-column shell: the ladder on the left, the
   newcomers on the right, and in the middle one board that fits the screen
   without scrolling (owner feedback) — the Hall of Fame across the top, the
   catch feed and the world totals side by side underneath.

   Two rules for the look: the top three are told apart by size and by their
   badge case, never by a medal emoji, and gold/silver/bronze live in the hall
   and nowhere else on the page.
   -------------------------------------------------------------------------- */
.stats-app {
  --gold: #f6c945;
  --silver: #cdd6e6;
  --bronze: #c98a52;
  --hair: rgba(255, 255, 255, 0.09);
}
/* Header: edge to edge against the top, like the game's #topbar. */
.stats-topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; gap: 10px;
  min-height: 52px;
  padding: 8px 16px;
  padding-top: calc(8px + env(safe-area-inset-top, 0px));
  background: rgba(14, 21, 38, 0.94);
  border-bottom: 1px solid rgba(255, 255, 255, 0.09);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
/* The rails are one continuous navy column, like the game's menu and party
   column: flush panels with a hairline between them, no floating cards. */
.stats-app .sidebar { padding: 0; gap: 0; }
.stats-app .rail-body { display: flex; flex-direction: column; }
.stats-app .rail-body > .panel + .panel { border-top: 1px solid var(--hair); }
/* No glass blur on this page (DECISIONS 286). The rails are navy on navy and the board only
   softened the 1 px dot pattern at ~2 %, so nobody saw it - but the browser re-blurred every
   panel on every scroll frame, the 9,000 px ladder included. The header keeps its blur. */
.stats-app .panel { -webkit-backdrop-filter: none; backdrop-filter: none; }
/* The ladder rail a notch wider than the game's 224 px (owner, 26 September): a prestige chip
   like "★1 Bronze" next to a name was truncating it. stats.html has class="app stats-app",
   so this two-class rule outweighs the `.app` base above; the game shell keeps its 224 px. */
/* Desktop only: this two-class rule also outweighed the one-class phone rule below (max-width 899px,
   `.stats-app { grid-template-columns: minmax(0, 1fr) }`), so a phone kept three columns and the
   board was a 30 px strip (owner, 27 September, DECISIONS 431). */
@media (min-width: 900px) {
  .app.stats-app { grid-template-columns: 268px minmax(0, 1fr) 264px; }
}

/* The board. Three blocks with real air between them. Every row is content-sized: a short
   window makes the middle column scroll (it is the scroller of the shell), it never squeezes
   the Hall of Fame into a strip you have to zoom out to read - DECISIONS 208. */
.stats-app .view-scroll {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  /* max-content, not auto (DECISIONS 231). The panels have min-height 0 or overflow hidden, so
     their minimum is 0, and auto rows in this fixed-height scroller were still given a share of
     the window instead of their content: on a laptop the timeline lost its bottom half and
     the totals ran over each other. A max-content row is never lower than what is in it. */
  grid-template-rows: repeat(5, max-content);
  /* "numbers" is .stats-side: the world totals with the Giants board under them (DECISIONS 436).
     "players" = the Player base chart above who is online (DECISIONS 437). */
  grid-template-areas:
    "hall    hall"
    "catches numbers"
    "players players"
    "online  online"
    "foot    foot";
  gap: 14px;
  padding: 14px;
}
.stats-app .view-scroll > * + * { margin-top: 0; }
.stats-app .view-scroll > *:first-child:not(.view-head) { margin-top: 0; }
.stats-app .view-scroll > .panel { padding: 14px 16px; min-height: 0; }
/* The loading screen (DECISIONS 356) in the middle of the screen. As a plain grid item it took the
   first cell of the grid above - the top of the left column - so the ball stood off to the left and
   high up (owner, 25 September). Now it spans both columns and is as high as the visible part of the
   column (the window minus the 52 px header and the padding, with a few px to spare so no scrollbar
   shows); the block itself already centres the ball and the line. On a phone, where the rails
   follow under it, the same height keeps it in the middle of the screen. */
.stats-app .view-scroll > .loading-block {
  grid-column: 1 / -1;
  min-height: calc(100vh - 52px - 40px);
  min-height: calc(100dvh - 52px - env(safe-area-inset-top, 0px) - 40px);
}
.hall { grid-area: hall; }
.catches { grid-area: catches; display: flex; flex-direction: column; min-height: 0; }
.numbers { grid-area: numbers; display: flex; flex-direction: column; min-height: 0; }
/* Without an area the Giants board fell into an implicit grid row and drew over the page
   background, body-less (owner screenshot, 26 September) - every board here needs its area. */
/* The totals and the Giants board share one column (owner, 27 September, DECISIONS 436: "the world in
   numbers mag minder hoog en daar moet giants onder"). As a grid item the totals stretched to the height
   of the catch feed next to them, half empty; in this flex column each board is as high as its content,
   so the Giants board is no grid item any more and needs no area of its own. */
.stats-side { grid-area: numbers; display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.stats-side > .panel { padding: 14px 16px; }
.stats-foot { grid-area: foot; }
/* --hair here too: the chart also stands on the game's Online now page (DECISIONS 468), outside .stats-app. */
.online-chart { grid-area: online; --hair: rgba(255, 255, 255, 0.09); }
.online-chart.chart-players { grid-area: players; }   /* same chart, own row above it (DECISIONS 437) */
.players-base:empty { display: none; }   /* Online now: no gap while the chart loads (or when it cannot) */

/* --- Trainers online over time (DECISIONS 229) -------------------------- */
/* One series on the dark panel: a 2px blue line with a 12% wash, dots on the seven days, the now
   dot green with a soft pulse when somebody is online. Grid and axes are hairlines in the
   panel's own greys; every number is text colour, never the line's. */
.online-chart h2 { margin: 0; font-size: 0.95rem; font-weight: 650; letter-spacing: -0.005em; }
.online-sub { margin: 3px 0 8px; font-size: 0.78rem; color: var(--muted); }
/* pan-y: a finger sliding sideways over the chart walks the points; up and down still scrolls the page
   (DECISIONS 432). */
.online-plot { position: relative; border-radius: 8px; touch-action: pan-y; }
.online-plot:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 4px; }
.online-svg { display: block; overflow: visible; }
.ot-grid { stroke: var(--hair); stroke-width: 1; }
.ot-divider { stroke: rgba(255, 255, 255, 0.16); stroke-width: 1; }
.ot-axis { fill: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.ot-seg { fill: var(--muted); font-size: 10.5px; font-weight: 650; letter-spacing: 0.02em; }
.ot-area { fill: rgba(62, 138, 230, 0.12); }
.ot-line { fill: none; stroke: #3e8ae6; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ot-dot { fill: #3e8ae6; stroke: #121a2c; stroke-width: 2; }
.ot-dot.ot-now { fill: #2ea44f; }
.ot-now-ring { fill: none; stroke: #2ea44f; stroke-width: 2; transform-box: fill-box; transform-origin: center; animation: ot-pulse 2s ease-out infinite; }
@keyframes ot-pulse { from { transform: scale(1); opacity: 0.8; } to { transform: scale(2.6); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .ot-now-ring { animation: none; opacity: 0; } }
.ot-label { fill: var(--text); font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ot-cross { stroke: rgba(255, 255, 255, 0.35); stroke-width: 1; }
.ot-hit { fill: transparent; pointer-events: all; cursor: crosshair; }
.ot-tip {
  position: absolute; transform: translate(-50%, calc(-100% - 12px)); pointer-events: none;
  padding: 6px 9px; border-radius: 8px; white-space: nowrap;
  background: rgba(10, 15, 28, 0.96); border: 1px solid var(--hair); box-shadow: var(--shadow);
  font-size: 0.78rem; color: var(--muted);
}
.ot-tip b { color: var(--text); font-size: 0.95rem; font-variant-numeric: tabular-nums; }
.ot-tip-when { margin-top: 1px; font-size: 0.72rem; }
.online-table { margin-top: 8px; font-size: 0.78rem; color: var(--muted); }
.online-table summary { cursor: pointer; width: max-content; }
.online-table table { margin-top: 6px; border-collapse: collapse; }
.online-table th, .online-table td { padding: 2px 14px 2px 0; text-align: left; font-weight: 400; }
.online-table th { color: var(--text); font-weight: 600; }
.online-table td + td { font-variant-numeric: tabular-nums; }

/* --- Hall of Fame ------------------------------------------------------- */
.hall {
  position: relative; overflow: hidden;
  background:
    radial-gradient(70% 120% at 18% 0%, rgba(246, 201, 69, 0.13), transparent 62%),
    var(--panel);
}
.hall-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 9px; }
.hall-head h1 { font-size: 1.2rem; font-weight: 700; letter-spacing: -0.015em; line-height: 1.05; margin: 0; }
.hall-head p { margin: 0; color: var(--muted); font-size: 0.82rem; }
.hall-empty { color: var(--muted); margin: 0; }
/* The champion lies next to the runners-up, and those two stand beside each other instead of
   on top of each other (owner, 23 September): that is what makes the hall a wide band of about
   190 px instead of a block you have to zoom out of - DECISIONS 208. */
.hall-row { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 10px; align-items: stretch; }
.hall-runners { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; min-width: 0; }
/* The champion's sprite is the tallest thing on the page; 92 px keeps him clearly bigger than
   the two runners-up (52 px) and saves 20 px of height (owner, 23 September). */
.hall-card.rank-1 .avatar.xl { width: 92px; height: 92px; }
.hall-card {
  position: relative;
  /* Sprite on the left over the full height, the trainer's line of facts on the right. */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "figure name" "figure case" "figure line" "figure stats";
  grid-template-rows: auto auto auto auto;
  align-content: center; align-items: center; gap: 0 14px;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--hair);
}
.hall-card.rank-1 {
  background: linear-gradient(135deg, rgba(246, 201, 69, 0.16), rgba(255, 255, 255, 0.03) 58%);
  border-color: rgba(246, 201, 69, 0.4);
  box-shadow: inset 0 1px 0 rgba(246, 201, 69, 0.35);
}
/* The rank is a quiet ghost numeral behind the sprite, not a medal. */
.hall-rank {
  position: absolute; left: 8px; top: 3px;
  font-size: 1.7rem; font-weight: 800; line-height: 1;
  font-variant-numeric: tabular-nums; color: #fff; opacity: 0.09;
  pointer-events: none;
}
.hall-card.rank-1 .hall-rank { font-size: 3.2rem; left: 14px; top: 2px; color: var(--gold); opacity: 0.16; }
.hall-figure { grid-area: figure; align-self: center; position: relative; display: flex; align-items: flex-end; justify-content: center; }
.hall-figure::after {
  content: ''; position: absolute; left: 50%; bottom: -5px; transform: translateX(-50%);
  width: 78%; height: 11px; border-radius: 50%;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.45), transparent 72%);
}
.hall-card.rank-1 .hall-figure::after { background: radial-gradient(ellipse at center, rgba(246, 201, 69, 0.34), transparent 72%); }
.hall-figure .avatar { background: none; border: 0; box-shadow: none; }
.hall-online { position: absolute; right: -2px; bottom: 4px; }
.hall-name { grid-area: name; align-self: end; margin: 0 0 5px; font-size: 0.92rem; font-weight: 700; line-height: 1.2; }
.hall-card.rank-1 .hall-name { font-size: 1.15rem; margin-bottom: 7px; }
/* Badge case: eight slots, the earned ones carry the real badge picture. */
/* Scoped to the hall: a bare .badge-case here would also re-space the trainer card's chips. */
/* One row, never a second (owner, 26 September: "moeten in 1 lijn staan, niet met enter"):
   the runners-up get 13 px chips so all eight Halcyon badges fit their narrower column. */
.hall-card .badge-case { grid-area: case; display: flex; flex-wrap: nowrap; gap: 4px; }
.hall-card:not(.rank-1) .badge-case { gap: 3px; }
.hall-card:not(.rank-1) .badge-case .badge-slot { width: 13px; height: 13px; }
.badge-case .badge-slot {
  width: 15px; height: 15px; border-radius: 50%;
  border: 1px dashed rgba(255, 255, 255, 0.18);
  display: inline-flex; align-items: center; justify-content: center;
}
.badge-case .badge-slot.owned { border: 0; }
.badge-case .badge-slot img { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45)); }
.hall-card .badge-case.big { gap: 6px; }
.badge-case.big .badge-slot { width: 21px; height: 21px; }
.hall-badge-line { grid-area: line; margin: 5px 0 0; font-size: 0.72rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.hall-card.rank-1 .hall-badge-line { margin-top: 7px; font-size: 0.78rem; }
/* Three pairs since "Time played" (DECISIONS 507). A grid, not the flex row it was: on a runner-up card
   (~225 px of text) the flex row squeezed the third pair until "12 h 30 min" broke in two; here a pair
   is at least 80 px and drops under the others when they do not fit side by side. */
.hall-stats { grid-area: stats; display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, max-content)); gap: 6px 16px; margin: 9px 0 0; padding-top: 8px; border-top: 1px solid var(--hair); }
.hall-stats div { display: flex; flex-direction: column; gap: 0; }
.hall-stats dt { font-size: 0.68rem; color: var(--muted); order: 2; line-height: 1.2; }
.hall-stats dd { margin: 0; font-size: 0.92rem; font-weight: 800; font-variant-numeric: tabular-nums; order: 1; line-height: 1.2; }
.hall-card.rank-1 .hall-stats dd { font-size: 1.1rem; }
.hall-card.rank-1 .hall-stats { gap: 6px 18px; margin-top: 9px; padding-top: 8px; }

/* --- Catch feed --------------------------------------------------------- */
.catches h2, .numbers h2 { margin: 0; font-size: 0.95rem; font-weight: 650; letter-spacing: -0.005em; }
/* Speed Tower line: one quiet sentence, not a second heading (owner, 23 September). */
.tower-line { margin: 5px 0 0; font-size: 0.78rem; line-height: 1.5; color: var(--muted); }
.tower-line b { color: var(--text); font-weight: 600; }
.tower-line .trainer-link { color: var(--text); }
.section-note { margin: 3px 0 11px; color: var(--muted); font-size: 0.8rem; max-width: 62ch; }
/* Compact tiles that fill the width - three to five across - so twenty finds fit at once
   (owner, 23 September, DECISIONS 252: two columns of nine was too little). */
.catch-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(165px, 1fr)); gap: 6px;
  min-height: 0; overflow: auto; padding-right: 2px;
  align-content: start;
}
.catch {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 8px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--hair);
}
/* The newest find is the wide one: recency is the story of a feed. */
.catch.feature { grid-column: 1 / -1; padding: 9px 12px; gap: 13px; }
.catch-art { position: relative; flex: none; display: flex; align-items: center; justify-content: center; }
.catch-art::before {
  content: ''; position: absolute; inset: -16%;
  border-radius: 50%; background: radial-gradient(circle, var(--v-glow, rgba(255, 255, 255, 0.14)), transparent 68%);
}
.catch.v-shiny { --v-glow: rgba(255, 216, 107, 0.34); }
.catch.v-dark { --v-glow: rgba(167, 139, 250, 0.34); }
.catch.v-golden { --v-glow: rgba(255, 176, 32, 0.34); }
.catch.v-frozen { --v-glow: rgba(143, 227, 255, 0.36); }
.catch.v-shadow { --v-glow: rgba(110, 76, 150, 0.4); }   /* the smoky purple-black of the Shadow form (DECISIONS 330) */
.catch-art img { position: relative; width: 32px; height: 32px; image-rendering: pixelated; }
.catch.feature .catch-art img { width: 58px; height: 58px; }
.catch-text { min-width: 0; }
.catch-text h3 { margin: 0; font-size: 0.8rem; font-weight: 650; line-height: 1.2; }
.catch.feature .catch-text h3 { font-size: 1.02rem; }
.catch-text p { margin: 2px 0 0; font-size: 0.7rem; color: var(--muted); }
/* The variant wears the game's own chip (.variant-label, css/game.css, DECISIONS 302) at its normal
   size (about 20 px high; it was a 9.5 px pill), a notch bigger on the wide newest find. */
.catch-text h3 { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 7px; }
.catch.feature .variant-label { font-size: 11px; }
/* The level it was caught at and the catcher's name as a link (DECISIONS 288). The name is plain
   white text, not bold and never underlined; pointing at it only makes it bold (owner, 24 September). */
.catch-lv { font-size: 0.7rem; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; }
.catch.feature .catch-lv { font-size: 0.8rem; }
.catch-text .trainer-link { color: var(--text); font-weight: 400; }
.catch-text .trainer-link:hover, .catch-text .trainer-link:focus-visible {
  color: var(--text); font-weight: 700; text-decoration: none; border-bottom-color: transparent;
}
/* The bold hover must not move anything (owner, 25 September: a name like "Watson" of just the
   wrong length pushed ", 3 min ago" onto a new line and the whole feed jumped). The link is a
   one-column box that also holds an invisible bold copy of the name (data-text), so it is as wide
   as its bold self from the start and hovering only changes the look, never the width. */
.catch-text .trainer-link { display: inline-flex; flex-direction: column; vertical-align: baseline; border-bottom: 0; }
.catch-text .trainer-link::after {
  content: attr(data-text);
  font-weight: 700; height: 0; overflow: hidden; visibility: hidden; pointer-events: none; user-select: none;
}
.catch-empty {
  flex: 1 1 auto; display: flex; flex-direction: column; justify-content: center; gap: 10px;
  padding: 16px 18px; border-radius: var(--radius-sm);
  border: 1px dashed rgba(255, 255, 255, 0.16);
  background: rgba(8, 12, 24, 0.35);
}
.catch-empty .variants { display: flex; flex-wrap: wrap; gap: 8px; }
.catch-empty .variant-label { font-size: 11px; }
.catch-empty p { margin: 0; color: var(--muted); font-size: 0.83rem; line-height: 1.5; max-width: 46ch; }

/* --- World totals: hairlines, not eight little boxes -------------------- */
.num-grid {
  flex: 0 1 auto; min-height: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: minmax(0, 1fr);
  margin-top: 9px;
}
.num-cell { display: flex; flex-direction: column; justify-content: center; padding: 7px 12px; border-left: 1px solid var(--hair); }
.num-grid .num-cell:nth-child(odd) { border-left: 0; padding-left: 0; }
.num-grid .num-cell:nth-child(n + 3) { border-top: 1px solid var(--hair); }
.num-value { font-size: 0.95rem; font-weight: 600; line-height: 1.15; letter-spacing: -0.005em; font-variant-numeric: tabular-nums; }
.num-value.accent { color: var(--accent-light); }
.num-value.shiny { color: var(--variant-shiny); }
.num-value.dark { color: var(--variant-dark); }
.num-value.golden { color: var(--variant-golden); }
.num-value.frozen { color: var(--variant-frozen); }
.num-value.shadow { color: var(--variant-shadow); }
.num-value.crystal { color: var(--variant-crystal); }
.num-label { margin-top: 1px; font-size: 0.74rem; line-height: 1.3; color: var(--muted); }

/* --- Rails -------------------------------------------------------------- */
.stats-app .rail-panel, .stats-app .rail-cta { padding: 13px 13px 12px; }
/* The sign-up card stays in view while the 50 newcomers scroll under it (owner, 26 September:
   "rechts moeten we scrollen maar Create your trainer blijft staan") - the same sticky trick
   as .ladder-search on the left rail. Rail colour behind it, so rows slide under, not through. */
.stats-app .rail-cta {
  position: sticky; top: 0; z-index: 1;
  background: var(--panel, rgba(18, 26, 44, 0.88));
}
.rail-title { margin: 0; font-size: 0.95rem; font-weight: 700; }
.rail-note { margin: 3px 0 9px; font-size: 0.74rem; color: var(--muted); }
.rail-empty { margin: 0; font-size: 0.8rem; color: var(--muted); }
/* Search box over the ladder (DECISIONS 208): the rail scrolls, this stays in reach. */
.ladder-search { position: sticky; top: 0; z-index: 1; padding: 2px 0 6px; background: var(--panel); }
.ladder-search-input {
  width: 100%; min-height: 28px;
  padding: 4px 9px;
  border-radius: var(--radius-sm); border: 1px solid var(--hair);
  background: rgba(8, 12, 24, 0.55); color: var(--text);
  font-size: 0.78rem;
}
.ladder-search-input::placeholder { color: var(--muted); }
.ladder-search-input:focus { outline: none; border-color: rgba(214, 69, 69, 0.55); }
/* [Online only] next to the search (DECISIONS 287). In the narrow desktop rail it wraps under
   the field; on a phone both fit on one line. Pressed = the green of the online dot. */
.ladder-search { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ladder-search[hidden] { display: none; }
.ladder-search-input { flex: 1 1 130px; min-width: 0; }
.ladder-online-btn {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  min-height: 28px; padding: 3px 11px 3px 9px;
  border-radius: 999px; border: 1px solid var(--hair);
  background: rgba(8, 12, 24, 0.55); color: var(--muted);
  font-size: 0.74rem; font-weight: 600; line-height: 1;
}
.ladder-online-btn .dot { width: 7px; height: 7px; box-shadow: none; opacity: 0.6; }
.ladder-online-btn:hover { color: var(--text); }
.ladder-online-btn[aria-pressed="true"] { color: var(--text); border-color: rgba(76, 217, 100, 0.55); background: rgba(76, 217, 100, 0.14); }
.ladder-online-btn[aria-pressed="true"] .dot { opacity: 1; }
/* Opened from "12 online" on a phone: the ladder lands just under the header. The scroller itself
   starts below the header now (DECISIONS 432), so only the 8 px of air are left. */
.ladder-panel { scroll-margin-top: 8px; }
.ladder-row[hidden] { display: none; }
.ladder, .newcomers { display: flex; flex-direction: column; }
.ladder-row, .newcomer {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 2px; border-top: 1px solid var(--hair);
  /* Rows out of view are skipped until they scroll in (DECISIONS 286): with hundreds of trainers
     only the ~20 on screen are laid out and painted. 41 px = a row with its 28 px avatar. */
  content-visibility: auto;
  contain-intrinsic-height: auto 41px;
}
.ladder-row:first-child, .newcomer:first-child { border-top: 0; }
.ladder-pos { width: 17px; text-align: right; font-size: 0.78rem; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }
.ladder-row:nth-child(1) .ladder-pos { color: var(--gold); }
.ladder-row:nth-child(2) .ladder-pos { color: var(--silver); }
.ladder-row:nth-child(3) .ladder-pos { color: var(--bronze); }
.ladder-main { min-width: 0; flex: 1 1 auto; }
.ladder-name { display: flex; align-items: center; gap: 5px; font-size: 0.83rem; line-height: 1.2; }
.ladder-name .trainer-link { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ladder-sub { font-size: 0.69rem; color: var(--muted); margin-top: 1px; }
.rail-cta h2 { margin: 0 0 4px; font-size: 0.95rem; font-weight: 700; }
.rail-cta p { margin: 0 0 9px; font-size: 0.77rem; color: var(--muted); line-height: 1.45; }
.rail-cta .btn { width: 100%; justify-content: center; }

/* On a dark strip: grey text straight on the sky and the hills could not be read (owner, 27 September,
   DECISIONS 432). */
.stats-foot {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 3px 10px;
  padding: 7px 12px; font-size: 0.72rem; color: var(--muted); text-align: center;
  background: var(--panel); border: 1px solid var(--hair); border-radius: var(--radius);
}

/* --- Narrow windows ----------------------------------------------------- */
@media (max-width: 1240px) {
  .hall-row { grid-template-columns: minmax(0, 1fr); }
  .hall-runners { grid-template-rows: none; grid-template-columns: 1fr 1fr; }
  /* Every area of the wide template must be here too: "giants" (the Giants board, 26 September) was
     missing, so `grid-area: giants` landed on invented implicit lines, the browser added two 0 px
     columns in front of the real one and every board became a 34 px strip on anything under 1240 px
     (owner, 27 September, DECISIONS 431). Since DECISIONS 436 the Giants board sits inside "numbers"
     (.stats-side), so neither template names it any more. */
  .stats-app .view-scroll { grid-template-columns: minmax(0, 1fr); grid-template-areas: "hall" "catches" "numbers" "players" "online" "foot"; grid-template-rows: repeat(6, max-content); }
  .catch-grid { overflow: visible; }
}
@media (max-width: 899px) {
  /* The page scrolls in this box under the header, not the document behind it (owner, 27 September,
     DECISIONS 432): the scrollbar starts below the header, like the game's own scroller (DECISIONS 127). */
  .stats-app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "main" "left" "right";
    /* max-content, not auto (the lesson of DECISIONS 231): in this fixed-height scroller auto rows of
       min-height-0 columns shared one screen height and the board, ladder and newcomers ran over each
       other (owner screenshot, 27 September). A max-content row is as tall as what is in it. */
    grid-template-rows: repeat(3, max-content);
    position: fixed; left: 0; right: 0; bottom: 0;
    top: calc(var(--header-h, 52px) + env(safe-area-inset-top, 0px));
    height: auto; min-height: 0;
    padding: 0;
    overflow-y: auto; overflow-x: hidden;
    scrollbar-color: rgba(255, 255, 255, 0.55) rgba(18, 26, 44, 0.30);   /* the .view-scroll bar */
  }
  /* Not drawers here: there is no menu to open, so the rails simply follow the page. */
  /* z-index auto: the drawer rule above gives .sidebar z-index 800, and on a grid item that counts even
     when static - the ladder slid OVER the header. Overflow visible + overscroll auto: the rail was a
     scroll box with nothing to scroll and `contain`, so a swipe at the end of the ladder list never
     reached the page (owner, 27 September, DECISIONS 432). */
  .stats-app .sidebar {
    position: static; transform: none; width: auto; inset: auto;
    z-index: auto; overflow: visible; overscroll-behavior: auto;
    background: none; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none;
    padding: 0 14px 14px;
  }
  .stats-app .sidebar .panel { border-radius: var(--radius); border: 1px solid var(--hair); background: var(--panel); }
  .stats-app .rail-body { gap: 12px; }
  .stats-app .rail-body > .panel + .panel { border-top: 1px solid var(--hair); }
  .stats-app .main, .stats-app .view-scroll { overflow: visible; }
  .stats-app .main { min-height: 0; }
  /* The rails follow the page here, so a ladder of hundreds of trainers would push the footer
     kilometres down: on a phone the list gets its own scroll box under the search field. */
  .stats-app .ladder-panel .ladder { max-height: 60vh; overflow-y: auto; }
  /* The same for the 50 newcomers (DECISIONS 289). */
  .stats-app .newest-panel .newcomers { max-height: 60vh; overflow-y: auto; }
}
@media (max-width: 640px) {
  .hall-head h1 { font-size: 1.4rem; }
  .hall-runners { grid-template-columns: minmax(0, 1fr); }
  .hall-card.rank-1 .hall-rank { font-size: 3.4rem; }
  /* Phones: two compact tiles side by side instead of one long list (DECISIONS 252). */
  .catch-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Eleven finds on a phone: the wide newest one + five rows of two (owner, 27 September, DECISIONS 433).
     The other nine only made the page long; the desktop board keeps all twenty. */
  .catch-grid > .catch:nth-child(n + 12) { display: none; }
  .num-value { font-size: 1rem; }
}
@media (prefers-reduced-motion: reduce) {
  .stats-app .dot.online { animation: none; }
}

/* --------------------------------------------------------------------------
   Battle Hub / Gyms / Coming-soon views (phase 2, js/ui/hub.js gyms.js soon.js)
   -------------------------------------------------------------------------- */
.hub-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; }
/* A card marked .wide runs over both columns (DECISIONS 121); this used to be pinned to
   the third child, which broke as soon as the hub got more cards. */
.hub-grid .hub-card.wide { grid-column: 1 / -1; }
@media (max-width: 1100px) { .hub-grid { grid-template-columns: minmax(0, 1fr); } .hub-grid .hub-card.wide { grid-column: auto; } }

/* Record strip above the cards: badges, battles, money (DECISIONS 121). */
.hub-stats { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; margin-bottom: 12px; }
.hub-stat {
  display: flex; flex-direction: column; gap: 2px;
  padding: 9px 12px; border-radius: var(--radius-sm, 8px);
  background: var(--panel); border: var(--panel-border);
}
.hub-stat-value { font-size: 1.15rem; font-weight: 800; line-height: 1.1; }
.hub-stat-label { font-size: 0.66rem; font-weight: 700; letter-spacing: 0.01em; color: var(--muted); }
.hub-stat.accent .hub-stat-value { color: var(--accent-light); }
.hub-stat.money .hub-stat-value { color: #f6c945; }

/* Next badge card */
.hub-next { display: flex; align-items: center; gap: 12px; }
.hub-next-badge { width: 46px; height: 46px; object-fit: contain; flex: none; filter: grayscale(1) brightness(0.45) contrast(1.3); }
.hub-next-icon {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(246, 201, 69, 0.16); color: #f6c945;
}
.hub-card-next.wide .hub-next .btn { flex: none; }

/* Your team card */
.hub-team { display: flex; flex-direction: column; gap: 6px; }
.hub-team-row { display: flex; align-items: center; gap: 9px; padding: 5px 8px; border-radius: var(--radius-sm, 8px); background: rgba(255, 255, 255, 0.04); }
.hub-team-row.fainted { background: rgba(229, 72, 77, 0.12); }
.hub-team-row.fainted .hub-team-name { color: var(--muted); }
.hub-team-name { font-size: 0.82rem; font-weight: 700; display: flex; align-items: center; gap: 6px; }
.hub-team-row .hpbar { margin-top: 3px; }   /* hpBar() renders .hpbar */
.hub-team-hp { flex: none; }
.hub-card.warn { border-color: rgba(229, 72, 77, 0.45); }
@media (max-width: 700px) {
  .hub-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .hub-stat { padding: 7px 9px; }
  .hub-stat-value { font-size: 1rem; }
}
.hub-card { padding: 12px 14px; }
.hub-card .panel-title { margin-bottom: 8px; }
.hub-card-continue { border-color: rgba(214, 69, 69, 0.55); box-shadow: var(--shadow), 0 0 0 1px rgba(214, 69, 69, 0.25); }
.hub-continue { display: flex; align-items: center; gap: 12px; }
.hub-continue-icon {
  flex: none; width: 44px; height: 44px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent-light);
}
.hub-card-title { font-weight: 800; font-size: 1rem; }
.hub-hint a { font-weight: 600; }
.hub-card .list-item.player-row { padding: 6px 10px; }

.gym-league-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.gym-league-name { font-weight: 800; font-size: 0.95rem; }
/* Gym cards (the owner's design, DECISIONS 183): the leader's VS art standing on the battle
   backdrop of their town, number / name / type / town on the left, status + action below.
   One grid, so every card is the same height and the rows line up. */
.gym-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 10px; align-items: stretch; }
.gym-card {
  position: relative; isolation: isolate; overflow: hidden;
  display: flex; flex-direction: column; justify-content: space-between; gap: 10px;
  min-height: 118px; padding: 10px 12px;   /* no footer row: the button is in the corner */
  border-radius: var(--radius-sm, 8px);
  border: 1px solid rgba(255, 255, 255, 0.12);
  background-color: #17203a; background-size: cover; background-position: center 35%;
  box-shadow: var(--shadow-sm);
  image-rendering: pixelated;
}
/* A dark wash over the backdrop: heavy where the text is, light where the leader stands. */
.gym-card::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg, rgba(10, 15, 28, 0.94) 28%, rgba(10, 15, 28, 0.6) 62%, rgba(10, 15, 28, 0.32));
}
.gym-card.earned { border-color: rgba(76, 217, 100, 0.45); }
.gym-card.here { border-color: var(--accent); }
.gym-card.locked { filter: saturate(0.55); }
.gym-card.locked .gym-card-art { opacity: 0.4; }
/* The leader stands in the lower right, under the button in the corner. */
.gym-card-art {
  position: absolute; right: -2px; top: 34px; bottom: 0; width: 44%; z-index: -1;
  display: flex; align-items: flex-end; justify-content: flex-end; pointer-events: none;
}
.gym-card-action { position: absolute; top: 8px; right: 10px; z-index: 1; display: flex; gap: 6px; }
.gym-card-art img { max-width: 100%; max-height: 100%; object-fit: contain; object-position: bottom right; filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.55)); }
.gym-card-body { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.gym-card .gym-no {
  flex: none; width: 21px; height: 21px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.16); color: #fff; font-weight: 800; font-size: 0.72rem;
}
.gym-card-id { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
/* Owner (22 September, evening): the name was too big and too heavy for a card this size. */
.gym-card-name { font-family: var(--font); font-weight: 700; font-size: 0.86rem; letter-spacing: 0; line-height: 1.2; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); }
.gym-card-tags { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
/* Town and team each keep their own line - no "2" above a lonely "Pokémon". */
.gym-card-town, .gym-card-meta { font-size: 0.72rem; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); }
.gym-card-town { color: #d3d9e8; }
.gym-card-meta { color: #aab4c9; }
/* The name keeps clear of the button, the lines under it keep clear of the leader. */
.gym-card-name { padding-right: 58px; }
.gym-card-town, .gym-card-meta, .gym-status { max-width: 62%; }
.gym-status { margin-top: 3px; }
/* The state of the gym, as a line with a dot - the button beside it is the only control. */
.gym-status { display: inline-flex; align-items: center; gap: 5px; min-width: 0; font-size: 0.72rem; font-weight: 700; color: #cfd6e6; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); }
.gym-status-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.gym-status-icon { display: inline-flex; flex: none; }
.gym-status.ok { color: #7fe08a; }
.gym-status.warn { color: #f5c645; }
.gym-status.open { color: #b9c2d6; }

/* League tabs above the cards (Halcyon · Kanto · …). */
.gym-tabs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.gym-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 8px;
  border: 1px solid transparent; background: rgba(255, 255, 255, 0.05);
  color: var(--muted); font: inherit; font-weight: 700; font-size: 0.85rem; cursor: pointer;
}
.gym-tab:hover { color: var(--text); background: rgba(255, 255, 255, 0.09); }
.gym-tab.active { background: var(--accent-soft); color: var(--text); border-color: rgba(214, 69, 69, 0.5); }
.gym-tab-count { margin-left: 2px; padding: 1px 6px; border-radius: 999px; background: rgba(0, 0, 0, 0.3); font-size: 0.72rem; }
.gym-league-body { display: flex; flex-direction: column; gap: 10px; }
/* The other leagues sit under the home league behind their own tabs (DECISIONS 184): a rule
   and a head, so the page reads as "Halcyon, and then five more regions". */
.gym-leagues { display: block; margin-top: 18px; padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, 0.1); }
.gym-leagues-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.gym-league-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.gym-league-sub { font-weight: 400; }
.gym-league-badges { display: flex; align-items: center; gap: 10px; flex: none; }
.gym-badge-case { display: inline-flex; align-items: center; gap: 3px; }
@media (max-width: 700px) {
  .gym-cards { grid-template-columns: minmax(0, 1fr); }
  /* One card per row: there is room for the whole line next to the leader. */
  .gym-card-town, .gym-card-meta, .gym-status { max-width: 70%; }
  .gym-league-head { flex-direction: column; align-items: flex-start; }
}
.gym-row { padding: 6px 10px; gap: 10px; }
.gym-row .gym-no { flex: none; width: 20px; text-align: center; font-weight: 800; color: var(--muted); font-size: 0.8rem; }
.gym-row .gym-sprite { flex: none; width: 34px; height: 40px; display: flex; align-items: flex-end; justify-content: center; }
.gym-row .gym-sprite img { max-width: 34px; max-height: 40px; image-rendering: pixelated; }
.gym-row .gym-title { font-size: 0.92rem; }
.gym-row .gym-sub { font-size: 0.75rem; color: var(--muted); display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.gym-row .gym-sub .badge-dot { width: 9px; height: 9px; margin: 0 3px; }
.gym-row.earned { border-color: rgba(76, 217, 100, 0.35); }
.gym-row.locked { opacity: 0.7; }
.gym-row.region { opacity: 0.55; }
.gym-row .tag { flex: none; }
/* Regional league rows (DECISIONS 145): the badge picture sits where Halcyon's rows keep
   their number; locked ones stay readable, they are the road ahead, not a dead end. */
.gym-row.league .league-badge { flex: none; width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; }
.gym-row.league .league-badge-art { width: 22px; height: 22px; object-fit: contain; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5)); }
.gym-row.league.locked { opacity: 0.8; }
.gym-row.league.locked .league-badge-art { filter: grayscale(1) brightness(0.45); }
.leagues .gym-league-head:first-child { margin-top: 14px; }

/* --------------------------------------------------------------------------
   Defeat & Own (js/ui/own.js, DECISIONS 178; page after the owner's sketch, DECISIONS 181)
   --------------------------------------------------------------------------
   Night-sky bar, the hero with the gold promise and three rule tiles, your collection at
   the right, the four steps, then the bounties as cards: status pill, art panel with the
   form's glow, the facts, and the one red button. Gold is the mode's colour; the variant
   colours belong to the sprites' glow and the form word only. */
/* The whole page a notch smaller (the owner liked it at 80 % browser zoom: three cards in a
   row instead of two), and the blocks 8 px apart. One property instead of re-sizing every rule;
   `zoom` is standard in all current browsers. Phones keep 1:1 (below). */
.own-root { display: flex; flex-direction: column; gap: 8px; zoom: 0.8; }
/* The bar is the same red view bar as every page (owner, 22 September; the night-sky version
   of the sketch is gone). The tagline is one quiet line before the button. */
.own-tagline { font-style: italic; font-size: 0.72rem; color: rgba(255, 255, 255, 0.85); margin-right: 6px; white-space: nowrap; }
/* Hero row: the promise left, your collection right. */
.own-hero-row { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 8px; align-items: stretch; }
.own-hero { display: flex; flex-direction: column; gap: 10px; padding: 12px 14px; }
.own-hero-top { display: flex; gap: 14px; align-items: center; }
.own-trophy {
  flex: none; width: 64px; height: 64px; border-radius: 14px;
  display: flex; align-items: center; justify-content: center; color: #ffd45e;
  background: linear-gradient(160deg, #3a3323, #1c1a16 60%);
  border: 1px solid rgba(246, 201, 69, 0.45); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4), 0 0 24px rgba(246, 201, 69, 0.18);
}
.own-trophy svg { width: 32px; height: 32px; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6)); }
.own-hero-copy { min-width: 0; }
.own-headline { margin: 0 0 4px; font-size: 1.3rem; font-weight: 700; letter-spacing: -0.01em; line-height: 1.15; }
.own-headline .gold { color: #f6c945; }
.own-hero-copy p { margin: 0; color: var(--muted); line-height: 1.45; max-width: 70ch; font-size: 0.86rem; }
.own-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.own-tile { display: flex; gap: 8px; align-items: flex-start; padding: 8px 10px; border-radius: var(--radius-sm); background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); }
.own-tile-icon { flex: none; width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center; background: rgba(246, 201, 69, 0.14); color: #f6c945; }
.own-tile-title { font-weight: 700; font-size: 0.84rem; }
.own-tile-text { font-size: 0.74rem; color: var(--muted); line-height: 1.3; margin-top: 1px; }
/* Your collection */
.own-progress { border-color: rgba(120, 170, 255, 0.35); display: flex; flex-direction: column; gap: 7px; padding: 12px 14px; }
.own-progress-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 0.88rem; }
.own-progress-head b { font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.own-bar { height: 9px; border-radius: 999px; background: rgba(0, 0, 0, 0.35); border: 1px solid rgba(255, 255, 255, 0.12); overflow: hidden; }
.own-bar-fill { height: 100%; border-radius: 999px; background: linear-gradient(90deg, #4cd964, #9be29f); min-width: 6px; transition: width 0.4s ease; }
.own-progress-note { margin: -2px 0 2px; }
.own-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.own-stat { display: flex; gap: 7px; align-items: center; padding: 6px 8px; border-radius: var(--radius-sm); background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); }
.own-stat-icon { flex: none; width: 24px; height: 24px; border-radius: 8px; display: flex; align-items: center; justify-content: center; background: rgba(246, 201, 69, 0.14); color: #f6c945; }
.own-stat.accent .own-stat-icon { background: var(--accent-soft); color: var(--accent-light); }
.own-stat-value { font-weight: 800; font-size: 0.92rem; line-height: 1.15; font-variant-numeric: tabular-nums; }
.own-stat-label { font-size: 0.7rem; color: var(--muted); }
/* How it works */
.own-how { display: flex; align-items: center; gap: 16px; padding: 9px 14px; }
.own-how-title { flex: none; display: flex; flex-direction: column; gap: 1px; padding-right: 16px; border-right: 1px solid rgba(255, 255, 255, 0.1); }
.own-how-title b { font-size: 0.95rem; }
.own-how-title .muted { font-size: 0.78rem; }
.own-steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; flex: 1 1 auto; min-width: 0; }
.own-step { display: flex; gap: 8px; align-items: center; min-width: 0; }
.own-step-no { flex: none; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 0.8rem; background: rgba(120, 170, 255, 0.18); color: #bcd3ff; border: 1px solid rgba(120, 170, 255, 0.4); }
.own-step-icon { flex: none; color: var(--muted); display: flex; }
.own-step-title { font-weight: 700; font-size: 0.84rem; line-height: 1.2; }
.own-step-text { font-size: 0.72rem; color: var(--muted); line-height: 1.3; }
/* The list */
.own-list { display: flex; flex-direction: column; gap: 8px; }
/* One slim line on its own dark strip (the page background is light): title, count, sort.
   base.css gives every <select> width 100 % and 38 px; the sort undoes that. */
.own-list-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: nowrap;
  padding: 6px 14px; border-radius: var(--radius-sm);
  background: var(--panel); border: 1px solid rgba(255, 255, 255, 0.1);
}
.own-list-title { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; }
.own-list-title h3 { flex: none; margin: 0; font-size: 0.95rem; font-weight: 600; letter-spacing: 0; }
.own-list-meta { overflow: hidden; text-overflow: ellipsis; }
.own-sort { flex: none; width: auto; min-height: 30px; }
.own-list-icon { flex: none; width: 22px; height: 22px; border-radius: 6px; display: flex; align-items: center; justify-content: center; background: var(--accent-soft); color: var(--accent-light); }
.own-sort { padding: 4px 8px; border-radius: 8px; background: rgba(255, 255, 255, 0.06); color: var(--text); border: 1px solid rgba(255, 255, 255, 0.14); font: inherit; font-size: 0.82rem; }
.own-ladder { display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 8px; }
/* A card */
.own-card {
  position: relative; display: flex; flex-direction: column;
  border-radius: var(--radius); overflow: hidden;
  background: #121a2c; border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: var(--shadow-sm);
}
.own-card.s-open { border-color: rgba(76, 217, 100, 0.55); box-shadow: var(--shadow-sm), 0 0 0 1px rgba(76, 217, 100, 0.15); }
.own-card.s-owned { border-color: rgba(246, 201, 69, 0.55); }
.own-card.s-fighting { border-color: rgba(240, 140, 140, 0.55); }
.own-pill { position: absolute; left: 6px; top: 6px; z-index: 3; display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px; font-size: 0.64rem; font-weight: 800; letter-spacing: 0.02em; background: rgba(14, 20, 36, 0.82); color: var(--muted); border: 1px solid rgba(255, 255, 255, 0.14); }
.own-pill.available { color: #7fe89a; border-color: rgba(76, 217, 100, 0.6); }
.own-pill.owned { color: #ffd45e; border-color: rgba(246, 201, 69, 0.6); }
.own-pill.fighting { color: var(--accent-light); border-color: var(--accent); }
.own-pill.today { color: #bcd3ff; border-color: rgba(120, 170, 255, 0.5); }
.own-no { position: absolute; right: 10px; top: 8px; z-index: 3; font-size: 0.7rem; font-weight: 800; color: rgba(255, 255, 255, 0.85); font-variant-numeric: tabular-nums; padding: 1px 6px; border-radius: 999px; background: rgba(14, 20, 36, 0.7); }
.own-card-body { display: flex; gap: 12px; padding: 10px 10px 8px 156px; min-height: 130px; }
/* The art column (sketch 2): the scene runs from the card's top-left corner to its bottom edge
   and dissolves to the right, under the start of the text; the sprite sits in its left part,
   always in full colour - the picture is the bait, even when the boss is still out of reach. */
.own-art {
  position: absolute; left: 0; top: 0; bottom: 0; width: 236px; padding-right: 88px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(180deg, #101a33 0%, #0a1226 100%);
}
/* The backdrop: the owner's picture (assets/ui/bounties/<id>.webp, set by own.js) over the
   painted scene of the data `art` key, which shows while a picture is missing. */
.own-art::before { content: ""; position: absolute; inset: 0; background: var(--own-image, none) center / cover no-repeat, var(--own-scene, none); }
/* Only a fade to the right, into the card colour - no vignette, the picture stays whole. */
.own-art::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(18, 26, 44, 0) 48%, #121a2c 100%); }
.own-art .own-glow { opacity: 0.85; }
.own-art .own-glow, .own-art .own-sprite { z-index: 1; }
.own-art.art-bridge { --own-scene: radial-gradient(18% 14% at 78% 18%, rgba(255, 244, 214, 0.9), rgba(255, 244, 214, 0) 60%), linear-gradient(180deg, #1a2a55 0%, #223a6b 55%, #5a4630 70%, #3a2c1c 100%); }
.own-art.art-lake { --own-scene: radial-gradient(16% 12% at 74% 16%, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0) 60%), linear-gradient(180deg, #0f1f45 0%, #1b3f7a 52%, #1f5f9c 68%, #123c6a 100%); }
.own-art.art-falls { --own-scene: repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0 8px, transparent 8px 22px), linear-gradient(180deg, #14264d 0%, #2b4f8e 60%, #7fb4d8 82%, #2f5d8a 100%); }
.own-art.art-cave { --own-scene: radial-gradient(60% 50% at 50% 60%, rgba(140, 90, 220, 0.35), transparent 70%), linear-gradient(180deg, #16112b 0%, #24173f 60%, #120c22 100%); }
.own-art.art-harbor { --own-scene: radial-gradient(14% 12% at 80% 18%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 60%), linear-gradient(180deg, #0e2a4a 0%, #14507a 55%, #8ed6ef 75%, #2a6e93 100%); }
.own-art.art-keep { --own-scene: repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.18) 0 14px, transparent 14px 28px), linear-gradient(180deg, #2a2f3a 0%, #4a5262 60%, #2b3038 100%); }
.own-art.art-pass { --own-scene: radial-gradient(50% 40% at 50% 15%, rgba(255, 255, 255, 0.35), transparent 70%), linear-gradient(180deg, #7fa2c9 0%, #dbe9f6 55%, #9fb9d3 100%); }
.own-art.art-spire { --own-scene: repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0 6px, transparent 6px 18px), linear-gradient(180deg, #0c1220 0%, #1f2a3f 60%, #4a5e7a 100%); }
.own-art.art-field { --own-scene: linear-gradient(180deg, #1f4d2f 0%, #2f7a45 70%, #1c4a2a 100%); }
.own-glow { position: absolute; left: 10px; top: 50%; width: 128px; height: 128px; transform: translateY(-50%); border-radius: 50%; background: radial-gradient(circle, var(--own-glow, rgba(255, 255, 255, 0.16)), transparent 68%); }
.own-card.v-shiny { --own-glow: rgba(255, 216, 107, 0.4); }
.own-card.v-dark { --own-glow: rgba(167, 139, 250, 0.42); }
.own-card.v-golden { --own-glow: rgba(255, 176, 32, 0.4); }
.own-card.v-frozen { --own-glow: rgba(143, 227, 255, 0.42); }
.own-card.v-shadow { --own-glow: rgba(110, 76, 150, 0.45); }
.own-sprite { position: relative; width: 108px; height: 108px; image-rendering: pixelated; object-fit: contain; filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.55)); }
.own-card.s-locked .own-text, .own-card.s-today .own-text { opacity: 0.78; }
.own-text { position: relative; z-index: 1; min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 5px; }
.own-title { font-weight: 700; font-size: 1.05rem; letter-spacing: -0.01em; line-height: 1.15; }
.own-name { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; font-size: 0.92rem; font-weight: 600; }
.own-name .variant-label { font-size: 9.5px; }   /* the chip sets its own padding since DECISIONS 473 */
.own-level { color: var(--muted); font-weight: 600; }
.own-types { margin: 0; }
.own-card .type-badge { font-size: 8.5px; padding: 0 5px; line-height: 1.5; }
.own-blurb { margin: 1px 0 0; font-size: 0.78rem; color: var(--muted); font-style: italic; line-height: 1.4; }
.own-facts { display: flex; flex-wrap: wrap; gap: 4px 6px; margin-top: auto; padding-top: 4px; }
.own-facts span { font-size: 0.7rem; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: rgba(255, 255, 255, 0.07); color: var(--muted); }
/* Card foot: the line that says whether you may go in, and the button. */
.own-card-foot { position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; padding: 0 10px 10px 132px; }
.own-foot-line { display: inline-flex; align-items: center; gap: 6px; padding: 5px 8px; border-radius: 8px; font-size: 0.72rem; background: rgba(46, 60, 92, 0.92); border: 1px solid rgba(255, 255, 255, 0.16); color: #d6deec; flex: 1 1 0; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.own-foot-line svg { flex: none; }
.own-foot-line.ok { background: rgba(28, 82, 52, 0.92); border-color: rgba(76, 217, 100, 0.45); color: #a9ecb0; }
.own-foot-line.warn { background: rgba(98, 38, 46, 0.92); border-color: rgba(229, 72, 77, 0.5); color: #ffb0b3; }
.own-challenge { display: flex; flex-direction: column; align-items: center; gap: 0; padding: 5px 14px; min-width: 120px; line-height: 1.15; }
.own-challenge b { font-size: 0.86rem; display: inline-flex; align-items: center; gap: 5px; }
.own-challenge span { font-size: 0.86rem; font-weight: 800; }
@media (max-width: 1100px) {
  .own-hero-row { grid-template-columns: minmax(0, 1fr); }
  .own-how { flex-direction: column; align-items: stretch; }
  .own-how-title { border-right: 0; padding-right: 0; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding-bottom: 8px; }
  .own-steps { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 700px) {
  .own-tiles { grid-template-columns: minmax(0, 1fr); }
  .own-steps { grid-template-columns: minmax(0, 1fr); }
  .own-ladder { grid-template-columns: minmax(0, 1fr); }
  .own-root { zoom: 1; }
  .own-tagline { display: none; }
  .own-hero-top { flex-direction: column; align-items: center; text-align: center; }
  .own-headline { font-size: 1.4rem; }
}
@media (max-width: 480px) {
  .own-card-body { flex-direction: column; align-items: center; padding-left: 10px; min-height: 0; }
  /* one column: the scene becomes a banner above the text, fading down instead of right */
  .own-art { position: relative; width: 100%; height: 150px; padding-right: 0; }
  .own-art::after { background: linear-gradient(180deg, rgba(18, 26, 44, 0) 55%, #121a2c 100%); }
  .own-glow { left: 50%; transform: translate(-50%, -50%); }
  .own-card-foot { padding-left: 10px; }
  .own-text { text-align: center; align-items: center; }
  .own-name, .own-facts { justify-content: center; }
  .own-card-foot { flex-direction: column; align-items: stretch; }
  .own-challenge { width: 100%; }
}

/* --------------------------------------------------------------------------
   Pokémon of the Day (js/ui/daily.js, DECISIONS 169)
   -------------------------------------------------------------------------- */
.daily-root { display: flex; flex-direction: column; gap: 12px; }
.daily-hero { display: flex; gap: 22px; align-items: flex-start; overflow: hidden; position: relative; }
.daily-art { flex: none; position: relative; width: 168px; height: 168px; display: flex; align-items: center; justify-content: center; }
.daily-glow {
  position: absolute; inset: 6%; border-radius: 50%;
  background: radial-gradient(circle, var(--daily-glow, rgba(255, 255, 255, 0.18)), transparent 68%);
}
.daily-hero.v-shiny { --daily-glow: rgba(255, 216, 107, 0.4); }
.daily-hero.v-dark { --daily-glow: rgba(167, 139, 250, 0.4); }
.daily-hero.v-golden { --daily-glow: rgba(255, 176, 32, 0.4); }
.daily-hero.v-frozen { --daily-glow: rgba(143, 227, 255, 0.42); }
.daily-hero.v-shadow { --daily-glow: rgba(110, 76, 150, 0.45); }
.daily-sprite { position: relative; width: 144px; height: 144px; image-rendering: pixelated; object-fit: contain; }
.daily-hero.done .daily-sprite { filter: grayscale(0.7) brightness(0.7); }
.daily-body { min-width: 0; flex: 1 1 auto; }
.daily-kicker { font-size: 0.78rem; font-weight: 700; color: var(--accent-light); margin-bottom: 2px; }
.daily-name { margin: 0 0 6px; font-size: 1.45rem; font-weight: 800; letter-spacing: -0.01em; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.daily-name .variant-label { font-size: 12px; }
.daily-level { font-size: 1rem; font-weight: 700; color: var(--muted); }
.daily-types { margin-bottom: 8px; }
.daily-blurb { margin: 0 0 10px; color: var(--muted); font-style: italic; line-height: 1.45; max-width: 62ch; }
.daily-rules { margin: 0 0 12px; padding-left: 20px; display: flex; flex-direction: column; gap: 5px; font-size: 0.86rem; line-height: 1.45; color: var(--muted); }
.daily-rules b { color: var(--text); }
.daily-status { padding-top: 12px; border-top: 1px solid rgba(255, 255, 255, 0.08); }
.daily-line { margin: 0 0 10px; font-size: 0.92rem; line-height: 1.45; }
.daily-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.daily-countdown { font-size: 0.8rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.daily-status.s-won .daily-line b { color: #4cd964; }
.daily-status.s-caught .daily-line b { color: #f6c945; }
.daily-status.s-lost .daily-line b, .daily-status.s-escaped .daily-line b, .daily-status.s-fainted .daily-line b { color: var(--accent-light); }
/* The ball vendor */
.daily-vendor-note { margin: -4px 0 10px; }
.daily-balls { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 8px; }
.daily-ball {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08);
}
.daily-ball-img { width: 32px; height: 32px; image-rendering: pixelated; flex: none; }
.daily-ball-name { font-weight: 700; font-size: 0.9rem; }
.daily-ball-buy { display: flex; gap: 6px; flex: none; }
/* The outcome panel's "Catch it now" (js/ui/battle.js) */
.btn-daily-catch { background: rgba(246, 201, 69, 0.18); border-color: rgba(246, 201, 69, 0.55); color: #ffe38a; font-weight: 700; }
.btn-daily-catch:hover { background: rgba(246, 201, 69, 0.3); }
@media (max-width: 700px) {
  .daily-hero { flex-direction: column; align-items: center; text-align: center; gap: 10px; }
  .daily-name { justify-content: center; }
  .daily-rules { text-align: left; }
  .daily-actions { justify-content: center; }
}

/* --------------------------------------------------------------------------
   Speed Tower (js/ui/tower.js, DECISIONS 146)
   -------------------------------------------------------------------------- */
.tower-body { display: flex; flex-direction: column; gap: 12px; }
.tower-hero { display: flex; gap: 18px; align-items: flex-start; }
.tower-hero-icon {
  flex: none; width: 72px; height: 72px; border-radius: 18px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent-light);
}
.tower-hero-body { min-width: 0; flex: 1 1 auto; }
.tower-hero-body h3 { margin: 2px 0 8px; font-size: 1.15rem; }
.tower-rules { margin: 0 0 12px; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 6px; font-size: 0.86rem; line-height: 1.45; color: var(--muted); }
.tower-rules b { color: var(--text); }
.tower-hero-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.btn.btn-lg { padding: 10px 18px; font-size: 0.95rem; }
/* The landing between floors: three facts in a row, the clock in the middle. */
.tower-run.boss-next { border-color: rgba(246, 201, 69, 0.45); }
.tower-run-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; align-items: stretch; }
.tower-run-grid > div { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; border-radius: var(--radius-sm); background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.08); }
/* Every "big number" block stacks label / number / note; the summary card used to lay the
   number and its label on one line, so a big "4" ran over "floors cleared" (owner, 22 September). */
.tower-big { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tower-big-label { font-size: 0.72rem; color: var(--muted); }
.tower-big-num { font-size: 1.5rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.tower-clock { font-size: 1.8rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; font-variant-numeric: tabular-nums; color: var(--accent-light); }
.tower-clock.low { color: var(--danger); }
.tower-money-num { font-size: 1.5rem; font-weight: 800; color: #f6c945; letter-spacing: -0.02em; line-height: 1.2; }
.tower-next { margin: 12px 0 8px; font-size: 0.88rem; color: var(--muted); }
.tower-next b { color: #f6c945; }
.tower-party { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.tower-mon { display: flex; flex-direction: column; align-items: center; gap: 3px; width: 56px; }
.tower-mon.fainted .tower-mon-icon { filter: grayscale(1) brightness(0.6); }
.tower-mon-icon { width: 32px; height: 32px; image-rendering: pixelated; }
.tower-mon-bar { width: 100%; }
.tower-mon-bar .hpbar { height: 5px; }
.tower-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
/* Board */
.tower-board .tower-row { padding: 6px 10px; gap: 10px; }
.tower-row.me { background: var(--accent-soft); }
.tower-pos { width: 20px; text-align: right; font-weight: 800; color: var(--muted); font-size: 0.82rem; font-variant-numeric: tabular-nums; }
.tower-row:nth-child(1) .tower-pos { color: #f6c945; }
.tower-name { font-size: 0.88rem; }
.tower-floor { font-weight: 800; font-variant-numeric: tabular-nums; }
.tower-time { min-width: 40px; text-align: right; font-variant-numeric: tabular-nums; }
/* Summary after a run */
.tower-summary.best { border-color: rgba(76, 217, 100, 0.4); }
.tower-summary-head { display: flex; gap: 8px; margin-bottom: 8px; }
.tower-summary-main { display: flex; gap: 26px; align-items: flex-end; flex-wrap: wrap; }
.tower-summary-main .tower-big { flex-direction: column-reverse; }   /* number above its label, like the facts */
.tower-summary-main .tower-big-num { font-size: 2rem; }
.tower-facts { display: flex; gap: 22px; margin: 0; padding-bottom: 2px; }
.tower-facts div { display: flex; flex-direction: column; gap: 2px; }
.tower-facts dt { font-size: 0.72rem; color: var(--muted); order: 2; }
.tower-facts dd { margin: 0; font-size: 1.2rem; font-weight: 800; line-height: 1.15; font-variant-numeric: tabular-nums; order: 1; }
/* The floor + countdown chip on the battle screen (js/ui/battle.js) */
.battle.has-tower { position: relative; }
.tower-chip {
  position: absolute; top: 8px; left: 50%; transform: translateX(-50%); z-index: 5;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 12px; border-radius: 999px;
  background: rgba(14, 21, 38, 0.9); border: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 0.8rem; font-weight: 700; box-shadow: var(--shadow-sm);
  pointer-events: none;
}
.tower-chip .tower-chip-clock { font-variant-numeric: tabular-nums; color: var(--accent-light); font-size: 0.95rem; }
.tower-chip.low .tower-chip-clock { color: var(--danger); animation: pulse-dot 1s ease-in-out infinite; }
.tower-chip.boss { border-color: rgba(246, 201, 69, 0.55); }
@media (max-width: 700px) {
  .tower-hero { flex-direction: column; gap: 10px; }
  .tower-run-grid { grid-template-columns: 1fr; }
}

.challenge-list { gap: 6px; }
.challenge-row { padding: 6px 10px; gap: 10px; color: inherit; font: inherit; width: 100%; text-align: left; }
.challenge-row .challenge-icon {
  flex: none; width: 30px; height: 30px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, 0.08); color: var(--muted);
}
.challenge-row .challenge-title { font-weight: 700; font-size: 0.9rem; }
.challenge-row .challenge-sub { font-size: 0.74rem; color: var(--muted); }
.challenge-row:hover .challenge-icon { color: var(--accent-light); background: var(--accent-soft); }

.soon-view { max-width: 820px; }
.soon-hero { display: flex; gap: 16px; align-items: flex-start; }
.soon-icon {
  flex: none; width: 72px; height: 72px; border-radius: 18px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent-light);
}
.soon-hero h3 { margin-bottom: 6px; }
.soon-hero p { color: var(--text); line-height: 1.5; }
.soon-bullets { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 4px 12px; margin-top: 8px; font-size: 0.85rem; color: var(--muted); }
.soon-bullets li { display: flex; align-items: center; gap: 6px; }
.soon-bullets li svg { color: var(--success); flex: none; }
@media (max-width: 600px) { .soon-hero { flex-direction: column; } }

/* Chat in the right column: same compact scale as the party and player rows. */
#chat-panel .tabs { padding: 2px; gap: 2px; }
#chat-panel .tab { padding: 3px 8px; font-size: 0.72rem; font-weight: 600; }
#chat-panel .tab .badge-count { min-width: 14px; height: 14px; padding: 0 4px; font-size: 9px; }

/* --------------------------------------------------------------------------
   Touch targets (review, 21 September). DECISIONS 125 sized the sidebar rows, the drawer
   buttons, the chat tabs and the view-bar buttons for a mouse (22-30 px). Under a finger
   (`pointer: coarse`: phones, tablets) the primary controls grow to 36-40 px; the layout is
   otherwise the same, the drawer just gets a little taller.
   -------------------------------------------------------------------------- */
@media (pointer: coarse) {
  .party-row { min-height: 40px; }
  .player-row .actions .btn-sm { min-height: 36px; }
  #players-panel .player-row { min-height: 40px; }
  #players-panel .player-row .actions .btn-sm { min-height: 36px; min-width: 36px; padding: 0 8px; }
  #players-panel .player-row .actions .btn-sm svg { width: 15px; height: 15px; }
  .notif-item .actions .btn-sm { min-height: 36px; padding: 4px 12px; }
  .chat-form input, .chat-form .btn { min-height: 38px; font-size: 0.85rem; }
  #chat-panel .tab, .tab { min-height: 36px; }
  #chat-panel .panel-head .btn-icon { width: 32px; height: 32px; min-width: 32px; min-height: 32px; }   /* all four finger-sized (DECISIONS 537) */
  .main > .view-head { min-height: 38px; }
  .main > .view-head .btn, .main > .view-head .actions .btn, .main > .view-head .view-tools .btn { min-height: 32px; padding: 3px 10px; }
}
/* Finger-sized menu rows only on a phone/tablet drawer: a touch-screen laptop also reports a
   coarse pointer and would keep the tall buttons (owner, 23 September, DECISIONS 203). */
@media (pointer: coarse) and (max-width: 899px) {
  .nav-item { height: 38px; font-size: 13px; }
  .nav-item.soon { height: 32px; }
}

/* EXP Boost (DECISIONS 387): one gold line under the money in the trainer card while it runs. */
.tc-boost { font-size: 0.72rem; font-weight: 800; color: #ffd24a; white-space: nowrap; }

/* EXP Boost (DECISIONS 389): the tiny item icon right of the party count while it runs. */
.party-boost { display: inline-flex; margin-left: 6px; }
.party-boost img { width: 14px; height: 14px; filter: drop-shadow(0 0 3px rgba(255, 210, 74, 0.7)); }
/* `hidden` must win from the display of these two (author display beats the attribute default):
   the party icon and the battle pill showed with no boost running (owner, 26 September). */
.party-boost[hidden], .battle-boost-pill[hidden] { display: none; }

/* Town Map: the focused node (#map?focus=<id> - the Pokédex, the Battle Hub, the gym list) wears a gold
   pulse, a dashed ring and "Go here" above it, so the eye lands there at once (DECISIONS 405). */
.map-target-pulse { fill: none; stroke: #ffd24a; stroke-width: 3; opacity: 0.9; transform-box: fill-box; transform-origin: center; animation: map-pulse 1.4s ease-out infinite; }
.map-target-ring { fill: none; stroke: #ffd24a; stroke-width: 2.5; stroke-dasharray: 5 4; }
.map-target-label { font-size: 12px; font-weight: 800; fill: #ffd24a; text-anchor: middle; paint-order: stroke; stroke: rgba(0, 0, 0, 0.65); stroke-width: 3px; letter-spacing: 0.02em; pointer-events: none; }
/* "Go here for Caterpie" from the Pokédex: the name in red (owner, DECISIONS 422) - a shade lighter than
   --accent so it reads on the dark stroke next to the gold. */
.map-target-name { fill: #ff5c5c; }
@media (prefers-reduced-motion: reduce) { .map-target-pulse { animation: none; } }

/* --------------------------------------------------------------------------
   "Giants of Halcyon" on stats.html (size fase 2, DECISIONS 414): the five
   biggest Pokemon, each sprite drawn on its real follower scale
   (40 px + 7.5% per step, js/sprites.js sizeGrow). */
.giant-rows { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; }
.giant-row {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 10px; border-radius: var(--radius-sm, 8px);
  background: var(--panel-2, rgba(255, 255, 255, 0.05));
}
.giant-rank { flex: none; width: 28px; font-weight: 800; font-size: 15px; color: var(--muted, #b8c0d4); font-variant-numeric: tabular-nums; }
.giant-row.first .giant-rank { color: #ffd86b; }
.giant-art { flex: none; width: 96px; display: flex; align-items: flex-end; justify-content: center; }
.giant-art img { image-rendering: pixelated; }
.giant-main { flex: 1; min-width: 0; }
.giant-name { font-weight: 700; }
.giant-sub { color: var(--muted, #b8c0d4); font-size: 12.5px; }
@media (max-width: 600px) {
  /* Mobiel-regel (PLAN-SIZE-VISUEEL par. 3): sprite hooguit 56 px op de telefoon. */
  .giant-art { width: 64px; }
  .giant-art img { max-width: 56px; height: auto; }
  /* Lower cards on a phone (owner, 27 September, DECISIONS 434): sprite capped at 44 px, less padding,
     a narrower rank and art column so the line under the name wraps less. Desktop keeps the real scale. */
  .giant-rows { gap: 4px; }
  .giant-row { gap: 8px; padding: 3px 8px; }
  .giant-rank { width: 22px; font-size: 14px; }
  .giant-art { width: 48px; }
  .giant-art img { max-width: 44px; }
  .giant-sub { font-size: 12px; line-height: 1.3; }
}

/* A mass outbreak on a map node (DECISIONS 532, player idea #36): an orange ring with the species' icon, left of
   the node, and an orange line in the info card. The node keeps its own look. */
.map-node .node-outbreak circle { fill: rgba(18, 26, 44, 0.85); stroke: #ff9f43; stroke-width: 2.5; }
.map-node .node-outbreak { animation: outbreakPulse 1.8s ease-in-out infinite; transform-box: fill-box; }
@keyframes outbreakPulse { 50% { opacity: 0.72; } }
@media (prefers-reduced-motion: reduce) { .map-node .node-outbreak { animation: none; } }
.map-info-outbreak { margin-top: 3px; font-size: 0.8rem; font-weight: 700; color: #ffb872; }

/* ---- Support / donations (DECISIONS 588): the quiet button in the middle of the top bar and the dialog. ---- */
.topbar-donate { color: #e0608a; white-space: nowrap; }
.topbar-donate:hover { color: #f27ba3; }
.topbar-donate[hidden] { display: none; }
.donate-dialog p { margin: 0 0 10px; }
.donate-btn-row { margin: 14px 0 10px; text-align: center; }
.donate-link { display: inline-flex; align-items: center; gap: 6px; }
.admin-donate-row { display: flex; align-items: center; gap: 10px; margin: 10px 0; }
.admin-donate-row .input { max-width: 420px; }
@media (max-width: 760px) {
  /* The bar is full on a phone: the heart alone is the button. */
  .topbar-donate-text { display: none; }
}

/* ---- Alipay in the donate dialog (DECISIONS 589): the QR picture the owner uploaded. ---- */
.donate-choose { margin-top: 12px; }
.donate-btn-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.donate-panel { text-align: center; margin: 4px 0 10px; }
.donate-panel img { width: 200px; max-width: 80%; border-radius: 8px; background: #fff; padding: 6px; }
.donate-panel .btn { margin-top: 6px; }
.donate-bank { text-align: left; background: rgba(255, 255, 255, .55); border: 1px solid rgba(0, 0, 0, .08); border-radius: 8px; padding: 8px 10px; margin: 8px 0; }
.donate-bank-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.donate-bank-head .btn { margin-top: 0; }
.donate-bank-line { font-family: ui-monospace, Consolas, monospace; font-size: .92em; overflow-wrap: anywhere; }
.admin-donate-bank { max-width: 420px; min-height: 64px; }

/* ---- Alipay tucked away (DECISIONS 590): a quiet text link under the method buttons, no button look. ---- */
.donate-quiet-row { text-align: center; margin: 2px 0 8px; }
.donate-quiet { background: none; border: 0; padding: 0; font: inherit; color: inherit; opacity: .75; text-decoration: underline; cursor: pointer; }
.donate-quiet:hover { opacity: 1; }

/* ---- Nicer donate dialog (DECISIONS 590): methods in one soft heart-tinted box, perks note tiny. ---- */
.donate-box { background: rgba(224, 96, 138, .07); border: 1px solid rgba(224, 96, 138, .22); border-radius: 10px; padding: 12px 12px 6px; margin: 14px 0 10px; }
.donate-box .donate-btn-row { margin: 8px 0; }
.donate-choose { text-align: center; margin: 0 0 2px; }
.donate-btn-row .btn { min-width: 128px; justify-content: center; }
.donate-perks { text-align: center; margin-bottom: 0; }

/* ---- CNY shortcut for Chinese players (DECISIONS 591): a quiet link row under the method buttons. ---- */
.donate-cn-row { text-align: center; margin: 2px 0 4px; }
.donate-cn-row a { color: inherit; opacity: .8; text-decoration: underline; }
.donate-cn-row a:hover { opacity: 1; }

/* ---- Name line in the bank blocks (DECISIONS 592): readable, but the eye should land on the IBAN. ---- */
.donate-bank-name { opacity: .55; }

/* ---- Admin announcements (DECISIONS 595): the popup players get. ---- */
.announce-dialog p { margin: 0 0 10px; }
.announce-icon { text-align: center; margin: 2px 0 8px; color: #d64545; }
.announce-sub { margin-top: -4px; }
