/* ===========================================================================
   Goblin Delve — the phone client.
   2026-08-19 · owns every pixel below 768px · owns nothing above it.
   ===========================================================================

   WHY THIS IS ITS OWN FILE
   ------------------------
   The G1-G5 rework (patches 107-111) designed a desktop app shell — grouped
   rail, sticky vitals HUD, wide-screen context column — and let it reflow
   onto phones. Reflowing a desktop layout is not the same thing as designing
   a phone layout, and the player report that followed said so directly: the
   NEW design is what broke their phone.

   The brief was "redesign the mobile view, do not change the desktop view".
   That constraint is enforced structurally rather than by discipline:

     <link rel="stylesheet" href="/static/mobile.css"
           media="(max-width: 767px)">

   The media attribute is on the LINK. Above 767px the browser never applies
   a single declaration in this file, so no edit here can regress the desktop
   — the guarantee is mechanical, not a promise to be careful.

   THE ONE RULE FOR EDITING THIS FILE
   ----------------------------------
   Every selector starts with `html`.

   Page templates carry their own <style> blocks inside {% block content %},
   which the browser sees AFTER this <link> in the head. Equal specificity
   plus later source order means the page would win every tie. The leading
   `html` adds one type selector, so `html #controls .row` (1,1,1) beats
   `#controls .row` (1,1,0) no matter where it appears. It costs nothing and
   it removes a whole class of "why did my phone rule not apply" bugs.

   (Learned the expensive way: the first version of this work put the phone
   block at the TOP of play.html's own <style>. `position: sticky` applied,
   `display: grid` did not, because a later rule of equal specificity won.
   Media queries do not add specificity.)

   LAYOUT MODEL
   ------------
   A phone gets an app shell, not a document:

     +--------------------------------+
     | status bar   fixed, 48px       |   who am I, how hurt, how rich
     +--------------------------------+
     |                                |
     | content      the only scroller |
     |                                |
     +--------------------------------+
     | tab bar      fixed, 56px+safe  |   5 destinations, thumb zone
     +--------------------------------+

   The play cockpit refines the middle into board / status / log / action
   dock, with the dock pinned directly above the tab bar. Everything the
   core loop presses lives in the bottom third, which is the only part of a
   phone a thumb reaches comfortably.

   Verified by tools/robots/phone.py at 390x844, dpr 3, touch.
   Research and sources: docs/MOBILE_GUI_RESEARCH_2026-08-19.md
   =========================================================================== */

/* ---------------------------------------------------------------------------
   1 · Tokens
   Metrics the layout agrees on. Local to the phone: the desktop sheet has
   its own scale and this file must not leak into it.
   --------------------------------------------------------------------------- */
html {
  --m-status-h: 48px;          /* fixed top bar */
  --m-tabbar-h: 56px;          /* fixed bottom bar, before safe area */
  --m-safe-b: env(safe-area-inset-bottom, 0px);
  --m-safe-t: env(safe-area-inset-top, 0px);
  --m-gap: 10px;
  --m-pad: 12px;
  --m-radius: 12px;
  --m-tap: 44px;               /* Apple HIG floor; Material says 48 */
  --m-tap-box: 24px;           /* WCAG 2.2 AA 2.5.8, for native check/radio */
  --m-dock-bg: #150f28;        /* opaque: the dock sits OVER the log */
  --m-line: #2f2559;
  /* z-index is a scale, not a guess */
  --m-z-board: 5;
  --m-z-dock: 30;
  --m-z-status: 40;
  --m-z-tabbar: 50;
  --m-z-sheet: 60;
}

/* ---------------------------------------------------------------------------
   2 · Gestures
   Three browser defaults cost a tap-heavy combat UI real turns.
   --------------------------------------------------------------------------- */

/* Pull-to-refresh reloads the page. Mid-fight that is indistinguishable
   from a crash — one over-scroll on the log and the turn is gone. */
html { overscroll-behavior-y: contain; }

/* Removes the 300ms click delay AND double-tap-to-zoom, which is what turns
   a fast second tap on "Angriff" into a zoom instead of an attack. */
html button,
html .btn,
html a,
html summary,
html input,
html select { touch-action: manipulation; }

html .btn,
html button,
html .tabbar a { -webkit-tap-highlight-color: transparent; }

/* Rapid tapping selects the label and pops the iOS callout. A control is
   not prose. Deliberately NOT applied to the log or any readable text. */
html #controls,
html .tabbar,
html .btn { -webkit-user-select: none; user-select: none; }

/* ---------------------------------------------------------------------------
   3 · Shell
   --------------------------------------------------------------------------- */

/* A `1fr` grid track has a min-content floor, so ONE wide child can drag the
   whole page past the screen edge — measured: a 594px layout inside a 390px
   phone. `minmax(0, 1fr)` cannot do that. */
html .shell.with-rail { grid-template-columns: minmax(0, 1fr); }
html .content { min-width: 0; }

/* The rail and the wide-screen context column are desktop furniture. The
   rail is already hidden below 1200px; the context column is belt-and-braces
   in case its own breakpoint ever moves. */
html .rail,
html .context { display: none !important; }

/* The band duplicates the tab bar and costs ~200px of an 844px screen —
   logo, four links, and a "Mehr" row before a single pixel of game. */
html body.has-tabbar header.site { display: none; }

html main,
html main.narrow {
  max-width: none;
  padding: var(--m-pad) var(--m-pad)
           calc(var(--m-tabbar-h) + var(--m-safe-b) + 24px);
}

/* ---------------------------------------------------------------------------
   4 · Status bar
   The HUD, re-proportioned. Four cells wrap to two lines at 390px, which is
   what pushed the board down; the run shortcut is the cell to drop, since it
   is a link to the page you are most likely already on.
   --------------------------------------------------------------------------- */
html .hud {
  position: sticky;
  top: 0;
  z-index: var(--m-z-status);
  min-height: var(--m-status-h);
  padding: 6px var(--m-pad);
  padding-top: calc(6px + var(--m-safe-t));
  gap: 4px 12px;
  font-size: .84rem;
  min-width: 0;
  /* backdrop-filter is expensive on mobile GPUs and buys nothing over a
     bar this opaque. */
  backdrop-filter: none;
  background: rgba(20, 15, 40, .98);
}
html .hud-cell { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
html .hud-hp-bar { width: 64px; }
/* Links in the status bar are targets, not text: measured at 19px. */
html .hud a.hud-cell { min-height: var(--m-tap); }

/* The run shortcut is dropped on a phone, and that is a design decision
   rather than a space saving. It is a full-width line of text in the top
   corner — the least reachable part of the device — pointing at /me/play,
   which the tab bar already offers permanently in the thumb zone. Keeping
   it wrapped the status bar onto a second line, and the board's sticky
   offset is measured against a one-line bar. One route back is enough. */
html #hud-run { display: none !important; }

/* ---------------------------------------------------------------------------
   5 · Tab bar
   Primary destinations in the thumb zone; the rest opens UPWARD as a sheet.
   A drawer anchored at the top of a phone is a drawer nobody reaches.
   --------------------------------------------------------------------------- */
html .tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--m-z-tabbar);
  display: flex;
  align-items: stretch;
  height: calc(var(--m-tabbar-h) + var(--m-safe-b));
  padding-bottom: var(--m-safe-b);
  background: rgba(20, 15, 40, .98);
  border-top: 1px solid var(--m-line);
}
html .tabbar a,
html .tabbar summary {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: var(--m-tap);
  padding: 0 2px;
  color: var(--muted);
  font-size: .66rem;
  letter-spacing: .03em;
  text-decoration: none;
  list-style: none;
}
html .tabbar .ico { font-size: 1.2rem; line-height: 1; }
html .tabbar a.active,
html .tabbar details[open] summary { color: var(--cyan); }
html .tabbar a:active,
html .tabbar summary:active { background: #2a2151; }
html .tabbar summary::-webkit-details-marker { display: none; }
html .tabbar details { flex: 1; display: flex; }

html .tabbar .sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(var(--m-tabbar-h) + var(--m-safe-b));
  z-index: var(--m-z-sheet);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  max-height: 60svh;
  overflow-y: auto;
  padding: 8px 10px calc(8px + var(--m-safe-b));
  background: var(--panel);
  border-top: 2px solid var(--frame);
  box-shadow: 0 -10px 28px rgba(0, 0, 0, .5);
}
html .tabbar .sheet a {
  flex: none;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  min-height: 48px;
  padding: 0 8px;
  font-size: .92rem;
  color: var(--text);
}

/* ---------------------------------------------------------------------------
   6 · Controls and forms
   Measure, do not assume: `.btn` carried a "44px" comment in three places
   and rendered at 43.
   --------------------------------------------------------------------------- */
html .btn { min-height: var(--m-tap); }
html input,
html select,
html textarea { min-height: var(--m-tap); font-size: 16px; }
/* 16px is not a style choice — iOS Safari zooms the whole page when a field
   below 16px receives focus, and the zoom does not undo itself. */
html input[type="checkbox"],
html input[type="radio"] {
  width: var(--m-tap-box);
  height: var(--m-tap-box);
  min-height: var(--m-tap-box);
}
/* Disclosures are controls too — "❓ Vendor help" measured 19px. */
html summary { min-height: var(--m-tap); display: flex; align-items: center; }
/* Dense desktop filter rows (atlas view modes, element filters) shrink their
   controls below the floor the rest of the app holds. */
html .map-modes button,
html .map-tools button,
html .map-tools select,
html .map-tools input:not([type="checkbox"]),
html .map-tool-check { min-height: var(--m-tap); }
html .map-tools { width: 100%; }
html .map-tools select,
html .map-tools input:not([type="checkbox"]) { flex: 1 1 140px; width: auto; }

/* ---------------------------------------------------------------------------
   7 · Tables become cards
   A table cannot shrink below its min-content width. The inventory — five
   columns holding two buttons and a number field — measured 426px against a
   390px screen. Column headers move into the cells via data-label.
   --------------------------------------------------------------------------- */
html .panel table,
html .panel tbody,
html .panel tr,
html .panel td,
html .panel th { display: block; }
html .panel tr:has(th) { display: none; }
html .panel tr {
  border: 1px solid var(--frame);
  border-radius: var(--m-radius);
  padding: 10px 12px;
  margin-bottom: var(--m-gap);
}
html .panel td { border: 0; padding: 3px 0; }
html .panel td[data-label]::before {
  content: attr(data-label) ": ";
  color: var(--muted);
  font-size: .85rem;
}
html .panel form.inline { display: block; margin-top: 4px; }

/* ---------------------------------------------------------------------------
   8 · Play cockpit
   The core loop, rebuilt for one hand. Flex `order` does the arranging, not
   the DOM: play.html's own script moves #controls after #stage for the
   desktop stack, and this layout must not depend on which ran last.
   --------------------------------------------------------------------------- */
html main.cockpit { padding: 8px 8px 0; }

html #play-panel {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0 0 calc(var(--m-tabbar-h) + var(--m-safe-b) + 8px);
  background: none;
  border: 0;
}

/* The board is the game. It stays on screen while the log scrolls under it. */
html #stage {
  order: 1;
  position: sticky;
  top: var(--m-status-h);
  z-index: var(--m-z-board);
  border-radius: var(--m-radius);
  /* A game board is not a document: no double-tap zoom, no pan-scroll
     stealing a move. */
  touch-action: none;
}
html #topline { order: 2; margin: 8px 0 4px; }
html #strip   { order: 3; min-height: 0; padding: 8px 10px 6px; }
html #log     { order: 4; height: 5.5em; }

/* The action dock: always reachable, whatever the log is doing. */
html #controls {
  order: 5;
  position: sticky;
  bottom: calc(var(--m-tabbar-h) + var(--m-safe-b));
  z-index: var(--m-z-dock);
  min-height: 0;
  margin-top: 8px;
  padding: 8px 6px 6px;
  background: var(--m-dock-bg);
  border-top: 1px solid var(--m-line);
  border-radius: var(--m-radius) var(--m-radius) 0 0;
  box-shadow: 0 -8px 20px rgba(0, 0, 0, .55);
}

/* Every row spends the FULL width. `flex: 1 1 84px` lets two buttons take
   half each and seven wrap to four-and-three, all stretched to fit. A fixed
   4-column grid was tried first and left the two-button rows half empty
   with their labels ellipsised ("Heim …"). */
html #controls .row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}
html #controls .btn.act {
  flex: 1 1 84px;
  min-width: 0;
  min-height: 48px;
  padding: 8px 6px;
  font-size: .82rem;
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The d-pad keeps its cross — a movement control that is not shaped like
   movement is read wrong under time pressure. */
html #controls .row:has(.btn.dir) {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  max-width: 250px;
  margin-inline: auto;
}
html #controls .row:has(.btn.dir) .btn.dir:only-child { grid-column: 2; }
html #controls .btn.dir { font-size: 1.25rem; min-height: 52px; }
/* Explanatory prose does not belong in a docked control block. */
html #controls .grp > p.muted { display: none; }

/* ---------------------------------------------------------------------------
   9 · Trimmings
   --------------------------------------------------------------------------- */
/* The pilot-version footnote is three lines of small print between the
   player and the tab bar on every single page. */
html main > p.muted:last-of-type { font-size: .78rem; opacity: .75; }
html .panel { padding: 14px; border-radius: var(--m-radius); }
html header.site h1 { font-size: 1.05rem; }
