/* ============================================================================
   TRADEEVERYTHING — THE MOBILE LAYER  (m/mobile.css)
   Authored 2026-08-03. Candidate. Not wired into any live page yet.

   WHY THIS FILE EXISTS
   The halls are single fixed-viewBox illustrations authored at desktop size
   (bank 1671 units wide, leaderboard 1680, how 1440). Squeezing one into a
   390 px phone renders it at scale 0.21 to 0.29, so every control and every
   letter comes out 3.5 to 4.7 times smaller than drawn. The bank's amount
   field measures 37.9 x 12.6 px on an iPhone 14. No media query can fix that,
   because the controls are absolutely positioned inside the drawing.

   So the mobile layer does not restyle the desktop composition. It HIDES it
   and renders its own portrait composition, built once, here. The artwork is
   kept as a cropped band at readable scale rather than a shrunk whole.

   The mobile DOM carries no logic. It mirrors the desktop DOM through
   m/mobile.js, so every quote, balance, trade and exchange still runs on the
   page's existing code. No money path is duplicated.

   ONE breakpoint. No !important unless overriding an inline style.
   ========================================================================= */

/* ---------------------------------------------------------------- tokens -- */
:root {
  /* festival palette, sampled from the live renders */
  --m-sky:        #6DAAE6;
  --m-sky-deep:   #4E8FD0;
  --m-sky-pale:   #A8D0F2;
  --m-grass:      #4E9E33;
  --m-grass-lt:   #63B443;
  --m-ink:        #172240;   /* the cartoon outline navy */
  --m-ink-soft:   #2B3B63;
  --m-ink-mute:   #5A6B90;
  --m-cream:      #FFF7E8;
  --m-card:       #FFFDF6;
  --m-gold:       #F2B32B;
  --m-amber:      #F59120;
  --m-orange:     #E8731C;
  --m-mango:      #FBA63A;
  --m-teal:       #37C5DC;
  --m-violet:     #A98BF0;
  --m-up:         #2FA85F;
  --m-up-lt:      #43D97C;
  --m-down:       #E0563C;
  --m-down-lt:    #F0785A;
  --m-magenta:    #ED2FA5;

  /* the deposit / withdraw door colours, taken from the painted plate */
  --m-door-dep:   #C4553A;
  --m-door-wd:    #8A6FD4;

  /* metrics — the control floor is 52, above Apple's 44 and Google's 48 */
  --m-tap:        52px;
  --m-tap-lg:     60px;
  --m-gutter:     16px;
  --m-radius:     18px;
  --m-radius-sm:  12px;
  --m-head-h:     56px;
  --m-tabs-h:     58px;

  /* type — nothing below 14, inputs never below 17 or iOS zooms on focus */
  --m-font:  'Baloo 2', 'Nunito', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --m-body:  'Inter', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --m-mono:  'DM Mono', 'DM Mono Fallback', ui-monospace, 'SF Mono', Menlo, monospace;

  /* safe areas — the live site uses none of these anywhere */
  --m-safe-t: env(safe-area-inset-top, 0px);
  --m-safe-b: env(safe-area-inset-bottom, 0px);
  --m-safe-l: env(safe-area-inset-left, 0px);
  --m-safe-r: env(safe-area-inset-right, 0px);
}

/* The shell also carries style="display:none" inline in the markup. That is the
   fail-safe: if this stylesheet does not load at all, the shell stays invisible
   and the phone gets the old page rather than a pile of unstyled markup. The
   inline style is the ONLY thing !important is used to beat, below. */
#mSite { display: none; }

/* ============================================================================
   THE TAKEOVER
   max-width 899  -> every phone portrait, every phone landscape up to 899,
                     iPad mini / 10.2 / Air / Pro 11 in PORTRAIT
   short + coarse -> phone LANDSCAPE above 899 (844x390, 932x430) which is a
                     phone by every measure except width
   iPad landscape (1133x744 and up) and iPad Pro 13 portrait (1024) stay on the
   desktop composition, which is the right shape for them.
   ========================================================================= */
@media (max-width: 899px), (max-height: 540px) and (pointer: coarse) {

  /* ---- retire the desktop composition ---------------------------------- */
  /* Nothing is hidden by CSS alone. mobile.js adds .m-parked to the desktop
     top-level nodes and .m-on to <html>. If JS fails the phone still gets the
     old page, which is poor but whole — never a blank screen. And the desktop
     DOM is PARKED, not removed, because mobile.js mirrors it and several pages
     measure their own elements after load. */
  html.m-on body > .m-parked {
    /* FIXED, not absolute. The cockpit's own `tuck()` (markets.html:3912) calls
       scrollIntoView on a .category chip to keep it framed. That chip lives in
       the parked composition, which sat at top:0, so every call dragged the
       document back to the top and the trader could not be scrolled at all —
       it snapped back within a second, every time.
       A fixed element is anchored to the viewport, so scrolling it into view
       moves the document nowhere and the page scrolls normally. */
    position: fixed !important;
    left: -20000px !important; top: 0 !important;
    width: 1440px !important; height: 900px !important;
    overflow: hidden !important;
    pointer-events: none !important;
    /* visibility, not opacity — it also silences position:fixed descendants,
       which escape the off-screen offset of their parked ancestor. Layout boxes
       survive, so the page's own measuring code still reads sane numbers. */
    visibility: hidden !important;
  }

  /* ---- NO FLASH OF THE OLD PAGE --------------------------------------- */
  /* mobile.js is deferred, so it parks the desktop composition at
     DOMContentLoaded — measured at ~350ms in a warm headless browser and much
     longer on a real phone over mobile data. Until then the browser happily
     paints the whole desktop hall, so every visit began with a shrunken
     flash of the old site before the phone layout replaced it.
     CSS applies before the first paint, so the composition is hidden from the
     start and the visitor sees the layer's own sky instead of a page that is
     about to disappear.
     THE FAIL-SAFE IS THE ANIMATION. This layer's founding rule is that a
     failure must never leave a blank screen, and a plain `visibility:hidden`
     would do exactly that if the script never arrived. So the same rule starts
     a zero length animation with a 2.6s delay that reveals the content again.
     If mobile.js runs, `html.m-on` stops matching and the rule and its timer
     are gone before it can fire; if it never runs, the old page comes back by
     itself. Hidden, not removed, so the halls that measure themselves on load
     still read real boxes. */
  /* 0804 — ANDY REPORTED THIS STILL HAPPENING ON AN iPHONE 16 PRO, and he was
     right. The 2.6s timer could not tell a script that had FAILED from one that
     was merely LATE, and mobile.js is deferred, so it cannot run until the whole
     document is parsed — 236KB on the bank, 494KB on the trader, 741KB on the
     arcade, all with large inline scripts. Measured on a throttled repeat visit
     the arcade turned over at 2545ms, 55ms inside the cliff; hold the script past
     2.6s and the desktop nav and header were photographed back on screen.
     So the release is now a SIGNAL, not a guess: mobile.js adds `m-boot` to <html>
     as the first thing it does, whether or not it goes on to take over. The hide
     ends the instant the script runs. On a phone `m-on` lands in the same
     synchronous task, so the old composition never gets a frame; on an iPad in
     landscape, where the script runs and deliberately does NOT take over, the old
     page comes straight back instead of waiting out a timer.
     The timer survives only for the one case a signal cannot cover — the script
     never arriving at all — and it is now 6s, comfortably past any parse measured
     on this site. */
  /* arcload-0827: :not(#bootveil) is the 0812 amendment every page's inline
     copy of this rule carries; this master copy never got it, so the moment
     this file landed it hid any page's boot veil (the arcade's vanishing
     star). The veil is the wait - it is never a reveal target. */
  html:not(.m-on):not(.m-boot) body > *:not(#mSite):not(#bootveil) {
    visibility: hidden;
    animation: m-failsafe-reveal 0s linear 6s forwards;
  }
  @keyframes m-failsafe-reveal { to { visibility: visible; } }

  /* ---- AND THE WAIT MUST LOOK LIKE THIS SITE -------------------------- */
  /* Hiding the composition's children does not touch the page canvas, which
     still comes from the desktop hall's own background. Measured at first paint
     on 0804: /how paints rgb(11,9,6) over the WHOLE screen, the arcade a pale
     rgb(222,245,253) and login a cream rgb(241,231,198). Andy killed black
     site-wide, so the phone opening every visit to How it works on a black field
     is the "old page" he is seeing as much as any flash of content is.
     From first paint the field is the same sky every phone page ends on, so the
     load reads as this site arriving rather than another site leaving.
     !important is used here, against this file's own rule, for the same reason
     the park block uses it: six pages (player, links, api, join, login and the
     404) declare `html { background: rgb(241,231,198) !important }` themselves,
     and !important cannot be beaten by specificity. Both rules are scoped to the
     phone media query AND to the pre-takeover state, so nothing here can reach a
     desktop render or survive past the moment the script runs. */
  html:not(.m-on):not(.m-boot) { background: var(--m-sky) !important; }
  html:not(.m-on):not(.m-boot) body { background: var(--m-sky) !important; }

  /* the live pages carry no global box-sizing reset, so the shell states its
     own. Without it a padded fixed element computes 12 px narrow. */
  html.m-on #mSite, html.m-on #mSite *, html.m-on #mSite *::before,
  html.m-on #mSite *::after, html.m-on .mtabs, html.m-on .mtabs *,
  html.m-on .msheet, html.m-on .msheet * { box-sizing: border-box; }

  html.m-on { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  html.m-on, html.m-on body {
    margin: 0; padding: 0;
    background: var(--m-sky);
    height: auto;              /* several live pages lock the height to the viewport */
    min-height: 100%;
  }
  /* THE VIEWPORT SCROLLS; THE BODY MUST NOT BE A SCROLL CONTAINER.
     Until 0805 the rule above stamped `overflow-x:hidden; overflow-y:auto` on
     BOTH elements. Any overflow but visible makes the body a scroll container,
     and a sticky element pins against its NEAREST scrolling ancestor — the
     body — whose scrollport never moves, because height:auto lets the body
     grow to its content while the actual scrolling happens at the viewport.
     Measured on the composed legal page and on live: the .mlg8 section bands
     never pinned, and neither did .mhead itself — every position:sticky in the
     layer was quietly dead on every page.
     So the overflow now splits. The root keeps both axes — x clips the
     sideways bleed at the viewport edge, y stays auto because the bank locks
     the root to hidden and the trader to clip, and the phone page must scroll
     anyway. The body is stamped back to visible on BOTH axes, stated rather
     than omitted because the halls fight it: the bank locks the body to
     hidden, the trader to clip, the leaderboard hides x alone — and one
     hidden axis computes the other to auto, which remakes the scroll
     container. html.m-on body outranks every one of those on specificity;
     none of them carries !important (grepped 0805). */
  html.m-on {
    overflow-x: hidden;
    overflow-y: auto;
  }
  html.m-on body {
    overflow: visible;
  }
  /* THE BODY MUST BE A PLAIN BLOCK.
     markets.html lays its cockpit out with `body{display:grid}` and a template
     of exactly two rows, `68px 0px`. The shell is the body's last child, so it
     became the second grid item and landed in the ZERO height track: it
     reported a 1748px box, computed `display:flex` and `visibility:visible`,
     and painted absolutely nothing. The whole trader was a blank sky on every
     phone while every computed style insisted it was fine.
     Any page is free to make its body a grid or a flex container for its own
     composition; once the layer takes over, that composition is parked and the
     body carries exactly one child that matters. So it is a block. */
  html.m-on body { display: block !important; }

  html.m-on #mSite {
    display: flex !important;   /* beats the inline display:none fail-safe */
    flex-direction: column;
    min-height: 100svh;        /* svh, not vh — vh is the bars-hidden height */
    font-family: var(--m-body);
    color: var(--m-ink);
    -webkit-font-smoothing: antialiased;
    padding-left: var(--m-safe-l);
    padding-right: var(--m-safe-r);
  }

  /* ---- header : sky band, wordmark, exactly one control ----------------- */
  .mhead {
    position: sticky; top: 0; z-index: 40;
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px;
    height: calc(var(--m-head-h) + var(--m-safe-t));
    padding: var(--m-safe-t) var(--m-gutter) 0;
    background: var(--m-sky);
    box-shadow: 0 2px 0 rgba(23,34,64,.10);
  }
  .mhead-brand { display: flex; align-items: center; min-width: 0; flex: 1 1 auto;
                 min-height: var(--m-tap); }   /* the wordmark is a link, so it gets a real hit area */
  .mhead-brand svg { display: block; height: 30px; width: auto; max-width: 100%; }
  .mhead-btn {
    flex: 0 0 auto;
    width: var(--m-tap); height: var(--m-tap);
    display: grid; place-items: center;
    border: 3px solid var(--m-ink);
    border-radius: 50%;
    background: linear-gradient(180deg, #B6E36B, #6FB52C);
    box-shadow: 0 3px 0 rgba(23,34,64,.35);
    cursor: pointer; padding: 0;
    -webkit-tap-highlight-color: transparent;
  }
  .mhead-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(23,34,64,.35); }
  .mhead-btn span { display: block; width: 22px; height: 3px; border-radius: 2px;
                    background: var(--m-ink); box-shadow: 0 -7px 0 var(--m-ink), 0 7px 0 var(--m-ink); }

  /* 0815 sweep-4 M6. The scalloped hem (.mr-edge) hangs transparent between its
     arcs, so page content read through the fixed awning on ten scrolling halls
     while /api and /legal, each with an opaque layer behind the hem, read right.
     The header's own ground now extends 13px further down behind the hem (the
     hem's exact height), so the notches always read as the hall's resting sky,
     and the hem's cream arc-glow keeps rendering above it exactly as at rest.
     Measured: the resting backdrop is var(--m-sky) on every hall. The bank is
     excluded: its hall art rises into the hem band and the page never scrolls,
     so it cannot leak and its art must not be covered. */
  html.m-on:not(:has(link[rel="canonical"][href$="/pineapplebank"])) #mSite header.mhead {
    box-shadow: 0 13px 0 var(--m-sky);
  }

  /* ---- main scroll region ---------------------------------------------- */
  .mmain {
    flex: 1 1 auto;
    display: flex; flex-direction: column;
    gap: 14px;
    padding: 14px var(--m-gutter)
             calc(var(--m-tabs-h) + var(--m-safe-b) + 22px);
  }
  html.m-notabs .mmain { padding-bottom: calc(var(--m-safe-b) + 22px); }

  /* ---- the art band : his artwork, CROPPED, never squeezed -------------- */
  /* background-size is set per hall so one source pixel lands at a fixed
     fraction of a CSS pixel. The band shows a WINDOW of the drawing at
     readable scale instead of the whole drawing at 0.23. */
  .mband {
    position: relative;
    height: var(--mband-h, 210px);
    margin: -14px calc(var(--m-gutter) * -1) 0;
    border-bottom: 3px solid var(--m-ink);
    background-color: var(--m-sky);
    background-repeat: no-repeat;
    background-position: var(--mband-pos, 50% 34%);
    background-size: var(--mband-size, cover);
    /* mband-size is set per hall in fixed px so ONE source pixel always lands at
       the same fraction of a CSS pixel. The window widens on a bigger phone; the
       artwork never shrinks. That is the whole difference from the live site. */
    overflow: hidden;
  }
  .mband::after {                       /* seat the band into the page */
    content: ''; position: absolute; inset: auto 0 0 0; height: 34px;
    background: linear-gradient(180deg, rgba(23,34,64,0), rgba(23,34,64,.16));
    pointer-events: none;
  }
  /* The hall name sits on its own plate under the band, never over the art.
     Text laid over a busy drawing is text used as design, which this site does
     not do, and it is the first thing to become unreadable on a small screen. */
  .mband-name {
    display: flex; align-items: center; gap: 10px;
    margin: -22px var(--m-gutter) 0; position: relative; z-index: 2;
    padding: 9px 14px;
    background: var(--m-cream);
    border: 3px solid var(--m-ink); border-radius: 14px;
    box-shadow: 0 4px 0 rgba(23,34,64,.30);
    font-family: var(--m-font); font-weight: 800;
    font-size: 22px; line-height: 1.15; color: var(--m-ink);
  }

  /* ---- cards ------------------------------------------------------------ */
  .mcard {
    background: var(--m-card);
    border: 3px solid var(--m-ink);
    border-radius: var(--m-radius);
    box-shadow: 0 4px 0 rgba(23,34,64,.28);
    padding: 14px;
  }
  .mcard-dark {
    background: linear-gradient(180deg, #1B2A4A, #121D36);
    color: var(--m-cream);
    border-color: var(--m-ink);
  }
  .mcard-h {
    font-family: var(--m-font); font-weight: 800;
    font-size: 15px; letter-spacing: 1.4px; text-transform: uppercase;
    color: var(--m-ink-mute);
    margin: 0 0 10px;
  }
  .mcard-dark .mcard-h { color: var(--m-sky-pale); }

  /* ---- type ------------------------------------------------------------- */
  .mtitle  { font-family: var(--m-font); font-weight: 800; font-size: 24px;
             line-height: 1.15; margin: 0 0 6px; color: var(--m-ink); }
  .mlede   { font-size: 17px; line-height: 1.5; margin: 0 0 10px; color: var(--m-ink-soft); }
  .mtext   { font-size: 16px; line-height: 1.55; color: var(--m-ink-soft); }
  .mtext p { margin: 0 0 12px; }
  .mtext p:last-child { margin-bottom: 0; }
  .mnum    { font-family: var(--m-mono); font-variant-numeric: tabular-nums;
             font-weight: 600; letter-spacing: -.5px; }
  .msmall  { font-size: 14px; line-height: 1.45; color: var(--m-ink-mute); }

  /* ---- controls : nothing below 52 px, no input below 17 px ------------- */
  .mbtn {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; min-height: var(--m-tap-lg);
    padding: 0 18px;
    font-family: var(--m-font); font-weight: 800; font-size: 19px;
    color: var(--m-ink); text-decoration: none;
    background: linear-gradient(180deg, #FFE9A8, #F2B32B);
    border: 3px solid var(--m-ink);
    border-radius: 14px;
    box-shadow: 0 4px 0 rgba(23,34,64,.42);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .mbtn:active { transform: translateY(3px); box-shadow: 0 1px 0 rgba(23,34,64,.42); }
  .mbtn[disabled], .mbtn[aria-disabled="true"] { filter: grayscale(.6); opacity: .6; }
  .mbtn-buy  { background: linear-gradient(180deg, #8DE8AC, #2FA85F); color: #06301A; }
  .mbtn-sell { background: linear-gradient(180deg, #FFB3A0, #E0563C); color: #3A0C04; }
  .mbtn-dep  { background: linear-gradient(180deg, #E89078, var(--m-door-dep)); color: var(--m-cream); }
  .mbtn-wd   { background: linear-gradient(180deg, #BBA6F0, var(--m-door-wd));  color: var(--m-cream); }
  .mbtn-ghost{ background: var(--m-card); }

  /* segmented pair — the pattern for asset and direction choices */
  .mseg { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .mseg-3 { grid-template-columns: repeat(3, 1fr); }
  .mseg > button, .mseg > a {
    min-height: var(--m-tap); padding: 0 10px;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--m-font); font-weight: 800; font-size: 17px;
    color: var(--m-ink-soft); text-decoration: none;
    background: #EFE6D2;
    border: 3px solid var(--m-ink);
    border-radius: 13px;
    box-shadow: 0 3px 0 rgba(23,34,64,.30);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  /* the two doors keep the painted plate's own colours so the choice reads the
     same on the phone as it does in the drawing */
  .mseg > .mbtn-dep { background: linear-gradient(180deg, #E89078, var(--m-door-dep)); color: var(--m-cream); }
  .mseg > .mbtn-wd  { background: linear-gradient(180deg, #BBA6F0, var(--m-door-wd));  color: var(--m-cream); }
  .mseg > .mbtn-dep, .mseg > .mbtn-wd { opacity: .78; }

  .mseg > [aria-checked="true"], .mseg > [aria-pressed="true"], .mseg > .is-on {
    background: linear-gradient(180deg, #FFE9A8, var(--m-gold));
    color: var(--m-ink);
    box-shadow: 0 3px 0 rgba(23,34,64,.42), inset 0 0 0 2px rgba(255,255,255,.5);
  }
  .mseg > .mbtn-dep[aria-checked="true"], .mseg > .mbtn-dep.is-on {
    background: linear-gradient(180deg, #E89078, var(--m-door-dep));
    color: var(--m-cream); opacity: 1;
    box-shadow: 0 3px 0 rgba(23,34,64,.42), inset 0 0 0 2px rgba(255,255,255,.42);
  }
  .mseg > .mbtn-wd[aria-checked="true"], .mseg > .mbtn-wd.is-on {
    background: linear-gradient(180deg, #BBA6F0, var(--m-door-wd));
    color: var(--m-cream); opacity: 1;
    box-shadow: 0 3px 0 rgba(23,34,64,.42), inset 0 0 0 2px rgba(255,255,255,.42);
  }

  /* stepper row : big field flanked by big keys */
  .mstep { display: grid; grid-template-columns: var(--m-tap) 1fr var(--m-tap); gap: 10px; align-items: stretch; }
  .mstep button {
    display: grid; place-items: center;
    min-height: var(--m-tap-lg);
    font-size: 30px; line-height: 1; font-weight: 700;
    color: var(--m-ink);
    background: linear-gradient(180deg, #FFFFFF, #DFD6C2);
    border: 3px solid var(--m-ink); border-radius: 13px;
    box-shadow: 0 3px 0 rgba(23,34,64,.32);
    cursor: pointer; padding: 0;
    -webkit-tap-highlight-color: transparent;
  }
  .mstep button:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(23,34,64,.32); }

  .minput {
    width: 100%; min-height: var(--m-tap-lg);
    padding: 0 14px;
    font-family: var(--m-mono); font-variant-numeric: tabular-nums;
    font-size: 26px; font-weight: 600; text-align: center;
    color: var(--m-ink);
    background: #FFFFFF;
    border: 3px solid var(--m-ink); border-radius: 13px;
    box-shadow: inset 0 2px 0 rgba(23,34,64,.12);
    -webkit-appearance: none; appearance: none;
  }
  .minput-text {
    font-family: var(--m-body); font-size: 18px; font-weight: 500;
    text-align: left; letter-spacing: 0;
  }
  .minput:focus-visible, .mbtn:focus-visible, .mseg > *:focus-visible,
  .mhead-btn:focus-visible, .mtab:focus-visible {
    outline: 3px solid #1F7BE0; outline-offset: 2px;
  }
  .mfield { display: block; margin: 0 0 12px; }
  .mfield:last-child { margin-bottom: 0; }
  .mlabel {
    display: block; margin: 0 0 7px;
    font-family: var(--m-font); font-weight: 800;
    font-size: 14px; letter-spacing: 1.2px; text-transform: uppercase;
    color: var(--m-ink-mute);
  }

  /* ---- quote pair : the live numbers, big ------------------------------- */
  .mquote { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .mquote > div {
    border: 3px solid var(--m-ink); border-radius: 13px;
    padding: 9px 8px 11px; text-align: center;
    background: linear-gradient(180deg, #23345A, #16233F);
  }
  .mquote .k { font-family: var(--m-font); font-weight: 800; font-size: 14px;
               letter-spacing: 2px; color: var(--m-sky-pale); }
  .mquote .v { font-family: var(--m-mono); font-variant-numeric: tabular-nums;
               font-size: 27px; font-weight: 600; line-height: 1.15; margin-top: 2px; }
  .mquote .bid .v { color: #FF9E86; }
  .mquote .ask .v { color: #7CE8A8; }

  /* ---- balance strip ---------------------------------------------------- */
  .mbal { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .mbal > div {
    background: var(--m-card); border: 3px solid var(--m-ink);
    border-radius: 13px; padding: 9px 10px 11px; text-align: center;
    box-shadow: 0 3px 0 rgba(23,34,64,.24);
  }
  .mbal .k { font-family: var(--m-font); font-weight: 800; font-size: 14px;
             letter-spacing: 1.4px; color: var(--m-ink-mute); }
  .mbal .v { font-family: var(--m-mono); font-variant-numeric: tabular-nums;
             font-size: 25px; font-weight: 600; color: var(--m-ink); line-height: 1.2; }

  /* ---- list rows (leaderboard, links, faq) ------------------------------ */
  .mrow {
    display: flex; align-items: center; gap: 12px;
    min-height: var(--m-tap); padding: 11px 0;
    border-bottom: 2px solid rgba(23,34,64,.12);
    text-decoration: none; color: inherit;
  }
  .mrow:last-child { border-bottom: 0; }
  .mrow-rank {
    flex: 0 0 auto; width: 38px; height: 38px;
    display: grid; place-items: center;
    font-family: var(--m-font); font-weight: 800; font-size: 17px;
    color: #06223A;
    border: 3px solid var(--m-ink); border-radius: 11px;
    background: linear-gradient(180deg, #B9F7FF, #22D9FF);
  }
  .mrow-main { flex: 1 1 auto; min-width: 0; }
  .mrow-name { font-family: var(--m-font); font-weight: 800; font-size: 18px;
               color: var(--m-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mrow-sub  { font-size: 14px; color: var(--m-ink-mute); margin-top: 1px; }
  .mrow-val  { flex: 0 0 auto; font-family: var(--m-mono); font-variant-numeric: tabular-nums;
               font-size: 18px; font-weight: 600; }
  .mrow-val.up { color: var(--m-up); } .mrow-val.down { color: var(--m-down); }

  /* ---- the hall tiles (landing) ----------------------------------------- */
  .mtiles { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
  .mtile {
    position: relative; display: flex; flex-direction: column; justify-content: flex-end;
    min-height: 132px; padding: 12px;
    border: 3px solid var(--m-ink); border-radius: var(--m-radius);
    box-shadow: 0 4px 0 rgba(23,34,64,.30);
    text-decoration: none; color: var(--m-cream);
    overflow: hidden;
    background-size: cover; background-position: center;
  }
  .mtile-wide { grid-column: 1 / -1; min-height: 116px; }
  .mtile b { font-family: var(--m-font); font-weight: 800; font-size: 20px; line-height: 1.1;
             text-shadow: 0 2px 0 var(--m-ink), 0 0 12px rgba(23,34,64,.6); }
  .mtile span { font-size: 14px; margin-top: 2px; color: rgba(255,247,232,.92);
                text-shadow: 0 1px 0 rgba(23,34,64,.9); }
  .mtile:active { transform: translateY(3px); box-shadow: 0 1px 0 rgba(23,34,64,.30); }

  /* ---- the bottom tab bar ----------------------------------------------- */
  .mtabs {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
    display: grid; grid-template-columns: repeat(4, 1fr);
    height: calc(var(--m-tabs-h) + var(--m-safe-b));
    padding: 0 var(--m-safe-l) var(--m-safe-b) var(--m-safe-r);
    background: var(--m-cream);
    border-top: 3px solid var(--m-ink);
    box-shadow: 0 -3px 14px rgba(23,34,64,.18);
  }
  html.m-notabs .mtabs { display: none; }
  .mtab {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; min-height: var(--m-tap);
    text-decoration: none; color: var(--m-ink-mute);
    font-family: var(--m-font); font-weight: 800; font-size: 12px; letter-spacing: .1px;
    -webkit-tap-highlight-color: transparent;
  }
  .mtab svg { width: 24px; height: 24px; display: block; }
  /* 0815 sweep-4 M15 (reached the door) + sweep-5 N14 (one grammar): the
     menu sheet's .mr-key (Player signed / Log in guest) is the sheet's one
     plate-shaped door, and its "you are here" mark now speaks the PLATES'
     own authored current-page treatment — ink released to full, the
     cream-gold face, the green lintel skirt — instead of a bare orange
     word the sheet says nowhere else. The selector is M15's own widened
     path; the ghost variant is named so the guest Log in key outranks its
     own ghost face. The old rule's .mtab co-selector and its svg
     drop-shadow rendered on zero pages (the sheet's .mtab doors are
     painted by their pages' higher-specificity rules) and are retired
     with the orange — #E8731C leaves the shell's key grammar. */
  html.m-on #mSite .msheet .mr-key[aria-current="page"],
  html.m-on #mSite .msheet .mr-key.mr-key-ghost[aria-current="page"] {
    color: var(--m-ink);
    background: linear-gradient(180deg, #FFF2C8, #FFFBEA);
    box-shadow: 0 3px 0 var(--mr-skirt, var(--m-ink-soft)),
                inset 0 4px 0 #7FB61F, inset 0 6px 0 var(--m-ink);
  }

  /* ---- the menu sheet ---------------------------------------------------- */
  .msheet {
    position: fixed; inset: 0; z-index: 60;
    display: flex; flex-direction: column;
    background: rgba(11,17,32,.55);
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  }
  .msheet[hidden] { display: none; }
  .msheet-panel {
    margin-top: auto;
    max-height: 82svh;
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    background: var(--m-cream);
    border-top: 3px solid var(--m-ink);
    border-radius: 22px 22px 0 0;
    padding: 10px var(--m-gutter) calc(var(--m-safe-b) + 18px);
  }
  .msheet-grab { width: 46px; height: 5px; border-radius: 3px; margin: 4px auto 12px;
                 background: rgba(23,34,64,.28); }
  .msheet a, .msheet button.msheet-item {
    display: flex; align-items: center; gap: 12px;
    width: 100%; min-height: var(--m-tap);
    padding: 0 4px;
    font-family: var(--m-font); font-weight: 800; font-size: 19px;
    color: var(--m-ink); text-decoration: none;
    background: none; border: 0; border-bottom: 2px solid rgba(23,34,64,.12);
    cursor: pointer; text-align: left;
  }
  .msheet a:last-of-type { border-bottom: 0; }
  .msheet-cta { margin-top: 14px; }
  /* the sheet groups its destinations but never names the groups — "no group
     kickers, ever" (Andy 0802). A gap does the separating instead of a word. */
  .msheet-gap { height: 12px; }
  .msheet-gap:first-child { height: 0; }

  /* ---- utility ----------------------------------------------------------- */
  .mstack   { display: flex; flex-direction: column; gap: 10px; }
  .mstack-8 { display: flex; flex-direction: column; gap: 8px; }
  .mhr { height: 2px; background: rgba(23,34,64,.14); border: 0; margin: 4px 0; }
  .mnote {
    background: rgba(242,179,43,.16);
    border: 2px solid rgba(242,179,43,.55);
    border-radius: var(--m-radius-sm);
    padding: 10px 12px; font-size: 15px; line-height: 1.45; color: var(--m-ink-soft);
  }
  .mstatus { font-size: 15px; line-height: 1.4; min-height: 21px; color: var(--m-ink-mute); }
  .mstatus.ok  { color: var(--m-up); font-weight: 600; }
  .mstatus.bad { color: var(--m-down); font-weight: 600; }
  .m-scroll-x {
    display: flex; gap: 10px;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    margin: 0 calc(var(--m-gutter) * -1); padding: 2px var(--m-gutter);
    scrollbar-width: none;
  }
  .m-scroll-x::-webkit-scrollbar { display: none; }
  .m-scroll-x > * { flex: 0 0 auto; scroll-snap-align: start; }

  /* ---- narrow phones (320 to 359) : tighten, never shrink type ---------- */
  @media (max-width: 359px) {
    :root { --m-gutter: 12px; }
    .mtitle { font-size: 22px; }
    .mband-title { font-size: 23px; }
    .mquote .v { font-size: 24px; }
    .minput { font-size: 23px; }
    .mtab { font-size: 11px; }
  }

  /* ---- phone landscape : the cruellest state, 330 to 430 px tall -------- */
  @media (orientation: landscape) and (max-height: 540px) {
    .mband { height: 132px; }
    .mband-title { font-size: 21px; }
    .mmain {
      display: grid; grid-template-columns: 1fr 1fr;
      align-items: start; gap: 12px;
    }
    .mmain > .mband, .mmain > .m-full { grid-column: 1 / -1; }
    .mtiles { grid-template-columns: repeat(4, 1fr); }
    .mtile { min-height: 108px; }
    .msheet-panel { max-height: 92svh; }
    .mtabs { height: calc(50px + var(--m-safe-b)); }
    .mtab { font-size: 11px; }
    .mtab svg { width: 20px; height: 20px; }
  }

  /* ---- small tablets in portrait : one comfortable column --------------- */
  @media (min-width: 700px) {
    .mmain { max-width: 620px; margin: 0 auto; width: 100%; }
    .mband { margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); height: 280px; }
    .mtitle { font-size: 28px; }
    .mtiles { grid-template-columns: repeat(2, 1fr); }
  }

  @media (prefers-reduced-motion: reduce) {
    #mSite *, #mSite *::before, #mSite *::after {
      animation-duration: .001ms !important; animation-iteration-count: 1 !important;
      transition-duration: .001ms !important;
    }
  }
}

/* Dark preference is deliberately NOT honoured. The festival is a daylight
   world and Andy's colour law encourages bold colour and rejects blacks. */
