/* ===================== THE PHONE UI: "LEGIBLE HOLOGRAM" (2026-10-15, PHONE UI lane 0 FOUNDATION) =====================
   Josh approved the phone design 2026-10-15 ~01:30 ICT ("approve all"): the standard is PHONE-DESIGN-SYSTEM.md (tokens,
   components, rails T1-T40), the screens SCREEN-SPECS.md, the frames mockups.html (scratchpad mobile/design/).
   PHONES ONLY. Every selector here starts with html.is-phone (client/js/platform.js formFactorOf, set before the first
   paint), every --p- token is declared under html.is-phone, so a desktop, a console and a TABLET (client/css/
   tablet-menu.css, frozen) never match a rule of this file; their pixels are proven unchanged against 77da9cc9.
   Laws kept here (design system 0, 10): class hooks, never widths (a CSS-turned match sees the physical viewport);
   one scroller per page (the page region); bars are grid rows, not fixed children of a scroller; transform and opacity
   are the only animated properties; no backdrop-filter; glow never carries legibility; amber on one element a screen.
   The shell's DOM is built by client/js/phone/shell.js (top bar, tab bar / rail, sheets, toasts) and drives the
   existing controls (the touchnav proxy pattern), so every id, main.js handler and the pad's spatial navigation stay.
   Sections: 1 faces . 2 tokens . 3 the shell . 4 top bar . 5 tab bar + rail . 6 buttons . 7 controls . 8 cards, rows,
   tiles . 9 sheet, toast, scrim . 10 motion . 11 the existing panes hosted in the shell (interim, until lanes 1-4
   rebuild each screen) . 12 the front door (interim). */

/* ------------------------------------------------------------------------------------------------ 1. FACES
   tools/fontsubset.mjs builds them (Latin subsets, hinting dropped, WOFF2) from google/fonts ofl/chakrapetch and
   ofl/sairasemicondensed; Saira's subsets carry the name TB Body (its Reserved Font Name, SIL OFL 1.1 condition 3).
   A face downloads only when a phone rule asks for its family: no desktop or tablet rule names these families.
   unicode-range is each subset's real cmap (printed by the tool): anything else falls to the system face.
   TB Num / TB Num Display: the ten figures centred in one advance (neither family has 'tnum'); listed first in the
   numeric stacks, they draw only the figures, so a ticking timer or a counting gold value never shimmies. */
@font-face { font-family: 'Chakra Petch'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('../assets/fonts/chakrapetch-600.woff2') format('woff2');
  unicode-range: U+0020-007E, U+00A0-00B4, U+00B6-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+2007, U+2010, U+2012-2015, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2044, U+20AC, U+2122, U+2190-2193, U+2212, U+2215; }
@font-face { font-family: 'Chakra Petch'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../assets/fonts/chakrapetch-700.woff2') format('woff2');
  unicode-range: U+0020-007E, U+00A0-00B4, U+00B6-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+2007, U+2010, U+2012-2015, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2044, U+20AC, U+2122, U+2190-2193, U+2212, U+2215; }
@font-face { font-family: 'TB Body'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('../assets/fonts/tbbody-500.woff2') format('woff2');
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+2002, U+2013-2014, U+2017-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+203C, U+2044, U+20AC, U+2122, U+2190-2193, U+2212; }
@font-face { font-family: 'TB Body'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('../assets/fonts/tbbody-600.woff2') format('woff2');
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+2002, U+2013-2014, U+2017-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+203C, U+2044, U+20AC, U+2122, U+2190-2193, U+2212; }
@font-face { font-family: 'TB Body'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../assets/fonts/tbbody-700.woff2') format('woff2');
  unicode-range: U+0020-007E, U+00A0-00FF, U+0131, U+0152-0153, U+02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+2002, U+2013-2014, U+2017-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+203C, U+2044, U+20AC, U+2122, U+2190-2193, U+2212; }
@font-face { font-family: 'TB Num'; font-style: normal; font-weight: 500; font-display: swap;
  src: url('../assets/fonts/tbnum-500.woff2') format('woff2'); unicode-range: U+0030-0039; }
@font-face { font-family: 'TB Num'; font-style: normal; font-weight: 600; font-display: swap;
  src: url('../assets/fonts/tbnum-600.woff2') format('woff2'); unicode-range: U+0030-0039; }
@font-face { font-family: 'TB Num'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../assets/fonts/tbnum-700.woff2') format('woff2'); unicode-range: U+0030-0039; }
@font-face { font-family: 'TB Num Display'; font-style: normal; font-weight: 700; font-display: swap;
  src: url('../assets/fonts/tbnumdisp-700.woff2') format('woff2'); unicode-range: U+0030-0039; }

/* ------------------------------------------------------------------------------------------------ 2. TOKENS
   Design system 2.1-2.6. Contrast figures are tools/contrast.mjs's (design folder). */
html.is-phone {
  /* colour roles */
  --p-bg-0: #02060C; --p-bg-1: #061120; --p-bg-2: #0A1A2C; --p-plate: rgba(8, 22, 38, 0.88);
  --p-raise: #0F2740; --p-sel: #133A5C; --p-line: #24506F; --p-line-ui: #3D86B8; --p-holo: #54C8FF;
  --p-ice: #EAF6FF; --p-text-2: #A8C3DC; --p-text-3: #86A2BE;
  --p-amber-1: #FFB547; --p-amber-2: #FF8A00; --p-amber-ink: #170B00; --p-gold: #FFD447;
  --p-danger: #FF5A5A; --p-warn: #FFC23D; --p-gain: #41E08A; --p-loss: #FF5A5A;
  --p-team-red: #FF4D4D; --p-team-blue: #4A93FF; --p-badge: #D8232A; --p-scrim: rgba(2, 6, 12, 0.66);
  /* the component colours of the mockups, named (T29: no raw colour in a phone rule) */
  --p-wash-1: rgba(6, 17, 32, 0.88); --p-wash-2: rgba(6, 17, 32, 0.9); --p-wash-3: rgba(2, 6, 12, 0.96);   /* the field over the held backdrop */
  --p-bar-fade: rgba(6, 17, 32, 0.96); --p-line-soft: rgba(36, 80, 111, 0.6);
  --p-bar-1: #0B1E33; --p-bar-2: #081525;                      /* the tab bar and rail */
  --p-pp-fill: #0D2640; --p-pp-fill-on: #15406A;               /* the raised PLAY plate */
  --p-emb-fill: #102C4A; --p-field-bg: #081626; --p-chip-fill: rgba(10, 26, 44, 0.94);
  --p-amber-hi: rgba(255, 255, 255, 0.6); --p-amber-lo: rgba(130, 55, 0, 0.35); --p-amber-ink-off: rgba(23, 11, 0, 0.78);
  --p-sheen: rgba(255, 255, 255, 0.3); --p-edge-hi: rgba(255, 255, 255, 0.14); --p-mask-on: #000;
  --p-badge-ink: #FFFFFF; --p-check-ink: #04101C; --p-accent-glow: rgba(84, 200, 255, 0.8); --p-grab: rgba(168, 195, 220, 0.5);
  --p-sheet-line: rgba(84, 200, 255, 0.55); --p-danger-ring: rgba(255, 90, 90, 0.45); --p-sheet-shade: rgba(0, 0, 0, 0.45);
  --p-stage-glow: rgba(84, 200, 255, 0.18); --p-stage-glow-0: rgba(84, 200, 255, 0); --p-stage-top: #0A1626; --p-stage-bot: #050C16;
  --p-holo-hair: rgba(84, 200, 255, 0.4);      /* the tab bar's top hairline (holo at 40%) */
  --p-holo-hi: rgba(84, 200, 255, 0.18);       /* e2's 1 px top highlight */
  --p-holo-glow: rgba(84, 200, 255, 0.45);
  --p-amber-ring: rgba(255, 181, 71, 0.55);
  --p-holo-ring: rgba(84, 200, 255, 0.5);
  /* rarity (shared/rarity.js) */
  --p-r-common: #f2f4f8; --p-r-uncommon: #37f07d; --p-r-rare: #35c8ff; --p-r-epic: #b57bff;
  --p-r-mythic: #ffc23d; --p-r-unique: #ff4d4d; --p-r-exclusive: #ff8c00;
  /* faces */
  --p-f-fallback: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --p-f-disp: 'Chakra Petch', 'TB Body', var(--p-f-fallback);
  --p-f-ui: 'TB Body', var(--p-f-fallback);
  --p-f-num: 'TB Num', 'TB Body', var(--p-f-fallback);
  --p-f-num-disp: 'TB Num Display', 'Chakra Petch', 'TB Body', var(--p-f-fallback);
  /* type scale (2.3): size / line in px x the TEXT SIZE setting (--p-ts: 1, 1.15, 1.3); tracking beside it */
  --p-ts: 1;
  --p-t-display-xl: 700 calc(40px * var(--p-ts)) / calc(44px * var(--p-ts)) var(--p-f-disp);  --p-tr-display-xl: 0.04em;
  --p-t-display: 700 calc(32px * var(--p-ts)) / calc(36px * var(--p-ts)) var(--p-f-disp);     --p-tr-display: 0.04em;
  --p-t-title: 700 calc(22px * var(--p-ts)) / calc(28px * var(--p-ts)) var(--p-f-disp);       --p-tr-title: 0.08em;
  --p-t-headline: 700 calc(18px * var(--p-ts)) / calc(24px * var(--p-ts)) var(--p-f-ui);      --p-tr-headline: 0.04em;
  --p-t-button: 700 calc(17px * var(--p-ts)) / calc(20px * var(--p-ts)) var(--p-f-ui);        --p-tr-button: 0.06em;
  --p-t-body: 500 calc(16px * var(--p-ts)) / calc(22px * var(--p-ts)) var(--p-f-ui);          --p-tr-body: 0;
  --p-t-label: 600 calc(14px * var(--p-ts)) / calc(18px * var(--p-ts)) var(--p-f-ui);         --p-tr-label: 0.08em;
  --p-t-caption: 500 calc(13px * var(--p-ts)) / calc(18px * var(--p-ts)) var(--p-f-ui);       --p-tr-caption: 0;
  --p-t-micro: 700 calc(12px * var(--p-ts)) / calc(16px * var(--p-ts)) var(--p-f-ui);         --p-tr-micro: 0.04em;
  /* spacing (4 px base) */
  --p-s1: 4px; --p-s2: 8px; --p-s3: 12px; --p-s4: 16px; --p-s6: 24px; --p-s8: 32px; --p-s12: 48px;
  /* shape: chamfer cuts (top-left + bottom-right), the pill only for badges and avatars */
  --p-cut-s: 6px; --p-cut-m: 10px; --p-cut-l: 16px; --p-r-pill: 999px;
  /* elevation (2.5): no blur anywhere */
  --p-e3-shadow: 0 -16px 40px rgba(0, 0, 0, 0.55);
  --p-e4-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  /* motion (2.6) */
  --p-dur-press: 90ms; --p-dur-micro: 150ms; --p-dur-std: 240ms; --p-dur-page: 280ms;
  --p-dur-sheet-in: 320ms; --p-dur-sheet-out: 220ms; --p-dur-count: 400ms;
  --p-ease-out: cubic-bezier(0.05, 0.7, 0.1, 1); --p-ease-in: cubic-bezier(0.3, 0, 0.8, 0.15); --p-ease-std: cubic-bezier(0.2, 0, 0, 1);
  --p-rise: 8px; --p-slide: 24px; --p-dur-shimmer: 900ms; --p-dur-turn: 1.6s;
  /* the shell (3.2): bars and insets */
  --p-top-h: 56px; --p-tab-h: 64px; --p-act-h: 96px; --p-rail-w: 80px;
  --p-inset-t: env(safe-area-inset-top, 0px); --p-inset-b: env(safe-area-inset-bottom, 0px);
  --p-inset-l: env(safe-area-inset-left, 0px); --p-inset-r: env(safe-area-inset-right, 0px);
  --p-gutter-l: max(16px, var(--p-inset-l)); --p-gutter-r: max(16px, var(--p-inset-r));
  --p-col-max: 398px;
  /* component geometry (mockups.html): offsets that are not spacing */
  --p-hair: 1px; --p-tag-y: 2px; /* a tag pill's top/bottom room: its words never touch its frame (T9) */ --p-hair-up: -1px; --p-ring-out: -4px; --p-focus-out: -5px;
  --p-pp-rise: -13px; --p-pp-half: -36px;                       /* the PLAY plate: 72 px, 12 px above the bar's hairline */
  --p-bar-slot: 64px;                                          /* a pushed page's Back / action slot */
  --p-badge-x: calc(50% + 6px); --p-dot-x: calc(50% + 10px); --p-ibadge-x: calc(50% + 4px);
  --p-chip-inset: 5px 1px; --p-gold-inset: 5.5px 1.5px; --p-thumb-y: -12px; --p-cb-pull: 0 -9px 0 0; --p-tgl-pad: 2px; --p-tgl-track-y: 6.5px; --p-tgl-knob-y: 8.5px;   /* the toggle: a 51 x 44 box (T7), its 31 px track and 27 px knob centred in it */
  --p-sheen-x: -90px; --p-sheet-head-x: 56px; --p-wide-top: 52px; --p-bar-pad: 6px;   /* a 44 px control centred in the 56 px bar */ --p-ind-in: 24%; --p-ind-in-rail: 20%;
  /* what the chrome takes at the top and bottom of the page region (the shell's state classes set the bottom) */
  --p-chrome-t: calc(var(--p-top-h) + var(--p-inset-t));
  --p-chrome-b: calc(var(--p-tab-h) - var(--p-pp-rise) + var(--p-inset-b));   /* a tab root's page ends at the raised PLAY plate's top */
  --p-app-h: 100dvh;
}
html.is-phone.p-ts-115 { --p-ts: 1.15; }
html.is-phone.p-ts-130 { --p-ts: 1.3; }
/* COLOUR-BLIND SAFE (2.2): only gain / loss change; team hues already pass every simulated type */
html.is-phone.p-cb { --p-gain: #4FA8FF; --p-loss: #FF8A3D; }
/* reduced motion (the OS, or Settings > Accessibility > REDUCE MOTION = html.p-rm): every transition a 150 ms fade */
@media (prefers-reduced-motion: reduce) {
  html.is-phone { --p-dur-std: 150ms; --p-dur-page: 150ms; --p-dur-sheet-in: 150ms; --p-dur-sheet-out: 150ms; --p-rise: 0px; --p-slide: 0px; }
}
html.is-phone.p-rm { --p-dur-std: 150ms; --p-dur-page: 150ms; --p-dur-sheet-in: 150ms; --p-dur-sheet-out: 150ms; --p-rise: 0px; --p-slide: 0px; }

/* typography utilities */
html.is-phone .p-t-display-xl { font: var(--p-t-display-xl); letter-spacing: var(--p-tr-display-xl); text-transform: uppercase; }
html.is-phone .p-t-display { font: var(--p-t-display); letter-spacing: var(--p-tr-display); text-transform: uppercase; }
html.is-phone .p-t-title { font: var(--p-t-title); letter-spacing: var(--p-tr-title); text-transform: uppercase; }
html.is-phone .p-t-headline { font: var(--p-t-headline); letter-spacing: var(--p-tr-headline); text-transform: uppercase; }
html.is-phone .p-t-button { font: var(--p-t-button); letter-spacing: var(--p-tr-button); text-transform: uppercase; }
html.is-phone .p-t-body { font: var(--p-t-body); }
html.is-phone .p-t-label { font: var(--p-t-label); letter-spacing: var(--p-tr-label); text-transform: uppercase; }
html.is-phone .p-t-caption { font: var(--p-t-caption); }
html.is-phone .p-t-micro { font: var(--p-t-micro); letter-spacing: var(--p-tr-micro); text-transform: uppercase; }
html.is-phone :is(.p-t-display-xl, .p-t-display, .p-t-title, .p-t-headline) { text-wrap: balance; }
html.is-phone .p-num { font-family: var(--p-f-num); font-variant-numeric: tabular-nums; }
html.is-phone :is(.p-t-display-xl, .p-t-display).p-num { font-family: var(--p-f-num-disp); }
html.is-phone .p-ell { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
html.is-phone .p-c-ice { color: var(--p-ice); }
html.is-phone .p-c-2 { color: var(--p-text-2); }
html.is-phone .p-c-3 { color: var(--p-text-3); }
html.is-phone .p-c-holo { color: var(--p-holo); }
html.is-phone .p-c-gold { color: var(--p-gold); }
html.is-phone .p-c-gain { color: var(--p-gain); }
html.is-phone .p-c-loss { color: var(--p-loss); }
html.is-phone .p-c-warn { color: var(--p-warn); }
html.is-phone .p-c-danger { color: var(--p-danger); }
/* an icon (client/js/phone/icons.js): line-work in currentColor */
html.is-phone .p-ic { width: 24px; height: 24px; flex: none; display: block; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
html.is-phone .p-ic--s { width: 18px; height: 18px; }
html.is-phone .p-ic--l { width: 28px; height: 28px; }
html.is-phone .p-ic--xl { width: 40px; height: 40px; }
/* team marks (2.2): a shape beside the word, never colour alone */
html.is-phone .p-team-red::before { content: ""; display: inline-block; width: 0; height: 0; margin-right: var(--p-s2); vertical-align: 0;
  border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 10px solid var(--p-team-red); }
html.is-phone .p-team-blue::before { content: ""; display: inline-block; width: 10px; height: 10px; margin-right: var(--p-s2); vertical-align: 0; background: var(--p-team-blue); }
html.is-phone .p-team-red { color: var(--p-team-red); }
html.is-phone .p-team-blue { color: var(--p-team-blue); }
/* the visually-hidden text an icon control may carry for assistive tech */
html.is-phone .p-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* THE CHAMFERED FRAME (2.4): a clipped corner top-left and bottom-right, never a skew. The element itself is not
   clipped (its hit area is the whole rectangle, its focus ring shows); ::before paints the border, ::after the fill,
   each cut to the chamfer. --p-c the cut, --p-bw the border width, --p-bc / --p-fill the colours. */
html.is-phone .p-fr { position: relative; isolation: isolate; --p-c: var(--p-cut-m); --p-bw: 1.5px; --p-bc: var(--p-line-ui); --p-fill: var(--p-bg-2); }
html.is-phone .p-fr::before, html.is-phone .p-fr::after {
  content: ""; position: absolute; pointer-events: none;
  clip-path: polygon(var(--p-c) 0, 100% 0, 100% calc(100% - var(--p-c)), calc(100% - var(--p-c)) 100%, 0 100%, 0 var(--p-c));
}
html.is-phone .p-fr::before { inset: 0; background: var(--p-bc); z-index: -2; }
html.is-phone .p-fr::after { inset: var(--p-bw); background: var(--p-fill); z-index: -1; }
html.is-phone .p-cut { clip-path: polygon(var(--p-c, var(--p-cut-m)) 0, 100% 0, 100% calc(100% - var(--p-c, var(--p-cut-m))), calc(100% - var(--p-c, var(--p-cut-m))) 100%, 0 100%, 0 var(--p-c, var(--p-cut-m))); }

/* ------------------------------------------------------------------------------------------------ 3. THE SHELL
   html.p-shell is on while the signed-in menu shows on a phone and no match runs (client/js/phone/shell.js). #menu
   becomes the grid of 3.2: top bar / page (THE one scroller) / tab bar, its height the dynamic viewport (100dvh: the
   visible height, Safari 15.4+, Chrome 108+; the shell writes --p-app-h from visualViewport only where dvh is not
   supported). The bars are rows, never position: fixed children of the scroller (iOS 26 Safari cuts fixed content off
   under its floating controls: Apple forums 800798). The field is navy over the held (still) map backdrop.
   Pushed pages (html.p-pushed) hide the tab bar; a page with a commit action shows the action bar (html.p-act). */
html.is-phone.p-shell { background: var(--p-bg-0); }
html.is-phone.p-shell body { background: var(--p-bg-0); }
/* OFF THE SHELL (signed out: the front door, sign-in, early access, the connect spinner; and every match) the shell's bars
   are not part of the page. shell.js builds them into #menu once at boot, so without this they were laid out as flow
   children of the desktop #menu: top bar, landing and tab bar side by side, PLAY pushed off a 393 px screen (lane 5
   contract walk 2026-10-15 08:40). Hidden, the signed-out screens are the base build's (77da9cc9) until their lane
   rebuilds them, plus the wave-7 thumb sizes below (THE FRONT DOOR). */
html.is-phone:not(.p-shell) :is(#p-topbar, #p-tabbar, #p-rail, #p-actionbar) { display: none; }
html.is-phone.p-shell #menu.halo {
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto;
  align-items: stretch; justify-content: stretch;
  top: 0; bottom: auto; height: var(--p-app-h); padding: 0; zoom: 1;
  overflow: hidden; overflow: clip;   /* clip: not a scroll container at all, so a scrollIntoView (Customize's head card) can
                                         never shift the whole shell (it moved it 1 px on an iPhone SE: overflow hidden still scrolls) */
  font-family: var(--p-f-ui); color: var(--p-ice);
  --fmin: 12px;
}
html.is-phone.p-shell #menu.halo .menu-bg { background: linear-gradient(180deg, var(--p-wash-1) 0%, var(--p-wash-2) 45%, var(--p-wash-3) 100%); }
html.is-phone.p-shell #menu.halo .menu-grid { display: none; }   /* the drifting grid is a second full-screen animation: not on a phone's idle menu */
html.is-phone.p-shell #menu.halo #menu-tools { display: none; }
html.is-phone.p-shell #menu.halo :is(.menu-h1, #menu-logo, #contract-banner, .halo-hints, #menu-nav) { display: none !important; }
/* the old floating chrome of the top edge: friends, the X pill + resources + gifts, chat, the corner meters; each one
   now lives in the top bar, the SOCIAL tab or SHOP > ITEMS */
html.is-phone.p-shell :is(#friends-fab, #top-pills, #mobile-chat-btn, #rank-box, #party-widget, #wallet-cta) { display: none !important; }
html.is-phone.p-shell #mobile-chat-btn.p-keep { display: inline-flex !important; }

/* row 1: the top bar (section 4); row 3: the tab bar (section 5) */
html.is-phone.p-shell #p-topbar { grid-row: 1; grid-column: 1; }
html.is-phone.p-shell #p-tabbar { grid-row: 3; grid-column: 1; }
html.is-phone.p-shell #p-actionbar { grid-row: 3; grid-column: 1; }
/* row 2: THE PAGE, the one scroller (design system 0.4; memory menu-rebuild-halo MOBILE SCROLL TRAP) */
html.is-phone.p-shell #menu.halo .menu-content {
  grid-row: 2; grid-column: 1; position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: stretch; gap: 0;
  width: 100%; max-width: none; height: auto; min-height: 0; margin: 0;
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  padding: var(--p-s4) var(--p-gutter-r) var(--p-s6) var(--p-gutter-l);
  scroll-padding-top: var(--p-s4);
}
html.is-phone.p-shell #menu.halo .halo-panes { display: block; width: 100%; max-width: none; max-height: none; padding: 0; margin: 0; overflow: visible; }
/* the page's column is a flex column of a FIXED height (the grid row): its parts keep their content height and the
   page scrolls, they never shrink to fit (a part that clips its overflow would otherwise collapse under the next) */
html.is-phone.p-shell #menu.halo .menu-content > *, html.is-phone.p-shell #menu.halo #menu-lobby:not(.hidden) > * { flex-shrink: 0; }
html.is-phone.p-shell #menu.halo .halo-panes .menu-card { max-width: none; width: 100%; margin: 0; max-height: none; }
/* the keyboard is up (visualViewport under 70% of the layout height): the tab bar and the action bar step aside */
html.is-phone.p-shell.p-kbd :is(#p-tabbar, #p-actionbar, #p-rail) { display: none; }
/* a tab root: the page ends where the raised PLAY plate (and the PLAY tab's raised box) begins, so nothing scrolls under
   it and no control sits inside the PLAY tab's hit box (lane 7 T7 430x739: pick-crate chips vs the PLAY box, gap -1) */
html.is-phone.p-shell:not(.p-pushed):not(.p-wide):not(.p-kbd) #menu.halo .menu-content { margin-bottom: calc(var(--p-s2) - var(--p-pp-rise)); }   /* 8 px above the plate (T7) */
/* sideways (the rail, no tab bar) the boards keep the plain tab-height bottom they had */
html.is-phone.p-shell.p-wide:not(.p-pushed) :is(#crates-screen, #career-screen, #challenges-screen, #leaderboard-screen, #worldstats-screen,
  #winloss-screen, #armor-skins, #weapon-skins, #nameplate-screen, #progress-screen, #settings-panel, #controls-panel,
  #pred-card-screen, #pred-match-screen, #pred-create-screen, #vault-panel, #friends-panel, #menu-chat, .p-chrome-fit) { --p-chrome-b: calc(var(--p-tab-h) + var(--p-inset-b)); }
/* pushed pages: no tab bar; the page runs to the home-indicator inset, or to the action bar */
html.is-phone.p-shell.p-pushed #p-tabbar { display: none; }
html.is-phone.p-shell.p-pushed #menu.halo .menu-content { padding-bottom: calc(var(--p-s6) + var(--p-inset-b)); }
html.is-phone.p-shell.p-pushed.p-act #menu.halo .menu-content { padding-bottom: var(--p-s6); }

/* a lane's own tab root or pushed page (client/js/phone/shell.js setRoot / push) replaces the hosted panes */
html.is-phone.p-shell.p-own #menu.halo :is(.halo-panes, .halo-left, .menu-footer) { display: none !important; }
html.is-phone :is(.p-rootview, .p-pageview)[hidden] { display: none; }
html.is-phone :is(.p-rootview, .p-pageview) { display: block; width: 100%; }

/* ------------------------------------------------------------------------------------------------ 4. TOP BAR
   7.1 (tab roots): profile chip left; gold chip, bell, gear right; identical on all five roots. 7.2 (pushed pages):
   [Back 44] [title, centred] [44 action or empty], Back at one rect on every page (rail T17). */
html.is-phone #p-topbar {
  position: relative; z-index: 3; display: flex; align-items: center; justify-content: space-between; gap: var(--p-s2);
  height: calc(var(--p-top-h) + var(--p-inset-t)); padding: var(--p-inset-t) var(--p-gutter-r) 0 var(--p-gutter-l);
  background: linear-gradient(180deg, var(--p-bg-0) 0%, var(--p-bar-fade) 100%);
  border-bottom: var(--p-hair) solid var(--p-line-soft);
}
html.is-phone #p-topbar .p-tb-root, html.is-phone #p-topbar .p-tb-page { display: flex; align-items: center; justify-content: space-between; width: 100%; height: var(--p-top-h); gap: var(--p-s2); }
html.is-phone #p-topbar .p-tb-page { display: none; position: relative; }
html.is-phone.p-pushed #p-topbar .p-tb-root { display: none; }
html.is-phone.p-pushed #p-topbar .p-tb-page { display: flex; }
html.is-phone #p-topbar .p-tb-right { display: flex; align-items: center; gap: var(--p-s2); flex: none; }   /* T7: 8 px between hit boxes */
/* the profile chip: emblem 36, callsign over level */
html.is-phone .p-pchip {
  display: flex; align-items: center; gap: var(--p-s2); height: 44px; max-width: 150px; min-width: 0; flex: 0 1 auto;
  padding: 0; background: none; border: 0; color: var(--p-ice); cursor: pointer; text-align: left;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-pchip .p-emb { width: 36px; height: 36px; flex: none; --p-c: 7px; --p-bw: 1px; --p-bc: var(--p-holo); --p-fill: var(--p-emb-fill);
  display: flex; align-items: center; justify-content: center; }
html.is-phone .p-pchip .p-emb img { width: 30px; height: 30px; object-fit: contain; display: block; }
html.is-phone .p-pchip .p-emb .p-ic { width: 22px; height: 22px; color: var(--p-holo); }
html.is-phone .p-pchip .p-pchip-txt { display: flex; flex-direction: column; min-width: 0; }
html.is-phone .p-pchip .p-pchip-nm { font: 700 calc(14px * var(--p-ts)) / calc(16px * var(--p-ts)) var(--p-f-ui); letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
html.is-phone .p-pchip .p-pchip-lv { font: 600 calc(12px * var(--p-ts)) / calc(14px * var(--p-ts)) var(--p-f-ui); letter-spacing: 0.04em; color: var(--p-text-2); text-transform: uppercase; white-space: nowrap; }
html.is-phone .p-pchip .p-pchip-lv b { font-family: var(--p-f-num); font-weight: 600; }
/* the gold chip: 36 visual, 44 hit (the button is 44 tall; the frame is drawn 36) */
html.is-phone .p-goldchip {
  position: relative; display: flex; align-items: center; gap: var(--p-s2); height: 44px; margin: 0; padding: 0 var(--p-s1) 0 var(--p-s3);
  background: none; border: 0; color: var(--p-ice); cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-goldchip::before, html.is-phone .p-goldchip::after {
  content: ""; position: absolute; left: 0; right: 0; top: var(--p-s1); bottom: var(--p-s1); pointer-events: none;
  clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
}
html.is-phone .p-goldchip::before { background: var(--p-line-ui); z-index: -2; }
html.is-phone .p-goldchip::after { inset: var(--p-gold-inset); background: var(--p-bg-2); z-index: -1; }
html.is-phone .p-goldchip { isolation: isolate; }
html.is-phone .p-goldchip .p-gold-glyph { color: var(--p-gold); font: 700 16px/1 var(--p-f-fallback); }
html.is-phone .p-goldchip .p-gold-v { font: 700 calc(14px * var(--p-ts)) / 1 var(--p-f-num); letter-spacing: 0.02em; color: var(--p-ice); font-variant-numeric: tabular-nums; }
html.is-phone .p-goldchip .p-gold-plus { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; color: var(--p-holo);
  --p-c: 5px; --p-bw: 1px; --p-bc: var(--p-holo); --p-fill: var(--p-raise); }
html.is-phone .p-goldchip .p-gold-plus .p-ic { width: 16px; height: 16px; }
/* an icon button (bell, gear, close): 44 x 44, 24 px glyph */
html.is-phone .p-iconbtn {
  position: relative; display: flex; align-items: center; justify-content: center; flex: none;
  width: 44px; height: 44px; padding: 0; background: none; border: 0; color: var(--p-ice); cursor: pointer;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-iconbtn:is(:active, .is-pressed) { color: var(--p-holo); }
/* the pushed page's bar: Back (chevron + BACK), the title centred between equal 72 px slots, the action slot */
html.is-phone .p-back {
  position: absolute; left: var(--p-ring-out); top: var(--p-bar-pad); height: 44px; min-width: var(--p-bar-slot); display: flex; align-items: center; gap: 0;
  padding: 0 var(--p-s1); background: none; border: 0; color: var(--p-holo); cursor: pointer;
  font: 700 calc(14px * var(--p-ts)) / 1 var(--p-f-ui); letter-spacing: 0.08em; text-transform: uppercase;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-tb-title {
  position: absolute; left: var(--p-bar-slot); right: var(--p-bar-slot); top: 0; height: var(--p-top-h); display: flex; align-items: center; justify-content: center;
  font: var(--p-t-title); letter-spacing: var(--p-tr-title); text-transform: uppercase; color: var(--p-ice); text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0;
}
/* Back to PLAY on another tab's root while the phone is held sideways (the rail has four tabs, no PLAY) */
html.is-phone #p-topbar .p-back--root { display: none; position: static; flex: none; }
html.is-phone.p-wideback #p-topbar .p-back--root { display: flex; }
html.is-phone .p-tb-act { position: absolute; right: var(--p-ring-out); top: var(--p-bar-pad); height: 44px; min-width: 44px; display: flex; align-items: center; justify-content: center; }

/* ------------------------------------------------------------------------------------------------ 5. TAB BAR + RAIL
   7.3 / 6.1: CUSTOMIZE | SHOP | PLAY | CAREER | SOCIAL, five equal slots, PLAY a raised 72 x 72 chamfered plate (navy,
   cyan frame: never amber, so the home's amber PLAY stays the screen's one warm element). 6.3 / T12: a phone held
   sideways with a box under 480 px tall gets a left RAIL of the four other tabs instead (html.p-wide). */
html.is-phone #p-tabbar {
  position: relative; z-index: 3; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  height: calc(var(--p-tab-h) + var(--p-inset-b)); padding: 0 var(--p-inset-r) var(--p-inset-b) var(--p-inset-l);
  background: linear-gradient(180deg, var(--p-bar-1), var(--p-bar-2)); border-top: var(--p-hair) solid var(--p-holo-hair);
}
html.is-phone .p-tab {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--p-s1);
  min-width: 0; height: var(--p-tab-h); padding: 0; background: none; border: 0; cursor: pointer; color: var(--p-text-3);
  font: 700 calc(12px * var(--p-ts)) / calc(16px * var(--p-ts)) var(--p-f-ui); letter-spacing: 0.04em; text-transform: uppercase;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-tab .p-tab-lbl { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html.is-phone .p-tab[aria-selected="true"] { color: var(--p-ice); }
html.is-phone .p-tab[aria-selected="true"] .p-ic { fill: currentColor; fill-opacity: 0.16; }
html.is-phone .p-tab[aria-selected="true"]::before {
  content: ""; position: absolute; top: var(--p-hair-up); left: var(--p-ind-in); right: var(--p-ind-in); height: 3px; background: var(--p-holo); box-shadow: 0 0 10px var(--p-holo);
}
html.is-phone .p-tab:is(:active, .is-pressed) { color: var(--p-holo); }
html.is-phone .p-tab .p-badge { position: absolute; top: var(--p-s1); left: var(--p-badge-x); }
html.is-phone .p-tab .p-dot { position: absolute; top: var(--p-s2); left: var(--p-dot-x); }
/* the raised PLAY plate: rises 12 px above the bar (top -13: the bar's 1 px hairline). The PLAY tab's own box rises with
   it (T11: the centre item is taller than its neighbours; its hit area is the plate it shows), so the plate sits at the
   top of its tab instead of hanging out of it */
html.is-phone .p-tab--play { overflow: visible; }
html.is-phone #p-tabbar .p-tab--play { height: calc(var(--p-tab-h) - var(--p-pp-rise)); margin-top: var(--p-pp-rise); }
html.is-phone .p-tab--play[aria-selected="true"]::before { display: none; }
html.is-phone .p-tab--play .p-pp {
  position: absolute; top: 0; left: 50%; margin-left: var(--p-pp-half); width: 72px; height: 72px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0; color: var(--p-ice);
  --p-c: 14px; --p-bw: 2px; --p-bc: var(--p-holo); --p-fill: var(--p-pp-fill);
}
html.is-phone .p-tab--play .p-pp .p-pp-lbl { font: 700 calc(12px * var(--p-ts)) / calc(14px * var(--p-ts)) var(--p-f-disp); letter-spacing: 0.12em; }
html.is-phone .p-tab--play[aria-selected="true"] .p-pp { --p-fill: var(--p-pp-fill-on); filter: drop-shadow(0 0 10px var(--p-holo-glow)); }
html.is-phone .p-tab--play[aria-selected="true"] .p-ic { fill: none; }
html.is-phone .p-tab--play:is(:active, .is-pressed) .p-pp { transform: scale(0.97); }
html.is-phone .p-tab:focus-visible, html.is-phone .p-tab.gp-focus { outline: 3px solid var(--p-ice); outline-offset: -4px; box-shadow: inset 0 0 0 8px var(--p-holo-ring); }
/* THE RAIL (phone held sideways, menus): the four non-PLAY tabs as 62 px labelled icons down the left edge of the safe
   zone; PLAY is home's loop-slot button there, and a non-PLAY root's top bar carries Back to PLAY (shell.js) */
html.is-phone.p-shell.p-wide #menu.halo { grid-template-columns: calc(var(--p-rail-w) + max(var(--p-s2), var(--p-inset-l))) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
html.is-phone.p-shell.p-wide #p-topbar { grid-column: 1 / -1; height: calc(var(--p-wide-top) + var(--p-inset-t)); padding-left: max(var(--p-s4), var(--p-inset-l)); padding-right: max(var(--p-s4), var(--p-inset-r)); }
html.is-phone.p-shell.p-wide #p-topbar :is(.p-tb-root, .p-tb-page) { height: var(--p-wide-top); }
html.is-phone.p-shell.p-wide #menu.halo .menu-content { grid-row: 2; grid-column: 2; padding-left: var(--p-s4); padding-bottom: calc(var(--p-s6) + var(--p-inset-b)); }
html.is-phone.p-shell.p-wide #p-tabbar { display: none; }
html.is-phone #p-rail { display: none; }
html.is-phone.p-shell.p-wide #p-rail {
  grid-row: 2; grid-column: 1; position: relative; z-index: 3; display: flex; flex-direction: column; justify-content: center; gap: var(--p-s2);
  padding-top: var(--p-s2); padding-bottom: calc(var(--p-s2) + var(--p-inset-b)); padding-left: max(var(--p-s2), var(--p-inset-l)); padding-right: 0;
  background: linear-gradient(90deg, var(--p-bar-2), var(--p-bar-1)); border-right: var(--p-hair) solid var(--p-holo-hair);
}
html.is-phone.p-shell.p-wide.p-pushed #p-rail { display: none; }
html.is-phone.p-shell.p-wide.p-pushed #menu.halo .menu-content { grid-column: 1 / -1; padding-left: max(var(--p-s4), var(--p-inset-l)); padding-right: max(var(--p-s4), var(--p-inset-r)); }
/* a pushed page's action bar held sideways (lane 7, 2026-10-16): the grid has two columns here (rail, page), so the bar
   spans both under the page; (rail T7 / T24 / T9: it was squeezed into the 80 px rail column, its label clipped) */
html.is-phone.p-shell.p-wide #p-actionbar { grid-row: 3; grid-column: 1 / -1; }
html.is-phone #p-rail .p-tab { height: 62px; width: 100%; }
html.is-phone #p-rail .p-tab[aria-selected="true"]::before { top: var(--p-ind-in-rail); bottom: var(--p-ind-in-rail); left: 0; right: auto; width: 3px; height: auto; }

/* ------------------------------------------------------------------------------------------------ 6. BUTTONS
   5.1 hierarchy and 5.2 states. Every one is a <button> (or role=button) with the chamfered frame; the pressed state
   paints on the next frame after pointerdown (no transition in) and releases over 90 ms; the press ring sits 4 px
   outside the bounds (the frame's border layer grows) so it shows around the thumb. Pressed = :active OR .is-pressed:
   on touch :active comes late (Chromium sets it at the tap gesture's show-press; iOS Safari only for an element with a
   touchstart listener), so shell.js marks the pressed control on pointerdown and clears it on pointerup / cancel. */
html.is-phone .p-btn {
  position: relative; isolation: isolate; display: inline-flex; align-items: center; justify-content: center; gap: var(--p-s2);
  min-height: 52px; padding: 0 var(--p-s4); margin: 0; border: 0; background: none; cursor: pointer; color: var(--p-ice);
  font: var(--p-t-button); letter-spacing: var(--p-tr-button); text-transform: uppercase; white-space: nowrap;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation; user-select: none; -webkit-user-select: none;
  --p-c: var(--p-cut-m); --p-bw: 1.5px; --p-bc: var(--p-line-ui); --p-fill: var(--p-bg-2); --p-ring: var(--p-holo-ring);
  transition: transform var(--p-dur-press) var(--p-ease-std);
}
html.is-phone .p-btn::before, html.is-phone .p-btn::after {
  content: ""; position: absolute; pointer-events: none;
  clip-path: polygon(var(--p-c) 0, 100% 0, 100% calc(100% - var(--p-c)), calc(100% - var(--p-c)) 100%, 0 100%, 0 var(--p-c));
}
html.is-phone .p-btn::before { inset: 0; background: var(--p-bc); z-index: -2; }
html.is-phone .p-btn::after { inset: var(--p-bw); background: var(--p-fill); z-index: -1; }
html.is-phone .p-btn:is(:active, .is-pressed):not(:disabled):not([aria-disabled="true"]) { transform: scale(0.97); transition: none; }
html.is-phone .p-btn:is(:active, .is-pressed):not(:disabled):not([aria-disabled="true"])::before { inset: var(--p-ring-out); background: var(--p-ring); }
html.is-phone .p-btn:is(:active, .is-pressed):not(:disabled):not([aria-disabled="true"])::after { inset: var(--p-bw); filter: brightness(1.08); }
html.is-phone .p-btn:focus-visible, html.is-phone .p-btn.gp-focus { outline: 3px solid var(--p-ice); outline-offset: 2px; }
html.is-phone .p-btn:focus-visible::before, html.is-phone .p-btn.gp-focus::before { inset: var(--p-focus-out); background: var(--p-holo); }
html.is-phone .p-btn:disabled, html.is-phone .p-btn[aria-disabled="true"] { cursor: default; }
html.is-phone .p-btn:disabled::before, html.is-phone .p-btn[aria-disabled="true"]::before,
html.is-phone .p-btn:disabled::after, html.is-phone .p-btn[aria-disabled="true"]::after { opacity: 0.45; }
html.is-phone .p-btn--block { display: flex; width: 100%; }
html.is-phone .p-btn--half { flex: 1 1 0; min-width: 0; }
/* PRIMARY: the amber fill, amber-ink label, the inner top highlight; full column x 64 (56 minimum); one per screen */
html.is-phone .p-btn--primary {
  min-height: 64px; color: var(--p-amber-ink); font: var(--p-t-title); letter-spacing: 0.16em;
  --p-bw: 0px; --p-bc: transparent; --p-ring: var(--p-amber-ring);
}
html.is-phone .p-btn--primary::after {
  background: linear-gradient(180deg, var(--p-amber-1), var(--p-amber-2));
  box-shadow: inset 0 1px 0 var(--p-amber-hi), inset 0 -3px 0 var(--p-amber-lo);
}
html.is-phone .p-btn--primary .p-btn-sub { display: block; font: 600 calc(13px * var(--p-ts)) / calc(16px * var(--p-ts)) var(--p-f-ui); letter-spacing: 0.04em; text-transform: none; opacity: 0.86; margin-top: 0; }
html.is-phone .p-btn--primary.p-btn--stack { flex-direction: column; gap: 0; }
html.is-phone .p-btn--primary:disabled, html.is-phone .p-btn--primary[aria-disabled="true"] { color: var(--p-ice); }   /* a disabled primary's label stays >= 4.5:1 on its 45% fill (5.2) */
/* the busy state keeps the footprint: a verb and a live value in place of the label (5.2) */
html.is-phone .p-btn.is-busy { cursor: progress; }
/* busy is not disabled: a busy primary (SEARCHING 0:23) stays amber with its ink (7.22), only its press is held */
html.is-phone .p-btn--primary.is-busy:is(:disabled, [aria-disabled="true"]) { color: var(--p-amber-ink); }
html.is-phone .p-btn--primary.is-busy:is(:disabled, [aria-disabled="true"])::before, html.is-phone .p-btn--primary.is-busy:is(:disabled, [aria-disabled="true"])::after { opacity: 1; }
/* SECONDARY: bg-2, a 1.5 px line-ui border, ice label */
html.is-phone .p-btn--secondary { --p-fill: var(--p-bg-2); --p-bc: var(--p-line-ui); }
html.is-phone .p-btn--secondary.p-btn--sm { min-height: 44px; font-size: calc(15px * var(--p-ts)); }
/* TERTIARY: text only in holo, 44 px tall */
html.is-phone .p-btn--tertiary { min-height: 44px; padding: 0 var(--p-s2); color: var(--p-holo); font: 600 calc(16px * var(--p-ts)) / 1 var(--p-f-ui); letter-spacing: 0.04em; text-transform: none; --p-bc: transparent; --p-fill: transparent; }
html.is-phone .p-btn--tertiary:is(:active, .is-pressed):not(:disabled)::before { inset: 0; background: transparent; }
html.is-phone .p-btn--tertiary:is(:active, .is-pressed):not(:disabled)::after { background: var(--p-raise); }
/* DESTRUCTIVE: a danger border and label; never amber, never the default focus */
html.is-phone .p-btn--destructive { color: var(--p-danger); --p-bc: var(--p-danger); --p-fill: var(--p-bg-2); --p-ring: var(--p-danger-ring); }
/* ICON: 44 x 44, a 24 px glyph, an optional 12 px label under it */
html.is-phone .p-btn--icon { min-height: 44px; min-width: 44px; padding: 0; flex-direction: column; gap: 0; --p-bc: transparent; --p-fill: transparent; font: var(--p-t-micro); letter-spacing: var(--p-tr-micro); }
/* PRICE: the secondary look with the currency glyph; the primary look only on a featured offer or in the confirm */
html.is-phone .p-btn--price { font-family: var(--p-f-num); font-variant-numeric: tabular-nums; }
html.is-phone .p-btn--price[data-p-afford="0"] { color: var(--p-loss); --p-bc: var(--p-loss); }
html.is-phone .p-reason { display: block; margin-top: var(--p-s1); font: var(--p-t-caption); color: var(--p-text-2); text-align: center; }
html.is-phone .p-reason.p-c-loss { color: var(--p-loss); }
/* the PLAY shimmer (7.4): one 900 ms sweep, run by JS once every 6 s while home shows (shell.js shimmer()), never a
   standing infinite animation (an idle compositor animation keeps frames coming) */
html.is-phone .p-btn .p-shimmer { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0;
  clip-path: polygon(var(--p-c) 0, 100% 0, 100% calc(100% - var(--p-c)), calc(100% - var(--p-c)) 100%, 0 100%, 0 var(--p-c)); }
html.is-phone .p-btn .p-shimmer::before { content: ""; position: absolute; top: 0; bottom: 0; left: var(--p-sheen-x); width: 70px; opacity: 0;
  background: linear-gradient(100deg, transparent, var(--p-sheen), transparent); transform: translateX(0) skewX(-18deg); }
html.is-phone .p-btn .p-shimmer.is-run::before { animation: p-shimmer var(--p-dur-shimmer) var(--p-ease-std) 1; }
@keyframes p-shimmer { 0% { opacity: 1; transform: translateX(0) skewX(-18deg); } 100% { opacity: 1; transform: translateX(520px) skewX(-18deg); } }
html.is-phone.p-rm .p-btn .p-shimmer::before { animation: none; }   /* reduced motion: no sheen (T23) */
@media (prefers-reduced-motion: reduce) { html.is-phone:not(.p-rm-off) .p-btn .p-shimmer::before { animation: none; } }

/* ------------------------------------------------------------------------------------------------ 7. CONTROLS */
/* the MODE CHIP (7.5): full column x 56, glued 12 px above PLAY */
html.is-phone .p-modechip {
  display: flex; align-items: center; gap: var(--p-s3); width: 100%; min-height: 56px; padding: 0 var(--p-s3) 0 var(--p-s2); margin: 0;
  border: 0; background: none; color: var(--p-ice); cursor: pointer; text-align: left;
  --p-c: var(--p-cut-m); --p-bw: 1.5px; --p-bc: var(--p-line-ui); --p-fill: var(--p-chip-fill);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-modechip .p-mc-art { width: 40px; height: 40px; flex: none; --p-c: 6px; --p-bw: 1px; --p-bc: var(--p-holo); --p-fill: var(--p-emb-fill); display: flex; align-items: center; justify-content: center; }
html.is-phone .p-modechip .p-mc-art img { width: 34px; height: 34px; object-fit: contain; }
html.is-phone .p-modechip .p-mc-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
html.is-phone .p-modechip .p-mc-name { font: 700 calc(17px * var(--p-ts)) / calc(20px * var(--p-ts)) var(--p-f-ui); letter-spacing: 0.06em; text-transform: uppercase; }
html.is-phone .p-modechip .p-mc-sub { font: var(--p-t-caption); color: var(--p-text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
html.is-phone .p-modechip .p-mc-go { flex: none; display: flex; align-items: center; gap: 0; color: var(--p-holo); font: 600 calc(14px * var(--p-ts)) / 1 var(--p-f-ui); letter-spacing: 0.06em; }
html.is-phone .p-modechip:is(:active, .is-pressed)::after { background: var(--p-raise); }
/* the SEGMENTED CONTROL (7.6): 44 tall, at most 4 segments, full column; scrolls with the pick centred when the labels
   do not fit (never a 2 x 2 grid of tabs) */
html.is-phone .p-seg {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(max-content, 1fr); width: 100%; min-height: 44px;
  overflow-x: auto; scrollbar-width: none; scroll-snap-type: x proximity; overscroll-behavior-x: contain;
  --p-c: 8px; --p-bw: 1px; --p-bc: var(--p-line-ui); --p-fill: var(--p-bg-2);
}
html.is-phone .p-seg::-webkit-scrollbar { display: none; }
html.is-phone .p-seg-btn {
  position: relative; display: flex; align-items: center; justify-content: center; gap: var(--p-s2); min-height: 44px; padding: 0 var(--p-s3);
  background: none; border: 0; color: var(--p-text-2); cursor: pointer; scroll-snap-align: center;
  font: 700 calc(15px * var(--p-ts)) / 1 var(--p-f-ui); letter-spacing: var(--p-tr-button); text-transform: uppercase; white-space: nowrap;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-seg-btn[aria-selected="true"] { color: var(--p-ice); background: var(--p-sel); }
html.is-phone .p-seg-btn[aria-selected="true"]::after { content: ""; position: absolute; left: var(--p-s3); right: var(--p-s3); bottom: var(--p-s1); height: 2px; background: var(--p-holo); box-shadow: 0 0 8px var(--p-holo); }
html.is-phone .p-seg-btn:disabled { color: var(--p-text-3); cursor: default; }
/* FILTER CHIPS (7.7): 36 visual / 44 hit; a row that scrolls, edge fades; the pick has sel + holo + a check */
html.is-phone .p-chips { display: flex; gap: var(--p-s2); overflow-x: auto; scrollbar-width: none;
  padding-top: var(--p-s1); padding-bottom: var(--p-s1); padding-left: var(--p-gutter-l); padding-right: var(--p-gutter-r);
  margin-left: calc(-1*var(--p-gutter-l)); margin-right: calc(-1*var(--p-gutter-r));
  -webkit-mask-image: linear-gradient(90deg, transparent 0, var(--p-mask-on) 16px, var(--p-mask-on) calc(100% - 24px), transparent 100%); mask-image: linear-gradient(90deg, transparent 0, var(--p-mask-on) 16px, var(--p-mask-on) calc(100% - 24px), transparent 100%); }
html.is-phone .p-chips::-webkit-scrollbar { display: none; }
html.is-phone .p-chip {
  position: relative; isolation: isolate; flex: none; display: inline-flex; align-items: center; gap: var(--p-s2); height: 44px; padding: 0 var(--p-s4);
  background: none; border: 0; color: var(--p-text-2); cursor: pointer;
  font: var(--p-t-label); letter-spacing: var(--p-tr-label); text-transform: uppercase;
  --p-c: var(--p-cut-s); --p-bw: 1px; --p-bc: var(--p-line-ui); --p-fill: var(--p-bg-2);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-chip::before, html.is-phone .p-chip::after { content: ""; position: absolute; left: 0; right: 0; top: var(--p-s1); bottom: var(--p-s1); pointer-events: none;
  clip-path: polygon(var(--p-c) 0, 100% 0, 100% calc(100% - var(--p-c)), calc(100% - var(--p-c)) 100%, 0 100%, 0 var(--p-c)); }
html.is-phone .p-chip::before { background: var(--p-bc); z-index: -2; }
html.is-phone .p-chip::after { inset: var(--p-chip-inset); background: var(--p-fill); z-index: -1; }
html.is-phone .p-chip[aria-selected="true"], html.is-phone .p-chip[aria-pressed="true"] { color: var(--p-ice); --p-fill: var(--p-sel); --p-bc: var(--p-holo); }
/* LIST ROW (7.12): 56 tall (64 with a sub-line), label start, value or control end, a chevron for a push */
html.is-phone .p-list { display: flex; flex-direction: column; background: var(--p-bg-2); border: 1px solid var(--p-line); }
html.is-phone .p-row {
  display: flex; align-items: center; gap: var(--p-s3); width: 100%; min-height: 56px; padding: 0 var(--p-s4);
  background: none; border: 0; border-top: var(--p-hair) solid var(--p-line-soft); color: var(--p-ice); text-align: left; cursor: pointer;
  font: 600 calc(16px * var(--p-ts)) / calc(20px * var(--p-ts)) var(--p-f-ui);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
html.is-phone .p-row:first-child { border-top: 0; }
html.is-phone .p-row:is(:active, .is-pressed) { background: var(--p-raise); }
html.is-phone .p-row .p-row-lbl { flex: 1 1 auto; min-width: 0; }
html.is-phone .p-row .p-row-lbl small { display: block; font: var(--p-t-caption); color: var(--p-text-3); margin-top: 0; }
html.is-phone .p-row .p-row-val { flex: none; font: 600 calc(15px * var(--p-ts)) / 1 var(--p-f-num); color: var(--p-text-2); font-variant-numeric: tabular-nums; }
html.is-phone .p-row .p-row-chev { flex: none; color: var(--p-text-3); }
html.is-phone .p-row--sub { min-height: 64px; }
html.is-phone .p-row--danger { color: var(--p-danger); }
html.is-phone .p-section-label { display: flex; align-items: center; justify-content: space-between; margin: var(--p-s6) 0 var(--p-s2);
  font: var(--p-t-label); letter-spacing: var(--p-tr-label); text-transform: uppercase; color: var(--p-text-2); }
html.is-phone .p-section-label:first-child { margin-top: 0; }
/* TOGGLE (7.13): the native switch (Safari 17.4 draws <input type=checkbox switch> as a switch with a system haptic) */
html.is-phone input[type="checkbox"].p-toggle {
  -webkit-appearance: none; appearance: none; position: relative; flex: none; width: 51px; height: 44px; margin: 0; cursor: pointer;
  background: transparent; border-radius: 0;
}
/* the element is the 51 x 44 tap target (T7 measures the box itself); ::before draws the 51 x 31 track centred in it,
   ::after the 27 px knob; the focus ring follows the track */
html.is-phone input[type="checkbox"].p-toggle::before { content: ""; position: absolute; left: 0; right: 0; top: var(--p-tgl-track-y); bottom: var(--p-tgl-track-y);
  border-radius: var(--p-r-pill); background: var(--p-line); }
html.is-phone input[type="checkbox"].p-toggle::after { content: ""; position: absolute; top: var(--p-tgl-knob-y); left: var(--p-tgl-pad); width: 27px; height: 27px; border-radius: 50%; background: var(--p-ice);
  transition: transform var(--p-dur-micro) var(--p-ease-std); }
html.is-phone input[type="checkbox"].p-toggle:checked::before { background: var(--p-holo); }
html.is-phone input[type="checkbox"].p-toggle:checked::after { transform: translateX(20px); }
html.is-phone input[type="checkbox"].p-toggle:focus-visible { outline: none; }
html.is-phone input[type="checkbox"].p-toggle:focus-visible::before { outline: 3px solid var(--p-ice); outline-offset: 2px; }
/* SLIDER (7.13): a 44 px touch track, 4 px drawn, 28 px thumb */
html.is-phone input[type="range"].p-slider { -webkit-appearance: none; appearance: none; width: 100%; height: 44px; margin: 0; background: transparent; cursor: pointer; touch-action: pan-y; }
html.is-phone input[type="range"].p-slider::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: linear-gradient(90deg, var(--p-holo) var(--p-fillpct, 50%), var(--p-line) var(--p-fillpct, 50%)); }
html.is-phone input[type="range"].p-slider::-moz-range-track { height: 4px; border-radius: 2px; background: var(--p-line); }
html.is-phone input[type="range"].p-slider::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--p-holo); }
html.is-phone input[type="range"].p-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 28px; height: 28px; margin-top: var(--p-thumb-y); border-radius: 50%; background: var(--p-ice); box-shadow: 0 0 0 3px var(--p-holo-glow); }
html.is-phone input[type="range"].p-slider::-moz-range-thumb { width: 28px; height: 28px; border: 0; border-radius: 50%; background: var(--p-ice); box-shadow: 0 0 0 3px var(--p-holo-glow); }
/* TEXT FIELD (7.14): 52 tall, 16 px text (iOS never zooms into it), focus border holo 2 px */
html.is-phone .p-field { display: flex; flex-direction: column; gap: var(--p-s2); }
html.is-phone .p-field > label, html.is-phone .p-field .p-field-lbl { font: var(--p-t-label); letter-spacing: var(--p-tr-label); text-transform: uppercase; color: var(--p-text-2); }
html.is-phone .p-input {
  width: 100%; height: 52px; padding: 0 var(--p-s3); border-radius: 0; outline: none;
  background: var(--p-field-bg); border: 1.5px solid var(--p-line-ui); color: var(--p-ice);
  font: var(--p-t-body); font-size: max(16px, calc(16px * var(--p-ts)));
}
html.is-phone .p-input:focus { border-color: var(--p-holo); box-shadow: inset 0 0 0 0.5px var(--p-holo); }
html.is-phone .p-input::placeholder { color: var(--p-text-3); }
html.is-phone .p-field-err { font: var(--p-t-caption); color: var(--p-danger); }
/* PROGRESS (7.20): 4 px on tiles, 8 px for XP and loading; determinate (transform scaleX, never width) */
html.is-phone .p-progress { position: relative; height: 4px; background: var(--p-line); overflow: hidden; }
html.is-phone .p-progress--8 { height: 8px; }
html.is-phone .p-progress > b { position: absolute; inset: 0; background: var(--p-holo); box-shadow: 0 0 6px var(--p-holo); transform-origin: left center; transform: scaleX(var(--p-pct, 0)); }
/* BADGE (7.18): a dot 8 px for presence; a count pill 18 tall, white on --p-badge, "99+" cap; never on PLAY */
html.is-phone .p-badge { min-width: 18px; height: 18px; padding: 0 var(--p-s1); border-radius: 9px; background: var(--p-badge); color: var(--p-badge-ink);
  font: 700 12px/18px var(--p-f-num); text-align: center; font-variant-numeric: tabular-nums; box-shadow: 0 0 0 2px var(--p-bg-1); pointer-events: none; }
html.is-phone .p-dot { width: 8px; height: 8px; border-radius: 4px; background: var(--p-badge); box-shadow: 0 0 0 2px var(--p-bg-1); pointer-events: none; }
html.is-phone .p-iconbtn .p-badge { position: absolute; top: var(--p-s1); left: var(--p-ibadge-x); }
html.is-phone :is(.p-badge, .p-dot)[hidden] { display: none; }
/* CAROUSEL + SWATCHES (7.21) */
html.is-phone .p-carousel { display: flex; gap: var(--p-s2); overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding-inline: var(--p-gutter-l); overscroll-behavior-x: contain;
  scrollbar-width: none; padding-top: var(--p-s1); padding-bottom: var(--p-s2); padding-left: var(--p-gutter-l); padding-right: var(--p-gutter-r);
  margin-left: calc(-1*var(--p-gutter-l)); margin-right: calc(-1*var(--p-gutter-r));
  -webkit-mask-image: linear-gradient(90deg, transparent 0, var(--p-mask-on) 12px, var(--p-mask-on) calc(100% - 28px), transparent 100%); mask-image: linear-gradient(90deg, transparent 0, var(--p-mask-on) 12px, var(--p-mask-on) calc(100% - 28px), transparent 100%); }
html.is-phone .p-carousel::-webkit-scrollbar { display: none; }
html.is-phone .p-carousel > * { scroll-snap-align: center; flex: none; }
html.is-phone .p-swatches { display: grid; grid-template-columns: repeat(7, 44px); justify-content: space-between; gap: var(--p-s2); }
html.is-phone .p-swatch { width: 44px; height: 44px; border: 0; padding: 0; cursor: pointer; border-radius: 0; box-shadow: inset 0 0 0 1px var(--p-edge-hi); }
html.is-phone .p-swatch[aria-checked="true"], html.is-phone .p-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--p-bg-1), 0 0 0 4px var(--p-ice); }

/* ------------------------------------------------------------------------------------------------ 8. CARDS, TILES */
/* home tiles (7.10): half column x 64, raise, chamfer s */
html.is-phone .p-tile { display: flex; flex-direction: column; justify-content: center; gap: 0; min-height: 64px; padding: var(--p-s2) var(--p-s3); margin: 0;
  border: 0; background: none; color: var(--p-ice); text-align: left; cursor: pointer;
  --p-c: 8px; --p-bw: 1px; --p-bc: var(--p-line-ui); --p-fill: var(--p-raise); -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
html.is-phone .p-tile:is(:active, .is-pressed)::after { background: var(--p-sel); }
html.is-phone .p-tile .p-tile-head { display: flex; align-items: center; gap: var(--p-s2); color: var(--p-holo); }
html.is-phone .p-tile .p-tile-ttl { font: 700 calc(14px * var(--p-ts)) / calc(18px * var(--p-ts)) var(--p-f-ui); letter-spacing: 0.06em; text-transform: uppercase; color: var(--p-ice); }
html.is-phone .p-tile .p-tile-line { font: 500 calc(13px * var(--p-ts)) / calc(16px * var(--p-ts)) var(--p-f-ui); color: var(--p-text-2); }
html.is-phone .p-grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--p-s3); }
html.is-phone .p-grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--p-s3); }
html.is-phone :is(.p-grid2, .p-grid3) > .p-span { grid-column: 1 / -1; }   /* C4: a remainder item spans its row */
/* cards (7.11): raise, line border, chamfer m; selected = sel + 2 px holo + a check */
html.is-phone .p-card { position: relative; isolation: isolate; display: flex; gap: var(--p-s3); padding: var(--p-s4); color: var(--p-ice); text-align: left;
  border: 0; background: none; --p-c: var(--p-cut-m); --p-bw: 1px; --p-bc: var(--p-line-ui); --p-fill: var(--p-raise); }
html.is-phone button.p-card { cursor: pointer; width: 100%; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
html.is-phone .p-card[aria-selected="true"], html.is-phone .p-card.is-sel { --p-fill: var(--p-sel); --p-bc: var(--p-holo); --p-bw: 2px; }
html.is-phone .p-card .p-check { position: absolute; top: var(--p-s2); right: var(--p-s2); width: 24px; height: 24px; border-radius: 12px; background: var(--p-holo); color: var(--p-check-ink); display: flex; align-items: center; justify-content: center; }
html.is-phone .p-card--hero { --p-fill: var(--p-raise); }
html.is-phone .p-card--hero::after { box-shadow: inset 0 1px 0 var(--p-holo-hi); }
html.is-phone .p-card .p-accent { position: absolute; left: var(--p-s3); right: 0; top: 0; height: 2px; background: var(--p-holo); box-shadow: 0 0 10px var(--p-accent-glow); z-index: 1; }
/* rarity frames: the colour AND the word (shared/rarity.js), never colour alone */
html.is-phone .p-card[data-rarity="common"] { --p-bc: var(--p-r-common); } html.is-phone .p-card[data-rarity="uncommon"] { --p-bc: var(--p-r-uncommon); }
html.is-phone .p-card[data-rarity="rare"] { --p-bc: var(--p-r-rare); } html.is-phone .p-card[data-rarity="epic"] { --p-bc: var(--p-r-epic); }
html.is-phone .p-card[data-rarity="mythic"] { --p-bc: var(--p-r-mythic); } html.is-phone .p-card[data-rarity="unique"] { --p-bc: var(--p-r-unique); }
html.is-phone .p-card[data-rarity="exclusive"] { --p-bc: var(--p-r-exclusive); }
/* the plate a word sits on over live 3D (2.1: 88% navy, ice text) */
html.is-phone .p-plate { background: var(--p-plate); color: var(--p-ice); }
/* EMPTY / ERROR (7.24): an icon, one line, one fix */
html.is-phone .p-empty { display: flex; flex-direction: column; align-items: center; gap: var(--p-s3); padding: var(--p-s8) var(--p-s4); text-align: center; color: var(--p-text-2); font: var(--p-t-body); }
html.is-phone .p-empty .p-ic { width: 40px; height: 40px; color: var(--p-holo); }
/* TURN-YOUR-PHONE CUE (7.23): only while the phone is still upright under a CSS-turned load */
html.is-phone .p-turncue { display: inline-flex; align-items: center; gap: var(--p-s2); font: var(--p-t-label); letter-spacing: var(--p-tr-label); text-transform: uppercase; color: var(--p-ice); }
html.is-phone .p-turncue .p-ic { width: 40px; height: 40px; animation: p-turn var(--p-dur-turn) var(--p-ease-std) 3; }
@keyframes p-turn { 0%, 20% { transform: rotate(0deg); } 60%, 100% { transform: rotate(90deg); } }
@media (prefers-reduced-motion: reduce) { html.is-phone .p-turncue .p-ic { animation: none; transform: rotate(90deg); } }

/* ------------------------------------------------------------------------------------------------ 9. SHEET, TOAST, SCRIM
   7.15 bottom sheet: grabber, centred title, Close (or Cancel / Done), detents medium (~55%) and large (59 px under the
   top inset); swipe down or a scrim tap closes it; its body is the only scroller while it is open; one sheet at a time
   (a sheet swaps its own content, Back inside it). 7.17 toast: top centre under the top bar, column wide, 2 lines,
   3.5 s, one at a time. Both live at <body> level above every board (z 185 / 186; the wallet modal keeps 200). */
html.is-phone #p-scrim { position: fixed; inset: 0; z-index: 185; background: var(--p-scrim); opacity: 0; pointer-events: none;
  transition: opacity var(--p-dur-sheet-out) var(--p-ease-in); }
html.is-phone #p-scrim.is-open { opacity: 1; pointer-events: auto; transition: opacity var(--p-dur-sheet-in) var(--p-ease-out); }
html.is-phone .p-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 186; display: flex; flex-direction: column;
  max-height: calc(var(--p-app-h) - var(--p-inset-t) - 59px); height: auto;
  padding: 0 var(--p-inset-r) 0 var(--p-inset-l); color: var(--p-ice); font-family: var(--p-f-ui);
  --p-c: var(--p-cut-l); --p-bw: 1px; --p-bc: var(--p-sheet-line); --p-fill: var(--p-bg-2);
  /* e3 without a filter: a filter on a box this size, moving, is the cost 10.2 bans; the scrim and the holo hairline
     separate the sheet, and the shade below is a static gradient above its top edge */
  transform: translateY(105%); visibility: hidden;
  transition: transform var(--p-dur-sheet-out) var(--p-ease-in), visibility 0s linear var(--p-dur-sheet-out);
}
html.is-phone .p-sheet::before, html.is-phone .p-sheet::after { clip-path: polygon(var(--p-c) 0, calc(100% - var(--p-c)) 0, 100% var(--p-c), 100% 100%, 0 100%, 0 var(--p-c)); }
html.is-phone .p-sheet.is-open { transform: translateY(var(--p-drag, 0px)); visibility: visible; transition: transform var(--p-dur-sheet-in) var(--p-ease-out); }
html.is-phone .p-sheet.is-dragging { transition: none; }
html.is-phone .p-sheet--medium { height: min(55dvh, calc(var(--p-app-h) - var(--p-inset-t) - 59px)); }
html.is-phone .p-sheet--large { height: calc(var(--p-app-h) - var(--p-inset-t) - 59px); }
html.is-phone .p-sheet-grab::after { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 40px; pointer-events: none;
  background: linear-gradient(0deg, var(--p-sheet-shade), transparent); }
html.is-phone .p-sheet-grab { position: relative; flex: none; height: 24px; display: flex; align-items: center; justify-content: center; touch-action: none; cursor: grab; }
html.is-phone .p-sheet-grab::before { content: ""; width: 36px; height: 5px; border-radius: 3px; background: var(--p-grab); }
html.is-phone .p-sheet-head { flex: none; position: relative; display: flex; align-items: center; justify-content: center; min-height: 44px; padding: 0 var(--p-sheet-head-x); touch-action: none; }
html.is-phone .p-sheet-title { margin: 0; font: var(--p-t-headline); letter-spacing: var(--p-tr-headline); text-transform: uppercase; text-align: center; color: var(--p-ice); }
html.is-phone .p-sheet-head .p-sheet-close { position: absolute; right: var(--p-s2); top: 0; }
html.is-phone .p-sheet-head .p-sheet-back { position: absolute; left: var(--p-s2); top: 0; color: var(--p-holo); }
html.is-phone .p-sheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--p-s3) var(--p-s4) calc(var(--p-s6) + var(--p-inset-b)); }
html.is-phone .p-sheet-foot { flex: none; display: flex; flex-direction: column; gap: var(--p-s3); padding: var(--p-s3) var(--p-s4) max(var(--p-s4), var(--p-inset-b)); border-top: var(--p-hair) solid var(--p-line-soft); }
html.is-phone .p-sheet-body + .p-sheet-foot { margin-top: 0; }
html.is-phone .p-sheet .p-sheet-body:has(+ .p-sheet-foot) { padding-bottom: var(--p-s4); }
html.is-phone.p-sheet-open .p-btn .p-shimmer::before { animation: none; }
/* the toast */
html.is-phone #p-toast {
  position: fixed; left: 50%; top: calc(var(--p-inset-t) + var(--p-top-h) + var(--p-s2)); z-index: 950;
  width: min(calc(100% - var(--p-gutter-l) - var(--p-gutter-r)), var(--p-col-max)); padding: var(--p-s3) var(--p-s4);
  display: flex; align-items: center; gap: var(--p-s3); color: var(--p-ice);
  font: 500 calc(15px * var(--p-ts)) / calc(20px * var(--p-ts)) var(--p-f-ui);
  --p-c: var(--p-cut-m); --p-bw: 1px; --p-bc: var(--p-line-ui); --p-fill: var(--p-raise);
  filter: drop-shadow(var(--p-e4-shadow));
  opacity: 0; visibility: hidden; transform: translate(-50%, -8px); pointer-events: none;
  transition: opacity var(--p-dur-std) var(--p-ease-in), transform var(--p-dur-std) var(--p-ease-in), visibility 0s linear var(--p-dur-std);
}
html.is-phone #p-toast.is-open { opacity: 1; visibility: visible; transform: translate(-50%, 0); pointer-events: auto; transition: opacity var(--p-dur-std) var(--p-ease-out), transform var(--p-dur-std) var(--p-ease-out); }
html.is-phone #p-toast .p-toast-txt { flex: 1 1 auto; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
html.is-phone #p-toast .p-ic { color: var(--p-holo); }
html.is-phone #p-toast.p-toast--bad .p-ic { color: var(--p-loss); }
html.is-phone #p-toast.p-toast--good .p-ic { color: var(--p-gain); }
html.is-phone.p-shell.p-wide #p-toast { top: calc(var(--p-inset-t) + var(--p-wide-top) + var(--p-s2)); }
/* the profile sheet (S22, shell.js profileSheet) */
html.is-phone .p-profile { display: flex; flex-direction: column; gap: var(--p-s4); }
html.is-phone .p-profile .p-plate-img { width: 100%; aspect-ratio: 4 / 1; object-fit: cover; display: block; background: var(--p-raise); }
html.is-phone.p-wide .p-profile .p-plate-img { width: 60%; align-self: center; }   /* sideways the plate leaves the rows on screen */
html.is-phone .p-profile .p-prof-row { display: flex; align-items: center; justify-content: space-between; gap: var(--p-s3); }
html.is-phone .p-profile .p-prof-k { font: var(--p-t-label); letter-spacing: var(--p-tr-label); text-transform: uppercase; color: var(--p-text-2); }
html.is-phone .p-profile .p-prof-v { font: 600 calc(16px * var(--p-ts)) / 1 var(--p-f-num); color: var(--p-ice); font-variant-numeric: tabular-nums; }
html.is-phone .p-profile .p-prof-actions { display: flex; flex-direction: column; gap: var(--p-s3); }

/* ------------------------------------------------------------------------------------------------ 10. MOTION
   2.6: a tab root's content fades in and rises 8 px over 240 ms; a pushed page slides 24 px and fades over 280 ms.
   The panes the shell hosts are shown and hidden by main.js (.hidden), so the entrance is a CSS animation that runs
   each time a pane becomes displayed: transform and opacity only, no JS, nothing on a frame after it ends. */
@keyframes p-enter-rise { from { opacity: 0; transform: translateY(var(--p-rise)); } to { opacity: 1; transform: none; } }
@keyframes p-enter-slide { from { opacity: 0; transform: translateX(var(--p-slide)); } to { opacity: 1; transform: none; } }
html.is-phone.p-shell #menu.halo .halo-panes > .menu-card:not(.hidden) { animation: p-enter-rise var(--p-dur-std) var(--p-ease-out) 1 both; }
html.is-phone.p-shell.p-pushed #menu.halo .halo-panes > .menu-card:not(.hidden) { animation-name: p-enter-slide; animation-duration: var(--p-dur-page); }

/* ------------------------------------------------------------------------------------------------ 11. THE EXISTING PANES IN THE SHELL
   INTERIM, until lanes 1-4 rebuild each screen (BUILD-PLAN lanes 1-4). The panes main.js shows keep their markup and
   handlers and sit in the page region: one column, the readable floor (12 px), thumb-sized controls (44 px), no
   second scroller inside the page, and the boards main.js opens over the menu (marketplace, career, settings...) as
   pages between the top bar and the tab bar (or the home-indicator inset on a pushed page). Every rule names the
   pane it serves, so a lane that rebuilds a screen deletes its rules here in the same change. */
html.is-phone.p-shell #menu.halo .halo-pane-title { font: var(--p-t-title); letter-spacing: var(--p-tr-title); margin: 0 0 var(--p-s1); color: var(--p-ice); }
html.is-phone.p-shell #menu.halo .halo-pane-sub { font: var(--p-t-caption); color: var(--p-text-2); margin: 0 0 var(--p-s3); }
html.is-phone.p-shell.p-pushed #menu.halo .halo-pane-title { display: none; }   /* the top bar carries the page's title */
/* PLAY root (S3 is lane 1's): the mode cards first, then the marketplace banner + daily challenges + the PREDICTIONS
   and TOURNAMENTS entries, then the server browser */
html.is-phone.p-shell #menu.halo:has(#menu-lobby:not(.hidden)) .halo-panes,
html.is-phone.p-shell #menu.halo .halo-panes #menu-lobby:not(.hidden) { display: contents; }
html.is-phone.p-shell #menu.halo:has(#menu-lobby:not(.hidden)) .halo-left { display: flex; flex-direction: column; gap: var(--p-s3); order: 2; margin: var(--p-s1) 0 var(--p-s4); }
html.is-phone.p-shell #menu.halo:has(#menu-lobby:not(.hidden)) .halo-left #menu-nav {
  display: grid !important; grid-template-columns: 1fr 1fr; gap: var(--p-s2); padding: 0; background: none; border: 0; clip-path: none; box-shadow: none;
}
html.is-phone.p-shell #menu.halo .halo-left #menu-nav::before, html.is-phone.p-shell #menu.halo .halo-left #menu-nav::after { display: none; }
html.is-phone.p-shell #menu.halo .halo-left #menu-nav > :not(.halo-nav-item[data-tab="predictions"], .halo-nav-item[data-tab="tournaments"]) { display: none; }
html.is-phone.p-shell #menu.halo .halo-left #menu-nav .halo-nav-item { min-height: 48px; font-size: 14px; justify-content: center; }
html.is-phone.p-shell #menu.halo:not(:has(#menu-lobby:not(.hidden))) .halo-left { display: none; }
html.is-phone.p-shell #menu.halo #menu-lobby > :is(.halo-pane-title, .halo-pane-sub, #rejoin-banner, .halo-cards) { order: 1; }
html.is-phone.p-shell #menu.halo #menu-lobby > :is(.halo-browser-head, #gpu-tip, #match-list, .lobby-foot) { order: 3; }
html.is-phone.p-shell #menu.halo :is(.menu-footer, #menu-error) { order: 4; }
html.is-phone.p-shell #menu.halo .halo-cards { grid-template-columns: 1fr 1fr; gap: var(--p-s3); margin: var(--p-s2) 0 var(--p-s4); }
html.is-phone.p-shell #menu.halo .halo-card { min-height: 92px; padding: var(--p-s3); display: flex; flex-direction: column; justify-content: flex-start; }
html.is-phone.p-shell #menu.halo .halo-card h3 { font-size: 15px; }
html.is-phone.p-shell #menu.halo .halo-card p { font-size: 12px; line-height: 1.35; }
html.is-phone.p-shell #menu.halo #match-list { max-height: none; overflow: visible; }
html.is-phone.p-shell #menu.halo .halo-browser-head { flex-wrap: wrap; gap: var(--p-s2); }
html.is-phone.p-shell #menu.halo .lobby-foot { display: flex; flex-wrap: wrap; gap: var(--p-s2); }
html.is-phone.p-shell #menu.halo .lobby-foot .btn { flex: 1 1 140px; min-height: 44px; }
html.is-phone.p-shell #menu.halo .menu-footer { display: none; }
html.is-phone.p-shell #menu.halo:has(#menu-lobby:not(.hidden)) .menu-footer { display: flex; margin-top: var(--p-s6); padding-bottom: var(--p-s2); }
html.is-phone.p-shell #menu .menu-footer #powered-by { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--p-s3); font-size: 13px; }
html.is-phone.p-shell #menu .menu-footer #social-links { gap: var(--p-s3); }
html.is-phone.p-shell #menu .menu-footer .social-btn { width: 44px; height: 44px; }
html.is-phone.p-shell #menu .menu-footer .social-btn svg { width: 18px; height: 18px; }
/* the readable floor and the thumb's size in every pane */
html.is-phone.p-shell #menu.halo :is(.field-label, .field-label .dim, .fine-print, .cm-list-head, .cm-prev-lbl, .cac-comp-lbl, .cac-comp-go,
  .xp-bar-text, .ksx-desc, .ksx-slot-label, .ksx-slot-tier, .ksx-state, .ksx-tier, .ksx-sub, .ksx-tokens, .tour-lobby-sub, .tour-lane-copy,
  .tour-lane-shape, .tour-pick-lab, .tour-chip, .btxt, .sc-meta, .sc-players, .sc-cta, .sc-arena, .sc-state, .halo-stat .l, .seg-btn, .slot-btn,
  .askin-btn-go, .xb-tier, #xp-bar-text, .stage-row .s-dep, .lobby-tab, .lobby-invite-btn) { font-size: max(12px, 1em); }
html.is-phone.p-shell #menu.halo :is(#menu-career .l, #menu-tournaments .l, #menu-tournaments .x, #menu-tournaments span:not([class]),
  #menu-tournaments .tour-legend b, #menu-tournaments .tour-legend span) { font-size: 12px; }
html.is-phone.p-shell #menu.halo .field-label { font-size: 12px; letter-spacing: 0.12em; }
html.is-phone.p-shell #menu.halo :is(.seg-btn, .cm-money, .slot-btn, .tour-chip, .ksx-unlock-btn, .lobby-tab, .lobby-invite-btn) { min-height: 44px; min-width: 44px; }
html.is-phone.p-shell #menu.halo .tour-chip { display: inline-flex; align-items: center; justify-content: center; }
html.is-phone.p-shell #menu.halo :is(.emblem-tile, .nameplate-tile) { min-width: 44px; min-height: 44px; }
html.is-phone.p-shell #menu.halo input[type="color"].inline-color { width: 48px; height: 44px; }
html.is-phone.p-shell #menu.halo .cm-more-below { pointer-events: none; }
/* lists that scroll inside their box on a desktop flow in the page here (one scroller) */
html.is-phone.p-shell #menu.halo :is(#cm-map, #cm-mode, #stage-roster, .cm-options, .custom-scroll, #pred-list, .tour-lanes) { max-height: none; overflow: visible; height: auto; }
/* the panes' own primary actions sit in the flow of their page (the action bar is each rebuilt screen's) */
html.is-phone.p-shell body:not(.in-match) #menu.halo #quickplay-btn h3 { margin: 0; }
/* CUSTOMIZE (S8-S11) is lane 2's own tab root since 2026-10-15: client/js/phone/customize.js + client/css/phone-customize.css
   (the hosted #menu-custom pane's interim rules that stood here were deleted with it, as section 11 asks). */
/* the boards main.js opens over the menu, as pages in the page region (S12-S21 rebuild them) */
html.is-phone.p-shell body:not(.in-match) .overlay { min-width: 0; }
html.is-phone.p-shell body:not(.in-match) :is(#crates-screen, #career-screen, #challenges-screen, #leaderboard-screen, #worldstats-screen,
  #winloss-screen, #armor-skins, #weapon-skins, #nameplate-screen, #progress-screen, #settings-panel, #controls-panel,
  #pred-card-screen, #pred-match-screen, #pred-create-screen, #vault-panel, #friends-panel) {
  position: fixed; left: 0; right: 0; margin: 0;
  top: var(--p-chrome-t); bottom: var(--p-chrome-b);
  transform: none; translate: none; width: auto; min-width: 0; max-width: none; height: auto; max-height: none; zoom: 1;
  clip-path: none; border-radius: 0; border-left: 0; border-right: 0; border-top: 0;
  padding: var(--p-s3) var(--p-gutter-r) var(--p-s4) var(--p-gutter-l);
  overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  align-items: stretch; justify-content: flex-start; background: var(--p-bg-1);
}
html.is-phone.p-shell.p-wide body:not(.in-match) :is(#crates-screen, #career-screen, #challenges-screen, #leaderboard-screen, #worldstats-screen,
  #winloss-screen, #armor-skins, #weapon-skins, #nameplate-screen, #progress-screen, #settings-panel, #controls-panel,
  #pred-card-screen, #pred-match-screen, #pred-create-screen, #vault-panel, #friends-panel) { top: calc(var(--p-wide-top) + var(--p-inset-t)); left: calc(var(--p-rail-w) + max(var(--p-s2), var(--p-inset-l))); }
html.is-phone.p-shell.p-wide.p-pushed body:not(.in-match) :is(#crates-screen, #career-screen, #challenges-screen, #leaderboard-screen, #worldstats-screen,
  #winloss-screen, #armor-skins, #weapon-skins, #nameplate-screen, #progress-screen, #settings-panel, #controls-panel,
  #pred-card-screen, #pred-match-screen, #pred-create-screen, #vault-panel, #friends-panel) { left: 0; }
/* the menu chat (SOCIAL > CHAT re-hosts its log and input: client/js/phone/social.js, lane 3) */
html.is-phone.p-shell #mobile-chat-btn.p-keep { position: fixed; z-index: 80; top: calc(var(--p-chrome-t) + var(--p-s2)); right: var(--p-gutter-r); translate: none; min-height: 44px; min-width: 72px; justify-content: center; clip-path: none; font-size: 13px; }
html.is-phone.p-shell #menu-chat { zoom: 1; }
html.is-phone.p-shell #menu-chat.active.open { top: var(--p-chrome-t); bottom: var(--p-chrome-b); width: 100%; right: 0; translate: none; z-index: 82; }
/* --p-chrome-b by the shell's state, set ON ITS CONSUMERS, never on <html>: a custom property that changes on the root is
   inherited by every element, so each push / pop restyled the whole document (fc4 2026-10-15: 17.7 ms on the RTX per
   Back). A lane element sized to the chrome takes class p-chrome-fit. */
html.is-phone.p-shell.p-pushed :is(#crates-screen, #career-screen, #challenges-screen, #leaderboard-screen, #worldstats-screen,
  #winloss-screen, #armor-skins, #weapon-skins, #nameplate-screen, #progress-screen, #settings-panel, #controls-panel,
  #pred-card-screen, #pred-match-screen, #pred-create-screen, #vault-panel, #friends-panel, #menu-chat, .p-chrome-fit) { --p-chrome-b: var(--p-inset-b); }
html.is-phone.p-shell.p-pushed.p-act :is(#crates-screen, #career-screen, #challenges-screen, #leaderboard-screen, #worldstats-screen,
  #winloss-screen, #armor-skins, #weapon-skins, #nameplate-screen, #progress-screen, #settings-panel, #controls-panel,
  #pred-card-screen, #pred-match-screen, #pred-create-screen, #vault-panel, #friends-panel, #menu-chat, .p-chrome-fit) { --p-chrome-b: calc(var(--p-act-h) - 16px + max(16px, var(--p-inset-b))); }
/* every control in a board is a thumb's size; a text field is set at 16 px (iOS zooms into a smaller one) */
html.is-phone body:not(.in-match) :is(input[type="text"], input[type="search"], input[type="number"], input[type="email"],
  input[type="password"], input:not([type]), select, textarea) { font-size: max(16px, 1em); }
html.is-phone.p-shell body:not(.in-match) :is(.overlay, #friends-panel) :is(button, .btn, select, input[type="text"], input[type="search"], input[type="number"]) { min-height: 44px; }
html.is-phone.p-shell body:not(.in-match) :is(.overlay, #friends-panel) input[type="range"] { height: 44px; }
html.is-phone.p-shell body:not(.in-match) :is(.overlay, #friends-panel) input[type="checkbox"]:not(.p-toggle) {
  -webkit-appearance: none; appearance: none; flex: none; width: 44px; height: 44px; margin: var(--p-cb-pull); cursor: pointer;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 26'%3E%3Crect x='1' y='1' width='24' height='24' fill='%230A1A2C' stroke='%2354C8FF' stroke-width='2'/%3E%3C/svg%3E") center / 26px 26px no-repeat;
}
html.is-phone.p-shell body:not(.in-match) :is(.overlay, #friends-panel) input[type="checkbox"]:not(.p-toggle):checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 26'%3E%3Crect x='1' y='1' width='24' height='24' fill='%2354C8FF' stroke='%2354C8FF' stroke-width='2'/%3E%3Cpath d='M6.5 13.5l4.5 4.5 8.5-9' fill='none' stroke='%2304101c' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); }
html.is-phone.p-shell body:not(.in-match) #challenges-screen :is(#ch-reset, #ch-reset b, .rl, .tlab, .c, .chc-bk, .chc-prog) { font-size: 12px; }
html.is-phone.p-shell body:not(.in-match) :is(#leaderboard-screen th, #leaderboard-screen .lb-field, #rank-box-next, .gift-badge, #stage-mic-status, .vm-label, .ksm-label) { font-size: 12px; }
html.is-phone.p-shell #menu.halo #marketplace-btn .mp-txt { font-size: 12px; }
/* the in-UI confirm (main.js uiConfirm) rises as a bottom sheet (S16's confirm sheet replaces it per flow) */
html.is-phone body:not(.in-match) #ui-confirm.overlay {
  top: auto; bottom: 0; left: 0; right: 0; transform: none; translate: none; width: auto; min-width: 0; max-width: none; zoom: 1;
  max-height: calc(var(--p-app-h) - var(--p-inset-t) - 59px); overflow-y: auto; border-radius: 0; align-items: stretch;
  clip-path: polygon(var(--p-cut-l) 0, calc(100% - var(--p-cut-l)) 0, 100% var(--p-cut-l), 100% 100%, 0 100%, 0 var(--p-cut-l));
  padding: var(--p-s6) max(var(--p-s4), var(--p-inset-l)) max(var(--p-s4), var(--p-inset-b)) max(var(--p-s4), var(--p-inset-r));
  background: var(--p-bg-2); border: 0; box-shadow: var(--p-e3-shadow);
}
html.is-phone body:not(.in-match) #ui-confirm .btn { min-height: 52px; }

/* ------------------------------------------------------------------------------------------------ 12. THE FRONT DOOR (interim)
   Signed out (#menu without .halo): S1 / S2 are lane 1's. Until then its controls keep the thumb's size and its column
   clears the home indicator, as wave 7 had them. */
html.is-phone #menu:not(.halo) .menu-content { padding-bottom: calc(var(--p-s12) + var(--p-s8) + var(--p-inset-b)); }
html.is-phone #menu:not(.halo) :is(#menu-gear, #menu-user) { width: 48px; height: 48px; }
html.is-phone #menu:not(.halo) #menu-swap { min-height: 48px; min-width: 88px; }
html.is-phone #menu .menu-footer { padding-bottom: var(--p-s2); }
html.is-phone #menu #access-help { min-height: 44px; padding: 0 var(--p-s3); font-size: 13px; }
html.is-phone #menu #access-help .access-help-i { font-size: 12px; }
/* THE FRONT DOOR on a phone (signed out: PLAY, sign-in, early access), wave 7's rules (77da9cc9 mobile-menu.css) carried
   until lane 1 rebuilds it (SCREEN-SPECS front-door / signin): its column clears the home indicator, the floating gear
   and SWAP are thumb-sized, its small links are real targets. */
html.is-phone #menu:not(.halo) .menu-content { padding-bottom: calc(var(--p-s12) + var(--p-s8) + var(--p-s1) + var(--p-inset-b)); }
html.is-phone #menu:not(.halo) :is(#menu-gear, #menu-user) { width: 48px; height: 48px; }
html.is-phone #menu:not(.halo) #menu-swap { min-height: 48px; min-width: 88px; }
html.is-phone #menu:not(.halo) .menu-footer { padding-bottom: var(--p-s2); }
html.is-phone #menu #access-help { min-height: 44px; padding: 0 var(--p-s3); font-size: 13px; }
html.is-phone #menu #access-help .access-help-i { font-size: 12px; }
html.is-phone #menu .menu-footer #powered-by { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--p-s3); }
html.is-phone #menu #landing-signin .fine-print { font-size: 12px; }
html.is-phone #menu #landing-signin .fine-print a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--p-s2); }
html.is-phone #menu:not(.halo) .menu-footer #powered-by { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--p-s3); font-size: 13px; }
html.is-phone #menu:not(.halo) .menu-footer .social-btn { width: 44px; height: 44px; }
html.is-phone .gift-badge { font-size: 12px; min-width: 18px; height: 18px; line-height: 18px; }
/* lane 7 (2026-10-17) SIDEWAYS SHELL, the rail's T7 / T17 / T24:
   - Back sits at ONE rect on every page: a pushed page's Back is a flex item at the bar's left edge exactly like the
     tab roots' Back to PLAY, and both are one width (the wider of BACK / PLAY), so the two never differ by a pixel;
   - the rail column keeps 8 px off a screen edge that has no inset (548 x 320: the top tab sat at x = 0);
   - the page (the one scroller) ends at the safe bottom: nothing scrolls to a place a thumb cannot use;
   - a chip row or carousel does not bleed past the column (the column already sits inside the safe edges). */
html.is-phone.p-shell.p-wide #p-topbar :is(.p-tb-page .p-back, .p-back--root) { position: static; flex: none; width: 72px; min-width: 72px; }
html.is-phone.p-shell.p-wide:not(.p-act) #menu.halo .menu-content { margin-bottom: var(--p-inset-b); padding-bottom: var(--p-s6); }
html.is-phone.p-wide :is(.p-chips, .p-carousel) { margin-left: 0; margin-right: 0; padding-left: 0; padding-right: 0; scroll-padding-inline: 0;
  -webkit-mask-image: linear-gradient(90deg, var(--p-mask-on) 0, var(--p-mask-on) calc(100% - 24px), transparent 100%); mask-image: linear-gradient(90deg, var(--p-mask-on) 0, var(--p-mask-on) calc(100% - 24px), transparent 100%); }
html.is-phone.p-shell.p-wide #p-topbar .p-tb-act { right: 0; }   /* sideways the bar's edge is the safe edge: its action stays inside (T24) */

/* THE SERVER UPDATE BANNER ON A PHONE (client/js/maintenance.js; style.css draws it): inside the safe area, two lines at most, the
   16 px gutters; the warning line sits in the screen's own gutters */
html.is-phone #maint-banner { font-size: 12px; padding: 6px 10px; max-width: calc(100vw - 32px); margin-top: calc(var(--p-inset-t, 0px) + 4px); }
/* in the menus, just under the top bar (review 2026-10-26: over it, it covered the callsign, the gold chip and a page's title) */
html.is-phone body:not(.in-match) #maint-banner { top: calc(var(--p-chrome-t, 56px) + 4px); margin-top: 0; }
/* in line with the cards: the screens' own gutters already inset it */
html.is-phone .maint-warn { width: auto; margin: 8px 0; font-size: 12px; }
/* PHONE HOME is a grid (phone-play.css): the line takes a row of its own ABOVE the hero, never the auto row under the tiles where
   PLAY painted over it on a short phone held sideways (review 2026-10-26, measured at 393x852, 320x568, 852x393, 667x375) */
html.is-phone .p-home > .maint-warn { grid-row: span 1 / 1; margin-bottom: calc(8px + var(--p-s4)); }
html.is-phone.p-shell.p-wide .p-home > .maint-warn { grid-column: 1; margin-bottom: calc(8px + var(--p-s4) - var(--p-s1)); }
/* the dock keeps its full height beside the line; only while a line is on screen, so the home screen is untouched otherwise */
html.is-phone.p-shell.p-wide .p-home:has(> .maint-warn:not([hidden])) .p-home-dock { grid-row: span 2 / 2; }
