/* ============================================================
   /hex — Hex Frontier

   A board you grow by hand. The map is the page; everything else
   is a thin layer of ink over it that gets out of the way.

   THINGS THIS PAGE DOES NOT DO, because they are the tells of a
   generated page and the owner has called each one out:

     · no kicker / eyebrow label above a heading
     · no dot, bullet or square marker before a section title
     · no wall of rounded cards. There are no cards at all here —
       the sheets are full-bleed, and every list is hairline-
       divided rows
     · no emoji anywhere. The three icons are inline SVG paths
       stroked with currentColor
     · no coloured buttons. Not one control on this page is
       tinted; a button is a neutral surface with a hairline

   THE GRADIENT BUDGET, and it is zero. Not one, here or on the
   canvas. Depth comes from a hard offset shadow a button presses
   down into, the way a physical key works, and from 1px
   hairlines — never from a blur, a glow or a colour ramp.

   THE COLOUR BUDGET, and it is five, all of them terrain:
   meadow, forest, water, field, town. They live in
   js/view/art.js because the canvas needs them too, and they are
   the ONLY saturated colour on the page. Every control, every
   number, every rule is ink on background. That is deliberate:
   your eye should have exactly one place to go.

   夜 is the default. There is no prefers-color-scheme block at
   all, so a light system setting can never reach the page —
   [data-theme="light"] exists only because the switch asked, and
   an inline script in the document applies it before first paint.
   ============================================================ */

:root {
  color-scheme: dark;

  --bg: #0e0f10;
  --panel: #17191b;
  --panel-hi: #1e2124;

  --ink: #e8e6e1;
  --ink-dim: rgba(232, 230, 225, 0.62);
  --ink-faint: rgba(232, 230, 225, 0.34);

  --line: rgba(232, 230, 225, 0.13);
  --line-strong: rgba(232, 230, 225, 0.26);
  --press: rgba(0, 0, 0, 0.55);

  --radius: 3px;

  /* Hoisted so they can be used inside calc() rather than repeated. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  --dock-h: 108px;

  --ui: ui-sans-serif, system-ui, -apple-system, "Segoe UI", "Noto Sans TC", sans-serif;
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f2f0ea;
  --panel: #ffffff;
  --panel-hi: #f7f5f0;
  --ink: #16181a;
  --ink-dim: rgba(22, 24, 26, 0.64);
  --ink-faint: rgba(22, 24, 26, 0.38);
  --line: rgba(22, 24, 26, 0.14);
  --line-strong: rgba(22, 24, 26, 0.28);
  --press: rgba(22, 24, 26, 0.32);
}

/* Nothing on this page is prose and a long press is a game gesture, so all
   three of these are needed: without them a long press on a phone starts a text
   selection, pops the callout menu, or raises the iOS magnifier mid-placement.
   The unprefixed property alone is not enough on Safari. */
* {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* An id selector with a display beats the browser's own [hidden] rule, so
   `el.hidden = true` on anything styled here would set the attribute and change
   nothing at all. That is how the run-over panel shipped its submit row to
   people who had played offline and could not submit. */
[hidden] { display: none !important; }

html, body {
  position: fixed;
  inset: 0;
  overflow: hidden;
  overscroll-behavior: none;
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ui);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

/* Every number that changes gets tabular figures, or the score jitters its own
   width as it counts and the whole corner of the screen twitches. */
#score, #best, #tiles, #final-score, #final-line, #final-tiles, #final-combo,
.q-count, .row-score, .row-rank, .row-tiles {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

#board {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* touch-action alone does not stop iOS rubber-banding under a drag; input.js
     also attaches a non-passive touchmove that calls preventDefault. Both. */
  touch-action: none;
}

/* ── the overlay ──────────────────────────────────────────── */

/* pointer-events:none here and auto on each control. This is the whole reason
   taps reach the canvas: an overlay stretched across the viewport otherwise
   eats every one of them and the board looks broken. */
#ui {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: flex;
  flex-direction: column;
}

/* While a sheet is open the HUD goes away entirely. The start sheet is
   deliberately see-through, and a live score and tile count showing behind it
   before you have played anything is noise pretending to be information. */
body.sheet-open #ui { display: none; }

#topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: calc(12px + var(--safe-t)) calc(14px + var(--safe-r)) 0 calc(14px + var(--safe-l));
}

/* The score is the largest object on the screen and carries no label above it.
   That is what replaced a kicker. */
#score {
  font-size: clamp(30px, 8vw, 44px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.45);
}
:root[data-theme="light"] #score { text-shadow: 0 1px 10px rgba(255, 255, 255, 0.6); }

#best {
  margin-top: 5px;
  font-size: 12px;
  color: var(--ink-dim);
  letter-spacing: 0.01em;
}

#topright {
  display: flex;
  align-items: center;
  gap: 6px;
  pointer-events: auto;
}

#lang-btn {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
}

#topright button,
#home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px of hit area bought with padding, never by growing the type. */
  min-width: 44px;
  min-height: 44px;
  padding: 0 10px;
  background: none;
  border: 0;
  color: var(--ink-dim);
  font: inherit;
  font-size: 12px;
  text-decoration: none;
  cursor: pointer;
  border-radius: var(--radius);
}
#topright button:active,
#home:active { color: var(--ink); }

/* ── open quests ──────────────────────────────────────────── */

/* A flush band against the right edge, square-cornered, one hairline down its
   left side. A text-shadow alone was tried first and is not enough: this list
   lands on five saturated terrain fills, and "ink over anything" is not a
   contrast strategy. This is a band, not a card — no radius, no float, no
   shadow, and it runs to the edge of the screen. */
#quests {
  pointer-events: none;
  margin: 6px 0 0 auto;
  padding: 2px calc(14px + var(--safe-r)) 8px 14px;
  width: min(244px, 52vw);
  display: flex;
  flex-direction: column;
  background: var(--bg);
  box-shadow: inset 1px 0 0 var(--line);
}

.q-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-dim);
}
.q-row:first-child { border-top: 0; }
.q-row:last-child { border-bottom: 0; }
.q-count { color: var(--ink); font-weight: 600; }
.q-row.is-close .q-count { color: var(--ink); }

/* ── the dock ─────────────────────────────────────────────── */

/* One auto margin for the whole bottom group. Putting it on #dock and #readout
   separately splits the free space between them and opens a gap. */
#bottom { margin-top: auto; }

#dock {
  pointer-events: none;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  gap: 10px;
  padding: 0 calc(14px + var(--safe-r)) calc(10px + var(--safe-b)) calc(14px + var(--safe-l));
}

.dock-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
#dock-count { align-items: flex-start; }
#dock-next { align-items: flex-end; }

#tiles {
  font-size: 22px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
:root[data-theme="light"] #tiles { text-shadow: 0 1px 8px rgba(255, 255, 255, 0.7); }
#tiles.is-low { color: var(--ink); }

/* A brief flash when a tile comes back, so the reward registers somewhere other
   than a number that quietly fails to go down. */
#tiles.is-gain { color: var(--ink); transform: scale(1.14); }
#tiles { transition: transform 180ms ease, color 180ms ease; transform-origin: left bottom; }

.dock-label {
  font-size: 10.5px;
  color: var(--ink-faint);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* The dock sits directly on the board, so its labels carry their own
     separation the way the sandbox HUD does. */
  text-shadow: 0 1px 7px rgba(0, 0, 0, 0.65);
}
:root[data-theme="light"] .dock-label { text-shadow: 0 1px 7px rgba(255, 255, 255, 0.85); }

#dock-tile {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  pointer-events: auto;
}

#tile-now {
  width: 78px;
  height: 78px;
  cursor: pointer;
  touch-action: none;
}
#tile-next {
  width: 40px;
  height: 40px;
  opacity: 0.62;
}

#rot-ccw, #rot-cw {
  width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--panel);
  border: 0;
  /* inset box-shadow rather than a border: a border changes the box size and
     the two buttons would shift by a pixel on press. */
  box-shadow: inset 0 0 0 1px var(--line), 0 3px 0 var(--press);
  border-radius: var(--radius);
  color: var(--ink-dim);
  cursor: pointer;
  touch-action: manipulation;
}
/* A key presses down INTO its own offset, rather than blooming. */
#rot-ccw:active, #rot-cw:active {
  transform: translateY(3px);
  box-shadow: inset 0 0 0 1px var(--line), 0 0 0 var(--press);
  color: var(--ink);
}

/* The readout is the tutorial. It sits directly above the tile you are holding
   because that is where you are already looking, and it says both halves of the
   refund rule — how many edges you touched and how many matched — while the
   ghost is still on screen to be adjusted. */
/* A full-bleed band, edge to edge, with one hairline along its top. This is the
   most important text on the screen and it was previously ink-over-terrain with
   a shadow, which is not a contrast strategy when the ground underneath can be
   any of five saturated fills. Same reasoning as the quest strip. */
#readout {
  pointer-events: none;
  padding: 7px 16px 8px;
  text-align: center;
  background: var(--bg);
  box-shadow: inset 0 1px 0 var(--line);
}

#read-value {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}

#read-points { margin-left: 10px; color: var(--ink-dim); }

#read-note {
  margin-top: 2px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-faint);
}

/* A placement that pays a tile back is the thing the whole game turns on, so it
   is the one state that gets full-strength ink. No colour: the terrain owns
   every saturated value on this page. */
#readout.is-perfect #read-note { color: var(--ink); }

#hint {
  pointer-events: none;
  margin: 0;
  padding: 0 16px calc(10px + var(--safe-b));
  text-align: center;
  font-size: 11.5px;
  color: var(--ink-faint);
  transition: opacity 240ms ease;
}
/* The hint fades rather than being removed: taking it out of flow would shift
   the dock up by its line height every time it changes. A class, not [hidden],
   because [hidden] is now unconditional above. */
#hint.is-off { opacity: 0; }

/* ── sheets ───────────────────────────────────────────────── */

/* The start sheet sits over a board the page has already laid for itself, and
   letting a little of it through is how you find out what the game is before
   pressing anything. Flat alpha, no blur — a backdrop-filter over a canvas is
   the single most expensive thing you can ask a mid-range phone to do, and this
   needs none of it to read. */

.sheet {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(20px + var(--safe-t)) 20px calc(20px + var(--safe-b));
  background: var(--bg);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.sheet[hidden] { display: none; }

#start { background: rgba(14, 15, 16, 0.9); }
:root[data-theme="light"] #start { background: rgba(242, 240, 234, 0.92); }

.sheet-body {
  width: 100%;
  max-width: 380px;
}

.sheet h1 {
  margin: 0 0 10px;
  font-size: clamp(26px, 7vw, 34px);
  font-weight: 600;
  letter-spacing: -0.035em;
}
.sheet h2 {
  margin: 0 0 14px;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.lede {
  margin: 0 0 22px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-dim);
}

/* Hairline-divided rows, which is what this page uses instead of boxes. */
.rules { margin-bottom: 22px; }
.rule {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 9px 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
}
.rule:last-child { border-bottom: 1px solid var(--line); }
.rule-k { color: var(--ink-faint); flex: none; }
.rule-v { color: var(--ink); text-align: right; }

.final { margin-bottom: 22px; }
#final-score {
  font-size: clamp(40px, 12vw, 54px);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
}
#final-line {
  margin-top: 7px;
  font-size: 13px;
  color: var(--ink-dim);
}

.actions { display: flex; flex-direction: column; gap: 9px; margin-top: 20px; }

.btn {
  appearance: none;
  padding: 13px 16px;
  min-height: 46px;
  background: var(--panel);
  border: 0;
  box-shadow: inset 0 0 0 1px var(--line), 0 3px 0 var(--press);
  border-radius: var(--radius);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  touch-action: manipulation;
}
.btn:active {
  transform: translateY(3px);
  box-shadow: inset 0 0 0 1px var(--line), 0 0 0 var(--press);
}
.btn-wide { width: 100%; }
.btn-quiet { background: none; color: var(--ink-dim); box-shadow: inset 0 0 0 1px var(--line); }
.btn-quiet:active { transform: none; background: var(--panel); color: var(--ink); }
.btn[disabled] { opacity: 0.45; cursor: default; transform: none; }

#submit-row {
  display: flex;
  gap: 8px;
  margin-top: 20px;
}
#name-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 12px 13px;
  min-height: 46px;
  background: var(--panel);
  border: 0;
  box-shadow: inset 0 0 0 1px var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  -webkit-user-select: text;
  user-select: text;
}
#name-input::placeholder { color: var(--ink-faint); }
#name-input:focus { outline: 0; box-shadow: inset 0 0 0 1px var(--line-strong); }
#submit-row .btn { flex: none; }

#submit-note, #board-note {
  margin: 10px 0 0;
  min-height: 16px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--ink-dim);
}

.foot {
  margin: 18px 0 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-faint);
}

.tabs { display: flex; gap: 4px; margin-bottom: 4px; }
.tab {
  padding: 8px 12px;
  min-height: 40px;
  background: none;
  border: 0;
  color: var(--ink-faint);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  border-radius: var(--radius);
  touch-action: manipulation;
}
.tab.is-on { color: var(--ink); box-shadow: inset 0 -1px 0 var(--ink); }

.row {
  display: grid;
  grid-template-columns: 26px 1fr auto;
  align-items: baseline;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
}
.row:last-child { border-bottom: 1px solid var(--line); }
.row-rank { color: var(--ink-faint); font-size: 12px; }
.row-name { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-score { color: var(--ink); font-weight: 600; }
.row.is-you .row-name { font-weight: 600; }
.row.is-you .row-rank { color: var(--ink); }

/* ── input models, by capability not by width ─────────────── */

@media (hover: hover) and (pointer: fine) {
  #topright button:hover, #home:hover { color: var(--ink); }
  #rot-ccw:hover, #rot-cw:hover { color: var(--ink); }
  .btn:hover { background: var(--panel-hi); }
  .tab:hover { color: var(--ink-dim); }
}

/* Coarse pointers get bigger targets and lose the desktop-only hint wording. */
@media (pointer: coarse) {
  #rot-ccw, #rot-cw { width: 52px; height: 52px; }
  #tile-now { width: 86px; height: 86px; }
}

/* Short landscape phones: the dock and the quest list together leave no board.
   Shrink the dock rather than hiding controls. */
@media (max-height: 460px) {
  #score { font-size: 26px; }
  #tile-now { width: 62px; height: 62px; }
  #rot-ccw, #rot-cw { width: 42px; height: 42px; }
  #hint { display: none; }
  #quests { width: min(200px, 38vw); }
}

/* The animation registry in main.js stays EMPTY under reduced motion rather
   than running with shorter durations — otherwise the rAF loop never sleeps and
   the page keeps a phone's GPU awake on a board that is not moving. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    transition-duration: 1ms !important;
  }
}
