/* style.css — Tratoh PWA, space-specific layer on top of the ported M3 system
 * (m3e.css = tokens + components + nav; lg.css = Liquid Glass / navpill).
 * Only what m3e.css does NOT already provide lives here: the fixed chat-app
 * viewport, inline-SVG icon sizing, message bubbles, the composer, empty states. */

/* ---- fixed-viewport app shell (override m3e's scrolling-document .app) ---- */
/* Actual rendered height of .navbar (10px pad + 32px indicator + 4px gap + label + 10px pad).
   It was 70px here and the bottom ~11px of the pinned pill sat under the bar. */
:root { --tr-navbar-h: 82px; }
html, body { height: 100%; }
body { margin: 0; }
.app {
  display: flex; flex-direction: column;
  height: 100dvh; max-width: 1080px; margin: 0 auto; padding-bottom: 0;
}
.appmain { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding: 0 16px; }
/* leave room for the fixed bottom navbar (compact only; rail is on the side >=600) */
@media (max-width: 599.98px) {
  .appmain { padding-bottom: calc(var(--tr-navbar-h) + env(safe-area-inset-bottom)); }
}
@media (min-width: 600px) { .app { padding-left: var(--rail-w, 80px); } }

/* the active view fills the main area; settings scrolls itself, chat scrolls its list */
.appmain > .view.active { flex: 1 1 auto; min-height: 0; }
#view-settings.active { display: block; overflow-y: auto; padding-bottom: 24px; }
#view-main.active, #view-chat.active, #view-extra.active { display: flex; flex-direction: column; }
/* 主線 holds two faces (回合 / 資訊); exactly one is un-hidden at a time and it fills the view */
.main-face { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.main-face.hidden { display: none; }
/* 資訊 does NOT scroll itself: map region (fixed) + boundary + content region (scrolls) */
.main-face[data-face="info"] { padding: 8px 0 0; }
#view-extra.active { align-items: center; justify-content: center; }
.extra-placeholder { max-width: 420px; }

/* ---- 資訊面 (read-only projection of the turn ledger) ---- */
/* map region: fixed size along the split axis (draggable); does NOT scroll — it pans/zooms itself */
.story-map-region { flex: 0 0 auto; height: var(--story-map-h, 38vh); min-height: 80px; overflow: hidden; }
.story-map-region.hidden { display: none; }
.story-map-region .story-map { height: 100%; margin: 0; box-sizing: border-box; justify-content: center; }
/* boundary: draggable grip + scrolling legend strip */
.story-boundary { flex: 0 0 auto; }
.story-boundary.hidden { display: none; }
.story-split { height: 12px; cursor: row-resize; display: flex; align-items: center; justify-content: center; touch-action: none; }
.story-split::before { content: ""; width: 40px; height: 4px; border-radius: 2px; background: var(--md-sys-color-outline-variant); }
.story-split:focus-visible { outline: none; }
.story-split:focus-visible::before { background: var(--md-sys-color-primary); }
.map-legend { display: flex; gap: 14px; overflow-x: auto; overflow-y: hidden; padding: 4px 2px; scrollbar-width: thin; }
.map-legend:empty { display: none; }
/* content region: fills the rest, content scrolls, pill pinned at the bottom by flex order */
.story-region { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.story-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.story-scroll.hidden { display: none; }
/* five tabs do not fit at 360px — let the pill scroll sideways rather than wrap or squash */
.story-tabs { flex: 0 0 auto; align-self: center; margin-top: 8px; max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.story-tabs::-webkit-scrollbar { display: none; }
.story-tabs button.hidden { display: none; }
.story-tab.hidden { display: none; }

/* Three layouts by viewport aspect ratio (CSS aspect-ratio = width/height):
 *   taller than 3:4 → map on top, the other four pages below (vertical grip)
 *   3:4 … 4:3       → one pill over all five pages, each full-bleed (no grip)
 *   wider than 4:3  → map on the left, the other four on the right (horizontal grip)
 * app.js mirrors these exact breakpoints in mapPinned()/wideInfo — keep the two in sync. */
@media (min-aspect-ratio: 3/4) and (max-aspect-ratio: 4/3) {
  .story-map-region { height: auto; flex: 1 1 auto; min-height: 0; }
}
@media (min-aspect-ratio: 4/3) {
  .main-face[data-face="info"] { flex-direction: row; align-items: stretch; }
  .story-map-region { height: auto; width: var(--story-map-w, 45%); min-width: 120px; }
  .story-boundary { display: flex; flex-direction: column; align-items: center; }
  .story-split { height: auto; flex: 1 1 auto; width: 12px; cursor: col-resize; }
  .story-split::before { width: 4px; height: 40px; }
  .map-legend { flex-direction: column; gap: 8px; overflow-x: hidden; overflow-y: auto; max-width: 168px; }
  .story-region { flex: 1 1 auto; min-width: 0; }
}
.story-empty { color: var(--md-sys-color-on-surface-variant); font-size: 14px; margin: 4px 0; }
.story-sec { border: 1px solid var(--md-sys-color-outline-variant); border-radius: 14px; background: var(--md-sys-color-surface-container-low); margin-bottom: 8px; overflow: hidden; }
.story-sec > summary { padding: 12px 14px; cursor: pointer; font-weight: 600; list-style: none; display: flex; align-items: center; gap: 8px; }
.story-sec > summary::-webkit-details-marker { display: none; }
.story-sec > summary::before { content: "▸"; color: var(--md-sys-color-on-surface-variant); transition: transform .15s; }
.story-sec[open] > summary::before { transform: rotate(90deg); }
.story-sec > .md { padding: 0 14px 12px; }
.todo-group { margin-bottom: 14px; }
.todo-label { font-size: .95rem; margin: 0 0 6px; color: var(--md-sys-color-on-surface); }
.todo-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.todo { display: flex; align-items: flex-start; gap: 9px; line-height: 1.5; }
.todo-box { flex: 0 0 auto; width: 18px; height: 18px; border-radius: 5px; border: 2px solid var(--md-sys-color-outline); margin-top: 2px; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; line-height: 1; }
.todo.done { color: var(--md-sys-color-on-surface-variant); }
.todo.done .todo-txt { text-decoration: line-through; }
.todo-box.checked { border-color: var(--md-sys-color-primary); background: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); }
.todo-done { margin-top: 8px; }
.todo-done > summary { cursor: pointer; color: var(--md-sys-color-on-surface-variant); font-size: 13px; list-style: none; }
.todo-done > summary::-webkit-details-marker { display: none; }
.todo-done > summary::before { content: "▸ "; }
.todo-done[open] > summary::before { content: "▾ "; }
.todo-done > .todo-list { margin-top: 8px; padding-left: 4px; }
.story-map { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px; padding: 28px 16px; border: 1px dashed var(--md-sys-color-outline-variant); border-radius: 14px; }
.story-map-ico { width: 44px; height: 44px; color: var(--md-sys-color-on-surface-variant); opacity: .7; }
.story-map .hint { max-width: 34ch; }

/* ---- pan/zoom grid map (rendered into #storyMapRegion when a map doc arrives) ---- */
.map-viewport { position: relative; width: 100%; height: 100%; overflow: hidden; touch-action: none; cursor: grab; background: var(--md-sys-color-surface-container-lowest); border-radius: 12px; }
.map-viewport:active { cursor: grabbing; }
.map-world { position: absolute; top: 0; left: 0; transform-origin: 0 0; will-change: transform; }
.map-overlay { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.map-cell { position: absolute; box-sizing: border-box; border: 1px solid var(--md-sys-color-outline-variant); display: flex; align-items: center; justify-content: center; gap: 2px; }
.map-cell.survey-unknown { background: repeating-linear-gradient(45deg, var(--md-sys-color-surface-container) 0 4px, transparent 4px 8px); opacity: .55; }
.map-subgrid { position: absolute; inset: 0; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.map-subcell { display: flex; align-items: center; justify-content: center; border: .5px solid color-mix(in srgb, var(--md-sys-color-outline-variant) 60%, transparent); }
.map-cell.player-loc, .map-subcell.player-loc { border: 2px solid red; z-index: 2; }
.map-obj { color: var(--md-sys-color-on-surface); font-size: 22px; position: relative; z-index: 1; }
.cell-coord { position: absolute; top: 1px; left: 2px; font-size: 8px; line-height: 1; color: var(--md-sys-color-on-surface-variant); opacity: .45; pointer-events: none; }
.region-center { position: absolute; width: 0; height: 0; }
svg.map-regions { position: absolute; top: 0; left: 0; overflow: visible; pointer-events: none; }
.region-outline { fill: color-mix(in srgb, var(--md-sys-color-primary) 8%, transparent); stroke: var(--md-sys-color-primary); stroke-width: 2; stroke-dasharray: 6 4; }
.map-label { position: absolute; transform: translate(-50%, -50%); font-size: 11px; line-height: 1.2; padding: 1px 5px; border-radius: 6px; white-space: nowrap; color: var(--md-sys-color-on-surface); background: color-mix(in srgb, var(--md-sys-color-surface) 85%, transparent); box-shadow: 0 1px 3px rgba(0, 0, 0, .25); }
.map-label.region { font-weight: 700; font-size: 12px; color: var(--md-sys-color-primary); background: color-mix(in srgb, var(--md-sys-color-surface) 72%, transparent); }
/* Object/building boxes carry a place's background and function, so they are prose, not a name.
   As one nowrap line they blew past the map width, covered neighbouring cells and got clipped at
   both ends. Give them a fixed 4:3 box that wraps, and scroll inside when the text still overruns.
   Region labels keep the one-line treatment — those really are just names. */
.map-label:not(.region) {
  white-space: normal;
  width: min(190px, 62vw);
  aspect-ratio: 4 / 3;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 6px 8px;
  border-radius: 8px;
  text-align: left;
  background: color-mix(in srgb, var(--md-sys-color-surface) 94%, transparent);
}
.map-legend .item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; font-size: 12px; color: var(--md-sys-color-on-surface-variant); }
.map-legend .item i { font-size: 15px; color: var(--md-sys-color-on-surface); }
.map-legend .item .swatch { width: 14px; height: 14px; border-radius: 4px; border: 1px solid var(--md-sys-color-outline-variant); flex: 0 0 auto; }
/* object/building names: tap the icon to toggle a box (collision-avoided, with a leader line) */
.map-obj[data-name] { cursor: pointer; }
.map-label .lbl-name { font-size: 11px; line-height: 1.25; }
.map-label .lbl-probes { font-size: 10px; line-height: 1.2; margin-top: 1px; color: var(--md-sys-color-on-surface-variant); }
svg.map-leaders { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
svg.map-leaders .leader { stroke: var(--md-sys-color-outline); stroke-width: 1.5; stroke-dasharray: 3 3; }

/* ---- appbar extras ---- */
.brand-ico { width: 24px; height: 24px; color: var(--md-sys-color-primary); flex: 0 0 auto; }

/* title = 寬廣 · XPC · {暱稱} {頭像} {玩家 id}. On a narrow screen only the name + avatar survive;
   the account id stays readable in 設定 → 裝置, so nothing is actually lost. */
.app-title { display: flex; align-items: center; gap: 6px; min-width: 0; }
.brand-prefix { flex: 0 0 auto; }
.brand-prefix::after { content: " ·"; }
.brand-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brand-avatar { flex: 0 0 auto; display: inline-flex; align-items: center; color: var(--md-sys-color-primary); font-size: .95rem; }
.brand-id { flex: 0 0 auto; font-size: .72rem; font-weight: 400; color: var(--md-sys-color-on-surface-variant); }
@media (max-width: 639.98px) {
  .brand-prefix, .brand-id { display: none; }
}

/* context switcher: the appbar's right-hand control. Its contents depend on the active view
   (主線 → 回合/資訊 per storyline, 聊天 → channels), so it is built in JS, not markup. */
.ctx-wrap { position: relative; flex: 0 0 auto; }
.ctx-wrap.hidden { display: none; }
.ctx-toggle {
  display: inline-flex; align-items: center; gap: 4px; max-width: 46vw;
  border: 1px solid var(--md-sys-color-outline-variant); background: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface-variant); border-radius: var(--r-full);
  padding: 6px 10px; font: inherit; font-size: .8rem; cursor: pointer;
}
.ctx-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctx-caret { width: 16px; height: 16px; }
.ctx-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60; min-width: 200px;
  display: flex; flex-direction: column; padding: 6px; border-radius: var(--r-md);
  background: var(--md-sys-color-surface-container-high); box-shadow: 0 6px 20px rgba(0, 0, 0, .3);
}
.ctx-menu.hidden { display: none; }
.ctx-item {
  border: none; background: none; color: var(--md-sys-color-on-surface); font: inherit; font-size: .9rem;
  text-align: left; padding: 10px 12px; border-radius: var(--r-sm); cursor: pointer; white-space: nowrap;
}
.ctx-item:hover { background: var(--md-sys-color-surface-container-highest); }
.ctx-item.on { background: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }

/* ---- inline-SVG icons (we don't ship Font Awesome) ---- */
.ico { width: 1.4em; height: 1.4em; display: inline-block; vertical-align: middle; fill: currentColor; flex: 0 0 auto; }
.ind .ico { width: 24px; height: 24px; }
.sec-head .ico { width: 22px; height: 22px; color: var(--md-sys-color-primary); }
.sec-head .sec-caret { color: var(--md-sys-color-on-surface-variant); }

/* ---- chat contact switcher (one 1:1 sub-channel per contact: M.A.C. + other players) ---- */
.contact-rail { flex: 0 0 auto; display: flex; gap: 8px; overflow-x: auto; padding: 10px 0 8px; scrollbar-width: none; }
.contact-rail::-webkit-scrollbar { display: none; }
.contact-rail:empty { display: none; }
.contact-chip { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; border-radius: var(--r-full); border: 1px solid var(--md-sys-color-outline-variant); background: var(--md-sys-color-surface-container); color: var(--md-sys-color-on-surface-variant); font: inherit; font-size: 14px; cursor: pointer; white-space: nowrap; }
.contact-chip.active { background: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); border-color: transparent; }
.contact-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--md-sys-color-error); flex: 0 0 auto; }

/* ---- chat ---- */
.chat { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.messages { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding: 12px 0 6px; }
.msg { max-width: 84%; padding: 9px 13px; border-radius: 16px; white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
.msg.gm { align-self: flex-start; background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); border-bottom-left-radius: 4px; }
.msg.me { align-self: flex-end; background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); border-bottom-right-radius: 4px; }
.msg .nm { display: block; font-size: 11px; opacity: .7; margin-bottom: 2px; }
/* ---- chat-style avatar+name row (turn channel: a msg-row wraps an avatar badge + the .msg bubble) ---- */
.msg-row { display: flex; gap: 8px; max-width: 92%; }
.msg-row.me { align-self: flex-end; flex-direction: row-reverse; }
.msg-row.gm { align-self: flex-start; }
.msg-row .msg { align-self: auto; max-width: 100%; }
.msg-avatar { flex: 0 0 auto; width: 30px; height: 30px; margin-bottom: 2px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 14px; }
.msg-row.gm .msg-avatar { background: var(--md-sys-color-surface-container-high); color: var(--md-sys-color-on-surface); }
.msg-row.me .msg-avatar { background: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); }
/* per-turn divider (persisted ␞ turn-end meta), rendered in-stream between turns */
.turn-divider { align-self: stretch; display: flex; align-items: center; gap: 12px; margin: 6px 2px; color: var(--md-sys-color-on-surface-variant); font-size: 12px; letter-spacing: .04em; }
.turn-divider::before, .turn-divider::after { content: ""; flex: 1 1 auto; border-top: 1px solid color-mix(in srgb, var(--md-sys-color-outline) 50%, transparent); }
.turn-divider span { flex: 0 0 auto; white-space: nowrap; }
/* per-message status note (own messages: 已送出 → 已讀 → 已排隊列) */
.msg .msg-status { display: block; margin-top: 3px; font-size: 10px; opacity: .6; text-align: right; }
/* ---- rendered markdown inside a message bubble ---- */
.md { white-space: normal; }
.md > :first-child { margin-top: 0; }
.md > :last-child { margin-bottom: 0; }
.md p { margin: 0 0 .5em; }
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 { margin: .5em 0 .3em; line-height: 1.25; font-weight: 700; }
.md h1 { font-size: 1.3em; } .md h2 { font-size: 1.2em; } .md h3 { font-size: 1.1em; }
.md h4, .md h5, .md h6 { font-size: 1em; }
.md ul, .md ol { margin: .3em 0; padding-left: 1.4em; }
.md li { margin: .15em 0; }
.md code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .9em; padding: .05em .35em; border-radius: 5px; background: color-mix(in srgb, currentColor 12%, transparent); }
.md .md-tablewrap { overflow-x: auto; margin: .5em 0; }
.md table { border-collapse: collapse; font-size: .92em; }
.md th, .md td { border: 1px solid var(--md-sys-color-outline-variant); padding: 5px 8px; text-align: left; vertical-align: top; }
.md th { background: color-mix(in srgb, currentColor 8%, transparent); font-weight: 600; white-space: nowrap; }
.md blockquote { margin: .4em 0; padding: .1em 0 .1em .8em; border-left: 3px solid color-mix(in srgb, currentColor 35%, transparent); opacity: .9; }
.md hr { border: 0; border-top: 1px solid color-mix(in srgb, currentColor 25%, transparent); margin: .6em 0; }
.md strong { font-weight: 700; }
.md a { color: inherit; text-decoration: underline; }
.chan-status { color: var(--md-sys-color-on-surface-variant); font-size: 13px; min-height: 18px; margin: 0 0 4px; }

/* ---- composer ---- */
.composer { flex: 0 0 auto; display: flex; gap: 8px; align-items: flex-end; padding: 8px 0 12px; }
/* The placeholder is two lines now (對{對象}說 / Enter 換行，Ctrl+Enter 送出). The auto-grow handler
   resets height to `auto` whenever the box is empty, so a one-row box would clip that second line —
   min-height is what actually keeps it readable, not the `rows` attribute. */
.composer textarea { flex: 1 1 auto; min-height: 76px; max-height: 120px; resize: none; }
.composer textarea::placeholder { font-size: .85em; line-height: 1.35; }
.composer .send { padding: 0; width: 48px; height: 48px; border-radius: var(--r-full); display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
.composer .send .ico { width: 22px; height: 22px; }
/* turnstate lock: visible but disabled, with a one-line reason under it (see chatLockInfo) */
.composer.locked { opacity: 0.55; }
.composer.locked textarea { cursor: not-allowed; }
.lock-note { margin: -6px 0 10px; }
.nick-confirm { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; margin-top: 8px; }

/* ---- empty / not-yet-joined state ---- */
.empty { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 8px; padding: 24px; }
.empty-ico { width: 48px; height: 48px; color: var(--md-sys-color-on-surface-variant); opacity: .7; }
.empty .t-title { margin: 6px 0 0; }
.empty .hint { max-width: 30ch; }

/* ---- composer "more actions" menu (left of the input, both channels) ---- */
.composer-menu-wrap { position: relative; flex: 0 0 auto; }
.composer-menu-toggle { padding: 0; width: 48px; height: 48px; border-radius: var(--r-full); display: inline-flex; align-items: center; justify-content: center; }
.composer-menu-toggle .ico { width: 22px; height: 22px; }
.composer-menu { position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 20; min-width: 190px; display: flex; flex-direction: column; gap: 2px; padding: 6px; border-radius: var(--r-lg); background: var(--md-sys-color-surface-container-high); box-shadow: 0 8px 24px -8px rgb(0 0 0 / .35); }
.composer-menu.hidden { display: none; }
.composer-menu-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: none; border-radius: 10px; background: none; color: var(--md-sys-color-on-surface); font: inherit; font-size: 14px; text-align: left; cursor: pointer; }
.composer-menu-item:hover, .composer-menu-item:focus-visible { background: var(--md-sys-color-surface-container-highest); }
.composer-menu-item .ico { width: 20px; height: 20px; flex: 0 0 auto; color: var(--md-sys-color-on-surface-variant); }

/* ---- settings: diagnostics output ---- */
.test-out { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.test-out:empty { display: none; }
.test-title { margin: 0 0 2px; font-weight: 600; color: var(--md-sys-color-on-surface-variant); font-size: .9rem; }
.test-row { display: flex; align-items: flex-start; gap: 8px; font-size: .88rem; line-height: 1.45; }
.test-mark { flex: 0 0 auto; width: 1.1em; font-weight: 700; }
.test-row.ok .test-mark { color: var(--md-sys-color-primary); }
.test-row.bad .test-mark { color: var(--md-sys-color-error); }
.test-row.bad { color: var(--md-sys-color-error); }

/* ---- dice ---- */
/* The self-serve dice panel was removed in v2.0.0: parameters are the GM's, the roll is the
   player's. The only remaining affordance is the one-tap button rendered from a [[roll:…]] token. */
.roll-cta { margin-top: 8px; display: inline-flex; align-items: center; gap: 8px; }
.roll-cta .ico { width: 18px; height: 18px; }

/* ---- ready gate: multiplayer tally + superseded-ask/counted-confirm hiding ---- */
.ready-collapsed { display: none !important; }
.ready-tally { margin-top: 8px; font-size: 13px; }
.ready-tally .rt-h { font-weight: 600; margin-top: 6px; color: var(--md-sys-color-on-surface-variant); }
.ready-tally .rt-done { margin-top: 2px; }
.ready-tally .rt-pend { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.ready-tally .rt-name { min-width: 3em; }
.ready-tally .rt-wait { color: var(--md-sys-color-on-surface-variant); font-size: 12px; }

/* ---- update-available banner (service-worker update prompt) ---- */
.update-bar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--tr-navbar-h) + env(safe-area-inset-bottom) + 12px); z-index: 60;
  display: flex; align-items: center; gap: 14px; max-width: 92vw;
  padding: 8px 8px 8px 18px; border-radius: var(--r-full);
  background: var(--md-sys-color-on-surface); color: var(--md-sys-color-surface);
  box-shadow: 0 8px 28px -10px rgb(0 0 0 / .5);
}
.update-bar .m3-btn { padding: 8px 18px; }
@media (min-width: 600px) { .update-bar { bottom: 24px; } }

/* ---- settings odds and ends ---- */
.status { color: var(--md-sys-color-on-surface-variant); font-size: 13px; min-height: 18px; margin: 8px 0 0; }
.dev summary { cursor: pointer; }
.dev .field { margin-top: 10px; margin-bottom: 10px; }
.seed-hex { color: var(--md-sys-color-on-surface-variant); font-weight: 600; }

/* ---- identity / avatar picker (settings) ---- */
.avatar-preview { display: flex; align-items: center; gap: 10px; margin: 10px 0 14px; }
.avatar-preview-badge { width: 44px; height: 44px; font-size: 20px; }
.avatar-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(48px, 1fr)); gap: 8px; margin-top: 12px; }
.avatar-opt {
  width: 48px; height: 48px; border-radius: 14px; font-size: 19px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--md-sys-color-outline-variant); background: var(--md-sys-color-surface-container);
  color: var(--md-sys-color-on-surface-variant);
}
.avatar-opt[aria-pressed="true"] { background: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); border-color: transparent; }

/* ---- 電子紙（E-ink）模式 ----
 * 一個把整台 app 換成翻頁機的顯示模式。三層：尺寸（zoom + 字級）、顏色（SVG 量化濾鏡）、
 * 操作（手勢全關、捲動全關、只剩頁/分頁）。所有規則都掛在 html[data-eink="on"] 之下，
 * 關掉就一條也不生效。--eink-ui / --eink-font 由 eink.js 寫在 :root 的 style 上。 */

/* 尺寸。zoom 會把整份版面（含 100dvh 算出來的高度）一起放大，所以 .app 的高度要先除回去，
   否則 1x 以上會撐出一條捲動、1x 以下會在底部留白。 */
html[data-eink="on"] { zoom: var(--eink-ui, 1); font-size: calc(100% * var(--eink-font, 1)); }
html[data-eink="on"] .app { height: calc(100dvh / var(--eink-ui, 1)); }

/* 顏色。灰階＝去彩度後壓成 16 階；其餘為每通道等階量化（2/3/4/5/6 階）。
   濾鏡掛在 root 上：root 本來就是 fixed 定位的容器塊，不會動到既有的固定元件。 */
html[data-eink="on"][data-eink-color="grey"] { filter: url(#eink-grey); }
html[data-eink="on"][data-eink-color="c16"] { filter: url(#eink-c16); }
html[data-eink="on"][data-eink-color="c32"] { filter: url(#eink-c32); }
html[data-eink="on"][data-eink-color="c64"] { filter: url(#eink-c64); }
html[data-eink="on"][data-eink-color="c128"] { filter: url(#eink-c128); }
html[data-eink="on"][data-eink-color="c256"] { filter: url(#eink-c256); }

/* 動畫與模糊：電子紙重繪慢、殘影重，兩者都只會留下髒畫面。 */
html[data-eink="on"] *, html[data-eink="on"] *::before, html[data-eink="on"] *::after {
  transition: none !important; animation: none !important;
  backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
}
html[data-eink="on"] #fieldBg { display: none !important; }

/* 捲動全關。容器仍然可以被程式設定 scrollTop（翻頁就是這樣做的），
   使用者自己捲不動——這正是「no scrolling」要的：頁是被翻的，不是被拖的。 */
html[data-eink="on"] #view-settings.active,
html[data-eink="on"] .story-scroll,
html[data-eink="on"] .messages { overflow: hidden !important; overscroll-behavior: none; touch-action: manipulation; }
/* 訊息不滿一區時整群貼在下緣（最新的那則在最下面）。用 margin-top:auto 而不是
   justify-content:flex-end——後者在內容超出時會把溢出推到上方且捲不到。 */
html[data-eink="on"] .messages > *:first-child { margin-top: auto; }
/* 橫向捲動的細條改成換行，免得有東西被藏在捲不到的地方。 */
html[data-eink="on"] .map-legend,
html[data-eink="on"] .story-tabs,
html[data-eink="on"] .contact-rail { overflow: visible !important; flex-wrap: wrap; max-width: none; }
/* 手勢關掉之後，這兩個地方不再是可拖的東西，游標也別再騙人。 */
html[data-eink="on"] .map-viewport { cursor: default; touch-action: manipulation; }
html[data-eink="on"] .story-split { cursor: default; }

/* 訊息分片：一則高過一整區的訊息會就地拆成數則。續片不重覆掛名字，
   並與前一片收窄間距，讓它讀起來仍是同一則。 */
html[data-eink="on"] .msg-frag-head { border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; }
html[data-eink="on"] .msg-frag { border-top-left-radius: 4px; border-top-right-radius: 4px; }
html[data-eink="on"] .msg-frag .nm { display: none; }
/* 不屬於目前這一區的訊息：用 visibility 藏起來而不是 display:none——版面高度必須維持不變，
   否則區界會在翻頁的當下自己跑掉。 */
html[data-eink="on"] .messages > .eink-off { visibility: hidden; }

/* 按鈕列：畫面上的方向鍵，與實體方向鍵一對一。壓在導覽列上方，不重疊。 */
.eink-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 55;
  display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap;
  padding: 6px 10px calc(6px + env(safe-area-inset-bottom));
  background: var(--md-sys-color-surface-container-high);
  border-top: 2px solid var(--md-sys-color-outline);
}
.eink-bar.hidden { display: none; }
.eink-grp { display: flex; gap: 6px; }
.eink-grp.hidden { display: none; }
.eink-btn {
  min-width: 52px; min-height: 44px; padding: 4px 10px; cursor: pointer;
  font-size: 1.15rem; font-weight: 700; line-height: 1;
  border: 2px solid var(--md-sys-color-outline); border-radius: 10px;
  background: var(--md-sys-color-surface); color: var(--md-sys-color-on-surface);
}
.eink-btn:active { background: var(--md-sys-color-secondary-container); }
.eink-btn.hidden { display: none; }
.eink-btn[data-eink-act="focus"] { font-size: .85rem; min-width: 60px; }
.eink-ind { font-size: .8rem; font-weight: 600; color: var(--md-sys-color-on-surface-variant); }
/* 讓內容區讓出按鈕列的高度（窄螢幕上還要再讓出底部導覽列）。 */
html[data-eink="on"] .appmain { padding-bottom: var(--eink-bar-h, 62px); }
@media (max-width: 599.98px) {
  html[data-eink="on"] .eink-bar { bottom: calc(var(--tr-navbar-h) + env(safe-area-inset-bottom)); }
  html[data-eink="on"] .appmain { padding-bottom: calc(var(--tr-navbar-h) + env(safe-area-inset-bottom) + var(--eink-bar-h, 62px)); }
}

/* 刷新（清殘影）：整面黑 0.5 秒 ＋ 整面白 0.1 秒，跑一或兩組。蓋在所有東西之上（含按鈕列與
   導覽列），但 pointer-events:none——閃的那一秒多裡使用者點下去仍然算數。
   inset:0 是相對於 initial containing block，會跟著 root 的 zoom 一起換算，所以各種 UI 倍率
   下都剛好蓋滿一整面。 */
.eink-flash { position: fixed; inset: 0; z-index: 2147483647; pointer-events: none; display: none; background: #000; }

/* 色彩有六個選項，窄螢幕上光靠「未選中只留數字」還差幾個 px 才收得進一行——把左右內距
   收窄一點補上。其餘的 seg 選項少，維持原本的手感。 */
@media (max-width: 520px) { #einkColorSeg button { padding: 9px 10px; } }
/* 這三組的標籤是「短字＋<span>其餘</span>」拼出來的，是一個詞不是兩個東西——
   所以要把 .seg button 給圖示用的 8px gap 收掉，否則會讀成「無 刷新」。 */
#einkColorSeg button, #einkRefreshSeg button, #einkFlashSeg button { gap: 0; }

/* 設定頁的電子紙區塊 */
.eink-scale-row { display: flex; align-items: center; gap: 12px; }
.eink-scale-row input[type="range"] { flex: 1 1 auto; min-width: 0; }
.eink-scale-out { min-width: 56px; text-align: right; font-weight: 700; color: var(--md-sys-color-on-surface); }
