/* ==========================================================================
   5126游戏厅 · Shell
   The app is one object: a handheld console. Everything inside is either the
   shell (matte plastic), the screen (dark glass), or a cartridge.
   ========================================================================== */

#app { height: 100%; }

/* 浏览器默认的 [hidden]{display:none} 会被任何一条作者样式里的 display 盖掉
   （.cart 是 display:flex，于是"藏起来的卡带"照样显示，分类看着就像没生效）。
   这一条是根因修法：不管哪个类，只要 hidden 就必须真的不显示。 */
[hidden] { display: none !important; }

.app {
  display: flex;
  flex-direction: column;
  /* 100vh first: dvh needs Safari 15.4, and without a fallback the whole flex
     column collapses to auto height on older iPhones. */
  height: 100vh;
  height: 100dvh;
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  position: relative;
}

.view {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* --- header --------------------------------------------------------------- */
.hdr {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--gap-2);
  min-height: 52px;
  padding: var(--gap-2) var(--gap-4);
  flex: 0 0 auto;
}
.hdr__side { display: flex; align-items: center; gap: var(--gap-2); min-width: 0; }
.hdr__side--end { justify-content: flex-end; }
.hdr__title {
  font-size: var(--step-1);
  font-weight: 700;
  letter-spacing: .02em;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 42vw;
}
/* The labelled back key: iOS puts a chevron and the destination here. */
.hdr__back {
  min-height: var(--tap-min);   /* 44pt: it is the only way out of a game */
  padding: 0 12px 0 8px;
  gap: 2px;
  font-size: var(--step-0);
}
.hdr__back svg { opacity: .85; }

/* The console's identity strip: an LED, the wordmark, and the link state. */
.hdr--console {
  grid-template-columns: auto 1fr auto;
  padding-bottom: var(--gap-1);
}
.brand { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.brand__name {
  font-size: var(--step-0);
  font-weight: 800;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}
.brand__sub {
  font-family: var(--font-data);
  font-size: 9px;
  letter-spacing: .18em;
  color: var(--ink-4);
  text-transform: uppercase;
}
.status-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-2);
  min-height: 32px;
  padding: 0 var(--gap-3);
  border-radius: 999px;
  background: var(--shell-1);
  font-size: var(--step--1);
  color: var(--ink-2);
  white-space: nowrap;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--r-md);
  color: var(--ink);
  transition: background-color var(--dur-ui) linear, transform var(--dur-press) var(--ease-out);
}
.icon-btn:active { background: var(--shell-2); transform: translateY(1px); }
.icon-btn[data-tone="muted"] { color: var(--ink-3); }

/* ==========================================================================
   Hall — the console face
   ========================================================================== */
.hall { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: var(--gap-2); }

/* 首页只有一个纵向滚动容器：卡带多了就往下滚。
   不做横向滑动——手指横滑会和系统的返回/切页手势打架，而且横滑的卡带总有
   一半藏在屏幕外，第一次打开的人根本不知道右边还有东西。
   主按钮和底部导航留在滚动区外面，用 flex 分高度，谁也不压谁。 */
.hall__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: var(--gap-3);
  padding-bottom: var(--gap-3);
}

/* The screen: dark glass in a bezel, live attract demo running inside. */
.cab {
  /* An explicit height: the glass holds only absolutely positioned children, so
     a content-based height would collapse it. It no longer has to shrink to fit
     a short screen — the hall scrolls — but it is capped so the cartridge grid
     always peeks above the fold and tells people there is more below. */
  flex: 0 0 auto;
  height: clamp(150px, 32vh, 300px);
  margin: 0 var(--gap-4);
  padding: 10px;
  border-radius: var(--r-xl);
  background: linear-gradient(180deg, var(--bezel-0), var(--bezel-1));
  box-shadow: var(--well), 0 18px 30px -26px #000;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}
.cab__screen {
  position: relative;
  flex: 1;
  min-height: 0;
  border-radius: 13px;
  overflow: hidden;
  background: radial-gradient(120% 90% at 50% 0%, #16130F 0%, var(--screen-0) 72%);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .8), inset 0 0 42px 10px rgba(0, 0, 0, .55);
}
.cab__canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Scanlines belong to a screen, and this is one. */
.cab__scan {
  position: absolute; inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    180deg,
    rgba(255, 255, 255, .028) 0 1px,
    transparent 1px 3px
  );
  mix-blend-mode: overlay;
}
.cab__marquee {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-3);
  padding: 8px 11px;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .72));
  pointer-events: none;
}
.cab__name {
  font-size: var(--step-2);
  font-weight: 800;
  letter-spacing: .01em;
  color: var(--on-screen);
  text-shadow: 0 2px 14px rgba(0, 0, 0, .9);
}
.cab__stat { text-align: right; }
.cab__stat b {
  display: block;
  font-size: var(--step-1);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--on-screen);
}
.cab__stat span { font-size: 10px; letter-spacing: .16em; color: var(--on-screen-3); text-transform: uppercase; }
.cab__stat b[data-empty="1"] { color: var(--on-screen-3); }

/* A real readout: recent runs, not decoration. */
.ticker {
  margin: 0 var(--gap-4);
  display: flex;
  align-items: center;
  gap: var(--gap-3);
  font-size: var(--step--1);
  color: var(--ink-3);
  min-height: 20px;
}
.ticker__label {
  font-family: var(--font-data);
  font-size: 9px;
  letter-spacing: .16em;
  color: var(--ink-4);
  border-radius: 4px;
  background: var(--shell-1);
  padding: 2px 6px;
  flex: 0 0 auto;
}
.ticker__text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  animation: tickerIn var(--dur-screen) var(--ease-out);
}
@keyframes tickerIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

/* The accent follows the cartridge when 设置 → 外观 · 自动 is on. */
:root[data-accent="game"] .tab[aria-selected="true"] { color: var(--accent); }
:root[data-accent="game"] .led[data-state="ready"] { background: var(--accent); }
:root[data-accent="game"] .hall__cats button[aria-selected="true"] { color: var(--accent); }
:root[data-accent="game"] .brand__name { color: var(--accent); }

/* Outdoor legibility: brighter ink, stronger hairlines, no decorative haze. */
:root[data-contrast="high"] {
  --ink: #FFFFFF;
  --ink-2: #E4DED6;
  --ink-3: #C3BAB0;
  --ink-4: #A79D93;
  --line: rgba(255, 255, 255, .26);
  --line-strong: rgba(255, 255, 255, .45);
}
:root[data-contrast="high"] .cab__scan { opacity: .35; }
:root[data-contrast="high"] .key { box-shadow: inset 0 0 0 1px var(--line-strong); }

/* Category switch above the shelf: 单机 / 联机. */
.hall__cats {
  flex: 0 0 auto;
  margin: 0 var(--gap-4);
}
/* 选择器带 .segmented 是为了压过下面 .segmented button 的 32px：
   这里两行文字（分类名 + 一句说明）本来就要 40px 高，32 会把字挤出去。 */
.hall__cats.segmented button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  /* 44pt：iOS 的最小触摸目标，42 差 2px 就不达标 */
  min-height: var(--tap-min);
  padding: 4px 10px;
  line-height: 1.15;
}
.hall__cats button small { font-size: 10px; font-weight: 500; opacity: .7; }

/* The rack: cartridges on a shelf. Two columns that wrap and scroll with the
   page — every cartridge is visible without a sideways swipe. */
.rack {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Rows take their content height and the container scrolls. Without this the
     rows compress to fit the box and cards collapse to a few pixels tall. */
  grid-auto-rows: max-content;
  align-content: start;
  align-items: stretch;
  gap: var(--gap-3);
  padding: 0 var(--gap-4);
}

.cart {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border-radius: 14px;
  background: var(--shell-1);
  box-shadow: var(--drop);
  overflow: hidden;
  text-align: left;
  transition: transform var(--dur-ui) var(--ease-out), box-shadow var(--dur-ui) var(--ease-out);
  position: relative;
}
.cart[aria-current="true"] {
  transform: translateY(-4px);
  box-shadow: 0 16px 26px -18px #000, 0 0 0 2px var(--tint);
  box-shadow: 0 16px 26px -18px #000, 0 0 0 2px color-mix(in oklab, var(--tint) 70%, transparent);
}
/* 按下要有反馈，而且只动 transform/阴影——不动布局，卡片不会互相挤。 */
.cart:active { transform: translateY(-1px); filter: brightness(1.08); }
.cart[aria-current="true"]:active { transform: translateY(-2px); }
.cart__label {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  height: 52px;
  padding: 8px 11px;
  /* Solid tint first: the cartridge label is the signature element and must not
     vanish where color-mix() is unsupported (Safari before 16.2). */
  background: var(--tint);
  background: linear-gradient(158deg, color-mix(in oklab, var(--tint) 94%, #fff 6%), color-mix(in oklab, var(--tint) 72%, #000 28%));
  color: #100D0A;
}
.cart__label .stamp { color: inherit; opacity: .7; }
.cart__glyph { width: 26px; height: 26px; opacity: .82; }
.cart__body { display: flex; flex-direction: column; flex: 1 1 auto; padding: 9px 10px 10px; min-width: 0; }
.cart__name { display: block; font-size: var(--step-0); font-weight: 700; letter-spacing: .01em; }
/* Two lines max: a longer blurb would make one card taller than its neighbour
   and break the grid rhythm. */
.cart__blurb {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 11px;
  color: var(--ink-3);
  margin-top: 2px;
  line-height: 1.35;
}
.cart__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: auto;
  padding-top: 7px;
  box-shadow: inset 0 1px 0 var(--line);
}
.cart__score { font-family: var(--font-data); font-size: 11px; color: var(--ink-2); }
/* The insert notch: only the cartridge in the slot shows it. */
.cart__notch {
  position: absolute;
  top: 0; left: 50%;
  transform: translate(-50%, -100%);
  width: 34px; height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--tint);
  transition: transform var(--dur-ui) var(--ease-out);
}
.cart[aria-current="true"] .cart__notch { transform: translate(-50%, 0); }

.hall__action {
  flex: 0 0 auto;
  /* Thumb zone: the primary key sits just above the tab bar. It is a flex
     sibling of the tab bar, never a fixed overlay, so the two can't collide
     however short the screen is. */
  padding: 0 var(--gap-4) var(--gap-2);
  display: flex;
  gap: var(--gap-3);
}
.hall__action-row { display: flex; flex: 1 1 auto; min-width: 0; gap: var(--gap-3); }
.hall__action .key { flex: 1 1 auto; min-width: 0; }

/* --- tab bar -------------------------------------------------------------- */
.tabbar {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  padding: 6px var(--gap-4) 8px;
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .35));
}
.tab {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: var(--tap-min);
  border-radius: var(--r-md);
  color: var(--ink-4);
  font-size: 10px;
  letter-spacing: .1em;
  transition: color var(--dur-ui) linear;
}
.tab[aria-selected="true"] { color: var(--amber); }
.tab:active { background: var(--shell-1); }

/* ==========================================================================
   Plain screens (stats, settings)
   ========================================================================== */
.scroll { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 32px; }
.pad-x { padding: 0 var(--gap-4); }

.lede {
  font-size: var(--step-1);
  color: var(--ink-2);
  line-height: 1.55;
  margin-bottom: var(--gap-5);
  max-width: 62ch;
}

.group { margin-bottom: var(--gap-5); }
.group__title {
  font-size: 11px;
  letter-spacing: var(--track-wider);
  text-transform: uppercase;
  color: var(--ink-4);
  margin: 0 var(--gap-1) var(--gap-2);
}
.list {
  border-radius: var(--r-lg);
  background: var(--shell-1);
  box-shadow: var(--drop);
  overflow: hidden;
}
.row {
  display: flex;
  align-items: center;
  gap: var(--gap-3);
  width: 100%;
  min-height: 52px;
  padding: var(--gap-3) var(--gap-4);
  text-align: left;
}
.row + .row { box-shadow: inset 0 1px 0 var(--line); }
.row__main { flex: 1; min-width: 0; }
.row__label { display: block; font-size: var(--step-0); font-weight: 600; }
.row__hint { display: block; font-size: var(--step--1); color: var(--ink-3); margin-top: 1px; }
.row__value { font-variant-numeric: tabular-nums; color: var(--ink-2); font-size: var(--step-0); }
.row[data-tappable]:active { background: var(--shell-2); }

.stat-hero {
  display: flex;
  align-items: baseline;
  gap: var(--gap-3);
  padding: var(--gap-4) var(--gap-1) var(--gap-5);
}
.stat-hero b {
  font-size: var(--step-4);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  line-height: 1;
}
.stat-hero span { color: var(--ink-3); font-size: var(--step--1); }

.empty {
  padding: var(--gap-6) var(--gap-4);
  color: var(--ink-3);
  font-size: var(--step-0);
  text-align: center;
}

/* --- platform-shaped controls (switch, segmented, slider) ----------------- */
.switch {
  position: relative;
  width: 51px;
  height: 31px;
  border-radius: 999px;
  background: var(--shell-3);
  flex: 0 0 auto;
  transition: background-color var(--dur-ui) var(--ease-in-out);
}
/* The switch keeps iOS's 51×31 look but gets a 44pt-tall touch target: the
   pseudo-element is part of the button, so it catches the tap. */
.switch::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -7px;
  bottom: -7px;
}
.switch::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 27px; height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .4);
  transition: transform var(--dur-ui) var(--ease-out);
}
.switch[aria-checked="true"] { background: var(--jade); }
.switch[aria-checked="true"]::after { transform: translateX(20px); }

.segmented {
  display: flex;
  gap: 2px;
  padding: 2px;
  border-radius: 10px;
  background: var(--shell-3);
}
.segmented button {
  flex: 1;
  min-height: 32px;
  padding: 0 10px;
  border-radius: 8px;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink-3);
  white-space: nowrap;
  transition: background-color var(--dur-ui) linear, color var(--dur-ui) linear;
}
.segmented { position: relative; }
.segmented button { position: relative; }
/* Same trick: 32pt visuals, 44pt hit area. Expanded only vertically so
   neighbouring segments keep their own taps. */
.segmented button::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -6px;
  bottom: -6px;
}
.segmented button[aria-pressed="true"] {
  background: var(--shell-1);
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .5);
}

/* Volume: a real range input, themed so it belongs to the palette. */
.avatar {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(160deg, var(--shell-2), var(--shell-1));
  box-shadow: inset 0 0 0 1px var(--line);
}
.avatar__initial { font-size: var(--step-1); font-weight: 700; color: var(--ink-2); }
.avatar[data-online="1"]::after {
  content: "";
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--jade);
  box-shadow: 0 0 0 2px var(--shell-0);
}
.badge {
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--accent);
  color: #1A0F05;
  font-size: 12px;
  font-weight: 700;
  display: grid;
  place-items: center;
}

/* Chat: bubbles on a scrollback, composer pinned above the tab bar. */
.chat { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.chat__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-2);
  padding: var(--gap-3) var(--gap-4);
}
.chat__bubble {
  max-width: 78%;
  padding: 9px 13px;
  border-radius: 14px;
  background: var(--shell-2);
  color: var(--ink);
  font-size: var(--step-0);
  align-self: flex-start;
  display: flex;
  flex-direction: column;
  gap: 2px;
  word-break: break-word;
}
.chat__bubble[data-mine="1"] {
  align-self: flex-end;
  background: color-mix(in oklab, var(--accent) 82%, #201C18);
  background: var(--accent);
  color: #160D04;
}
.chat__bubble time { font-size: 10px; opacity: .6; font-variant-numeric: tabular-nums; }
.chat__compose {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: var(--gap-2);
  padding: var(--gap-2) var(--gap-4) var(--gap-3);
  box-shadow: inset 0 1px 0 var(--line);
}
.chat__compose .field { flex: 1; min-height: 44px; max-height: 96px; }

.slider-row { display: flex; align-items: center; gap: var(--gap-3); width: 100%; }
.slider { flex: 1; display: flex; align-items: center; min-width: 120px; }
.slider input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  background: transparent;
}
.slider input[type="range"]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--accent), color-mix(in oklab, var(--accent) 30%, var(--shell-3)));
}
.slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 26px;
  height: 26px;
  margin-top: -10px;
  border-radius: 50%;
  background: #F7F4F0;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .55);
}
.slider input[type="range"]::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: var(--shell-3);
}
.slider input[type="range"]::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 50%;
  background: #F7F4F0;
}
.slider__value { color: var(--ink-2); font-size: var(--step-0); min-width: 3.2em; text-align: right; }

.field {
  width: 100%;
  min-height: var(--tap-min);
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--screen-0);
  box-shadow: var(--well);
  color: var(--ink);
  resize: none;
}
.field--code {
  font-family: var(--font-data);
  font-size: 11px;
  line-height: 1.45;
  word-break: break-all;
  letter-spacing: 0;
}
.field::placeholder { color: var(--ink-4); }

/* ==========================================================================
   Game screen
   ========================================================================== */
.game { flex: 1; min-height: 0; display: flex; flex-direction: column; }

/* 好友对战的比分牌：贴在画布右上角，不占游戏自己的 HUD 位置。 */
.race-chip {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 6px);
  right: 10px;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  padding: 5px 10px;
  border-radius: 10px;
  background: var(--chip-bg);
  box-shadow: inset 0 0 0 1px var(--line);
  backdrop-filter: blur(6px);
  pointer-events: none;
}
.race-chip b { font-family: var(--font-data); font-size: var(--step--1); color: var(--on-screen); }
.race-chip span { font-size: 10px; color: var(--on-screen-3); }
.race-chip[data-state="live"] span { color: var(--accent); }
.race-chip[data-state="over"] { box-shadow: inset 0 0 0 1px var(--amber); }

/* ==========================================================================
   对局里的人：头像条 + 操作流水（棋牌类共用，见 core/players.js）
   ========================================================================== */
.table-col { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 6px; width: 100%; }
.seats { display: flex; gap: var(--gap-2); flex: 0 0 auto; padding: 0 var(--gap-2); }
.seat {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  padding: 6px 10px;
  border-radius: 12px;
  background: var(--shell-1);
  box-shadow: inset 0 0 0 1px var(--line);
  transition: box-shadow var(--dur-ui) var(--ease-out), background-color var(--dur-ui) linear;
}
/* 轮到谁，谁的牌子亮起来：不用去读上面的小字。 */
.seat[data-active="1"] {
  background: var(--shell-2);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.seat__avatar {
  position: relative;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 800;
  font-size: 15px;
  color: var(--shell-0);
  background: linear-gradient(160deg, #C9BEA6, #A79D93);
}
.seat__avatar[data-me="1"] { background: linear-gradient(160deg, var(--accent), var(--amber-deep)); color: var(--amber-ink); }
/* 在线点：只有联机局才有（data-online 为空字符串时不画）。 */
.seat__avatar[data-online]::after {
  content: '';
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ink-4);
  box-shadow: 0 0 0 2px var(--shell-1);
}
.seat__avatar[data-online="1"]::after { background: var(--jade); }
.seat__pulse {
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--accent);
  animation: seatPulse 1.6s var(--ease-out) infinite;
}
@keyframes seatPulse { 0%, 100% { opacity: .25; } 50% { opacity: .85; } }
.seat__body { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.seat__name { display: flex; align-items: center; gap: 6px; font-size: var(--step--1); min-width: 0; }
.seat__name b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seat__tag {
  flex: 0 0 auto;
  font-style: normal;
  font-size: 9px;
  padding: 1px 5px;
  border-radius: 999px;
  background: var(--shell-3);
  color: var(--ink-3);
}
.seat__last { font-size: 11px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seat[data-active="1"] .seat__last { color: var(--ink-2); }

.feed { flex: 0 0 auto; display: flex; flex-direction: column; gap: 1px; padding: 4px var(--gap-3) 0; }
.feed__row { display: flex; gap: 6px; font-size: 11px; color: var(--ink-3); line-height: 1.55; min-width: 0; }
.feed__row b { flex: 0 0 auto; font-weight: 600; color: var(--ink-2); max-width: 5.5em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.feed__row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.feed__row[data-me="1"] b { color: var(--accent); }
.feed__row--idle { color: var(--ink-4); }

/* ==========================================================================
   局内消息与表情（core/chat.js）
   ========================================================================== */
.quick { display: flex; flex-wrap: wrap; gap: var(--gap-2); }
.quick__key { font-size: var(--step-0); padding: 0 14px; min-height: var(--tap-min); }
.emoji-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--gap-2); }
.emoji-grid__key {
  aspect-ratio: 1;
  min-height: var(--tap-min);
  border-radius: var(--r-md);
  background: var(--shell-1);
  box-shadow: inset 0 0 0 1px var(--line);
  display: grid;
  place-items: center;
}
.emoji-grid__key:active { background: var(--shell-2); }
.emoji-grid__glyph { font-size: 26px; line-height: 1; }
.emoji-grid__img { width: 70%; height: 70%; object-fit: contain; }

/* 飘在画面上方的小气泡：6 秒后自己消失，不参与布局。 */
.bubble {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 54px);
  left: 50%;
  transform: translate(-50%, -6px);
  z-index: 7;
  display: flex;
  align-items: baseline;
  gap: 7px;
  max-width: 82%;
  padding: 8px 14px;
  border-radius: 14px;
  background: var(--shell-2);
  box-shadow: 0 12px 24px -14px #000, inset 0 0 0 1px var(--line);
  opacity: 0;
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out);
  pointer-events: none;
}
.bubble[data-open="1"] { opacity: 1; transform: translate(-50%, 0); }
.bubble b { font-size: var(--step--1); color: var(--accent); flex: 0 0 auto; }
.bubble span { font-size: var(--step-0); color: var(--ink); overflow-wrap: anywhere; }
.bubble[data-mine="1"] b { color: var(--ink-3); }

/* 联机对局的回合倒计时：贴在画面右上，和比分牌同一位置家族。
   圆环用 conic-gradient 画剩余比例，颜色在最后 15 秒开始变。 */
.turnclock {
  position: absolute;
  top: calc(env(safe-area-inset-top, 0px) + 6px);
  right: 10px;
  z-index: 6;
  display: flex;
  align-items: baseline;
  gap: 2px;
  padding: 5px 10px 5px 12px;
  border-radius: 999px;
  background: var(--chip-bg);
  box-shadow: inset 0 0 0 1px var(--line);
  backdrop-filter: blur(6px);
  font-family: var(--font-data);
  pointer-events: none;
}
.turnclock[hidden] { display: none; }
.turnclock__ring {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  padding: 1.5px;
  background: conic-gradient(currentColor calc(var(--left, 1) * 360deg), transparent 0);
  -webkit-mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  color: var(--on-screen-3);
}
.turnclock b { font-size: var(--step-1); color: var(--on-screen); min-width: 1.4em; text-align: right; }
.turnclock span { font-size: 10px; color: var(--on-screen-3); }
.turnclock b { font-size: var(--step-1); color: var(--ink); min-width: 1.4em; text-align: right; }
.turnclock span { font-size: 10px; color: var(--ink-3); }
.turnclock[data-tone="warn"] .turnclock__ring { color: var(--amber); }
.turnclock[data-tone="warn"] b { color: var(--amber); }
.turnclock[data-tone="urgent"] .turnclock__ring { color: var(--red); }
.turnclock[data-tone="urgent"] b { color: var(--red); }

.hud {
  display: flex;
  align-items: center;
  gap: var(--gap-4);
  padding: 0 var(--gap-4) var(--gap-2);
  min-height: 34px;
}
.hud__item { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.hud__item span { font-size: 9px; letter-spacing: .18em; color: var(--ink-4); text-transform: uppercase; }
.hud__item b {
  font-size: var(--step-2);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
.hud__item--end { margin-left: auto; text-align: right; }
.hud__item[data-tone="amber"] b { color: var(--amber); }
.hud__item[data-tone="jade"] b { color: var(--jade); }
.hud__item[data-tone="red"] b { color: var(--red); }

.stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--gap-2) var(--gap-4);
  position: relative;
}

.pad {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-3);
  /* 键大了就要把上下内边距让出来，不然游戏画面被挤掉几像素（画布会顶到 HUD）。 */
  padding: var(--gap-1) var(--gap-4) var(--gap-2);
  min-height: 0;
}
.pad__cluster { display: flex; align-items: center; gap: var(--gap-2); min-width: 0; }
/* 左侧那组（暂停 / 菜单 / 提示…）允许换行：右边有四向键时，
   一行放不下两把 92px 的键 + 一副 62px 的方向键（手机只有 390 宽）。 */
.pad__cluster:not(.actions) { flex-wrap: wrap; max-width: 48%; }
.pad__cluster.actions { justify-content: flex-end; }
/* 游戏底部的操作键：加高一档、字更大、左右留更多 —— 拇指按下去要"有肉"。 */
.pad .key {
  min-height: var(--tap-key);
  min-width: var(--tap-key);
  padding: 0 14px;
  font-size: var(--step-1);
}
.pad .key svg { width: 22px; height: 22px; }
.pad .key[data-square] { min-width: var(--tap-key); }
.pad .key span { white-space: nowrap; }

/* D-pad and action buttons: physical, thumb-sized, no hover-only states. */
.dpad {
  display: grid;
  /* 两行四键：上行"上"，下行"左 下 右"。以前是个十字（中间塞了暂停），
     每个键只能摊到 52px；现在是四个 62px 的键，拇指按得准得多。 */
  grid-template-columns: repeat(3, 62px);
  grid-template-rows: repeat(2, 62px);
  gap: 6px;
  flex: 0 0 auto;
}
.dpad button {
  border-radius: 10px;
  background: linear-gradient(180deg, var(--shell-2), var(--shell-1));
  box-shadow: var(--bevel), 0 4px 10px -8px #000;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--dur-press) var(--ease-out), color var(--dur-press) linear;
}
.dpad button:active { transform: translateY(2px); color: var(--amber); }
.dpad__up { grid-area: 1 / 2; }
.dpad__left { grid-area: 2 / 1; }
.dpad__down { grid-area: 2 / 2; }
.dpad__right { grid-area: 2 / 3; }

.actions { display: flex; gap: var(--gap-3); }
.actions .key { min-width: 62px; }

/* --- overlays ------------------------------------------------------------- */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(8, 7, 6, .68);
  padding: var(--gap-3);
  padding-bottom: calc(var(--gap-3) + env(safe-area-inset-bottom));
  opacity: 0;
  transition: opacity var(--dur-ui) linear;
}
.overlay[data-open="true"] { opacity: 1; }
.overlay[data-align="center"] { align-items: center; }

.panel {
  width: 100%;
  max-width: 520px;
  max-height: 86dvh;
  overflow-y: auto;
  border-radius: var(--r-xl);
  background: var(--shell-1);
  box-shadow: 0 30px 60px -24px #000;
  padding: var(--gap-5) var(--gap-4) var(--gap-4);
  transform: translateY(18px) scale(.985);
  transition: transform var(--dur-screen) var(--ease-out);
}
.overlay[data-open="true"] .panel { transform: none; }
.panel--sheet { border-radius: var(--r-xl) var(--r-xl) var(--r-lg) var(--r-lg); }
.panel__grab {
  width: 38px; height: 4px;
  border-radius: 2px;
  background: var(--line-strong);
  margin: -12px auto var(--gap-4);
}
.panel__title { font-size: var(--step-2); font-weight: 800; letter-spacing: -.01em; }
.panel__text { color: var(--ink-2); font-size: var(--step-0); margin-top: var(--gap-2); line-height: 1.6; }
.panel__actions { display: flex; gap: var(--gap-3); margin-top: var(--gap-5); }
.panel__actions .key { flex: 1; }

/* the finish-line banner: result without a full-page interruption */
.banner {
  position: absolute;
  inset: auto var(--gap-4) var(--gap-4);
  z-index: 12;
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--banner-0), var(--banner-1));
  box-shadow: 0 20px 40px -22px #000, inset 0 1px 0 rgba(255, 255, 255, .06);
  /* Compact on purpose: it used to be a tall block that swallowed the bottom
     of the play area. Title and detail share a line, actions stay 44pt. */
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: var(--gap-2);
  overflow: hidden;   /* 让自动关闭的进度条贴着圆角走 */
  animation: bannerIn var(--dur-screen) var(--ease-out);
}
@keyframes bannerIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.banner__head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
.banner__title { font-size: var(--step-1); font-weight: 800; }
.banner__text { color: var(--ink-2); font-size: var(--step--1); }
.banner__actions { display: flex; gap: var(--gap-2); }
.banner__actions .key { flex: 1; }
/* 关掉它的按钮：结算横幅压在棋盘上时，用户第一反应就是找这个 ✕。
   44pt 的点击区但视觉很小，不抢按钮的位置。 */
.banner__close {
  margin-left: auto;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--ink-3);
  background: var(--shell-2);
}
.banner__close:active { color: var(--ink); background: var(--shell-3); }
/* 自动关闭的进度：贴着横幅上沿走一条线，一眼看出还剩多久。 */
.banner__timer {
  position: absolute;
  top: 0;
  left: 0;
  height: 2px;
  width: 100%;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--accent);
  transform-origin: left center;
  animation: bannerTimer linear forwards;
}
@keyframes bannerTimer { from { transform: scaleX(1); } to { transform: scaleX(0); } }
.banner[data-open="0"] { opacity: 0; transform: translateY(10px); transition: opacity 200ms linear, transform 200ms var(--ease-out); }

/* --- toasts --------------------------------------------------------------- */
/* Overlays, banners and toasts share one layer above the app. It covers the
   viewport but never eats a tap that belongs to the game underneath. */
#layer-root {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
}
#layer-root .overlay,
#layer-root .banner,
#layer-root .panel { pointer-events: auto; }
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(84px + env(safe-area-inset-bottom));
  transform: translate(-50%, 12px);
  max-width: min(440px, calc(100vw - 32px));
  padding: 11px 16px;
  border-radius: 999px;
  background: var(--toast-bg);
  box-shadow: 0 14px 30px -14px #000, inset 0 1px 0 rgba(255, 255, 255, .07);
  font-size: var(--step--1);
  color: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-ui) linear, transform var(--dur-screen) var(--ease-out);
  text-align: center;
}
.toast[data-open="true"] { opacity: 1; transform: translate(-50%, 0); }
.toast[data-tone="warn"] { color: var(--toast-warn-ink, #FFD9CF); }

/* ==========================================================================
   Link (peer-to-peer) register: an engraved technical manual.
   ========================================================================== */
.handshake { display: flex; flex-direction: column; gap: var(--gap-4); }
.step { display: flex; gap: var(--gap-3); }
.step__no {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--shell-3);
  font-family: var(--font-data);
  font-size: 12px;
  color: var(--ink-2);
}
.step[data-active="true"] .step__no { background: var(--amber); color: var(--amber-ink); font-weight: 700; }
.step__body { flex: 1; min-width: 0; }
.step__title { font-size: var(--step-0); font-weight: 700; }
.step__hint { font-size: var(--step--1); color: var(--ink-3); margin-top: 2px; line-height: 1.5; }

.codebox {
  margin-top: var(--gap-3);
  border-radius: var(--r-md);
  background: var(--screen-0);
  box-shadow: var(--well);
  padding: var(--gap-3);
}
.codebox__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-2);
  margin-bottom: var(--gap-2);
}
.codebox__title { font-size: 10px; letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--ink-4); }
.codebox__text {
  font-family: var(--font-data);
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-2);
  max-height: 132px;
  overflow-y: auto;
  word-break: break-all;
  user-select: text;
  -webkit-user-select: text;
}
.codebox .field { min-height: 96px; }

.link-state {
  display: flex;
  align-items: center;
  gap: var(--gap-3);
  padding: var(--gap-3) var(--gap-4);
  border-radius: var(--r-md);
  background: var(--shell-2);
  font-size: var(--step-0);
}
.link-state__text { flex: 1; min-width: 0; }
.link-state__text b { display: block; }
.link-state__text span { font-size: var(--step--1); color: var(--ink-3); }

.calm-note {
  display: flex;
  gap: var(--gap-3);
  padding: var(--gap-3) var(--gap-4);
  border-radius: var(--r-md);
  background: #221a12;
  background: color-mix(in oklab, var(--amber) 9%, var(--shell-1));
  color: var(--ink-2);
  font-size: var(--step--1);
  line-height: 1.55;
}
.calm-note svg { flex: 0 0 auto; margin-top: 2px; color: var(--amber); }

/* ==========================================================================
   Responsive posture
   ========================================================================== */
@media (max-height: 700px) {
  .cab { min-height: 108px; padding: 8px; }
  .cart__label { height: 44px; }
  .hall { gap: var(--gap-2); }
}

@media (min-width: 620px) {
  .app { max-width: 720px; margin: 0 auto; width: 100%; }
  .overlay { align-items: center; }
}

/* Landscape phone: the console lies on its side. The screen and the insert key
   stay on the left, and the shelf becomes a vertical list on the right. */
@media (orientation: landscape) and (max-height: 560px) {
  .hdr--console { padding: var(--gap-1) var(--gap-4); }
  .hall {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(210px, 0.95fr);
    /* minmax(0, 1fr): without the 0 the tall cartridge list stretches the row
       past the viewport and the rack's own scrolling never kicks in. */
    grid-template-rows: minmax(0, 1fr) auto auto auto;
    gap: var(--gap-2) var(--gap-3);
    align-items: stretch;
    justify-content: stretch;
    padding: 0 var(--gap-4) var(--gap-2);
  }
  /* The scroll container is a portrait layout device; lying on its side the
     console puts its children straight on the grid, and the rack becomes the
     one scrolling column on the right. */
  .hall__scroll { display: contents; }
  .cab { grid-column: 1; grid-row: 1; height: 100%; min-height: 96px; margin: 0; }
  .ticker { grid-column: 1; grid-row: 2; margin: 0; }
  .hall__cats { grid-column: 1; grid-row: 3; margin: 0; }
  .hall__action { grid-column: 1; grid-row: 4; padding: 0; }
  .rack {
    grid-column: 2;
    grid-row: 1 / span 4;
    grid-template-columns: minmax(0, 1fr);
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0 4px 0 2px;   /* room for the selected cartridge's ring */
  }
  .cart__label { height: 42px; }
}

/* Short landscape: the chrome gets out of the game's way. */
@media (orientation: landscape) and (max-height: 480px) {
  .hdr { min-height: 42px; padding: 2px var(--gap-4); }
  .hud { min-height: 26px; padding-bottom: 4px; }
  .hud__item b { font-size: var(--step-1); }
  .pad { min-height: 48px; padding: var(--gap-1) var(--gap-4) var(--gap-2); }
  .dpad { grid-template-columns: repeat(3, 40px); grid-template-rows: repeat(2, 40px); }
  .dpad button { border-radius: 8px; }
  .tabs, .tabbar { padding-bottom: 4px; }
}

/* 手机横屏是"高度"最紧张的方向：按键回到 44pt 下限，但绝不小于它。 */
@media (orientation: landscape) and (max-height: 560px) {
  .pad .key { min-height: var(--tap-min); min-width: var(--tap-min); padding: 0 12px; font-size: var(--step-0); }
  /* 矮屏（横屏）用不着 62px，但也不小于 44pt 的下限。 */
  .dpad { grid-template-columns: repeat(3, var(--tap-min)); grid-template-rows: repeat(2, var(--tap-min)); }
  .sudoku__keys .key { min-height: var(--tap-min); }
}
