@font-face {
  font-family: "Good Hand Ranks";
  src: url("fonts/roboto-condensed/latin-600-eaf782735c.woff2") format("woff2");
  font-style: normal;
  font-weight: 600;
  font-display: swap;
}
/* Brush lettering for the table's moments, cut down to the characters drawn (fonts/ink).
   Block rather than swap: a seal in a fallback face reads worse than a moment's wait. */
@font-face {
  font-family: "Good Hand Ink";
  src: url("fonts/ink/ma-shan-zheng-f5ea806eb6.woff2") format("woff2");
  font-display: block;
}
@font-face {
  font-family: "Good Hand Ink TC";
  src: url("fonts/ink/yuji-syuku-f3895e4f74.woff2") format("woff2");
  font-display: block;
}

/* Card sizes are registered lengths: the stylesheet derives them from the viewport, and the
   client still reads them back in pixels. */
@property --cw { syntax: '<length>'; inherits: true; initial-value: 84px; }
@property --ch { syntax: '<length>'; inherits: true; initial-value: 120px; }
:root {
  --ch: clamp(86px, 24vh, 120px); /* a short window gets smaller cards, never a cut hand */
  --cw: calc(var(--ch) * 0.7);
  --paper: #f2ebc8;
  --paper-2: #e8e0b8;
  --paper-3: #ddd4a8;
  --glow: #f8f2d8; /* the pool of light in the middle of the table */
  --turn-light-core: rgba(255, 250, 222, 0.84);
  --turn-light-edge: rgba(255, 239, 177, 0.46);
  --turn-edge-near: rgba(245, 185, 60, 0.58);
  --turn-edge-light: rgba(255, 248, 205, 0.85);
  --edge: #e2d8ae;
  --ink: #1f3b8f;
  --ink-2: #2b4ba8;
  --ink-soft: rgba(31, 59, 143, 0.8); /* the faintest text: 4.5:1 or better on every paper */
  --ink-faint: rgba(31, 59, 143, 0.38); /* strokes and placeholders' outlines only, never text */
  --line: rgba(31, 59, 143, 0.22);
  --panel: #fbf8ef; /* sheets, dialogs, pills */
  --knob: #fbf8ef;
  --scrim: rgba(31, 59, 143, 0.28);
  --alert: #c8102e; /* warnings on the table; card faces keep --red */
  --accent: #1f3b8f; /* the ring round a chosen card */
  --sky: #9db4e0;
  /* The table's moments: a seal's red and what is printed on it, the gold of sparks, the
     edges of the table when an opponent is down to one card, and brush lettering. */
  --seal: #c8102e;
  --on-seal: #fdf6e6;
  --gold: #e0a21c;
  --dim-edge: rgba(24, 28, 64, 0.26);
  --ink-font: "Good Hand Ink", "Kaiti SC", STKaiti, KaiTi, serif;
  /* what is printed on the cards, the same by day and by night */
  --card: #fbf8ef;
  --card-2: #f1ead6;
  --red: #c8102e;
  --black: #1b1b1b;
  --navy: #1f3b8f;
  --back: #1f3b8f;
  --back-2: #2a4ba6;
  --back-3: #17307a;
  --sans: -apple-system, "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Noto Sans CJK SC", "Microsoft YaHei", sans-serif;
  --serif: "Bodoni 72", Didot, "Playfair Display", "Noto Serif", "Songti SC", Georgia, "Times New Roman", serif;
  /* the index on a card: tall and narrow, as on a printed deck */
  --rank-font: "Good Hand Ranks", "Arial Narrow", Arial, sans-serif;
  --card-font: var(--rank-font);
  /* Shared component contracts. Layout rules own geometry, not these recipes. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --text-micro: 11px; /* the floor: tags, badges, counts in tight cells */
  --text-caption: 13px; /* notes, hints and any sentence */
  --text-label: 14px;
  --text-body: 16px;
  --text-heading: 18px;
  --radius-small: 6px;
  --radius-control: 8px;
  --radius-panel: 12px;
  --radius-dialog: 20px;
  --control-height: 44px;
  --control-compact-height: 32px;
  --disabled-opacity: 0.4;
  --busy-opacity: 0.8;
  --motion-control: 0.12s;
  --motion-panel: 0.2s;
  --shadow-control: 0 2px 5px rgba(12, 20, 42, 0.12);
  --shadow-panel: 0 12px 40px rgba(31, 59, 143, 0.22), 0 0 0 1px var(--line);
  --shadow-dialog: 0 20px 60px rgba(31, 59, 143, 0.35);
}
/* Local font stacks keep every choice available without a font download. */
[data-font="modern"] { --ui-font: system-ui, var(--sans); }
[data-font="book"] { --ui-font: Georgia, "Songti SC", "Songti TC", "Noto Serif CJK SC", serif; }
[data-font="rounded"] { --ui-font: ui-rounded, "Hiragino Maru Gothic ProN", "Trebuchet MS", var(--sans); }
/* A coordinated printed deck: the same illustration and rank geometry in every finish.
   Settings previews reset their variables locally, independently of the active deck. */
[data-card-style] {
  --red: #b62e42; --black: #242c38; --navy: #304a67;
  --diamond: var(--red); --club: var(--black);
  --card: #fffefb; --card-2: #f7f5ef;
  --card-surface: linear-gradient(170deg, #fffefb, #faf9f4);
  --card-radius: 0.055;
  --card-font: var(--rank-font);
  --card-rim: rgba(255, 255, 255, 0.8);
  --back: #304a67; --back-2: #385574; --back-3: #263e59;
  --back-ink: #e8dfc7;
  --back-pattern: repeating-linear-gradient(45deg, rgba(246, 241, 222, 0.1) 0 1px, transparent 1px 5px), repeating-linear-gradient(-45deg, rgba(246, 241, 222, 0.1) 0 1px, transparent 1px 5px);
}
[data-card-style="ivory"] {
  --card: #fff8e9; --card-2: #f2e8d1;
  --card-surface: linear-gradient(170deg, #fff9ed, #f5ebd7);
  --red: #a3303b; --black: #403b32;
  --back: #496357; --back-2: #587466; --back-3: #354e43;
}
[data-card-style="fourcolor"] { --diamond: #255da1; --club: #24714f; }
[data-card-style="quiet"], [data-card-style="soft"] {
  --card: #fcfcfb; --card-2: #f6f6f4; --card-surface: var(--card);
  --card-radius: 0.04;
  --black: #252c32; --red: #b52b3a; --navy: var(--black);
  --back: #213954; --back-ink: #f4f0e4;
  --deck-accent: #29445e; --deck-edge: rgba(42, 48, 54, 0.25);
}
[data-card-style="soft"] {
  --card: #fcfaf5; --card-2: #f4f0e8;
  --card-font: ui-rounded, "Hiragino Maru Gothic ProN", "Trebuchet MS", var(--sans);
  --card-radius: 0.055;
  --black: #343735; --red: #b4463c;
  --back: #82947f; --back-ink: #fcf9ee;
  --deck-accent: #637a61; --deck-edge: rgba(75, 69, 58, 0.27);
}
/* Where the browser knows its own toolbars, size by the viewport that is actually visible. */
@supports (height: 1dvh) { :root { --ch: clamp(86px, 24dvh, 120px); } }
html[lang="zh-Hant"] {
  --sans: -apple-system, "PingFang TC", "Helvetica Neue", Helvetica, Arial, "Noto Sans CJK TC", "Microsoft JhengHei", sans-serif;
  --ink-font: "Good Hand Ink TC", "Kaiti TC", BiauKai, DFKai-SB, serif;
}
/* Night: navy felt, cream ink. Follows the system unless the settings sheet says otherwise. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #1b2342;
    --paper-2: #161d37;
    --paper-3: #2a3560;
    --glow: #26305c;
    --turn-light-core: rgba(198, 170, 105, 0.22);
    --turn-light-edge: rgba(198, 170, 105, 0.1);
    --turn-edge-near: rgba(236, 188, 102, 0.42);
    --turn-edge-light: rgba(255, 234, 183, 0.5);
    --edge: #0d1226;
    --ink: #ece4c6;
    --ink-2: #fff8dc;
    --ink-soft: rgba(236, 228, 198, 0.78);
    --ink-faint: rgba(236, 228, 198, 0.4);
    --line: rgba(236, 228, 198, 0.22);
    --panel: #232c50;
    --knob: #1b2342;
    --scrim: rgba(5, 8, 20, 0.55);
    --alert: #ff6b7a;
    --accent: #8db4ff;
    --sky: #6f8ac9;
    --seal: #e0344c;
    --gold: #f0b53a;
    --dim-edge: rgba(0, 2, 12, 0.5);
  }
}
:root[data-theme="dark"] {
  --paper: #1b2342;
  --paper-2: #161d37;
  --paper-3: #2a3560;
  --glow: #26305c;
  --turn-light-core: rgba(198, 170, 105, 0.22);
  --turn-light-edge: rgba(198, 170, 105, 0.1);
  --turn-edge-near: rgba(236, 188, 102, 0.42);
  --turn-edge-light: rgba(255, 234, 183, 0.5);
  --edge: #0d1226;
  --ink: #ece4c6;
  --ink-2: #fff8dc;
  --ink-soft: rgba(236, 228, 198, 0.78);
  --ink-faint: rgba(236, 228, 198, 0.4);
  --line: rgba(236, 228, 198, 0.22);
  --panel: #232c50;
  --knob: #1b2342;
  --scrim: rgba(5, 8, 20, 0.55);
  --alert: #ff6b7a;
  --accent: #8db4ff;
  --sky: #6f8ac9;
  --seal: #e0344c;
  --gold: #f0b53a;
  --dim-edge: rgba(0, 2, 12, 0.5);
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
  font-family: var(--ui-font, var(--sans));
  background: var(--paper);
  /* a soft pool of light at the centre of the table, darker towards the edges */
  background-image: radial-gradient(130% 110% at 50% 42%, var(--glow) 0%, var(--paper) 45%, var(--edge) 100%);
  color: var(--ink);
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
}
body.in-room { overflow: hidden; position: fixed; width: 100%; height: min(100dvh, var(--app-height, 100dvh)); } /* not the height behind a phone's toolbar */
#app { height: 100%; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
svg.ic { width: 22px; height: 22px; display: block; }

/* ---------- controls ---------- */
button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  font: inherit;
  font-weight: 600;
  line-height: 1.25;
  padding: 10px 18px;
  border-radius: var(--radius-control);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--motion-control), border-color var(--motion-control), color var(--motion-control), transform var(--motion-control), opacity var(--motion-control);
}
button:hover:not(:disabled) { border-color: var(--ink); }
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { opacity: var(--disabled-opacity); cursor: default; }
button[aria-busy="true"] { opacity: var(--busy-opacity); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
button.primary {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
  font-weight: 700;
  padding: 11px 22px;
  box-shadow: var(--shadow-control);
}
button.primary:hover:not(:disabled) { background: var(--ink-2); border-color: var(--ink-2); }
button.primary svg.ic { width: 18px; height: 18px; }
button.text { border-color: transparent; padding: 10px 12px; }
button.text:hover:not(:disabled) { border-color: transparent; color: var(--ink-2); text-decoration: underline; text-underline-offset: 4px; }
button.ghost { border-color: transparent; color: var(--ink-soft); }
button.small { min-height: var(--control-compact-height); padding: 5px 12px; font-size: var(--text-caption); }
button.icon {
  width: var(--control-compact-height);
  min-height: var(--control-compact-height);
  padding: 5px;
  border-color: transparent;
  color: var(--ink);
  line-height: 0;
}
button.icon:hover:not(:disabled) { border-color: transparent; background: rgba(31, 59, 143, 0.08); }
button.icon.on { background: var(--ink); color: var(--paper); }
input, select {
  font: inherit;
  padding: 10px 2px;
  border: 0;
  border-bottom: 1.5px solid var(--line);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  outline: none;
}
input { width: 100%; }
input::placeholder { color: var(--ink-soft); opacity: 0.85; }
input:focus, select:focus { border-bottom-color: var(--ink); }
select { padding: 4px 6px; font-weight: 600; }
select option { color: #111; }

.segmented { display: inline-flex; border: 0; border-radius: var(--radius-control); padding: 3px; gap: 2px; background: color-mix(in srgb, var(--ink) 6%, transparent); }
.segmented button { min-height: var(--control-compact-height); border: 0; border-radius: var(--radius-small); padding: 7px 14px; font-size: 13px; color: var(--ink-soft); }
.segmented button:hover:not(:disabled, .on) { color: var(--ink); }
.segmented button.on { background: var(--ink); color: var(--paper); }
button.switch {
  width: var(--control-height);
  height: var(--control-height);
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  position: relative;
}
button.switch::before { content: ""; position: absolute; inset: 11px 2px; border-radius: 12px; background: color-mix(in srgb, var(--ink) 18%, transparent); transition: background 0.15s; }
button.switch .knob { position: absolute; top: 14px; left: 5px; width: 16px; height: 16px; border-radius: 50%; background: var(--ink-soft); transition: transform 0.15s, background 0.15s; }
button.switch.on::before { background: var(--ink); }
button.switch.on .knob { transform: translateX(18px); background: var(--knob); }

/* ---------- wordmark ---------- */
.brand { display: flex; flex-direction: column; line-height: 1; color: var(--ink); user-select: none; }
.brand .mark { font-size: 26px; font-weight: 900; letter-spacing: 0.02em; }
.brand .sub { font-size: 8px; font-weight: 700; letter-spacing: 0.32em; margin-top: 3px; }

/* ---------- home: your hand is the menu ---------- */
/* Your seat and the table's options along the top; 斗地主 over the panel for the raised card;
   the ways to play as a hand of five cards, held at the bottom edge as at the table. */
.home.hand-home {
  --mc-w: clamp(108px, min(10.5vw, 18vh), 150px);
  --mc-h: calc(var(--mc-w) * 1.42);
  min-height: 100%;
  display: flex;
  flex-direction: column;
  padding: max(14px, env(safe-area-inset-top, 0px)) max(28px, env(safe-area-inset-right, 0px)) 0 max(28px, env(safe-area-inset-left, 0px));
  overflow: clip; /* the hand's lower edge runs off the page */
}
.home-bar { position: relative; z-index: 3; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; width: 100%; max-width: 1080px; margin: 0 auto; }
.home-brand { flex-direction: row; align-items: baseline; gap: 10px; }
.home-brand .mark { font-size: 28px; }
.home-brand .sub { margin: 0; font-size: 9px; letter-spacing: 0.3em; }
.home-seat { display: flex; align-items: center; gap: 2px; margin-left: auto; min-width: 0; }
.home-name { min-width: 0; }
.home-name input { width: 136px; min-height: 44px; padding: 6px 2px; font-size: 16px; font-weight: 700; }
.home-bar .segmented button { min-height: 44px; padding: 8px 12px; }
.home-bar .langs { background: transparent; }
.home-bar .langs button.on { background: color-mix(in srgb, var(--ink) 10%, transparent); color: var(--ink); }
/* Your avatar opens the six to choose from; the small tab on it says it can be changed. */
.avatar-picker { position: relative; flex: none; }
.avatar-picker summary { position: relative; display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; cursor: pointer; list-style: none; }
.avatar-picker summary::-webkit-details-marker { display: none; }
.avatar-picker summary .disc { width: 32px; height: 32px; }
.avatar-picker summary::after { content: "+"; position: absolute; right: 1px; bottom: 3px; width: 15px; height: 15px; border-radius: 50%; background: var(--ink); color: var(--paper); font: 700 12px/15px var(--sans); text-align: center; box-shadow: 0 0 0 2px var(--paper); }
.avatar-picker[open] summary::after { content: "−"; }
.avatar-picker summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.avatar-picker .avatars { position: absolute; left: 0; top: calc(100% + 8px); z-index: 5; display: grid; grid-template-columns: repeat(3, 46px); gap: 8px; padding: 12px; background: var(--panel); border-radius: var(--radius-panel); box-shadow: var(--shadow-panel); }
.avatar-picker .avatars button { width: 46px; height: 46px; padding: 3px; border: 2px solid transparent; border-radius: 50%; }
.avatar-picker .avatars button.on { border-color: var(--ink); }
.avatar-picker .avatars .disc { display: block; width: 36px; height: 36px; }

.home-stage { flex: 1 0 auto; display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 380px); align-items: center; align-content: center; gap: 20px 56px; width: 100%; max-width: 1080px; margin: 0 auto; padding: 20px 0 8px; }
.home-word h1 { margin: 0; font: 400 clamp(80px, min(10vw, 17vh), 160px) / 0.95 var(--ink-font); letter-spacing: 0.02em; color: var(--ink); }
.home-tagline { margin: 14px 0 0; max-width: 40ch; font-size: 16px; line-height: 1.5; }
.home .rules { margin: 10px 0 0; max-width: 60ch; font-size: var(--text-caption); line-height: 1.6; color: var(--ink-soft); }
.home .variant-note { display: none; margin: 0; font-size: var(--text-caption); line-height: 1.45; color: var(--ink-soft); text-wrap: balance; }
.home-side { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
/* The middle of the table: what the raised card does, and its buttons. */
.home-panel { display: flex; flex-direction: column; gap: 12px; min-height: 212px; padding: 18px 20px; border-radius: var(--radius-panel); background: var(--panel); box-shadow: 0 0 0 1px var(--line), 0 14px 32px -20px rgba(31, 59, 143, 0.45); text-align: left; }
.home-panel-head { display: flex; align-items: center; gap: 12px; }
.home-glyph { flex: none; width: 40px; height: 40px; display: grid; place-items: center; border-radius: 4px; rotate: -4deg; font: 24px/1 var(--ink-font); color: var(--on-seal); background: var(--seal); box-shadow: inset 0 0 0 1.5px var(--seal), inset 0 0 0 3px var(--on-seal); }
.home-glyph.ink { color: var(--paper); background: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink), inset 0 0 0 3px var(--paper); }
.home-panel h2 { margin: 0; font-size: 21px; line-height: 1.2; font-weight: 800; }
.home-panel p { margin: 0; font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
/* Every panel ends the same way: its main button across the panel, and at most one row
   beneath it (the account and the rating, the code to join, the album's count). Whatever
   room the tallest panel leaves falls between the description and the buttons. */
.home-actions { display: flex; flex-direction: column; gap: 8px; margin-top: auto; }
.home-actions > .primary { width: 100%; min-height: 48px; }
.home-actions .ic { width: 18px; height: 18px; }
.home-sub { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; }
.home-sub button.text { padding-inline: 0; }
.home-sub:has(> :only-child) { justify-content: center; }
.home-join { justify-content: flex-start; gap: 8px; }
.home-join input { flex: 1; min-width: 0; min-height: 44px; padding: 6px 2px; font-size: 16px; font-weight: 700; letter-spacing: 0.24em; text-transform: uppercase; }
.home-join button { flex: none; min-width: 88px; }
.home .mm-rating-small, .home-facts { font-size: var(--text-caption); color: var(--ink-soft); }
.home-facts { text-align: center; text-wrap: balance; }
.home-panel .home-hint { margin-top: -4px; font-size: var(--text-caption); color: var(--ink-soft); text-align: center; }
.home .status { margin: 0; font-size: 13px; color: var(--ink-soft); }
.home .status:empty { display: none; }

/* The hand. Each card's name sits on the part the next card leaves showing. */
.home-hand { position: relative; z-index: 1; display: flex; justify-content: center; padding-top: 34px; margin-bottom: calc(var(--mc-h) * -0.2); }
.menu-card {
  --rot: calc((var(--i) - 2) * 3.5deg);
  --arc: calc(max(var(--i) - 2, 2 - var(--i)) * 6px);
  --lift: 0px;
  position: relative;
  flex: none;
  display: block;
  width: var(--mc-w);
  height: var(--mc-h);
  min-height: 0;
  margin: 0 0 0 calc(var(--mc-w) * -0.12);
  padding: 0;
  border: 0;
  border-radius: calc(var(--mc-w) * 0.1);
  background: var(--card-surface, #fbf8ef);
  color: var(--black);
  text-align: left;
  white-space: normal;
  /* A printed edge, and a shadow on the left that sets each card off the one beneath it. */
  --mc-edge: 0 0 0 1px rgba(42, 38, 32, 0.24), -3px 1px 6px -2px rgba(12, 20, 42, 0.3);
  box-shadow: var(--mc-edge), 0 12px 22px -16px rgba(12, 20, 42, 0.55);
  transform-origin: 50% 130%;
  transform: translateY(calc(var(--arc) + var(--lift))) rotate(var(--rot));
  transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 0.22s;
  touch-action: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.menu-card:first-child { margin-left: 0; }
.menu-card.red { color: var(--red); }
.menu-card:hover:not(:disabled) { --lift: -8px; }
.menu-card:active:not(:disabled) { transform: translateY(calc(var(--arc) + var(--lift))) rotate(var(--rot)); }
.menu-card[aria-pressed="true"] { --lift: -30px; box-shadow: 0 0 0 2.5px var(--accent), -3px 1px 6px -2px rgba(12, 20, 42, 0.3), 0 18px 28px -14px rgba(12, 20, 42, 0.55); }
.mc-index { position: absolute; left: 11%; top: 7%; font: calc(var(--mc-w) * 0.26) / 1 var(--ink-font); }
.mc-label { position: absolute; left: 11%; right: 20%; top: 34%; display: flex; flex-direction: column; gap: 5px; }
.mc-label b { font: 600 calc(var(--mc-w) * 0.135) / 1.05 var(--rank-font); text-wrap: balance; }
.mc-label i { display: none; font: 600 12px / 1.1 var(--rank-font); font-style: normal; }
.mc-label small { font-size: 11px; line-height: 1.3; opacity: 0.72; }
.menu-card.flying { position: fixed; z-index: 40; margin: 0; transform: none; transition: none; pointer-events: none; }

/* A narrow or upright window stacks the name over the panel. */
@media (max-width: 760px) {
  .home-stage { grid-template-columns: minmax(0, 1fr); justify-items: center; text-align: center; }
  .home-word h1 { font-size: 88px; }
  .home-tagline, .home .rules { margin-inline: auto; }
  .home-side { width: min(100%, 420px); }
}
@media (orientation: portrait) {
  html[data-layout="wide"] .home.hand-home { --mc-w: clamp(116px, 17vw, 160px); }
  html[data-layout="wide"] .home-stage { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 32px; text-align: center; }
  html[data-layout="wide"] .home-word h1 { font-size: clamp(96px, 16vw, 150px); }
  html[data-layout="wide"] :is(.home-tagline, .home .rules) { margin-inline: auto; }
  html[data-layout="wide"] .home-side { width: min(100%, 460px); }
}
/* Phones show each card's short name, and the panel keeps only what it needs. */
:is(html[data-layout="phone-landscape"], html[data-layout="phone-portrait"]) .mc-label :is(b, small) { display: none; }
:is(html[data-layout="phone-landscape"], html[data-layout="phone-portrait"]) .mc-label i { display: block; }
:is(html[data-layout="phone-landscape"], html[data-layout="phone-portrait"]) .home-brand .sub,
:is(html[data-layout="phone-landscape"], html[data-layout="phone-portrait"]) .home .rules { display: none; }
/* In its place, one line on the chosen game: under its name on its side, under the choice upright. */
:is(html[data-layout="phone-landscape"], html[data-layout="phone-portrait"]) .home .variant-note { display: block; }
/* A phone on its side: the name and the panel side by side, the hand's upper half showing. */
html[data-layout="phone-landscape"] .home.hand-home { --mc-w: 104px; padding: max(6px, env(safe-area-inset-top, 0px)) max(16px, env(safe-area-inset-right, 0px)) 0 max(16px, env(safe-area-inset-left, 0px)); }
html[data-layout="phone-landscape"] .home-bar { gap: 6px 12px; }
html[data-layout="phone-landscape"] .home-name input { width: 112px; }
html[data-layout="phone-landscape"] .home-stage { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); justify-items: stretch; align-items: start; gap: 12px 24px; padding: 6px 0 0; text-align: left; }
html[data-layout="phone-landscape"] .home-word { padding-top: 6px; }
html[data-layout="phone-landscape"] .home-word h1 { font-size: 64px; }
html[data-layout="phone-landscape"] .home-tagline { margin: 8px 0 0; font-size: var(--text-caption); }
html[data-layout="phone-landscape"] .home-word .variant-note { margin-top: 6px; }
html[data-layout="phone-landscape"] .home-side { width: auto; }
html[data-layout="phone-landscape"] .home-panel { min-height: 0; gap: 8px; padding: 12px 14px; }
html[data-layout="phone-landscape"] .home-panel p { font-size: var(--text-caption); }
html[data-layout="phone-landscape"] .home-panel h2 { font-size: 18px; }
html[data-layout="phone-landscape"] .home-glyph { width: 32px; height: 32px; font-size: 19px; }
html[data-layout="phone-landscape"] .home-hint { display: none; }
html[data-layout="phone-landscape"] .home-hand { padding-top: 26px; margin-bottom: calc(var(--mc-h) * -0.4); }
/* A small phone on its side keeps the header to one line where it can. */
@media (max-width: 720px) {
  html[data-layout="phone-landscape"] .home-bar { gap: 6px 10px; }
  html[data-layout="phone-landscape"] .home-bar .segmented button { padding-inline: 9px; }
  html[data-layout="phone-landscape"] .home-name input { width: 90px; }
}
/* Upright, the hand is a fan turned about a point below the screen: each card's corner, with
   its brush index and short name, spreads clear of the next one. */
/* The header keeps one row: the wordmark, then your seat and the language, each a button
   that opens a sheet. The player count and the panel sit over the hand, where the thumb is,
   and 斗地主 stands in the middle of the room above them. */
html[data-layout="phone-portrait"] .home.hand-home { --mc-w: min(108px, 27vw); padding: max(10px, env(safe-area-inset-top, 0px)) 16px 0; }
html[data-layout="phone-portrait"] .home-bar { flex-wrap: nowrap; gap: 8px; }
html[data-layout="phone-portrait"] .home-stage { grid-template-rows: minmax(0, 1fr) auto; align-content: stretch; gap: 16px; padding: 18px 0 0; }
html[data-layout="phone-portrait"] .home-word { align-self: center; }
html[data-layout="phone-portrait"] .home-word h1 { font-size: clamp(56px, min(22vw, 11vh), 110px); }
html[data-layout="phone-portrait"] .home-tagline { margin-top: 10px; font-size: 13px; }
html[data-layout="phone-portrait"] .home-side { gap: 12px; }
html[data-layout="phone-portrait"] .home-side .variants { align-self: center; }
html[data-layout="phone-portrait"] .home-side .variant-note { margin-top: -6px; text-align: center; }
/* The narrowest phones close the three games' chips up so they keep the 16px gutter. */
@media (max-width: 359px) {
  html[data-layout="phone-portrait"] .home-side .variants button { padding-inline: 8px; }
}
html[data-layout="phone-portrait"] .home-panel { min-height: 0; padding: 14px 16px; gap: 10px; }
html[data-layout="phone-portrait"] .home-hand { display: block; height: calc(var(--mc-h) * 0.8 + 34px); margin: 8px -16px 0; padding: 0; }
html[data-layout="phone-portrait"] .menu-card {
  position: absolute;
  left: calc(50% - var(--mc-w) / 2);
  bottom: calc(var(--mc-h) * -0.2);
  margin: 0;
  transform-origin: 50% 300%;
  transform: rotate(calc((var(--i) - 2) * 8deg)) translateY(var(--lift));
}
html[data-layout="phone-portrait"] .menu-card:active:not(:disabled) { transform: rotate(calc((var(--i) - 2) * 8deg)) translateY(var(--lift)); }
html[data-layout="phone-portrait"] .menu-card[aria-pressed="true"] { --lift: -28px; }
html[data-layout="phone-portrait"] .mc-index { left: 9%; font-size: calc(var(--mc-w) * 0.28); }
html[data-layout="phone-portrait"] .mc-label { left: 9%; right: auto; top: 35%; }
html[data-layout="phone-portrait"] .mc-label i { font-size: 11px; }
/* A short upright screen (an SE) drops the tagline and the hint so the fan still fits. */
@media (max-height: 640px) {
  html[data-layout="phone-portrait"] :is(.home-tagline, .home-hint) { display: none; }
  html[data-layout="phone-portrait"] .home-word h1 { font-size: 44px; }
  html[data-layout="phone-portrait"] .home-stage { gap: 10px; padding-top: 4px; }
  html[data-layout="phone-portrait"] .home-side { gap: 8px; }
  html[data-layout="phone-portrait"] .home-side .variant-note { margin-top: -4px; }
  html[data-layout="phone-portrait"] .home-hand { margin-top: 0; }
}
/* Upright: your seat and the language, in the header's one row. */
.home-tools { display: flex; align-items: center; gap: 4px; min-width: 0; margin-left: auto; }
button.home-you { min-width: 0; min-height: 44px; gap: 8px; padding: 5px 12px; border-radius: 999px; font-size: 15px; font-weight: 700; }
button.home-you .disc { flex: none; width: 30px; height: 30px; }
button.home-you span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
button.home-you span.guest { color: var(--ink-soft); }
button.home-lang { flex: none; min-height: 44px; gap: 6px; padding: 5px 12px; border-radius: 999px; font-size: 13px; font-weight: 700; color: var(--ink-soft); }
button.home-lang .ic { width: 18px; height: 18px; }
/* Their sheets: the four languages by their own names, and your name and avatar. */
.home-langs { display: flex; flex-direction: column; gap: 4px; }
.home-langs button { justify-content: flex-start; min-height: 52px; padding: 12px 14px; border: 0; border-radius: var(--radius-control); font-size: 17px; }
.home-langs button[aria-pressed="true"] { background: color-mix(in srgb, var(--ink) 10%, transparent); }
.home-langs button[aria-pressed="true"]::after { content: ""; margin-left: auto; width: 6px; height: 12px; border: solid currentColor; border-width: 0 2.5px 2.5px 0; rotate: 45deg; translate: 0 -2px; }
.home-field { display: flex; flex-direction: column; gap: 8px; margin: 0 0 18px; }
.home-field > span { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.home-field input { min-height: 44px; font-size: 18px; font-weight: 700; }
.home-profile-sheet .avatars { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 6px; }
.home-profile-sheet .avatars button { aspect-ratio: 1; min-height: 44px; padding: 3px; border: 2px solid transparent; border-radius: 50%; }
.home-profile-sheet .avatars button.on { border-color: var(--ink); }
.home-profile-sheet .avatars .disc { display: block; width: 100%; height: 100%; }
.home-done { width: 100%; }
/* Upright, a home sheet rises from the bottom over a scrim, within the thumb's reach. */
html[data-layout="phone-portrait"] .home-sheet { position: fixed; inset: 0; z-index: 30; display: flex; flex-direction: column; justify-content: flex-end; background: var(--scrim); }
html[data-layout="phone-portrait"] .home-sheet .sheet { position: relative; inset: auto; width: 100%; max-height: 85%; border-radius: 18px 18px 0 0; padding-bottom: max(18px, env(safe-area-inset-bottom, 0px)); animation: sheetUp var(--motion-panel) ease-out; }
@keyframes sheetUp { from { transform: translateY(24px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .menu-card { transition: none; }
  html[data-layout="phone-portrait"] .home-sheet .sheet { animation: none; }
}

/* ---------- table (landscape first) ---------- */
.table {
  position: relative;
  isolation: isolate;
  height: 100%;
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px max(14px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left));
}
/* One quiet line: the room code (tap to share), the round, and the tools. (Not `.top`: the
   seat across the table carries that class too, and would be squashed to this height.) */
.topbar { flex: none; height: 36px; display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.topbar .meta { flex: 1; min-width: 0; font-size: 13px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.topbar .meta b { color: var(--ink-soft); letter-spacing: 0.2em; font-weight: 700; transition: color 0.12s; }
.topbar .meta:hover b { color: var(--ink); }
.topbar .meta .dot { margin: 0 6px; }
.topbar .meta .warn { color: var(--alert); margin-left: 8px; }
.topbar .tools { display: flex; gap: 2px; flex: none; }
.topbar .tools button.icon { color: var(--ink-soft); }
.topbar .tools button.icon:hover:not(:disabled) { color: var(--ink); }
.topbar .tools button.icon.on { color: var(--paper); }

.field {
  flex: 1;
  min-height: 0;
  width: 100%; /* said outright, so the columns share the width rather than sit at their minimum */
  overflow: hidden; /* on a short screen the middle yields; the hand is never cut */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas: "left right" "center center";
  gap: 12px;
  align-items: stretch;
}
.table.four .field { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-areas: "left top right" "center center center"; }
.side { min-width: 0; min-height: 0; display: flex; flex-direction: column; justify-content: flex-start; }
.seat { max-width: 100%; min-width: 0; }
.table > [data-region="sheet"], .table > [data-region="result"] { display: contents; }
.side.left { grid-area: left; }
.side.right { grid-area: right; align-items: flex-end; }
.side.right .player { flex-direction: row-reverse; text-align: right; }
.center { display: contents; }
.center .center-body { grid-area: center; min-width: 0; min-height: 0; display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center; overflow: hidden; }
/* Centred while there is room, pinned to the top once there is not. Auto margins do what
   `justify-content: safe center` would, and Safari does not know `safe`. */
.center .center-body > :first-child { margin-top: auto; }
.center .center-body > :last-child { margin-bottom: auto; }
.center .across { grid-area: top; display: flex; justify-content: center; min-width: 0; }

/* ---------- player blocks: no box, an avatar disc and two lines of type ---------- */
.player { position: relative; display: flex; align-items: center; gap: 12px; min-width: 0; }
.player .avatar-wrap { position: relative; width: 48px; height: 48px; flex: none; }
.player button.avatar-wrap { padding: 0; border: 0; border-radius: 50%; background: transparent; }
.player .avatar { position: absolute; inset: 4px; border-radius: 50%; display: block; }
.player .avatar .disc { width: 100%; height: 100%; display: block; }
.player .avatar.hollow { border: 1.5px dashed var(--line); }
.player .ring { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.player .ring circle { fill: none; stroke-width: 2.5; }
.player .ring .ring-bg { stroke: var(--line); }
.player .ring .ring-fg { stroke: var(--ink); stroke-linecap: round; stroke-dasharray: 113.1; stroke-dashoffset: 113.1; transition: stroke-dashoffset 0.25s linear; }
.player .ring .ring-fg.urgent { stroke: var(--alert); }
.player .ptext { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.player .nm { font-size: 18px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.player .nm .you { font-size: 12px; font-weight: 500; color: var(--ink-soft); margin-left: 4px; }
.player .role { font-size: 13px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.player .role .sep { margin: 0 5px; opacity: 0.7; }
.player .role .off { color: var(--alert); }
.player .role .x2 { color: var(--alert); font-weight: 700; }
.player .remaining { display: inline-flex; align-items: center; gap: 4px; vertical-align: middle; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
.player .remaining svg.ic { width: 14px; height: 14px; }
.player .remaining.alarm { color: var(--alert); }
.player .remaining.unknown { color: var(--ink-soft); } /* a house rule keeps a hand above ten to its owner */
.player .bid-decision { min-height: 2.4em; font-size: 11px; line-height: 1.2; font-weight: 600; color: var(--ink); white-space: normal; overflow-wrap: anywhere; }
/* Who is who, in seals on the avatar's corner: a red seal with the crown for the landlord,
   an outline 农 for each farmer once the sides are known, a red outline 友 for a found partner. */
.player .seal-badge { position: absolute; right: -3px; bottom: -3px; width: 22px; height: 22px; display: grid; place-items: center; border-radius: 3px; rotate: -8deg; font: 14px/1 var(--ink-font); pointer-events: none; }
.player .seal-badge.crown { background: var(--seal); color: var(--on-seal); box-shadow: inset 0 0 0 1.5px var(--seal), inset 0 0 0 2.5px var(--on-seal), 0 0 0 2px var(--paper); }
.player .seal-badge.crown svg.ic { width: 13px; height: 13px; }
.player .seal-badge.farmer { background: var(--paper); color: var(--ink); box-shadow: inset 0 0 0 1.25px var(--ink-soft), 0 0 0 2px var(--paper); }
.player .seal-badge.ally { background: var(--paper); color: var(--seal); box-shadow: inset 0 0 0 1.25px var(--seal), 0 0 0 2px var(--paper); }
/* Shuangkou: a player out of cards carries their place (头游, 二游, 三游) as a tall two-character
   chop, red for the first out, and their avatar fades; nothing is left for them to play. */
.player .seal-badge.place { width: 17px; height: 31px; right: 0; bottom: -4px; writing-mode: vertical-rl; font-size: 12.5px; letter-spacing: 0.04em; background: var(--paper); color: var(--seal); box-shadow: inset 0 0 0 1.25px var(--seal), 0 0 0 2px var(--paper); }
.player .seal-badge.place.first { background: var(--seal); color: var(--on-seal); box-shadow: inset 0 0 0 1.5px var(--seal), inset 0 0 0 2.5px var(--on-seal), 0 0 0 2px var(--paper); }
.player.out .avatar { opacity: 0.55; filter: grayscale(0.6); }
.player .role .out-place { color: var(--ink); font-weight: 600; }
.player .role .sc { font-variant-numeric: tabular-nums; white-space: nowrap; }
.player.landlord .role { color: var(--ink); font-weight: 600; }
.player.turn .nm { color: var(--ink-2); }
/* Whose turn it is: a ring breathes round their avatar (the timer's ring, if any, sits inside). */
.player.turn .avatar-wrap::after { content: ""; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--accent); pointer-events: none; animation: breathe 1.6s ease-in-out infinite; }
/* The rings break around the seal rather than running under it. Centres are in each ring's
   own box; the timer ring is turned -90deg, so its notch sits at its bottom-left. */
.player .avatar-wrap:has(.seal-badge)::after { --notch: radial-gradient(circle at 44px 44px, transparent 17.5px, #000 18px); -webkit-mask: var(--notch); mask: var(--notch); }
.player .avatar-wrap:has(.seal-badge) .ring { --notch: radial-gradient(circle at 8px 40px, transparent 17.5px, #000 18px); -webkit-mask: var(--notch); mask: var(--notch); }
.player.empty { opacity: 0.7; }
.player .small { align-self: flex-start; margin-top: 4px; }
.side.right .player .small { align-self: flex-end; }
/* An empty chair, or a bot's, can be taken between rounds: the way in reads plainly, not greyed. */
.player button.small.sit { border-color: var(--ink); color: var(--ink); font-weight: 600; }
.player.empty:has(.sit) { opacity: 0.85; cursor: pointer; }
.player.me { gap: 10px; flex: none; }
.player.me .avatar-wrap { width: 48px; height: 48px; }

.table.four .bottom-cards .card-row .card { --cw: 44.8px; --ch: 64px; }
@media (min-height: 481px) {
  /* With four seats a play can be eight cards wide, and the table is fuller: slightly smaller
     cards in the middle keep the whole play, its caption and the called card in view. */
  .table.four .center .center-body { gap: 8px; }
  .table.four .current .card { --cw: 70px; --ch: 100px; }
  .table.four .current { min-height: 0; }
}
.stamp {
  display: inline-block;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--alert);
  border: 1.5px solid var(--alert);
  border-radius: 4px;
  padding: 2px 8px;
  transform: rotate(-6deg);
  opacity: 0.85;
}
.bubble {
  position: absolute;
  top: -10px;
  left: 60px;
  transform: translateY(-100%);
  background: var(--ink);
  color: var(--paper);
  padding: 7px 12px;
  border-radius: 14px;
  font-size: 13px;
  font-weight: 600;
  max-width: 220px;
  z-index: 6;
  animation: pop 0.15s ease-out;
  white-space: nowrap;
  pointer-events: none;
}
.bubble::after { content: ""; position: absolute; bottom: -6px; left: 14px; border: 6px solid transparent; border-top-color: var(--ink); border-bottom: 0; }
.side.right .bubble { left: auto; right: 60px; }
.side.right .bubble::after { left: auto; right: 14px; }

/* ---------- centre: cards lying on the table ---------- */
.bottom-cards { display: flex; gap: 22px; align-items: flex-start; justify-content: center; }
.bottom-cards .group { display: flex; flex-direction: column; align-items: center; gap: 8px; flex: none; }
.bottom-cards .card-row { flex-wrap: nowrap; }
.bottom-cards .label { font-size: var(--text-caption); line-height: 1.4; font-weight: 600; color: var(--ink-soft); }
.bottom-cards .label .yours { display: block; font-size: inherit; color: var(--seal); }
.bottom-cards .card.flip { animation: flipIn 0.3s backwards; }
.bottom-cards .card-row .card { --cw: 57.4px; --ch: 82px; }
.bottom-cards .card-row .card + .card { margin-left: 6px; }
/* The called card wears the seal a found partner does, and so does any copy in a hand that
   would make its player the partner. */
.card.called::after { content: "友"; position: absolute; left: 5%; top: calc(var(--ch) * 0.36); width: clamp(13px, calc(var(--ch) * 0.2), 22px); height: clamp(13px, calc(var(--ch) * 0.2), 22px); display: grid; place-items: center; border-radius: 3px; rotate: -8deg; font: clamp(9px, calc(var(--ch) * 0.14), 14px)/1 var(--ink-font); color: var(--seal); background: var(--paper); box-shadow: inset 0 0 0 1.25px var(--seal); pointer-events: none; }
/* 百变: a red joker on the table wears the rank it stood for, low in its corner, where the next
   card of the play leaves it showing. */
.card.wild::after { content: var(--as); position: absolute; left: 5%; bottom: 5%; min-width: calc(var(--ch) * 0.2); padding: 0 calc(var(--ch) * 0.03); box-sizing: border-box; border-radius: calc(var(--ch) * 0.05); font: 600 calc(var(--ch) * 0.16)/1.3 var(--card-font); text-align: center; color: var(--on-seal); background: var(--seal); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); pointer-events: none; }
.current {
  min-width: 0;
  width: 100%;
  max-width: 100%;
  min-height: calc(var(--ch) + 30px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.current .cards { display: flex; justify-content: flex-start; width: max-content; max-width: 100%; overflow-x: auto; padding: 6px; overscroll-behavior-x: contain; touch-action: pan-x pinch-zoom; }
.current .cards .card + .card { margin-left: calc(var(--cw) * -0.5); }
/* On a large screen the play grows with the table's height, and a long one (--n cards, each
   overlapping half the last) shrinks to fit across it rather than scroll. */
html[data-layout="wide"] .current { --play-ch: clamp(120px, 19dvh, 196px); min-height: calc(var(--play-ch) + 30px); container-type: inline-size; }
html[data-layout="wide"] .table.four .current { --play-ch: clamp(100px, 16dvh, 160px); min-height: 0; }
html[data-layout="wide"] .table .current .card { --ch: min(var(--play-ch), max(60px, (100cqw - 16px) * 2 / (var(--n, 1) + 1) / 0.7)); --cw: calc(var(--ch) * 0.7); }
.current .caption { display: flex; justify-content: center; gap: 6px; max-width: 100%; font-size: 14px; font-weight: 600; color: var(--ink-soft); }
.current .play-owner { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.current .play-combo { flex: none; font-weight: 400; white-space: nowrap; }
.current .caption.soft { color: var(--ink-soft); font-weight: 500; }
.center .lobby { display: flex; flex-direction: column; align-items: center; gap: 12px; }
/* The lobby's own row (Start, Add bot) stays in view: what is above it scrolls, should it run
   longer than the table has room for (house rules open in a short window, a phone held
   sideways). Its foot fades while there is more below, its head once it has scrolled. An
   upright phone scrolls the whole lobby instead. */
.center .lobby-main { display: contents; }
html:not([data-layout="phone-portrait"]) .center .lobby { min-height: 0; max-height: 100%; }
html:not([data-layout="phone-portrait"]) .center .lobby-main { display: flex; flex-direction: column; align-items: center; gap: 12px; min-height: 0; max-width: 100%; padding: 2px 4px; overflow-y: auto; overscroll-behavior-y: contain; }
html:not([data-layout="phone-portrait"]) .center .lobby .row { flex: none; }
.center .lobby-main.more { --lobby-fade: linear-gradient(#000 calc(100% - 20px), transparent); }
.center .lobby-main.scrolled { --lobby-fade: linear-gradient(transparent, #000 16px); }
.center .lobby-main.scrolled.more { --lobby-fade: linear-gradient(transparent, #000 16px, #000 calc(100% - 20px), transparent); }
.center .lobby-main:is(.more, .scrolled) { -webkit-mask-image: var(--lobby-fade); mask-image: var(--lobby-fade); }
.lobby .big-code { font-family: var(--serif); font-size: 64px; letter-spacing: 0.22em; font-weight: 700; color: var(--ink); line-height: 1; padding-left: 0.22em; }
.lobby .hint { font-size: 14px; color: var(--ink-soft); }
.lobby .setting { font-size: 13px; color: var(--ink-soft); display: flex; gap: 8px; align-items: center; }
.lobby .row { display: flex; gap: 8px; align-items: center; }
.lobby .partners-hint { max-width: 32em; font-size: 13px; text-align: center; }
/* The house rules fold away under the settings, saying only whether any differs from the
   standard game, so the lobby stays short enough to keep Start in view on a phone. */
.lobby .house-rules { max-width: 100%; font-size: 13px; color: var(--ink-soft); }
.lobby .house-rules summary { display: flex; align-items: center; justify-content: center; gap: 8px; min-height: var(--control-compact-height); padding: 0 8px; cursor: pointer; list-style: none; }
.lobby .house-rules summary::-webkit-details-marker { display: none; }
.lobby .house-rules summary::after { content: ""; width: 6px; height: 6px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; rotate: 45deg; translate: 0 -2px; }
.lobby .house-rules[open] summary::after { rotate: 225deg; translate: 0 2px; }
.lobby .house-title { color: var(--ink); font-weight: 600; }
.lobby .house-state.changed { color: var(--seal); font-weight: 600; }
.lobby .house-list { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; padding: 4px 12px; border: 1px solid var(--line); border-radius: 10px; }
.lobby .house-setting { justify-content: space-between; min-height: 36px; }
.lobby .house-setting button.switch { flex: none; margin: -4px 0; }

/* The landlord's partner picker: every base card, greyed where every copy is held. */
.call-picker { display: flex; flex-direction: column; gap: 4px; align-items: center; }
.call-picker .title { font-size: 14px; color: var(--ink); font-weight: 600; margin-bottom: 4px; }
.call-hand { display: none; }
.call-picker .suit-row { display: flex; gap: 3px; align-items: center; }
.call-picker .suit-row .suit { width: 18px; font-size: 15px; text-align: center; color: var(--ink); }
.call-picker .suit-row .suit.red, .call-picker button.red { color: var(--red); }
.call-picker button { padding: 4px 0; width: 28px; font-size: 13px; border-radius: var(--radius-small); border-color: var(--line); background: var(--card); color: var(--black); font-family: var(--card-font); font-weight: 600; }
.call-picker button:hover:not(:disabled) { border-color: var(--ink); }
.call-picker .jokers button { width: auto; padding: 4px 12px; font-family: var(--sans); font-weight: 600; }

/* ---------- me: one row of avatar, status and actions, then the hand ---------- */
.foot { position: relative; flex: none; display: flex; flex-direction: column; gap: 4px; }
/* A warm pool of light draws attention to the hand without changing the table's ink
   or putting a border around the controls. */
.table::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  opacity: 0; transition: opacity 0.2s ease-out;
  background: radial-gradient(ellipse 66% 60% at 50% 82%, var(--turn-light-core) 0%, var(--turn-light-edge) 42%, transparent 78%);
}
.table.spotlight::before { opacity: 1; animation: turnSpotlight 2.2s ease-in-out infinite; }
/* On phones the arrival cue belongs to the rim; keep the table interior still. */
html.phone .table.spotlight::before { opacity: 0; animation: none; }
/* One luminous surface avoids doubled color where the four edge fades meet. */
html.phone .table::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(125deg, var(--turn-edge-light), var(--turn-edge-near) 35%, var(--turn-edge-light) 70%, var(--turn-edge-near));
  --edge-fade: #000 0%, rgba(0, 0, 0, 0.55) 24%, rgba(0, 0, 0, 0.15) 58%, transparent 100%;
  -webkit-mask:
    linear-gradient(to bottom, var(--edge-fade)) top / 100% 30px no-repeat,
    linear-gradient(to top, var(--edge-fade)) bottom / 100% 30px no-repeat,
    linear-gradient(to right, var(--edge-fade)) left / 30px 100% no-repeat,
    linear-gradient(to left, var(--edge-fade)) right / 30px 100% no-repeat;
  mask:
    linear-gradient(to bottom, var(--edge-fade)) top / 100% 30px no-repeat,
    linear-gradient(to top, var(--edge-fade)) bottom / 100% 30px no-repeat,
    linear-gradient(to right, var(--edge-fade)) left / 30px 100% no-repeat,
    linear-gradient(to left, var(--edge-fade)) right / 30px 100% no-repeat;
  opacity: 0;
}
html.phone .table.spotlight.turn-arrival::after { animation: turnGlowPulse 1.3s cubic-bezier(0.4, 0, 0.2, 1) both; }
.my-turn .console .status { display: block; }
.turn-label { display: block; width: max-content; max-width: 100%; color: var(--ink); font-size: 14px; line-height: 1.25; letter-spacing: 0.18em; text-transform: uppercase; font-weight: 800; margin-bottom: 2px; }
.status-detail { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.console { display: flex; align-items: center; gap: 14px; min-height: 52px; }
.console-main { display: contents; }
.console .status { flex: 1 1 auto; min-width: 0; padding: 4px 8px; margin: -4px -8px; border-radius: 10px; font-size: 15px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.table.spotlight .console .status { color: var(--ink); }
.console .status b { color: var(--ink); font-weight: 700; }
.console .status .dot { margin: 0 6px; }
.console .status .timer { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600; }
.console .status .timer.urgent { color: var(--alert); }
.console .actions .hint-label { white-space: nowrap; }
.console .actions .left { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.console .status .soft { color: var(--ink-soft); }
.console .actions { flex: none; display: flex; gap: 6px; align-items: center; }
/* A yes-or-no decision (call, rob, double) is two buttons of one size: the answer that
   declines is outlined, like Pass, and the one that commits is filled, like Play. */
html:not(.phone) .console .actions:has(> .decline):not(.playing) { display: grid; grid-template-columns: 1fr 1fr; }
.console .actions button { min-height: var(--control-height); line-height: 20px; }
.console .actions .bidbtn { min-width: 46px; justify-content: center; padding: 10px 0; }
.console .actions button.text, .console .actions button.primary, .console .actions button.decline { font-size: var(--text-body); }
/* Pass is always a button with an edge; when nothing can beat the table it is the move to make. */
.console .actions [data-act="pass"].pass-ready { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--panel)); }
.console .actions.playing #play { min-width: 100px; justify-content: center; }
.console .actions [data-act="clear"]:disabled { visibility: hidden; }
/* The row grows left when Clear appears, keeping Pass and Play anchored at the right. */
html:not(.phone) .console .actions [data-act="clear"]:disabled { display: none; }
.console .connection-note, .console .sending-note { display: block; white-space: normal; font-size: 13px; }
/* Keep my avatar for turn feedback and social gestures; my hand already carries the count. */
.table:not(.lobby) .console .player.me.self { gap: 0; }
.table:not(.lobby) .console .player.me.self .ptext { display: none; }
html.phone .console .player.me .avatar-wrap { width: 36px; height: 36px; }
.hand { --ch: clamp(90px, calc(var(--app-height, 100dvh) * .32), 240px); --cw: calc(var(--ch) * .7); min-height: calc(var(--ch) + 22px); padding-top: 4px; touch-action: none; user-select: none; -webkit-user-select: none; }
html:not(.phone) .hand { width: calc(100% - 80px); max-width: 1040px; align-self: center; }
.table.lobby .hand { min-height: 0; height: auto; } /* nothing to hold yet: the lobby gets the room */
/* Out of cards, my partner's hand takes my hand's place: smaller, framed in a dashed line and
   captioned, and nothing in it can be picked. */
.partner-region:empty { display: none; }
.table.watching-partner .foot > .hand { display: none; }
.partner-hand { --pch: clamp(72px, calc(var(--app-height, 100dvh) * .13), 110px); --pcw: calc(var(--pch) * .7); display: flex; flex-direction: column; align-items: center; gap: 6px; width: fit-content; max-width: 100%; margin: 6px auto 0; padding: 8px 14px 10px; border: 1.5px dashed color-mix(in srgb, var(--ink) 30%, transparent); border-radius: 12px; }
.partner-head { display: flex; align-items: baseline; flex-wrap: wrap; justify-content: center; gap: 2px 10px; font-size: 13px; color: var(--ink-soft); }
.partner-title { font-size: 12px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink); }
.partner-name .dot { margin: 0 5px; }
.partner-cards { display: flex; flex-direction: column; align-items: center; pointer-events: none; user-select: none; -webkit-user-select: none; }
.partner-row { display: flex; }
.partner-row + .partner-row { margin-top: calc(var(--pch) * -0.45); }
.partner-row .card { --ch: var(--pch); --cw: var(--pcw); }
.partner-row .card + .card { margin-left: calc(var(--pcw) * -0.52); }
.hand-row { --row-headroom: 16px; display: flex; justify-content: center; padding-top: var(--row-headroom); }
.foot.two-rows .hand { --cw: 70px; --ch: 100px; } /* 33 cards in two rows must leave the table room */
.hand.two-rows .hand-row { --row-headroom: 10px; }
/* Overlap about a third of each row, leaving the top ranks and tap areas clear. */
.hand.two-rows .hand-row + .hand-row { margin-top: calc(0px - var(--row-headroom) - clamp(16px, var(--ch) * 0.30, 28px)); }
.hand-row .card { cursor: pointer; }
.hand-row .card + .card { margin-left: calc(var(--step) - var(--cw)); }
.hand-row .card.selected { transform: translateY(calc(-14px + var(--drag-lift, 0px))); }
/* Cards that cannot take part in a legal play keep their paper, their edges and their
   colour: only the printed ink recedes. Only selected cards rise, so the baseline stays
   quiet and choosing a card is unambiguous. */
.hand-row .card.dim { opacity: 1; filter: none; }
.hand-row .card.dim > * { opacity: 0.58; transition: opacity 0.2s; }
.hand-row .card.dim .idx { opacity: 0.9; }
.hand-row .card.dim.selected > * { opacity: 1; }
.hand-row .card.dealt { animation: dealIn 0.24s cubic-bezier(0.2, 0.8, 0.3, 1) backwards; }

.hand.lifting .hand-row .card.selected { transition: none; }

/* ---------- cards ---------- */
/* Centered faces and mirrored corner indices, like a printed deck. The index fits
   inside the exposed strip of an overlapping hand without moving the artwork. */
.card {
  --index-width: 0.14;
  --rank-scale: 0.18;
  --ten-scale: 0.1575;
  --suit-scale: 0.085;
  --joker-scale: 0.10;
  --minimal-joker-scale: 0.073;
  --card-shadow: 0 0 0 0.5px rgba(42, 38, 32, 0.18), 0 2px 4px rgba(12, 20, 42, 0.14);
  position: relative;
  width: var(--cw);
  height: var(--ch);
  border-radius: calc(var(--ch) * var(--card-radius, 0.07));
  background: var(--card-surface, #fbf8ef);
  color: var(--black);
  box-shadow: var(--card-shadow);
  flex: none;
  user-select: none;
  -webkit-user-select: none;
  transition: transform 0.12s, box-shadow 0.12s, opacity 0.2s;
}
.card.red { color: var(--red); }
.card[data-suit="D"] { color: var(--diamond, var(--red)); }
.card[data-suit="C"] { color: var(--club, var(--black)); }
.card .suit { display: block; fill: currentColor; }
.card .idx { position: absolute; top: 5%; left: 5%; width: calc(var(--ch) * var(--index-width)); display: flex; flex-direction: column; align-items: center; line-height: 0.95; }
.card .idx b { font-family: var(--card-font); font-size: calc(var(--ch) * var(--rank-scale)); font-weight: 600; font-variant-numeric: lining-nums tabular-nums; letter-spacing: 0; }
.card .idx .suit { width: calc(var(--ch) * var(--suit-scale)); height: calc(var(--ch) * var(--suit-scale)); margin-top: calc(var(--ch) * 0.005); }
.card .pip { position: absolute; inset: 0; display: grid; place-items: center; }
.card .pip .suit { width: calc(var(--ch) * 0.22); height: calc(var(--ch) * 0.22); }
.card .pip.ace .suit { width: calc(var(--ch) * 0.26); height: calc(var(--ch) * 0.26); }
.card.joker .idx b { font-size: calc(var(--ch) * var(--joker-scale)); writing-mode: vertical-rl; letter-spacing: 0.08em; line-height: 1; }
.card.joker .pip .joker-mark { width: calc(var(--ch) * 0.3); height: calc(var(--ch) * 0.3); }
.card.joker.red .joker-mark .cap { fill: currentColor; }
.card.joker.black { color: var(--navy); }
.card .idx.br { inset: auto 5% 5% auto; transform: rotate(180deg); }
.card .court.illustrated { position: absolute; inset: 25% 31%; display: block; }
.court.illustrated img { width: 100%; height: 100%; object-fit: contain; display: block; pointer-events: none; }
/* Keep a clear gutter beside the corner indices and enough vertical space for 9s/10s.
   Every deck uses this geometry because numbered cards share the same suit symbols. */
.printed-pips { position: absolute; inset: 26% 35%; }
.printed-pips i { position: absolute; display: block; width: calc(var(--ch) * 0.085); height: calc(var(--ch) * 0.085); transform: translate(-50%, -50%); }
.printed-pips .suit { width: 100%; height: 100%; }
.printed-pips i.inverted .suit { transform: rotate(180deg); }
.large-ranks .hand-row .card { --index-width: 0.22; --rank-scale: 0.25; --ten-scale: 0.215; --suit-scale: 0.095; --joker-scale: 0.145; --minimal-joker-scale: 0.085; }
.large-ranks .hand-row .court { inset: 32% 34%; }
.large-ranks .hand-row .printed-pips { inset: 26% 43%; }
.large-ranks .hand-row .printed-pips i { width: calc(var(--ch) * 0.065); height: calc(var(--ch) * 0.065); }
.card.selected { box-shadow: 0 0 0 2px var(--accent), 0 5px 12px rgba(12, 20, 42, 0.2); }
/* Navy patterned backs with a cream frame and a centered diamond. */
.card.back {
  --back-frame: inset 0 0 0 3px var(--back), inset 0 0 0 4px rgba(242, 235, 200, 0.8), inset 0 0 0 7px var(--back);
  background:
    var(--back-pattern),
    linear-gradient(165deg, var(--back-2) 0%, var(--back) 55%, var(--back-3) 100%);
  box-shadow: var(--back-frame), 0 1px 2px rgba(14, 24, 60, 0.3), 0 4px 10px -4px rgba(14, 24, 60, 0.45);
}
.card.back::after {
  content: "";
  position: absolute;
  inset: 37% 30%;
  border: 1px solid var(--back-ink);
  box-shadow: 0 0 0 3px var(--back), 0 0 0 4px var(--back-ink);
  transform: rotate(45deg);
  opacity: 0.65;
}

/* The minimal decks retain the same corner footprint as the printed deck. */
.card.minimal { box-shadow: var(--card-shadow); }
.card.minimal .idx b { font-weight: 600; }
.card.minimal .pip .suit { width: calc(var(--ch) * 0.22); height: calc(var(--ch) * 0.22); }
.card.minimal.selected { box-shadow: 0 0 0 2px var(--deck-accent), 0 3px 6px rgba(25, 31, 29, 0.13); }
.card.minimal.back { background: var(--back); }
.card.minimal.back::after { content: none; }
.back-mark { position: absolute; inset: 0; display: grid; place-items: center; color: var(--back-ink); }
.back-mark .suit, .back-mark .deck-symbol { width: calc(var(--ch) * 0.105); height: calc(var(--ch) * 0.105); }
.deck-symbol {
  display: block; flex: none;
  width: calc(var(--ch) * 0.25); height: calc(var(--ch) * 0.25);
  background-color: currentColor;
  -webkit-mask: var(--symbol) center / contain no-repeat;
  mask: var(--symbol) center / contain no-repeat;
}
.symbol-queen { --symbol: url("cards/crown-fill-a285272ce5.svg"); }
.symbol-king { --symbol: url("cards/crown-simple-fill-bda9527688.svg"); }
.symbol-sparkle { --symbol: url("cards/star-four-fill-2e323dacc3.svg"); }
.symbol-sun { --symbol: url("cards/seal-fill-815d2ed46a.svg"); }
.symbol-dot { --symbol: url("cards/circle-fill-196c9eaad1.svg"); }
.card.minimal.joker .idx b { font-size: calc(var(--ch) * var(--minimal-joker-scale)); text-orientation: upright; letter-spacing: 0; }
.card.minimal .joker-face { display: flex; flex-direction: column; align-items: center; gap: calc(var(--ch) * 0.035); }
.joker-face .ic, .joker-face .deck-symbol { width: calc(var(--ch) * 0.23); height: calc(var(--ch) * 0.23); }
.joker-label { font-family: var(--card-font); font-size: calc(var(--ch) * 0.057); font-weight: 600; letter-spacing: 0.12em; line-height: 1; }
/* Image decks (static/skins): a pack's picture covers the whole card, or sits in the centre
   like a court illustration. Index-only miniatures hide the picture and keep the drawn index. */
.card .skin { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; pointer-events: none; }
.card.skinned .idx { display: none; }
.card.back.skinned { background: var(--back); }
.card.back.skinned::after { content: none; }

/* Artwork sheets supply motifs; game-drawn indices and pip layouts stay authoritative. */
.atlas-art { display: block; width: 100%; height: 100%; overflow: hidden; pointer-events: none; }
.card.atlas-deck .pip, .card.atlas-deck .printed-pips, .card.atlas-deck .court { mix-blend-mode: darken; }
.card.atlas-deck .atlas-art image { filter: brightness(1.13); }
.card.atlas-deck .pip.ace .atlas-art { width: 52%; height: 60%; }
.card.atlas-deck .court.illustrated { inset: 25% 32%; }
.atlas-back { position: absolute; inset: 0; border-radius: inherit; }
.large-ranks .hand-row .card.atlas-deck .court { inset: 28% 34%; }
/* Finished illustrations have clear margins and white stock: retain their full shapes
   and print them naturally on the card, without brightening or cutting away corners. */
.card.atlas-clean .pip, .card.atlas-clean .printed-pips, .card.atlas-clean .court { mix-blend-mode: multiply; }
.card.atlas-clean .atlas-art image { filter: none; }
.card.atlas-clean .pip.ace .atlas-art { width: 49%; height: 56%; }

.card-row, .mini { display: flex; flex-wrap: wrap; row-gap: var(--space-1); }
.mini .card { --cw: 33.6px; --ch: 48px; }
.mini .card + .card { margin-left: -15px; } /* Leave enough of each card visible for a "10". */
.mini.small .card { --cw: 25.2px; --ch: 36px; }
.mini.small .card + .card { margin-left: -9px; }
.card.ten .idx b { font-size: calc(var(--ch) * var(--ten-scale)); letter-spacing: -0.02em; }
/* Compact optical treatment for index-only history/review cards, at every size. */
.card[data-presentation="index"] { --index-width: 0.23; --rank-scale: 0.333333; --ten-scale: 0.291667; --suit-scale: 0.1875; --joker-scale: 0.125; --minimal-joker-scale: 0.085; }
.card[data-presentation="index"] .idx.tl { top: 6.25%; left: 8.33%; display: flex; }
.card[data-presentation="index"] :is(.skin, .pip, .court, .printed-pips, .idx.br) { display: none; }

/* ---------- sheets: emotes, history, settings ---------- */
.sheet {
  position: absolute;
  top: 46px;
  right: max(14px, env(safe-area-inset-right, 0px));
  width: min(340px, 84vw);
  max-height: calc(100% - 62px - env(safe-area-inset-bottom, 0px));
  overflow: auto;
  background: var(--panel);
  border-radius: var(--radius-panel);
  padding: var(--space-4);
  z-index: 20;
  box-shadow: var(--shadow-panel);
  animation: popIn var(--motion-panel) ease-out;
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-3); }
.sheet-head button.icon { flex: none; width: var(--control-height); height: var(--control-height); }
.sheet h3 { margin: 0; font-size: var(--text-heading); line-height: 1.25; font-weight: 700; }
.sheet h4 { margin: 14px 0 6px; font-size: 12px; font-weight: 600; color: var(--ink-soft); letter-spacing: 0.06em; text-transform: uppercase; }
.emote-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.emote-grid button { padding: 9px 10px; font-size: 14px; }
.history { display: flex; flex-direction: column; }
.history .row { display: flex; align-items: center; gap: 10px; font-size: 13px; padding: 4px 0; }
.history .row .who { flex: none; width: 110px; display: flex; align-items: center; gap: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.history .who .disc { width: 22px; height: 22px; flex: none; }
.history .row .stamp { font-size: 11px; padding: 1px 6px; transform: none; }
.history .played { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.history .wild-note { color: var(--ink-soft); font-size: var(--text-caption); line-height: 1.3; }
.history .empty, .history .log-line { color: var(--ink-soft); font-size: var(--text-caption); line-height: 1.6; }
.setting-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 5px 0; border-bottom: 1px solid color-mix(in srgb, var(--ink) 9%, transparent); font-size: 14px; font-weight: 500; }
.setting-row:last-child { border-bottom: 0; }
.setting-row.language-row { flex-wrap: wrap; }
.setting-row .segmented { padding: 2px; }
.setting-row .segmented button { padding: 5px 10px; font-size: var(--text-caption); }
.setting-row button.text { padding: 4px; }
.sheet.settings { scrollbar-gutter: stable; scroll-padding-top: 64px; overflow-anchor: none; }
.sheet.settings .sheet-head { position: sticky; top: 0; z-index: 2; background: var(--panel); box-shadow: 0 -16px 0 var(--panel); }
.sheet.settings .setting-row .segmented button { min-width: 44px; min-height: 44px; padding-inline: 9px; }
.sheet.settings .setting-row button.switch { flex: none; }
.sheet.settings .setting-row button.text { min-width: 44px; min-height: 44px; display: grid; place-items: center; }
.setting-note { margin: 4px 0 14px; color: var(--ink-soft); font-size: var(--text-caption); line-height: 1.4; }
.sheet.settings .settings-section { margin-top: 24px; color: var(--ink); }
.settings-link { display: flex; justify-content: space-between; width: 100%; min-height: 48px; padding: 10px 0; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; text-align: left; white-space: normal; font-size: 14px; }
.settings-link .ic { flex: none; width: 18px; height: 18px; color: var(--ink-soft); }
.decision-help { display: block; max-width: 36ch; margin-top: 4px; font-size: var(--text-caption); font-weight: 400; line-height: 1.4; color: var(--ink-soft); white-space: normal; text-wrap: balance; }
.team-label { color: var(--ink); font-weight: 600; }
/* Equal side columns keep the status on the table's axis, over the hand, whatever the buttons' width. */
html:not(.phone) .table:not(.lobby) .console { display: grid; grid-template-columns: minmax(max-content, 1fr) minmax(0, 2fr) minmax(max-content, 1fr); gap: 20px; }
html:not(.phone) .table:not(.lobby) .console .player.me { grid-column: 1; justify-self: start; }
html:not(.phone) .table:not(.lobby) .console .status { grid-column: 2; justify-self: center; width: min(100%, 560px); text-align: center; white-space: normal; }
html:not(.phone) .table:not(.lobby) .console .actions { grid-column: 3; justify-self: end; }
html:not(.phone) .table:not(.lobby) .status-detail { white-space: normal; }
html:not(.phone) .console .status .turn-label, html:not(.phone) .console .status .decision-help { margin-inline: auto; }
/* Too narrow to centre on the table without squeezing it, so the status sits beside my avatar. */
@media (max-width: 1100px) {
  html:not(.phone) .table:not(.lobby) .console { grid-template-columns: auto minmax(0, 1fr) auto; gap: 16px; }
  html:not(.phone) .table:not(.lobby) .console .status { justify-self: start; text-align: left; }
  html:not(.phone) .console .status .turn-label, html:not(.phone) .console .status .decision-help { margin-inline: 0; }
}
.appearance-options { border: 0; padding: 0; margin: 0 0 16px; min-width: 0; }
.appearance-options legend { padding: 0; margin-bottom: 8px; font-size: 13px; font-weight: 600; }
.style-options { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.style-options.card-options { grid-template-columns: 1fr; gap: 8px; }
.style-choice.card-choice { flex-direction: row; justify-content: space-between; min-height: 112px; padding: 14px; gap: 14px; font-size: 13px; white-space: normal; }
.card-choice > span:last-child { min-width: 0; text-align: right; }
.style-choice { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 9px; min-width: 0; min-height: 76px; padding: 10px 3px; border-radius: var(--radius-panel); font-size: 11px; }
.style-choice[aria-pressed="true"] { border-color: var(--ink); background: var(--paper-2); box-shadow: inset 0 0 0 1px var(--ink); }
.font-sample { font-family: var(--ui-font); font-size: 28px; line-height: 1; font-weight: 600; }
.deck-preview { display: flex; justify-content: center; gap: 4px; pointer-events: none; }
.deck-preview .card { --cw: 42px; --ch: 60px; }

/* ---------- result ---------- */
.overlay { position: fixed; inset: 0; background: var(--scrim); display: flex; align-items: center; justify-content: center; padding: max(16px, env(safe-area-inset-top, 0px)) max(16px, env(safe-area-inset-right, 0px)) max(16px, env(safe-area-inset-bottom, 0px)) max(16px, env(safe-area-inset-left, 0px)); z-index: 10; -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.modal { background: var(--panel); border-radius: var(--radius-dialog); padding: var(--space-6); max-width: 400px; width: 100%; text-align: center; box-shadow: var(--shadow-dialog); max-height: 100%; overflow: auto; animation: popIn var(--motion-panel) ease-out; }
.modal h2 { margin: 0 0 4px; font-size: 30px; font-weight: 900; color: var(--ink); }
.modal h2.won { color: var(--ink-2); }
.modal .sub { color: var(--ink-soft); font-size: var(--text-label); line-height: 1.4; }
.modal .spring { color: var(--alert); font-weight: 700; }
.modal .deltas { margin: 18px 0; display: flex; flex-direction: column; gap: 8px; font-size: 15px; }
.modal .deltas div { display: flex; justify-content: space-between; gap: 8px; align-items: center; }
.modal .deltas .who { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.modal .deltas .who .disc { width: 26px; height: 26px; }
.modal .deltas .tag { font-size: 11px; font-weight: 700; color: var(--paper); background: var(--ink); padding: 1px 7px; border-radius: 999px; }
.modal .deltas .tag.x2 { background: var(--alert); }
.modal .deltas .tag.place.first { background: var(--seal); color: var(--on-seal); }
.modal .deltas .tag.place.last { background: transparent; color: var(--ink-soft); box-shadow: inset 0 0 0 1px var(--line); }
.modal .deltas .tag.mate { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink-faint); }
.modal .sub .sub-line { display: block; word-break: keep-all; text-wrap: balance; } /* Chinese breaks at its spaces, not inside 每人 */
.modal .deltas .nums { display: flex; align-items: baseline; gap: 12px; font-variant-numeric: tabular-nums; }
.modal .deltas .total { color: var(--ink-soft); font-size: 12px; min-width: 70px; text-align: right; }
.pos { color: var(--ink); font-weight: 700; }
.neg { color: var(--alert); font-weight: 700; }
.modal .buttons { display: flex; gap: 8px; justify-content: center; align-items: center; flex-wrap: wrap; }

/* Results give the player their score first; the table's complete scores follow. */
.result-modal { border: 1px solid var(--line); animation: resultIn 0.28s ease-out; }
.result-modal.won { border-color: rgba(178, 141, 66, 0.55); background: radial-gradient(ellipse at 50% 0%, rgba(206, 171, 87, 0.16), transparent 65%), var(--panel); }
.result-head { display: flex; flex-direction: column; align-items: center; }
/* The winning side's seal: two brush characters in a square chop, red when it is mine. */
.result-seal { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 48px; height: 48px; margin-bottom: 10px; border-radius: 5px; rotate: -5deg; font: 19px/1.02 var(--ink-font); color: var(--on-seal); background: var(--seal); box-shadow: inset 0 0 0 1.5px var(--seal), inset 0 0 0 3px var(--on-seal); }
.result-seal b { font-weight: 400; }
.result-seal.ink { color: var(--paper); background: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink), inset 0 0 0 3px var(--paper); }
.result-modal.won .result-seal { animation: winnerIn 0.55s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
.result-modal h2 { font-size: 28px; line-height: 1.12; }
.round-score { display: flex; flex-direction: column; gap: 2px; margin: 12px 0; }
.round-score strong { font-size: 44px; line-height: 1; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; }
.round-score > span { font-size: var(--text-caption); color: var(--ink-soft); }
.result-modal .deltas { gap: 4px; }
.result-modal .deltas .result-row { padding: 9px 10px; border-radius: 10px; animation: scoreIn 0.25s ease-out both; animation-delay: calc(80ms + var(--row) * 45ms); }
.result-row.self { background: var(--paper-2); }
.result-modal .deltas .result-side { flex-direction: column; align-items: stretch; gap: 2px; }
.result-modal .deltas .side-name { padding: 0 10px; font-size: 11px; font-weight: 600; color: var(--ink-soft); text-align: left; }
.result-modal .deltas .result-side + .result-side { margin-top: 4px; }
.result-modal .result-note { max-width: 32ch; margin: 6px 0 0; font-size: 12px; line-height: 1.35; color: var(--ink-soft); }
.result-modal .deltas .result-row.team-break { position: relative; margin-top: 7px; }
.result-modal .deltas .result-row.team-break::before { content: ""; position: absolute; left: 10px; right: 10px; top: -5px; border-top: 1px solid var(--line); }
.result-modal .deltas .who { min-width: 0; flex-wrap: wrap; gap: 6px; }
.result-modal .deltas .name { max-width: 100px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.result-modal .deltas .nums { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.result-modal .delta { font-size: 21px; line-height: 1.1; font-variant-numeric: tabular-nums; }
.result-row.winner .delta { font-weight: 800; }
.result-modal .deltas .total { min-width: 0; font-size: 11px; }
.result-modal .buttons { margin-top: 16px; }
.result-modal .buttons button { min-height: 44px; }
/* Next round across the card; the review and a look at the table side by side above it. */
.result-modal .result-buttons { display: grid; grid-template-columns: 1fr 1fr; }
.result-buttons > .primary, .result-buttons > [data-mm] { grid-column: 1 / -1; min-height: 48px; }
.result-buttons > [data-act="dismiss"]:first-child { grid-column: 1 / -1; }
/* A result taller than the screen (a row of new seals on a laptop, both sides named) scrolls,
   and Next round stays in reach: the buttons keep to the card's bottom edge and the rows fade
   under them. Where it all fits, nothing shows of this. (Short landscape screens lay the result
   out in two columns instead.) */
@media (min-height: 521px) {
  .result-modal { scroll-padding-bottom: 150px; } /* a control tabbed to scrolls clear of them */
  .result-modal .result-buttons {
    position: sticky;
    bottom: calc(-1 * var(--space-6)); /* the card's own edge, not its padding's */
    z-index: 1;
    margin: 4px calc(-1 * var(--space-6)) calc(-1 * var(--space-6));
    padding: 12px var(--space-6) var(--space-6);
    background: linear-gradient(to bottom, transparent, var(--panel) 12px);
  }
}
@media (max-height: 480px) {
  .result-modal { max-width: 580px; padding: 14px 22px; display: grid; grid-template-columns: 160px minmax(0, 1fr); column-gap: 24px; }
  .result-head { grid-column: 1; grid-row: 1; align-items: flex-start; text-align: left; align-self: center; }
  .result-seal { width: 38px; height: 38px; font-size: 15px; margin-bottom: 6px; }
  .result-modal h2 { font-size: 25px; }
  .round-score { margin: 10px 0; }
  .round-score strong { font-size: 38px; }
  .result-modal .deltas { grid-column: 2; grid-row: 1; margin: 0; align-self: center; }
  .result-modal .deltas .result-row { padding: 5px 8px; }
  .result-modal .delta { font-size: 19px; }
  .result-modal .buttons { grid-column: 1 / -1; margin-top: 12px; }
  .result-modal .result-buttons { grid-template-columns: auto auto minmax(0, 1fr); }
  .result-buttons > .primary, .result-buttons > [data-mm], .result-buttons > [data-act="dismiss"]:first-child { grid-column: auto; }
}

/* ---------- effects ---------- */
.card.flip { transition: transform 0.26s cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 0.12s, opacity 0.2s; z-index: 3; }
.console .status.turn-cue .turn-label { animation: turnCue 1.2s ease-out; }
/* above the table and its bubbles, below sheets (20) and the result overlay (10) */
.fx-layer { position: fixed; inset: 0; pointer-events: none; z-index: 8; overflow: hidden; }
.fx-layer .fly { position: absolute; --cw: 29.4px; --ch: 42px; transition: transform 0.3s cubic-bezier(0.3, 0.7, 0.4, 1), opacity 0.1s 0.2s; }
.fx-layer .ghost { position: absolute; margin: 0; transition: transform 0.28s cubic-bezier(0.3, 0.7, 0.4, 1), opacity 0.2s 0.08s; }

.toast {
  position: fixed;
  left: 50%;
  top: 50px; /* under the header, where the table is empty; never over the hand or the buttons */
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--paper);
  padding: 10px 18px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  z-index: 60;
  box-shadow: 0 6px 24px rgba(31, 59, 143, 0.3);
  max-width: 90vw;
  pointer-events: none; /* a tap meant for a control underneath still reaches it */
}
.toast.hidden { display: none; }
/* A four-seat table has a seat right there, across from mine, and the middle of its table fills
   with controls (the lobby's, the partner-card picker, the reactions to a play): the toast keeps
   to the header's middle instead, as it does on a phone held sideways. */
html:not(.phone) body:has(.table.four) .toast { top: 8px; max-width: min(44vw, 560px); text-align: center; }

/* ---------- keyframes ---------- */
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
@keyframes pop { from { transform: translateY(-100%) scale(0.7); opacity: 0; } to { transform: translateY(-100%) scale(1); opacity: 1; } }
@keyframes popIn { from { transform: scale(0.6); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes tilt { 0%, 15% { transform: rotate(0); } 55%, 100% { transform: rotate(-90deg); } }
@keyframes dealIn { from { opacity: 0; translate: 0 18px; } to { opacity: 1; translate: 0 0; } }
@keyframes turnCue { 0% { opacity: 0.5; transform: translateY(5px); } 35% { opacity: 1; transform: translateY(0); } 100% { opacity: 1; transform: translateY(0); } }
@keyframes turnSpotlight { 0%, 100% { opacity: 0.78; } 50% { opacity: 1; } }
@keyframes turnGlowPulse { 0%, 100% { opacity: 0; } 32% { opacity: 1; } 52% { opacity: 0.85; } }
@keyframes resultIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes winnerIn { 0% { transform: scale(0.8) rotate(-12deg); } 65% { transform: scale(1.08) rotate(4deg); } 100% { transform: scale(1) rotate(0); } }
@keyframes scoreIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes flipIn { from { transform: rotateY(90deg); opacity: 0.4; } 60% { transform: rotateY(-12deg); opacity: 1; } to { transform: rotateY(0); } }
@keyframes breathe { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.4; transform: scale(1.07); } }
@media (prefers-reduced-motion: reduce) {
  .hand-row .card.dealt, .bottom-cards .card.flip, .modal, .sheet, .player.turn .avatar-wrap::after { animation: none; }
  .table.spotlight::before { transition: none; animation: none; }
  html.phone .table.spotlight.turn-arrival::after { animation: none; opacity: 0.8; }
  .hand .hand-row .card { transition: none; }
  .fx-layer .fly { transition: none; opacity: 0; }
  .console .status.turn-cue .turn-label, .result-modal, .result-modal.won .result-seal, .result-modal .deltas .result-row { animation: none; }
}

/* ---------- desktop and tablet ---------- */
.selection-summary { width: 100%; min-width: 0; min-height: 44px; padding: 2px 0; border: 0; border-radius: 6px; background: transparent; color: var(--ink); display: flex; flex-direction: column; justify-content: center; gap: 3px; text-align: left; font-size: 13px; }
.selection-summary:hover:not(:disabled) { background: transparent; color: var(--ink); }
.selection-caption { width: 100%; display: flex; align-items: center; gap: 8px; color: var(--ink-soft); font-size: 11px; line-height: 14px; }
.selection-caption .turn-label { margin: 0; }
.selection-detail { display: flex; width: 100%; min-width: 0; align-items: center; gap: 6px; line-height: 18px; }
.selection-count { flex: none; font-weight: 700; }
.selection-combo { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-soft); }
.selection-summary.invalid .selection-count { display: none; }
.selection-summary.invalid .selection-combo { white-space: normal; font-size: 12px; line-height: 14px; color: var(--ink); }
html.phone .sheet.selection-sheet { top: auto; bottom: max(10px, env(safe-area-inset-bottom)); max-height: min(70%, calc(100% - 64px - env(safe-area-inset-bottom))); }
.selection-sheet .sheet-head { position: sticky; top: 0; z-index: 2; background: var(--panel); box-shadow: 0 -16px 0 var(--panel); }
.selection-sheet .sheet-head button { min-width: 44px; min-height: 44px; }
.selection-verdict { margin: 0 0 6px; font-size: 15px; font-weight: 700; }
.selection-note { margin: 0 0 14px; font-size: 13px; color: var(--ink-soft); }
.selection-cards { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px 8px; }
.selection-card { flex: none; display: block; padding: 0; border: 0; border-radius: var(--card-radius); background: transparent; }
.selection-card .card { --ch: 112px; --cw: 78.4px; pointer-events: none; }

html.desktop .table { overflow: auto; }
html.has-hover .hand-row .card:hover { transform: translateY(-6px); }
html.has-hover .hand-row .card.selected:hover { transform: translateY(calc(-18px + var(--drag-lift, 0px))); }

/* ---------- between players: throws, reactions, nudges ---------- */
.player .avatar-wrap[data-act="seat"] { cursor: pointer; }
body.aiming .player .avatar-wrap[data-act="seat"] { cursor: crosshair; }
body.aiming .player .avatar-wrap[data-act="seat"] .avatar { box-shadow: 0 0 0 3px var(--accent); animation: pulse 0.9s ease-in-out infinite; }
/* What landed on someone: a hit covers the face, a gift sits at the shoulder. */
.player .splat { position: absolute; inset: -5px; z-index: 2; pointer-events: none; animation: splatIn 0.25s cubic-bezier(0.2, 1.4, 0.4, 1) backwards; }
.player .splat .obj { width: 100%; height: 100%; display: block; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25)); }
.player .splat.tomato { transform: rotate(-12deg); }
.player .splat.egg { transform: rotate(14deg); }
.player .splat.flower, .player .splat.tea, .player .splat.hongbao, .player .splat.beer { inset: 0 -10px auto auto; width: 30px; height: 30px; animation-name: popIn; } /* not above it: the field clips there */
.side.right .player .splat.flower, .side.right .player .splat.tea, .side.right .player .splat.hongbao, .side.right .player .splat.beer { inset: 0 auto auto -10px; }
html.phone .player .splat { inset: -8px; } /* a small avatar wears a bigger hit */
.player .avatar-wrap.nudged { animation: nudge 0.6s ease-in-out; }
.player .avatar-wrap.bonked .avatar { animation: bonk 0.45s ease-out; }
.player .avatar-wrap.flash .avatar { animation: flash 0.3s ease-out 3; }
.table.shake { animation: shake 0.45s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }
/* Juice: a hit sprays drops from where it landed and they vanish; when it is me, a few
   smears cling to the screen and drip, the edges flush with the colour, and the upper table
   jolts. The hand and the buttons never move and nothing here can be tapped. */
.fx-layer .drop { position: absolute; border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%; will-change: transform, opacity; }
.fx-layer .smear { position: absolute; opacity: 0; will-change: transform, opacity; }
.fx-layer .smear .obj { width: 100%; height: 100%; display: block; }
.fx-layer .flush { position: absolute; inset: 0; opacity: 0; }
.field.jolt { animation: jolt 0.35s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }
@keyframes jolt {
  20% { transform: translate(-3px, 1px) rotate(-0.3deg); }
  45% { transform: translate(3px, -1px) rotate(0.3deg); }
  70% { transform: translate(-2px, 0); }
}
.fx-layer .missile { position: absolute; width: 36px; height: 36px; }
/* The chicken gag, drawn in pixels about the middle of its body; each part moves about its own origin. */
.fx-layer .gag { position: absolute; width: 0; height: 0; opacity: 0; }
.fx-layer .gag-svg { position: absolute; overflow: visible; filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.22)); }
.fx-layer .gag-eye { transform-box: fill-box; transform-origin: center; }
.fx-layer .gag-jaw { transform-box: fill-box; transform-origin: 0 0; }
.fx-layer .gag-body { transform-origin: 0 20px; } /* its feet */
.fx-layer .gag-squeeze, .fx-layer .gag-pop { transform-box: fill-box; transform-origin: center; }
.fx-layer .gag-comb { transform-origin: 0 -8px; }
.fx-layer .gag-wattle { transform-box: fill-box; transform-origin: 50% 0; }
.fx-layer .gag-pop { font-weight: 800; font-size: 12px; fill: #d8352c; stroke: #fffaf0; stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; letter-spacing: 0.02em; }
.fx-layer .missile .lob { width: 100%; height: 100%; }
.fx-layer .missile .obj { width: 100%; height: 100%; display: block; filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.3)); }
.fx-layer .reaction { position: absolute; width: 32px; height: 32px; color: var(--ink); opacity: 0; }
.fx-layer .reaction.fire { color: var(--alert); }
.fx-layer .reaction svg.ic { width: 100%; height: 100%; }
.fx-layer .ripple { position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px; border-radius: 50%; border: 2px solid var(--ink); opacity: 0; animation: ripple 0.55s ease-out forwards; }
.fx-layer .ripple.late { animation-delay: 0.14s; }
.obj-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.obj-btn { width: 46px; height: 46px; padding: 6px; display: grid; place-items: center; }
.obj-btn .obj, .obj-btn svg.ic { width: 28px; height: 28px; display: block; }
.nudge-btn { display: inline-flex; align-items: center; gap: 6px; height: 46px; padding: 0 12px 0 10px; }
.nudge-btn svg.ic { width: 18px; height: 18px; }
.sheet.emotes h4 + .emote-grid, .sheet.emotes .obj-row + .emote-grid { margin-top: 14px; }
.current .cards[data-act] { cursor: pointer; }
.react-bar { display: flex; gap: 6px; justify-content: center; margin-top: 2px; animation: popIn 0.15s ease-out; }
.react-bar .obj-btn { width: 40px; height: 40px; padding: 4px; }
.react-bar .obj-btn svg.ic { width: 22px; height: 22px; }
@keyframes splatIn { from { transform: scale(1.8); opacity: 0; } to { opacity: 1; } }
@keyframes bonk {
  25% { transform: scale(1.14, 0.78); }
  55% { transform: scale(0.94, 1.06); }
  80% { transform: scale(1.02, 0.98); }
}
@keyframes ripple { from { transform: scale(0.6); opacity: 0.7; } to { transform: scale(3.2); opacity: 0; } }
@keyframes nudge {
  0%, 100% { transform: translateX(0) rotate(0); }
  15% { transform: translateX(-5px) rotate(-8deg); }
  30% { transform: translateX(5px) rotate(8deg); }
  45% { transform: translateX(-4px) rotate(-6deg); }
  60% { transform: translateX(4px) rotate(6deg); }
  75% { transform: translateX(-2px) rotate(-3deg); }
}
@keyframes flash { 0%, 100% { filter: none; } 50% { filter: brightness(1.6) drop-shadow(0 0 8px var(--alert)); } }
@keyframes shake {
  10%, 90% { transform: translate(-1px, 0); }
  20%, 80% { transform: translate(2px, 1px); }
  30%, 50%, 70% { transform: translate(-4px, -1px); }
  40%, 60% { transform: translate(4px, 1px); }
}
@media (prefers-reduced-motion: reduce) {
  .player .splat, .player .avatar-wrap.nudged, .player .avatar-wrap.bonked .avatar, .player .avatar-wrap.flash .avatar, .table.shake, .react-bar,
  body.aiming .player .avatar-wrap[data-act="seat"] .avatar { animation: none; }
}
/* Side and top seats sit against the top edge, so their bubbles hang below the name instead. */
.side .bubble, .across .bubble { top: auto; bottom: -8px; transform: translateY(100%); }
.side .bubble::after, .across .bubble::after { bottom: auto; top: -6px; border-top: 0; border-bottom: 6px solid var(--ink); }
/* A phone's bubbles wrap rather than run off a narrow screen; mine rises from the small avatar. */
html.phone .bubble { white-space: normal; width: max-content; max-width: min(240px, calc(100vw - 72px)); }
html.phone .console .bubble { left: 44px; top: auto; bottom: calc(100% + var(--bubble-rise, 0px) + 8px); }

/* ---------- moments: ink, seals, the stakes, last cards, the fuse, the finale ---------- */
/* The stake: a seal with the multiplier, and a firecracker on the string for each doubling.
   At ×1 the seal is only an outline; once the stake doubles it is printed in red. */
.topbar .stakes { flex: none; display: flex; align-items: center; gap: 4px; height: 100%; pointer-events: none; }
.stake-seal { display: grid; place-items: center; min-width: 28px; height: 22px; padding: 0 5px; border-radius: 3px; rotate: -4deg; font: 700 12px/1 var(--serif); font-variant-numeric: lining-nums tabular-nums; letter-spacing: -0.02em; color: var(--ink-soft); box-shadow: inset 0 0 0 1.25px var(--ink-faint); }
.stakes.raised .stake-seal { background: var(--seal); color: var(--on-seal); box-shadow: inset 0 0 0 1.5px var(--seal), inset 0 0 0 2.5px var(--on-seal); }
.stakes .string { position: relative; display: flex; align-items: flex-start; gap: 2px; height: 22px; padding: 2px 3px 0; }
.stakes .string::before { content: ""; position: absolute; left: 0; right: 0; top: 2px; height: 1.5px; border-radius: 1px; background: var(--ink-faint); }
/* A firecracker's paper is its own red, the same by day and by night, with gold caps. */
.cracker { display: block; width: 6px; height: 14px; margin-top: 1px; border-radius: 1.5px; background: linear-gradient(90deg, #99091f, #dd2a41 45%, #b1142b); box-shadow: inset 0 1.5px 0 var(--gold), inset 0 -1.5px 0 var(--gold); transform-origin: 50% 0; rotate: -12deg; }
.stakes .cracker:nth-child(odd), .fx-layer .cracker.odd { rotate: 12deg; }
.fx-layer .cracker.spent { position: absolute; margin: 0; }
/* 报单 and 报双: the call on a player's last one or two cards, printed beside the count. */
.call-seal { display: inline-block; margin-left: 5px; padding: 2px 4px 1px; border-radius: 2px; rotate: -5deg; font: 1.2em/1.05 var(--ink-font); color: var(--on-seal); background: var(--seal); vertical-align: middle; }
/* An opponent on their last card: the table's pool of light narrows, and a short fuse sparks
   by their count. Calm keeps the count and the call, and drops the rest. */
.table-dim { position: absolute; inset: 0; z-index: -1; pointer-events: none; opacity: 0; transition: opacity 1.2s ease; background: radial-gradient(ellipse 72% 66% at 50% 46%, transparent 38%, var(--dim-edge) 100%); }
.table.last-call .table-dim { opacity: 1; }
.player.last-card .remaining { position: relative; }
.player.last-card .remaining::after { content: ""; position: absolute; left: -1px; top: -1px; width: 5px; height: 5px; border-radius: 50%; background: radial-gradient(#fff6c8, #ffb020 55%, transparent 72%); box-shadow: 0 0 6px 2px rgba(255, 150, 40, 0.6); animation: wick 0.5s steps(2) infinite; }
@keyframes wick { 50% { transform: scale(0.6) translate(1px, -1px); opacity: 0.7; } }
html[data-effects="calm"] .table-dim, html[data-effects="calm"] .player.last-card .remaining::after { display: none; }
/* The turn's fuse along the top of my hand, burning towards Pass; its ember reddens at the end.
   It stands in for the ring on my own avatar. */
.table .fuse { position: absolute; z-index: 6; height: 4px; pointer-events: none; }
.table .fuse[hidden] { display: none; }
.fuse .ash { position: absolute; inset: 1px 0; background: repeating-linear-gradient(90deg, var(--ink-faint) 0 3px, transparent 3px 7px); }
.fuse .rope { position: absolute; inset: 0; border-radius: 2px; background: repeating-linear-gradient(60deg, #a4712f 0 3px, #dba65a 3px 6px); transform-origin: 100% 50%; transition: transform 0.25s linear; }
.fuse .ember { position: absolute; left: -4px; top: -2px; width: 8px; height: 8px; border-radius: 50%; background: radial-gradient(#fff6c8, #ffb020 50%, #e2461c); box-shadow: 0 0 8px 3px rgba(255, 160, 40, 0.7); transition: transform 0.25s linear; }
.fuse.hot .ember { box-shadow: 0 0 12px 5px rgba(255, 70, 40, 0.75); animation: ember 0.14s steps(2) infinite; }
@keyframes ember { 50% { filter: brightness(1.45); } }
.table.fuse-on .console .player.me .ring { display: none; }
/* Over the table: brush lettering, seals coming down, stand-in cards in flight, sparks, paper
   and smoke. The finale's layer sits above the result (10) and below the sheets (20). */
.fx-over { position: fixed; inset: 0; pointer-events: none; z-index: 12; overflow: hidden; }
.fx-layer .ink { position: absolute; translate: -50% -50%; font-family: var(--ink-font); line-height: 1; white-space: nowrap; letter-spacing: 0.02em; text-shadow: 0 0 1px currentColor; }
.fx-layer .stamp {
  position: absolute; display: grid; place-items: center; writing-mode: vertical-rl; letter-spacing: 0.06em;
  border: 0; padding: 0; opacity: 1; /* none of the history's pass stamp (.stamp) */
  border-radius: 5px; font-family: var(--ink-font); font-weight: 400; line-height: 1; color: var(--on-seal);
  background-color: var(--seal);
  background-image: radial-gradient(circle at 22% 30%, rgba(255, 255, 255, 0.22) 0 1px, transparent 2px), radial-gradient(circle at 74% 64%, rgba(255, 255, 255, 0.18) 0 1.5px, transparent 2.5px), radial-gradient(circle at 42% 82%, rgba(0, 0, 0, 0.14) 0 1px, transparent 2px);
  box-shadow: inset 0 0 0 2px var(--seal), inset 0 0 0 3.5px var(--on-seal), 0 6px 18px -8px rgba(80, 0, 10, 0.6);
}
.fx-layer .stamp.ally { background-color: var(--panel); color: var(--seal); box-shadow: inset 0 0 0 2px var(--seal), 0 6px 18px -8px rgba(80, 0, 10, 0.5); }
.fx-layer .actor { position: absolute; margin: 0; z-index: 1; }
:is(.fx-layer, .fx-over) .spark { position: absolute; border-radius: 50%; }
:is(.fx-layer, .fx-over) .shred { position: absolute; width: 6px; height: 10px; margin: -5px 0 0 -3px; border-radius: 1px; }
:is(.fx-layer, .fx-over) .shred.star { width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px; border-radius: 0; clip-path: polygon(50% 0, 61% 36%, 100% 38%, 69% 60%, 80% 100%, 50% 76%, 20% 100%, 31% 60%, 0 38%, 39% 36%); }
.fx-layer .ink-ring { position: absolute; border-radius: 50%; border-style: solid; }
.fx-layer .edge-flush { position: absolute; inset: 0; opacity: 0; }
.fx-layer .plane { position: absolute; left: 0; top: 0; width: 30px; height: 30px; offset-rotate: auto; }
.fx-layer .plane svg { display: block; width: 100%; height: 100%; }
.fx-layer .trail { position: absolute; width: 4px; height: 4px; border-radius: 50%; background: var(--ink-faint); }
.fx-layer .smoke { position: absolute; border-radius: 50%; background: var(--ink-faint); }
.fx-over .petal { position: absolute; width: 9px; height: 9px; border-radius: 60% 0 60% 0; background: var(--seal); }
/* A spring: an ink plum branch with red blossoms and the word in brush, hung beside the result
   (to its left, or above it on a narrow screen), never over the scores. */
.overlay .spring-art { position: fixed; z-index: 1; display: flex; gap: 4px; pointer-events: none; visibility: hidden; }
.overlay .spring-art:is(.side, .above) { visibility: visible; }
.spring-art .spring-branch { flex: none; width: var(--branch, 160px); height: var(--branch, 160px); overflow: visible; }
.spring-art.side { flex-direction: column; align-items: flex-end; width: var(--branch); }
.spring-art.side .spring-branch { transform: scaleX(-1); }
.spring-art.above { align-items: flex-end; height: var(--branch); }
.spring-art .ink-spring { font: 44px/1 var(--ink-font); color: var(--seal); letter-spacing: 0.06em; rotate: -6deg; }
.spring-art.side .ink-spring { writing-mode: vertical-rl; margin-right: 34px; }
.spring-art.above .ink-spring { margin-bottom: 22px; }
.spring-branch path { fill: none; stroke: var(--ink); stroke-width: 2.2; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 0; opacity: 0.8; }
.spring-branch path.bough { stroke-width: 3.6; }
.spring-branch .blossom { fill: var(--seal); transform-box: fill-box; transform-origin: center; }
.spring-branch .blossom .heart { fill: var(--gold); }
@media (prefers-reduced-motion: reduce) {
  .player.last-card .remaining::after, .fuse.hot .ember { animation: none; }
  .table-dim, .fuse .rope, .fuse .ember { transition: none; }
}

/* ---------- the card tracker (记牌器) ---------- */
/* One cell per rank, jokers first: the rank over how many are still in other hands. A rank
   that has gone is faint; a red dot marks one that could still make a bomb. */
.topbar .tracker { flex: none; display: flex; align-items: stretch; height: 30px; margin-inline: auto; padding: 0 3px; border-radius: 8px; background: var(--paper-2); box-shadow: inset 0 0 0 1px var(--line); pointer-events: none; }
.tracker .tk { position: relative; flex: none; width: 22px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; }
.tracker .tk i { display: block; font: 600 11px/1 var(--rank-font); font-style: normal; color: var(--ink-soft); }
.tracker .tk b { display: block; font-size: 12px; line-height: 1; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ink); }
.tracker .tk.joker i .ic { display: block; width: 11px; height: 11px; }
.tracker .tk.joker.red i { color: var(--seal); }
.tracker .tk.out { opacity: 0.32; }
.tracker .tk.threat::after { content: ""; position: absolute; top: 3px; right: 2px; width: 4px; height: 4px; border-radius: 50%; background: var(--seal); }

/* ---------- the seal album (印谱) ---------- */
/* A seal is two characters read top to bottom, printed in red; one not yet earned is only
   its outline. */
.seal-art { flex: none; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; width: var(--seal-size, 34px); height: var(--seal-size, 34px); border-radius: 4px; rotate: -4deg; font: calc(var(--seal-size, 34px) * 0.4) / 1 var(--ink-font); color: var(--on-seal); background: var(--seal); box-shadow: inset 0 0 0 1.5px var(--seal), inset 0 0 0 2.75px var(--on-seal); }
.seal-art b { display: block; font-weight: 400; line-height: 1.02; }
.result-modal button.round-seals { display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start; gap: 6px 12px; min-height: 0; margin: 0 0 10px; padding: 4px 6px; border: 0; border-radius: 10px; white-space: normal; }
.result-modal button.round-seals:hover { background: var(--paper-2); }
.round-seal { display: flex; flex-direction: column; align-items: center; gap: 4px; --seal-size: 34px; }
.round-seal small { max-width: 72px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-micro); font-weight: 700; color: var(--ink-soft); }
.round-seal.fresh small { color: var(--seal); }
/* A short screen keeps the seals and drops their names; a first one keeps a red dot. */
@media (max-height: 480px) {
  .result-modal button.round-seals { justify-content: flex-start; margin: 0 0 6px; padding: 2px; gap: 6px; }
  .round-seal { position: relative; --seal-size: 26px; }
  .round-seal small { display: none; }
  .round-seal.fresh::after { content: ""; position: absolute; top: -3px; right: -3px; width: 7px; height: 7px; border-radius: 50%; background: var(--seal); box-shadow: 0 0 0 2px var(--panel); }
}
.sheet.album-sheet { width: min(380px, calc(100vw - 28px)); }
.modal.album-modal { max-width: 420px; padding: var(--space-4); text-align: left; }
.album-head { display: flex; align-items: center; gap: 14px; margin-bottom: 10px; }
.album-title { font: 34px/1 var(--ink-font); color: var(--seal); rotate: -4deg; }
.album-head p { margin: 0; display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
.album-head p span { font-size: var(--text-caption); color: var(--ink-soft); }
.album-grid { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.album-seal { display: flex; align-items: center; gap: 12px; padding: 6px 8px; border-radius: 10px; --seal-size: 40px; }
.album-seal.got { background: var(--paper-2); }
.album-seal:not(.got) .seal-art { background: transparent; color: var(--ink-faint); box-shadow: inset 0 0 0 1.25px var(--ink-faint); rotate: none; }
.album-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; text-align: left; }
.album-text b { font-size: 14px; }
.album-text span { font-size: var(--text-caption); line-height: 1.35; color: var(--ink-soft); }
.album-text .album-meta { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--seal); }
/* On the home page the album opens over the page. */
.home-sheet .sheet { position: fixed; top: max(16px, env(safe-area-inset-top, 0px)); max-height: calc(100% - 32px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)); z-index: 30; }

/* ---------- between screens ---------- */
/* The home page's hand becomes the table's hand and its header the table's top bar; the
   rest of the page crossfades underneath. See render() in app/view.js. */
html.screen-move :is(.home-hand, .table .hand) { view-transition-name: hand; }
html.screen-move :is(.home-bar, .table .topbar) { view-transition-name: bar; }
::view-transition-group(hand) { animation-duration: 0.42s; animation-timing-function: cubic-bezier(0.3, 0.7, 0.3, 1); }
::view-transition-group(bar), ::view-transition-group(root) { animation-duration: 0.32s; }
::view-transition-old(hand), ::view-transition-new(hand) { height: 100%; object-fit: cover; object-position: 50% 100%; }
