/* ═══════════════════════════════════════════════════════════════
   BotV8-WebLive — ธีมและผังจอ
   ───────────────────────────────────────────────────────────────
   ⚠️ ทุกเอฟเฟกต์ทำด้วย transform/opacity เท่านั้น (GPU)
      ขยับ top/left/width = มือถือรุ่นกลางกระตุกทันทีที่มีชิป 20 ตัวบนจอ

   ⚠️ ใช้ dvh ไม่ใช่ vh — แถบ URL ของ Safari บนมือถือทำให้ vh เพี้ยน
      จนกระดานถูกดันตกขอบจอ (มี fallback vh ไว้ให้เบราว์เซอร์เก่า)

   ⚠️ ทุกปุ่ม ≥ 44×44 px และ touch-action: manipulation
      เกมสดคนแตะรัวที่ปุ่มเดิม เบราว์เซอร์จะตีความเป็น double-tap แล้วซูมเข้า
   ═══════════════════════════════════════════════════════════════ */

/* ── โทเคนสี ─────────────────────────────────────────────────── */
:root {
  --bg:        #070a10;
  --bg-2:      #0d131d;
  --panel:     #121a26;
  --panel-2:   #18222f;
  --line:      #223047;
  --ink:       #e8eef7;
  --ink-dim:   #93a3b8;
  --ink-faint: #5d6f88;

  /**
   * 🔴 **เสือ = น้ำเงิน · มังกร = แดง**
   *    เป็นสีที่ลูกค้าเห็นในรูปผลของไลน์ (`td1.png` / `td2.png`) มาตลอด
   *    เคยตั้งเสือเป็นสีส้มทองเพราะดูเข้ากับธีม — แต่ผู้เล่นอ่านสีก่อนอ่านตัวอักษร
   *    สีไม่ตรงกับที่จำได้ = อ่านกระดานผิดฝั่งในเกมที่เดิมพันด้วยเงินจริง
   */
  --tiger:     #3b6fe0;
  --tiger-2:   #1e40af;
  --dragon:    #ef4444;
  --dragon-2:  #991b1b;
  --pair:      #a855f7;
  --tie:       #22c55e;
  --gold:      #fbbf24;
  --danger:    #f43f5e;
  --line-green:#06c755;

  --accent:    #38bdf8;
  --accent-2:  #0ea5e9;

  /**
   * ⭐ **ชั้นทับพื้นผิว — ค่าสำรองเป็นชุด "ธีมมืด"**
   *    ตัวจริงมาจาก `LiveTheme` ซึ่งพลิกให้เองเมื่อเลือกธีมโทนสว่าง
   *    (ชั้นมืดบนพื้นขาว = คราบเทา · ประกายขาวบนพื้นขาว = หายไปเฉย ๆ)
   */
  --veil:  rgba(0, 0, 0, .26);
  --sheen: rgba(255, 255, 255, .05);

  /**
   * ⭐ **พื้นช่องกรอกที่ลอยอยู่บนไลฟ์** — ต้องพลิกตามโทนเหมือน `--veil`
   *
   * 🔴 ค่าสำรองต้องเป็น `rgba(0,0,0,.55)` เท่าของเดิมเป๊ะ
   *    เปลี่ยนเป็นสีที่ "ดูดีกว่า" = ช่องแชทของบอทที่ใช้อยู่เปลี่ยนสีทันที
   *    (จับได้ตอนเทียบภาพก่อน/หลังทีละพิกเซล — ต่างกัน 3.9% ของทั้งหน้า)
   */
  --field-bg:   rgba(0, 0, 0, .55);
  --field-line: rgba(255, 255, 255, .18);

  /**
   * 🔴 **สีตัวอักษรที่ทับอยู่บน "วิดีโอ" ต้องสว่างเสมอทุกธีม**
   *    ใต้แถบนี้มี `.stream__scrim` ไล่ดำอยู่แล้ว และภาพไลฟ์เป็นอะไรก็ได้
   *    ใช้ `--ink` ตามธีม = พอเลือกธีมโทนสว่าง ชื่อโต๊ะกลายเป็นดำบนดำ
   */
  /**
   * ⚠️ **ค่านี้คือ `--ink` ของธีมมืดเดิมเป๊ะ ๆ ไม่ใช่ขาวสว่างกว่า**
   *    ตั้งเป็น `#f8fafc` ก็อ่านออกเหมือนกัน แต่ทำให้ชื่อโต๊ะของบอทที่ใช้อยู่
   *    สว่างขึ้นจากเดิม — เปลี่ยนหน้าตาโต๊ะจริงโดยไม่มีใครสั่ง
   *    (จับได้ตอนเทียบภาพก่อน/หลังทีละพิกเซล)
   */
  --on-media: #e8eef7;

  --radius:    16px;
  --radius-sm: 10px;
  --shadow:    0 12px 34px rgba(0, 0, 0, .45);
  --safe-b:    env(safe-area-inset-bottom, 0px);
}

/**
 * 🔴 **สีของธีมไม่ได้อยู่ในไฟล์นี้แล้ว — มาจาก `<style>` ที่หน้าเว็บฝังให้**
 *
 *    เดิมมี `[data-theme="emerald"]` กับ `[data-theme="royal"]` เขียนไว้ตรงนี้
 *    ตอนนี้คลังธีมมี 155 แบบ ซึ่งเขียนลงไฟล์นี้ไม่ไหวและไม่ควร —
 *    ผู้เล่นหนึ่งคนใช้ธีมเดียว แต่ต้องโหลดค่าของอีก 154 ธีมไปด้วย
 *
 *    ที่มาที่เดียวคือ `Cpbot\Shared\Web\LiveTheme` แล้วหน้าเว็บฝังเฉพาะ
 *    ชุดที่เลือกไว้ **หลัง** ไฟล์นี้ · ค่าใน `:root` ข้างบนจึงเป็นค่าสำรอง
 *    เผื่อวันที่ตัวฝังพัง — ไม่ใช่จอไม่มีสี
 *
 * ⚠️ เพิ่มสีธีมกลับมาที่นี่เมื่อไร = มีสองที่ที่นิยามธีมเดียวกัน
 *    แล้ววันหนึ่งหน้าเว็บกับหน้าพรีวิวในแผงแอดมินจะโชว์คนละสี
 */

/* ── พื้นฐาน ─────────────────────────────────────────────────── */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Prompt", "Noto Sans Thai", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* ⚠️ กันการเลือกข้อความตอนแตะรัว — บนมือถือจะขึ้นเมนู copy มาบัง */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

button, input { font: inherit; color: inherit; }

/**
 * 🔴 **ช่องกรอกทุกช่องต้องเลือกข้อความได้ — ไม่ใช่แค่ `.chat__input`**
 *
 *    เบราว์เซอร์ในแอป LINE/Facebook บน iOS เป็น WKWebView ซึ่งมีพฤติกรรมว่า
 *    ถ้า **บรรพบุรุษ** ของช่องกรอกเป็น `user-select: none`
 *    แตะช่องแล้ว **แป้นพิมพ์ไม่เด้งขึ้นมาเลย** ถึงตัวช่องเองจะประกาศ
 *    `user-select: text` ไว้แล้วก็ตาม (Safari ปกติกับ Chrome ไม่เป็น
 *    จึงไม่มีทางเจอตอนทดสอบบนคอม)
 *
 * ⚠️ ต้องปลดให้ **ตัวครอบด้วย** ไม่ใช่ปลดแค่ที่ช่อง — นั่นคือทั้งหมดของบั๊กนี้
 *    (เจอจริง 2026-08-24: ผู้เล่นเปิดจากลิงก์ในไลน์ แล้วพิมพ์แชทไม่ได้)
 *
 * ⚠️ `body` ยังเป็น `none` เหมือนเดิม — กันเมนู copy โผล่มาบังตอนแตะรัว
 *    ซึ่งเป็นเหตุผลที่ใส่ไว้แต่แรก
 */
input, textarea, select,
.say, .say__form, .form, .field {
  -webkit-user-select: text;
  user-select: text;
}

button {
  cursor: pointer;
  border: none;
  background: none;
  touch-action: manipulation;
}

a { color: var(--accent); text-decoration: none; }
img { max-width: 100%; display: block; }
[hidden] { display: none !important; }

/* ⚠️ ผู้ที่ตั้งค่า "ลดการเคลื่อนไหว" ต้องได้จอที่นิ่งจริง ๆ ไม่ใช่แค่ช้าลง */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ═══ หน้าเข้าระบบ / สมัคร ══════════════════════════════════
   🔴 **ชื่อคลาสขึ้นต้น `entry` ไม่ใช่ `gate`**
      `gate` เป็นศัพท์ของเกม (ประตูแทง) ที่ใช้ทั่วทั้ง PHP และ JS
      — `data-gate` · `gateCodes()` · `winningGates()` · `.gate.is-won`

      เคยตั้งชื่อชนกันมาแล้ว: ทั้งสองอยู่ในไฟล์ CSS เดียวกัน
      กฎของปุ่มประตูที่มาทีหลังจึงทับกฎของหน้าล็อกอินทั้งชุด
      (min-height, display, padding, border) โดยไม่มีอะไรฟ้อง          */
.entry {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 24px 16px calc(24px + var(--safe-b));
  position: relative;
  overflow: hidden;
  background: radial-gradient(120% 80% at 50% 0%, var(--bg-2), var(--bg) 70%);
}

.entry__glow {
  position: absolute;
  inset: -40% -20% auto -20%;
  height: 70%;
  background:
    /* ⚠️ แสงซ้าย = เสือ (น้ำเงิน) · ขวา = มังกร (แดง) — ต้องตรงกับสีในกระดาน */
    radial-gradient(40% 60% at 25% 40%, rgba(59, 111, 224, .24), transparent 70%),
    radial-gradient(40% 60% at 75% 40%, rgba(239, 68, 68, .22), transparent 70%);
  filter: blur(30px);
  animation: drift 14s ease-in-out infinite alternate;
  /**
   * 🔴 **ชั้นประดับต้องไม่รับการกด (แก้ 2026-08-31)**
   *    มันกินพื้นที่ 70% ของความสูงจากขอบบน · บนจอเตี้ย ๆ (มือถือแนวนอน)
   *    มันทับการ์ดล็อกอินแล้ว**กินคลิกของปุ่มไปเงียบ ๆ**
   *    ชั้นประดับอื่นที่เพิ่มทีหลังตั้ง `none` ไว้หมดแล้ว ตัวนี้ตกสำรวจ
   */
  pointer-events: none;
}

@keyframes drift {
  from { transform: translate3d(-3%, 0, 0) scale(1); }
  to   { transform: translate3d(3%, 2%, 0) scale(1.08); }
}

.entry__card {
  position: relative;
  width: min(420px, 100%);
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-radius: 22px;
  padding: 28px 24px;
  box-shadow: var(--shadow);
}

.entry__brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-bottom: 14px;
}

.entry__mark { font-size: calc(40px * var(--fs, 1)); animation: breathe 3.4s ease-in-out infinite; }
.entry__mark:last-child { animation-delay: 1.7s; }

/**
 * ⭐ โลโก้บนหน้าเข้าระบบ/สมัคร — หายใจเบา ๆ เหมือนไอคอนเดิมที่มันแทนที่
 *
 * ⚠️ `breathe` มีอยู่แล้วด้านล่าง และเคารพ `prefers-reduced-motion` ที่ประกาศรวมไว้
 */
.entry__logo {
  width: 72px;
  height: 72px;
  object-fit: contain;
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, .45));
  animation: breathe 3.4s ease-in-out infinite;
}

/* ⭐ ไอคอน "หายใจ" เบา ๆ — สื่อว่าหน้าจอยังมีชีวิตและระบบยังต่ออยู่ */
@keyframes breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.12); }
}

.entry__vs {
  font-size: calc(14px * var(--fs, 1));
  font-weight: 800;
  letter-spacing: 2px;
  color: var(--ink-faint);
}

.entry__title { margin: 0; font-size: calc(24px * var(--fs, 1)); text-align: center; }
.entry__sub   { margin: 6px 0 18px; text-align: center; color: var(--ink-dim); font-size: calc(15px * var(--fs, 1)); }
.entry__foot  { margin: 18px 0 0; text-align: center; color: var(--ink-dim); font-size: calc(15px * var(--fs, 1)); }

.entry__or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 18px 0;
  color: var(--ink-faint);
  font-size: calc(14px * var(--fs, 1));
}
.entry__or::before, .entry__or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* ── ฟอร์ม ───────────────────────────────────────────────────── */
.form { display: grid; gap: 14px; }

.field { display: grid; gap: 6px; }
.field__label { font-size: calc(14px * var(--fs, 1)); color: var(--ink-dim); }
.field__hint  { font-size: calc(13px * var(--fs, 1)); color: var(--ink-faint); }

/**
 * ⭐ ผลการตรวจชื่อผู้ใช้สด ๆ — เขียวว่าง / แดงซ้ำ
 *
 * ⚠️ **สีอย่างเดียวไม่พอ** — ข้อความบอกอยู่แล้วว่าว่างหรือซ้ำ
 *    คนตาบอดสีจึงยังอ่านออก (สีเป็นตัวเสริม ไม่ใช่ตัวบอก)
 */
.field__hint--ok  { color: var(--ok, #22c55e); }
.field__hint--bad { color: var(--bad, #ef4444); }

/* ── หน้าโปรไฟล์ของผู้เล่น ───────────────────────────────────── */
/**
 * ⚠️ รูปตัวอย่างใหญ่พอให้เห็นว่าเป็นรูปอะไร แต่ไม่กินที่จนต้องเลื่อนหาปุ่มบันทึก
 */
.prof__pic {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.prof__preview {
  width: 64px;
  height: 64px;
  font-size: calc(25px * var(--fs, 1));
  flex: none;
}

.prof__file { flex: 1; min-width: 180px; margin: 0; }

.field__input {
  width: 100%;
  min-height: 46px;
  padding: 10px 14px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: border-color .15s, box-shadow .15s;
  /* ⚠️ 16px ขั้นต่ำ — เล็กกว่านี้ iOS ซูมเข้าอัตโนมัติตอนโฟกัส */
  font-size: calc(18px * var(--fs, 1));
  -webkit-user-select: text;
  user-select: text;
}

.field__input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(56, 189, 248, .18);
}

.alert {
  margin: 0 0 14px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-size: calc(15px * var(--fs, 1));
  line-height: 1.5;
}
.alert--bad  { background: rgba(244, 63, 94, .14); border: 1px solid rgba(244, 63, 94, .4); }
.alert--warn { background: rgba(251, 191, 36, .12); border: 1px solid rgba(251, 191, 36, .38); }
/* ⚠️ ข้อความ "สำเร็จ" ต้องมีสีของตัวเอง — ใช้สีเดียวกับข้อความผิดพลาดคือทำให้คนอ่านผิด */
.alert--ok   { background: rgba(34, 197, 94, .13);  border: 1px solid rgba(34, 197, 94, .4); }

/* ── ปุ่ม ─────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 18px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  font-size: calc(16px * var(--fs, 1));
  transition: transform .12s, filter .15s, background .15s;
}

.btn:active { transform: scale(.96); }
.btn--block { width: 100%; }
.btn__ico   { font-size: calc(18px * var(--fs, 1)); line-height: 1; }

.btn--primary { background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: #06121c; }
.btn--gold    { background: linear-gradient(135deg, var(--gold), #f59e0b); color: #2a1a00; }
.btn--line    { background: var(--line-green); color: #00230d; }
.btn--danger  { background: rgba(244, 63, 94, .16); color: #ffc9d2; border: 1px solid rgba(244, 63, 94, .42); }
.btn--ghost   { background: transparent; color: var(--ink-dim); border: 1px solid var(--line); }
.btn--quiet   { background: transparent; color: var(--ink-faint); padding: 10px 12px; }

.btn:disabled { opacity: .5; pointer-events: none; }

/* ═══ หน้าโต๊ะ ═══════════════════════════════════════════════ */
.live {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  grid-template-rows: auto 1fr;
  background: radial-gradient(140% 90% at 50% -10%, var(--bg-2), var(--bg) 65%);
}

/* ── เวทีไลฟ์ ────────────────────────────────────────────────── */
.live__stage {
  position: relative;
  background: #000;
  overflow: hidden;
}

.stream { position: relative; width: 100%; height: 100%; }

/* ── ดูหลายมุมกล้องพร้อมกัน (เจ้าของสั่ง 2026-10-02) ─────────────────
   `weblive_stream_view` = multi2 · multi3 · multi4 → `.mview[data-mview]`
   🔴 ไม่มีจอเสริม = ไม่มี `.mview` ในหน้าเลย กฎพวกนี้ไม่แตะโต๊ะที่ใช้แบบสลับทีละมุม
   ⚠️ เส้นคั่น 2px คือพื้นดำของกรอบที่โผล่ระหว่างจอ — ไม่ต้องมีขอบของแต่ละจอ
      ·  2 จอ  เคียงกันซ้ายขวา
      ·  3 จอ  จอหลักใหญ่ซ้าย + สองจอเล็กซ้อนขวา
      ·  4 จอ  ตาราง 2×2
   ⚠️ ปุ่มขยาย = จอนั้นเต็มกรอบ จอที่เหลือซ่อนด้วย `visibility` ไม่ใช่ `display`
      — วิดีโอยังเดินอยู่ กลับมาเป็นตารางแล้วไม่ต้องรอต่อสายใหม่ */

.mview {
  position: relative;
  display: grid;
  width: 100%;
  height: 100%;
  gap: 2px;
  background: #000;
}

.mview[data-mview="2"] { grid-template-columns: 1fr 1fr; }

.mview[data-mview="3"] {
  grid-template-columns: 2fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.mview[data-mview="3"] > .stream:first-child { grid-row: 1 / span 2; }

.mview[data-mview="4"] {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.mview > .stream { min-width: 0; min-height: 0; overflow: hidden; }

.mview.has-big { grid-template-columns: 1fr; grid-template-rows: 1fr; }

.mview.has-big > .stream { grid-area: 1 / 1; visibility: hidden; }
.mview.has-big > .stream.is-big { visibility: visible; z-index: 1; }

.stream__tileName {
  position: absolute;
  left: 6px;
  bottom: 6px;
  z-index: 4;
  max-width: calc(100% - 52px);
  overflow: hidden;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .55);
  color: #fff;
  font-size: calc(11px * var(--fs, 1));
  font-weight: 700;
  white-space: nowrap;
  text-overflow: ellipsis;
  pointer-events: none;
}

.stream__tileMax {
  position: absolute;
  right: 6px;
  bottom: 6px;
  z-index: 4;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 1px solid rgba(255, 255, 255, .25);
  border-radius: 50%;
  background: rgba(0, 0, 0, .55);
  color: #fff;
  cursor: pointer;
}

.stream__tileMax svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/** ⭐ ตอนขยายอยู่ ไอคอนหมุนเข้าหากัน — บอกว่ากดอีกทีคือย่อกลับ */
.stream__tileMax[aria-pressed="true"] svg { transform: rotate(180deg) scale(.85); }

/* ⚠️ จอหลักยังมีปุ่มเปิดเสียงมุมขวาล่าง — ปุ่มขยายวางข้าง ๆ แนวเดียวกัน
   (ตำแหน่งจริงอยู่ที่บล็อก "ปุ่มเลือกแบบการดู" ซึ่งย่อปุ่มทั้งกรอบหลายจอ) */

.stream__video,
.stream__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  /*
    ⭐ `contain` ไม่ใช่ `cover` — ไลฟ์แนวตั้งที่ถ่ายมาจากมือถือ ถ้า cover
       จะถูกครอบตัดหัวและท้ายทิ้ง ซึ่งเป็นตรงที่ไพ่วางอยู่พอดี
  */
  object-fit: contain;
  background: #000;
}

/**
 * ⭐ ไลฟ์แนวตั้ง — js ติดคลาสนี้ให้เองหลังอ่านขนาดจริงจาก <video> (หรือร้านเลือกแนวตั้งเอง)
 *
 * 🔴 **`contain` ไม่ใช่ `cover`** (เจ้าของแจ้ง 2026-09-13: "ไลฟ์แนวตั้ง ภาพไม่เต็ม เห็นบางส่วน")
 *    เดิมบรรทัดนี้ตั้ง `cover` ทับกฎข้างบน · กรอบไลฟ์เป็นแนวนอน วิดีโอแนวตั้งจึงถูกขยาย
 *    เต็มความกว้างแล้วตัดหัว-ท้ายทิ้ง — ตรงที่ไพ่/ลูกเต๋าวางอยู่พอดี · หน้า Studio แสดงครบเพราะใช้ `contain`
 */
.stream.is-portrait .stream__video { object-fit: contain; }

/**
 * ══ ⭐ มุมกล้อง — แผงเลือกมุมบนจอไลฟ์ (เจ้าของสั่ง 2026-09-13) ══
 *
 * ⭐ แคปซูลกระจกกลางขอบล่าง · แถบไล่สีเลื่อนตามมุมที่เลือก · จุดเขียวเต้น = มุมนั้นมีภาพเข้ามา
 * ⚠️ อยู่เหนือชั้นรับการแตะของ iframe (`.stream__grab` z 3) — ไม่งั้นกดไม่ติด
 * ⚠️ กว้างไม่เกิน "จอ − ปุ่มเปิดเสียง (ขวา) − ลิงก์ TikTok (ซ้าย)" — ห้ามทับปุ่มเดิม
 * ⚠️ มือถือซ่อนชื่อมุม เหลือเลข — ชื่อมุมยาวบนจอ 360px คือแผงล้นกรอบ
 */
.stream__angles {
  position: absolute;
  z-index: 6;
  left: 50%;
  bottom: 12px;
  display: flex;
  align-items: center;
  gap: 2px;
  max-width: calc(100% - 128px);
  padding: 4px;
  border-radius: 999px;
  background: rgba(8, 12, 20, .58);
  border: 1px solid rgba(255, 255, 255, .16);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  box-shadow: 0 12px 30px -14px rgba(0, 0, 0, .8), inset 0 1px 0 rgba(255, 255, 255, .08);
  overflow-x: auto;
  scrollbar-width: none;
  transform: translateX(-50%);
  animation: angRise .5s cubic-bezier(.22, .61, .36, 1) both;
}

.stream__angles::-webkit-scrollbar { display: none; }
.stream__angles[hidden] { display: none; }

.stream__anglesInd {
  position: absolute;
  z-index: 0;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #22d3ee, #a78bfa);
  box-shadow: 0 0 18px -2px rgba(34, 211, 238, .75);
  transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), width .45s cubic-bezier(.34, 1.56, .64, 1);
  pointer-events: none;
}

.stream__angle {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  height: 34px;
  padding: 0 12px 0 6px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: rgba(255, 255, 255, .84);
  font: inherit;
  font-size: calc(12.5px * var(--fs, 1));
  font-weight: 800;
  white-space: nowrap;
  cursor: pointer;
  transition: color .2s;
}

.stream__angle:hover { color: #fff; }
.stream__angle.is-on { color: #06121a; }
.stream__angle:focus-visible { outline: 2px solid #22d3ee; outline-offset: 2px; }

.stream__angleIco {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  transition: background .2s, transform .3s cubic-bezier(.34, 1.56, .64, 1);
}

.stream__angle.is-on .stream__angleIco { background: rgba(255, 255, 255, .4); transform: rotate(-8deg) scale(1.06); }
.stream__angleIco svg { width: 14px; height: 14px; }

.stream__angleDot { width: 7px; height: 7px; flex: none; border-radius: 50%; background: rgba(255, 255, 255, .28); }
.stream__angleDot.is-live { background: #22c55e; animation: angPing 1.8s infinite; }
.stream__angle.is-on .stream__angleDot:not(.is-live) { background: rgba(6, 18, 26, .35); }

.stream__angleToast {
  position: absolute;
  z-index: 6;
  top: 12px;
  left: 50%;
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(8, 12, 20, .72);
  border: 1px solid rgba(255, 255, 255, .18);
  color: #fff;
  font-size: calc(13px * var(--fs, 1));
  font-weight: 800;
  white-space: nowrap;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  transform: translateX(-50%);
  pointer-events: none;
  animation: angToast 1.6s ease both;
}

.stream.is-angle-switch .stream__video { animation: angSwap .7s ease both; }

@media (max-width: 560px) {
  .stream__angleName { max-width: 58px; overflow: hidden; text-overflow: ellipsis; }
  .stream__angle { gap: 4px; padding: 0 9px 0 5px; }
}

@keyframes angRise  { from { opacity: 0; transform: translate(-50%, 10px); } }
@keyframes angPing  { 0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, .6); } 100% { box-shadow: 0 0 0 7px rgba(34, 197, 94, 0); } }
@keyframes angSwap  { 0% { filter: none; } 35% { filter: blur(10px) brightness(.7); transform: scale(1.02); } 100% { filter: none; transform: none; } }
@keyframes angToast { 0% { opacity: 0; transform: translate(-50%, -6px); } 15%, 75% { opacity: 1; transform: translate(-50%, 0); } 100% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .stream__angles, .stream__anglesInd, .stream__angleIco, .stream__angleDot,
  .stream.is-angle-switch .stream__video, .stream__angleToast { animation: none !important; transition: none !important; }
}

/**
 * ⭐ **สัดส่วนของ iframe ต้องบังคับจากข้างนอก**
 *
 * 🔴 `object-fit` ใช้กับ `<iframe>` ไม่ได้ — มันไม่ใช่ replaced content
 *    แบบรูปหรือวิดีโอ · ไลฟ์แนวตั้งในกรอบ 16:9 จึงกลายเป็นภาพจิ๋ว
 *    กลางแถบดำสองข้าง ซึ่งดูเหมือนตั้งค่าผิด
 *    ทางเดียวคือ**ย่อตัว iframe ให้ได้สัดส่วนจริง แล้วจัดกลางกรอบ**
 *
 * ⚠️ ติดคลาสเฉพาะตอนมีไลฟ์จริง — ภาพนิ่งตอนยังไม่ไลฟ์ต้องเต็มกรอบเหมือนเดิม
 */
.stream.is-portrait,
.stream.is-landscape,
.stream.is-square { display: grid; place-items: center; background: #000; }

/**
 * 🔴 **ตั้งขนาดจริงแล้วย่อด้วย `transform` ไม่ใช่บีบกล่อง**
 *    บีบกล่องเฉย ๆ เนื้อหาข้างในไม่ย่อตาม — มันถูกครอบตัดทิ้งแทน
 *    (ผู้ใช้เลือกแนวตั้งแล้วยังเห็นแนวนอนเพราะเหตุนี้)
 *
 * ⚠️ `--fw`/`--fh` มาจาก PHP (ขนาดจริงของผู้ให้บริการ)
 *    `--fs` มาจาก js เพราะต้องวัดกรอบจริงซึ่งเปลี่ยนตามจอ
 */
.stream.is-portrait  .stream__frame,
.stream.is-landscape .stream__frame,
.stream.is-square    .stream__frame {
  /**
   * 🔴 **ต้องหลุดจากการไหลของหน้า — `transform` ไม่คืนที่ว่างให้ผัง**
   *
   *    กล่อง iframe ถูกตั้งเป็น**ขนาดจริง**ซึ่ง "สูงกว่ากรอบไลฟ์" เกือบทุกครั้ง
   *    (แนวตั้ง 720px ในกรอบสูง 34dvh) · ถ้ายังอยู่ในผัง แถวของกริดจะโตตาม
   *    เนื้อหาแล้วล้นลงข้างล่าง — `place-items: center` จัดกลาง**แถวที่ล้น**
   *    ไม่ใช่กลางกรอบ · จุดหมุนของ `scale()` จึงเลื่อนลงไปครึ่งหนึ่งของส่วนที่ล้น
   *    ผลคือ**ภาพถูกดันลงจนท่อนล่างโดน `overflow: hidden` ตัดหาย**
   *    (ผู้ใช้แจ้งว่า "ไลฟ์หายไปครึ่งหนึ่ง")
   *
   * ⚠️ `inset: 0` + `margin: auto` จัดกลางได้แม้กล่องใหญ่กว่ากรอบ
   *    (ระยะขอบติดลบได้) — ต่างจาก flex/grid ที่ล้นออกทางเดียว
   *    ห้ามเปลี่ยนไปใช้ `translate(-50%, -50%)` เพราะจะทับ `scale()` ที่ js ตั้ง
   */
  position: absolute;
  inset: 0;
  margin: auto;
  width: var(--fw, 640px);
  height: var(--fh, 360px);
  max-width: none;
  max-height: none;
  transform: scale(var(--fs, 1));
  transform-origin: center center;

  /* ⚠️ ย่อแล้วอย่ากระตุก — จอหมุนหรือปรับขนาดหน้าต่างเกิดบ่อยกว่าที่คิด */
  transition: transform .18s ease-out;
}

/**
 * ⚠️ วิดีโอ (HLS · CPBot Live) ไม่ต้องย่อ — `object-fit: contain` จัดการให้ · แต่**ต้องตรึงกล่องไว้กับกรอบ**
 *
 * 🔴 **ห้ามกลับไปเป็น `width/height: 100%` เฉย ๆ** (เจ้าของแจ้ง 2026-09-15: "ภาพขาดหาย ไม่แสดงแนวตั้งเต็ม")
 *    คลาสสัดส่วนเปลี่ยน `.stream` เป็น grid · `height: 100%` ของลูกใน grid ไปอิงแถว `auto`
 *    ซึ่งโตตามขนาดจริงของวิดีโอ → กล่องวิดีโอแนวตั้งสูงเกินกรอบ (วัดจริง: กรอบ 1000×600 ได้กล่อง 1000×1778)
 *    แล้ว `overflow: hidden` ของเวทีตัดท่อนล่างทิ้ง — เห็นแค่เพดาน/ผนัง ไม่เห็นโต๊ะ
 *    `contain` ช่วยไม่ได้เพราะกล่องใหญ่ผิดตั้งแต่แรก · หน้า Studio แสดงครบเพราะไม่มีคลาสนี้
 *    `position: absolute; inset: 0` = กล่องเท่ากรอบเสมอ ไม่ว่าผังไหน (ทุกผังตั้งความสูงเวทีไว้แล้ว)
 */
.stream.is-portrait  .stream__video,
.stream.is-landscape .stream__video,
.stream.is-square    .stream__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.stream__idle {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  min-height: 180px;
}

.stream__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: .35;
}

.stream__idleText { position: relative; text-align: center; color: var(--ink-dim); }
.stream__idleText p { margin: 6px 0 2px; font-weight: 600; }
.stream__idleText small { color: var(--ink-faint); }
.stream__idleIcon { font-size: calc(36px * var(--fs, 1)); opacity: .7; }

/* ไล่เฉดบน-ล่าง ให้ตัวหนังสือบนวิดีโออ่านออกไม่ว่าภาพจะสว่างแค่ไหน */
.stream__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, .6), transparent 22%),
    linear-gradient(0deg, rgba(0, 0, 0, .5), transparent 25%);
}

/**
 * ⭐ **ปุ่มไปดูที่ต้นทาง** (ใช้กับ TikTok ที่ต้องขออนุญาตโดเมนก่อนฝัง)
 *
 * ⚠️ วางมุมล่างซ้าย — ขวาล่างเป็นที่ของปุ่มเปิดเสียงอยู่แล้ว
 *    และกลางจอเป็นที่ของเนื้อหา
 */
.stream__out {
  position: absolute;
  left: 8px;
  bottom: 8px;
  z-index: 3;
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(7, 10, 16, .68);
  border: 1px solid rgba(255, 255, 255, .16);
  color: #e7f0ff;
  font-size: calc(12px * var(--fs, 1));
  line-height: 1;
  text-shadow: 0 1px 4px rgba(0, 0, 0, .9);
  backdrop-filter: blur(3px);
}

.stream__out:hover { background: rgba(7, 10, 16, .85); }

.stream__unmute {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .55);
  border: 1px solid rgba(255, 255, 255, .18);
  font-size: calc(20px * var(--fs, 1));
  backdrop-filter: blur(6px);
}

/* ── ปุ่มเลือกแบบการดูมุมกล้องของผู้เล่น (เจ้าของสั่ง 2026-10-02) ─────
   ⚠️ มุมซ้ายล่าง สมมาตรกับปุ่มเสียงขวาล่าง — แผงเลือกมุมอยู่กลางล่าง
      (`max-width: calc(100% - 128px)`) จึงเหลือที่ข้างละ 64px พอดีปุ่ม 44px
   ⚠️ โผล่เฉพาะร้านที่เปิดไว้มากกว่าหนึ่งแบบ — โต๊ะอื่นไม่มีองค์ประกอบนี้ในหน้า */

.stream__views {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 7;
}

.stream__viewsBtn {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .55);
  border: 1px solid rgba(255, 255, 255, .18);
  color: #fff;
  backdrop-filter: blur(6px);
  cursor: pointer;
}

.stream__viewsBtn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; }
.stream__viewsBtn[aria-expanded="true"] { background: color-mix(in srgb, var(--accent) 55%, #000); }

.stream__viewsMenu {
  position: absolute;
  left: 0;
  bottom: 52px;
  display: grid;
  gap: 2px;
  min-width: 170px;
  padding: 5px;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 14px;
  background: rgba(8, 11, 18, .92);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
  backdrop-filter: blur(8px);
}

.stream__viewsMenu[hidden] { display: none; }

.stream__viewsOpt {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 40px;
  padding: 0 10px;
  border-radius: 10px;
  color: #fff;
  font-size: calc(13px * var(--fs, 1));
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.stream__viewsOpt:hover { background: rgba(255, 255, 255, .08); }
.stream__viewsOpt.is-on { background: color-mix(in srgb, var(--accent) 30%, transparent); }

/** ⭐ ไอคอนจิ๋วบอกผังของแต่ละแบบ — 4 ช่องเดียวกัน ซ่อน/ขยายตามแบบ */
.stream__viewsIco {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 2px;
  width: 22px;
  height: 16px;
  flex: none;
}

.stream__viewsIco i { border-radius: 2px; background: currentColor; opacity: .85; }
.stream__viewsIco.is-0 i { display: none; }
.stream__viewsIco.is-0 i:first-child { display: block; grid-area: 1 / 1 / 3 / 3; }
.stream__viewsIco.is-2 i:nth-child(n+3) { display: none; }
.stream__viewsIco.is-2 i { grid-row: 1 / 3; }
.stream__viewsIco.is-3 i:nth-child(4) { display: none; }
.stream__viewsIco.is-3 i:first-child { grid-row: 1 / 3; }

/* ⚠️ หลายจอ: ป้ายชื่อของจอหลักต้องหลบปุ่มเลือกแบบที่มุมซ้ายล่าง */
/* ⚠️ หลายจอ: จอหลักเหลือกว้าง ~190px (4 จอบนมือถือ) — ปุ่มขนาดเต็ม 44px สามตัว
      เบียดจนป้ายชื่อมุมเหลือตัวอักษรเดียว (วัดจริง) · ย่อปุ่มในกรอบหลายจอเหลือ 34px */
.mview .stream__unmute { right: 8px; bottom: 8px; width: 34px; height: 34px; font-size: calc(16px * var(--fs, 1)); }
.mview .stream__views { left: 8px; bottom: 8px; }
.mview .stream__viewsBtn { width: 34px; height: 34px; }
.mview .stream__viewsBtn svg { width: 17px; height: 17px; }
.mview .stream__viewsMenu { bottom: 42px; }
.mview > .stream:first-child .stream__tileMax { right: 48px; bottom: 10px; }
.mview > .stream:first-child .stream__tileName { bottom: 14px; max-width: calc(100% - 100px); }
/**
 * ⚠️ ปุ่มเลือกแบบการดูอยู่มุมซ้ายล่างของทั้งกรอบ — ป้ายชื่อของจอที่อยู่มุมนั้นต้องหลบ
 *    2 จอ / 3 จอ = จอหลัก (กินสองแถว) · 4 จอ = จอที่ 3 · ขยายจอเดียว = จอที่ขยาย
 */
.mview.has-views[data-mview="2"] > .stream:first-child .stream__tileName,
.mview.has-views[data-mview="3"] > .stream:first-child .stream__tileName,
.mview.has-views[data-mview="4"] > .stream:nth-child(3) .stream__tileName,
.mview.has-views.has-big > .stream.is-big .stream__tileName { left: 46px; max-width: calc(100% - 124px); }

/* ⚠️ ป้ายจอที่ 3 ในตาราง 4 จอไม่มีปุ่มเสียงข้างขวา — ไม่ต้องบีบเท่าจอหลัก */
.mview.has-views[data-mview="4"] > .stream:nth-child(3) .stream__tileName { max-width: calc(100% - 90px); }

.stage__bar {
  position: absolute;
  inset: 0 0 auto 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  z-index: 3;
}

.stage__id { display: flex; align-items: center; gap: 8px; min-width: 0; }

.stage__title {
  /**
   * 🔴 **ตอกให้สว่างเสมอ ห้ามใช้ `--ink` ของธีม**
   *    ชื่อโต๊ะลอยอยู่บน**ภาพไลฟ์** ซึ่งใต้มันมี `.stream__scrim` ไล่ดำอยู่แล้ว
   *    ใช้สีตัวอักษรของธีม = เลือกธีมโทนสว่างแล้วชื่อโต๊ะกลายเป็นดำบนดำ
   *    (เหมือน `.stage__num` กับ `.pill` ที่ตอกค่าไว้ตั้งแต่แรกด้วยเหตุผลเดียวกัน)
   */
  color: var(--on-media);
  font-weight: 700;
  font-size: calc(16px * var(--fs, 1));
  text-shadow: 0 2px 8px rgba(0, 0, 0, .8);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ⭐ จุดแดงเต้น = การเชื่อมต่อยังดีอยู่ · js ดับให้เมื่อ pulse ล้มติดกัน */
.stage__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--dragon);
  box-shadow: 0 0 0 0 rgba(239, 68, 68, .7);
  animation: ping 1.8s ease-out infinite;
  flex: none;
}

.stage__dot.is-stale { background: var(--ink-faint); animation: none; }

/**
 * ⭐ **วงกระเพื่อมรอบจุดแดง** — สัญญาณ "กำลังถ่ายทอดสด" ที่คนอ่านออกทันที
 *
 * ⚠️ วาดด้วย `::after` ของจุดเดิม ไม่เพิ่ม element — จุดเดิมยังทำงาน
 *    เหมือนเดิมทุกอย่าง รวมถึงตอนสัญญาณขาด (`is-stale`)
 *
 * 🔴 **หยุดสนิทเมื่อสัญญาณขาด** — วงที่ยังเต้นอยู่ทั้งที่ข้อมูลค้าง
 *    คือการโกหกผู้เล่นว่าภาพยังสด
 */
.stage__dot::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  color: #ef4444;
  opacity: 0;
  animation: liveRing 2s ease-out infinite;
}

.stage__dot { position: relative; }
.stage__dot.is-stale::after { animation: none; opacity: 0; }

@keyframes liveRing {
  0%   { transform: scale(.7); opacity: .85; }
  70%  { transform: scale(1.9); opacity: 0; }
  100% { transform: scale(1.9); opacity: 0; }
}

/**
 * ⭐ **จำนวนคนที่เปิดหน้านี้อยู่** — ติดกับจุดแดง
 *
 * ⚠️ **ต้องอ่านออกบนพื้นวิดีโอที่สีเปลี่ยนตลอด** — พื้นทึบ + เงาตัวอักษร
 *    ไม่ใช่ตัวอักษรลอยเปล่า ๆ ซึ่งหายไปทันทีที่ไลฟ์เป็นภาพสว่าง
 *
 * ⚠️ ปุ่มสูง 26px แต่แตะได้ทั้งแถบ (`padding`) — เล็กกว่านี้บนมือถือกดไม่โดน
 */
.stage__eyes {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  padding: 0 9px;
  border-radius: 999px;
  background: rgba(7, 10, 16, .55);
  border: 1px solid rgba(255, 255, 255, .14);
  color: #e7f0ff;
  font-size: calc(13px * var(--fs, 1));
  line-height: 1;
  text-shadow: 0 1px 4px rgba(0, 0, 0, .9);
  flex: none;
}

/**
 * ⭐ **ไอคอนคน** — เส้นบาง โปร่ง อ่านออกบนพื้นวิดีโอทุกสี
 *
 * ⚠️ `vector-effect` ทำให้เส้นหนาเท่าเดิมทุกขนาดจอ — ไม่งั้นบนจอ 3x
 *    เส้นจะบางจนหาย
 */
.eyes__ico {
  width: 15px;
  height: 15px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/** ⚠️ คนที่สองจางกว่าเล็กน้อย — บอกว่า "หลายคน" โดยไม่ต้องวาดเยอะ */
.eyes__head--2, .eyes__body--2 { opacity: .55; }

/**
 * ⭐ **ไอคอนหายใจตามจังหวะ** — ช้ามาก (4 วินาที) เพราะมันอยู่บนจอตลอดเวลา
 *    เร็วกว่านี้จะกวนสายตาคนที่กำลังดูไพ่
 */
.stage__eyes:not(.is-cold) .eyes__ico { animation: eyesBreathe 4s ease-in-out infinite; }

@keyframes eyesBreathe {
  0%, 100% { transform: translateY(0)    scale(1); }
  50%      { transform: translateY(-.5px) scale(1.06); }
}

.stage__eyes b {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  min-width: 1ch;
  text-align: center;
}

/**
 * ⭐ **ตัวเลขเด้งตอนเปลี่ยน** — เขียวตอนคนเข้า · จางตอนคนออก
 *
 * 🔴 **สีต้องมากับการเคลื่อนไหว ไม่ใช่สีอย่างเดียว** — คนตาบอดสีเห็นการเด้ง
 *    ส่วนคนที่ตั้ง "ลดการเคลื่อนไหว" ยังเห็นสี · ทั้งสองทางจึงบอกเรื่องเดียวกันได้
 */
.eyes__n.is-up   { animation: eyesPop .5s cubic-bezier(.22, 1, .36, 1); color: #4ade80; }
.eyes__n.is-down { animation: eyesPop .5s cubic-bezier(.22, 1, .36, 1); color: var(--ink-faint); }

@keyframes eyesPop {
  0%   { transform: translateY(4px) scale(.82); opacity: .35; }
  60%  { transform: translateY(0)   scale(1.14); opacity: 1; }
  100% { transform: none; }
}

/** ⭐ ป้ายทั้งอันวาบตอนมีคนเข้า — เห็นได้แม้ไม่ได้จ้องตัวเลข */
.stage__eyes.is-up {
  animation: eyesGlow .6s ease-out;
  border-color: rgba(74, 222, 128, .55);
}

@keyframes eyesGlow {
  0%   { box-shadow: 0 0 0 0 rgba(74, 222, 128, .45); }
  100% { box-shadow: 0 0 0 10px rgba(74, 222, 128, 0); }
}

/** ⚠️ กดได้ต้องรู้สึกว่ากดได้ */
button.stage__eyes { cursor: pointer; transition: transform .12s ease, background .2s ease; }
button.stage__eyes:hover  { background: rgba(7, 10, 16, .75); }
button.stage__eyes:active { transform: scale(.94); }

/* ⚠️ แบบที่กดไม่ได้ต้องไม่ทำท่าว่ากดได้ — ผู้เล่นกดแล้วไม่มีอะไรเกิดขึ้นคือของเสีย */
.stage__eyes--flat { cursor: default; }

/* ── รายชื่อคนออนไลน์ ───────────────────────────────────────── */
.who {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  max-height: 46vh;
  overflow-y: auto;
}

.who__one {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px 6px 6px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  max-width: 100%;
}

.who__pic {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  font-size: calc(13px * var(--fs, 1));
  color: var(--ink-dim);
}

.who__pic img { width: 100%; height: 100%; object-fit: cover; }

.who__name {
  font-size: calc(14px * var(--fs, 1));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 12ch;
}

.who__code { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/**
 * ⭐ **ป้ายสถานะในรายชื่อ** — ลงแล้ว / ดูอยู่
 *
 * 🔴 **"ลงแล้ว" ต้องเด่นกว่า "ดูอยู่" ชัดเจน** — คนเปิดรายชื่อมาเพื่อดูว่า
 *    มีคนลงไปแล้วหรือยัง · สองป้ายที่หน้าตาเท่ากันคือกวาดตาแล้วไม่ได้อะไร
 */
.who__tag {
  font-size: calc(11px * var(--fs, 1));
  padding: 2px 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .07);
  color: var(--ink-faint);
  flex: none;
}

.who__tag--bet {
  background: rgba(74, 222, 128, .16);
  border: 1px solid rgba(74, 222, 128, .4);
  color: #4ade80;
  font-weight: 700;
}

.who__lead {
  margin: 0 0 10px;
  font-size: calc(15px * var(--fs, 1));
  color: var(--ink-dim);
}

.who__lead b { color: #4ade80; font-size: calc(19px * var(--fs, 1)); }

/**
 * ⚠️ **ผู้ชมที่ไม่ล็อกอินนับรวมในตัวเลข แต่ไม่มีชื่อ** — ต้องบอกให้รู้
 *    ไม่งั้นคนเห็น "ออนไลน์ 12" แต่รายชื่อมี 3 คนแล้วคิดว่าระบบพัง
 */
.who__note { width: 100%; margin: 0; font-size: calc(13px * var(--fs, 1)); color: var(--ink-faint); }

@keyframes ping {
  0%   { box-shadow: 0 0 0 0 rgba(239, 68, 68, .7); }
  70%  { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
  100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}

.stage__round { display: flex; align-items: center; gap: 8px; flex: none; }
.stage__num   { font-size: calc(14px * var(--fs, 1)); color: #dbe6f5; text-shadow: 0 2px 8px rgba(0, 0, 0, .8); }

.pill {
  /**
   * ⚠️ **ป้ายลอยอยู่บนภาพไลฟ์ — สีตัวอักษรต้องสว่างเสมอ**
   *    ตัวที่มี `data-can-bet` เขียนทับด้วยเขียว/แดงอยู่แล้ว
   *    ที่เหลือ (เช่นตัวนับถอยหลัง) เคยตกไปใช้ `--ink` ของธีม
   *    ซึ่งกลายเป็นดำบนพื้นดำทันทีที่เลือกธีมโทนสว่าง
   */
  color: var(--on-media);
  padding: 4px 10px;
  border-radius: 999px;
  font-size: calc(13px * var(--fs, 1));
  font-weight: 700;
  background: rgba(0, 0, 0, .55);
  border: 1px solid rgba(255, 255, 255, .16);
  backdrop-filter: blur(6px);
  white-space: nowrap;
}

.pill[data-can-bet="1"] { color: #86efac; border-color: rgba(34, 197, 94, .45); }
.pill[data-can-bet="0"] { color: #fca5a5; border-color: rgba(239, 68, 68, .45); }

/* ── ปุ่มอีโมจิใต้ช่องแชท ────────────────────────────────────── */
/**
 * ⚠️ **แถวเดียว เลื่อนได้ ไม่ตัดบรรทัด** — มันอยู่ติดกับช่องพิมพ์
 *    ซึ่งเป็นพื้นที่ที่ทุกพิกเซลแย่งกับกระดาน
 */
.react {
  display: flex;
  gap: 6px;
  padding: 4px 10px 0;
  overflow-x: auto;
  scrollbar-width: none;
}

.react::-webkit-scrollbar { display: none; }

.react__btn {
  flex: none;
  width: 34px;
  height: 30px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  font-size: calc(16px * var(--fs, 1));
  line-height: 1;
  transition: transform .12s ease, background .2s ease;
}

.react__btn:hover  { background: var(--panel); }
.react__btn:active { transform: scale(.88); }

/** ⚠️ กดแล้วต้องรู้ว่าส่งไปแล้ว — ไม่มีอะไรตอบสนอง = กดซ้ำรัว ๆ */
.react__btn.is-sent { animation: reactSent .45s ease-out; }

@keyframes reactSent {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.35) translateY(-4px); }
  100% { transform: scale(1); }
}

/* ── ประกาศคนชนะใหญ่ ────────────────────────────────────────── */
/**
 * 🔴 **ลอยกลางบน ไม่ใช่กลางจอ** — กลางจอบังกระดานตอนที่คนกำลังจะแทงรอบใหม่
 *    ซึ่งเป็นวินาทีที่แพงที่สุดของหน้านี้
 *
 * ⚠️ `pointer-events: none` — ห้ามกินการแตะ ไม่งั้นบังปุ่มโดยไม่มีใครรู้
 */
.bigwin {
  position: fixed;
  left: 50%;
  top: calc(8px + env(safe-area-inset-top, 0px));
  transform: translateX(-50%);
  z-index: 70;
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: min(92vw, 420px);
  padding: 8px 14px;
  border-radius: 999px;
  background: linear-gradient(135deg, rgba(250, 204, 21, .95), rgba(245, 158, 11, .95));
  color: #201400;
  font-size: calc(14px * var(--fs, 1));
  font-weight: 700;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
  pointer-events: none;
  animation: bigwinIn .5s cubic-bezier(.22, 1, .36, 1);
}

.bigwin__pic {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
  background: rgba(0, 0, 0, .18);
}

.bigwin__pic img { width: 100%; height: 100%; object-fit: cover; }
.bigwin__net { font-weight: 900; font-variant-numeric: tabular-nums; }

@keyframes bigwinIn {
  0%   { opacity: 0; transform: translate(-50%, -14px) scale(.9); }
  100% { opacity: 1; transform: translate(-50%, 0) scale(1); }
}

.bigwin.is-out { animation: bigwinOut .4s ease-in forwards; }

@keyframes bigwinOut {
  to { opacity: 0; transform: translate(-50%, -12px); }
}

/* ── กระดานอันดับของวันนี้ ───────────────────────────────────── */
/**
 * ⚠️ **การ์ดเตี้ย ๆ** — มันเป็นของประดับบรรยากาศ ไม่ใช่ของที่ต้องอ่านนาน
 *    กินที่เท่าเค้าไพ่เมื่อไหร่คือแย่งที่ของสิ่งที่ผู้เล่นใช้ตัดสินใจจริง
 */
.lead {
  padding: 8px 10px;
  background: linear-gradient(135deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.lead__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.lead__label { font-size: calc(13px * var(--fs, 1)); font-weight: 800; }
.lead__note  { font-size: calc(11px * var(--fs, 1)); color: var(--ink-faint); }

.lead__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.lead__row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: calc(13px * var(--fs, 1));
}

/** ⚠️ เหรียญอันดับต้องกว้างเท่ากันทุกแถว — ไม่งั้นชื่อเยื้องกันทั้งกระดาน */
.lead__rank { width: 18px; text-align: center; flex: none; font-size: calc(14px * var(--fs, 1)); }

.lead__pic {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  font-size: calc(11px * var(--fs, 1));
  color: var(--ink-dim);
}

.lead__pic img { width: 100%; height: 100%; object-fit: cover; }

.lead__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/** 🔴 ยอดต้องเป็นสีเดียวกับ "ได้" ทั้งเว็บ — สีทองคือเงินที่ได้ */
.lead__net {
  flex: none;
  font-weight: 800;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

/* ── แชทลอยมุมขวา ────────────────────────────────────────────── */
/* 🔴 **กล่องลอยมีแต่ข้อความ — ช่องพิมพ์ย้ายไปอยู่ใต้จอไลฟ์ (`.say`)**
   เดิมช่องพิมพ์อยู่ในนี้ด้วยแล้วบังมุมล่างขวาของไลฟ์ตลอดเวลา
   ซึ่งเป็นตรงที่คนแจกไพ่วางไพ่พอดี · ข้อความลอยทับได้เพราะจางหายเอง
   แต่ช่องพิมพ์ไม่หาย จึงบังถาวร                                        */
.chat {
  position: absolute;
  right: 8px;
  bottom: 8px;
  z-index: 4;
  width: min(58%, 300px);
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* ⚠️ ทั้งกล่องไม่รับการแตะเลย — ไม่มีอะไรให้กดแล้ว */
  pointer-events: none;
}

.chat__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 4px;
  max-height: 42vh;
  overflow: hidden;

  /**
   * ⭐ **รายการรับการแตะได้ ต่างจากกล่องรอบนอก**
   *
   * ⚠️ กล่องนี้ลอยอยู่บน**จอไลฟ์** ไม่ใช่บนกระดาน (กระดานอยู่คนละคอลัมน์)
   *    จึงไม่ไปบังปุ่มแทง · และต้องรับการแตะเพราะผู้เล่นขอให้
   *    "ชี้หรือกดที่กล่องแชทแล้วกลับมาเห็นชัด"
   *
   * ⚠️ จอเตี้ยแนวนอนซ่อนแชททั้งกล่องอยู่แล้ว (ดูท้ายไฟล์) ตรงนั้นไลฟ์เต็มจอ
   *    และกระดานลอยทับ — เปิดรับการแตะตรงนั้นจะบังปุ่มจริง
   */
  pointer-events: auto;
  transition: opacity .5s ease;
}

/**
 * 🔴 **เงียบครบ 3 วินาที = จางทั้งกล่อง** (ผู้เล่นขอมาเอง)
 *    แชทมีไว้ให้รู้สึกว่ามีคนเล่นด้วย ไม่ใช่ให้มาบังคนแจกไพ่
 *
 * ⚠️ ชี้เมาส์หรือแตะแล้วกลับมาเต็มทันที — ไม่ใช่ต้องรอ
 */
.chat.is-dim .chat__list { opacity: .12; }
.chat:hover .chat__list,
.chat.is-awake .chat__list { opacity: 1; }

.chat__item {
  /**
   * 🔴 **ความจางมาจาก "ลำดับที่" ไม่ใช่ "อายุ"** (ผู้เล่นขอมาแบบนี้)
   *    5 ข้อความล่าสุดเด่นชัด · ที่ 6-8 ไล่จางลง · ที่ 9 ขึ้นไป js ลบทิ้ง
   *
   * ⚠️ ห้ามใส่ `chatFade` (จางตามเวลา) กลับมาพร้อมกัน — animation ทับ
   *    `opacity` ธรรมดาตลอดช่วงที่มันวิ่ง กฎตามลำดับจะไม่มีผลเลย
   *    ห้องเงียบใช้ตัวเก็บกวาดของ js แทน (`CHAT_TTL`)
   */
  animation: chatIn .28s ease-out;
  transition: opacity .4s ease;
  padding: 5px 10px;
  border-radius: 12px;
  background: rgba(0, 0, 0, .48);
  backdrop-filter: blur(4px);
  font-size: calc(14px * var(--fs, 1));
  line-height: 1.4;
  word-break: break-word;
  max-width: 100%;
}

/* ⚠️ เด่นชัด 3 ข้อความ · ที่ 4-6 ไล่จางลง · ที่ 7 ขึ้นไป js ลบทิ้ง */
.chat__item:nth-last-child(4) { opacity: .58; }
.chat__item:nth-last-child(5) { opacity: .36; }
.chat__item:nth-last-child(n+6) { opacity: .18; }

.chat__item.is-me            { background: rgba(56, 189, 248, .28); }
.chat__item--system          { background: rgba(148, 163, 184, .22); font-style: italic; }
.chat__item--win             { background: rgba(251, 191, 36, .26); font-weight: 600; }
.chat__who                   { color: var(--accent); font-weight: 700; margin-right: 6px; }

@keyframes chatIn {
  from { opacity: 0; transform: translate3d(24px, 0, 0); }
  to   { opacity: 1; transform: none; }
}

/* ── ช่องพิมพ์แชท — อยู่ใต้จอไลฟ์ ไม่ทับจอ ─────────────────── */
/* ⚠️ **แถวเตี้ยและชิดจอไลฟ์** — ของเดิมเว้นที่รอบช่องพิมพ์มากเกินไป
   จนดูเหมือนมีช่องว่างเปล่า ๆ คั่นกลางหน้า (ผู้ใช้แจ้งมาเอง)
   ช่องพิมพ์เป็นของรอง — พื้นที่ต้องไปอยู่ที่กระดาน                      */
.say {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 0;
}

.say__form { display: flex; gap: 6px; flex: 1; min-width: 0; }

.say__send,
.say__sfx {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  flex: none;
  display: grid;
  place-items: center;
  font-size: calc(18px * var(--fs, 1));
}

.say__send { background: var(--accent); color: #06121c; }

.say__sfx {
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--ink-dim);
}

.chat__input {
  flex: 1;
  min-width: 0;
  min-height: 36px;
  padding: 5px 12px;
  border-radius: 999px;
  /**
   * ⚠️ **พื้นมาจากโทเคน ไม่ใช่ตอกดำไว้**
   *    ธีมมืดได้ค่าเดิมเป๊ะ · ธีมโทนสว่างได้พื้นอ่อนที่ตัวอักษรสีเข้มอ่านออก
   *    (ตอกดำไว้ = บนธีมสว่างได้แท่งเทาเข้มที่ดูเหมือนช่องยังโหลดไม่เสร็จ)
   */
  background: var(--field-bg);
  border: 1px solid var(--field-line);
  font-size: calc(18px * var(--fs, 1));
  -webkit-user-select: text;
  user-select: text;
}

.chat__input:focus { outline: none; border-color: var(--accent); }

/**
 * 🔴 **คำชี้แจงเล็กกว่าตัวที่พิมพ์** (เจ้าของสั่งข้อความยาว 2026-09-23)
 *
 *    "พิมพ์คุยกับเจ้ามือและเพื่อนๆในช่องนี้ได้เลยค่ะ" ที่ 18px กว้าง 333px
 *    แต่ช่องจริงกว้าง 238–312px (วัดที่ 360 / 390 / 434) — โดนตัดทุกขนาดจอ
 *    ท่อนท้ายประโยคทิ้ง = คำชี้แจงที่ไม่ได้ชี้แจงอะไรเลย
 *
 * 🔴 **ตัวที่ผู้เล่นพิมพ์เองยัง 18px เท่าเดิม** — ต่ำกว่า 16px บน iOS
 *    จะซูมหน้าเว็บเข้าเองตอนแตะช่อง แล้วไม่ซูมกลับ · ย่อเฉพาะคำชี้แจงจึงปลอดภัย
 *
 * ⚠️ ยังคูณด้วย `--fs` — คนที่ขยายตัวหนังสือไว้ต้องได้คำชี้แจงใหญ่ตามไปด้วย
 */
.chat__input::placeholder {
  font-size: calc(14px * var(--fs, 1));
  opacity: .72;
}

/* ══ โหมด "ข้อความวิ่ง" — ใต้กรอบไลฟ์ ไม่ทับจอ ══════════════════

   🔴 **ทุกกฎในบล็อกนี้อยู่ใต้ `[data-chat-mode="ticker"]` ทั้งหมด**
      โหมด `float` คือของเดิมที่โต๊ะจริงทุกตัวใช้อยู่ และต้องเหมือนเดิม
      **ทุกพิกเซล** — กติกาเดียวกับธีม/ผัง `classic` (§18.3)
      ไม่มีกฎไหนในบล็อกนี้แตะ selector เปล่า ๆ ของ `.chat` เลยสักตัว

   ⚠️ ตัวโหนดถูก render **นอก** `.live__stage` อยู่แล้ว (ดู `table/game1.php`)
      — CSS ที่นี่จึงแค่คืนสภาพจากกล่องลอยเป็นแถบปกติ ไม่ได้ "ดัน" อะไรลงมา
      (ดันด้วย CSS = ในผังโรงหนัง/ไลฟ์เต็มจอ มันจะยังซ้อนบนไลฟ์อยู่ดี)         */

[data-chat-mode="ticker"] {
  position: static;
  width: auto;
  right: auto;
  bottom: auto;
  padding: 6px 10px 0;
}

/**
 * 🔴 **`.live` ประกาศไว้แค่ 2 แถว (`auto 1fr`) — ข้อความวิ่งเป็นลูกคนที่ 3**
 *
 *    ลูกคนที่ 3 ตกไปอยู่ในแถวโดยปริยาย ส่วน `1fr` ที่ตั้งใจให้แผงเล่น
 *    กลับไปตกกับ**รางข้อความวิ่ง** — รางสูง 34px จะพองกินครึ่งจอ
 *    แล้วแผงเล่นถูกบีบจนกระดานเตี้ยลง · วัดด้วย headless แล้วเห็นชัด
 *    (เดาจาก CSS อย่างเดียวมองไม่เห็นเลย)
 *
 * ⚠️ ใช้ `:has(>)` เพราะโหมดลอยต้องไม่ได้กฎนี้ติดไปด้วย — โต๊ะที่ใช้
 *    กล่องลอยมีลูกแค่ 2 คนเหมือนเดิม ประกาศ 3 แถวให้จะเหลือแถวเปล่า
 */
.live:has(> [data-chat-mode="ticker"]) { grid-template-rows: auto auto 1fr; }

/* ⚠️ รายการลอยไม่มีที่ยืนในโหมดนี้ — ตัวที่ทำงานคือราง */
[data-chat-mode="ticker"] .chat__list { display: none; }

.chat__rail {
  position: relative;
  overflow: hidden;
  height: 34px;
  border-radius: 999px;
  background: var(--veil);
  border: 1px solid var(--line);

  /**
   * ⚠️ **ขอบซ้าย-ขวาต้องจางลง ไม่ใช่ตัดตรง ๆ**
   *    ข้อความที่โผล่มาแบบตัดครึ่งกลางอากาศอ่านเหมือนจอวาดไม่เสร็จ
   */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);

  /* ⚠️ ไม่รับการแตะ — ข้อความวิ่งผ่านแล้วหายไป ไม่มีอะไรให้กด */
  pointer-events: none;
}

.rail__item {
  position: absolute;
  left: 100%;
  top: 50%;
  white-space: nowrap;
  font-size: calc(14px * var(--fs, 1));
  line-height: 1;
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .42);
  color: var(--on-media);

  /**
   * ⚠️ **ระยะวิ่งมาจาก `--run` ที่ js ตั้งตามความกว้างจริงของราง**
   *    ตอกเป็น `-100vw` ไว้ = บนจอกว้างข้อความออกจากรางไปตั้งนานแล้ว
   *    แต่แอนิเมชันยังไม่จบ ทำให้เกิดช่วงเงียบยาว ๆ ที่ไม่มีอะไรวิ่งเลย
   */
  animation-name: railRun;
  animation-timing-function: linear;
  animation-fill-mode: both;
}

.rail__who  { color: var(--accent); font-weight: 700; margin-right: 6px; }
.rail__item.is-me       { background: rgba(56, 189, 248, .34); }
.rail__item--system     { background: rgba(148, 163, 184, .28); font-style: italic; }
.rail__item--win        { background: rgba(251, 191, 36, .32); font-weight: 600; }
.rail__item--admin      { background: rgba(34, 197, 94, .34); font-weight: 600; }

@keyframes railRun {
  from { transform: translate(0, -50%); }
  to   { transform: translate(calc(-1 * var(--run, 100vw) - 100%), -50%); }
}

/**
 * 🔴 **คนที่ปิดแอนิเมชันไว้ต้องได้แชทที่อ่านได้ ไม่ใช่แถบว่าง**
 *    กฎรวมด้านบนบังคับ `animation-duration: .001ms` กับทุกอย่าง —
 *    ข้อความวิ่งจะจบทันทีที่เกิดแล้วถูก js เก็บทิ้ง กลายเป็นรางเปล่า
 *    โหมดนี้จึงถอยกลับไปเป็นรายการปกติ (js ก็สลับทางวาดเองด้วย)
 */
@media (prefers-reduced-motion: reduce) {
  [data-chat-mode="ticker"] .chat__rail { display: none; }

  [data-chat-mode="ticker"] .chat__list {
    display: flex;
    max-height: none;
  }
}

/* ══ ปุ่มดูแชทย้อนหลัง + ป้ายเมื่อพิมพ์ไม่ได้ ═══════════════════ */

.say__log {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--ink-dim);
  transition: transform .18s ease, color .18s ease, border-color .18s ease;
}

.say__log-ico { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }

/** ⭐ เข็มนาฬิกาหมุนถอยหลังตอนชี้/กด — บอกว่า "ย้อนเวลา" โดยไม่ต้องมีคำอธิบาย */
.say__log:hover   { color: var(--accent); border-color: var(--accent); }
.say__log:active  { transform: scale(.9); }
.say__log.is-on   { color: #06121c; background: var(--accent); border-color: var(--accent); }
.say__log:hover .say__log-dial,
.say__log.is-on .say__log-dial { animation: dialBack 1.1s linear infinite; transform-origin: 12px 12px; }

@keyframes dialBack {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-360deg); }
}

.say__mute {
  flex: 1;
  min-width: 0;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: calc(14px * var(--fs, 1));
  color: var(--ink-dim);
}

.say__mute-ico { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }

/* ══ แผงแชทย้อนหลัง — เลื่อนขึ้นมาจากด้านล่าง ══════════════════

   ⚠️ **ไม่ใช้ `<dialog>`** — หน้าโต๊ะต้องยังเล่นได้ระหว่างที่แผงเปิดอยู่
      (รอบเปิด-ปิดไม่รอใคร) · `showModal()` กลืนโฟกัสทั้งหน้าและปิดกั้น
      การเลื่อนจอในเบราว์เซอร์มือถือบางตัว                                */

.chatlog {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(2, 6, 12, 0);
  transition: background .22s ease;
}

.chatlog.is-open { background: rgba(2, 6, 12, .58); }

.chatlog__sheet {
  width: min(560px, 100%);
  max-height: min(70vh, 560px);
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: 0 -18px 48px rgba(0, 0, 0, .48);

  /* ⚠️ เริ่มจากใต้ขอบจอ แล้วเลื่อนขึ้น — โผล่ทันทีอ่านเหมือนจอกระตุก */
  transform: translateY(100%);
  transition: transform .24s cubic-bezier(.22, .9, .3, 1);
}

.chatlog.is-open .chatlog__sheet { transform: none; }

.chatlog__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
}

.chatlog__grow { flex: 1; }

/** ⭐ จุดเต้น — บอกว่าแผงนี้ขยับเองอยู่ ไม่ใช่ภาพนิ่งที่ถ่ายไว้ตอนกด */
.chatlog__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent);
  animation: logPing 1.8s ease-out infinite;
}

@keyframes logPing {
  0%   { box-shadow: 0 0 0 0 rgba(56, 189, 248, .5); }
  70%  { box-shadow: 0 0 0 7px rgba(56, 189, 248, 0); }
  100% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0); }
}

.chatlog__close {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--ink-dim);
}

.chatlog__list {
  list-style: none;
  margin: 0;
  padding: 10px 14px 16px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  -webkit-overflow-scrolling: touch;
}

.chatlog__item {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--sheen);
  font-size: calc(15px * var(--fs, 1));
  line-height: 1.45;
  word-break: break-word;

  /** ⚠️ ไล่เข้าทีละใบตอนวาดใหม่ — ก้อนที่โผล่พร้อมกันอ่านเหมือนจอกระพริบ */
  animation: logIn .24s ease-out both;
}

.chatlog__at   { flex: none; font-size: calc(12px * var(--fs, 1)); color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.chatlog__who  { flex: none; color: var(--accent); font-weight: 700; }
.chatlog__body { min-width: 0; }

.chatlog__item.is-me        { background: rgba(56, 189, 248, .16); }
.chatlog__item--system      { font-style: italic; color: var(--ink-dim); }
.chatlog__item--win         { background: rgba(251, 191, 36, .16); }
.chatlog__item--admin       { background: rgba(34, 197, 94, .16); }
.chatlog__item--admin .chatlog__who { color: #34d399; }

.chatlog__empty { padding: 26px 14px 34px; text-align: center; color: var(--ink-dim); }

@keyframes logIn {
  from { opacity: 0; transform: translate3d(0, 6px, 0); }
  to   { opacity: 1; transform: none; }
}

/* ══ กล่องแจ้งว่าบัญชีถูกจำกัด ═════════════════════════════════

   🔴 **ต้องอ่านออกก่อนผู้เล่นแตะอะไร** — จึงคลุมทั้งจอและมีปุ่มเดียว
      แต่ **ไม่ปิดกั้นถาวร**: กดรับทราบแล้วเล่นส่วนที่เหลือได้ตามปกติ
      (คนที่โดน `ห้ามเข้าห้อง` ไม่เคยมาถึงหน้านี้ — `Kernel` ตัดไปก่อนแล้ว)   */

.banbox {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(2, 6, 12, .72);
  backdrop-filter: blur(3px);
  animation: banFade .2s ease-out both;
}

.banbox.is-out { animation: banFade .24s ease-in both reverse; }

.banbox__card {
  width: min(400px, 100%);
  padding: 26px 22px 20px;
  text-align: center;
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
  animation: banPop .3s cubic-bezier(.2, .9, .3, 1.25) both;
}

/** ⭐ วงแหวนกระเพื่อมรอบไอคอน — ดึงตาไปที่สาเหตุก่อนไปที่ปุ่ม */
.banbox__ring {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  margin: 0 auto 12px;
  border-radius: 50%;
  background: rgba(248, 113, 113, .12);
  animation: banRing 2.2s ease-out infinite;
}

.banbox__ico { width: 34px; height: 34px; fill: none; stroke: #f87171; stroke-width: 2; stroke-linecap: round; }

/** ⚠️ เส้นทแยงลากทีหลังวงกลม — ลำดับการวาดคือสิ่งที่ทำให้อ่านออกว่า "ห้าม" */
.banbox__circle { stroke-dasharray: 57; animation: banDraw .5s ease-out both; }
.banbox__slash  { stroke-dasharray: 19; animation: banDraw .3s ease-out .42s both; }

.banbox__title { margin: 0 0 12px; font-size: calc(20px * var(--fs, 1)); }

.banbox__list {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: left;
}

.banbox__item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  background: rgba(248, 113, 113, .1);
  border: 1px solid rgba(248, 113, 113, .28);
  font-size: calc(15px * var(--fs, 1));
  line-height: 1.45;
}

.banbox__tag  { flex: none; color: #fca5a5; }
.banbox__hint { margin: 0 0 16px; font-size: calc(14px * var(--fs, 1)); color: var(--ink-dim); line-height: 1.5; }

.banbox__ok {
  width: 100%;
  min-height: 44px;
  border-radius: 999px;
  background: var(--accent);
  color: #06121c;
  font-weight: 700;
}

@keyframes banFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes banPop  { from { opacity: 0; transform: translate3d(0, 14px, 0) scale(.96); } to { opacity: 1; transform: none; } }
@keyframes banDraw { from { stroke-dashoffset: 60; } to { stroke-dashoffset: 0; } }

@keyframes banRing {
  0%   { box-shadow: 0 0 0 0 rgba(248, 113, 113, .38); }
  70%  { box-shadow: 0 0 0 16px rgba(248, 113, 113, 0); }
  100% { box-shadow: 0 0 0 0 rgba(248, 113, 113, 0); }
}

/* ── แผงเล่น ─────────────────────────────────────────────────── */
/**
 * 🔴 **หน้านี้ห้ามเลื่อนแนวนอนเด็ดขาด**
 *    ของกว้างเกิน (ตารางผล · เค้าไพ่) ต้องเลื่อนอยู่ในกล่องของตัวเอง
 *    หลุดออกมาทีเดียว = หัวเว็บโดนตัด กระดานเบี้ยว และแตะปุ่มพลาด
 *    (เจอจริง: ตาราง 6×10 ดันทั้งหน้าไปทางขวา)
 *
 * ⚠️ `minmax(0, 1fr)` ที่คอลัมน์ — ค่าปริยาย `auto` ของ grid
 *    ยอมให้ลูกที่กว้างเกินขยายคอลัมน์ออกไป ซึ่งเป็นต้นเหตุเดียวกัน
 */
.live__panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto;
  gap: 8px;
  padding: 8px 10px calc(8px + var(--safe-b));
  min-height: 0;

  /**
   * 🔴 **`clip` ไม่ใช่ `hidden` — นี่คือบั๊กที่ทำให้เลื่อนหน้าจอไม่ติด**
   *
   *    กติกาของ CSS: ถ้าแกนหนึ่งเป็น `hidden` อีกแกนที่เป็น `visible`
   *    จะ**ถูกบังคับให้เป็น `auto`** · แผงนี้จึงกลายเป็น **ตัวเลื่อนซ้อน**
   *    โดยไม่มีใครตั้งใจ (วัดแล้ว: `overflow-x=hidden` → `overflow-y=auto`)
   *
   *    ผลคือการลากนิ้ว/หมุนล้อบนอะไรก็ตามในแผง ตกเป็นของแผค ไม่ใช่ของหน้า
   *    และเมื่อแผงไม่มีอะไรให้เลื่อน เบราว์เซอร์มือถือหลายตัว
   *    (Safari/iOS · WebView บน Android) **ไม่ส่งต่อให้หน้า** —
   *    ผู้เล่นจึงต้องจิ้มโดนพื้นหลังจริง ๆ ถึงจะเลื่อนได้
   *    (เจ้าของแจ้งซ้ำสองรอบ 2026-08-30)
   *
   * ⚠️ `clip` ตัดส่วนเกินเหมือน `hidden` แต่ **ไม่สร้างตัวเลื่อน** จึงไม่ไป
   *    บังคับอีกแกน · ประกาศ `hidden` ไว้ก่อนเป็นทางถอยของเบราว์เซอร์เก่า
   *    ที่ไม่รู้จัก `clip` (Safari ก่อน 16) ซึ่งจะได้พฤติกรรมเดิม
   */
  overflow-x: hidden;
  overflow-x: clip;
}

/* ── กล่องผู้เล่น ─────────────────────────────────────────────
   🔴 **แถวเดียวจบ** — ของเดิมเป็นกล่องสองแถวสูง ~90px ซึ่งกินที่กระดาน
      บนมือถือแนวตั้งมากเกินไป (ผู้ใช้แจ้งมาเอง)
      ข้อมูลในนี้เป็นของ "ดูแวบเดียว" — พื้นที่ต้องไปอยู่ที่กระดานที่แตะจริง  */

.player {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: linear-gradient(135deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-radius: var(--radius);

  /**
   * 🔴 **จอกว้างไม่ได้แปลว่ากล่องกว้าง**
   *
   *    ผัง `bento` กับผัง PC วางแถบผู้เล่นไว้ใน**คอลัมน์แคบ** — บนจอ 3840px
   *    คอลัมน์นั้นกว้างราว 470px ซึ่งแคบกว่ามือถือแนวนอนเสียอีก
   *    ปุ่มชุดขวาเป็น `flex: none` และแถวไม่ยอมขึ้นบรรทัดใหม่
   *    → ปุ่ม "เติม/ถอนเครดิต" ถูกขอบการ์ดตัดครึ่ง (วัดได้ล้น 94px ที่ 400px)
   *
   * ⚠️ **ยังเป็นแถวเดียวเมื่อพื้นที่พอ** — เจตนาเดิมของกล่องนี้ไม่เปลี่ยน
   *    ขึ้นบรรทัดใหม่เฉพาะตอนที่ไม่พอจริง ๆ เท่านั้น
   */
  flex-wrap: wrap;

  /**
   * ⚠️ **ถามขนาดของตัวเอง ไม่ใช่ของจอ** — ผัง `sidestage` วางแถบนี้ไว้ใน
   *    คอลัมน์กว้างราว 46% ของจอ · `@media` จึงตอบผิดเสมอ
   *    (จอ 1200px แต่กล่องกว้าง 550px)
   */
  container-type: inline-size;
  row-gap: 6px;
}

.player__avatar {
  position: relative;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--bg-2);
  display: grid;
  place-items: center;
  flex: none;
  box-shadow: 0 0 0 2px rgba(251, 191, 36, .45);
}

.player__avatar img { width: 100%; height: 100%; object-fit: cover; }
.player__initial    { font-weight: 800; color: var(--accent); font-size: calc(16px * var(--fs, 1)); }

.player__id {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.15;

  /**
   * 🔴 **ที่ว่างต้องไปอยู่ตรงนี้ ไม่ใช่ที่ `margin-left: auto` ของกระเป๋าเงิน**
   *
   *    ตอนเปิด `flex-wrap` แล้ว auto margin จะ "กิน" ที่ว่างที่เหลือทั้งหมด
   *    ทำให้บรรทัดแรกเต็มทันที แล้วปุ่มตกลงบรรทัดใหม่**ทั้งที่ยังพอ**
   *    (วัดได้: กล่อง 520px เคยพอดีแถวเดียว กลับกลายเป็นสองแถว)
   *
   * ⚠️ **ต้องเป็น `flex-basis: 0` ไม่ใช่ `auto`** — เบราว์เซอร์ตัดสินว่าจะขึ้น
   *    บรรทัดใหม่ไหมจาก**ขนาดฐาน**ก่อนหด · ใช้ `auto` = นับความกว้างของชื่อเต็ม ๆ
   *    เข้าไปด้วย แล้วขึ้นบรรทัดใหม่ทั้งที่ชื่อหดได้
   */
  flex: 1 1 0;

  /**
   * 🔴 **ห้ามหดจนเหลือศูนย์** — `flex-basis: 0` ปล่อยให้กล่องนี้หดได้ไม่จำกัด
   *    พอปุ่มเยอะขึ้น (แยกเติม/ถอนเป็นสองปุ่ม) กล่องเหลือ 6px ทั้งที่ชื่อ
   *    กว้าง 32px · ชื่อกับรหัสจึงล้นออกไปทับ "เครดิต" กับยอดเงิน
   *    (เจ้าของเห็นเองจากหน้าจอ 2026-08-30 "ปุ้มซ้อนกัน ดันกันจนล้น")
   *
   * ⚠️ วัดด้วยกล่องจริงไม่เจอ เพราะ**กล่องไม่ได้ทับกัน — ตัวหนังสือล้นออกมา**
   *    ต้องวัดจาก `scrollWidth > clientWidth` ถึงจะเห็น
   */
  min-width: 72px;
}

.player__name {
  font-weight: 700;
  font-size: calc(14px * var(--fs, 1));
  white-space: nowrap;

  /** ⚠️ ตัดท้ายด้วยจุดไข่ปลา ไม่ใช่ล้นออกไปทับของข้าง ๆ */
  overflow: hidden;
  text-overflow: ellipsis;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 22vw;
}

.player__code {
  font-size: calc(11px * var(--fs, 1));
  color: var(--gold);
  letter-spacing: .4px;

  /** ⚠️ ตัดท้ายเหมือนชื่อ — รหัสยาว ๆ ก็ล้นทับได้เหมือนกัน */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ⚠️ เครดิตดันไปชิดขวาเสมอ — เป็นตัวเลขที่ตาไปหาเป็นอันดับแรก */
.player__wallet {
  margin-left: auto;
  display: flex;
  align-items: baseline;
  gap: 5px;

  /**
   * 🔴 **ยอดเงินห้ามหดและห้ามตัดท้าย** — "8,980.00" ที่ถูกตัดเหลือ "8,98…"
   *    คือตัวเลขที่อ่านผิดได้ ซึ่งบนโต๊ะเงินจริงร้ายแรงกว่าชื่อที่ถูกตัด
   */
  flex: 0 0 auto;
  white-space: nowrap;
}

.player__walletLabel { font-size: calc(11px * var(--fs, 1)); color: var(--ink-faint); }

/* ⚠️ ย่อกล่องได้ แต่ห้ามย่อความสำคัญ — ยังต้องเป็นตัวใหญ่สุดในแถว */
.player__credit {
  font-size: calc(20px * var(--fs, 1));
  font-weight: 800;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  display: inline-block;
  transition: transform .2s;
}

/* ⭐ เครดิตขึ้น/ลง — เห็นทันทีว่าเงินขยับจริง ไม่ใช่แค่กระพริบเฉย ๆ */
.player__credit.is-up   { animation: bumpUp .5s ease-out; color: #86efac; }
.player__credit.is-down { animation: bumpDown .5s ease-out; color: #fca5a5; }

@keyframes bumpUp {
  0%   { transform: translate3d(0, 6px, 0); }
  60%  { transform: translate3d(0, -3px, 0); }
  100% { transform: none; }
}

@keyframes bumpDown {
  0%   { transform: translate3d(0, -6px, 0); }
  60%  { transform: translate3d(0, 3px, 0); }
  100% { transform: none; }
}

/**
 * ⚠️ `flex: 0 1 auto` ไม่ใช่ `none` — ยอมให้หดและขึ้นบรรทัดใหม่ได้
 *    `justify-content: flex-end` = ตอนตกลงมาบรรทัดที่สองยังชิดขวาเหมือนเดิม
 */
.player__actions {
  display: flex;
  align-items: center;
  gap: 5px;

  /**
   * ⚠️ **`flex-basis: auto` + ห้ามหด** — ปุ่มที่ถูกบีบจนตัวหนังสือหาย
   *    แย่กว่าปุ่มที่ตกลงบรรทัดใหม่ · ไม่พอเมื่อไหร่ให้ลงบรรทัดใหม่ทั้งก้อน
   */
  flex: 0 0 auto;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/**
 * ⭐ **ตกบรรทัดใหม่แล้วกินเต็มแถว** — ปุ่มสี่ตัวเบียดกันอยู่มุมขวาของแถวที่สอง
 *    อ่านยากกว่ากระจายเต็มแถว · `100%` ทำให้เบราว์เซอร์ตัดบรรทัดให้แน่นอน
 *    เมื่อรวมกับของข้างบนแล้วเกิน
 */
@container (max-width: 33rem) {
  .player__actions { flex: 1 0 100%; justify-content: flex-start; }
}
.player__logout  { margin: 0; }

/* ปุ่มเล็กในแถบผู้เล่น — ยังต้องแตะโดน (44px คือขั้นต่ำที่นิ้วโป้งไม่พลาด) */
.btn--sm {
  min-height: 34px;
  padding: 5px 10px;
  font-size: calc(13px * var(--fs, 1));
  border-radius: 9px;
}

/* ── ผลย้อนหลัง (เค้าไพ่อย่างย่อ) ─────────────────────────────── */
/**
 * 🔴 **หัวข้อต้องอยู่ "เหนือ" ตาราง ไม่ใช่ "ข้าง" ตาราง**
 *
 *    ของเดิมเป็น `flex` แนวนอน ซึ่งพอตอนมีแค่แถบลูกปัดบาง ๆ ก็ดูโอเค
 *    แต่พอเพิ่มตาราง 6×10 กับเค้าไพ่ทำนาย ความกว้างรวมเกินจอมือถือ
 *    → **ทะลุออกไปดันทั้งหน้าให้เลื่อนแนวนอน** หัวเว็บโดนตัด
 *      และแท็บไปทับตัวเลขในตาราง (ผู้เล่นแจ้งว่า "ทับซ้อน")
 *
 * ⚠️ `.road__head` มี `margin-bottom` อยู่แล้ว — มันถูกเขียนไว้สำหรับ
 *    ผังแนวตั้งตั้งแต่แรก · ตัว `.road` ต่างหากที่เพี้ยน
 *
 * 🔴 **`overflow: hidden` ที่ตัวกล่อง** — ลูกที่กว้างเกิน (ตาราง · เค้าไพ่)
 *    ต้องเลื่อนอยู่ในกล่องตัวเอง ห้ามดันหน้าเว็บทั้งหน้า
 */
.road {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  min-width: 0;
  overflow: hidden;
}

/* ⚠️ ลูกทุกตัวต้องยอมหดได้ — `min-width: auto` ของ flex คือต้นเหตุที่ของกว้าง
   ดันกล่องแม่ให้บานออกแทนที่จะเลื่อนในตัวเอง */
.road > * { min-width: 0; max-width: 100%; }

.road__label { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); flex: none; }

.road__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}

/* ⚠️ สี่แท็บบนจอแคบต้องขึ้นบรรทัดใหม่ได้ ไม่ใช่บีบจนอ่านไม่ออก */
.road__tabs { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }

.road__tab {
  padding: 3px 10px;
  border-radius: 999px;
  font-size: calc(12px * var(--fs, 1));
  font-weight: 700;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  transition: color .15s, border-color .15s, background .15s;
}

.road__tab.is-on { color: var(--bg); background: var(--accent); border-color: var(--accent); }

/* ── Big Road — ตารางสตรีคแบบโต๊ะจริง ────────────────────────
   ผลเดิมซ้ำ = ลงคอลัมน์เดิม · ผลเปลี่ยน = ขึ้นคอลัมน์ใหม่
   ผู้เล่นอ่าน "มังกรยาว" กับ "ปิงปอง" จากรูปทรงนี้                        */

/* ⭐ **ตารางผลย้อนหลัง — หน้าตาเดียวกับที่ลูกค้าเห็นในไลน์**
 *
 * 🔴 6 แถวเสมอ ไล่ลงคอลัมน์ก่อนแล้วค่อยข้ามคอลัมน์
 *    เซิร์ฟเวอร์เป็นคนวางช่องมาให้แล้ว (`RecentResults::grid()`)
 *    CSS แค่เรียงตามที่ส่งมา — ห้ามใช้ `grid-auto-flow: column`
 *    มาจัดใหม่เอง เพราะจะกลายเป็นการนับช่องซ้ำอีกที่หนึ่ง
 *
 * ⚠️ ในไลน์ช่องกว้าง 22px ตายตัวบนพื้นขาว · บนเว็บย่อตามจอ
 *    10 คอลัมน์ต้องลงพอดีจอ 360px โดยไม่ต้องเลื่อน
 */
.road__grid {
  display: grid;
  gap: 4px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 2px 0;
}

.road__grid::-webkit-scrollbar { display: none; }

.g6row { display: flex; gap: 4px; }

.g6 {
  flex: none;
  width: clamp(20px, 6.2vw, 28px);
  height: clamp(20px, 6.2vw, 28px);
  border-radius: 50%;
  display: grid;
  place-items: center;
  overflow: hidden;
}

.g6--img img { width: 100%; height: 100%; object-fit: cover; }

/* ⚠️ รอบที่ยังไม่เล่น — เทียบเท่า `#eeeeee` + เลข `#999999` ของไลน์
   แต่พลิกให้เข้ากับพื้นเข้ม ไม่ใช่ยัดวงกลมขาวลงบนจอดำ */
.g6--empty {
  background: var(--sheen);
  border: 1px solid var(--line);
  color: var(--ink-faint);
  font-size: calc(clamp(8px, 2.3vw, 10px) * var(--fs, 1));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ถอยเมื่อโหลดรูปผลไม่ได้ — ยังต้องอ่านออกว่าฝั่งไหนชนะ */
.g6--td1, .g6--td3 { background: var(--tiger); }
.g6--td2, .g6--td4 { background: var(--dragon); }
.g6--td5           { background: var(--tie); }
.g6--td3, .g6--td4 { box-shadow: inset 0 0 0 3px var(--pair); }

.road__big {
  display: flex;
  gap: 2px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 2px;
  background: var(--bg-2);
  border-radius: var(--radius-sm);
}

.road__big::-webkit-scrollbar { display: none; }
.road__empty { margin: 8px auto; color: var(--ink-faint); font-size: calc(13px * var(--fs, 1)); }

.bigcol { display: grid; grid-template-rows: repeat(6, 1fr); gap: 2px; flex: none; }

.bigdot {
  position: relative;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  animation: beadIn .3s ease-out;
}

.bigdot--tiger  { background: var(--tiger); }
.bigdot--dragon { background: var(--dragon); }

/* ⚠️ ช่องว่างต้องมีที่จอง — ไม่งั้นคอลัมน์สั้นยาวไม่เท่ากันแล้วตารางเบี้ยว */
.bigdot--none { background: var(--sheen); animation: none; }

/* ⭐ เสมอเกาะช่องล่าสุด — ไม่ทำให้สตรีคขาด (ธรรมเนียมโต๊ะจริง) */
.bigdot__tie {
  position: absolute;
  right: -2px;
  bottom: -2px;
  min-width: 11px;
  height: 11px;
  border-radius: 999px;
  background: var(--tie);
  color: #04240f;
  font-size: calc(9px * var(--fs, 1));
  font-weight: 800;
  font-style: normal;
  display: grid;
  place-items: center;
}

.road__track {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}

.road__track::-webkit-scrollbar { display: none; }

.bead {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: calc(13px * var(--fs, 1));
  font-weight: 800;
  color: #0b0f16;
  /* ⭐ ผลใหม่เลื่อนเข้าจากขวา — เห็นทันทีว่ามีรอบใหม่เข้ามา */
  animation: beadIn .35s ease-out;
}

@keyframes beadIn {
  from { opacity: 0; transform: translate3d(16px, 0, 0) scale(.6); }
  to   { opacity: 1; transform: none; }
}

.bead--tiger       { background: var(--tiger); }
.bead--dragon      { background: var(--dragon); color: #fff; }
.bead--tiger-pair  { background: var(--tiger); box-shadow: inset 0 0 0 3px var(--pair); }
.bead--dragon-pair { background: var(--dragon); color: #fff; box-shadow: inset 0 0 0 3px var(--pair); }
.bead--tie         { background: var(--tie); }
.bead--muted       { background: var(--ink-faint); }

/* ⭐ **รอบที่ยังไม่เล่น — วงกลมเทามีเลขรอบตรงกลาง**
   หน้าตาเดียวกับตารางผลย้อนหลังในไลน์ที่ลูกค้าเห็นมาตลอด */
.bead--empty {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--ink-faint);
  font-size: calc(11px * var(--fs, 1));
  font-weight: 700;
  animation: none;
}

/* ⭐ วงกลมผลแบบรูปจริง — ชุดเดียวกับตารางผลย้อนหลังในไลน์
   ผู้เล่นเสือมังกรอ่านเค้าไพ่จากรูปที่คุ้นตา ไม่ได้อ่านตัวอักษร */
.bead--img {
  background: transparent;
  box-shadow: none;
  width: 26px;
  height: 26px;
  padding: 0;
  overflow: hidden;
}

.bead--img img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 50%;
}

/* ═══ ป้ายประกาศกลางจอ ═════════════════════════════════════
   🔴 **`position: fixed` กลาง viewport — ไม่ใช่ absolute ในเวทีไลฟ์**

      เดิมวางไว้ในเวที ซึ่งมี `overflow: hidden` และสูงแค่ 34dvh
      ป้ายที่มีรูปจึงสูงเกินเวทีแล้ว**ถูกตัดขอบบน-ล่าง** และดูเหมือนไม่อยู่กลาง
      (ผู้ใช้แจ้งว่า "การแสดงผลไม่สวย ไม่อยู่ตรงกลาง")

      กลาง viewport แปลว่ากลางจริงเสมอ ไม่ว่าเวทีจะสูงเท่าไหร่
      และไม่มีใครตัดขอบมันได้

   ⚠️ ปิดเองเสมอและกดปิดได้ — ป้ายที่ค้างตอนรอบใหม่เปิดแล้ว
      คือป้ายที่บังกระดานในจังหวะที่ต้องรีบแทง                            */

.note {
  position: fixed;
  z-index: 70;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(86vw, 320px);
  max-height: 82dvh;
  overflow: hidden;
  display: grid;
  gap: 10px;
  justify-items: center;
  padding: 18px 18px 16px;
  border-radius: 20px;
  text-align: center;
  background: rgba(9, 13, 20, .93);
  border: 2px solid var(--line);
  backdrop-filter: blur(12px);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .7);
  animation: noteIn .3s cubic-bezier(.2, 1.4, .4, 1);
}

@keyframes noteIn {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.84); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

/* ⚠️ รูปต้องพอดีกรอบเสมอ — รูปที่ลูกค้าอัปมามีทุกสัดส่วน */
.note__img {
  width: 100%;
  max-height: 46dvh;
  object-fit: contain;
  border-radius: 14px;
  animation: notePop .45s cubic-bezier(.2, 1.5, .4, 1);
}

@keyframes notePop {
  0%   { transform: scale(.72) rotate(-3deg); opacity: 0; }
  100% { transform: none; opacity: 1; }
}

.note__body  { display: grid; gap: 3px; }
.note__title { font-size: calc(23px * var(--fs, 1)); font-weight: 800; letter-spacing: .5px; line-height: 1.25; }
.note__sub   { font-size: calc(14px * var(--fs, 1)); color: var(--ink-dim); }

/**
 * ⭐ **ยอดได้เสีย — สีบอกผลก่อนที่จะทันอ่านตัวเลข**
 *
 * ⚠️ สีมาจาก `tone` ของป้าย ไม่ใช่จากเครื่องหมายของตัวเลข —
 *    ตัวเดียวกับที่ตัดสินไอคอนกับเสียง จึงเป็นไปไม่ได้ที่จะขัดกันเอง
 *    (เขียนเงื่อนไขซ้ำใน CSS = วันหนึ่งป้ายบอก WIN แต่ตัวเลขเป็นสีแดง)
 */
.note__net {
  font-size: calc(29px * var(--fs, 1));
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: .5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.note__x {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .45);
  color: var(--ink-dim);
  font-size: calc(15px * var(--fs, 1));
  z-index: 1;
}

/* สีขอบตามจังหวะ — อ่านออกจากมุมตาโดยไม่ต้องอ่านตัวหนังสือ */
.note--open   { border-color: var(--tie);    box-shadow: 0 0 44px rgba(34, 197, 94, .4); }
.note--close  { border-color: var(--dragon); box-shadow: 0 0 44px rgba(239, 68, 68, .36); }
.note--result { border-color: var(--accent); box-shadow: 0 0 44px rgba(56, 189, 248, .36); }
.note--win    { border-color: var(--gold);   box-shadow: 0 0 52px rgba(251, 191, 36, .5); }
.note--win .note__title  { color: var(--gold); }
.note--lose   { border-color: var(--danger); }
.note--lose .note__title { color: #fca5a5; }
.note--even   { border-color: var(--ink-faint); }
.note--even .note__title { color: var(--ink-dim); }

/* ⚠️ ได้ = เขียว · เสีย = แดง · เจ๊า = สีปกติ (ผู้เล่นขอมาแบบนี้)
   หัวข้อ WIN ยังเป็นสีทองเหมือนเดิม — ทองคือ "ชนะ" ไม่ใช่ "ยอดเงิน" */
.note--win  .note__net { color: var(--tie); text-shadow: 0 0 18px rgba(34, 197, 94, .45); }
.note--lose .note__net { color: var(--danger); }
.note--even .note__net { color: var(--ink); }

/* ── แถบยอด เสือ vs มังกร ────────────────────────────────────
   ⚠️ อ่านได้จากมุมตา — ตัวเลขดิบอ่านไม่ทันตอนโต๊ะเร็ว                    */

.vs {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--line);
  font-size: calc(12px * var(--fs, 1));
}

.vs__side { display: flex; align-items: center; gap: 4px; flex: none; }
.vs__side b { font-size: calc(14px * var(--fs, 1)); font-variant-numeric: tabular-nums; }
.vs__side small { color: var(--ink-faint); }
.vs__side--tiger  b { color: var(--tiger); }
.vs__side--dragon b { color: var(--dragon); }

.vs__bar {
  flex: 1;
  min-width: 0;
  height: 8px;
  display: flex;
  border-radius: 999px;
  overflow: hidden;
  background: var(--bg-2);
}

/* ⚠️ กว้างเปลี่ยนแบบมี transition — เห็นว่า "ยอดกำลังไหล" ไม่ใช่กระโดด */
.vs__fill { height: 100%; transition: width .45s cubic-bezier(.3, 1, .4, 1); }
.vs__fill--tiger  { background: linear-gradient(90deg, var(--tiger-2), var(--tiger)); }
.vs__fill--dragon { background: linear-gradient(90deg, var(--dragon), var(--dragon-2)); }

/* ── นับถอยหลัง ─────────────────────────────────────────────── */
.pill--clock { color: #bfe9ff; border-color: rgba(56, 189, 248, .45); font-variant-numeric: tabular-nums; }

/* ⚠️ เหลือ 5 วิ = เต้น — จังหวะสุดท้ายต้องสะดุดตาจริง ๆ */
.pill--clock.is-hot {
  color: #fecaca;
  border-color: rgba(239, 68, 68, .6);
  animation: tick 1s steps(2) infinite;
}

@keyframes tick {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.12); }
}

/* ── ปุ่มลัด (ซ้ำ · ×2 · ตามเค้า) ────────────────────────────── */
/* ⭐ **ปุ่มลัดมีข้อความไทยกำกับ ไม่ใช่วงกลมสัญลักษณ์เปล่า**
 *    `↻ ×2 ↗` ลอย ๆ ไม่มีใครเดาออกว่าคืออะไร และวงกลม 36px
 *    ก็เล็กกว่าเป้าแตะขั้นต่ำ 44px ที่นิ้วโป้งกดพลาดยาก */
.chipbtn {
  min-height: 38px;
  padding: 0 11px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: calc(14px * var(--fs, 1));
  font-weight: 700;
  color: var(--ink-dim);
  background: var(--panel-2);
  border: 1px solid var(--line);
  transition: transform .12s, color .15s, border-color .15s, background .15s;
}

.chipbtn__ico { font-size: calc(15px * var(--fs, 1)); line-height: 1; }

/* ⚠️ ปุ่มตั้งค่าเหรียญไม่ใช่ปุ่มลัดสำหรับแทง — ต้องไม่เด่นเท่ากัน */
.chipbtn--quiet { padding: 0 9px; }
.chipbtn--quiet:not(:disabled) { background: transparent; border-color: var(--line); color: var(--ink-dim); }

.modal__row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }

/* ⭐ **ตัวเลือกเหรียญ — แตะเลือก ไม่ใช่พิมพ์เอง**
 *
 * 🔴 ของเดิมเป็นช่องให้พิมพ์ `20,100,300,1000` — ผู้เล่นที่ทำไม่เป็น
 *    หรือพิมพ์ผิดจะได้แถบเหรียญที่ไม่เหมือนที่ตั้งใจโดยไม่รู้ว่าพลาดตรงไหน
 *    ปุ่มที่เห็นหน้าตาเหรียญจริงตอบคำถาม "จะได้อะไร" ตั้งแต่ก่อนกดบันทึก
 */
.pick {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 9px;
  padding: 4px 0 2px;
}

/* ⚠️ ตัวที่ยังไม่เลือกจางไว้ — เหมือนแถบเหรียญจริงบนกระดาน คนจะได้เชื่อมโยงถูก */
.pick__coin { opacity: .55; }

.pick__coin.is-on {
  opacity: 1;
  filter: none;
  transform: translate3d(0, -3px, 0) scale(1.04);
  box-shadow:
    inset 0 0 0 3px rgba(255, 255, 255, .45),
    0 5px 0 rgba(0, 0, 0, .36),
    0 0 0 3px var(--gold);
}

/* ⚠️ ครบโควตาแล้ว — ดูกดไม่ได้ แต่ยังกดได้จริงเพื่อให้ระบบอธิบายว่าทำไม
   ปุ่มที่เงียบสนิททำให้คนเดาว่าต้องเอาตัวเก่าออกก่อน ซึ่งไม่มีอะไรบอก */
.pick__coin.is-full { opacity: .22; }

.pick__count {
  margin: 2px 0 14px !important;
  text-align: center;
  font-size: calc(14px * var(--fs, 1));
}

.pick__count b { color: var(--gold); }

/* ⭐ ช่องกรอก + พรีวิวอยู่บรรทัดเดียวกัน — เห็นผลทันทีที่พิมพ์ */
.pick__custom { display: flex; align-items: center; gap: 10px; }
.pick__custom .field__input { flex: 1 1 auto; min-width: 0; }

.pick__preview { flex: none; opacity: .3; }
.pick__preview.is-on { opacity: 1; box-shadow: inset 0 0 0 3px rgba(255, 255, 255, .4), 0 0 0 3px var(--gold); }

/* ⚠️ ปุ่มที่ "กดได้" ต้องดูต่างจากปุ่มที่ "กดไม่ได้" ชัด ๆ
   ผู้เล่นเคยแจ้งว่า "กดอะไรไม่ได้เลย" ทั้งที่บางปุ่มเปิดอยู่ —
   เพราะจาง 0.32 กับ 1.0 บนพื้นเข้มแยกด้วยตาแทบไม่ออก */
.chipbtn:not(:disabled) {
  color: var(--ink);
  border-color: rgba(56, 189, 248, .45);
  background: rgba(56, 189, 248, .12);
}

.chipbtn:not(:disabled):hover { color: var(--accent); border-color: var(--accent); }
.chipbtn:active:not(:disabled) { transform: scale(.93); }
.chipbtn:disabled { opacity: .3; }

/* ── กระดาน ──────────────────────────────────────────────────── */
.board {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1.15fr 1fr;
  gap: 8px;
  padding: 10px;
  border-radius: var(--radius);
  background:
    radial-gradient(90% 70% at 50% 0%, rgba(56, 189, 248, .08), transparent 70%),
    linear-gradient(180deg, var(--panel), var(--bg-2));
  /* ⚠️ **เตี้ยกว่าที่คิดว่าสวย** — บนมือถือกระดานแย่งที่กับไลฟ์และแถบเหรียญ
     ผู้เล่นแจ้งเองว่า "กระดานคำสั่งแทงสูงเกินไป"                        */
  min-height: 170px;
}

/* ⭐ ขอบกระดานเรืองเมื่อเปิดรับ · หรี่ลงเมื่อปิด — บอกสถานะโดยไม่ต้องอ่าน */
.board__frame {
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  border: 2px solid transparent;
  pointer-events: none;
  transition: border-color .3s, box-shadow .3s;
}

.board.is-open .board__frame {
  border-color: rgba(34, 197, 94, .45);
  box-shadow: inset 0 0 24px rgba(34, 197, 94, .12);
  animation: frameGlow 2.4s ease-in-out infinite;
}

.board.is-locked .board__frame { border-color: rgba(239, 68, 68, .3); }

@keyframes frameGlow {
  0%, 100% { box-shadow: inset 0 0 20px rgba(34, 197, 94, .10); }
  50%      { box-shadow: inset 0 0 34px rgba(34, 197, 94, .22); }
}

.board__mid { display: grid; grid-template-rows: 1fr 1fr; gap: 8px; }

.board__lock {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  background: rgba(7, 10, 16, .62);
  backdrop-filter: blur(2px);
  font-weight: 800;
  font-size: calc(18px * var(--fs, 1));
  color: #fca5a5;
  letter-spacing: .5px;
  z-index: 5;
  animation: fadeIn .2s ease-out;
}

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* ── ประตูแทง ────────────────────────────────────────────────
   🔴 **ทุกอย่างอยู่กลางกรอบแนวตั้ง** — ของเดิมใช้ `align-content: start`
      ปุ่มเสือ/มังกรสูงกว่าปุ่มกลาง ไอคอนจึงลอยเกาะขอบบน (ผู้ใช้แจ้งมาเอง)  */

.gate {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;   /* ← กลางแนวตั้งเสมอ ไม่ว่าปุ่มจะสูงเท่าไหร่ */
  gap: 2px;
  padding: 9px 8px;
  min-height: 74px;
  border-radius: 16px;
  border: 2px solid var(--line);
  background: linear-gradient(180deg, var(--sheen), var(--veil));
  color: var(--ink);
  overflow: hidden;
  isolation: isolate;
  transition: transform .12s, border-color .2s, box-shadow .25s;
}

.gate:active { transform: scale(.97); }

/* ⭐ แสงวิ่งตอนแตะ — บอกว่าปุ่มรับแล้วก่อนที่ชิปจะบินถึง */
.gate__glow {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  background: radial-gradient(60% 60% at 50% 50%, currentColor, transparent 70%);
  transition: opacity .35s;
}

.gate:active .gate__glow { opacity: .22; transition: none; }

/* ⚠️ ค่า rgba ตรงนี้ต้องเป็นสีเดียวกับ `--tiger` — เขียนแยกเพราะต้องใส่ alpha
   เปลี่ยนโทเคนแล้วลืมแก้บรรทัดนี้ = ปุ่มเสือเป็นคนละสีกับทุกที่ในหน้า */
.gate--tiger  { border-color: rgba(59, 111, 224, .55); background: linear-gradient(180deg, rgba(59, 111, 224, .17), var(--veil)); color: var(--tiger); }
.gate--dragon { border-color: rgba(239, 68, 68, .55);  background: linear-gradient(180deg, rgba(239, 68, 68, .17), var(--veil)); color: var(--dragon); }
.gate--pair   { border-color: rgba(168, 85, 247, .55); background: linear-gradient(180deg, rgba(168, 85, 247, .15), var(--veil)); color: var(--pair); }
.gate--tie    { border-color: rgba(34, 197, 94, .55);  background: linear-gradient(180deg, rgba(34, 197, 94, .15), var(--veil)); color: var(--tie); }

/* ⭐ **รูปที่ตั้งไว้ = พื้นหลังทั้งกรอบ**
   ⚠️ `cover` + `center` — กรอบประตูบนมือถือเป็นแนวตั้ง รูปจัตุรัสจะโดนครอบตัด
      บน-ล่าง จึงแนะนำให้ทำรูปมาแนวตั้ง 2:3 (600×900) ในหน้าตั้งค่า */
.gate.has-bg {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* 🔴 **ต้องมีชั้นมืดคั่น** — รูปสว่างทำให้ตัวหนังสือขาวอ่านไม่ออก
   และกองชิปกับยอดโต๊ะจมหายไปในลาย · ถ้าลูกค้าไม่อยากได้ก็ปิดการซ้อน
   ตัวหนังสือทั้งหมดที่ /settings แทน ไม่ใช่ถอดชั้นนี้ */
.gate.has-bg::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, .18), rgba(0, 0, 0, .58));
  z-index: 0;
}

.gate.has-bg > * { position: relative; z-index: 1; }

/* ⚠️ ตัวหนังสือบนรูปต้องมีเงา — พื้นหลังเป็นอะไรก็ได้ที่เราคุมไม่ได้ */
.gate.has-bg .gate__name,
.gate.has-bg .gate__rate { text-shadow: 0 2px 6px rgba(0, 0, 0, .85); }

/* ⚠️ ช่องไอคอนขนาดคงที่ — ปุ่มทั้ง 4 จึงเรียงกันตรงแม้ไอคอนคนละสัดส่วน */
.gate__icon {
  display: grid;
  place-items: center;
  width: clamp(34px, 9.5vw, 62px);
  height: clamp(34px, 9.5vw, 62px);
  font-size: calc(clamp(24px, 7vw, 40px) * var(--fs, 1));
  line-height: 1;
  animation: breathe 4s ease-in-out infinite;
}

/* ⭐ รูปแทนไอคอน — แนะนำ 256×256 px พื้นหลังโปร่ง ≤120 KB */
.gate__icon img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 3px 8px rgba(0, 0, 0, .5));
}

.gate__name { font-weight: 700; font-size: calc(clamp(13px, 3.6vw, 18px) * var(--fs, 1)); color: var(--ink); }
.gate__rate { font-size: calc(clamp(11px, 3vw, 13px) * var(--fs, 1)); color: var(--gold); font-weight: 700; }

.gate__table {
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: calc(11px * var(--fs, 1));
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* ⭐ ประตูที่ชนะ — เรืองแสงและเด้ง */
.gate.is-won {
  border-color: var(--gold);
  box-shadow: 0 0 0 2px rgba(251, 191, 36, .5), 0 0 34px rgba(251, 191, 36, .4);
  animation: winPulse 1s ease-out 3;
}

@keyframes winPulse {
  0%, 100% { transform: none; }
  40%      { transform: scale(1.04); }
}

/* ── กองชิป ──────────────────────────────────────────────────── */
.gate__stack {
  position: relative;
  margin-top: 6px;
  min-height: 30px;
  display: grid;
  place-items: center;
}

.stack__coin {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #fde68a, var(--gold) 45%, #b45309);
  box-shadow: 0 3px 0 #92400e, 0 6px 10px rgba(0, 0, 0, .45);
  /* ⭐ กองชิป "เด้ง" ทุกครั้งที่ยอดเพิ่ม — สื่อว่ารับคำสั่งแล้ว */
  animation: chipDrop .3s cubic-bezier(.2, 1.6, .4, 1);
}

@keyframes chipDrop {
  0%   { transform: translate3d(0, -22px, 0) scale(.7); opacity: .2; }
  70%  { transform: translate3d(0, 3px, 0) scale(1.06); opacity: 1; }
  100% { transform: none; }
}

.stack__sum {
  position: absolute;
  font-size: calc(13px * var(--fs, 1));
  font-weight: 800;
  color: #3b2200;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
  font-variant-numeric: tabular-nums;
}

/* ⭐ เหรียญบินจากแถบล่างไปที่ประตู — องค์ประกอบชั่วคราวที่ js สร้างแล้วลบทิ้ง */
.flying {
  position: fixed;
  z-index: 60;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #fde68a, var(--gold) 45%, #b45309);
  box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
  pointer-events: none;
  transition: transform .34s cubic-bezier(.3, .9, .3, 1), opacity .34s;
}

/* ⚠️ เด้งกลับ = ไม่ผ่าน — ต้องรู้สึกต่างจากตอนสำเร็จอย่างชัดเจน */
.gate.is-rejected { animation: shake .32s ease-in-out; border-color: var(--danger); }

@keyframes shake {
  0%, 100% { transform: none; }
  25%      { transform: translate3d(-5px, 0, 0); }
  75%      { transform: translate3d(5px, 0, 0); }
}

/* ── กล่อง "โพยของฉัน" ───────────────────────────────────────
   ⭐ ใช้ได้ทุกเกม — ปุ่มอยู่ในถาดเหรียญ ตัววาดอยู่ใน live.js
   ⚠️ ตัวเลขใช้ `tabular-nums` ทั้งหมด ไม่งั้นคอลัมน์ยอดเต้นตามความกว้างเลข */

.mybets {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  gap: 4px;
  max-height: 46vh;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .18) transparent;
}

.mybets__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .05);
}

.mybets__name { font-weight: 700; min-width: 0; overflow-wrap: anywhere; }
.mybets__amt  { font-weight: 900; color: var(--gold); font-variant-numeric: tabular-nums; }

.mybets__round {
  font-size: calc(13px * var(--fs, 1));
  font-weight: 700;
  color: var(--ink-dim);
  margin-left: 6px;
}

.mybets__empty { color: var(--ink-dim); margin: 10px 0 0; }

.mybets__total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  color: var(--ink-dim);
}

.mybets__total b { color: var(--ink); font-size: calc(20px * var(--fs, 1)); font-variant-numeric: tabular-nums; }

/* ── กล่องบอกเหตุผลที่แทงไม่ติด ──────────────────────────────
   ⭐ ใช้ได้ทุกเกม — สร้างโดย live.js จากคำตอบของ /api/bet
   🔴 ต้องอ่านง่ายกว่ารายการโพยปกติ เพราะนี่คือข่าวร้ายที่ผู้เล่นต้องอ่านจริง */

.betbad {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  gap: 6px;
  max-height: 44vh;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .18) transparent;
}

.betbad__row {
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: rgba(244, 63, 94, .12);
  border: 1px solid rgba(244, 63, 94, .35);
}

.betbad__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.betbad__name { font-weight: 800; min-width: 0; overflow-wrap: anywhere; }
.betbad__amt  { font-weight: 900; font-variant-numeric: tabular-nums; color: var(--ink-dim); }

/** ⚠️ เหตุผลคือสิ่งที่ผู้เล่นต้องอ่าน — ห้ามเล็กกว่าชื่อโพย */
.betbad__why {
  margin-top: 3px;
  font-size: calc(14px * var(--fs, 1));
  line-height: 1.45;
  color: var(--ink);
}

.betbad__note { margin: 10px 0 0; font-size: calc(13px * var(--fs, 1)); color: var(--ink-dim); }

/* ── แถบเหรียญ ───────────────────────────────────────────────── */
/* ⚠️ แถบเหรียญกินที่เท่าที่เหลือ — ปุ่มด้านขวาไม่ถูกบีบจนกดไม่โดน */
/* ⭐ **แถบเหรียญ — สองแถวเสมอ ไม่ใช่แถวเดียวแล้วเลื่อน**
 *
 * 🔴 **แถวเดียวคือที่มาของ "เหรียญล้นกรอบ"**
 *    ของเดิมเป็น grid สองคอลัมน์ `minmax(0,1fr) auto` — คอลัมน์ขวา
 *    (ปุ่มลัด + เคลียร์ + ยอดวางแล้ว) กินความกว้างตามเนื้อหาก่อน
 *    เหลืออะไรค่อยให้เหรียญ พอมี 6 เหรียญบนจอ 360px เหรียญสุดท้าย
 *    ก็โดนตัดครึ่งอยู่ใต้ปุ่ม ↻ ทั้งที่ `overflow-x: auto` เลื่อนได้
 *    — แต่ไม่มีใครรู้ว่าเลื่อนได้ **มันอ่านเหมือนเว็บพัง ไม่ใช่แถบเลื่อน**
 *    (ผู้เล่นแจ้งเรื่องนี้สองรอบติด)
 *
 * ⭐ แยกเป็นสองแถว เหรียญได้ความกว้างเต็มไปเลย แล้ว `flex-wrap`
 *    เป็นตาข่ายรับท้าย — เหรียญตกลงไปแถวล่างได้ แต่**ไม่มีทางโดนตัด**
 *    ห้ามเปลี่ยนกลับไปใช้ `overflow-x` แทน `wrap`
 */
.tray {
  display: grid;
  gap: 8px;
  padding: 9px 10px calc(9px + var(--safe-b));
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.tray__coins {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 9px 8px;
  padding: 4px 0 2px;
}

/* 🔴 ห้ามตั้งขนาดตายตัว — 6 เหรียญ (5 + กำหนดเอง) บนจอ 360px
   ต้องลงแถวเดียวได้พอดี · `12vw` ที่ 360px = 43px + ช่องไฟ = พอดีกรอบ */
.coin {
  position: relative;
  flex: none;
  width: clamp(40px, 11vw, 50px);
  height: clamp(40px, 11vw, 50px);
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 800;
  font-size: calc(clamp(11px, 2.9vw, 14px) * var(--fs, 1));
  color: #2a1a00;
  border: none;
  box-shadow:
    inset 0 0 0 3px rgba(255, 255, 255, .3),
    inset 0 0 0 5px rgba(0, 0, 0, .16),
    0 3px 0 rgba(0, 0, 0, .34),
    0 5px 10px rgba(0, 0, 0, .3);
  transition: transform .16s cubic-bezier(.2, 1.4, .4, 1), box-shadow .2s, filter .2s;
  filter: saturate(.6) brightness(.74);
}

/* ⭐ แสงวาบมุมบนซ้าย — ทำให้เหรียญดูนูนเป็นวัตถุจริง ไม่ใช่วงกลมแบน */
.coin::after {
  content: '';
  position: absolute;
  inset: 12% 30% 55% 14%;
  border-radius: 50%;
  background: linear-gradient(180deg, rgba(255, 255, 255, .55), transparent);
  pointer-events: none;
}

.coin__face { position: relative; z-index: 1; letter-spacing: -.3px; }

.coin--1 { background: radial-gradient(circle at 32% 28%, #f1f5f9, #94a3b8 55%, #475569); }
.coin--2 { background: radial-gradient(circle at 32% 28%, #bbf7d0, #22c55e 55%, #15803d); }
.coin--3 { background: radial-gradient(circle at 32% 28%, #bfdbfe, #3b82f6 55%, #1d4ed8); color: #fff; }
.coin--4 { background: radial-gradient(circle at 32% 28%, #e9d5ff, #a855f7 55%, #6b21a8); color: #fff; }
.coin--5 { background: radial-gradient(circle at 32% 28%, #fde68a, #f59e0b 55%, #b45309); }
.coin--6 { background: radial-gradient(circle at 32% 28%, #fecdd3, #f43f5e 55%, #9f1239); color: #fff; }

.coin.is-active {
  filter: none;
  transform: translate3d(0, -5px, 0) scale(1.07);
  box-shadow:
    inset 0 0 0 3px rgba(255, 255, 255, .45),
    inset 0 0 0 5px rgba(0, 0, 0, .18),
    0 6px 0 rgba(0, 0, 0, .38),
    0 0 0 3px var(--gold),
    0 8px 18px rgba(251, 191, 36, .38);
}

.coin:active { transform: scale(.93); }

/* ── แถวล่าง: ปุ่มลัด · เคลียร์ · ยอดที่วางแล้ว ─────────────── */
.tray__ops {
  display: flex;
  align-items: center;
  gap: 5px;
  padding-top: 7px;
  border-top: 1px solid var(--line);
}

/* ⚠️ ยอดวางแล้วดันไปชิดขวาด้วย `margin-left:auto` ไม่ใช่ `justify-content`
   — ปุ่มซ้ายต้องเกาะกลุ่มกันไว้ ไม่ใช่กระจายเต็มแถว */
.tray__staked {
  margin-left: auto;
  text-align: right;
  line-height: 1.15;
  flex: none;
}

.tray__stakedLabel { display: block; font-size: calc(11px * var(--fs, 1)); color: var(--ink-faint); }

.tray__stakedValue {
  font-weight: 800;
  font-size: calc(19px * var(--fs, 1));
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

/* ⭐ **หน้าเลือกฝั่ง — สองการ์ดคู่กันให้เทียบได้ในสายตาเดียว**
   🔴 เทียบไม่ได้ในจอเดียว = ต้องเลื่อนขึ้นลงจำตัวเลข ซึ่งจำผิดง่าย
      และนี่เป็นการตัดสินใจที่ผู้เล่นเปลี่ยนเองไม่ได้ */
.entry__card--wide { width: min(680px, 100%); }

.pick2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 4px 0 6px; }

@media (max-width: 560px) { .pick2 { grid-template-columns: 1fr; } }

.pick2__card {
  display: grid;
  gap: 3px;
  padding: 14px;
  border-radius: 14px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  text-align: center;
}

.pick2__label  { font-weight: 800; font-size: calc(16px * var(--fs, 1)); }
.pick2__hint   { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); }

.pick2__credit {
  margin-top: 6px;
  font-size: calc(28px * var(--fs, 1));
  font-weight: 800;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.pick2__unit { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); margin-bottom: 8px; }

.pick2__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 8px;
  margin: 0 0 12px;
  text-align: left;
}

.pick2__facts dt { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); }
.pick2__facts dd { margin: 0; font-size: calc(13px * var(--fs, 1)); text-align: right; font-variant-numeric: tabular-nums; }

/* ⭐ แถบโหมดผู้ชม — แทนที่กล่องผู้เล่นตอนยังไม่ล็อกอิน */
.guestbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
}

.guestbar__text { font-size: calc(14px * var(--fs, 1)); color: var(--ink-dim); line-height: 1.4; }
.guestbar__text b { color: var(--ink); }

/**
 * 🔴 **บอกจังหวะของเกมก่อน แล้วค่อยเสนอทางเข้า**
 *
 *    แถบเดิมพูดถึงแต่สิ่งที่ผู้ชม**ทำไม่ได้** ("ต้องเข้าระบบก่อน")
 *    ตัวใหม่บอก**สิ่งที่กำลังเกิดขึ้น**ก่อน (รอบเปิดอยู่ · ลงแล้วกี่คน)
 *    แล้วค่อยมีปุ่ม — จังหวะที่คนตัดสินใจสมัครคือตอนเห็นรอบกำลังจะเปิด
 *
 * ⚠️ ต้องขึ้นบรรทัดใหม่ได้ — บนมือถือแนวตั้งของสามชิ้นนี้ไม่มีทางอยู่แถวเดียว
 */
.guestbar { flex-wrap: wrap; }

.guestbar__now { display: flex; align-items: center; gap: 6px; flex: none; }

/** ⚠️ ป้ายสถานะรอบต้องเด่นที่สุดในแถบ — มันคือเหตุผลที่คนจะอยู่ต่อ */
.guestbar__badge {
  font-size: calc(13px * var(--fs, 1));
  font-weight: 800;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .08);
  border: 1px solid var(--line);
  white-space: nowrap;
}

.guestbar__badge.is-open {
  background: rgba(34, 197, 94, .16);
  border-color: rgba(34, 197, 94, .45);
  color: #4ade80;
  animation: guestPulse 1.6s ease-in-out infinite;
}

@keyframes guestPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, .35); }
  60%      { box-shadow: 0 0 0 7px rgba(34, 197, 94, 0); }
}

.guestbar__hint { font-size: calc(13px * var(--fs, 1)); color: #4ade80; font-variant-numeric: tabular-nums; }
.guestbar__go   { display: flex; gap: 6px; flex: none; }

/* ⭐ **เค้าไพ่ทำนาย 3 ใบ**
   🔴 แดง = รูปทรงซ้ำเดิม · น้ำเงิน = รูปทรงเปลี่ยน — **ไม่ได้บอกผลรอบหน้า**
      สีมาจากกติกาสากล ไม่ใช่สีของเสือ/มังกร จึงตั้งเป็นสีตรง ๆ ไม่ใช้โทเคนธีม */
.road__derived { display: grid; gap: 7px; padding: 2px 0; }

.dr { display: flex; align-items: center; gap: 8px; min-height: 20px; }

.dr__name {
  flex: none;
  width: 74px;
  font-size: calc(12px * var(--fs, 1));
  color: var(--ink-faint);
  font-weight: 700;
}

.dr__dots { display: flex; gap: 3px; flex-wrap: nowrap; overflow: hidden; }

.dr__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex: none;
}

.dr__dot--r { background: #e8455f; }
.dr__dot--b { background: #3b6fe0; }
.dr__empty  { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); }

.road__note { margin: 2px 0 0; font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); line-height: 1.5; }
.road__note b { color: var(--ink-dim); }

/* ⭐ ปุ่มสลับช่องไลฟ์ — อยู่มุมบนซ้ายของจอไลฟ์ ไม่ทับปุ่มเปิดเสียง */
.stream__src {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 5;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: calc(16px * var(--fs, 1));
  color: #fff;
  background: rgba(0, 0, 0, .5);
  backdrop-filter: blur(6px);
}

/* ⭐ ป้ายผลถอน — ใช้โทนเดียวกับ WIN/LOST ของรอบ
   ⚠️ ผู้เล่นคุ้นกับสีชุดนี้จากป้ายได้-เสียอยู่แล้ว ใช้สีชุดใหม่คือให้เรียนรู้ใหม่ */
.note--win  .note__net { animation: netPop .45s cubic-bezier(.2, 1.5, .4, 1); }
.note--lose .note__net { animation: netPop .45s cubic-bezier(.2, 1.5, .4, 1); }

@keyframes netPop {
  0%   { transform: scale(.6); opacity: 0; }
  60%  { transform: scale(1.12); opacity: 1; }
  100% { transform: scale(1); }
}

/* ⭐ **สลิปกำลังถูกตรวจ — เส้นกวาดวิ่งผ่านรูป**
   🔴 SlipOK ใช้เวลา 2-4 วิ · จอที่นิ่งอยู่เฉย ๆ นานขนาดนั้นทำให้คนกดซ้ำ
      หรือปิดหน้าไปเลย · เส้นกวาดบอกว่า "ระบบยังทำงานอยู่" โดยไม่ต้องอ่าน */
.drop.is-scan { border-color: var(--accent); position: relative; overflow: hidden; }

.drop.is-scan::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 34%;
  /** ⚠️ ต้องมี `top` ตั้งต้น — animation ขยับด้วย `transform` จากจุดนี้ */
  top: 0;
  background: linear-gradient(180deg, transparent, rgba(56, 189, 248, .38), transparent);
  animation: scanSweep 1.5s ease-in-out infinite;
  pointer-events: none;
}

/**
 * 🔴 **ขยับด้วย `transform` ไม่ใช่ `top` (แก้ 2026-08-31)**
 *
 *    ของเดิมไล่ `top: -34% → 100%` ซึ่งบังคับให้เบราว์เซอร์คำนวณผังใหม่
 *    **ทุกเฟรม** ตลอดเวลาที่แถบสแกนวิ่ง · `transform` วิ่งบน GPU
 *    ไม่แตะผังเลย · หน้าตาเหมือนเดิมเป๊ะเพราะเป็นชั้นซ้อนที่ลอยอยู่แล้ว
 *
 * ⚠️ เจอตอนไล่ตรวจ keyframes ทั้งไฟล์ด้วยเบราว์เซอร์จริง
 *    ตอนทำลูกเล่นหน้าล็อกอิน
 */
@keyframes scanSweep {
  0%   { transform: translate3d(0, -100%, 0); }
  100% { transform: translate3d(0, 294%, 0); }
}

/* ⭐ **เครื่องหมายถูกวาดเองทีละเส้น**
   ⚠️ บอกว่า "จบแล้วและสำเร็จ" ชัดกว่าอิโมจินิ่ง ๆ เพราะการเคลื่อนไหว
      สื่อว่ามีบางอย่าง "เพิ่งเกิดขึ้น" ไม่ใช่ป้ายที่ค้างอยู่ตั้งแต่แรก */
.okmark { display: grid; place-items: center; margin: 4px 0 10px; }
.okmark svg { width: 74px; height: 74px; }

.okmark__c {
  stroke: var(--tie);
  stroke-width: 3;
  stroke-dasharray: 151;
  stroke-dashoffset: 151;
  animation: drawRing .5s cubic-bezier(.2, .8, .3, 1) forwards;
}

.okmark__k {
  stroke: var(--tie);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 40;
  stroke-dashoffset: 40;
  animation: drawRing .32s .42s cubic-bezier(.2, .8, .3, 1) forwards;
}

@keyframes drawRing { to { stroke-dashoffset: 0; } }

/* ⚠️ ไม่ผ่าน = สั่น — รับรู้ได้เร็วกว่าอ่านข้อความ */
.failmark {
  display: grid;
  place-items: center;
  width: 66px;
  height: 66px;
  margin: 4px auto 10px;
  border-radius: 50%;
  font-size: calc(32px * var(--fs, 1));
  font-weight: 800;
  color: #fff;
  background: var(--danger);
  animation: failShake .5s cubic-bezier(.36, .07, .19, .97);
}

@keyframes failShake {
  0%, 100%   { transform: translateX(0); }
  20%, 60%   { transform: translateX(-8px); }
  40%, 80%   { transform: translateX(8px); }
}

.center { text-align: center; }

/* ⭐ **การ์ดคำขอถอนที่ยังไม่จบ** — ยอด · บัญชี · เวลา · สถานะ
   🔴 ผู้เล่นที่กดถอนแล้วอยากรู้แค่สามอย่าง: ได้กี่บาท เข้าบัญชีไหน ถึงไหนแล้ว */
.wreq {
  padding: 14px;
  margin-bottom: 14px;
  border-radius: 14px;
  background: linear-gradient(135deg, rgba(251, 191, 36, .14), rgba(0, 0, 0, .26));
  border: 1px solid rgba(251, 191, 36, .38);
}

.wreq__head { display: flex; align-items: center; gap: 8px; font-size: calc(14px * var(--fs, 1)); }
.wreq__st   { margin-left: auto; font-size: calc(13px * var(--fs, 1)); color: var(--gold); }

/* ⚠️ จุดเต้น = "ยังไม่จบ" — สื่อสถานะรอโดยไม่ต้องมีตัวหนังสือเพิ่ม */
.wreq__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--gold);
  animation: livePulse 1.6s ease-in-out infinite;
}

@keyframes livePulse {
  0%, 100% { opacity: 1;  box-shadow: 0 0 0 0 rgba(251, 191, 36, .55); }
  50%      { opacity: .5; box-shadow: 0 0 0 7px rgba(251, 191, 36, 0); }
}

.wreq__amt {
  margin: 8px 0 10px;
  font-size: calc(29px * var(--fs, 1));
  font-weight: 800;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.wreq__facts { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0; }
.wreq__facts dt { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); }
.wreq__facts dd { margin: 0; font-size: calc(13px * var(--fs, 1)); text-align: right; }
.wreq__old  { color: var(--danger); }

.wreq__warn {
  margin: 10px 0 0;
  font-size: calc(13px * var(--fs, 1));
  color: #ffc9d2;
}

/* ⭐ **แท็บกระเป๋าเงิน — ฝาก / ถอน / ประวัติ**
   ⚠️ อยู่บนสุดของป๊อปอัพก่อนหัวข้อ — คนต้องเห็นว่ามีทางเลือกอื่นก่อนอ่านเนื้อหา */
.wtabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin: 0 0 14px;
  border-radius: 999px;
  background: var(--bg-2);
  border: 1px solid var(--line);
}

.wtabs__b {
  flex: 1;
  min-height: 36px;
  border-radius: 999px;
  font-size: calc(14px * var(--fs, 1));
  font-weight: 700;
  color: var(--ink-dim);
  transition: background .15s, color .15s;
}

.wtabs__b.is-on { background: var(--accent); color: #06121c; }

/* ⭐ ประวัติเติม-ถอน */
.mlog { display: grid; gap: 6px; max-height: 52dvh; overflow-y: auto; }

.mlog__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 12px;
  background: var(--bg-2);
  border: 1px solid var(--line);
}

.mlog__left  { display: grid; gap: 2px; }
.mlog__right { display: grid; gap: 2px; text-align: right; }
.mlog__at    { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); }

.mlog__right b { font-variant-numeric: tabular-nums; font-size: calc(16px * var(--fs, 1)); }
.mlog__right b.is-up   { color: var(--tie); }
.mlog__right b.is-down { color: var(--danger); }

.mlog__st { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); }

/* ⚠️ ใบที่ยังไม่จบต้องเด่นกว่าใบที่จบแล้ว — เป็นตัวเดียวที่ผู้เล่นรออยู่ */
.mlog__st--open { color: var(--gold); font-weight: 700; }

/* ⭐ กล่องถอนเงิน */
.wd { display: grid; gap: 10px; }

.wd__max {
  text-align: center;
  font-size: calc(14px * var(--fs, 1));
  color: var(--ink-dim);
  padding: 8px;
  border-radius: 10px;
  background: var(--bg-2);
}

.wd__max b { color: var(--gold); font-size: calc(19px * var(--fs, 1)); font-variant-numeric: tabular-nums; }

/* 🔴 คำเตือนต้องเห็นชัดตั้งแต่ตอนพิมพ์ ไม่ใช่รอกดแล้วค่อยรู้ */
.wd__warn {
  margin: 0;
  padding: 8px 12px;
  border-radius: 10px;
  font-size: calc(14px * var(--fs, 1));
  background: rgba(244, 63, 94, .14);
  border: 1px solid rgba(244, 63, 94, .4);
  color: #ffc9d2;
}

/* ⭐ กล่องสถิติของผู้เล่น */
.st { display: grid; gap: 12px; }

.st__big {
  text-align: center;
  font-size: calc(34px * var(--fs, 1));
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.st__big.is-up   { color: var(--tie); }
.st__big.is-down { color: var(--danger); }

.st__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

.st__grid > div {
  display: grid;
  gap: 2px;
  padding: 9px 11px;
  border-radius: 12px;
  background: var(--bg-2);
  border: 1px solid var(--line);
}

.st__grid span { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); }
.st__grid b    { font-size: calc(16px * var(--fs, 1)); font-variant-numeric: tabular-nums; }
.st__grid b.is-up   { color: var(--tie); }
.st__grid b.is-down { color: var(--danger); }

.st__head { margin: 4px 0 0; font-size: calc(14px * var(--fs, 1)); color: var(--ink-dim); }

.st__row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 10px;
  align-items: center;
  padding: 7px 11px;
  border-radius: 10px;
  background: var(--bg-2);
  font-size: calc(14px * var(--fs, 1));
}

.st__row b.is-up   { color: var(--tie); }
.st__row b.is-down { color: var(--danger); }

/* ═══ กล่องแจ้งเตือน ════════════════════════════════════════ */
.toast-stack {
  position: fixed;
  left: 50%;
  top: 12px;
  transform: translateX(-50%);
  z-index: 90;
  display: grid;
  gap: 8px;
  width: min(420px, calc(100% - 24px));
  pointer-events: none;
}

.toast {
  padding: 12px 16px;
  border-radius: 14px;
  font-size: calc(15px * var(--fs, 1));
  font-weight: 600;
  line-height: 1.45;
  box-shadow: var(--shadow);
  animation: toastIn .26s cubic-bezier(.2, 1.4, .4, 1);
  backdrop-filter: blur(8px);
}

.toast--ok   { background: rgba(34, 197, 94, .92);  color: #04240f; }
.toast--bad  { background: rgba(244, 63, 94, .92);  color: #2b0009; }
.toast--warn { background: rgba(251, 191, 36, .94); color: #2a1a00; }
.toast.is-out { animation: toastOut .22s ease-in forwards; }

@keyframes toastIn {
  from { opacity: 0; transform: translate3d(0, -16px, 0) scale(.94); }
  to   { opacity: 1; transform: none; }
}

@keyframes toastOut {
  to { opacity: 0; transform: translate3d(0, -12px, 0); }
}

/* ═══ โมดัล ═════════════════════════════════════════════════ */
.modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 16px;
}

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(3, 6, 12, .72);
  backdrop-filter: blur(4px);
  animation: fadeIn .18s ease-out;
}

.modal__panel {
  position: relative;
  width: min(440px, 100%);
  max-height: 88dvh;
  overflow-y: auto;
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 22px;
  box-shadow: var(--shadow);
  animation: modalIn .24s cubic-bezier(.2, 1.3, .4, 1);
}

@keyframes modalIn {
  from { opacity: 0; transform: translate3d(0, 18px, 0) scale(.96); }
  to   { opacity: 1; transform: none; }
}

.modal__x {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  color: var(--ink-dim);
  font-size: calc(16px * var(--fs, 1));
}

.modal h2 { margin: 0 0 4px; font-size: calc(21px * var(--fs, 1)); }
.modal p  { margin: 0 0 14px; color: var(--ink-dim); font-size: calc(15px * var(--fs, 1)); line-height: 1.6; }

/* ── การ์ดบัญชีรับเงิน ───────────────────────────────────────── */
.bankcard {
  display: grid;
  gap: 8px;
  padding: 16px;
  margin-bottom: 16px;
  border-radius: 14px;
  background: linear-gradient(135deg, rgba(251, 191, 36, .16), rgba(0, 0, 0, .26));
  border: 1px solid rgba(251, 191, 36, .34);
}

.bankcard__row   { display: flex; justify-content: space-between; gap: 12px; font-size: calc(15px * var(--fs, 1)); }
.bankcard__key   { color: var(--ink-dim); }
.bankcard__value { font-weight: 700; text-align: right; word-break: break-all; }

.bankcard__num {
  font-size: calc(24px * var(--fs, 1));
  font-weight: 800;
  color: var(--gold);
  letter-spacing: 1px;
  font-variant-numeric: tabular-nums;
  -webkit-user-select: text;
  user-select: text;
}

.drop {
  display: grid;
  place-items: center;
  gap: 6px;
  padding: 22px;
  border: 2px dashed var(--line);
  border-radius: 14px;
  color: var(--ink-dim);
  font-size: calc(15px * var(--fs, 1));
  text-align: center;
  transition: border-color .18s, background .18s;
}

.drop.is-hot { border-color: var(--accent); background: rgba(56, 189, 248, .08); }
.drop__icon  { font-size: calc(32px * var(--fs, 1)); }

.drop__preview {
  max-height: 190px;
  border-radius: 10px;
  margin: 0 auto;
}

/* ⭐ วงกลมหมุนตอนกำลังตรวจสลิป — SlipOK ใช้เวลา 2-3 วินาที ห้ามปล่อยจอเงียบ */
.spinner {
  width: 26px;
  height: 26px;
  border: 3px solid rgba(255, 255, 255, .18);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin .7s linear infinite;
  margin: 0 auto;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* ⭐ ฝนเหรียญตอนเติมเงินสำเร็จ / ชนะรอบใหญ่ */
.rain {
  position: fixed;
  inset: 0;
  z-index: 95;
  pointer-events: none;
  overflow: hidden;
}

.rain__coin {
  position: absolute;
  top: -40px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #fde68a, var(--gold) 45%, #b45309);
  animation: fall linear forwards;
}

@keyframes fall {
  to { transform: translate3d(0, 110vh, 0) rotate(540deg); opacity: .2; }
}

/* ═══ สไตล์พื้นผิว ═══════════════════════════════════════════
   ⭐ **แกนที่สองของธีม** — จานสีบอกว่า "สีอะไร" ส่วนสไตล์บอกว่า "ผิวแบบไหน"
      โทเคนอย่างเดียวทำไม่ได้ทั้งหมด (`backdrop-filter` ไม่ใช่ค่าสี)
      จึงต้องมีกฎชุดเล็ก ๆ ชุดนี้

   🔴 **ห้ามแตะพื้นหลังของ `.gate` เด็ดขาด**
      สีปุ่มประตูคือสีที่ผู้เล่นใช้อ่านกระดาน (เสือ=น้ำเงิน มังกร=แดง)
      และรูปที่ลูกค้าอัปเองอยู่ใน `background-image` ของปุ่มนั้น —
      เขียนทับ `background` = ทั้งสีประจำประตูและรูปของลูกค้าหายพร้อมกัน

   ⚠️ `classic` ไม่มีกฎในบล็อกนี้โดยตั้งใจ — ของเดิมต้องเหมือนเดิมเป๊ะ    */

/* ── กระจก — โปร่งจริงต้องมี blur คู่กัน ไม่งั้นได้แค่ "จาง" ── */
[data-style="glass"] .player,
[data-style="glass"] .vs,
[data-style="glass"] .road,
[data-style="glass"] .board,
[data-style="glass"] .tray,
[data-style="glass"] .chat,
[data-style="glass"] .say {
  background: var(--surface);
  border-color: var(--edge);
  -webkit-backdrop-filter: blur(var(--panel-blur, 0px));
  backdrop-filter: blur(var(--panel-blur, 0px));
}

/* ── นีออน — เรืองที่ขอบแผง ไม่ใช่ย้อมทั้งแผง ── */
[data-style="neon"] .player,
[data-style="neon"] .vs,
[data-style="neon"] .board,
[data-style="neon"] .tray {
  border-color: var(--edge);
  box-shadow: var(--glow);
}

/* ⚠️ `.gate.is-won` (0,2,0) ยังชนะกฎนี้ (0,1,1) — ปุ่มที่ชนะต้องเป็นทองเสมอ */
[data-style="neon"] .gate {
  box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 15%, transparent);
}

/* ── หรูหรา — ไล่เฉดเข้ม-อ่อน + เส้นทองบาง ๆ ── */
[data-style="luxe"] .player,
[data-style="luxe"] .vs,
[data-style="luxe"] .road,
[data-style="luxe"] .board,
[data-style="luxe"] .tray {
  background: var(--surface);
  border-color: var(--edge);
  box-shadow: var(--glow);
}

/* ── มินิมอล — เอาเงากับไล่เฉดออกให้หมด เหลือเส้นขอบคม ๆ ── */
[data-style="flat"] .player,
[data-style="flat"] .vs,
[data-style="flat"] .road,
[data-style="flat"] .board,
[data-style="flat"] .tray,
[data-style="flat"] .chat,
[data-style="flat"] .say {
  background: var(--surface);
  box-shadow: none;
}

/* ── จักรพรรดิ — แล็กเกอร์ขอบทองนูนสองชั้น ─────────────────────
   🔴 **ขายขาดให้ร้านเดียว** — ดู `LiveTheme::EXCLUSIVE`

   ⚠️ ทุกค่ามาจากโทเคนของสไตล์ (`--surface` `--edge` `--glow` `--shadow`)
      ไม่มีรหัสสีของจานสีอยู่ตรงนี้เลย — จานสีไหนมาต่อก็ยังเป็นจานสีนั้น

   🔴 **ห้ามแตะสีประจำประตู** (`--tiger` `--dragon` `--pair` `--tie`)
      สไตล์แต่งได้แค่ "เปลือก" ของแผง ไม่ใช่สีที่ผู้เล่นใช้อ่านฝั่ง    */
[data-style="imperial"] .player,
[data-style="imperial"] .vs,
[data-style="imperial"] .road,
[data-style="imperial"] .board,
[data-style="imperial"] .tray,
[data-style="imperial"] .chat,
[data-style="imperial"] .say {
  background: var(--surface);
  border-color: var(--edge);
  box-shadow: var(--shadow), var(--glow);
}

/* ⭐ **เส้นทองบางด้านในอีกชั้น** — สิ่งที่ทำให้ขอบดู "ตีนูน" คือการมี
      สองเส้นห่างกันนิดเดียว ไม่ใช่การทำเส้นเดียวให้หนาขึ้น

   ⚠️ `bento` ใช้ `::before` ของ `.board` เหมือนกันแต่คนละแกน
      (ผัง vs สไตล์) — จับคู่กันแล้วกฎหลังชนะ ซึ่งไม่พัง แค่ได้กรอบเดียว */
[data-style="imperial"] .board,
[data-style="imperial"] .tray { position: relative; }

[data-style="imperial"] .board::before,
[data-style="imperial"] .tray::before {
  content: "";
  position: absolute;
  inset: 5px;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: calc(var(--radius) - 5px);
  pointer-events: none;
}

/* ═══ โมดูลพิเศษ ══════════════════════════════════════════════
   ⭐ ทุกตัวปิดเป็นค่าเริ่มต้น · เปิดทีละตัวที่ /settings

   🔴 **ไม่มีโมดูลไหนแตะกระดานหรือปุ่มแทง** — ของประดับที่บังปุ่มแทง
      คือของประดับที่ทำให้เสียเงิน (แตะพลาดในเกมเงินจริง)             */

/* ── 1. เอฟเฟกต์ตอนออกผล ─────────────────────────────────────── */

/* 🔴 `pointer-events: none` ตลอดเวลา — ชั้นที่กินการแตะคือชั้นที่บังปุ่ม */
.fx {
  position: fixed;
  inset: 0;
  z-index: 80;
  pointer-events: none;
  opacity: 0;
}

.fx.is-on { animation: fxSweep 1.4s cubic-bezier(.22, 1, .36, 1); }

.fx.is-on::before {
  content: '';
  position: absolute;
  inset: -20% -60%;
  background: linear-gradient(105deg, transparent 42%, color-mix(in srgb, var(--gold) 55%, transparent) 50%, transparent 58%);
  animation: fxStreak 1.4s cubic-bezier(.22, 1, .36, 1);
}

@keyframes fxSweep {
  0%   { opacity: 0; }
  18%  { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes fxStreak {
  0%   { transform: translateX(-60%); }
  100% { transform: translateX(60%); }
}

/* ⚠️ ประกายที่ปุ่มชนะ — `is-won` (0,2,0) ยังคุมสีทองอยู่ อันนี้เติมแค่จังหวะเด้ง */
.gate.is-pop { animation: gatePop .9s cubic-bezier(.34, 1.56, .64, 1); }

@keyframes gatePop {
  0%   { transform: scale(1); }
  22%  { transform: scale(1.07); }
  45%  { transform: scale(.98); }
  70%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}

/* ── 1b. ชุดอลังการ — เหรียญทองโปรยตอนออกผล ───────────────────
   🔴 **ปิดสนิทจนกว่าจะมีทั้ง `[data-grandfx]` และ `.is-on`**
      ชั้นนี้อยู่บนหน้าเสมอเมื่อโมดูลเปิด (js จะได้หาเจอ) — สิ่งที่
      ทำให้มันเงียบคือ `opacity: 0` กับ `animation: none` ไม่ใช่การไม่มีตัวตน

   ⚠️ **`pointer-events: none` ตลอด** — ชั้นที่กินการแตะคือชั้นที่บังปุ่มแทง

   ⚠️ **เหรียญย้อมด้วย `--accent` ของธีม** ไม่ใช่รหัสทองตายตัว
      ร้านที่ใช้จานสีอื่นจะได้เหรียญสีของร้านตัวเอง ไม่ใช่ทองที่เราเลือกให้ */
.grandfx {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  z-index: 3;
}

.grandfx__coin {
  position: absolute;
  top: -8%;
  /** ⚠️ กระจายตำแหน่งจาก `--n` ที่ PHP ใส่มา — ไม่ต้องมี js มาสุ่มให้ */
  left: calc(3% + var(--n) * 7%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 30%,
      color-mix(in srgb, var(--accent) 92%, #fff) 0 26%,
      var(--accent) 42%,
      color-mix(in srgb, var(--accent) 62%, #000) 100%);
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 45%, transparent);
  opacity: 0;
}

/* ⭐ ยิงทีเดียวจบ — js ถอด `is-on` ออกเองเมื่อจบจังหวะ */
[data-grandfx] .grandfx.is-on { opacity: 1; }

[data-grandfx] .grandfx.is-on .grandfx__coin {
  animation: grandCoin 1.5s cubic-bezier(.3, .7, .4, 1) forwards;
  /** ⚠️ เหลื่อมเวลาทีละชิ้น ไม่งั้นได้ "แถวเหรียญ" ไม่ใช่ "เหรียญโปรย" */
  animation-delay: calc(var(--n) * 55ms);
}

/* ⭐ ชนะแล้วโปรยหนักขึ้นกับนานขึ้น — ไม่ใช่เอฟเฟกต์คนละตัว */
[data-grandfx] .grandfx.is-on.is-won .grandfx__coin {
  animation-duration: 2.1s;
  animation-iteration-count: 2;
}

@keyframes grandCoin {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg) scale(.7); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translate3d(0, 118dvh, 0) rotate(540deg) scale(1); opacity: 0; }
}

/* ⭐ **ประกายทองกวาดแถบเหรียญตอนออกผล** — ของที่ผู้เล่นจ้องอยู่แล้ว */
[data-grandfx] .tray { position: relative; overflow: hidden; }

[data-grandfx] .tray::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg,
    transparent 40%,
    color-mix(in srgb, var(--accent) 26%, transparent) 50%,
    transparent 60%);
  transform: translateX(-120%);
  pointer-events: none;
}

[data-grandfx] .grandfx.is-on ~ * .tray::after,
[data-grandfx] .tray.is-sheen::after { animation: grandSheen 1.1s ease-out; }

@keyframes grandSheen {
  to { transform: translateX(120%); }
}

/* ⭐ **ไอคอนขยับ** — เหรียญในแถบเลือกยอดเด้งเบา ๆ ตอนถูกเลือก
   ⚠️ แตะแค่ `transform` — สีของเหรียญเป็นของธีม ไม่ใช่ของโมดูลนี้ */
[data-grandfx] .tray__coins .coin.is-on { animation: grandCoinPick .5s cubic-bezier(.34, 1.56, .64, 1); }

@keyframes grandCoinPick {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.12); }
  100% { transform: scale(1); }
}

/* 🔴 **คนที่ตั้งค่าลดการเคลื่อนไหวต้องได้หน้านิ่งจริง ๆ**
      ไม่ใช่ "ช้าลง" — อาการเมาภาพไม่ได้หายไปเพราะช้าลง */
@media (prefers-reduced-motion: reduce) {
  [data-grandfx] .grandfx.is-on,
  [data-grandfx] .grandfx.is-on .grandfx__coin { animation: none; opacity: 0; }
  [data-grandfx] .tray.is-sheen::after,
  [data-grandfx] .tray__coins .coin.is-on { animation: none; }
}

/* ── 2. การ์ดสถิติสด ─────────────────────────────────────────── */
.stats {
  display: grid;
  gap: 7px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface, linear-gradient(135deg, var(--panel), var(--panel-2)));
}

.stats__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.stats__label { font-size: calc(13px * var(--fs, 1)); font-weight: 700; color: var(--ink-dim); }
.stats__n { font-size: calc(12px * var(--fs, 1)); color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* ⭐ แถบสัดส่วน — อ่านจากมุมตาได้เร็วกว่าตัวเลข (แบบเดียวกับแถบยอดสองฝั่ง) */
.stats__bars { display: flex; gap: 3px; height: 8px; }

.stats__bar {
  flex: 1;
  display: flex;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  overflow: hidden;
}

.stats__bar--dragon { justify-content: flex-end; }
.stats__bar i { display: block; height: 100%; border-radius: 999px; transition: width .4s cubic-bezier(.22, 1, .36, 1); }
.stats__bar--tiger i  { background: var(--tiger); }
.stats__bar--dragon i { background: var(--dragon); }

.stats__row { display: flex; gap: 6px; }

.stats__cell {
  flex: 1;
  display: grid;
  justify-items: center;
  gap: 1px;
  padding: 4px 2px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}

.stats__cell b { font-size: calc(16px * var(--fs, 1)); font-weight: 800; font-variant-numeric: tabular-nums; }
.stats__cell small { font-size: calc(10px * var(--fs, 1)); color: var(--ink-faint); }
.stats__cell--streak b { font-size: calc(13px * var(--fs, 1)); color: var(--gold); }

/* ⭐ ผล 20 รอบล่าสุด — ล่าสุดอยู่ขวาสุด เหมือนกราฟทุกอันที่คนคุ้น */
.stats__spark { display: flex; gap: 2px; align-items: flex-end; height: 14px; }

.spark { flex: 1; border-radius: 2px; }
.spark--tiger  { height: 100%; background: var(--tiger); }
.spark--dragon { height: 100%; background: var(--dragon); }
.spark--tie    { height: 45%; background: var(--tie); }

.stats__note { margin: 0; font-size: calc(11px * var(--fs, 1)); color: var(--ink-faint); line-height: 1.4; }
.stats__note b { color: var(--ink-dim); }

/* ── 3. เมนูลอย ──────────────────────────────────────────────── */
/* ⚠️ ต้องไม่ทับแถบเหรียญ — วางเหนือ safe-area ของ iPhone ด้วย */
.fab {
  position: fixed;
  right: 12px;
  bottom: calc(84px + var(--safe-b));
  z-index: 70;
  display: grid;
  justify-items: end;
  gap: 8px;
}

/* ── เมนูลอยแบบลากย้าย/พับซ่อน (เจ้าของสั่ง 2026-10-02) ─────────────
   🔴 กฎทุกตัวผูกกับ `[data-fab-move]` — ร้านที่ไม่ได้ตั้ง (`fixed`)
      ไม่มีแอตทริบิวต์นี้ จึงไม่โดนอะไรแตะเลย
   ⚠️ `touch-action: none` เฉพาะปุ่มกลม — ไม่งั้นลากแล้วจอเลื่อนตามนิ้ว */

.fab[data-fab-move] .fab__btn {
  touch-action: none;
  cursor: grab;
  transition: transform .3s cubic-bezier(.22, 1, .36, 1), opacity .3s;
}

.fab.is-left { justify-items: start; }

.fab.is-dragging .fab__btn { cursor: grabbing; transform: scale(1.1); transition: none; }

/** ⭐ พับซ่อน — เหลือครึ่งตัวโผล่ขอบจอ จาง ๆ ไม่บังกระดาน */
.fab.is-tucked .fab__btn { transform: translateX(calc(50% + 12px)); opacity: .55; }
.fab.is-left.is-tucked .fab__btn { transform: translateX(calc(-50% - 12px)); }

/** ⚠️ ลากไว้บนจอ = ที่เหนือปุ่มไม่พอ เมนูกางลงล่างแทน */
.fab.is-below .fab__menu { position: absolute; top: calc(100% + 8px); right: 0; }
.fab.is-left.is-below .fab__menu { right: auto; left: 0; }

.fab__btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(140deg, var(--accent), var(--accent-2));
  box-shadow: 0 8px 22px rgba(0, 0, 0, .45);
}

.fab__bars { display: grid; gap: 4px; }

.fab__bars i {
  display: block;
  width: 18px;
  height: 2px;
  border-radius: 2px;
  background: #06121c;
  transition: transform .25s cubic-bezier(.34, 1.56, .64, 1), opacity .2s;
}

/* ⭐ ขีดสามเส้นกลายเป็นกากบาทตอนเปิด — บอกว่ากดอีกทีคือปิด */
.fab.is-open .fab__bars i:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.fab.is-open .fab__bars i:nth-child(2) { opacity: 0; }
.fab.is-open .fab__bars i:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.fab__menu {
  display: grid;
  gap: 4px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  animation: fabIn .22s cubic-bezier(.34, 1.56, .64, 1);
}

@keyframes fabIn {
  from { opacity: 0; transform: translateY(8px) scale(.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.fab__item {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 12px;
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: calc(14px * var(--fs, 1));
  font-weight: 600;
  white-space: nowrap;
  text-align: left;
}

.fab__item:active { background: color-mix(in srgb, var(--accent) 18%, transparent); }
.fab__ico { font-size: calc(16px * var(--fs, 1)); }

/* ── เมนูลอยแบบหมึก — เมนูเดิมทุกปุ่ม เพิ่มแค่จังหวะที่มันโผล่ ───────
 *
 * ══════════════════════════════════════════════════════════════
 * 🔴 **เพิ่มการเคลื่อนไหวเท่านั้น ห้ามขยับผัง**
 *    ตำแหน่ง · ขนาด · สี · ระยะห่าง ของเมนูนี้ไม่เปลี่ยนสักค่า —
 *    มีโต๊ะจริงเปิดใช้อยู่ 3 ร้าน · ปุ่มที่ย้ายที่เองตอน deploy
 *    คือปุ่มที่คนกดพลาดในวันแรก
 *
 * ⚠️ **ไล่ทีละใบ ไม่ใช่โผล่พร้อมกัน** — ตาไล่ตามของที่เข้ามาทีละชิ้น
 *    แล้วรู้ว่ามีกี่ตัวเลือกโดยไม่ต้องนับ · แต่ต้องจบภายใน ~0.3 วินาที
 *    ไม่งั้นกลายเป็นเมนูที่ "ช้า" ซึ่งแย่กว่าเมนูที่ไม่มีลูกเล่นเลย
 *
 * ⚠️ `prefers-reduced-motion` ถูกดับรวมไว้แล้วที่หัวไฟล์ (บรรทัด ~146)
 *    — ทั้ง `animation` และ `transition` เหลือ .001ms ให้เอง
 * ══════════════════════════════════════════════════════════════
 */
.fab__item {
  position: relative;
  overflow: hidden;
  animation: fabItemIn .26s cubic-bezier(.22, 1, .36, 1) both;
}

.fab__item:nth-child(1) { animation-delay: .02s; }
.fab__item:nth-child(2) { animation-delay: .05s; }
.fab__item:nth-child(3) { animation-delay: .08s; }
.fab__item:nth-child(4) { animation-delay: .11s; }
.fab__item:nth-child(5) { animation-delay: .14s; }
.fab__item:nth-child(6) { animation-delay: .17s; }

@keyframes fabItemIn {
  from { opacity: 0; transform: translateX(10px); }
  to   { opacity: 1; transform: none; }
}

/**
 * ⭐ **รอยหมึกตอนแตะ** — วงกลมจาง ๆ แผ่จากกลางปุ่มแล้วหายไป
 *
 * 🔴 **`::after` ไม่ใช่ `::before`** — `.fab__ico` เป็นลูกจริงของปุ่ม
 *    ซึ่งต้องอยู่เหนือรอยหมึก · ใช้ `::before` แล้วต้องไปไล่ใส่ `z-index`
 *    ให้ลูกทุกตัว ซึ่งเป็นของที่วันหนึ่งจะลืมตัวใดตัวหนึ่ง
 *
 * ⚠️ ไม่มี `pointer-events` ของตัวเอง — `inset: 0` ทับทั้งปุ่มพอดี
 *    ปล่อยให้รับคลิกแทนปุ่มเมื่อไหร่ `data-fab-go` จะไม่ทำงาน
 */
.fab__item::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 42%, transparent);
  opacity: 0;
  transform: scale(.3);
  pointer-events: none;
}

.fab__item:active::after {
  animation: fabInk .42s ease-out;
}

@keyframes fabInk {
  from { opacity: .55; transform: scale(.3); }
  to   { opacity: 0;   transform: scale(9); }
}

/**
 * ⚠️ **ปุ่มหลักไม่หมุน** — ขีดสามเส้นกลายเป็นกากบาทอยู่แล้ว · หมุนทั้งปุ่ม
 *    ซ้อนอีกชั้นแปลว่าระหว่างเปลี่ยนมีสองอย่างขยับพร้อมกันบนของชิ้นเดียว
 *    ซึ่งอ่านเป็น "กระตุก" มากกว่า "ลื่น"
 */

/* ── 4. โหมดโฟกัส ────────────────────────────────────────────── */
/**
 * 🔴 **ห้ามซ่อนแถบเครดิตกับปุ่มแทง** — สองอย่างนี้คือเหตุผลที่หน้านี้มีอยู่
 *    โหมดที่ซ่อนยอดเงินคือโหมดที่คนแทงโดยไม่รู้ว่าเหลือเท่าไร
 */
.is-focus .road,
.is-focus .stats,
.is-focus .chat,
.is-focus .say,
.is-focus .vs { display: none; }

.is-focus .live__stage { height: 46dvh; min-height: 220px; }

/* ⚠️ ทางออกต้องเห็นเสมอในโหมดนี้ — ไม่งั้นเข้าแล้วออกไม่ได้ */
.focusout {
  position: fixed;
  left: 50%;
  top: calc(10px + env(safe-area-inset-top, 0px));
  transform: translateX(-50%);
  z-index: 75;
  display: none;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .2);
  background: rgba(0, 0, 0, .6);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: #f8fafc;
  font-size: calc(13px * var(--fs, 1));
  font-weight: 700;
}

.is-focus .focusout { display: block; }

/* ═══ ผังหน้าจอ ═══════════════════════════════════════════════
   ⭐ **แกนที่สามของหน้าตา** — ธีมบอก "สีอะไร" · สไตล์บอก "ผิวแบบไหน"
      ผังบอก **"อะไรอยู่ตรงไหน และอะไรใหญ่กว่ากัน"**

   🔴 **ทุกผังใช้ DOM ชุดเดิม** — ไม่มีผังไหนมีมาร์กอัปของตัวเอง
      ผังที่มีมาร์กอัปของตัวเอง = แก้หน้าเล่นทีต้องแก้ทุกผัง
      แล้วผังที่คนใช้น้อยจะพังเงียบ ๆ จนลูกค้าโทรมา

   🔴 **`classic` ไม่มีกฎในบล็อกนี้เลยแม้แต่บรรทัดเดียว** — เป็นค่าพื้นฐาน
      ของโต๊ะที่เปิดขายอยู่ · มีกฎเมื่อไร = หน้าตาเปลี่ยนโดยไม่มีใครสั่ง

   🔴 **ทั้งบล็อกอยู่ใน `@media` ที่กันจอเตี้ยแนวนอนออก**
      จอนั้นมีผังเฉพาะของมันอยู่แล้ว (สูง ~280–330px ไม่มีที่ให้จัดอะไรใหม่)
      กฎผังมีความจำเพาะสูงกว่า (0,2,0 > 0,1,0) จึงชนะกฎจอเตี้ยเสมอ
      ถ้าไม่กันไว้ = หมุนมือถือเป็นแนวนอนแล้วผังพังกลับไปเหมือนก่อนแก้    */
@media (orientation: portrait), (min-height: 521px) {

  /* ── โมเดิร์น — โครงเดิมเป๊ะ เปลี่ยนแค่ "ผิว" ───────────────
     ⚠️ ห้ามแตะขนาดหรือตำแหน่งแม้แต่ค่าเดียว — ทั้งข้อของผังนี้คือ
        "ดูใหม่โดยที่ผู้เล่นไม่ต้องเรียนรู้อะไรใหม่"                  */
  [data-layout="modern"] .player,
  [data-layout="modern"] .vs,
  [data-layout="modern"] .road,
  [data-layout="modern"] .board,
  [data-layout="modern"] .tray {
    border-radius: calc(var(--radius) + 4px);
    box-shadow: var(--shadow), inset 0 1px 0 color-mix(in srgb, var(--ink) 8%, transparent);
  }

  /* ⭐ เส้นเรืองบางบนหัวการ์ด — เทคนิคเดียวกับ bento ของ skill-ui */
  [data-layout="modern"] .board { position: relative; overflow: hidden; }

  [data-layout="modern"] .board::after {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--accent), transparent);
    opacity: .5;
    pointer-events: none;
  }

  /* ⭐ ยอดเครดิตเป็นตัวอักษรไล่เฉด — จุดที่ตาไปหยุดอยู่แล้ว */
  [data-layout="modern"] .player__credit {
    background: linear-gradient(135deg, var(--gold), var(--accent));
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
  }

  /* ⭐ ปุ่มหลักมีแสงกวาด — เทคนิค "shine sweep" */
  [data-layout="modern"] .btn--gold { position: relative; overflow: hidden; }

  [data-layout="modern"] .btn--gold::after {
    content: '';
    position: absolute;
    top: 0;
    left: -75%;
    width: 45%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .35), transparent);
    transform: skewX(-20deg);
    animation: sweep 3.6s ease-in-out infinite;
    pointer-events: none;
  }

  /* ⚠️ สปริงตอนกด — หนักกว่าของเดิมนิดเดียว ไม่ใช่เด้งจนกดพลาด */
  [data-layout="modern"] .gate {
    transition: transform .16s cubic-bezier(.34, 1.56, .64, 1), border-color .2s, box-shadow .25s;
  }

  [data-layout="modern"] .gate:active { transform: scale(.94); }

  /* ── โรงหนัง — ไลฟ์ใหญ่ กระดานย่อ ─────────────────────────── */
  /**
   * ⚠️ **46dvh ไม่ใช่ 50** — วัดจริงบนจอ 330×740 แล้ว 50dvh ดันให้ทั้งหน้า
   *    ต้องเลื่อนมากกว่าผังเดิมอย่างเห็นได้ชัด · ผังนี้ควรแลกแค่ "เค้าไพ่ย่อ"
   *    กับ "กระดานเตี้ยลง" ไม่ใช่แลกกับการต้องเลื่อนหาปุ่มแทง
   */
  [data-layout="theater"] .live__stage { height: 46dvh; min-height: 220px; }
  [data-layout="theater"] .board { min-height: 130px; }
  [data-layout="theater"] .gate { min-height: 58px; padding: 6px; }
  [data-layout="theater"] .gate__icon { width: 34px; height: 34px; font-size: calc(25px * var(--fs, 1)); }

  /* ⚠️ เค้าไพ่ยุบเป็นค่าเริ่มต้น ไม่ใช่ซ่อนถาวร — ยังกดแท็บเปิดดูได้
     ผังนี้แลกพื้นที่เค้าไพ่ไปให้ไลฟ์ ซึ่งเป็นสิ่งที่คนเลือกผังนี้ต้องการ */
  [data-layout="theater"] .road__grid,
  [data-layout="theater"] .road__track,
  [data-layout="theater"] .road__big { max-height: 5.4rem; overflow-y: auto; }

  /* ── ไลฟ์เต็มจอ — ไลฟ์เป็นพื้นหลัง แผงลอยทับ ───────────────── */
  [data-layout="immersive"] .live { grid-template-rows: 1fr; }

  /**
   * ⚠️ **ผังนี้เวทีเป็น `position: fixed` จึงหลุดออกจากกริด** — เหลือลูก
   *    2 คนคือรางข้อความวิ่งกับแผงเล่น · ปล่อยไว้ที่ `1fr` แถวเดียว
   *    รางจะกินแถวนั้นทั้งแถวแล้วดันแผงเล่นตกไปแถวโดยปริยาย
   *
   * 🔴 กฎ `:has()` ตัวข้างบนสู้ไม่ได้เพราะ specificity เท่ากันแต่มาก่อน —
   *    ต้องประกาศซ้ำในบล็อกนี้ ไม่ใช่หวังว่าตัวเดิมจะครอบมาถึง
   */
  /**
   * ⭐ **ผังนี้ข้อความวิ่งอยู่ล่างสุด ไม่ใช่บนสุด**
   *
   * 🔴 วัดด้วย headless แล้วเจอ: ปล่อยตามลำดับ DOM รางไปโผล่ที่ **ขอบบนจอ**
   *    ห่างจากแผงเล่นครึ่งจอ — อ่านเหมือนแถบของเว็บอื่นที่หลงมา
   *    ผังนี้ไลฟ์เป็นพื้นหลังเต็มจอ ทุกอย่างจึงลอยทับอยู่แล้วโดยเจตนา
   *    สิ่งที่ทำได้คือให้มันอยู่**ติดกับของอื่น** ไม่ใช่ลอยเดี่ยวกลางไลฟ์
   *
   * ⚠️ ต้องย้ายด้วย `grid-row` ไม่ใช่ `order` — `order` ไม่ขยับตำแหน่งแถว
   *    ที่ประกาศไว้ตายตัว และอ่านยากกว่ามากเวลาไล่ผัง
   */
  [data-layout="immersive"] .live:has(> [data-chat-mode="ticker"]) {
    grid-template-rows: 1fr auto;
  }

  [data-layout="immersive"] .live__panel        { grid-row: 1; }
  [data-layout="immersive"] [data-chat-mode="ticker"] { grid-row: 2; padding: 0 10px 6px; }

  [data-layout="immersive"] .live__stage {
    position: fixed;
    inset: 0;
    height: 100dvh;
    z-index: 0;
  }

  [data-layout="immersive"] .live__panel {
    position: relative;
    z-index: 2;
    grid-template-rows: none;
    align-content: end;
    align-content: safe end;
    background: linear-gradient(0deg, color-mix(in srgb, var(--bg) 92%, transparent), transparent);
  }

  /* ⚠️ โปร่งต้องมาคู่กับ blur ไม่งั้นได้แค่ "จาง" แล้วอ่านตัวหนังสือไม่ออก */
  [data-layout="immersive"] .player,
  [data-layout="immersive"] .vs,
  [data-layout="immersive"] .road,
  [data-layout="immersive"] .board,
  [data-layout="immersive"] .tray {
    background: color-mix(in srgb, var(--panel) 66%, transparent);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }

  /**
   * 🔴 **ต้องยุบของที่ไม่จำเป็นออกจริง ๆ ไม่ใช่แค่ตั้งใจให้ไลฟ์ใหญ่**
   *
   *    วัดจริงบนจอ 330×740: แผงเต็มความสูง 700px → ไลฟ์ที่อยู่ข้างหลัง
   *    เหลือให้เห็น 40px · ผังที่ชื่อว่า "ไลฟ์เต็มจอ" แต่มองไม่เห็นไลฟ์เลย
   *    คือผังที่ผิดสัญญากับคนเลือก
   *
   *    เค้าไพ่จึงหายไปทั้งก้อน (ดูตอนสลับไปผังอื่นได้) และกระดานย่อลง
   *    เหลือประมาณครึ่งหนึ่ง — ที่เหลือคือไลฟ์
   */
  [data-layout="immersive"] .road { display: none; }

  /**
   * ⚠️ **ช่องพิมพ์แชทหายไปด้วย — ฟองแชทยังลอยทับไลฟ์อยู่**
   *    ผังนี้แลกช่องพิมพ์เป็นพื้นที่ไลฟ์ · คนที่อยากคุยสลับไปผังอื่นได้
   *    (กติกาเดียวกับที่จอเตี้ยแนวนอนทำอยู่แล้ว)
   */
  [data-layout="immersive"] .say { display: none; }

  [data-layout="immersive"] .live__panel { gap: 6px; padding: 6px 8px calc(6px + var(--safe-b)); }
  [data-layout="immersive"] .board { min-height: 0; gap: 6px; padding: 7px; }
  [data-layout="immersive"] .gate { min-height: 62px; padding: 5px; }
  [data-layout="immersive"] .gate__icon { width: clamp(30px, 8vw, 44px); height: clamp(30px, 8vw, 44px); font-size: calc(clamp(20px, 5.5vw, 30px) * var(--fs, 1)); }
  [data-layout="immersive"] .tray { gap: 5px; padding: 6px 8px calc(6px + var(--safe-b)); }
  [data-layout="immersive"] .stats { padding: 6px 9px; gap: 5px; }

  /**
   * 🔴 **แถบชื่อโต๊ะต้องลอยอยู่เหนือแผงเสมอในผังนี้**
   *    เวทีไลฟ์เป็น `z-index: 0` ส่วนแผงเป็น `2` — แถบชื่อโต๊ะกับป้ายรอบ
   *    จึงถูกแผงทับหายไปทั้งแถบ ทั้งที่เป็นที่เดียวที่บอกว่ารอบเปิดอยู่ไหม
   */
  [data-layout="immersive"] .stage__bar { z-index: 5; }

  /* ── กระดานใหญ่ — ไลฟ์เล็กสุด ปุ่มแทงใหญ่สุด ────────────────── */
  [data-layout="compact"] .live__stage { height: 22dvh; min-height: 130px; }
  [data-layout="compact"] .board { min-height: 250px; }
  [data-layout="compact"] .gate { min-height: 104px; }
  [data-layout="compact"] .gate__icon { width: clamp(40px, 11vw, 66px); height: clamp(40px, 11vw, 66px); }
  [data-layout="compact"] .gate__name { font-size: calc(clamp(15px, 4.2vw, 20px) * var(--fs, 1)); }
  [data-layout="compact"] .coin { width: clamp(46px, 12.5vw, 56px); height: clamp(46px, 12.5vw, 56px); }
}

/* ── เบนโตะ — จัดเป็นกระเบื้อง ────────────────────────────────
   ⭐ แต่ละส่วนเป็นการ์ดที่มี "เส้นสีประจำตัว" อยู่ขอบบน
      (เทคนิค `--hue` ต่อการ์ดของ skill-ui) — กวาดตาแล้วรู้ทันทีว่า
      กำลังดูส่วนไหน โดยไม่ต้องอ่านหัวข้อ

   🔴 **สองคอลัมน์เฉพาะจอที่กว้างพอจริง ๆ** — บีบสองคอลัมน์ลงจอ 390px
      ได้การ์ดกว้าง 180px ที่ทั้งยอดเงินและปุ่มตกบรรทัดกันหมด
      จอแคบจึงเป็นกระเบื้องเรียงลงมาแถวเดียว ซึ่งยังเป็นเบนโตะอยู่      */
[data-layout="bento"] .player,
[data-layout="bento"] .vs,
[data-layout="bento"] .road,
[data-layout="bento"] .board,
[data-layout="bento"] .tray {
  position: relative;
  overflow: hidden;
  border-radius: calc(var(--radius) + 2px);
}

[data-layout="bento"] .player::before,
[data-layout="bento"] .vs::before,
[data-layout="bento"] .road::before,
[data-layout="bento"] .board::before,
[data-layout="bento"] .tray::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--hue, var(--accent));
  opacity: .8;
  pointer-events: none;
  z-index: 4;
}

[data-layout="bento"] .player { --hue: var(--gold); }
[data-layout="bento"] .vs     { --hue: var(--tiger); }
[data-layout="bento"] .road   { --hue: var(--accent); }
[data-layout="bento"] .board  { --hue: var(--tie); }
[data-layout="bento"] .tray   { --hue: var(--pair); }

@media (min-width: 720px) and (min-height: 521px) {
  /**
   * ⚠️ **แผงเป็นกริดสองคอลัมน์ แต่กระดานกับแถบเหรียญกินเต็มแถว**
   *    กระดานคือของที่ต้องแตะแม่น ๆ · บีบครึ่งเดียวแล้วปุ่มแคบลงครึ่งหนึ่ง
   *    ซึ่งขัดกับเหตุผลทั้งหมดที่หน้านี้มีอยู่
   */
  [data-layout="bento"] .live__panel {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: none;
    align-content: start;
  }

  [data-layout="bento"] .say,
  [data-layout="bento"] .board,
  [data-layout="bento"] .tray { grid-column: 1 / -1; }
}

/**
 * 🔴 **ขยับด้วย `transform` ไม่ใช่ `left` (แก้ 2026-08-31)**
 *    เหตุผลเดียวกับ `scanSweep` — `left` บังคับคำนวณผังใหม่ทุกเฟรม
 *
 * ⚠️ ตัวที่ใช้มัน (`.btn--gold::after`) มี `transform: skewX(-20deg)` อยู่แล้ว
 *    จึงต้องเขียน skew ซ้ำในทุกเฟรม ไม่งั้น animation จะทับ skew ทิ้ง
 *    แล้วแสงกวาดกลายเป็นแท่งตรงแทนที่จะเอียง
 */
@keyframes sweep {
  0%, 55% { transform: translate3d(0, 0, 0) skewX(-20deg); }
  100%    { transform: translate3d(456%, 0, 0) skewX(-20deg); }
}

/* ═══ ผังจอแต่ละขนาด ═══════════════════════════════════════ */

/* ── มือถือแนวตั้ง (ค่าเริ่มต้น) ─────────────────────────────
   ไลฟ์ติดบน · กระดานเต็มจอ · แถบเหรียญลอยล่าง                */
.live__stage { height: 34dvh; min-height: 190px; }

/* ── มือถือแนวนอน ────────────────────────────────────────────
   ⚠️ จอเตี้ยมาก — ไลฟ์เต็มจอเป็นพื้นหลัง
      กระดานลอยทับแบบโปร่ง ไม่งั้นไม่เหลือที่ให้อะไรเลย

   🔴 **ต้องมี `max-width` ด้วย ไม่ใช่เช็คแค่ความสูง**
      หน้าต่างบนคอมที่เตี้ยกว่า 520px ก็เข้าเงื่อนไขนี้ได้ แล้วจะได้
      `position: fixed` ของเวทีไลฟ์ไปทับผัง PC ซึ่งวางไลฟ์ไว้ในคอลัมน์ซ้าย
      → ไลฟ์ลอยเต็มจอคลุมแผงขวาทั้งแผง

   🔴 **สามช่วงจอต้องไม่ทับกันเอง** — ดูบล็อกแท็บเล็ตข้างล่าง
      มือถือแนวนอนสมัยนี้กว้าง 850–950px ซึ่ง "กว้างกว่าแท็บเล็ตขั้นต่ำ"
      ถ้าบล็อกแท็บเล็ตไม่กันความสูงไว้ มันจะมาทับบล็อกนี้ทั้งดุ้น       */
@media (orientation: landscape) and (max-height: 520px) and (max-width: 1279px) {
  .live { grid-template-rows: 1fr; }

  /**
   * 🔴 **จอเตี้ยซ่อนกล่องแชททั้งอัน — แถวของมันจึงต้องหายไปด้วย**
   *
   *    `display: none` ทำให้มันไม่เป็นลูกของกริดอีกต่อไป แต่แถวที่ประกาศไว้
   *    ยังอยู่ · ผลคือแผงเล่นไปกินแถวแรก (สูงเท่าเนื้อหา) แล้วเหลือ
   *    **ช่องว่างเปล่า 123px ที่ก้นจอ** ทั้งที่จอเตี้ยอยู่แล้ว
   *    และแผงที่ควรชิดล่างก็ไปติดอยู่บน
   *
   *    วัดด้วย headless ที่ 866×323 ถึงเห็น — อ่านจาก CSS อย่างเดียว
   *    มองไม่เห็นเลยเพราะกฎทั้งสองอันดู "ถูก" ทั้งคู่
   */
  .live:has(> [data-chat-mode="ticker"]) { grid-template-rows: 1fr; }

  .live__stage {
    position: fixed;
    inset: 0;
    height: 100dvh;
    z-index: 0;
  }

  .live__panel {
    position: relative;
    z-index: 2;
    grid-template-columns: 1fr;

    /**
     * ⭐ **ทุกแถวสูงตามเนื้อหา ไม่มีแถวไหนยืด**
     *
     * 🔴 ผังปกติให้กระดานเป็นแถว `1fr` — บนจอเตี้ยมันจึง**พองกินที่ที่เหลือ
     *    ทั้งหมด** แล้วบังไลฟ์ที่อยู่ข้างหลังจนมิด ทั้งที่คนหมุนจอเป็นแนวนอน
     *    ก็เพราะอยากดูไลฟ์ให้ใหญ่ขึ้น · ตรึงให้เท่าเนื้อหาแล้วดันไปชิดล่าง
     *    = ครึ่งบนเป็นไลฟ์จริง ๆ ครึ่งล่างเป็นของที่ต้องแตะ
     *
     * ⚠️ **`safe` ขาดไม่ได้** — จอที่เตี้ยกว่าเนื้อหา (เช่น iPhone SE
     *    แนวนอนในเบราว์เซอร์ของ LINE เหลือ ~287px) ถ้าจัดชิดล่างแบบไม่ `safe`
     *    ส่วนที่ล้นจะโผล่**ทางบน** ซึ่งเลื่อนตามไปดูไม่ได้เลย —
     *    แถบผู้เล่นกับเครดิตจะหายไปเฉย ๆ
     *    เขียนสองบรรทัดเพราะเบราว์เซอร์เก่าที่ไม่รู้จัก `safe` จะทิ้งบรรทัดหลัง
     *    แล้วยังได้ `end` ธรรมดาไปใช้
     *
     * ⚠️ `none` ไม่ใช่ `auto auto auto auto` — ประกาศแถวตายตัวไว้สี่แถว
     *    แล้วมีของจริงสามชิ้น (เช่นจอเตี้ยมากที่ซ่อนแถบยอดสองฝั่ง)
     *    จะเหลือแถวเปล่า **พร้อมช่องไฟของมัน** ติดมาด้วย
     */
    grid-template-rows: none;
    align-content: end;
    align-content: safe end;

    background: linear-gradient(0deg, rgba(7, 10, 16, .88), transparent);
  }

  .player, .road, .board, .tray {
    background: rgba(7, 10, 16, .62);
    backdrop-filter: blur(8px);
  }

  .road { display: none; }               /* พื้นที่ไม่พอ — เค้าไพ่ดูตอนตั้งจอได้ */
  .chat { display: none; }               /* แชทบังกระดานตอนจอเตี้ย */
  .say  { display: none; }               /* ช่องพิมพ์ก็ไม่มีที่ — ตั้งจอค่อยคุย */

  /**
   * 🔴 **กระดานต้องเรียงแถวเดียว 4 ประตู ไม่ใช่ 3 คอลัมน์ซ้อนกลาง**
   *
   *    ผังปกติเอา "คู่" กับ "เสมอ" ซ้อนกันในคอลัมน์กลาง = กระดาน**สูง
   *    เท่าสองประตู** ซึ่งกินความสูงที่ไม่มีให้กินตอนจอเตี้ย
   *    (วัดจริงบนจอสูง 330px: กระดานสูง 351px → แถบเหรียญตกใต้ขอบจอ
   *     ต้องเลื่อนหา ทั้งที่เป็นปุ่มที่ต้องแตะทุกไม้)
   *
   *    จอแนวนอนมี**ความกว้างเหลือเฟือ** (900px+) แต่ความสูงแทบไม่มี —
   *    เรียงสี่ประตูเป็นแถวเดียวคือใช้ของที่มีให้ตรงกับที่ขาด
   *
   * ⚠️ `display: contents` ทำให้ประตูกลางสองตัวขึ้นมาเป็นลูกของกระดานเอง
   *    ลำดับที่ได้คือ เสือ · คู่ · เสมอ · มังกร ซึ่งอ่านจากซ้ายไปขวาได้เลย
   */
  .board {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    min-height: 0;
    gap: 8px;
    padding: 8px;
  }

  .board__mid { display: contents; }

  /**
   * 🔴 **ขนาดในประตูต้องเลิกอิง `vw` ตอนจอแนวนอน**
   *
   *    `.gate__icon` ปกติกว้าง `clamp(34px, 9.5vw, 62px)` — บนมือถือ**แนวตั้ง**
   *    9.5vw ที่จอกว้าง 390px = 37px กำลังดี · พอหมุนเป็นแนวนอนจอกว้าง 900px
   *    ค่าเดียวกันพุ่งไปชนเพดาน **62px** ทั้งที่ความสูงที่มีให้ลดลงครึ่งหนึ่ง
   *    ชื่อประตูกับอัตราจ่ายก็อิง `vw` เหมือนกัน — ประตูเดียวจึงสูงถึง ~136px
   *
   *    นี่คือกับดักของหน่วย `vw`: แนวนอน "ด้านกว้าง" คือด้านยาว
   *    ทุกอย่างที่ผูกกับความกว้างจึงโตสวนทางกับที่ควรเป็น
   */
  .gate { min-height: 0; padding: 6px; gap: 1px; }
  .gate__icon { width: 30px; height: 30px; font-size: calc(24px * var(--fs, 1)); }
  .gate__name { font-size: calc(14px * var(--fs, 1)); }
  .gate__rate { font-size: calc(12px * var(--fs, 1)); }
  .gate__table { top: 4px; right: 6px; font-size: calc(10px * var(--fs, 1)); }

  /**
   * ⚠️ **กองชิปกินที่เงียบ ๆ 36px ในทุกประตู**
   *    (`min-height: 30px` + `margin-top: 6px`) — มองไม่เห็นตอนยังไม่วาง
   *    แต่จองที่ไว้ตลอด · บนจอเตี้ยนี่คือ 1 ใน 3 ของความสูงกระดาน
   */
  .gate__stack { margin-top: 3px; min-height: 22px; }
  .stack__coin { width: 22px; height: 22px; }
  .stack__sum  { font-size: calc(12px * var(--fs, 1)); }

  /* ⚠️ แถบบน/ล่างต้องเตี้ยด้วย ไม่งั้นกระดานที่หดแล้วก็ยังไม่พอที่ */
  .player { padding: 3px 8px; gap: 6px; }
  .player__avatar { width: 30px; height: 30px; }
  .vs { padding: 3px 10px; }

  .tray      { gap: 4px; padding: 5px 8px calc(5px + var(--safe-b)); }
  .tray__coins { padding: 0; gap: 6px; }
  .tray__ops { padding-top: 4px; }
  .coin      { width: 32px; height: 32px; font-size: calc(12px * var(--fs, 1)); }
  .chipbtn   { min-height: 30px; padding: 0 9px; font-size: calc(13px * var(--fs, 1)); }
  .btn--sm   { min-height: 30px; }

  .note { width: min(58%, 300px); padding: 10px 12px; }
  .note__img   { max-height: 22dvh; }
  .note__title { font-size: calc(18px * var(--fs, 1)); }
}

/* ── มือถือแนวนอน "จอเตี้ยมาก" ────────────────────────────────
   ⚠️ เครื่องเล็กในเบราว์เซอร์ของ LINE เหลือความสูงจริงน้อยกว่าที่คิดมาก
      (iPhone SE แนวนอน 375px หัก headerกับแถบเครื่องมือของ LINE
       เหลือ ~280px) · แค่นั้นไม่พอให้ทุกแถบอยู่ครบโดยไม่ต้องเลื่อน

   ⭐ ตัดแถบยอดสองฝั่งออกเป็นอันดับแรก — เป็น "ข้อมูลไว้ดู" ตัวเดียวที่เหลือ
      ส่วนกระดานกับแถบเหรียญคือของที่**ต้องแตะ** ตัดไม่ได้                */
@media (orientation: landscape) and (max-height: 320px) and (max-width: 1279px) {
  .vs { display: none; }
}

/* ── แท็บเล็ต / iPad ───────────────────────────────────────────
   🔴 **ต้อง "ทั้งกว้างและสูง" ไม่ใช่กว้างอย่างเดียว**

      มือถือแนวนอนสมัยนี้กว้าง 850–950px ซึ่งผ่าน `min-width: 768px`
      สบาย ๆ · บล็อกนี้อยู่**หลัง**บล็อกมือถือแนวนอนในไฟล์ จึงชนะ
      แล้วยัดขนาดของแท็บเล็ตกลับเข้าไป: กระดานสูง 205px · ประตูสูง 100px
      · เวทีไลฟ์ `height: 42dvh` ทับ `100dvh` ทั้งที่ยัง `position: fixed` อยู่

      ผลจริงบนมือถือแนวนอน: **ไลฟ์หายไปอยู่หลังแผง · ประตูสูงเกินจอ ·
      แถบเหรียญตกไปใต้เส้นขอบจอจนต้องเลื่อนหา**
      (ผู้ใช้แจ้ง: "UI UX ไม่รองรับมือถือแนวนอน")

   ⚠️ 521px = ต่อจาก `max-height: 520px` ของบล็อกมือถือแนวนอนพอดี
      ห้ามขยับตัวใดตัวหนึ่งโดยไม่ขยับอีกตัว ไม่งั้นเกิดช่องว่างที่ไม่มี
      บล็อกไหนคุม (จอเตี้ยกว้าง = ได้ผังมือถือแนวตั้ง ซึ่งล้นจอทันที)  */
@media (min-width: 768px) and (min-height: 521px) {
  .live__stage { height: 42dvh; }
  .live__panel { padding: 14px; gap: 12px; }
  .board       { grid-template-columns: 1fr 1fr 1fr; min-height: 205px; gap: 12px; }
  .gate        { min-height: 100px; }
  .gate__icon  { font-size: calc(40px * var(--fs, 1)); }
  .gate__name  { font-size: calc(20px * var(--fs, 1)); }
  .coin        { width: 52px; height: 52px; font-size: calc(15px * var(--fs, 1)); }
  .tray        { padding: 12px; }
  .chat        { width: min(40%, 320px); }
}

/* ── PC ──────────────────────────────────────────────────────
   ไลฟ์ซ้าย 2 ส่วน · กระดาน+กล่องผู้เล่นขวา 1 ส่วน            */
@media (min-width: 1280px) {
  /* 🔴 **`.live` ต้องมีลูกแค่ 2 ตัว: `.live__stage` กับ `.live__panel`**
     ลูกตัวที่สามจะถูกดันไปแถวที่ 2 ซึ่งอยู่นอก `height: 100dvh`
     แล้วโดน `overflow: hidden` ตัดหายทั้งก้อน
     เกิดขึ้นจริง: ย้ายช่องแชทออกมาเป็นลูกตัวที่สาม → **กระดานหายทั้งอัน**  */
  .live {
    grid-template-rows: 1fr;
    grid-template-columns: minmax(0, 2fr) minmax(400px, 1fr);
    height: 100dvh;
    overflow: hidden;
  }

  /**
   * 🔴 **นี่คือเคสที่หมายเหตุข้างบนเตือนไว้เป๊ะ ๆ — และมันเกิดขึ้นจริงอีกครั้ง**
   *
   *    รางข้อความวิ่งเป็นลูกตัวที่สาม → ถูกดันไปแถว 2 ซึ่งอยู่นอก
   *    `height: 100dvh` แล้วโดน `overflow: hidden` ตัด · ผลคือ**กระดานหาย
   *    ทั้งอัน**บนจอคอม เหมือนตอนที่ย้ายช่องแชทออกมาเมื่อก่อน
   *
   *    ทางแก้ไม่ใช่ "ย้ายรางกลับเข้าไปในเวที" (นั่นคือกลับไปทับไลฟ์)
   *    แต่คือ **ประกาศพื้นที่ให้มันตรง ๆ**: ไลฟ์กับรางอยู่คอลัมน์ซ้าย
   *    (รางใต้ไลฟ์พอดี) · แผงเล่นกินคอลัมน์ขวาเต็มสองแถว
   *
   * ⚠️ ต้องปลด `height: 100dvh` ของเวทีด้วย — ไม่งั้นเวทีสูงเต็มจอ
   *    แล้วดันรางตกขอบล่างไปโดนตัดอยู่ดี
   */
  .live:has(> [data-chat-mode="ticker"]) {
    grid-template-rows: 1fr auto;
    grid-template-columns: minmax(0, 2fr) minmax(400px, 1fr);
  }

  .live:has(> [data-chat-mode="ticker"]) .live__stage {
    grid-area: 1 / 1;
    height: auto;
  }

  .live:has(> [data-chat-mode="ticker"]) [data-chat-mode="ticker"] { grid-area: 2 / 1; }

  .live:has(> [data-chat-mode="ticker"]) .live__panel { grid-area: 1 / 2 / -1 / 3; }

  .live__stage { height: 100dvh; }

  .live__panel {
    overflow-y: auto;
    padding: 16px;
    gap: 14px;
    border-left: 1px solid var(--line);
    background: var(--bg-2);
  }

  .board      { min-height: 250px; gap: 14px; }
  .gate       { min-height: 122px; }
  .chat       { width: min(34%, 340px); }
  .bigdot     { width: 20px; height: 20px; }

  /**
   * ⚠️ แผงขวาเลื่อนเองได้ — เนื้อหายาวกว่าจอเมื่อเปิดเค้าไพ่
   *
   * 🔴 **ไม่ใส่ `overscroll-behavior: contain`** — มันสั่งห้ามส่งต่อการเลื่อน
   *    ให้หน้า · เลื่อนแผงจนสุดแล้วหน้าไม่ขยับต่อ ซึ่งผู้ใช้อ่านว่า "จอค้าง"
   *    (เจ้าของแจ้งเรื่องเลื่อนไม่ติด 2026-08-30) · ปล่อยให้ส่งต่อตามปกติ
   */
  .live__panel { overflow-y: auto; }

  /**
   * 🔴 **แถว `1fr` ของกระดานกินที่ของถาดเหรียญบนจอคอม**
   *    (เจ้าของแจ้ง 2026-09-23 "จอใหญ่ ตรงส่วนเหรียญ เลื่อนลงไม่ได้ ขาดเห็นครึ่งเดียว")
   *
   *    บนมือถือแผงไม่เลื่อน แถว `1fr` จึงแจกที่เหลือให้กระดานได้ถูกต้อง
   *    แต่บนคอมแผง**เลื่อนเองได้** และเนื้อหาสูงกว่าจอเสมอ — กระดานกินที่จนหมด
   *    แล้วแถวสุดท้าย (ถาดเหรียญ) ถูกบีบเหลือ 26px จาก 138px
   *    ซ้ำร้ายกว่านั้น: ขอบล่างของมันเลยพ้นระยะที่เลื่อนได้ไป 22px
   *    → เลื่อนจนสุดแล้วก็ยังเห็นถาดเหรียญไม่ครบอยู่ดี (วัดด้วย headless Edge)
   *
   * ⭐ **บนคอมให้ทุกแถวสูงตามเนื้อหาแล้วเลื่อนเอา** — ไม่มีแถวไหนถูกบีบ
   *    มือถือยังใช้ `1fr` เหมือนเดิม เพราะที่นั่นแผงไม่เลื่อนและกระดานต้องเต็มจอ
   */
  .live__panel {
    grid-template-rows: none;
    grid-auto-rows: max-content;
    align-content: start;
  }
}

/* ══════════════════════════════════════════════════════════════
   ⭐ ผังหน้าจอของโต๊ะ — รอบขยาย 6 → 20 แบบ (2026-08-29)

   🔴 **ผังเดิมเล็งไปที่คลาสของเกม 1 เท่านั้น** (`.gate` · `.road` · `.vs`)
      โต๊ะเกม 2/4 เลือกผังแล้วแทบไม่มีอะไรขยับ — เจ้าของทักมาเอง

      ผังรอบนี้จึงใช้เฉพาะ **hook ที่ทุกเกมมีเหมือนกัน**:
        `.live`  `.live__stage`  `.live__panel`  `.live__main`
        `.tray`  `.player`  `.chat`
      `.live__main` เป็นชื่อกลางที่เติมให้ `.board` (เกม 1) ·
      `.g2main` (เกม 2) · `.g4main` (เกม 4) แล้ว

   🔴 **ห้ามแตะ DOM และห้ามแตะ js** — ผังคือกฎ CSS ล้วน
      ย้ายของด้วย `order` กับ `grid-template-*` เท่านั้น
      ผังที่มีมาร์กอัปของตัวเอง = ทุกครั้งที่แก้หน้าเล่นต้องแก้ทุกผัง

   ⚠️ `classic` ไม่มีกฎของตัวเองแม้แต่บรรทัดเดียว — เป็นค่าพื้นฐาน
      ของโต๊ะที่เปิดขายอยู่ · มีกฎเมื่อไรแปลว่าหน้าตาเปลี่ยนโดยไม่มีใครสั่ง
   ══════════════════════════════════════════════════════════════ */

/* ── 1. กระดานขึ้นก่อน ไลฟ์ลงล่าง ─────────────────────────────
   ⚠️ ย้ายด้วย `order` ไม่ใช่สลับ DOM — ลำดับการอ่านของ screen reader
      ยังเป็นลำดับเดิม และ js ที่อ้าง `nth-child` ไม่พัง */
[data-layout="boardfirst"] .live { grid-template-rows: 1fr auto; }
[data-layout="boardfirst"] .live__stage { order: 2; max-height: 34dvh; }
[data-layout="boardfirst"] .live__panel { order: 1; }

/* ── 2. ไลฟ์ล่างสุด · แผงเต็มบน ───────────────────────────── */
[data-layout="stagebottom"] .live { grid-template-rows: 1fr auto; }
[data-layout="stagebottom"] .live__stage { order: 2; height: 26dvh; min-height: 150px; }
[data-layout="stagebottom"] .live__panel { order: 1; }

/* ── 3. ถาดเหรียญขึ้นบนสุดของแผง (โซนนิ้วโป้งบนมือถือใหญ่) ──
   ⚠️ ถาดอยู่บนแปลว่ามือไม่บังกระดานตอนเลือกเหรียญ */
[data-layout="traytop"] .live__panel { grid-template-rows: auto auto auto 1fr; }
[data-layout="traytop"] .tray { order: -1; }

/* ── 4. ผู้เล่นขึ้นบนสุด ─────────────────────────────────────── */
[data-layout="playertop"] .player { order: -2; }
[data-layout="playertop"] .chat-input { order: -1; }

/* ── 5. ไลฟ์ซ้าย กระดานขวา (จอกว้าง) ────────────────────────
   ⚠️ บนมือถือกลับไปเรียงบนล่างเหมือนเดิม — สองคอลัมน์บนจอ 390px
      คือคอลัมน์ละ 180px ซึ่งกดไม่โดนทั้งคู่ */
@media (min-width: 900px) {
  [data-layout="sidestage"] .live {
    grid-template-columns: 1.15fr 1fr;
    grid-template-rows: 1fr;
  }

  [data-layout="sidestage"] .live__stage { height: 100dvh; }
  [data-layout="sidestage"] .live__panel { overflow-y: auto; }
}

/* ── 6. กระดานซ้าย ไลฟ์ขวา (จอกว้าง) ───────────────────────── */
@media (min-width: 900px) {
  [data-layout="sideboard"] .live {
    grid-template-columns: 1fr 1.15fr;
    grid-template-rows: 1fr;
  }

  [data-layout="sideboard"] .live__stage { order: 2; height: 100dvh; }
  [data-layout="sideboard"] .live__panel { order: 1; overflow-y: auto; }
}

/* ── 7. ไลฟ์เป็นภาพซ้อนมุมขวาบน (picture-in-picture) ─────────
   🔴 ไลฟ์ลอยทับ = ต้องเล็กพอที่จะไม่บังกระดาน และต้องมีที่ว่างให้
      ปุ่มแถวบนของแผงเสมอ ไม่งั้นกดปุ่มแรกไม่ได้ทั้งแถว */
/**
 * ⚠️ **ที่เผื่อต้องผูกกับขนาดจริงของกล่อง ไม่ใช่ตัวเลขที่เดาไว้**
 *    เดิมเผื่อ `26vw` ตายตัวแต่กล่องสูงตามสัดส่วน 16:9 ของความกว้าง —
 *    พอความกว้างเปลี่ยน กล่องก็ทับแถวแรกของแผง (เห็นตอนเรนเดอร์ตรวจ)
 *    ตอนนี้ทั้งความกว้าง ความสูง และที่เผื่อ คิดจากตัวแปรเดียวกัน
 */
[data-layout="pip"] { --pip-w: min(40vw, 210px); }
[data-layout="pip"] .live { grid-template-rows: 1fr; }
[data-layout="pip"] .live__stage {
  position: fixed;
  top: calc(8px + var(--safe-t, 0px));
  right: 8px;
  width: var(--pip-w);
  max-width: none;
  height: calc(var(--pip-w) * 9 / 16);
  min-height: 0;
  z-index: 30;
  border-radius: var(--radius-sm);
  overflow: hidden;
  box-shadow: var(--shadow);
}
[data-layout="pip"] .live__panel { padding-top: calc(18px + var(--pip-w) * 9 / 16); }

/* ── 8. กระดานเต็มความกว้าง ไลฟ์เป็นแถบบาง ─────────────────── */
[data-layout="wide"] .live__stage { height: 20dvh; min-height: 116px; }
[data-layout="wide"] .live__panel { padding-left: 4px; padding-right: 4px; }
[data-layout="wide"] .live__main { margin-inline: -2px; }

/* ── 9. เรียบ เว้นเยอะ (zen) ────────────────────────────────── */
[data-layout="zen"] .live__panel { gap: 14px; padding: 14px 16px calc(14px + var(--safe-b)); }
[data-layout="zen"] .live__stage { height: 28dvh; }
[data-layout="zen"] .live__main,
[data-layout="zen"] .tray,
[data-layout="zen"] .player { box-shadow: none; }

/* ── 10. แน่น เห็นของเยอะ (dense) ───────────────────────────── */
[data-layout="dense"] .live__panel { gap: 4px; padding: 4px 6px calc(4px + var(--safe-b)); }
[data-layout="dense"] .live__stage { height: 22dvh; min-height: 120px; }
[data-layout="dense"] .tray { padding: 5px 6px calc(5px + var(--safe-b)); }
[data-layout="dense"] .live__main { font-size: .94em; }

/* ── 11. ปุ่มใหญ่ โซนนิ้วโป้ง (thumb) ────────────────────────
   ⚠️ ถาดเหรียญสูงขึ้นแลกกับไลฟ์ที่เล็กลง — โต๊ะที่คนแทงถี่ */
[data-layout="thumb"] .live__stage { height: 18dvh; min-height: 104px; }
[data-layout="thumb"] .tray { padding: 12px 10px calc(14px + var(--safe-b)); }
[data-layout="thumb"] .coin { transform: scale(1.14); }
[data-layout="thumb"] .tray__coins { gap: 10px; }

/* ── 12. ไลฟ์แนวตั้ง 9:16 (คลิปจากมือถือ) ───────────────────── */
/**
 * 🔴 **ห้ามบีบด้วย `aspect-ratio`** — iframe ของผู้ให้บริการวาดเนื้อหา
 *    ตามขนาดของมันเอง · บีบกล่องได้แค่ภาพที่ถูกครอบตัด ไม่ใช่ภาพที่ย่อลง
 *    ตัวที่ย่อจริงคือ `fitFrame()` ใน `live.js` (`transform: scale()`)
 *    ซึ่งทำงานเองอยู่แล้วเมื่อกรอบสูงขึ้น (`StreamEmbedTest` กันไว้)
 *
 * ผังนี้จึงแค่**ทำให้กรอบสูง** แล้วปล่อยให้ตัวย่อของเดิมจัดการ
 */
[data-layout="portrait"] .live__stage { height: 56dvh; min-height: 320px; }

/* ── 13. เวทีกลาง กระดานล้อมรอบ (arena) ─────────────────────── */
[data-layout="arena"] .live__stage { height: 24dvh; min-height: 140px; margin: 8px 10px 0; border-radius: var(--radius); overflow: hidden; }
[data-layout="arena"] .live__panel { padding-top: 8px; }
[data-layout="arena"] .live__main { border-radius: var(--radius); overflow: hidden; }

/* ── 14. การ์ดซ้อนชั้น (stack) ──────────────────────────────── */
[data-layout="stack"] .live__panel > * {
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
[data-layout="stack"] .live__panel { gap: 10px; }
[data-layout="stack"] .live__stage { height: 26dvh; margin: 8px 10px 0; border-radius: var(--radius); overflow: hidden; }

/* ── 15. ไม่มีไลฟ์ เหลือแต่โต๊ะ (boardonly) ──────────────────
   ⚠️ ไม่ได้ปิดไลฟ์ — แค่ย่อจนเป็นแถบสถานะ · โต๊ะที่ไม่ได้ถ่ายทอด
      จะได้ไม่เสียพื้นที่ครึ่งจอให้กล่องเปล่า */
[data-layout="boardonly"] .live__stage { height: 0; min-height: 0; overflow: hidden; }
[data-layout="boardonly"] .stage__bar { display: none; }
[data-layout="boardonly"] .live { grid-template-rows: 0 1fr; }

/* ── 16. แถบผลย้อนหลังใหญ่ (roadbig) ────────────────────────── */
[data-layout="roadbig"] .live__stage { height: 22dvh; min-height: 124px; }
[data-layout="roadbig"] [data-recent],
[data-layout="roadbig"] .road { min-height: 96px; }
[data-layout="roadbig"] .g2recent,
[data-layout="roadbig"] .g4recent { min-height: 84px; }

/* ── 17. แชทเด่น (chatty) ───────────────────────────────────── */
[data-layout="chatty"] .live__stage { height: 24dvh; min-height: 140px; }
[data-layout="chatty"] .chat { max-height: 44dvh; }
[data-layout="chatty"] .chat-input { order: -1; }

/* ── 18. ขอบมน เงาลึก (soft) ────────────────────────────────── */
[data-layout="soft"] .live__panel > * { border-radius: 22px; }
[data-layout="soft"] .live__stage { margin: 10px; border-radius: 22px; overflow: hidden; height: 28dvh; }
[data-layout="soft"] .live__panel { gap: 12px; }

/* ── 19. ขอบคม ไม่มีเงา (edge) ──────────────────────────────── */
[data-layout="edge"] .live__panel > *,
[data-layout="edge"] .live__main,
[data-layout="edge"] .tray { border-radius: 0; box-shadow: none; }
[data-layout="edge"] .live__panel { gap: 2px; padding: 2px; }
[data-layout="edge"] .live__stage { height: 26dvh; }

/* ── 20. จอกว้างแบ่งสามส่วน (studio) ─────────────────────────
   ⚠️ ใช้ได้เฉพาะจอกว้างจริง — บนมือถือกลับไปเรียงบนล่าง */
@media (min-width: 1100px) {
  [data-layout="studio"] .live {
    grid-template-columns: 1.4fr 1fr;
    grid-template-rows: 1fr;
  }

  [data-layout="studio"] .live__stage { height: 100dvh; }
  [data-layout="studio"] .live__panel { overflow-y: auto; gap: 12px; padding: 12px; }
  [data-layout="studio"] .live__main { border-radius: var(--radius); overflow: hidden; }
}

/* ── 21. อลังการ — กระดานใหญ่กลางจอในกรอบทอง (grand) ──────────
   🔴 **ขายขาดให้ร้านเดียว** — ดู `LiveLayout::EXCLUSIVE`

   ⚠️ ใช้เฉพาะ hook ที่ทุกเกมมีเหมือนกัน (`live__stage` · `live__panel`
      · `live__main` · `tray`) — เล็งคลาสของเกมใดเกมหนึ่งเมื่อไร
      โต๊ะเกมอื่นเลือกผังแล้วแทปไม่ขยับ (บทเรียนของผังชุด 6→26)

   ⚠️ กฎที่ขยับ **ขนาด/ตำแหน่ง** อยู่ใน `@media` ที่กันจอเตี้ยแนวนอนออก
      ส่วนกฎที่แต่ง **ผิว** อยู่นอก — จอเตี้ยจะได้ไม่เสียกรอบทองไปด้วย  */
/* 🔴 **ไม่มีกรอบทองล้อม `.live__main` แล้ว — ถอดออก 2026-09-06**

      เคยวาดเป็น `::before` ที่ `inset: 0` โดยเข้าใจว่า `.live__main`
      คือ "กระดานทั้งใบ" · **บนไฮโลไม่ใช่** — ส่วนโต๊ด 3 ตัวเป็นบล็อก
      พี่น้องที่อยู่นอก `.live__main` กรอบจึงไปจบกลางคันแล้วกลายเป็น
      เส้นขีดผ่ากลางกระดาน · แถมยังเลื่อนไปพร้อมเนื้อหาในกล่องที่เลื่อนได้
      (เจ้าของเจอบนของจริง — จอจริงอีกครั้ง ไม่ใช่จากการอ่าน CSS)

   ⚠️ **กรอบทองไม่ได้หายไปไหน** — สไตล์ `imperial` ให้ขอบทองนูนกับ
      `.board` `.tray` `.road` `.player` ทุกชิ้นอยู่แล้ว ซึ่งเป็นขอบที่
      โตตามเนื้อหาจริงของแต่ละกล่อง · กรอบใบที่สองจึงซ้ำซ้อนมาแต่แรก

   ⚠️ **บทเรียน: ห้ามวาดกรอบครอบ `.live__main` ด้วย absolute overlay**
      ความสูงของมันไม่เท่ากับสิ่งที่ผู้เล่นมองว่าเป็น "กระดาน" ในทุกเกม  */

@media (orientation: portrait), (min-height: 521px) {
  [data-layout="grand"] .live__stage {
    height: 26dvh;
    min-height: 150px;
    margin: 8px 10px 0;
    border-radius: var(--radius);
    overflow: hidden;
  }

  [data-layout="grand"] .live__panel { padding: 10px; gap: 10px; }

  /* 🔴 **ห้าม `overflow: hidden` ที่ `.live__main` เด็ดขาด**
        กระดานไฮโลมีตารางโต๊ด/เป่า **ยาวกว่ากรอบ** — ซ่อนส่วนเกิน
        = แถวล่างหายไปเฉย ๆ และเลื่อนลงไปดูไม่ได้เลย
        (เจ้าของเจอบนของจริง 2026-09-06: "โต๊ด 3 ตัว หาย เลื่อนไม่ได้")

     ⚠️ กรอบทองไม่ได้พึ่ง `overflow` อยู่แล้ว — มันวาดด้วย `::before`
        ที่ลอยทับและ `pointer-events: none` · มุมมนจึงเก็บไว้ได้โดยไม่ต้องตัดอะไร

     ⚠️ **กับดักเดียวกับที่ `.live__panel` เจอมาแล้ว** (ดูหมายเหตุ `clip` ไม่ใช่
        `hidden` ที่นิยามของแผง) — `hidden` บังคับอีกแกนให้เป็น `auto`
        แล้วกลายเป็นตัวเลื่อนซ้อนที่เลื่อนไม่ติด · ผังใหม่ไปทำซ้ำที่ชั้นถัดไป
        **ผังไหนจะใส่ `overflow` ที่ `.live__main` ต้องถามก่อนว่ากระดานเกมที่
        ยาวที่สุด (ไฮโล) ยังเลื่อนได้อยู่ไหม** — `arena` กับ `studio` ยังมีกฎนี้อยู่ */
  [data-layout="grand"] .live__main { border-radius: var(--radius); }
  [data-layout="grand"] .tray { border-radius: var(--radius); }
}

/**
 * 🔴 **ทุกที่ที่ประกาศแถวของ `.live` ต้องเผื่อรางข้อความวิ่ง**
 *    รางเป็นลูกคนที่ 3 ของกริด — ไม่เผื่อแถวไว้ `1fr` ที่ตั้งใจ
 *    ให้แผงจะตกไปเป็นของรางสูง 34px แล้วกระดานถูกบีบ
 *    (`ChatModesTest` ไล่ให้ทุกจุด)
 */
[data-layout="boardfirst"] .live:has(> [data-chat-mode="ticker"]) { grid-template-rows: 1fr auto auto; }
[data-layout="stagebottom"] .live:has(> [data-chat-mode="ticker"]) { grid-template-rows: 1fr auto auto; }
[data-layout="pip"] .live:has(> [data-chat-mode="ticker"]) { grid-template-rows: 1fr auto; }
[data-layout="boardonly"] .live:has(> [data-chat-mode="ticker"]) { grid-template-rows: 0 1fr auto; }


/* ══════════════════════════════════════════════════════════════
   ⭐ **สไตล์พื้นผิวชุดที่เพิ่ม 2026-08-29** (19 แบบ · รวมเป็น 24)

   🔴 **กฎเดียวสำหรับทุกแบบใหม่ ไม่ใช่บล็อกละแบบ**
      สี่แบบเดิม (`neon` `glass` `flat` `luxe`) มีบล็อกของตัวเองอยู่ข้างบน
      และ **ห้ามแตะ** — ของเดิมต้องเหมือนเดิมเป๊ะ · ของใหม่ทั้งหมดวาดจาก
      โทเคนชุดเดียวกัน (`--surface` `--edge` `--glow`) ที่ `LiveTheme`
      ส่งมาให้อยู่แล้ว จึงไม่ต้องมีกฎเฉพาะตัวนอกจากตัวที่ต้องการของพิเศษ

   ⚠️ **ไม่แตะ `.gate`** — สีปุ่มประตูคือสีที่ผู้เล่นใช้อ่านกระดาน
      และรูปที่ลูกค้าอัปเองอยู่ใน `background-image` ของปุ่มนั้น
      (กติกาเดียวกับที่เขียนไว้ที่บล็อกของสี่แบบเดิม)
   ══════════════════════════════════════════════════════════════ */

[data-style="paper"] .player,
[data-style="paper"] .vs,
[data-style="paper"] .road,
[data-style="paper"] .board,
[data-style="paper"] .tray,
[data-style="carbon"] .player,
[data-style="carbon"] .vs,
[data-style="carbon"] .road,
[data-style="carbon"] .board,
[data-style="carbon"] .tray,
[data-style="wood"] .player,
[data-style="wood"] .vs,
[data-style="wood"] .road,
[data-style="wood"] .board,
[data-style="wood"] .tray,
[data-style="steel"] .player,
[data-style="steel"] .vs,
[data-style="steel"] .road,
[data-style="steel"] .board,
[data-style="steel"] .tray,
[data-style="velvet"] .player,
[data-style="velvet"] .vs,
[data-style="velvet"] .road,
[data-style="velvet"] .board,
[data-style="velvet"] .tray,
[data-style="ring"] .player,
[data-style="ring"] .vs,
[data-style="ring"] .road,
[data-style="ring"] .board,
[data-style="ring"] .tray,
[data-style="pillow"] .player,
[data-style="pillow"] .vs,
[data-style="pillow"] .road,
[data-style="pillow"] .board,
[data-style="pillow"] .tray,
[data-style="blade"] .player,
[data-style="blade"] .vs,
[data-style="blade"] .road,
[data-style="blade"] .board,
[data-style="blade"] .tray,
[data-style="pixel"] .player,
[data-style="pixel"] .vs,
[data-style="pixel"] .road,
[data-style="pixel"] .board,
[data-style="pixel"] .tray,
[data-style="emboss"] .player,
[data-style="emboss"] .vs,
[data-style="emboss"] .road,
[data-style="emboss"] .board,
[data-style="emboss"] .tray,
[data-style="deboss"] .player,
[data-style="deboss"] .vs,
[data-style="deboss"] .road,
[data-style="deboss"] .board,
[data-style="deboss"] .tray,
[data-style="frost"] .player,
[data-style="frost"] .vs,
[data-style="frost"] .road,
[data-style="frost"] .board,
[data-style="frost"] .tray,
[data-style="holo"] .player,
[data-style="holo"] .vs,
[data-style="holo"] .road,
[data-style="holo"] .board,
[data-style="holo"] .tray,
[data-style="grid"] .player,
[data-style="grid"] .vs,
[data-style="grid"] .road,
[data-style="grid"] .board,
[data-style="grid"] .tray,
[data-style="dots"] .player,
[data-style="dots"] .vs,
[data-style="dots"] .road,
[data-style="dots"] .board,
[data-style="dots"] .tray,
[data-style="stripe"] .player,
[data-style="stripe"] .vs,
[data-style="stripe"] .road,
[data-style="stripe"] .board,
[data-style="stripe"] .tray,
[data-style="tape"] .player,
[data-style="tape"] .vs,
[data-style="tape"] .road,
[data-style="tape"] .board,
[data-style="tape"] .tray,
[data-style="aurora"] .player,
[data-style="aurora"] .vs,
[data-style="aurora"] .road,
[data-style="aurora"] .board,
[data-style="aurora"] .tray,
[data-style="ink"] .player,
[data-style="ink"] .vs,
[data-style="ink"] .road,
[data-style="ink"] .board,
[data-style="ink"] .tray {
  background: var(--surface);
  border-color: var(--edge);
  box-shadow: var(--glow);
}

/* ⚠️ น้ำแข็งต้องมี blur คู่กับพื้นโปร่ง ไม่งั้นได้แค่ "จาง" (เหมือน `glass`) */
[data-style="frost"] .player,
[data-style="frost"] .vs,
[data-style="frost"] .road,
[data-style="frost"] .board,
[data-style="frost"] .tray,
[data-style="frost"] .chat,
[data-style="frost"] .say {
  background: var(--surface);
  border-color: var(--edge);
  -webkit-backdrop-filter: blur(var(--panel-blur, 0px));
  backdrop-filter: blur(var(--panel-blur, 0px));
}

/* ⚠️ จุดไข่ปลาต้องมี `background-size` ไม่งั้นได้จุดใหญ่จุดเดียวเต็มแผง
      (ชั้นแรกคือลายจุด ชั้นหลังคือสีพื้น จึงต้องบอกขนาดสองค่า) */
[data-style="dots"] .player,
[data-style="dots"] .vs,
[data-style="dots"] .road,
[data-style="dots"] .board,
[data-style="dots"] .tray {
  background-size: 10px 10px, auto;
}

/* ⚠️ เงาแข็งของ พิกเซล/เทปกาว อยู่ที่ `--shadow` ซึ่งแผงพวกนี้ไม่ได้ใช้
      จึงต้องบอกซ้ำตรงนี้ ไม่งั้นสองแบบนี้จะดูเหมือน `blade` เฉย ๆ */
[data-style="pixel"] .board,
[data-style="pixel"] .tray,
[data-style="tape"] .board,
[data-style="tape"] .tray {
  box-shadow: var(--shadow);
}


/* ══════════════════════════════════════════════════════════════
   ⭐ ช่องตารางที่วาดด้วยป้ายสั้น (เกมที่ไม่มีรูปผลให้อัป)

   🔴 เกม 4 ผลเป็น `S651` ซึ่งมี 216 แบบ — ทำคลาสสีให้ทีละรหัสไม่ได้
      จึงทาจาก **โทน** ที่ `GameBoard::resultBadge()` บอกมาแทน
      (ชุดเดียวกับที่แถบผลย้อนหลังใช้ จึงไม่มีทางบอกคนละสี)
   ══════════════════════════════════════════════════════════════ */

.g6--tone {
  display: grid;
  place-items: center;
  font-size: calc(12px * var(--fs, 1));
  font-weight: 800;
  color: #fff;
  font-variant-numeric: tabular-nums;
}

.g6--tone.g6--tiger  { background: var(--tiger, #3b6fe0); }
.g6--tone.g6--dragon { background: var(--dragon, #ef4444); }
.g6--tone.g6--tie    { background: var(--tie, #22c55e); }

/** ⚠️ โทนที่ยังไม่มีสีเฉพาะ = เทากลาง ไม่ใช่โปร่งใสจนอ่านไม่ออก */
.g6--tone.g6--muted,
.g6--tone.g6--gold   { background: color-mix(in srgb, var(--ink) 30%, transparent); }


/* ══════════════════════════════════════════════════════════════
   ⭐ **แถบปุ่มข้อมูล + ทิปเครดิตไม่พอ** (2026-08-29)

   🔴 แถบอยู่ใต้กล่องเครดิต เหนือผลย้อนหลัง — จุดที่ตาผ่านอยู่แล้ว
      และไม่ต้องเลื่อนผ่านปุ่มแทงไปหา
   ⚠️ ทั้งสองอย่างนี้ **ห้ามบังปุ่มแทง** — ทิปเป็น pointer-events: none
      ที่ตัวกล่อง เปิดเฉพาะปุ่มข้างใน
   ══════════════════════════════════════════════════════════════ */

/** ⚠️ ใช้ flex ไม่ใช่ `grid-auto-flow` — `RecentGridTest` ห้ามคำนั้นในไฟล์นี้
    เพราะเคยมีคนเอาไปจัดคอลัมน์ตารางผลใหม่เอง จนเว็บกับไลน์วางเลขคนละทาง */
.infobar {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}

.infobar__b { flex: 1 1 0; min-width: 0; }

.infobar[hidden] { display: none; }

.infobar__b {
  display: grid;
  justify-items: center;
  gap: 3px;
  padding: 7px 4px;
  border: 1px solid var(--edge, var(--line));
  border-radius: var(--radius-sm, 10px);
  background: var(--surface, var(--panel));
  color: var(--ink-dim);
  font: inherit;
  font-size: calc(0.75rem * var(--fs, 1));
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: transform .15s, border-color .15s, color .15s;
}

.infobar__b[hidden] { display: none; }

.infobar__b:hover,
.infobar__b:focus-visible {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  color: var(--ink);
}

.infobar__b:active { transform: translateY(0) scale(.97); }

.infobar__ico {
  font-size: calc(1.26rem * var(--fs, 1));
  line-height: 1;
  display: block;
}

/* ── ไอคอนขยับ — คนละจังหวะกัน ไม่ให้เต้นพร้อมกันจนรก ───────────
   ⚠️ ช้าและเบา · หน้านี้เปิดค้างทั้งวัน แอนิเมชันแรง ๆ กวนสายตา
   ⚠️ หยุดทั้งหมดเมื่อผู้ใช้ขอลดการเคลื่อนไหว (บล็อกท้ายไฟล์) */

.ico-book  { animation: infoBook 4.5s ease-in-out infinite; }
.ico-coin  { animation: infoCoin 5s ease-in-out infinite; }
.ico-gift  { animation: infoGift 3.6s ease-in-out infinite; }
.ico-call  { animation: infoCall 4.2s ease-in-out infinite; }

@keyframes infoBook {
  0%, 84%, 100% { transform: none; }
  88% { transform: rotate(-7deg); }
  94% { transform: rotate(6deg); }
}

@keyframes infoCoin {
  0%, 80%, 100% { transform: none; }
  86% { transform: rotateY(180deg); }
  92% { transform: rotateY(360deg); }
}

@keyframes infoGift {
  0%, 78%, 100% { transform: none; }
  84% { transform: translateY(-3px) scale(1.08); }
  90% { transform: translateY(0) scale(.96); }
}

@keyframes infoCall {
  0%, 82%, 100% { transform: none; }
  86% { transform: translateX(-2px); }
  90% { transform: translateX(2px); }
  94% { transform: translateX(-1px); }
}

/* ── โลโก้บนปุ่มติดต่อพนักงาน — LINE · Facebook · Telegram (2026-10-02) ──
   ⭐ ร้านเลือกที่ `weblive_contact_icon` · มาร์กอัปมาจาก `ContactIcon` ที่เดียว
      ใช้ทั้งแถบข้อมูล · เมนูลอย · หน้าเข้าระบบ
   🔴 `default` ไม่มีคลาส `cico` เลย — ไอคอนเดิมของแต่ละที่ไม่โดนกฎพวกนี้แตะ
   ⚠️ ขนาดตาม `font-size` ของคลาสที่ห่ออยู่ (`infobar__ico` · `fab__ico`)
      โลโก้จึงตัวเท่าไอคอนข้างเคียงในแถวเดียวกันเสมอ
   ⚠️ เคลื่อนไหว 2 ชั้น — ตัวโลโก้เด้งเอียงเป็นจังหวะ + วงแสงสีประจำยี่ห้อ
      กระเพื่อมออก · ช้าและเบาเหมือนไอคอนอื่นในแถว (หน้านี้เปิดค้างทั้งวัน) */

.cico--line     { --cico: #06C755; }
.cico--facebook { --cico: #1877F2; }
.cico--telegram { --cico: #229ED9; }

.cico {
  position: relative;
  border-radius: 50%;
  line-height: 0;
  animation: cicoHop 3.8s ease-in-out infinite;
}

.cico--line { border-radius: 28%; }

.cico__svg {
  display: block;
  width: 1em;
  height: 1em;
}

.cico::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  animation: cicoRing 2.4s ease-out infinite;
}

.fab__ico.cico {
  display: inline-block;
  vertical-align: middle;
  margin-right: 2px;
}

.entry__contact-brand {
  flex: none;
  font-size: calc(17px * var(--fs, 1));
}

@keyframes cicoHop {
  0%, 76%, 100% { transform: none; }
  82% { transform: translateY(-3px) rotate(-8deg) scale(1.1); }
  88% { transform: translateY(0) rotate(6deg) scale(.97); }
  94% { transform: rotate(-3deg); }
}

@keyframes cicoRing {
  0%        { box-shadow: 0 0 0 0 color-mix(in srgb, var(--cico) 60%, transparent); }
  70%, 100% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--cico) 0%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .cico, .cico::after { animation: none; }
}

/* ── กล่องรูปในป๊อปอัป ─────────────────────────────────────────── */

.imodal__h { margin: 0 0 .6rem; font-size: calc(1.1rem * var(--fs, 1)); }

.itabs { display: flex; gap: 6px; margin-bottom: .6rem; }

.itab {
  flex: 1;
  padding: .45rem .3rem;
  border: 1px solid var(--edge, var(--line));
  border-radius: var(--radius-sm, 10px);
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-weight: 700;
  font-size: calc(0.88rem * var(--fs, 1));
  cursor: pointer;
}

.itab.is-on {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  color: var(--ink);
}

.ishot { display: grid; place-items: center; min-height: 60px; }

.ishot img {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-sm, 10px);
  animation: infoFade .25s ease both;
}

.ishot__err { margin: 0; color: var(--ink-faint); font-size: calc(0.9rem * var(--fs, 1)); text-align: center; }
.ishot__err[hidden] { display: none; }

@keyframes infoFade { from { opacity: 0; transform: scale(.98); } }

/* ══════════════════════════════════════════════════════════════
   ⭐ **ทิปเครดิตไม่พอ — เด้งเหนือเหรียญที่เลือกอยู่**

   🔴 เครดิตไม่พอเป็นเหตุผลที่ผู้เล่นแก้ได้ทันที ต่างจากลิมิตหรือรอบปิด
      กล่องเต็มจอบังกระดานแล้วต้องกดปิดก่อนถึงจะลดเหรียญได้
      ทั้งที่สิ่งที่เขาต้องทำอยู่ห่างไปสองนิ้ว
   ══════════════════════════════════════════════════════════════ */

.ctip {
  position: fixed;
  z-index: 70;
  transform: translate(-50%, -100%);
  margin-top: -10px;
  display: flex;
  align-items: center;
  gap: 7px;
  max-width: min(92vw, 340px);
  padding: 8px 10px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, #ef4444 45%, var(--line));
  background: var(--panel);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .45);
  color: var(--ink);
  font-size: calc(0.86rem * var(--fs, 1));
  font-weight: 700;
  white-space: nowrap;

  /** 🔴 ห้ามบังปุ่มแทง — เปิดคลิกเฉพาะปุ่มข้างใน */
  pointer-events: none;
  animation: ctipIn .28s cubic-bezier(.2, 1.5, .4, 1) both;
}

/** ⚠️ ลูกศรชี้ลงไปที่เหรียญ — ไม่งั้นไม่รู้ว่าทิปพูดถึงอันไหน */
.ctip::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -7px;
  width: 12px;
  height: 12px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--panel);
  border-right: 1px solid color-mix(in srgb, #ef4444 45%, var(--line));
  border-bottom: 1px solid color-mix(in srgb, #ef4444 45%, var(--line));
}

.ctip__ico { font-size: calc(1.21rem * var(--fs, 1)); animation: ctipShake .6s ease-in-out 2; }

.ctip__msg { color: #fca5a5; }

.ctip__now { color: var(--ink-faint); font-weight: 600; }

.ctip__go,
.ctip__x {
  pointer-events: auto;
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-weight: 800;
  cursor: pointer;
}

.ctip__go {
  padding: .3rem .65rem;
  background: var(--gold, #fbbf24);
  color: #3b2200;
  font-size: calc(0.84rem * var(--fs, 1));
  animation: ctipPulse 1.6s ease-in-out infinite;
}

.ctip__x {
  padding: .2rem .4rem;
  background: transparent;
  color: var(--ink-faint);
  font-size: calc(0.88rem * var(--fs, 1));
}

@keyframes ctipIn {
  from { opacity: 0; transform: translate(-50%, -70%) scale(.9); }
}

@keyframes ctipShake {
  0%, 100% { transform: none; }
  25% { transform: rotate(-12deg); }
  75% { transform: rotate(12deg); }
}

@keyframes ctipPulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--gold, #fbbf24) 60%, transparent); }
  60% { box-shadow: 0 0 0 7px transparent; }
}

/* ⚠️ ผู้ที่ขอลดการเคลื่อนไหวต้องได้ของที่ยังใช้งานได้ครบ แค่ไม่ขยับ */
@media (prefers-reduced-motion: reduce) {
  .ico-book, .ico-coin, .ico-gift, .ico-call,
  .ctip, .ctip__ico, .ctip__go, .ishot img {
    animation: none !important;
  }

  .infobar__b { transition: none; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **เหรียญของผู้เล่นคนอื่น** (2026-08-29)

   🔴 **จางกว่าเหรียญของตัวเองเสมอ** — ผู้เล่นต้องแยกออกในครึ่งวินาที
      ว่าอันไหนเงินตัวเอง · เหรียญคนอื่นที่เด่นเท่ากันคือสาเหตุที่คนกดซ้ำ
      เพราะคิดว่าตัวเองยังไม่ได้ลง

   ⚠️ **ห้ามกินคลิก** — ทับอยู่บนปุ่มแทง · `pointer-events: none` ทั้งแถว
   ══════════════════════════════════════════════════════════════ */

/**
 * 🔴 **อยู่ในลำดับปกติ ไม่ลอยทับอะไรทั้งนั้น** (แก้รอบสอง 2026-08-29)
 *
 *    รอบแรกวางลอยกลางล่าง — ทับทั้งรูปลูกเต๋าและกองเหรียญของตัวเอง
 *    รอบสองย้ายไปมุมซ้ายบน — **ยังทับรูปลูกเต๋าอยู่ 55px²** และบนช่องแคบ
 *    ของไฮโล (กว้าง 48px) `max-width` ยังไปบีบแถวจนเหลือ 6px รูปหายหมด
 *    (วัดด้วยกล่องจริงจาก Edge headless ไม่ใช่ดูด้วยตา)
 *
 *    เจ้าของบอกว่า "มองไม่เห็นเลย โดนบัง" — ที่ถูกคือ **ไม่ทับเลย**
 *    ไม่ใช่หาที่ทับที่เจ็บน้อยที่สุด · อยู่ในลำดับปกติแล้วช่องสูงขึ้นนิดเดียว
 *    แต่รับประกันว่าไม่มีวันบังอะไรได้อีก ไม่ว่าเกมไหนหรือช่องแคบแค่ไหน
 *
 * ⚠️ ไม่มีใครลง = ไม่มีลูกในกล่อง = ไม่กินที่เลย (`:empty`)
 */
.crowd {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 2px;
  pointer-events: none;
}

/** 🔴 ว่างต้องไม่กินที่ — ไม่งั้นทุกช่องสูงขึ้นทั้งกระดานเพราะกล่องเปล่า */
.crowd:empty { display: none; }

/** ⚠️ ปุ่มประตูต้องเป็นจุดอ้างอิงของแถวนี้ ไม่งั้นมันไปเกาะทั้งกระดาน */
[data-gate] { position: relative; }

/** ⭐ ผู้เล่นปิดเอง = ไม่วาดเลย · กฎนี้กันของค้างระหว่างรอชีพจรถัดไป */
.crowd-off .crowd { display: none; }

/** ⚠️ ซ้อนเหลื่อมกัน — สามคนจึงกินที่เท่าคนครึ่ง บนช่องแคบ ๆ ของไฮโล */
.crowd__c {
  display: grid;
  place-items: center;
  margin-left: -5px;
  animation: crowdIn .3s cubic-bezier(.2, 1.5, .4, 1) both;
  animation-delay: calc(var(--ci, 0) * .06s);
}

.crowd__c:first-child { margin-left: 0; }

.crowd__pic {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  object-fit: cover;

  /** 🔴 จางกว่าของตัวเอง — นี่คือกฎหลักของทั้งบล็อกนี้ */
  opacity: .5;
  border: 1px solid rgba(0, 0, 0, .45);
}

.crowd__pic--none {
  display: grid;
  place-items: center;
  font-size: calc(10px * var(--fs, 1));
  line-height: 1;
  color: rgba(255, 255, 255, .55);
  background: rgba(255, 255, 255, .14);
}

.crowd__more {
  margin-left: 2px;
  padding: 0 3px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .45);
  color: rgba(255, 255, 255, .55);
  font-size: calc(9px * var(--fs, 1));
  font-weight: 800;
  line-height: 14px;
}

@keyframes crowdIn {
  from { opacity: 0; transform: translateY(6px) scale(.6); }
}

/* ── สวิตช์ในถาดเหรียญ ────────────────────────────────────────
   ⚠️ อยู่ตรงถาด เพราะคนที่รำคาญเหรียญคนอื่นคือคนที่กำลังจะกดแทง */

.crowdsw { position: relative; }
.crowdsw[hidden] { display: none; }

.crowdsw__ico { transition: opacity .15s; }

.crowdsw.is-off .crowdsw__ico { opacity: .35; }

/** ⭐ จุดบอกสถานะ — เขียว = โชว์อยู่ · เทา = ปิดอยู่ */
.crowdsw__dot {
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #22c55e;
  box-shadow: 0 0 0 2px var(--panel);
  animation: crowdPulse 2.4s ease-in-out infinite;
}

.crowdsw.is-off .crowdsw__dot {
  background: var(--ink-faint);
  animation: none;
}

@keyframes crowdPulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  55% { transform: scale(1.35); opacity: .65; }
}

@media (prefers-reduced-motion: reduce) {
  .crowd__c,
  .crowdsw__dot { animation: none !important; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **โทนสว่าง — ชั้นแก้รวม** (2026-08-29)

   🔴 **ปัญหาที่แก้**: ธีมสว่างเปลี่ยนแค่ตัวแปรสี แต่ของประดับทั้งหน้า
      เขียนด้วย `rgba(0,0,0,…)` และ `rgba(255,255,255,…)` ซึ่งตั้งอยู่บน
      สมมติฐานว่าพื้นหลังมืด · ผลคือ
        · คราบดำจาง ๆ ที่ไม่มีความหมายกระจายทั้งหน้า
        · เส้นขอบสีขาวจาง ๆ **หายไปสนิท** บนพื้นขาว → "ไม่มีกรอบ ไม่มีขอบ"
        · ผ้ากระดานของเกม 4 เป็นสีเขียวเข้มตายตัว → กระดานมืดบนหน้าขาว
      (เจ้าของทัก: "ดูโล้น ๆ ไม่เป็นสัดส่วน ไม่มีกรอบ ... โคตรไม่สวย")

   ⚠️ **แก้ที่ชั้นนี้ชั้นเดียว ไม่ไล่แก้ทีละกฎ** — กฎเดิมมี 60+ จุด
      ไล่แก้ทีละจุดคือลืมจุดใดจุดหนึ่งแน่นอน และวันหลังเพิ่มกฎใหม่ก็ลืมอีก
      ที่นี่ทำงานด้วย `[data-tone="light"]` ซึ่งเซิร์ฟเวอร์คิดมาให้แล้ว

   ⚠️ **ไม่แตะสีประจำประตู** (เสือน้ำเงิน · มังกรแดง · ต่ำแดง · สูงน้ำเงิน)
      พวกนั้นเป็นข้อมูล ไม่ใช่ของแต่ง — เปลี่ยนตามธีมคือทำให้อ่านกระดานผิด
   ══════════════════════════════════════════════════════════════ */

[data-tone="light"] {
  /**
   * 🔴 **เงาต้องเบามาก** — เงาดำหนัก ๆ บนพื้นขาวอ่านได้ว่า "ภาพเบลอ"
   *    ไม่ใช่ "กล่องลอย" · ความลึกบนพื้นสว่างมาจาก **เส้นขอบ** เป็นหลัก
   */
  --lift-1: 0 1px 2px rgba(15, 23, 42, .06);
  --lift-2: 0 4px 12px rgba(15, 23, 42, .10);

  /** ⭐ เส้นขอบที่เห็นจริงบนพื้นขาว — ตัวหลักที่ทำให้ "เป็นสัดส่วน" */
  --hair: color-mix(in srgb, var(--ink) 14%, transparent);
  --hair-strong: color-mix(in srgb, var(--ink) 24%, transparent);

  /** ⚠️ ชั้นทับต้องเป็นสีหมึกจาง ไม่ใช่สีดำ — ดำบนพื้นสีทำให้สีขุ่น */
  --tint-1: color-mix(in srgb, var(--ink) 4%, transparent);
  --tint-2: color-mix(in srgb, var(--ink) 8%, transparent);
}

/* ── 1. กรอบและสัดส่วน — สิ่งที่หายไปทั้งหมดบนโทนสว่าง ─────────── */

[data-tone="light"] .player,
[data-tone="light"] .vs,
[data-tone="light"] .road,
[data-tone="light"] .board,
[data-tone="light"] .tray,
[data-tone="light"] .chat,
[data-tone="light"] .say,
[data-tone="light"] .stats,
[data-tone="light"] .infobar__b {
  border: 1px solid var(--hair);
  box-shadow: var(--lift-1);
}

/** ⭐ กระดานเป็นพระเอกของหน้า — ขอบเข้มกว่าและยกสูงกว่าคนอื่น */
[data-tone="light"] .board {
  border-color: var(--hair-strong);
  box-shadow: var(--lift-2);
}

/* ── 2. ช่องแทง — ต้องเห็นเส้นแบ่งชัดว่าช่องไหนถึงไหน ─────────── */

[data-tone="light"] .g4cell,
[data-tone="light"] .g2cell {
  box-shadow: inset 0 0 0 1px var(--hair);
}

[data-tone="light"] .g4cell:hover,
[data-tone="light"] .g2cell:hover,
[data-tone="light"] .gate:hover {
  box-shadow: inset 0 0 0 1px var(--hair-strong);
}

/* ── 3. ผ้ากระดานเกม 4 — เขียวเข้มตายตัว → ผ้าสีอ่อนตามธีม ────── */

/**
 * 🔴 นี่คือข้อที่เจ้าของเห็นชัดที่สุด — "กระดานเล่นหลักพื้นหลังยังเป็นโทนมืด"
 *    ผ้าจริงของไฮโลเป็นสีเขียว แต่บนธีมสว่างต้องเป็น **เขียวอ่อน**
 *    ไม่ใช่เขียวเข้มเท่าเดิม ไม่งั้นได้กระดานมืดวางอยู่บนหน้าขาว
 */
[data-tone="light"] {
  --g4-felt-1: color-mix(in srgb, var(--panel) 88%, #2f7d5b);
  --g4-felt-2: color-mix(in srgb, var(--panel) 78%, #2f7d5b);
}

[data-tone="light"] .g4box {
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, .5), transparent 62%),
    linear-gradient(180deg, var(--g4-felt-1), var(--g4-felt-2));
  box-shadow: inset 0 0 0 1px var(--hair-strong);
}

/** ⚠️ เงาใต้ลูกเต๋าต้องเบาลงด้วย ไม่งั้นเต๋าดูสกปรกบนผ้าอ่อน */
[data-tone="light"] .g4die { filter: drop-shadow(0 2px 3px rgba(15, 23, 42, .28)); }

/* ── 4. คราบดำ — เปลี่ยนเป็นหมึกจาง ๆ ที่อ่านออกบนพื้นสว่าง ───── */

[data-tone="light"] .g4cell__total,
[data-tone="light"] .g2cell__total,
[data-tone="light"] .crowd__more,
[data-tone="light"] .g6--tone.g6--muted {
  background: var(--tint-2);
  color: var(--ink-dim);
}

[data-tone="light"] .crowd__pic {
  border-color: var(--hair-strong);
  opacity: .65;
}

[data-tone="light"] .crowd__pic--none {
  background: var(--tint-2);
  color: var(--ink-faint);
}

/** ⚠️ ยอดบนกองเหรียญของตัวเอง — เหรียญทองอยู่แล้ว ตัวอักษรต้องเข้ม */
[data-tone="light"] .stack__sum { text-shadow: 0 1px 0 rgba(255, 255, 255, .7); }

/* ── 5. แถบผลย้อนหลัง · ช่องว่าง — เส้นขาวจางหายไปบนพื้นขาว ───── */

[data-tone="light"] .g6--empty {
  background: var(--tint-1);
  box-shadow: inset 0 0 0 1px var(--hair);
  color: var(--ink-faint);
}

[data-tone="light"] .bigdot--none {
  background: var(--tint-1);
  box-shadow: inset 0 0 0 1px var(--hair);
}

/* ── 6. ทิปเครดิตไม่พอ · กล่องลอย — พื้นต้องทึบพอให้อ่านออก ───── */

[data-tone="light"] .ctip {
  background: var(--panel);
  box-shadow: 0 8px 24px rgba(15, 23, 42, .16);
}

[data-tone="light"] .ctip::after { background: var(--panel); }

[data-tone="light"] .ctip__msg { color: #b91c1c; }

/* ── 7. ปุ่มในถาดเหรียญ — ต้องดูกดได้ ไม่ใช่ข้อความลอย ────────── */

[data-tone="light"] .chipbtn {
  border: 1px solid var(--hair);
  background: var(--panel);
  box-shadow: var(--lift-1);
}

[data-tone="light"] .chipbtn:disabled {
  background: var(--tint-1);
  box-shadow: none;
}

/* ══════════════════════════════════════════════════════════════
   ⭐ **ปุ่ม "รายการที่แทง" — เด่นกว่าปุ่มลัดอื่น** (เจ้าของสั่ง)

   ⚠️ เด่นด้วย **สีของธีม** ไม่ใช่สีตายตัว — ธีม 112 จานต้องใช้ได้หมด
   ⚠️ ไม่เด่นเท่าปุ่มยกเลิก (แดง) เพราะยกเลิกเป็นการกระทำที่ย้อนไม่ได้
   ══════════════════════════════════════════════════════════════ */

.chipbtn--mine {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--ink);
  font-weight: 800;
}

.chipbtn--mine .chipbtn__ico {
  animation: mineNudge 5s ease-in-out infinite;
}

.chipbtn--mine:hover {
  background: color-mix(in srgb, var(--accent) 26%, transparent);
  border-color: var(--accent);
}

/** ⭐ มีของวางอยู่ = เต้นเบา ๆ เตือนว่ามีอะไรให้ดู · js ติดคลาสให้ */
.chipbtn--mine.has-bets {
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);
  animation: minePulse 2.2s ease-in-out infinite;
}

@keyframes mineNudge {
  0%, 88%, 100% { transform: none; }
  92% { transform: translateY(-2px) rotate(-6deg); }
  96% { transform: translateY(0) rotate(4deg); }
}

@keyframes minePulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  60% { box-shadow: 0 0 0 6px transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .chipbtn--mine .chipbtn__ico,
  .chipbtn--mine.has-bets { animation: none !important; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **ฮีตแมป · ซ่อนตัว · แจ้งเตือนเปิดรอบ** (2026-08-29)
   ══════════════════════════════════════════════════════════════ */

/* ── 1. ฮีตแมป — ช่องที่คนลงหนักขอบเข้มกว่า ───────────────────
   🔴 **ทาที่ขอบ ไม่ใช่ที่พื้น** — พื้นของช่องคือสีประจำประตู
      (เสือน้ำเงิน · มังกรแดง · ต่ำแดง · สูงน้ำเงิน) ซึ่งเป็น **ข้อมูล**
      ย้อมทับเมื่อไหร่ผู้เล่นอ่านกระดานผิดทันที

   ⚠️ `--heat` เป็น 0..1 ที่ js คำนวณจากยอดรวมของช่องเทียบช่องที่หนักสุด
      ไม่มีค่า = 0 = ไม่มีอะไรเปลี่ยน (ร้านที่ปิดฟีเจอร์ไม่ต้องเจออะไรเลย) */

[data-gate] {
  --heat: 0;
}

[data-gate]::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;

  /** ⚠️ ใช้ `--gold` เป็นสีความร้อน — ไม่ชนกับสีประจำประตูของเกมไหนเลย */
  box-shadow: inset 0 0 0 calc(var(--heat) * 3px)
              color-mix(in srgb, var(--gold, #fbbf24) calc(var(--heat) * 70%), transparent);
  transition: box-shadow .4s ease;
}

/** ⭐ ช่องที่หนักที่สุดเรืองเบา ๆ — จุดที่ตาควรไปก่อน */
[data-gate][data-heat="1"]::before {
  animation: heatGlow 2.6s ease-in-out infinite;
}

@keyframes heatGlow {
  0%, 100% { box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--gold, #fbbf24) 70%, transparent); }
  55% { box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--gold, #fbbf24) 38%, transparent); }
}

/* ── 2. ปุ่มซ่อนตัว ──────────────────────────────────────────── */

.anonsw__ico,
.notifysw__ico { transition: opacity .15s, transform .15s; }

.anonsw:not(.is-on) .anonsw__ico { opacity: .4; }

/** ⭐ กำลังซ่อนอยู่ = แว่นเข้มขึ้นและมีวงรอบ บอกว่า "โหมดนี้ทำงานอยู่" */
.anonsw.is-on {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.anonsw.is-on .anonsw__ico { opacity: 1; animation: anonPeek 6s ease-in-out infinite; }

@keyframes anonPeek {
  0%, 90%, 100% { transform: none; }
  94% { transform: translateY(-2px); }
}

/* ── 3. ปุ่มแจ้งเตือน ────────────────────────────────────────── */

.notifysw[hidden] { display: none; }

.notifysw:not(.is-on) .notifysw__ico { opacity: .4; }

.notifysw.is-on {
  border-color: color-mix(in srgb, var(--gold, #fbbf24) 60%, var(--line));
  background: color-mix(in srgb, var(--gold, #fbbf24) 14%, transparent);
}

/** ⭐ เปิดอยู่ = กระดิ่งสั่นเบา ๆ นาน ๆ ครั้ง */
.notifysw.is-on .notifysw__ico { opacity: 1; animation: bellRing 7s ease-in-out infinite; }

@keyframes bellRing {
  0%, 88%, 100% { transform: none; }
  91% { transform: rotate(-14deg); }
  94% { transform: rotate(11deg); }
  97% { transform: rotate(-6deg); }
}

@media (prefers-reduced-motion: reduce) {
  [data-gate][data-heat="1"]::before,
  .anonsw.is-on .anonsw__ico,
  .notifysw.is-on .notifysw__ico { animation: none !important; }

  [data-gate]::before { transition: none; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **จัดถาดเหรียญใหม่ — ปุ่มล้นออกนอกกรอบ** (2026-08-29)

   🔴 แถวล่างมี 8 ปุ่มแล้วล้นออกนอกกรอบบนมือถือ (เจ้าของทัก)
      · "ซ้ำ" กับ "×2" ย้ายขึ้นไปแถวเดียวกับเหรียญ — เป็นการลงเดิมพัน
        ด้วยยอดเดิม ซึ่งเป็นเรื่องเดียวกับการเลือกเหรียญอยู่แล้ว
      · ปุ่มไอคอนล้วน (ตั้งค่าของจอ) จับกลุ่มชิดขวา ตัดบรรทัดทั้งก้อน
   ══════════════════════════════════════════════════════════════ */

.tray__row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/** ⚠️ เหรียญกินที่ที่เหลือทั้งหมด — ปุ่มลัดเกาะขวาโดยไม่บีบเหรียญ */
.tray__row > .tray__coins { flex: 1 1 auto; min-width: 0; }

.tray__quick {
  display: flex;
  gap: 5px;
  flex: 0 0 auto;
}

/** ⭐ เล็กกว่าปุ่มแถวล่าง — อยู่ข้างเหรียญที่สูงกว่า ไม่ควรแย่งสายตา */
.chipbtn--sm {
  padding: 5px 8px;
  font-size: calc(0.81rem * var(--fs, 1));
}

/**
 * 🔴 **ตัดบรรทัดได้ ไม่ใช่ล้นออกนอกกรอบ**
 *    จอ 360px ใส่ปุ่มมีข้อความ 3 ปุ่ม + ไอคอน 4 ปุ่ม + ยอด ไม่พออยู่แล้ว
 *    ปล่อยให้ตัดบรรทัดคือคำตอบที่ตรงไปตรงมา ไม่ใช่ย่อจนกดไม่โดน
 */
.tray__ops { flex-wrap: wrap; row-gap: 6px; }

/** ⚠️ กลุ่มไอคอนต้องไม่แตกกลางกลุ่ม — ตัดทั้งก้อนหรือไม่ตัดเลย */
.tray__tools {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  flex: 0 0 auto;
}

/** ⚠️ ยอด "วางแล้ว" เคยเกาะขวาด้วย `margin-left:auto` — ตอนนี้กลุ่มไอคอน
    ทำหน้าที่นั้นแทน ยอดจึงต่อท้ายกลุ่มไปเลย ไม่ต้องดันซ้ำ */
.tray__ops > .tray__staked { margin-left: 0; }

/* ⚠️ จอแคบมาก — ปุ่มมีข้อความยอมย่อลงอีกขั้นก่อนจะยอมให้ล้น */
@media (max-width: 380px) {
  .tray__ops .chipbtn,
  .tray__ops .btn--sm { padding-inline: 7px; font-size: calc(0.81rem * var(--fs, 1)); }

  .tray__quick .chipbtn--sm { padding-inline: 6px; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **โทนสว่าง — สีที่ตอกไว้ตายตัวสำหรับพื้นมืด** (2026-08-29)

   🔴 สีชมพู/แดงอ่อนพวกนี้ออกแบบมาให้อ่านบน**พื้นมืด** · บนพื้นขาวเหลือ
      contrast 1.4-1.8:1 คือมองไม่เห็นเลย (เจ้าของเห็นปุ่ม "ยกเลิกแทง"
      เป็นชมพูบนชมพู)

   ⚠️ ไล่จากรายการที่ตัวตรวจ contrast หาให้ ไม่ใช่จากที่ตาเห็นในภาพ
      แก้เฉพาะที่เห็นแปลว่ารอบหน้าจะมีจุดที่ไม่เห็นอีก
   ══════════════════════════════════════════════════════════════ */

[data-tone="light"] .btn--danger {
  background: rgba(244, 63, 94, .10);
  color: #9f1239;
  border-color: rgba(244, 63, 94, .38);
}

/**
 * ⚠️ **`.banbox__tag` ไม่อยู่ในรายการนี้โดยตั้งใจ**
 *    กล่องแบนเป็น**ชั้นทับเต็มจอสีเข้ม**เสมอ (`rgba(2,6,12,.72)`)
 *    แม้บนธีมสว่าง — มันคือกล่องที่ต้องหยุดคนให้อ่าน ไม่ใช่แผงของหน้า
 *    ทำให้ตัวหนังสือเข้มตามธีมคือ**ทำให้อ่านไม่ออกหนักกว่าเดิม**
 *    (แก้ผิดทางมาแล้วหนึ่งรอบ — ตัวตรวจ contrast จับได้ว่าแย่ลงจาก 3.1 เหลือ 2.0)
 */
[data-tone="light"] .player__credit.is-down,
[data-tone="light"] .note--lose .note__title,
[data-tone="light"] .pill[data-can-bet="0"],
[data-tone="light"] .ctip__msg {
  color: #b91c1c;
}

/**
 * ⚠️ **ทองของโทนสว่างเป็นอำพัน*เข้ม* (`#a16207`) ไม่ใช่ทองอ่อน**
 *    ตัวหนังสือสีเข้มบนพื้นนั้นได้แค่ 3.7:1 — ต้องเป็นสีขาวถึงจะผ่าน
 *    (เดาว่า "ทอง = พื้นสว่าง" แล้วพลาด · วัดแล้วถึงรู้)
 */
[data-tone="light"] .ctip__go { color: #ffffff; }


/** ⚠️ ตัวหนังสือบนแถบไลฟ์ยังต้องสว่างเสมอ — พื้นหลังเป็นวิดีโอ ไม่ใช่ธีม */
[data-tone="light"] .stage__num { color: #f1f5f9; }


/* ══════════════════════════════════════════════════════════════
   ⭐ **ขนาดการแสดงผลที่ผู้เล่นเลือกเอง — 3 ระดับ** (2026-08-30)

   🔴 "เผื่อผู้เล่นที่มีอายุมากต้องการตัวอักษรใหญ่ ๆ" (เจ้าของสั่ง)

   ⚠️ **ทุกขนาดคูณด้วย `--fs` อยู่แล้ว** (`font-size: calc(Npx * var(--fs,1))`)
      ที่นี่จึงตั้งแค่ตัวคูณตัวเดียว ไม่ต้องเขียนขนาดซ้ำอีกสามชุด

   ⚠️ **ขั้นละ 12%** — มากกว่านี้บนจอ 360px จะดันปุ่มล้นกรอบ
      น้อยกว่านี้คนที่ตาไม่ดีแยกไม่ออกว่าเปลี่ยนแล้ว
   ══════════════════════════════════════════════════════════════ */

:root { --fs: 1; }

[data-fs="lg"] { --fs: 1.12; }
[data-fs="xl"] { --fs: 1.24; }

/**
 * ⚠️ **ที่ว่างต้องโตตามตัวอักษรด้วย** — ตัวโตขึ้นแต่กล่องเท่าเดิม
 *    คือตัวอักษรชนขอบกล่อง · ขยาย padding ของแผงหลักตามสัดส่วนเดียวกัน
 */
[data-fs="lg"] .tray,
[data-fs="xl"] .tray { padding-block: calc(9px * var(--fs)); }

/* ── ปุ่มเปิดกล่องเลือกขนาด ─────────────────────────────────── */

.fssw__ico {
  font-weight: 900;
  letter-spacing: -.04em;
  transition: transform .15s;
}

/** ⭐ เลือกใหญ่กว่าปกติอยู่ = ปุ่มติดสีไว้ให้รู้ว่ากำลังใช้อยู่ */
.fssw.is-on {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.fssw.is-on .fssw__ico { animation: fsGrow 4.5s ease-in-out infinite; }

@keyframes fsGrow {
  0%, 86%, 100% { transform: none; }
  92% { transform: scale(1.18); }
}

/* ── กล่องเลือกขนาด ─────────────────────────────────────────── */

.fspick { display: grid; gap: 8px; }

/**
 * 🔴 **ปุ่มต้องใหญ่และตัวอย่างต้องเป็นขนาดจริง**
 *    คนที่มาเปลี่ยนขนาดคือคนที่มองไม่ชัด · ปุ่มเล็ก ๆ ที่เขียนว่า "ใหญ่มาก"
 *    ด้วยตัวอักษรขนาดปกติคือเรื่องตลกร้าย
 */
.fspick__b {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 14px 14px;
  border: 1px solid var(--edge, var(--line));
  border-radius: var(--radius-sm, 10px);
  background: var(--surface, var(--panel));
  color: var(--ink);
  font: inherit;
  text-align: start;
  cursor: pointer;
  transition: border-color .15s, transform .12s;
}

.fspick__b:hover { transform: translateY(-1px); }

.fspick__b.is-on {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
}

/** ⚠️ ตัวอย่างใช้ขนาดจริงของระดับนั้น ไม่ใช่ขนาดคงที่ */
.fspick__s { font-weight: 900; line-height: 1; }
.fspick__b[data-fs-set="1"] .fspick__s { font-size: 17px; }
.fspick__b[data-fs-set="lg"] .fspick__s { font-size: 19px; }
.fspick__b[data-fs-set="xl"] .fspick__s { font-size: 22px; }

.fspick__t { font-weight: 800; }
.fspick__n { margin-inline-start: auto; color: var(--ink-faint); font-size: calc(12px * var(--fs, 1)); }

.fspick__note {
  margin: 2px 0 0;
  color: var(--ink-faint);
  font-size: calc(12px * var(--fs, 1));
}

@media (prefers-reduced-motion: reduce) {
  .fssw.is-on .fssw__ico { animation: none !important; }
  .fspick__b { transition: none; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **ชั้นรับการเลื่อนทับเฟรมไลฟ์** (2026-08-30)

   🔴 `<iframe>` เป็นหน้าต่างของเว็บอื่น — การลากนิ้ว/หมุนล้อบนเฟรมตกเป็น
      ของเฟรม ไม่ใช่ของหน้าเรา · ไลฟ์กินพื้นที่บนสุดราวหนึ่งในสามของจอ
      ซึ่งเป็นจุดที่นิ้วโป้งไปโดนตอนสไลด์พอดี
      (เจ้าของแจ้ง "สไลด์หน้าจอไม่ค่อยติด ต้องจิ้มโดนพื้นหลังจริง ๆ")

   ⚠️ **`touch-action: pan-y`** — ยอมให้เลื่อนแนวตั้งผ่านชั้นนี้ไปที่หน้า
      แต่ไม่กินการซูมสองนิ้ว
   ══════════════════════════════════════════════════════════════ */

.stream__grab {
  position: absolute;
  inset: 0;
  z-index: 3;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;

  /** ⚠️ ต้องเป็น `pan-y` ไม่ใช่ `none` — `none` คือกินทุกอย่างรวมการเลื่อน */
  touch-action: pan-y;
}

/** ⭐ แตะแล้วหลบให้คุมเครื่องเล่น 6 วินาที แล้วกลับมาเอง */
.stream__grab.is-away { pointer-events: none; }

/**
 * ⚠️ บอกให้รู้ว่าแตะได้ — ไม่ใช่ชั้นลึกลับที่กดแล้วไม่มีอะไรเกิดขึ้น
 *    ขึ้นแค่ตอนแตะ ไม่ได้ค้างบนภาพไลฟ์ตลอดเวลา
 */
.stream__grab.is-away::after {
  content: 'คุมเครื่องเล่นได้ 6 วิ';
  position: absolute;
  left: 50%;
  bottom: 8px;
  transform: translateX(-50%);
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .6);
  color: #fff;
  font-size: calc(11px * var(--fs, 1));
  font-weight: 700;
  animation: grabHint .3s ease both;
}

@keyframes grabHint { from { opacity: 0; transform: translate(-50%, 6px); } }

@media (prefers-reduced-motion: reduce) {
  .stream__grab.is-away::after { animation: none !important; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **CPBot Live — ตัวเล่น WebRTC ของเราเอง** (2026-09-12)

   🔴 **ของใหม่ทั้งหมดอยู่ใต้ `[data-stream-type="cpbot"]` / คลาส `stream__rtc*`**
      โต๊ะที่ใช้ YouTube/HLS/iframe อยู่ต้องไม่เปลี่ยนแม้แต่พิกเซลเดียว

   ⚠️ ม่านสถานะ (กำลังเชื่อมต่อ · รอสัญญาณ · หยุดชั่วคราว) ต้อง **บอกทางไปต่อ**
      เสมอ — จอดำเฉย ๆ คือสิ่งที่ผู้เล่นแปลว่า "ระบบพัง" แล้วเลิกแทง
   ══════════════════════════════════════════════════════════════ */

.stream__rtc {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 10px;
  padding: 16px;
  text-align: center;
  color: #fff;
  background:
    radial-gradient(60% 60% at 50% 45%, rgba(69, 217, 255, .14), transparent 70%),
    rgba(4, 8, 16, .72);
}

/**
 * 🔴 **ห้ามใส่ `backdrop-filter` ที่ม่านนี้** (เจ้าของแจ้ง 2026-09-23
 *    "หน้าเว็บกระตุก เลื่อนจอไม่ค่อยติด")
 *
 *    ม่านนี้คลุม**เต็มพื้นที่ไลฟ์** และข้างในมีวงแหวนหมุนอยู่ตลอด
 *    เนื้อในขยับทุกเฟรม = ชั้นที่มี `backdrop-filter` ต้อง**เบลอร์พื้นหลังใหม่ทุกเฟรม**
 *    เต็มพื้นที่เวที ตลอดเวลาที่ร้านยังไม่เปิดกล้อง — งานหนักที่สุดบนหน้านี้
 *    และมันแย่งเฟรมกับการเลื่อนจอด้วย
 *
 * ⚠️ ตัวเบลอร์เองแทบไม่เห็นอยู่แล้ว (2px บนจอดำ) — เอาออกแล้วหน้าตาเหมือนเดิม
 */

.stream__rtc[hidden] { display: none; }

.stream__rtc p {
  margin: 0;
  max-width: 28ch;
  font-size: calc(13px * var(--fs, 1));
  font-weight: 600;
  line-height: 1.45;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
}

/** ⭐ วงแหวนเชื่อมต่อ — ขึ้นเฉพาะตอนกำลังต่อ ไม่ใช่ตอนหยุดรอผู้เล่นแตะ */
.stream__rtcSpin {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, .18);
  border-top-color: #45d9ff;
  border-right-color: #a97bff;
  display: none;
  animation: rtcSpin .9s linear infinite;
}

.stream.is-rtc-wait .stream__rtcSpin { display: block; }

@keyframes rtcSpin { to { transform: rotate(360deg); } }

.stream__rtcBtn {
  padding: 9px 18px;
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: 999px;
  background: linear-gradient(135deg, rgba(69, 217, 255, .9), rgba(169, 123, 255, .9));
  color: #06101c;
  font-weight: 800;
  font-size: calc(13px * var(--fs, 1));
  cursor: pointer;
  box-shadow: 0 6px 18px -6px rgba(69, 217, 255, .8);
}

.stream__rtcBtn[hidden] { display: none; }

/**
 * ⭐ **ป้ายดีเลย์จริง** — ผู้เล่นเห็นเองว่าภาพสดแค่ไหน (ความเชื่อใจของโต๊ะ)
 * ⚠️ มุมขวาบน ไม่ใช่ซ้ายบน — ซ้ายบนเป็นที่ของป้ายสด/จำนวนคนดู
 */
.stream__lat {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(3, 12, 20, .62);
  border: 1px solid rgba(63, 251, 99, .45);
  color: #3ffb63;
  font-size: calc(10.5px * var(--fs, 1));
  font-weight: 800;
  letter-spacing: .02em;
  pointer-events: none;
}

.stream__lat[hidden] { display: none; }

.stream__lat.is-slow { border-color: rgba(255, 196, 61, .55); color: #ffc43d; }

@media (prefers-reduced-motion: reduce) {
  .stream__rtcSpin { animation: none !important; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **โหมดคอนทราสต์สูง** (2026-08-30)

   🔴 สำหรับคนสายตาเลือนราง — ไม่ใช่ "ธีมสีเข้ม" · ต่างกันตรงที่
      อันนี้เน้น **ขอบหนา · เส้นแบ่งชัด · ตัวหนา** ไม่ใช่แค่เปลี่ยนสี
      คนกลุ่มนี้แยกสีใกล้กันไม่ออก แต่แยก "มีขอบ/ไม่มีขอบ" ได้

   ⚠️ **ต่อยอดจาก `data-tone` ที่มีอยู่แล้ว** — ทำงานได้ทั้งโทนมืดและสว่าง
      ไม่ได้บังคับให้ต้องเป็นโทนใดโทนหนึ่ง

   ⚠️ **ไม่แตะสีประจำประตู** (เสือน้ำเงิน · มังกรแดง · ต่ำแดง · สูงน้ำเงิน)
      พวกนั้นเป็นข้อมูล · ย้อมทับเมื่อไหร่ผู้เล่นอ่านกระดานผิด
   ══════════════════════════════════════════════════════════════ */

[data-hc="1"] {
  /** ⭐ เส้นขอบหนาและเข้มกว่าปกติมาก — ตัวหลักของโหมดนี้ */
  --hc-line: color-mix(in srgb, var(--ink) 62%, transparent);
}

[data-hc="1"] .player,
[data-hc="1"] .vs,
[data-hc="1"] .road,
[data-hc="1"] .board,
[data-hc="1"] .tray,
[data-hc="1"] .chat,
[data-hc="1"] .say,
[data-hc="1"] .stats,
[data-hc="1"] .infobar__b {
  border: 2px solid var(--hc-line);
  box-shadow: none;
}

/** ⭐ ช่องแทงต้องเห็นเส้นแบ่งชัดว่าช่องไหนถึงไหน */
[data-hc="1"] .gate,
[data-hc="1"] .g4cell,
[data-hc="1"] .g2cell {
  box-shadow: inset 0 0 0 2px var(--hc-line);
}

/** ⭐ ตัวหนาขึ้นทั้งหน้า — เส้นบางอ่านยากกว่าตัวเล็กสำหรับคนกลุ่มนี้ */
[data-hc="1"] body { font-weight: 600; }

[data-hc="1"] .coin__face,
[data-hc="1"] .stack__sum,
[data-hc="1"] .tray__stakedValue,
[data-hc="1"] .player__credit { font-weight: 900; }

/**
 * 🔴 **ตัวอักษรจางต้องเลิกจาง** — `--ink-faint` ออกแบบมาให้ "อยู่เบื้องหลัง"
 *    ซึ่งเป็นสิ่งที่คนสายตาเลือนรางอ่านไม่ออกพอดี
 */
[data-hc="1"] {
  --ink-dim: var(--ink);
  --ink-faint: color-mix(in srgb, var(--ink) 88%, var(--panel));
}

/** ⚠️ ของประดับที่จางกว่าเนื้อหาต้องเลิกจางด้วย ไม่งั้นหายไปเลย */
[data-hc="1"] .crowd__pic { opacity: .85; }

/** ⭐ ปุ่มที่กดไม่ได้ต้องยังอ่านออก — `opacity: .5` เดิมจางเกินสำหรับโหมดนี้ */
[data-hc="1"] .btn:disabled,
[data-hc="1"] .chipbtn:disabled { opacity: .75; }

/* ── ปุ่มสลับโหมด ───────────────────────────────────────────── */

.hcsw.is-on {
  border-color: color-mix(in srgb, var(--accent) 60%, var(--line));
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}

.hcsw:not(.is-on) .hcsw__ico { opacity: .4; }

.hcsw.is-on .hcsw__ico { animation: hcBlink 5s ease-in-out infinite; }

@keyframes hcBlink {
  0%, 88%, 100% { filter: none; }
  93% { filter: contrast(2.2); }
}

/* ══════════════════════════════════════════════════════════════
   ⭐ **กล่องยืนยันก่อนเงินออก** (2026-08-30)

   🔴 บอก**ยอดที่จะถูกหักจริง** ไม่ใช่ถาม "แน่ใจหรือไม่?" ลอย ๆ
      คำถามที่ไม่มีตัวเลขคือคำถามที่คนกดผ่านโดยไม่อ่าน
   ══════════════════════════════════════════════════════════════ */

.cfm__list {
  margin: 0 0 10px;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 4px;
  max-height: 42vh;
  overflow-y: auto;
}

.cfm__list li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 10px;
  border-radius: var(--radius-sm, 10px);
  background: var(--tint-1, rgba(255, 255, 255, .05));
  font-size: calc(13px * var(--fs, 1));
}

.cfm__sum {
  margin: 0 0 12px;
  text-align: end;
  font-size: calc(15px * var(--fs, 1));
}

.cfm__sum b { color: var(--gold); font-size: calc(19px * var(--fs, 1)); }

.cfm__act { display: flex; gap: 8px; }
.cfm__act .btn { flex: 1; }

/** ⭐ ปุ่มยืนยันเด่นกว่าปุ่มยกเลิกชัดเจน — คนสูงอายุต้องเห็นว่ากดตรงไหน */
.cfm__go { animation: cfmPulse 1.8s ease-in-out infinite; }

@keyframes cfmPulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); }
  60% { box-shadow: 0 0 0 8px transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .hcsw.is-on .hcsw__ico,
  .cfm__go { animation: none !important; }
}

/* ═══ หน้าเต๋าที่ออกบ่อย — แท็บ "หน้าเต๋า" ของเกมไฮโล ═════════════
   ⭐ **หกแถว หกหน้า ความยาวแถบเทียบกับหน้าที่ออกมากสุด**
   🔴 ห้ามใช้ `%` ของทั้งหมดมาเป็นความยาวแถบ — หกหน้าเฉลี่ยแล้วได้
      หน้าละ ~17% ทุกแถบจะสั้นเท่ากันหมดจนดูไม่ออกว่าอันไหนมากกว่า
   ⚠️ ทุกขนาดคูณ `--fs` — ผู้เล่นสูงอายุเลือกตัวใหญ่ได้ (เจ้าของสั่งไว้) */
.road__faces { padding: calc(6px * var(--fs, 1)) 0; }

.dface { display: grid; gap: calc(5px * var(--fs, 1)); }

.dface__r {
  display: grid;
  grid-template-columns: calc(28px * var(--fs, 1)) 1fr auto auto auto;
  align-items: center;
  gap: calc(7px * var(--fs, 1));
  padding: calc(4px * var(--fs, 1)) calc(6px * var(--fs, 1));
  border-radius: 8px;
  background: var(--panel-2);
}

/* ⚠️ เน้นเบา ๆ เท่านั้น — ไม่ใช่คำแนะนำให้แทงหน้านี้ */
.dface__r.is-top { background: var(--panel); }

/* ⭐ ช่องหน้าเต๋า — เป็นลูกเต๋าจริง ๆ ไม่ใช่ตัวเลขลอย ๆ
   ⚠️ ตัวเลขยังอยู่ข้างใต้ · โปร่งใสเฉพาะตอนมีรูปลายจากร้าน */
.dface__n {
  display: grid;
  place-items: center;
  width: calc(26px * var(--fs, 1));
  height: calc(26px * var(--fs, 1));
  border-radius: 22%;
  background: var(--g4-die, var(--panel));
  color: var(--g4-die-pip, var(--ink));
  font-size: calc(15px * var(--fs, 1));
  font-weight: 900;
  box-shadow: 0 1px 3px rgb(0 0 0 / .45);
}

.dface__n.is-red { color: var(--g4-die-red, var(--tiger)); }

.dface__n[style*="--face"] {
  background-image: var(--face);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  color: transparent;
}

.dface__bar {
  height: calc(9px * var(--fs, 1));
  border-radius: 999px;
  background: var(--hair);
  overflow: hidden;
}

.dface__bar > i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: width .35s ease;
}

.dface__c, .dface__p, .dface__a {
  font-size: calc(12px * var(--fs, 1));
  color: var(--ink-dim);
  white-space: nowrap;
}

.dface__p { font-weight: 700; color: var(--ink); }

.dface__a { color: var(--ink-faint); }

/* 🔴 จอแคบ: ตัด "กี่รอบก่อน" ทิ้ง ไม่ใช่ให้ล้นออกนอกกล่อง
   (เจ้าของแจ้งเรื่องปุ่มดันกันจนล้นมาแล้ว — อย่าให้ซ้ำรอย) */
@media (max-width: 26rem) { .dface__a { display: none; } }

/* ═══ แจกแจงแต้มรวม — แท็บ "แต้ม" ของเกมไฮโล ═══════════════════
   ⭐ **สิบหกแถว แต้ม 3–18 · ขีดจางคือค่าที่ควรเป็นตามคณิตศาสตร์**
   🔴 สีตามช่วงจริงของกติกา: ต่ำ ≤10 แดง · ไฮโล 11 ทอง · สูง ≥12 น้ำเงิน
      (ผ้าไฮโลจริงพิมพ์ "ต่ำ" สีแดง — ห้ามสลับให้ตรงกับชื่อโทเคน)
   ⚠️ แถวเตี้ยกว่าแผงหน้าเต๋าเพราะมีสิบหกแถว ไม่ใช่หก */
.road__sums { padding: calc(6px * var(--fs, 1)) 0; }

.dsum { display: grid; gap: calc(3px * var(--fs, 1)); }

.dsum__r {
  display: grid;
  grid-template-columns: calc(24px * var(--fs, 1)) 1fr calc(24px * var(--fs, 1));
  align-items: center;
  gap: calc(6px * var(--fs, 1));
}

.dsum__n {
  font-size: calc(12px * var(--fs, 1));
  font-weight: 800;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}

.dsum__c {
  font-size: calc(11px * var(--fs, 1));
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

.dsum__bar {
  position: relative;
  height: calc(11px * var(--fs, 1));
  border-radius: 4px;
  background: var(--panel-2);
  overflow: hidden;
}

.dsum__bar > i {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--quiet);
  transition: width .35s ease;
}

/* ⭐ ขีดค่าที่ควรเป็น — เส้นเดียว ไม่ใช่แถบซ้อน จะได้ไม่แย่งสายตา */
.dsum__bar > u {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--ink-faint);
  opacity: .75;
}

.dsum__r--lo .dsum__bar > i { background: var(--dragon); }
.dsum__r--hi .dsum__bar > i { background: var(--tiger); }
.dsum__r--el .dsum__bar > i { background: var(--gold); }
.dsum__r--el .dsum__n { color: var(--gold); }

/* ── การ์ดระยะห่าง ไฮโล / ตอง / คู่ / สายยาวสุด ──────────────── */
.dgap {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: calc(5px * var(--fs, 1));
  margin-top: calc(8px * var(--fs, 1));
}

@media (min-width: 30rem) { .dgap { grid-template-columns: repeat(4, 1fr); } }

.dgap__c {
  display: grid;
  gap: 1px;
  padding: calc(5px * var(--fs, 1)) calc(7px * var(--fs, 1));
  border-radius: 8px;
  background: var(--panel-2);
  min-width: 0;
}

.dgap__k { font-size: calc(11px * var(--fs, 1)); color: var(--ink-faint); }

/* 🔴 **ตัดคำลงบรรทัดใหม่ ไม่ใช่ตัดข้อความทิ้ง** — วัดจริงที่ 320px และที่
   ขนาดอักษร "ใหญ่มาก" แล้ว "ต่ำ 7 · สูง 2" กับ "รอบล่าสุด" โดน ellipsis
   กินหาย · ตัวเลขที่อ่านไม่ครบแย่กว่าการ์ดสูงขึ้นหนึ่งบรรทัด
   (บทเรียนเดิม: ellipsis ไม่เปลี่ยน scrollWidth จึงหลุดสายตาได้ง่าย) */
.dgap__v {
  font-size: calc(13px * var(--fs, 1));
  font-weight: 800;
  color: var(--ink);
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.dgap__n { font-size: calc(10px * var(--fs, 1)); color: var(--ink-faint); }


/* ═══════════════════════════════════════════════════════════════
   ⭐ หน้าเข้าระบบ — ลูกเล่นเคลื่อนไหว (2026-08-31)
   ───────────────────────────────────────────────────────────────
   🔴 **ทุกอันเป็น `transform` / `opacity` ล้วน**
      ไม่มีตัวไหนขยับ `top`/`left`/`width`/`height` — สองอย่างหลัง
      บังคับให้เบราว์เซอร์คำนวณผังใหม่ทุกเฟรม ซึ่งบนมือถือราคาถูก
      คือหน้าล็อกอินที่กระตุกตั้งแต่วินาทีแรกที่เห็น

   ⚠️ **ตัวลดการเคลื่อนไหวประกาศรวมไว้บนสุดของไฟล์แล้ว**
      (`prefers-reduced-motion` ตัดทุก animation ทั้งหน้า)
      ที่นี่จึงไม่ต้องเขียนซ้ำ — แต่ห้ามลบตัวนั้นเด็ดขาด

   ⚠️ ชั้นประดับทุกตัว `pointer-events: none` — ห้ามขวางปุ่ม
   ═══════════════════════════════════════════════════════════════ */

.entry--fx { isolation: isolate; }

/* ── ฉากหลังชั้นที่ 2: ลำแสงหมุนช้า ๆ ─────────────────────────
   ⚠️ ช้ามาก (48 วินาทีต่อรอบ) โดยตั้งใจ — เร็วกว่านี้กลายเป็นของ
      ที่แย่งสายตากับปุ่ม ซึ่งเป็นสิ่งเดียวที่หน้านี้อยากให้คนกด        */
.entry__rays {
  position: absolute;
  inset: -60% -60% auto -60%;
  height: 160%;
  pointer-events: none;
  opacity: .30;
  background:
    conic-gradient(from 0deg at 50% 50%,
      transparent 0deg, rgba(59, 111, 224, .16) 24deg, transparent 48deg,
      transparent 120deg, rgba(239, 68, 68, .14) 144deg, transparent 168deg,
      transparent 250deg, rgba(34, 197, 94, .12) 274deg, transparent 298deg);
  animation: rays 48s linear infinite;
}

@keyframes rays { to { transform: rotate(1turn); } }

/* ── ฉากหลังชั้นที่ 3: เม็ดฝุ่นลอยขึ้น ────────────────────────
   🔴 **สร้างจาก PHP ไม่ใช่ js** — ต้องมีตั้งแต่เฟรมแรก · ให้ js สร้าง
      แปลว่าคนเน็ตช้าเห็นฉากหลังโล่ง ๆ ก่อนแล้วเม็ดค่อยโผล่มาทีหลัง
   ⚠️ 12 เม็ดพอ — มากกว่านี้ไม่ได้สวยขึ้นแต่กิน GPU ของมือถือจริง      */
.entry__dust {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.dust {
  position: absolute;
  bottom: -12px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold);
  opacity: 0;
  filter: blur(.5px);
  animation: floatUp linear infinite;
}

@keyframes floatUp {
  0%   { transform: translate3d(0, 0, 0) scale(.6); opacity: 0; }
  12%  { opacity: .55; }
  88%  { opacity: .35; }
  100% { transform: translate3d(var(--dx, 20px), -102vh, 0) scale(1); opacity: 0; }
}

/* ⚠️ เขียนทีละเม็ดเพราะต้องการค่าที่ **ไม่สุ่มใหม่ทุกครั้งที่โหลด** —
   สุ่มด้วย js แล้วหน้าจะดูไม่เหมือนเดิมทุกครั้ง ซึ่งอ่านเหมือนของเสีย */
.dust--1  { left: 6%;  --dx: 18px;  animation-duration: 19s; animation-delay: 0s;    }
.dust--2  { left: 14%; --dx: -22px; animation-duration: 24s; animation-delay: 2.5s;  }
.dust--3  { left: 23%; --dx: 26px;  animation-duration: 17s; animation-delay: 5s;    }
.dust--4  { left: 31%; --dx: -14px; animation-duration: 27s; animation-delay: 1.2s;  }
.dust--5  { left: 39%; --dx: 30px;  animation-duration: 21s; animation-delay: 7.4s;  }
.dust--6  { left: 47%; --dx: -28px; animation-duration: 25s; animation-delay: 3.8s;  }
.dust--7  { left: 56%; --dx: 16px;  animation-duration: 18s; animation-delay: 9.1s;  }
.dust--8  { left: 64%; --dx: -20px; animation-duration: 23s; animation-delay: 6.2s;  }
.dust--9  { left: 72%; --dx: 24px;  animation-duration: 20s; animation-delay: 11s;   }
.dust--10 { left: 80%; --dx: -18px; animation-duration: 26s; animation-delay: 4.6s;  }
.dust--11 { left: 88%; --dx: 22px;  animation-duration: 22s; animation-delay: 8.3s;  }
.dust--12 { left: 94%; --dx: -25px; animation-duration: 28s; animation-delay: 13s;   }

/* ── การ์ด: ลอยขึ้นตอนเปิดหน้า + ขอบเรืองแสงหมุน ──────────────
   ⚠️ `backwards` — ระหว่างหน่วง 0.05 วิ ต้องอยู่ที่เฟรมแรกของ animation
      ไม่ใช่โผล่เต็มตัวก่อนแล้วค่อยกระตุกลงไปเริ่มใหม่                  */
.entry__card--fx {
  animation: cardIn .55s cubic-bezier(.16, .84, .44, 1) .05s backwards;
}

@keyframes cardIn {
  from { transform: translate3d(0, 18px, 0) scale(.97); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/**
 * ⭐ ขอบเรืองแสงหมุนรอบการ์ด
 *
 * 🔴 **ใช้ `mask` เจาะไส้ออก ไม่ใช่วางกล่องทึบทับ** — วางทับแปลว่า
 *    ต้องมีพื้นหลังทึบมาปิดตรงกลาง ซึ่งจะบังเนื้อหาของการ์ดทั้งใบ
 *
 * ⚠️ เบราว์เซอร์ที่ไม่รองรับ `mask` จะไม่เห็นขอบนี้ (ก็แค่ไม่มีลูกเล่น)
 *    ไม่ใช่เห็นสี่เหลี่ยมทึบทับการ์ด — จึงตั้ง `opacity` ไว้ต่ำเป็นตัวกัน
 */
.entry__ring {
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  opacity: .85;
  background: conic-gradient(from 0deg,
    transparent 0%, var(--tiger) 12%, transparent 26%,
    transparent 50%, var(--gold) 62%, transparent 76%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: rays 6s linear infinite;
}

/* ── แถบสถานะสด ──────────────────────────────────────────────
   ⭐ บอกว่าโต๊ะกำลังเล่นอยู่ไหม **ก่อน** ที่ผู้เล่นจะล็อกอิน            */

.lobby {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: calc(4px * var(--fs, 1)) calc(8px * var(--fs, 1));
  margin: calc(10px * var(--fs, 1)) 0 0;
  padding: calc(6px * var(--fs, 1)) calc(12px * var(--fs, 1));
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  font-size: calc(13px * var(--fs, 1));
  color: var(--ink-dim);
  animation: cardIn .5s ease-out .25s backwards;
}

/* ⚠️ เขียวเฉพาะตอนรอบเปิดจริง — เขียวตลอดเวลาแปลว่าไม่ได้บอกอะไรเลย */
.lobby.is-open {
  border-color: rgba(34, 197, 94, .45);
  background: rgba(34, 197, 94, .10);
}

.lobby__dot {
  width: calc(8px * var(--fs, 1));
  height: calc(8px * var(--fs, 1));
  border-radius: 50%;
  background: var(--ink-faint);
  flex: none;
}

/**
 * 🔴 **จุดกะพริบเฉพาะตอนรอบเปิด**
 *    กะพริบตลอดเวลา = สัญญาณที่ไม่ได้แปลว่าอะไร คนจะเลิกมอง
 *    ซึ่งทำให้ตอนที่มันสำคัญจริง ๆ ก็ไม่มีใครเห็น
 */
.lobby.is-open .lobby__dot {
  background: #22c55e;
  animation: pulseDot 1.6s ease-in-out infinite;
}

@keyframes pulseDot {
  0%, 100% { transform: scale(1);   box-shadow: 0 0 0 0 rgba(34, 197, 94, .55); }
  70%      { transform: scale(1.1); box-shadow: 0 0 0 8px rgba(34, 197, 94, 0); }
}

.lobby__text { display: inline-flex; align-items: center; gap: calc(7px * var(--fs, 1)); }
.lobby__round { color: var(--ink); font-weight: 800; }
.lobby__label { color: var(--ink-dim); }

.lobby__clock {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  color: var(--gold);
}

/* ⚠️ ใกล้หมดเวลาต้องเห็นชัดขึ้น ไม่ใช่แค่เปลี่ยนสี — คนที่กำลังพิมพ์รหัสอยู่
   ไม่ได้จ้องตัวเลขนี้ · การขยับคือสิ่งที่ตาจับได้จากมุมตา              */
.lobby__clock.is-hot { color: var(--danger); animation: pulseDot 1s ease-in-out infinite; }

.lobby__who { color: var(--ink-faint); }

.lobby__who::before {
  content: "\00b7";
  margin-right: calc(6px * var(--fs, 1));
  color: var(--ink-faint);
}

/* ── ปุ่มที่มีลูกเล่น (ใช้กับปุ่ม LINE) ────────────────────────
   ⚠️ คลาส `btn--fx` แยกจาก `btn--line` โดยตั้งใจ — ลูกเล่นกับสี
      เป็นคนละเรื่อง · ปุ่มอื่นในอนาคตหยิบไปใช้ได้โดยไม่ต้องเป็นสีเขียว */

.btn--fx {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  gap: calc(9px * var(--fs, 1));
  transition: transform .15s ease, box-shadow .25s ease, filter .2s ease;
}

.btn--fx:hover  { transform: translateY(-1px); filter: brightness(1.06); }
.btn--fx:active { transform: translateY(1px); }

/**
 * ⭐ **แสงกวาดผ่านปุ่มเป็นระยะ** — บอกว่าปุ่มนี้กดได้ โดยไม่ต้องเขียนว่ากดได้
 *
 * ⚠️ หน่วง 6 วินาทีต่อรอบ · ถี่กว่านี้กลายเป็นไฟกะพริบที่น่ารำคาญ
 *    บนหน้าที่คนต้องอ่านชื่อโต๊ะกับกรอกรหัสผ่าน
 */
.btn__sheen {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -60%;
  width: 45%;
  pointer-events: none;
  z-index: 0;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .45), transparent);
  animation: sheen 6s ease-in-out infinite;
}

@keyframes sheen {
  0%, 62%  { transform: translate3d(0, 0, 0); }
  100%     { transform: translate3d(400%, 0, 0); }
}

/** ⚠️ ทุกอย่างในปุ่มต้องอยู่เหนือแสงกวาด ไม่งั้นตัวหนังสือจะจมหายตอนแสงผ่าน */
.btn--fx > :not(.btn__sheen) { position: relative; z-index: 1; }

/**
 * ⭐ โลโก้ LINE — เอียงเบา ๆ เป็นจังหวะ
 *
 * 🔴 เป็น **SVG** ไม่ใช่อิโมจิ · ของเดิมเป็น `\1f4ac` ซึ่งบน Windows/Chrome
 *    เรนเดอร์เป็นวงกลมขาวเปล่า ๆ (เจ้าของถ่ายหน้าจอมาให้เห็นเอง)
 */
.btn__line {
  flex: none;
  animation: nudge 3.2s ease-in-out infinite;
}

@keyframes nudge {
  0%, 82%, 100% { transform: rotate(0) scale(1); }
  88%           { transform: rotate(-9deg) scale(1.12); }
  94%           { transform: rotate(6deg) scale(1.08); }
}

/* ⭐ ลูกศรขยับ — บอกว่ากดแล้วพาออกไปที่อื่น (ไปหน้า LINE) */
.btn__go {
  font-weight: 900;
  animation: nudgeX 2.4s ease-in-out infinite;
}

@keyframes nudgeX {
  0%, 70%, 100% { transform: translate3d(0, 0, 0); }
  85%           { transform: translate3d(4px, 0, 0); }
}

/* ⚠️ ลิงก์ "สมัครสมาชิก" ต้องดูกดได้ — ของเดิมเป็นตัวหนังสือธรรมดา
   ที่กลืนไปกับประโยครอบข้างจนคนหาไม่เจอ                              */
.entry__join {
  font-weight: 800;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px dashed currentColor;
  transition: color .2s;
}

.entry__join:hover { color: var(--gold); }


/* ═══════════════════════════════════════════════════════════════
   ⭐ เหรียญทองลอยขึ้นบนฉากหลังหน้านอกโต๊ะ (2026-08-31)
   ───────────────────────────────────────────────────────────────
   🔴 **หน้าตาเดียวกับกองชิปบนกระดาน** (`.stack__coin`) โดยตั้งใจ —
      สิ่งที่ลอยอยู่บนหน้าเข้าระบบ คือสิ่งเดียวกับที่เขาจะได้กด
      ในอีกสองคลิก · ใช้เหรียญหน้าตาอื่นคือโฆษณาของที่ไม่มีในเกม

   🔴 **สองชั้นซ้อนกัน: ตัวนอกลอย ตัวใน (`b`) หมุน**
      ยัดสอง animation ลงอิลิเมนต์เดียวไม่ได้ — ทั้งคู่เขียน `transform`
      ตัวหลังจะทับตัวแรกทิ้ง แล้วเหรียญจะหมุนอยู่กับที่ไม่ลอยไปไหน

   ⚠️ **`transform`/`opacity` ล้วน** ไม่มีตัวไหนขยับผัง
      (`EntryMotionTest` ไล่ตรวจทุก keyframes ในไฟล์นี้ให้อยู่แล้ว)

   ⚠️ **10 เหรียญพอ** — ความรู้สึก "เยอะ" มาจากความต่างของขนาดกับ
      ความเร็ว ไม่ใช่จำนวน · เพิ่มจำนวนคือจ่ายค่า GPU ของมือถือเปล่า ๆ
   ═══════════════════════════════════════════════════════════════ */

.entry__coins {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;

  /**
   * ⚠️ **ต้องมี perspective ที่พ่อ ไม่ใช่ที่ตัวเหรียญ**
   *    ใส่ที่ตัวเหรียญแล้วแต่ละอันจะมีจุดมองของตัวเอง — หมุนออกมา
   *    หน้าตาเหมือนกันหมดและแบน · ใส่ที่พ่อจึงได้มุมมองร่วมกันทั้งฉาก
   *    เหรียญริมจอเลยเอียงต่างจากเหรียญกลางจอ เหมือนของจริง
   */
  perspective: 700px;
}

.gcoin {
  position: absolute;
  bottom: -60px;
  display: block;
  transform-style: preserve-3d;
  animation: coinRise linear infinite;
}

/**
 * ⭐ ตัวเหรียญจริง — ไล่สีทองชุดเดียวกับ `.stack__coin`
 *
 * ⚠️ **ขอบในเป็น `inset` box-shadow ไม่ใช่ `border`**
 *    `border` กินพื้นที่ของกล่องแล้วต้องไปคิด `box-sizing` ต่อ ·
 *    `inset` วาดทับข้างในเฉย ๆ ขนาดจึงตรงตามที่ตั้งเสมอ
 */
.gcoin > b {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #fde68a, var(--gold) 45%, #b45309);
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, .35),
    inset 0 -3px 6px rgba(146, 64, 14, .55),
    0 4px 12px rgba(0, 0, 0, .35);
  animation: coinSpin linear infinite;
}

/**
 * ⭐ ลอยขึ้นพร้อมส่ายซ้ายขวา
 *
 * ⚠️ **จางหัวจางท้าย** — โผล่มาเต็มตัวที่ขอบจอแล้วหายวับที่อีกขอบ
 *    อ่านเหมือนของกระตุก · ต้องค่อย ๆ ชัดและค่อย ๆ จาง
 */
@keyframes coinRise {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  10%  { opacity: .85; }
  50%  { transform: translate3d(var(--sway, 26px), -55vh, 0); }
  85%  { opacity: .7; }
  100% { transform: translate3d(0, -112vh, 0); opacity: 0; }
}

/**
 * ⭐ หมุนรอบแกนตั้ง — ได้เงาขอบเหรียญตอนหันข้างมาฟรี ๆ จาก perspective
 *
 * ⚠️ หมุนแกนเดียว (`rotateY`) ไม่ใช่สามแกน — หมุนมั่วดูเหมือนของหลุด
 *    ไม่ใช่เหมือนเหรียญที่ถูกโยนขึ้นไป
 */
@keyframes coinSpin {
  from { transform: rotateY(0); }
  to   { transform: rotateY(1turn); }
}

/**
 * ⚠️ เขียนทีละเหรียญเพราะต้องการค่าที่ **ไม่สุ่มใหม่ทุกครั้งที่โหลด**
 *    เหรียญเล็ก = อยู่ไกล = ช้ากว่าและจางกว่า (พารัลแลกซ์)
 *
 * 🔴 **หน่วงต้องเป็นค่าติดลบ ไม่ใช่ค่าบวก**
 *
 *    ค่าบวก = เฟรมแรกที่ผู้เล่นเห็น **ไม่มีเหรียญสักเม็ด** แล้วต้องรอ
 *    13-18 วินาทีกว่าฉากจะเต็ม · วัดจริงได้ "เหรียญที่เห็นเฟรมแรก: 0"
 *    ซึ่งขัดกับเหตุผลทั้งหมดที่ฉากนี้มีอยู่ — คนตัดสินใจว่าเว็บนี้น่าเล่น
 *    ไหมภายในวินาทีแรก ไม่ใช่นาทีแรก
 *
 *    ค่าติดลบ = animation เริ่มไปแล้วบางส่วนตั้งแต่เฟรมแรก · เหรียญจึง
 *    กระจายอยู่คนละความสูงพร้อมกันทันทีที่หน้าโหลดเสร็จ
 *    (กระจายค่าให้ทั่วช่วงของ duration ตัวเอง จะได้ไม่กองอยู่ระดับเดียวกัน)
 */
.gcoin--1  { left: 4%;  width: 30px; height: 30px; --sway: 34px;  animation-duration: 15s; animation-delay: -2.5s;    }
.gcoin--2  { left: 17%; width: 18px; height: 18px; --sway: -22px; animation-duration: 23s; animation-delay: -14s;  opacity: .7; }
.gcoin--3  { left: 28%; width: 24px; height: 24px; --sway: 28px;  animation-duration: 18s; animation-delay: -9s;  }
.gcoin--4  { left: 41%; width: 14px; height: 14px; --sway: -18px; animation-duration: 27s; animation-delay: -20s;  opacity: .55; }
.gcoin--5  { left: 52%; width: 34px; height: 34px; --sway: 24px;  animation-duration: 13s; animation-delay: -4s;  }
.gcoin--6  { left: 63%; width: 20px; height: 20px; --sway: -30px; animation-duration: 21s; animation-delay: -16s;  opacity: .75; }
.gcoin--7  { left: 74%; width: 27px; height: 27px; --sway: 20px;  animation-duration: 17s; animation-delay: -12s; }
.gcoin--8  { left: 85%; width: 16px; height: 16px; --sway: -26px; animation-duration: 25s; animation-delay: -6s;  opacity: .6; }
.gcoin--9  { left: 93%; width: 22px; height: 22px; --sway: 16px;  animation-duration: 19s; animation-delay: -15s;   }
.gcoin--10 { left: 9%;  width: 16px; height: 16px; --sway: -20px; animation-duration: 29s; animation-delay: -24s; opacity: .6; }

/** ⚠️ ยิ่งเล็กยิ่งอยู่ไกล — หมุนช้าลงตามด้วย ไม่งั้นดูเหมือนของใกล้ที่หมุนเร็ว */
.gcoin--1 > b, .gcoin--5 > b, .gcoin--7 > b { animation-duration: 3.2s; }
.gcoin--3 > b, .gcoin--6 > b, .gcoin--9 > b { animation-duration: 4.4s; }
.gcoin--2 > b, .gcoin--4 > b, .gcoin--8 > b, .gcoin--10 > b { animation-duration: 6s; }

/**
 * 🔴 **จอเตี้ยมาก (มือถือแนวนอน) ต้องลดของลง**
 *    ความสูงเหลือ ~300px แต่เหรียญยังวิ่ง 112vh เท่าเดิม = ของเยอะจน
 *    บังการ์ดที่กินพื้นที่เกือบทั้งจอ · เหลือครึ่งเดียวพอให้รู้ว่ามีลูกเล่น
 */
@media (max-height: 460px) {
  .gcoin--2, .gcoin--4, .gcoin--6, .gcoin--8, .gcoin--10 { display: none; }
  .entry__dust { opacity: .5; }
}

/**
 * 🔴 **โหมดคอนทราสต์สูงต้องไม่มีของวิ่งอยู่หลังตัวหนังสือ**
 *    คนที่เปิดโหมดนี้คือคนที่อ่านยากอยู่แล้ว — ฉากหลังที่ขยับคือสิ่งที่
 *    ทำให้อ่านยากขึ้นอีก · เก็บแค่พื้นเรียบ ๆ พอ
 */
:root[data-hc="1"] .entry__coins,
:root[data-hc="1"] .entry__dust,
:root[data-hc="1"] .entry__rays { display: none; }


/* ═══════════════════════════════════════════════════════════════
   ⭐ เครื่องยนต์ฉากหลังของหน้านอกโต๊ะ — รองรับ 120 ฉาก (2026-08-31)
   ───────────────────────────────────────────────────────────────
   🔴 **ที่นี่มีแต่ของที่ทุกฉากใช้ร่วมกัน**
      ตำแหน่งเม็ด · ขนาด · จังหวะ · keyframes · ชั้นพิเศษ
      สิ่งที่ต่างกันต่อฉากมีแค่ **สองสี** ซึ่ง `EntryScene::inlineStyle()`
      ฝังลงหน้าเป็น `--fx1` / `--fx2` ไม่กี่ร้อยไบต์

      เขียน CSS แยกทีละฉาก = ผู้เล่นทุกคนโหลด 119 ฉากที่ร้านไม่ได้เลือก
      ทุกครั้งที่เปิดหน้า

   🔴 **หน่วงทุกช่องเป็นค่าติดลบ**
      ค่าบวก = เฟรมแรกที่ผู้เล่นเห็นไม่มีเม็ดสักอัน แล้วต้องรอสิบกว่าวินาที
      กว่าฉากจะเต็ม · เคยพลาดมาแล้วกับเหรียญทอง วัดได้ "เห็นเฟรมแรก 0 เม็ด"

   ⚠️ **ความทึบล็อกไว้ที่นี่ ไม่ให้ฉากตั้งเอง** — ฉากหลังคือของประดับ
      ตัวหนังสือบนการ์ดต้องอ่านออกทั้ง 120 ฉาก ไม่มีข้อยกเว้น

   ⚠️ ทุก animation เป็น `transform`/`opacity` ล้วน (`EntryMotionTest` ตรวจ)
   ═══════════════════════════════════════════════════════════════ */

/**
 * ⭐ **สีของฉากต้องลงพื้นหลังจริง ไม่ใช่แค่ทาเม็ด**
 *
 * ══════════════════════════════════════════════════════════════
 * 🔴 **ของเดิมพื้นหลังใช้สีธีม (`--bg-2`) ล้วน**
 *
 *    ฉากทั้ง 120 แบบจึงต่างกันแค่ **เม็ดเล็ก ๆ** ที่ลอยอยู่ —
 *    เปิดหน้าจริงแล้วแทบแยกไม่ออกว่าเลือกฉากไหน (จอเกือบดำหมด)
 *    ขณะที่การ์ดพรีวิวทาสีฉากลงพื้น จึงดูสวยและต่างกันชัด
 *    ลูกค้าเลือกจากพรีวิวแล้วได้ของคนละอย่าง
 *    (เจ้าของทัก 2026-09-01 "ทำไมสีพื้นหลังพรีวิวกับเว็บจริงไม่เหมือนกัน")
 *
 * ⚠️ **ใช้สูตรเดียวกับการ์ดพรีวิวเป๊ะ** — `EntrySceneTest` เทียบสองไฟล์
 *    ให้ตรงกัน · แก้ข้างเดียวเมื่อไหร่ พรีวิวกับของจริงจะเพี้ยนอีก
 *
 * ⚠️ **ทาทับพื้นธีม ไม่ได้แทนที่** — จางลงเป็นโปร่งใสที่ 72%
 *    พื้นล่างยังเป็นสีธีมที่ร้านเลือก การ์ดกับตัวหนังสือจึงอ่านออกเหมือนเดิม
 * ══════════════════════════════════════════════════════════════
 */
.entry__fx::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 50% 0%, var(--fx2), transparent 72%);
}

/** ⚠️ โทนสว่างต้องจางกว่ามาก — สีเข้มทับพื้นสว่างคือคราบมืดกลางจอ */
:root[data-tone="light"] .entry__fx::before { opacity: .30; }

.entry__fx {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;

  /** ⚠️ perspective อยู่ที่พ่อ เม็ดริมจอจึงเอียงต่างจากเม็ดกลางจอ */
  perspective: 700px;
}

.fxp {
  position: absolute;
  display: block;
  width: var(--fxs, 22px);
  height: var(--fxs, 22px);
  bottom: -8%;
  transform-style: preserve-3d;
  animation: fxrise linear infinite;
}

/** ⭐ ตัวในหมุน — แยกจากตัวนอกที่ลอย เพราะทั้งคู่เขียน `transform` */
.fxp > i {
  display: block;
  width: 100%;
  height: 100%;
  animation: fxspinY linear infinite;
  animation-duration: 4s;
}

/* ── ตำแหน่ง 14 ช่อง — ใช้ร่วมกันทุกฉาก ───────────────────────
   ⚠️ ฉากต่างกันที่หน้าตากับการเคลื่อนไหว ไม่ใช่ที่พิกัด          */

.fxp--1  { left: 4%; --fxs: 30px; --sway: 34px; animation-duration: 15s; animation-delay: -2.5s; opacity: 1.00; }
.fxp--2  { left: 12%; --fxs: 16px; --sway: -22px; animation-duration: 24s; animation-delay: -14s; opacity: 0.62; }
.fxp--3  { left: 21%; --fxs: 24px; --sway: 26px; animation-duration: 18s; animation-delay: -9s; opacity: 0.90; }
.fxp--4  { left: 29%; --fxs: 13px; --sway: -18px; animation-duration: 27s; animation-delay: -20s; opacity: 0.52; }
.fxp--5  { left: 37%; --fxs: 34px; --sway: 24px; animation-duration: 13s; animation-delay: -4s; opacity: 1.00; }
.fxp--6  { left: 45%; --fxs: 19px; --sway: -30px; animation-duration: 21s; animation-delay: -16s; opacity: 0.72; }
.fxp--7  { left: 53%; --fxs: 27px; --sway: 20px; animation-duration: 17s; animation-delay: -12s; opacity: 0.92; }
.fxp--8  { left: 61%; --fxs: 15px; --sway: -26px; animation-duration: 25s; animation-delay: -6s; opacity: 0.58; }
.fxp--9  { left: 69%; --fxs: 22px; --sway: 16px; animation-duration: 19s; animation-delay: -15s; opacity: 0.82; }
.fxp--10 { left: 77%; --fxs: 16px; --sway: -20px; animation-duration: 29s; animation-delay: -24s; opacity: 0.60; }
.fxp--11 { left: 84%; --fxs: 29px; --sway: 28px; animation-duration: 14s; animation-delay: -7s; opacity: 0.96; }
.fxp--12 { left: 91%; --fxs: 18px; --sway: -16px; animation-duration: 23s; animation-delay: -19s; opacity: 0.68; }
.fxp--13 { left: 96%; --fxs: 13px; --sway: 22px; animation-duration: 26s; animation-delay: -11s; opacity: 0.50; }
.fxp--14 { left: 8%; --fxs: 21px; --sway: -28px; animation-duration: 20s; animation-delay: -22s; opacity: 0.76; }

/* ── รูปร่างของเม็ด ──────────────────────────────────────────── */

/** ⭐ เหรียญ — ชุดสีเดียวกับกองชิปบนกระดาน (`.stack__coin`) */
[data-fx-shape="coin"] .fxp > i {
  border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, #fff8, var(--fx1) 45%, var(--fx2));
  box-shadow: inset 0 0 0 2px rgb(255 255 255 / .32), 0 4px 12px rgb(0 0 0 / .35);
}

/** ⭐ ชิปคาสิโน — ขอบขีดรอบวง */
[data-fx-shape="chip"] .fxp > i {
  border-radius: 50%;
  background:
    repeating-conic-gradient(var(--fx1) 0 18deg, var(--fx2) 18deg 36deg);
  box-shadow: inset 0 0 0 22% var(--fx1), inset 0 0 0 26% #fff6, 0 4px 12px rgb(0 0 0 / .35);
}

[data-fx-shape="dot"] .fxp > i {
  border-radius: 50%;
  background: radial-gradient(circle, var(--fx1), var(--fx2) 70%);
  filter: blur(.4px);
}

[data-fx-shape="ring"] .fxp > i {
  border-radius: 50%;
  background: transparent;
  box-shadow: inset 0 0 0 3px var(--fx1), 0 0 10px var(--fx1);
}

/** ⭐ เพชร — สี่เหลี่ยมหมุน 45 องศา */
[data-fx-shape="gem"] .fxp > i {
  border-radius: 14%;
  background: linear-gradient(135deg, var(--fx1), var(--fx2));
  box-shadow: 0 3px 10px rgb(0 0 0 / .4);
  rotate: 45deg;
}

/** ⭐ ดาวสี่แฉก — วาดด้วย clip-path ไม่ต้องมีรูป */
[data-fx-shape="star"] .fxp > i {
  background: radial-gradient(circle, #fff, var(--fx1) 60%, var(--fx2));
  clip-path: polygon(50% 0, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0 50%, 39% 39%);
}

/**
 * ⭐ **ตัวอักษร/อิโมจิเป็นเม็ด** — ตัวที่ทำให้ฉากอย่างสวนสัตว์ ·
 *    ลูกเต๋า · อียิปต์ · แมทริกซ์ เกิดขึ้นได้โดยไม่ต้องวาด SVG หลายสิบชิ้น
 *
 * ⚠️ `line-height: 1` — ไม่งั้นตัวอักษรจะไม่อยู่กลางกล่องที่เราตั้งขนาดไว้
 */
[data-fx-shape="char"] .fxp > i {
  font-size: var(--fxs, 22px);
  line-height: 1;
  text-align: center;
  color: var(--fx1);
  text-shadow: 0 2px 10px var(--fx2);
  font-style: normal;
}

/* ── ทิศทาง ───────────────────────────────────────────────────
   ⚠️ ชื่อต้องตรงกับ `EntryScene::MOTIONS` เป๊ะ
      (`EntrySceneTest` เทียบสองที่ให้)                            */

[data-fx-motion="rise"]  .fxp { animation-name: fxrise; }
[data-fx-motion="fall"]  .fxp { animation-name: fxfall; top: -8%; bottom: auto; }
[data-fx-motion="drift"] .fxp { animation-name: fxdrift; }
[data-fx-motion="zoom"]  .fxp { animation-name: fxzoom; top: 42%; bottom: auto; }
[data-fx-motion="spin"]  .fxp { animation-name: fxorbit; top: 40%; bottom: auto; }
[data-fx-motion="pulse"] .fxp { animation-name: fxpulse; top: 12%; bottom: auto; }

/** ⚠️ ลอยขึ้นแล้วส่ายซ้ายขวา · จางหัวจางท้าย ไม่โผล่มาเต็มตัวแล้วหายวับ */
@keyframes fxrise {
  0%   { transform: translate3d(0, 0, 0) scale(.7); opacity: 0; }
  12%  { opacity: 1; }
  50%  { transform: translate3d(var(--sway, 20px), -58vh, 0) scale(1); }
  88%  { opacity: .8; }
  100% { transform: translate3d(0, -118vh, 0) scale(1); opacity: 0; }
}

@keyframes fxfall {
  0%   { transform: translate3d(0, 0, 0) scale(.8); opacity: 0; }
  12%  { opacity: 1; }
  50%  { transform: translate3d(var(--sway, 20px), 58vh, 0) scale(1); }
  88%  { opacity: .8; }
  100% { transform: translate3d(0, 118vh, 0) scale(1); opacity: 0; }
}

/** ⚠️ ลอยเรื่อย ๆ ไม่ไปไหนไกล — ใช้กับเมฆ หมอก ผีเสื้อ */
@keyframes fxdrift {
  0%   { transform: translate3d(-8vw, -20vh, 0) scale(.9); opacity: 0; }
  15%  { opacity: 1; }
  50%  { transform: translate3d(6vw, -52vh, 0) scale(1.05); }
  85%  { opacity: .85; }
  100% { transform: translate3d(-4vw, -86vh, 0) scale(.95); opacity: 0; }
}

/** ⭐ พุ่งเข้าหาคนดู — สนามดาว · วาร์ป */
@keyframes fxzoom {
  0%   { transform: translate3d(0, 0, 0) scale(.15); opacity: 0; }
  20%  { opacity: 1; }
  100% { transform: translate3d(var(--sway, 20px), 0, 0) scale(3.2); opacity: 0; }
}

/** ⭐ โคจรรอบกลางจอ — รูเล็ต · กาแล็กซี */
@keyframes fxorbit {
  0%   { transform: rotate(0) translateX(30vh) rotate(0) scale(.85); opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: .9; }
  100% { transform: rotate(1turn) translateX(30vh) rotate(-1turn) scale(1); opacity: 0; }
}

/** ⭐ เต้นอยู่กับที่ — จุดบนแผงวงจร */
@keyframes fxpulse {
  0%, 100% { transform: scale(.6); opacity: .25; }
  50%      { transform: scale(1.25); opacity: 1; }
}

@keyframes fxspinY { from { transform: rotateY(0); } to { transform: rotateY(1turn); } }

/**
 * ⚠️ **เม็ดที่เป็นตัวอักษรห้ามหมุนรอบแกนตั้ง** — อิโมจิที่พลิกหลังคือ
 *    ภาพกลับด้านที่อ่านไม่ออก · ให้ส่ายเบา ๆ แทน
 */
[data-fx-shape="char"] .fxp > i { animation-name: fxsway; animation-duration: 5s; }

@keyframes fxsway {
  0%, 100% { transform: rotate(-7deg); }
  50%      { transform: rotate(7deg); }
}

/** ⚠️ เม็ดที่โคจรอยู่แล้วไม่ต้องหมุนซ้อนอีก — เวียนหัวและดูรก */
[data-fx-motion="spin"] .fxp > i,
[data-fx-motion="pulse"] .fxp > i { animation: none; }

/* ══ ชั้นพิเศษ ═══════════════════════════════════════════════════
   ⚠️ ทุกตัว `pointer-events: none` และไม่มีตัวไหนขยับผัง            */

.fxx {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/** ⭐ ฟ้าผ่า — วาบทั้งจอเป็นระยะ */
.fxx--flash {
  background: linear-gradient(180deg, var(--fx1), transparent 55%);
  opacity: 0;
  animation: fxflash 7s ease-out infinite;
}

@keyframes fxflash {
  0%, 88%, 100% { opacity: 0; }
  90%           { opacity: .30; }
  92%           { opacity: .06; }
  94%           { opacity: .22; }
}

/** ⭐ แผงวงจร — เส้นตารางเรืองแสงเลื่อนช้า ๆ */
.fxx--grid {
  opacity: .22;
  background-image:
    linear-gradient(var(--fx1) 1px, transparent 1px),
    linear-gradient(90deg, var(--fx1) 1px, transparent 1px);
  background-size: 46px 46px;
  animation: fxgrid 9s linear infinite;
}

@keyframes fxgrid { to { transform: translate3d(46px, 46px, 0); } }

/** ⭐ สแกนไลน์จอเก่า — วินเทจ · แมทริกซ์ · กลิตช์ */
.fxx--scan {
  opacity: .16;
  background: repeating-linear-gradient(180deg, #000 0 2px, transparent 2px 4px);
  animation: fxscan 8s linear infinite;
}

@keyframes fxscan { to { transform: translate3d(0, 4px, 0); } }

/** ⭐ หมอก — แถบนุ่ม ๆ ลอยผ่าน */
.fxx--fog {
  opacity: .28;
  background:
    radial-gradient(60% 40% at 20% 60%, var(--fx1), transparent 70%),
    radial-gradient(50% 35% at 80% 40%, var(--fx1), transparent 70%);
  filter: blur(26px);
  animation: fxfog 26s ease-in-out infinite alternate;
}

@keyframes fxfog {
  from { transform: translate3d(-6%, 0, 0) scale(1); }
  to   { transform: translate3d(6%, -3%, 0) scale(1.12); }
}

/** ⭐ ลำแสงหมุน — ช้ามากโดยตั้งใจ ไม่ให้แย่งสายตากับปุ่ม */
.fxx--rays {
  opacity: .26;
  inset: -60% -60% auto -60%;
  height: 160%;
  background: conic-gradient(from 0deg at 50% 50%,
    transparent 0deg, var(--fx1) 22deg, transparent 46deg,
    transparent 160deg, var(--fx2) 184deg, transparent 208deg);
  animation: rays 52s linear infinite;
}

/** ⚠️ ขอบมืด — ดึงสายตาเข้ากลางจอที่การ์ดอยู่ · ไม่มี animation โดยตั้งใจ */
.fxx--vignette {
  background: radial-gradient(75% 60% at 50% 45%, transparent 40%, rgb(0 0 0 / .55));
}

/**
 * 🔴 **จอเตี้ยมาก (มือถือแนวนอน) ต้องลดของลง**
 *    ความสูงเหลือ ~300px แต่การ์ดกินเกือบทั้งจอ — ของเยอะจะบัง
 */
@media (max-height: 460px) {
  .fxp--2, .fxp--4, .fxp--6, .fxp--8, .fxp--10, .fxp--12, .fxp--14 { display: none; }
  .fxx--fog, .fxx--rays { opacity: .14; }
}

/**
 * 🔴 **คอนทราสต์สูง = ปิดฉากทั้งหมด**
 *    คนที่เปิดโหมดนี้คือคนที่อ่านยากอยู่แล้ว · ของวิ่งหลังตัวหนังสือ
 *    ทำให้แย่ลง ไม่ใช่ดีขึ้น
 */
:root[data-hc="1"] .entry__fx,
:root[data-hc="1"] .fxx { display: none; }


/* ═══════════════════════════════════════════════════════════════
   ⭐ เครื่องยนต์ฉากหลัง รุ่นที่ 2 — ทิศทาง/รูปเม็ด/ชั้นพิเศษชุดใหม่
   ───────────────────────────────────────────────────────────────
   ⭐ เจ้าของขอ: ไฟวิ่ง · ไฟวิ่งตามวงจร · สายฟ้าฟาดโหด ๆ · ผีน่ากลัว ·
      การ์ตูนน่ารัก — ทั้งหมดต้องการ **ทิศทางกับรูปเม็ดที่ยังไม่มี**
      ไม่ใช่แค่เปลี่ยนสีของฉากเดิม (2026-09-01)

   🔴 **ทุก keyframe ยังเป็น `transform`/`opacity` ล้วน**
      (`EntryMotionTest` ไล่ตรวจทั้งไฟล์ · ขยับ top/left = แดงทันที)

   ⚠️ ทุกอย่างที่เพิ่มที่นี่ ต้องมีคู่ของมันใน `app.css` (การ์ดพรีวิว)
      ไม่งั้นลูกค้าเลือกจากภาพที่ไม่ตรงกับของจริง
   ═══════════════════════════════════════════════════════════════ */

/* ── ทิศทางใหม่ ─────────────────────────────────────────────── */

[data-fx-motion="chase"]  .fxp { animation-name: fxchase; left: -8%; }
[data-fx-motion="zigzag"] .fxp { animation-name: fxzigzag; top: -8%; bottom: auto; }
[data-fx-motion="swirl"]  .fxp { animation-name: fxswirl; top: 42%; bottom: auto; }
[data-fx-motion="bounce"] .fxp { animation-name: fxbounce; top: 18%; bottom: auto; }
[data-fx-motion="wave"]   .fxp { animation-name: fxwave; }
[data-fx-motion="strike"] .fxp { animation-name: fxstrike; top: -10%; bottom: auto; }

/**
 * ⭐ **ไฟวิ่ง** — พุ่งขวางจอเป็นเส้น เหมือนไฟวิ่งบนป้ายหน้าร้าน
 *
 * ⚠️ ยืดตัวตอนวิ่ง (`scaleX`) แล้วหดตอนจบ — ได้ความรู้สึก "เร็ว"
 *    โดยไม่ต้องวาดหางแยกอีกชิ้น
 */
@keyframes fxchase {
  0%   { transform: translate3d(0, 0, 0) scaleX(.6); opacity: 0; }
  8%   { opacity: 1; }
  50%  { transform: translate3d(60vw, var(--sway, 0), 0) scaleX(2.2); }
  92%  { opacity: .9; }
  100% { transform: translate3d(122vw, 0, 0) scaleX(.6); opacity: 0; }
}

/** ⭐ ตกแบบหักซ้ายขวา — ใบไม้ · เศษกระดาษ · หิมะที่มีลม */
@keyframes fxzigzag {
  0%   { transform: translate3d(0, 0, 0) rotate(0); opacity: 0; }
  10%  { opacity: 1; }
  25%  { transform: translate3d(var(--sway, 24px), 28vh, 0) rotate(20deg); }
  50%  { transform: translate3d(calc(var(--sway, 24px) * -1), 56vh, 0) rotate(-20deg); }
  75%  { transform: translate3d(var(--sway, 24px), 84vh, 0) rotate(14deg); }
  90%  { opacity: .8; }
  100% { transform: translate3d(0, 118vh, 0) rotate(0); opacity: 0; }
}

/** ⭐ หมุนวนออกจากกลางจอ — กาแล็กซี · น้ำวน · เวทมนตร์ */
@keyframes fxswirl {
  0%   { transform: rotate(0) translateX(2vh) rotate(0) scale(.4); opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: .85; }
  100% { transform: rotate(1.5turn) translateX(46vh) rotate(-1.5turn) scale(1.1); opacity: 0; }
}

/** ⭐ เด้งอยู่กับที่ — การ์ตูนน่ารัก · ของเล่น */
@keyframes fxbounce {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  25%      { transform: translate3d(0, -14px, 0) scale(1.08); }
  50%      { transform: translate3d(0, 0, 0) scale(.94); }
  75%      { transform: translate3d(0, -7px, 0) scale(1.04); }
}

/** ⭐ ลอยเป็นคลื่น — ใต้ทะเล · ผี · ควัน */
@keyframes fxwave {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  10%  { opacity: 1; }
  25%  { transform: translate3d(var(--sway, 24px), -30vh, 0); }
  50%  { transform: translate3d(calc(var(--sway, 24px) * -1), -58vh, 0); }
  75%  { transform: translate3d(var(--sway, 24px), -86vh, 0); }
  90%  { opacity: .75; }
  100% { transform: translate3d(0, -118vh, 0); opacity: 0; }
}

/**
 * ⭐ **สายฟ้าฟาด** — โผล่แล้วฟาดลงเร็วมาก แล้วหายไป
 *
 * 🔴 ช่วง "นิ่ง" ยาวกว่าช่วง "ฟาด" มาก — ฟ้าผ่าที่ผ่าตลอดเวลา
 *    ไม่ใช่ฟ้าผ่า มันคือไฟกะพริบ · ความน่ากลัวมาจากการรอ
 */
@keyframes fxstrike {
  0%, 62%  { transform: translate3d(0, 0, 0) scaleY(.2); opacity: 0; }
  66%      { transform: translate3d(0, 18vh, 0) scaleY(1); opacity: 1; }
  70%      { transform: translate3d(0, 52vh, 0) scaleY(1.3); opacity: 1; }
  76%      { transform: translate3d(0, 92vh, 0) scaleY(.8); opacity: 0; }
  100%     { transform: translate3d(0, 92vh, 0) scaleY(.2); opacity: 0; }
}

/* ── รูปเม็ดใหม่ ─────────────────────────────────────────────── */

/** ⭐ สายฟ้า — วาดด้วย clip-path ไม่ต้องมีรูป */
[data-fx-shape="bolt"] .fxp > i {
  background: linear-gradient(180deg, #fff, var(--fx1) 55%, var(--fx2));
  clip-path: polygon(58% 0, 22% 52%, 46% 52%, 34% 100%, 78% 42%, 52% 42%, 68% 0);
  filter: drop-shadow(0 0 6px var(--fx1));
}

/**
 * ⭐ **ผี** — หัวมน ชายกระโปรงหยัก
 *
 * ⚠️ ตาเป็น `box-shadow` สองจุด ไม่ใช่อิลิเมนต์เพิ่ม — เม็ดมี 14 ตัว
 *    เพิ่มลูกอีกสองตัวต่อเม็ดคือ 28 อิลิเมนต์ที่ไม่ได้ให้อะไรเพิ่ม
 */
[data-fx-shape="ghost"] .fxp > i {
  background: var(--fx1);
  border-radius: 50% 50% 12% 12% / 60% 60% 18% 18%;
  box-shadow:
    inset -22% -18% 0 -14% var(--fx2),
    inset 22% -18% 0 -14% var(--fx2),
    0 0 14px var(--fx1);
  opacity: .9;
}

/** ⭐ แท่งไฟ — ใช้กับไฟวิ่ง · ป้ายนีออน */
[data-fx-shape="bar"] .fxp > i {
  border-radius: 999px;
  background: linear-gradient(90deg, transparent, var(--fx1) 45%, #fff 55%, transparent);
  box-shadow: 0 0 10px var(--fx1), 0 0 22px var(--fx2);
  height: 34%;
  margin-top: 33%;
}

/** ⭐ หัวใจ — แนวการ์ตูนน่ารัก */
[data-fx-shape="heart"] .fxp > i {
  background: radial-gradient(circle at 34% 30%, #fff6, var(--fx1) 55%, var(--fx2));
  clip-path: path('M12 21s-9-5.5-9-12a5 5 0 0 1 9-3 5 5 0 0 1 9 3c0 6.5-9 12-9 12z');
  filter: drop-shadow(0 2px 6px var(--fx2));
}

/** ⭐ ประกายไฟ — จุดสว่างที่มีรัศมี ใช้กับวงจร · เวทมนตร์ */
[data-fx-shape="spark"] .fxp > i {
  border-radius: 50%;
  background: radial-gradient(circle, #fff, var(--fx1) 45%, transparent 72%);
  box-shadow: 0 0 12px var(--fx1), 0 0 26px var(--fx2);
}

/* ── ชั้นพิเศษใหม่ ──────────────────────────────────────────── */

/**
 * ⭐ **ไฟวิ่งรอบขอบจอ** — เหมือนป้ายไฟหน้าร้าน
 *
 * 🔴 ใช้ `mask` เจาะไส้ออกเหมือนขอบการ์ด — วางกล่องทึบทับแปลว่า
 *    ต้องมีพื้นมาปิดตรงกลาง ซึ่งจะบังทั้งหน้า
 */
.fxx--chase {
  padding: 3px;
  opacity: .8;
  background: conic-gradient(from 0deg,
    transparent 0%, var(--fx1) 6%, #fff 9%, var(--fx1) 12%, transparent 20%,
    transparent 50%, var(--fx2) 56%, #fff 59%, var(--fx2) 62%, transparent 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: rays 3.5s linear infinite;
}

/** ⭐ แฟลชโหด — แรงและถี่กว่า `flash` ใช้กับฉากพายุจัด */
.fxx--strobe {
  background: linear-gradient(180deg, #fff, var(--fx1) 40%, transparent 70%);
  opacity: 0;
  animation: fxstrobe 4.5s steps(1, end) infinite;
}

@keyframes fxstrobe {
  0%, 70%, 100% { opacity: 0; }
  72%           { opacity: .55; }
  74%           { opacity: 0; }
  76%           { opacity: .38; }
  78%           { opacity: 0; }
  86%           { opacity: .5; }
  88%           { opacity: 0; }
}

/** ⭐ จอหลอดเก่า — เส้นสแกน + ขอบมืด + เรืองแสงอ่อน ๆ */
.fxx--crt {
  opacity: .3;
  background:
    repeating-linear-gradient(180deg, #000 0 2px, transparent 2px 4px),
    radial-gradient(70% 60% at 50% 50%, transparent 45%, #000 100%);
  animation: fxscan 8s linear infinite;
}

/** ⭐ ควัน — เข้มกว่าหมอก เคลื่อนช้ากว่า ใช้กับฉากผี */
.fxx--smoke {
  opacity: .42;
  background:
    radial-gradient(55% 38% at 25% 75%, var(--fx2), transparent 72%),
    radial-gradient(45% 32% at 75% 60%, var(--fx2), transparent 72%);
  filter: blur(34px);
  animation: fxfog 34s ease-in-out infinite alternate;
}

/** ⭐ กรอบนีออน — เรืองแสงนิ่ง ๆ ไม่วิ่ง (ต่างจาก `chase`) */
.fxx--neon {
  padding: 2px;
  opacity: .75;
  background: linear-gradient(135deg, var(--fx1), var(--fx2), var(--fx1));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: fxneon 3.2s ease-in-out infinite;
}

@keyframes fxneon {
  0%, 100% { opacity: .55; }
  50%      { opacity: .95; }
}

/**
 * ⭐ ดาวนิ่งเต็มฟ้า — เป็นฉากหลัง ไม่ใช่เม็ดที่วิ่ง
 * ⚠️ **ไม่มี animation โดยตั้งใจ** — ดาวที่กะพริบทั้งฟ้าคือจอเสีย ไม่ใช่ท้องฟ้า
 */
.fxx--stars {
  opacity: .5;
  background-image:
    radial-gradient(1.5px 1.5px at 12% 18%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 34% 42%, #fff, transparent),
    radial-gradient(1px 1px at 58% 22%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 74% 56%, #fff, transparent),
    radial-gradient(1px 1px at 88% 30%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 22% 72%, #fff, transparent),
    radial-gradient(1px 1px at 46% 84%, #fff, transparent),
    radial-gradient(1.5px 1.5px at 66% 90%, #fff, transparent);
}

/**
 * ⭐ ลำแสงกวาด — เหมือนไฟส่องเวที
 *
 * 🔴 `beam` เคยประกาศไว้ใน `EntryScene::EXTRAS` แต่**ไม่เคยมี CSS**
 *    ฉากไหนเลือกไปก็ได้ชั้นว่างเปล่า · เติมให้ครบตอนขยายเครื่องยนต์
 */
.fxx--beam {
  opacity: .3;
  inset: -30% -50% auto -50%;
  height: 140%;
  background: conic-gradient(from 200deg at 50% 0%,
    transparent 0deg, var(--fx1) 14deg, transparent 30deg);
  filter: blur(6px);
  animation: fxbeam 9s ease-in-out infinite alternate;
}

@keyframes fxbeam {
  from { transform: rotate(-16deg); }
  to   { transform: rotate(16deg); }
}

/* ═══ แผงชีพจรโต๊ะ (โมดูล `weblive_pulsepanel`) ══════════════════
   ⭐ ใช้โทเคนของธีมล้วน ๆ (--line · --ink · --tiger …) — สีตอกไว้เมื่อไหร่
      ธีมสว่างและธีมสีอื่นจะอ่านไม่ออกทันที (มี 160 ธีมในระบบ)
   ⚠️ ทุกอนิเมชันขยับแค่ transform/opacity/filter — ห้ามแตะ width/top/left
   ═══════════════════════════════════════════════════════════════ */
.pp {
  display: grid;
  gap: 7px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface, linear-gradient(135deg, var(--panel), var(--panel-2)));
}

.pp__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.pp__title {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: calc(13px * var(--fs, 1)); font-weight: 700; color: var(--ink-dim);
}

/* ⭐ จุดหายใจ — บอกว่าตัวเลขยังไหลอยู่ ไม่ใช่ค้างอยู่ */
.pp__live {
  width: 7px; height: 7px; border-radius: 999px;
  background: var(--tie, #22c55e);
  animation: pp-breathe 2.4s ease-in-out infinite;
}

@keyframes pp-breathe {
  0%, 100% { opacity: .35; transform: scale(.8); }
  50%      { opacity: 1;   transform: scale(1.15); }
}

.pp__tempo {
  padding: 2px 9px; border-radius: 999px;
  font-size: calc(11px * var(--fs, 1)); font-weight: 800;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  color: var(--ink-faint);
  transition: color .3s ease, background-color .3s ease;
}

.pp__tempo--warm  { color: var(--gold, #eab308);   background: color-mix(in srgb, var(--gold, #eab308) 16%, transparent); }
.pp__tempo--hot   { color: var(--dragon, #ef4444); background: color-mix(in srgb, var(--dragon, #ef4444) 16%, transparent); }
.pp__tempo--blaze {
  color: var(--dragon, #ef4444);
  background: color-mix(in srgb, var(--dragon, #ef4444) 24%, transparent);
  animation: pp-throb 1.1s ease-in-out infinite;
}

@keyframes pp-throb { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.35); } }

.pp__kpis { display: flex; gap: 6px; }

.pp__kpi {
  flex: 1; display: grid; justify-items: center; gap: 1px;
  padding: 4px 2px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}

.pp__kpi b { font-size: calc(16px * var(--fs, 1)); font-weight: 800; font-variant-numeric: tabular-nums; }
.pp__kpi small { font-size: calc(10px * var(--fs, 1)); color: var(--ink-faint); }

.pp__bars { display: grid; gap: 5px; }

.pp__bar {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "top top" "track val";
  align-items: center;
  gap: 2px 8px;
}

.pp__barTop { grid-area: top; display: flex; justify-content: space-between; gap: 8px; }
.pp__barName { font-size: calc(11px * var(--fs, 1)); color: var(--ink-dim); }
.pp__barPct  { font-size: calc(11px * var(--fs, 1)); color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.pp__barVal  { grid-area: val; font-size: calc(12px * var(--fs, 1)); font-weight: 700; font-variant-numeric: tabular-nums; }

.pp__track {
  grid-area: track;
  position: relative;
  display: block; height: 9px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--ink) 10%, transparent);
}

/* 🔴 ยืดด้วย scaleX จากขอบซ้าย — origin ผิด = แท่งโตออกจากกลางเหมือนยางยืด */
.pp__fill,
.pp__glow {
  position: absolute; inset: 0;
  transform-origin: left center;
  transform: scaleX(0);
  border-radius: 999px;
}

.pp__fill {
  background: linear-gradient(90deg,
              color-mix(in srgb, var(--tiger, #3b6fe0) 55%, transparent),
              var(--tiger, #3b6fe0));
  transition: transform .45s cubic-bezier(.22, 1, .36, 1);
}

/* ⭐ ประตูที่คนลงหนักสุดของรอบ — เปลี่ยนโทน ไม่ใช่แค่ยาวกว่า */
.pp__bar.is-lead .pp__fill {
  background: linear-gradient(90deg,
              color-mix(in srgb, var(--gold, #eab308) 45%, transparent),
              var(--gold, #eab308));
}

/* ⭐ วาบครั้งเดียวตอนเงินเพิ่งเข้า — `is-hit` ถูกถอดแล้วใส่ใหม่ทุกครั้ง */
.pp__glow { background: color-mix(in srgb, #fff 65%, transparent); opacity: 0; }
.pp__glow.is-hit { animation: pp-hit .5s ease-out; }

@keyframes pp-hit {
  0%   { opacity: .55; transform: scaleX(1); }
  100% { opacity: 0;   transform: scaleX(1); }
}

.pp__rail { display: flex; gap: 3px; align-items: center; min-height: 12px; }

.pp__dot { width: 9px; height: 9px; border-radius: 999px; flex: 0 0 auto; }
.pp__dot--tiger  { background: var(--tiger, #3b6fe0); }
.pp__dot--dragon { background: var(--dragon, #ef4444); }
.pp__dot--tie    { background: var(--tie, #22c55e); }
.pp__dot--gold   { background: var(--gold, #eab308); }
.pp__dot--muted  { background: color-mix(in srgb, var(--ink) 30%, transparent); }

/* ⭐ รอบล่าสุดเลื่อนเข้ามาจากขวา — สื่อว่า "รอบใหม่มาแล้ว" */
.pp__dot.is-new { animation: pp-in .45s cubic-bezier(.22, 1, .36, 1); }

@keyframes pp-in {
  from { opacity: 0; transform: translateX(10px) scale(.6); }
  to   { opacity: 1; transform: none; }
}

.pp__note { margin: 0; font-size: calc(11px * var(--fs, 1)); color: var(--ink-faint); line-height: 1.4; }
.pp__note b { color: var(--ink-dim); }

/* 🔴 คนที่ตั้ง "ลดการเคลื่อนไหว" ต้องได้จอที่นิ่งจริง ไม่ใช่แค่ช้าลง */
@media (prefers-reduced-motion: reduce) {
  .pp__live,
  .pp__tempo--blaze,
  .pp__glow.is-hit,
  .pp__dot.is-new { animation: none; }

  .pp__fill { transition: none; }
}

/* ══ แถบสมาชิกเหนือไลฟ์ · ปุ่มเครื่องมือ · กล่องผลย้อนหลัง ═══════
   (เจ้าของสั่งรื้อผัง 2026-09-18)

   🔴 **`.live` มีลูกสามตัวแล้ว** — `.livetop` · `.live__stage` · `.live__panel`
      คำเตือนเดิมในไฟล์นี้ยังจริงทุกตัวอักษร: ลูกตัวที่สามที่ไม่มีใคร
      ประกาศแถวให้ จะถูกดันไปแถวที่อยู่นอก `height: 100dvh` แล้วโดน
      `overflow: hidden` ตัดหายทั้งก้อน (เคยทำกระดานหายมาสองครั้ง)

      ทางแก้คือ **ประกาศแถวให้ตรง ๆ ทุกผัง** ผ่านคลาส `live--bar`
      ที่ PHP ใส่มาให้ ไม่ใช่ปล่อยให้กริดเดา                              */

.live--bar { grid-template-rows: auto auto 1fr; }

/* ⚠️ รางข้อความวิ่งเป็นลูกตัวที่สี่ — ต้องมีแถวของมันเองด้วย */
.live--bar:has(> [data-chat-mode="ticker"]) { grid-template-rows: auto auto auto 1fr; }

.livetop {
  position: relative;
  z-index: 3;
  padding: 8px 10px 0;
}

/**
 * ⭐ **แถบสมาชิกเลื่อนเข้าตอนเปิดหน้า** — ของชิ้นแรกที่ผู้เล่นเห็น
 *    ควรบอกว่าหน้าพร้อมแล้ว ไม่ใช่โผล่มาเฉย ๆ
 */
.livetop > * { animation: liveTopIn .4s cubic-bezier(.2, .9, .3, 1); }

@keyframes liveTopIn {
  from { transform: translateY(-8px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* ── ปุ่มเครื่องมือในแถบสมาชิก ─────────────────────────────── */

.ptool {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  min-height: 36px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: linear-gradient(135deg, var(--panel), var(--panel-2));
  color: var(--ink-dim);
  font: inherit;
  font-size: .84em;
  cursor: pointer;
  touch-action: manipulation;
  transition: color .16s ease, border-color .16s ease, transform .12s ease;
}

.ptool:hover  { color: var(--accent); border-color: var(--accent); }
.ptool:active { transform: scale(.94); }

.ptool__ico {
  width: 16px;
  height: 16px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ptool__ico circle { fill: currentColor; stroke: none; }

/**
 * ⭐ **เส้นกราฟวิ่งตอนโต๊ะมีผลใหม่** — `live-road.js` เติมคลาส `is-new`
 *    ให้ตอนแถวผลเปลี่ยน · บอกว่า "มีอะไรให้ดูแล้ว" โดยไม่ต้องกางกล่องทิ้งไว้
 */
.ptool--fresh .ptool__line {
  stroke-dasharray: 26;
  animation: ptoolDraw 1.1s ease-out 2;
}

@keyframes ptoolDraw {
  from { stroke-dashoffset: 26; }
  to   { stroke-dashoffset: 0; }
}

.ptool--fresh .ptool__dot { animation: ptoolPing 1.1s ease-out 2; }

@keyframes ptoolPing {
  0%   { transform: scale(.4); opacity: 0; }
  40%  { transform: scale(1);  opacity: 1; }
  100% { transform: scale(1);  opacity: 1; }
}

.ptool--more { padding: 0 8px; }

/* ── เมนูบัญชี ─────────────────────────────────────────────── */

.pmenu { position: relative; flex: none; }

.pmenu__list {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 186px;
  padding: 5px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  box-shadow: var(--shadow);
  animation: pmenuIn .18s ease-out;
}

.pmenu__list[hidden] { display: none; }

@keyframes pmenuIn {
  from { transform: translateY(-6px) scale(.98); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

.pmenu__item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 38px;
  padding: 0 10px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: .88em;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.pmenu__item:hover { background: var(--veil); color: var(--accent); }
.pmenu__ico { font-size: 1em; }
.pmenu__out { margin-top: 4px; border-top: 1px solid var(--line); padding-top: 4px; }
.pmenu__item--out { color: var(--ink-dim); }
.pmenu__item--out:hover { color: var(--danger); }

/* ── กล่องผลย้อนหลัง ───────────────────────────────────────── */

/**
 * 🔴 **เคยเป็นกล่องที่กางอยู่กลางแผงตลอดเวลา** สูง ~110px ทั้งที่ผู้เล่น
 *    อ่านเป็นช่วง ๆ · เจ้าของสั่งเปลี่ยนเป็นปุ่ม (2026-09-18)
 *    พื้นที่ที่คืนมาคือพื้นที่ของกระดาน ซึ่งเป็นของที่แตะจริงทุกวินาที
 */
.roadbox {
  position: fixed;
  inset: 0;
  z-index: 65;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(2, 6, 12, 0);
  transition: background .2s ease;
}

.roadbox[hidden] { display: none; }
.roadbox.is-open { background: rgba(2, 6, 12, .6); }

.roadbox__sheet {
  position: relative;
  width: min(620px, 100%);
  max-height: 84dvh;
  overflow-y: auto;
  padding: 14px 12px calc(14px + var(--safe-b));
  border: 1px solid var(--line);
  border-radius: 20px 20px 0 0;
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  box-shadow: var(--shadow);
  transform: translateY(100%);
  transition: transform .26s cubic-bezier(.2, .9, .3, 1);
}

.roadbox.is-open .roadbox__sheet { transform: none; }

/**
 * 🔴 **ปุ่มปิดอยู่ในแถวหัว ไม่ลอยทับเนื้อหาอีกแล้ว** (เจ้าของแจ้ง 2026-09-19)
 *    ของเดิม `position: absolute` มุมขวาบน ไปทับแท็บเลือกมุมมองพอดี
 *    จนกดแท็บขวาสุดไม่ได้
 */
.roadbox__bar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.roadbox__title { font-size: 1.02em; }
.roadbox__grow { flex: 1; }

.roadbox__close {
  flex: none;
  width: 32px;
  height: 32px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--panel);
  color: var(--ink-dim);
  font: inherit;
  cursor: pointer;
}

/** ⚠️ ในกล่องลอยไม่ต้องมีกรอบซ้อนอีกชั้น — กล่องเป็นกรอบให้แล้ว */
.roadbox .road {
  border: 0;
  background: transparent;
  padding: 0;
}

@media (prefers-reduced-motion: reduce) {
  .livetop > *,
  .pmenu__list,
  .ptool--fresh .ptool__line,
  .ptool--fresh .ptool__dot { animation: none; }

  .roadbox__sheet { transition: none; }
}

/* ── ปุ่มติดต่อพนักงานบนหน้าเข้าระบบ ────────────────────────────
   🔴 **ไม่ทึบ และอยู่คนละบรรทัดกับปุ่มเข้าระบบ** — ปุ่มหลักคือทางที่คน
      ส่วนใหญ่มา ตัวนี้เป็นทางออกของคนที่ติดปัญหา ต้องหาเจอแต่ไม่แย่งสายตา */

.entry__contact {
  margin-top: 10px;
  gap: 8px;
  border-color: color-mix(in srgb, var(--accent) 34%, var(--line));
}

.entry__contact-ico {
  width: 19px;
  height: 19px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linejoin: round;
}

/** ⭐ จุดสามจุดกระพริบไล่กัน — อ่านได้ทันทีว่า "มีคนตอบอยู่" */
.entry__contact-dot { fill: currentColor; stroke: none; animation: entryTyping 1.4s ease-in-out infinite; }
.entry__contact-dot:nth-of-type(2) { animation-delay: .18s; }
.entry__contact-dot:nth-of-type(3) { animation-delay: .36s; }

@keyframes entryTyping {
  0%, 60%, 100% { opacity: .35; transform: translateY(0); }
  30%           { opacity: 1;   transform: translateY(-1.5px); }
}

@media (prefers-reduced-motion: reduce) {
  .entry__contact-dot { animation: none; opacity: .8; }
}

/* ── แถบสมาชิกบนจอ PC ────────────────────────────────────────
   🔴 **วัดจริงที่ 1440×900 แล้วพังเป๊ะตามที่ไฟล์นี้เตือนไว้**

      ผัง PC เป็น `grid-template-rows: 1fr` + สองคอลัมน์ · พอมีลูกตัวที่สาม
      กริดแจกให้ **แถบสมาชิก = คอลัมน์ซ้าย · เวที = คอลัมน์ขวา**
      แล้ว **แผงเล่นตกไปแถวที่สองซึ่งอยู่นอก `height: 100dvh`**
      → วัดได้ `panelTop = 803` คือหลุดพ้นขอบล่างของจอไปทั้งแผง
      (กระดาน · ถาดเหรียญ · ช่องพิมพ์ หายหมดในครั้งเดียว)

   ⭐ ทางแก้เดียวกับที่รางข้อความวิ่งเคยใช้: **ประกาศพื้นที่ให้ตรง ๆ**
      ไม่ใช่หวังว่ากริดจะเดาถูก                                            */

@media (min-width: 1280px) {
  .live--bar {
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-columns: minmax(0, 2fr) minmax(400px, 1fr);
  }

  /** ⭐ แถบสมาชิกพาดเต็มความกว้าง — เป็นของคนคนเดียว ไม่ใช่ของคอลัมน์ใดคอลัมน์หนึ่ง */
  .live--bar > .livetop { grid-area: 1 / 1 / 2 / -1; }

  /** ⚠️ ต้องปลด `height: 100dvh` ของเวที ไม่งั้นมันสูงเต็มจอแล้วดันแถวล้น */
  .live--bar > .live__stage { grid-area: 2 / 1; height: auto; }
  .live--bar > .live__panel { grid-area: 2 / 2; }

  /* รางข้อความวิ่ง = ลูกตัวที่สี่ · ต้องมีพื้นที่ของตัวเองใต้เวที */
  .live--bar:has(> [data-chat-mode="ticker"]) {
    grid-template-rows: auto minmax(0, 1fr) auto;
  }

  .live--bar:has(> [data-chat-mode="ticker"]) > .live__stage { grid-area: 2 / 1; height: auto; }
  .live--bar:has(> [data-chat-mode="ticker"]) > [data-chat-mode="ticker"] { grid-area: 3 / 1; }
  .live--bar:has(> [data-chat-mode="ticker"]) > .live__panel { grid-area: 2 / 2 / -1 / 3; }
}

/* ── กล่องสกอร์ — เนื้อหาต้องไม่ชนขอบกล่อง ─────────────────────
   🔴 เจ้าของแจ้ง 2026-09-19 "ล้นกรอบ ติดขอบ Popup"
      ตาราง/เค้าไพ่กว้างเกินกล่องได้เสมอ (ขอนยาว) — ต้องเลื่อนใน
      กล่องของตัวเอง ไม่ใช่ดันกล่องจนล้น                              */

.roadbox__sheet { padding: 14px 14px calc(16px + var(--safe-b)); }

/** ⚠️ ในกล่องลอยไม่ต้องมีกรอบซ้อนอีกชั้น — กล่องเป็นกรอบให้แล้ว */
.roadbox .road {
  border: 0;
  background: transparent;
  padding: 0;
  overflow: visible;
}

/** ⚠️ แท็บขึ้นบรรทัดใหม่ได้เมื่อจอแคบ — เกม 4 มีถึง 5 แท็บ */
.roadbox .road__head { flex-wrap: wrap; row-gap: 6px; margin-bottom: 10px; }
.roadbox .road__tabs { flex-wrap: wrap; gap: 6px; }

/**
 * ⚠️ **ของกว้างเกินต้องเลื่อนในตัวเอง** — ตารางขอนยาวกับเค้าไพ่
 *    กว้างกว่ากล่องได้เสมอ · ปล่อยไว้คือมันดันขอบกล่องจนล้นออกนอกจอ
 */
.roadbox .road__grid,
.roadbox .road__big,
.roadbox .road__track,
.roadbox .road__derived {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* ── แผงควบคุมโต๊ะของทีมงาน ─────────────────────────────────────
   ⭐ เจ้าของสั่ง 2026-09-19 — ทีมงานที่ร้านเปิดสิทธิ์ให้คุมโต๊ะจากหน้าโต๊ะได้
      โดยไม่ต้องเข้าหลังบ้านหรือพิมพ์ในกลุ่มไลน์

   🔴 เป็นกล่องลอย **นอก** `.live__panel` เหมือนกล่องสกอร์ — แผงเป็นกริด
      ที่นับลูกของมัน เพิ่มลูกเมื่อไรผังเลื่อนทั้งแถว (โดนมาแล้วสามครั้ง)
      และกล่องลอยยังใช้ได้ทั้งกระดาน "แตะ" และ "พิมพ์" โดยไม่ต้องทำสองชุด */

/** ⭐ ปุ่มเปิดแผง — สีต่างจากปุ่มอื่นในราง เพราะไม่ใช่ปุ่มของผู้เล่น */
.ptool--ctl {
  border-color: color-mix(in srgb, var(--danger) 55%, var(--line));
  color: color-mix(in srgb, var(--danger) 70%, var(--ink));
}

.ptool--ctl:hover { border-color: var(--danger); color: var(--danger); }

.ctlbox {
  position: fixed;
  inset: 0;
  z-index: 66;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(2, 6, 12, 0);
  transition: background .2s ease;
}

.ctlbox[hidden] { display: none; }
.ctlbox.is-open { background: rgba(2, 6, 12, .62); }

/** ⚠️ ระหว่างยิงคำขอ ทั้งแผงต้องกดไม่ได้ — กดซ้ำสองทีคือเปิดสองรอบ */
.ctlbox.is-busy .ctlbox__sheet { pointer-events: none; opacity: .72; }

.ctlbox__sheet {
  position: relative;
  width: min(620px, 100%);
  max-height: 86dvh;
  overflow-y: auto;
  padding: 14px 14px calc(16px + var(--safe-b));
  border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--line));
  border-radius: 20px 20px 0 0;
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  box-shadow: var(--shadow);
  transform: translateY(100%);
  transition: transform .26s cubic-bezier(.2, .9, .3, 1);
}

.ctlbox.is-open .ctlbox__sheet { transform: none; }

.ctlbox__bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.ctlbox__title { font-size: 1.02em; }
.ctlbox__grow { flex: 1; }

.ctlbox__state {
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--ink-dim);
  font-size: .82em;
}

.ctlbox__close {
  flex: none;
  width: 32px;
  height: 32px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--panel);
  color: var(--ink-dim);
  font: inherit;
  cursor: pointer;
}

/** ⚠️ ป้ายหมวดอยู่ซ้าย ปุ่มอยู่ขวา — คนคุมโต๊ะกวาดตาหาหมวดก่อนหาปุ่ม */
.ctl__row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
  border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}

.ctl__row:first-child { border-top: 0; }

.ctl__lab {
  flex: none;
  width: 68px;
  padding-top: 7px;
  color: var(--ink-dim);
  font-size: .88em;
}

.ctl__btns {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  flex: 1;
}

.ctl__btn {
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: transform .12s ease, border-color .12s ease;
}

.ctl__btn:active { transform: scale(.96); }

/**
 * 🔴 **ปุ่มที่ทำไม่ได้ต้องจาง ไม่ใช่หายไป** — ปุ่มที่หาย ๆ โผล่ ๆ
 *    ทำให้คนคุมโต๊ะกดผิดตำแหน่ง เพราะปุ่มเลื่อนที่ทุกครั้งที่สถานะเปลี่ยน
 */
.ctl__btn[disabled] { opacity: .34; cursor: not-allowed; }

.ctl__btn--go   { border-color: color-mix(in srgb, var(--tie) 55%, var(--line)); }
.ctl__btn--stop { border-color: color-mix(in srgb, var(--gold) 55%, var(--line)); }
.ctl__btn--pick { border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }
.ctl__btn--undo { border-color: var(--line); color: var(--ink-dim); }

/** 🔴 ปุ่มเดียวในชุดที่ย้ายเงินจริง — ต้องดูไม่เหมือนปุ่มอื่นเลย */
.ctl__btn--pay {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 16%, var(--panel));
  color: var(--ink);
}

.ctl__wait,
.ctl__none {
  margin: 0;
  color: var(--ink-dim);
  font-size: .9em;
}

/* ── โพยของผู้เล่นทุกคนในรอบ ───────────────────────────────── */

.ctlbets {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.ctlbets__bar {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.ctlbets__title { font-size: .98em; }

.ctlbets__sum {
  color: var(--ink-dim);
  font-size: .82em;
  font-variant-numeric: tabular-nums;
}

/** ⚠️ รายชื่อยาวได้เป็นสิบคน — เลื่อนในตัวเอง ไม่ดันกล่องจนล้นจอ */
.ctlbets__list {
  max-height: 38dvh;
  overflow-y: auto;
  overscroll-behavior-y: contain;
}

.ctlbets__row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid color-mix(in srgb, var(--line) 50%, transparent);
}

.ctlbets__row:first-child { border-top: 0; }

.ctlbets__who {
  flex: none;
  width: 34%;
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .9em;
}

.ctlbets__bets {
  flex: 1;
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
}

.ctlbets__bets li {
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-2);
  font-size: .82em;
}

.ctlbets__bets span { color: var(--accent); font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .ctlbox__sheet { transition: none; }
}

/* ── กระดานกดออกผลในแผงควบคุม ─────────────────────────────────
   🔴 **เจ้าของแจ้ง 2026-09-19 "ทำไมออกผลไม่ได้"**

      แผงเคยมีแต่ปุ่มชื่อผลแบบกดปุ่มเดียวจบ ซึ่งมีเฉพาะเกมที่ผลมีไม่กี่แบบ
      ไฮโลมี 216 แบบจึงไม่มีปุ่ม แล้วแผงขึ้นว่า "ออกผลจากหน้าเว็บไม่ได้"
      ทั้งที่เซิร์ฟเวอร์ออกผลได้มาตลอด — แค่ต้องส่งคำสั่งมา

   ⭐ จิ้มลูกเต๋าทีละลูก หรือพิมพ์รวดเดียวก็ได้ (กติกาเดียวกับกระดานหลังบ้าน) */

.ctlpad {
  margin-top: 8px;
  padding: 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}

.ctlpad__hint {
  margin: 0 0 8px;
  color: var(--ink-dim);
  font-size: .84em;
}

/** ⚠️ ช่องผลเรียงตามลำดับที่คนแจกอ่านออก — เลขกำกับอยู่เหนือค่าเสมอ */
.ctlpad__slots {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.ctlpad__slot {
  display: grid;
  gap: 2px;
  min-width: 64px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: var(--ink-dim);
  font: inherit;
  cursor: pointer;
  text-align: center;
}

.ctlpad__slot-lab { font-size: .72em; }
.ctlpad__slot b { color: var(--ink); font-size: 1.15em; line-height: 1.1; }

.ctlpad__slot.is-on { border-color: color-mix(in srgb, var(--tie) 55%, var(--line)); }

/** ⭐ ช่องที่กำลังจะถูกเติม — ต้องเห็นได้จากหางตา ไม่ต้องอ่าน */
.ctlpad__slot.is-at {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent);
}

.ctlpad__keys {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 9px;
}

/** ⚠️ ปุ่มค่าอย่างน้อย 44px — คนแจกกดด้วยนิ้วโป้งขณะถือไพ่อีกมือ */
.ctlpad__key {
  min-width: 44px;
  min-height: 40px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  touch-action: manipulation;
}

.ctlpad__key:active { transform: scale(.94); }

.ctlpad__go { display: flex; flex-wrap: wrap; gap: 6px; }

/** ⚠️ ช่องคำสั่งกินที่เหลือทั้งหมด — คำสั่งบางเกมยาวกว่า 20 ตัวอักษร */
.ctlpad__in {
  flex: 1 1 140px;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-variant-numeric: tabular-nums;
}

.ctlpad__in:focus { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); }

/* ── หน้าเต๋า — ตัววาดตัวเดียวของทั้งหน้า (`Live.dieHtml`) ─────────
   ⭐ เจ้าของสั่ง 2026-09-21 — ป้ายออกผลกับแผงควบคุมใช้ลายเต๋าที่ร้านตั้งไว้
      (`weblive_dice`) · ไม่ได้ตั้งลาย = วาดจุดเอง ไม่ใช่เหลือแต่ตัวเลข

   🔴 ขนาดมาจาก `--die` ของที่วาง — ตัวเต๋าไม่รู้ว่าตัวเองอยู่ในป้ายหรือในปุ่ม */

.die {
  display: inline-grid;
  place-items: center;
  width: var(--die, 44px);
  height: var(--die, 44px);
  flex: none;
  line-height: 0;
}

.die__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 18%;
}

.die__svg { width: 100%; height: 100%; }
.die__svg rect { fill: #f4f5f7; stroke: rgba(0, 0, 0, .18); stroke-width: 2; }
.die__svg circle { fill: #1c2230; }

/** ⚠️ หน้า 1 กับ 4 จุดแดง — แบบเต๋าไทยที่ผู้เล่นคุ้น (เหมือนแถบผลย้อนหลังของเกม 4) */
.die__svg.is-red circle { fill: #c0262d; }

/**
 * ⭐ **แถวเต๋าในป้ายประกาศผล**
 *    ทอยลงทีละลูก — ลูกที่ 2 และ 3 ตามมาหลังนิด ๆ ให้อ่านเป็น "ทอยออกมา"
 *    ไม่ใช่รูปนิ่งสามรูป
 */
.note__dice {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin: 4px 0 2px;
  --die: 58px;
}

.note__die {
  display: inline-flex;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, .45));
  animation: dieRoll .6s cubic-bezier(.2, 1.3, .4, 1) both;
  animation-delay: calc(var(--i, 0) * 120ms);
}

@keyframes dieRoll {
  0%   { opacity: 0; transform: translateY(-26px) rotate(-200deg) scale(.6); }
  70%  { opacity: 1; transform: translateY(3px) rotate(8deg) scale(1.04); }
  100% { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .note__die { animation: none; }
}

/* ── แผงออกผล: หน้าเต๋าแทนตัวเลข ─────────────────────────────────
   ⭐ เจ้าของสั่ง 2026-09-21 — ปุ่ม 1–6 เป็นหน้าเต๋าตามลายของร้าน
      ช่องผลแต่ละลูกก็โชว์หน้าเต๋าที่กดไว้ ไม่ใช่ตัวเลข */

.ctlpad__key--die {
  --die: 38px;
  min-width: 50px;
  min-height: 50px;
  padding: 5px;
  display: inline-grid;
  place-items: center;
}

.ctlpad__slot { --die: 34px; }
.ctlpad__slot b { display: grid; place-items: center; min-height: 34px; }
.ctlpad__slot-empty { color: var(--ink-faint); font-size: 1.2em; }

/** ⭐ เต๋าที่เพิ่งลงช่อง เด้งเข้าเบา ๆ — บอกว่า "ลงแล้ว" โดยไม่ต้องอ่าน */
.ctlpad__slot.is-on .die { animation: dieIn .28s cubic-bezier(.2, 1.4, .4, 1); }

@keyframes dieIn {
  from { transform: scale(.5) rotate(-40deg); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/**
 * 🔴 **สถานะยังไม่ถึงออกผล = ทั้งแผงจาง และกดไม่ได้จริง** (เจ้าของสั่ง 2026-09-21)
 *    `disabled` อยู่บนทุกปุ่มแล้ว · ตรงนี้แค่ทำให้ตาเห็นก่อนนิ้วจะกด
 */
.ctlpad.is-locked { opacity: .5; }
.ctlpad.is-locked .ctlpad__hint { color: var(--gold); opacity: 1; }
.ctlpad button[disabled],
.ctlpad input[disabled] { cursor: not-allowed; }

/** ⚠️ ช่องเลขกว้างพอดีสามหลัก — ช่องยาวเต็มแถวชวนให้พิมพ์อะไรยาว ๆ ลงไป */
.ctlpad__in[data-ctl-digits] {
  flex: 1 1 110px;
  letter-spacing: .3em;
  font-size: 1.1em;
  font-weight: 700;
  text-align: center;
}

/**
 * 🔴 **ช่องไฟไม่ระยะห่างตัวอักษรกับข้อความไทย** — สระบน/ล่างและวรรณยุกต์
 *    หลุดออกจากพยัญชนะ ("พ ิ ม พ ์") และข้อความยาวเกินช่องจนโดนตัด
 *    (เจอตอนวัดด้วย headless Edge) · ระยะห่างมีไว้ให้ตัวเลขที่พิมพ์เท่านั้น
 */
.ctlpad__in[data-ctl-digits]::placeholder {
  letter-spacing: normal;
  font-size: .82em;
  font-weight: 400;
}

@media (prefers-reduced-motion: reduce) {
  .ctlpad__slot.is-on .die { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════
   ⭐ **แผงควบคุมโต๊ะ รุ่น 2** (เจ้าของสั่ง 2026-10-10)

      · ถอดเปิด/ปิดคลับ · "แก้ไขรอบ" (ยกเลิกผล + ยกเลิกรอบล่าสุด) ลงล่างสุด
      · ทุกปุ่มมีคำสั่งไลน์กำกับ `เปิดรอบ (O)` · เต๋าออกผลใหญ่ขึ้น
      · ขั้นของรอบ · การ์ด "ผลที่ออก" · ภาพรวมรอบแบบสด (ตัวเลข + แท่งโฮโลแกรม)
      · พื้นหลังออโรรา + ไอคอนเคลื่อนไหว — ปิดทั้งหมดเมื่อผู้ใช้ขอลดการเคลื่อนไหว

   🔴 วางท้ายชุดเดิมโดยเจตนา — ทับค่าของรุ่นแรกที่นี่ที่เดียว ไม่ไล่แก้กระจาย
   ══════════════════════════════════════════════════════════════════ */

.ctlbox__sheet {
  width: min(680px, 100%);
  isolation: isolate;
  overflow-x: hidden;
}

/** ⭐ พื้นหลังเคลื่อนไหว — แสงสองดวงลอยช้า ๆ + กริดจาง (ตกแต่งล้วน ไม่รับคลิก) */
.ctlbox__aura {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  border-radius: inherit;
  overflow: hidden;
}

.ctlbox__aura::before,
.ctlbox__aura::after {
  content: "";
  position: absolute;
  width: 70%;
  aspect-ratio: 1;
  border-radius: 50%;
  filter: blur(60px);
  opacity: .22;
}

.ctlbox__aura::before {
  top: -30%;
  left: -20%;
  background: var(--accent);
  animation: ctlAura 14s ease-in-out infinite alternate;
}

.ctlbox__aura::after {
  bottom: -35%;
  right: -25%;
  background: var(--danger);
  animation: ctlAura 18s ease-in-out infinite alternate-reverse;
}

.ctlbox__sheet::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(color-mix(in srgb, var(--ink) 5%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--ink) 5%, transparent) 1px, transparent 1px);
  background-size: 28px 28px;
  mask-image: linear-gradient(180deg, #000, transparent 55%);
  -webkit-mask-image: linear-gradient(180deg, #000, transparent 55%);
}

@keyframes ctlAura {
  from { transform: translate(0, 0) scale(1); }
  to   { transform: translate(18%, 12%) scale(1.15); }
}

/** ⭐ ไอคอนหัวแผง — วงเรดาร์หมุน บอกว่า "กำลังเฝ้าโต๊ะสด" */
.ctlbox__icon {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--danger) 18%, var(--panel));
  color: var(--danger);
}

.ctlbox__icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.ctlbox__icon circle:first-child { fill: currentColor; stroke: none; }

.ctlbox__ring {
  stroke-dasharray: 12 38;
  transform-origin: 12px 12px;
  animation: ctlSpin 2.4s linear infinite;
}

@keyframes ctlSpin { to { transform: rotate(360deg); } }

/** ⭐ ป้ายสถานะมีจุดสีตามขั้นของรอบ — เต้นเบา ๆ ตอนรอคนกด */
.ctlbox__state {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.ctlbox__state::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink-faint);
}

.ctlbox[data-phase="open"] .ctlbox__state::before,
.ctlbox[data-phase="handicap1"] .ctlbox__state::before,
.ctlbox[data-phase="handicap2"] .ctlbox__state::before { background: var(--tie); animation: ctlBlink 1.4s ease-in-out infinite; }
.ctlbox[data-phase="close"] .ctlbox__state::before { background: var(--gold); animation: ctlBlink 1.4s ease-in-out infinite; }
.ctlbox[data-phase="waiting"] .ctlbox__state::before { background: var(--danger); animation: ctlBlink .9s ease-in-out infinite; }
.ctlbox[data-phase="succeed"] .ctlbox__state::before { background: var(--accent); }

@keyframes ctlBlink {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 0%, transparent); opacity: 1; }
  50%      { opacity: .35; }
}

/* ── ขั้นของรอบ ─────────────────────────────────────────────── */

.ctlstep {
  --fill: 0;
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}

/** เส้นเชื่อมพื้น + เส้นที่เติมสีตามความคืบหน้า */
.ctlstep::before,
.ctlstep::after {
  content: "";
  position: absolute;
  top: 13px;
  left: 12.5%;
  height: 2px;
  border-radius: 2px;
}

.ctlstep::before { right: 12.5%; background: var(--line); }

.ctlstep::after {
  width: calc(75% * var(--fill));
  background: linear-gradient(90deg, var(--tie), var(--accent));
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 60%, transparent);
  transition: width .5s cubic-bezier(.2, .9, .3, 1);
}

.ctlstep__i {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 4px;
  color: var(--ink-faint);
  font-size: .76em;
  text-align: center;
}

.ctlstep__dot {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--panel);
  font-weight: 700;
  font-size: .95em;
  transition: border-color .3s ease, background .3s ease, color .3s ease;
}

.ctlstep__i.is-done { color: var(--ink-dim); }

.ctlstep__i.is-done .ctlstep__dot {
  border-color: var(--tie);
  background: color-mix(in srgb, var(--tie) 22%, var(--panel));
  color: var(--ink);
}

.ctlstep__i.is-at { color: var(--ink); font-weight: 700; }

.ctlstep__i.is-at .ctlstep__dot {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 24%, var(--panel));
  color: var(--ink);
  animation: ctlRing 1.6s ease-out infinite;
}

@keyframes ctlRing {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  100% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
}

.ctlstep.is-idle { opacity: .55; }

/* ── ปุ่ม: ไอคอน + ข้อความ + ป้ายคำสั่ง ───────────────────────── */

.ctl__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 44px;
  position: relative;
  overflow: hidden;
}

.ctl__ico {
  flex: none;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ctl__ico--play,
.ctl__ico--stop,
.ctl__ico--rewind { fill: currentColor; stroke: none; }

/** ⭐ ป้ายคำสั่งไลน์ `(O)` — ตัวพิมพ์แบบแป้นคีย์บอร์ด */
.ctl__key {
  display: inline-block;
  min-width: 1.6em;
  padding: 1px 6px;
  border: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);
  border-bottom-width: 2px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  color: var(--ink-dim);
  font: 700 .74em/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: .04em;
  text-align: center;
}

/** ⭐ ไอคอนขยับเฉพาะปุ่มที่กดได้ — ปุ่มจางต้องนิ่ง ไม่งั้นอ่านเหมือนชวนให้กด */
.ctl__btn:not([disabled]) .ctl__ico--play { animation: ctlNudge 1.6s ease-in-out infinite; }
.ctl__btn:not([disabled]) .ctl__ico--stop { animation: ctlPulse 1.6s ease-in-out infinite; }
.ctl__btn:not([disabled]) .ctl__ico--dice { animation: ctlWobble 2.2s ease-in-out infinite; }
.ctl__btn:not([disabled]) .ctl__ico--coin { animation: ctlBob 1.4s ease-in-out infinite; }
.ctl__btn:not([disabled]) .ctl__ico--rewind { animation: ctlBack 1.6s ease-in-out infinite; }

@keyframes ctlBack { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(-3px); } }

.ctlfix__note { margin: 6px 0 0; color: var(--ink-faint); font-size: .76em; }

@keyframes ctlNudge { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(3px); } }
@keyframes ctlPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(.82); } }
@keyframes ctlWobble { 0%, 70%, 100% { transform: rotate(0); } 80% { transform: rotate(-14deg); } 90% { transform: rotate(10deg); } }
@keyframes ctlBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }

/** ⭐ แสงกวาดผ่านปุ่มที่ถึงคิวกด — บอกจากหางตาว่าขั้นถัดไปคือปุ่มนี้ */
.ctl__btn:not([disabled])::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, #fff 16%, transparent) 50%, transparent 70%);
  transform: translateX(-120%);
  animation: ctlSheen 3.2s ease-in-out infinite;
  pointer-events: none;
}

@keyframes ctlSheen { 0%, 60% { transform: translateX(-120%); } 100% { transform: translateX(120%); } }

/** ⭐ ปุ่มหลัก (เปิด/ปิดรอบ) กว้างเต็มแถว แบ่งครึ่ง — เป้าใหญ่ กดด้วยนิ้วโป้งได้ */
.ctl__row--main .ctl__btns,
.ctl__row--pay .ctl__btns { display: grid; grid-template-columns: 1fr 1fr; }
.ctl__row--pay .ctl__btns { grid-template-columns: 1fr; }

.ctl__row--main .ctl__btn,
.ctl__row--pay .ctl__btn { min-height: 52px; font-size: 1.04em; }

.ctl__btn--go:not([disabled])   { background: color-mix(in srgb, var(--tie) 16%, var(--panel)); }
.ctl__btn--stop:not([disabled]) { background: color-mix(in srgb, var(--gold) 16%, var(--panel)); }

/** 🔴 ยืนยันจ่ายตอนถึงคิว — เรืองแดงเป็นจังหวะ (ปุ่มเดียวที่ย้ายเงินจริง) */
.ctl__btn--pay:not([disabled]) {
  background: linear-gradient(135deg, color-mix(in srgb, var(--danger) 70%, #000), var(--danger));
  color: #fff;
  animation: ctlPayGlow 1.8s ease-in-out infinite;
}

.ctl__btn--pay:not([disabled]) .ctl__key { color: #fff; border-color: rgba(255, 255, 255, .45); background: rgba(255, 255, 255, .14); }

@keyframes ctlPayGlow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--danger) 50%, transparent); }
  50%      { box-shadow: 0 0 18px 2px color-mix(in srgb, var(--danger) 45%, transparent); }
}

/* ── กระดานออกผล: เต๋าใหญ่ขึ้น ──────────────────────────────── */

.ctlpad__slots {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: 8px;
}

.ctlpad__slot { --die: 54px; min-height: 92px; padding: 8px; border-radius: 14px; }
.ctlpad__slot b { min-height: 56px; }
.ctlpad__slot-empty { font-size: 1.8em; }

.ctlpad__keys {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 8px;
}

.ctlpad__key--die {
  --die: clamp(34px, 10vw, 62px);
  min-width: 0;
  padding: 4px;
  min-height: 0;
  aspect-ratio: 1;
  width: 100%;
  border-radius: 14px;
  transition: transform .12s ease, border-color .15s ease, box-shadow .15s ease;
}

.ctlpad__key--die:not([disabled]):hover {
  border-color: var(--accent);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--accent) 30%, transparent);
  transform: translateY(-2px);
}

/** ⚠️ เกมที่ค่าเป็นชื่อ (ไม่ใช่เต๋า) ยังเรียงไหลตามความยาวคำเหมือนเดิม */
.ctlpad__keys:not(:has(.ctlpad__key--die)) { display: flex; flex-wrap: wrap; }

.ctlpad__go { display: grid; grid-template-columns: 1fr auto auto; gap: 8px; }
.ctl__btn--send { min-width: 132px; }

@media (max-width: 420px) {
  .ctlpad__go { grid-template-columns: 1fr 1fr; }
  .ctlpad__go .ctlpad__in { grid-column: 1 / -1; }
  .ctlpad__keys { gap: 5px; }
}

/** ⚠️ ข้อความบนปุ่มห้ามแตกบรรทัด ("เปิด / รอบ") — อ่านยากกว่าปุ่มที่แคบ */
.ctl__txt { white-space: nowrap; }

/** ⚠️ หัวแผงอยู่บรรทัดเดียวเสมอ — ป้ายสถานะยาวหดเอง ไม่ดันปุ่ม ✕ ตกบรรทัด */
.ctlbox__bar { flex-wrap: nowrap; }
.ctlbox__title { flex: none; white-space: nowrap; }

.ctlbox__state {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ctlbox__state::before { flex: none; }

/** ⭐ จอแคบ: ป้ายหมวดขึ้นบรรทัดบน ปุ่มได้ความกว้างเต็มแถว */
@media (max-width: 520px) {
  .ctl__row { flex-direction: column; align-items: stretch; gap: 6px; }
  .ctl__lab { width: auto; padding-top: 0; font-size: .8em; }
  .ctlbox__title { font-size: .95em; }
}

/* ── การ์ด "ผลที่ออก" ───────────────────────────────────────── */

.ctlres {
  margin: 10px 0 2px;
  padding: 12px;
  border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--line));
  border-radius: 14px;
  background:
    radial-gradient(120% 100% at 50% 0%, color-mix(in srgb, var(--danger) 16%, transparent), transparent 70%),
    var(--panel);
  text-align: center;
  animation: ctlRise .35s cubic-bezier(.2, 1.2, .4, 1);
}

.ctlres__h {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  color: var(--ink-dim);
  font-size: .84em;
}

.ctlres__raw {
  padding: 0 6px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  font: 600 .92em ui-monospace, Consolas, monospace;
}

.ctlres__body { display: grid; justify-items: center; gap: 6px; }

.ctlres__dice { display: flex; gap: 10px; --die: 64px; }

.ctlres__die {
  display: inline-flex;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, .4));
  animation: dieRoll .6s cubic-bezier(.2, 1.3, .4, 1) both;
  animation-delay: calc(var(--i, 0) * 120ms);
}

.ctlres__name { font-size: 1.1em; }
.ctlres__name--big { font-size: 1.5em; letter-spacing: .02em; }

.ctlres__fix {
  margin-top: 8px;
  padding: 4px 10px;
  border: 1px dashed var(--line);
  border-radius: 999px;
  background: none;
  color: var(--ink-dim);
  font: inherit;
  font-size: .82em;
  cursor: pointer;
}

.ctlres__fix:not([disabled]):hover { border-color: var(--gold); color: var(--ink); }

@keyframes ctlRise { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }

/* ── แก้ไขรอบ (ล่างสุด) ─────────────────────────────────────── */

.ctlfix {
  margin-top: 12px;
  padding: 10px;
  border: 1px dashed color-mix(in srgb, var(--danger) 35%, var(--line));
  border-radius: 12px;
}

.ctlfix__h {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin: 0 0 8px;
  font-size: .9em;
}

.ctlfix__h small { color: var(--ink-faint); font-size: .86em; }

.ctl__btn--danger { border-color: color-mix(in srgb, var(--danger) 55%, var(--line)); color: color-mix(in srgb, var(--danger) 70%, var(--ink)); }
.ctl__btn--danger:not([disabled]):hover { background: color-mix(in srgb, var(--danger) 14%, var(--panel)); }
.ctl__btn--undo:not([disabled]):hover { border-color: var(--gold); color: var(--ink); }

/** ⭐ บรรทัดบอกว่าพิมพ์คำสั่งได้ + สิ่งที่กำลังพิมพ์ (เฉพาะจอที่มีเมาส์/คีย์บอร์ด) */
.ctlkbd {
  margin: 12px 0 0;
  color: var(--ink-faint);
  font-size: .78em;
}

.ctlkbd__buf:not(:empty) {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  color: var(--ink);
  font: 700 1.1em ui-monospace, Consolas, monospace;
  animation: ctlCaret 1s steps(1) infinite;
}

@keyframes ctlCaret { 50% { border-color: transparent; } }

/* ── ภาพรวมรอบแบบสด: ตัวเลข + แท่งโฮโลแกรม ───────────────────── */

.ctlviz { margin: 0 0 10px; }

.ctlviz__kpis {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 10px;
}

.ctlviz__kpi {
  display: grid;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--line));
  border-radius: 12px;
  background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 10%, var(--panel)), var(--panel));
  min-width: 0;
}

.ctlviz__kpi span { color: var(--ink-dim); font-size: .74em; }

.ctlviz__kpi b {
  font-size: 1.15em;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ctlviz__bars { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }

.ctlviz__bar {
  display: grid;
  grid-template-columns: minmax(56px, 26%) 1fr auto;
  align-items: center;
  gap: 8px;
  font-size: .82em;
}

.ctlviz__k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-dim); }

.ctlviz__track {
  position: relative;
  height: 12px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 7%, transparent);
  overflow: hidden;
}

.ctlviz__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w);
  border-radius: inherit;
  background:
    repeating-linear-gradient(90deg, transparent 0 6px, rgba(255, 255, 255, .14) 6px 7px),
    linear-gradient(90deg, color-mix(in srgb, var(--accent) 55%, transparent), var(--accent));
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 55%, transparent);
  animation: ctlGrow .7s cubic-bezier(.2, .9, .3, 1) both;
  animation-delay: calc(var(--i, 0) * 70ms);
  transform-origin: left;
}

/** ⭐ เส้นสแกนวิ่งผ่านแท่ง — ลุคโฮโลแกรม */
.ctlviz__fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .45), transparent);
  width: 30%;
  animation: ctlScan 2.6s linear infinite;
  animation-delay: calc(var(--i, 0) * 200ms);
}

.ctlviz__v { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ctlviz__v small { color: var(--ink-faint); }

@keyframes ctlGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes ctlScan { from { transform: translateX(-110%); } to { transform: translateX(360%); } }

/* ── เพิ่ม/ลดเครดิตผู้เล่น (2026-10-13 · 000295) ───────────────────
   ⭐ การ์ดพับได้ใต้ "แก้ไขรอบ" — เปิดเมื่อใช้ ไม่กินที่ปุ่มคุมรอบ
   ⚠️ สีเขียว = เพิ่ม · แดง = ลด ทั้งปุ่มทิศ ตัวอย่างยอด และรายการล่าสุด (อ่านจากหางตา) */

.ctlcr {
  margin-top: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: 14px;
  background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 9%, var(--panel)), var(--panel));
  overflow: hidden;
}

.ctlcr[hidden] { display: none; }

.ctlcr__head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas: "ico t chev" "ico s chev";
  align-items: center;
  gap: 0 10px;
  width: 100%;
  padding: 11px 12px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ctlcr__head b { grid-area: t; font-size: .98em; }
.ctlcr__head small { grid-area: s; color: var(--ink-faint); font-size: .76em; }

.ctlcr__ico {
  grid-area: ico;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  background: color-mix(in srgb, var(--accent) 22%, var(--panel));
  color: var(--accent);
}

.ctlcr__ico svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.ctlcr:not(.is-open) .ctlcr__ico svg { animation: ctlPulse 2.4s ease-in-out infinite; }

.ctlcr__chev { grid-area: chev; color: var(--ink-dim); transition: transform .2s ease; }
.ctlcr.is-open .ctlcr__chev { transform: rotate(180deg); }

.ctlcr__body { padding: 0 12px 12px; animation: ctlRise .25s ease; }

.ctlcr__search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
}

.ctlcr__search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.ctlcr__search svg { flex: none; width: 18px; height: 18px; fill: none; stroke: var(--ink-dim); stroke-width: 2; stroke-linecap: round; }

.ctlcr__search input {
  flex: 1;
  min-width: 0;
  height: 44px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  outline: none;
}

.ctlcr__list { display: grid; gap: 6px; margin-top: 8px; max-height: 40dvh; overflow-y: auto; }

.ctlcr__p {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel-2);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  animation: ctlRise .22s ease both;
}

button.ctlcr__p:hover { border-color: var(--accent); }

.ctlcr__av {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  overflow: hidden;
  background: color-mix(in srgb, var(--accent) 25%, var(--panel));
  font-weight: 700;
}

.ctlcr__av img { width: 100%; height: 100%; object-fit: cover; }
.ctlcr__pn { display: grid; min-width: 0; }
.ctlcr__pn b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctlcr__pn small { color: var(--ink-faint); font-size: .78em; }
.ctlcr__pc { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent); }

.ctlcr__form { display: grid; gap: 8px; margin-top: 10px; }
.ctlcr__form[hidden] { display: none; }

.ctlcr__who .ctlcr__p { cursor: default; border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.ctlcr__who.is-flash .ctlcr__pc { animation: ctlCredit .9s ease; }

@keyframes ctlCredit {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.25); color: var(--tie); text-shadow: 0 0 14px color-mix(in srgb, var(--tie) 70%, transparent); }
  100% { transform: scale(1); }
}

.ctlcr__dir { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

.ctlcr__dirbtn {
  min-height: 44px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--ink-dim);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.ctlcr__dirbtn.is-on[data-ctlcr-dir="1"] {
  border-color: var(--tie);
  background: color-mix(in srgb, var(--tie) 20%, var(--panel));
  color: var(--ink);
}

.ctlcr__dirbtn.is-on[data-ctlcr-dir="-1"] {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 20%, var(--panel));
  color: var(--ink);
}

/** ⭐ ลดเครดิตต้องเลือกชนิด (2026-10-23) — `.ctlcr__dir` ตั้ง display:grid จึงต้องบอก [hidden] เอง */
.ctlcr__kind[hidden] { display: none; }
.ctlcr__kind .ctlcr__dirbtn { padding: 6px 8px; line-height: 1.25; }
.ctlcr__dirbtn small { display: block; margin-top: 2px; font-size: .74em; font-weight: 400; color: var(--ink-faint); }

.ctlcr__dirbtn.is-on[data-ctlcr-kind] {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, var(--panel));
  color: var(--ink);
}

.ctlcr__amt,
.ctlcr__reason {
  height: 46px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
}

.ctlcr__amt { font-size: 1.3em; font-weight: 800; text-align: center; font-variant-numeric: tabular-nums; }
.ctlcr__amt::placeholder { font-size: .7em; font-weight: 400; }
.ctlcr__amt:focus, .ctlcr__reason:focus { outline: none; border-color: var(--accent); }

.ctlcr__chips { display: flex; flex-wrap: wrap; gap: 6px; }

.ctlcr__chips button {
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--ink-dim);
  font: inherit;
  font-size: .82em;
  cursor: pointer;
}

.ctlcr__chips button:hover { border-color: var(--accent); color: var(--ink); }

.ctlcr__preview {
  margin: 0;
  padding: 8px 10px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  font-size: .86em;
  font-variant-numeric: tabular-nums;
}

.ctlcr__preview.is-up   { color: var(--tie); }
.ctlcr__preview.is-down { color: color-mix(in srgb, var(--danger) 80%, var(--ink)); }
.ctlcr__preview.is-warn { color: var(--gold); }

.ctlcr__go { display: grid; grid-template-columns: auto 1fr; gap: 8px; }
.ctlcr__submit { min-height: 48px; }

/** ⭐ ทิศ "เพิ่ม" = ปุ่มยืนยันสีเขียว · "ลด" คงสีแดงของปุ่มเงินเดิม */
.ctlcr:not(.is-minus) .ctlcr__submit:not([disabled]) {
  background: linear-gradient(135deg, color-mix(in srgb, var(--tie) 70%, #000), var(--tie));
  border-color: var(--tie);
  animation: none;
}

.ctlcr__recent { margin-top: 10px; }
.ctlcr__rh { margin: 0 0 6px; color: var(--ink-faint); font-size: .78em; }

.ctlcr__r {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 10px;
  align-items: center;
  padding: 6px 0;
  border-top: 1px solid color-mix(in srgb, var(--line) 50%, transparent);
  font-size: .84em;
}

.ctlcr__ra { min-width: 78px; font-weight: 800; font-variant-numeric: tabular-nums; text-align: right; }
.ctlcr__ra.is-up { color: var(--tie); }
.ctlcr__ra.is-down { color: color-mix(in srgb, var(--danger) 80%, var(--ink)); }
.ctlcr__rt { display: grid; min-width: 0; }
.ctlcr__rt b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctlcr__rt small { color: var(--ink-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (prefers-reduced-motion: reduce) {
  .ctlcr__ico svg, .ctlcr__body, .ctlcr__p, .ctlcr__who.is-flash .ctlcr__pc { animation: none !important; }
}

/* ── ปุ่มยืนยันจ่ายใน Popup ผล (เฉพาะทีมงานที่มีสิทธิ์จ่าย) ────────── */

.note__act {
  display: grid;
  justify-items: center;
  gap: 4px;
  margin-top: 10px;
  pointer-events: auto;
}

.ctl__btn--pop { min-width: 190px; min-height: 46px; font-size: 1em; }
.note__act-hint { color: var(--ink-faint); font-size: .74em; }

@media (prefers-reduced-motion: reduce) {
  .ctlbox__aura::before,
  .ctlbox__aura::after,
  .ctlbox__ring,
  .ctlbox__state::before,
  .ctlstep__i.is-at .ctlstep__dot,
  .ctl__btn .ctl__ico,
  .ctl__btn::after,
  .ctl__btn--pay,
  .ctlres,
  .ctlres__die,
  .ctlviz__fill,
  .ctlviz__fill::after,
  .ctlkbd__buf { animation: none !important; }

  .ctlstep::after { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════
   ⭐ **ความเด่นของช่องที่ชนะ** — `data-winlook` ที่ `.live`
      (เจ้าของแจ้ง 2026-09-23 "บางช่องเป็นขอบเหลือง … ตั้งปิดได้หรือไม่"
       แล้วสั่งต่อว่า "เปิด/ปิด/ให้มันจางลง/หายเร็วขึ้น")

        open   ค้างขอบทองจนเปิดรอบใหม่ (เดิม — ไม่มีกฎอะไรตรงนี้)
        dim    โชว์แต่จาง ๆ ไม่มีแสงกระพริบ
        fade   โชว์เต็มแล้วจางหายเองใน 5 วินาที
        close  ไม่โชว์เลย

   🔴 **ห้ามแตะคลาส `is-won` ใน js** — กระดาน · เอฟเฟกต์ · การนับช่องที่ชนะ
      ใช้คลาสนี้ร่วมกันหมด ถอดออกเมื่อไหร่ของอื่นพังตาม
      ที่นี่จึงจัดการด้วยหน้าตาอย่างเดียว ตัวคลาสยังติดครบทุกโหมด

   ⚠️ **ต้องครอบทุกกระดาน** — `.gate` (เกม 1/3) · `.g2cell` + `.g2vschip`
      (เกม 2) · `.g4cell` (เกม 4) · กฎเดิมของแต่ละเกมอยู่คนละไฟล์
      แต่ `[data-winlook=…]` ทำให้ตัวเลือกนี้ชนะทุกใบโดยไม่ต้องใช้ `!important`
      (0,3,0 > 0,2,0 — รวมถึงกฎในบล็อกลดการเคลื่อนไหวด้วย)
   ══════════════════════════════════════════════════════════════════ */

/* ── ปิด ─────────────────────────────────────────────────────────── */
[data-winlook="close"] .gate.is-won {
  border-color: var(--line);
  box-shadow: none;
  animation: none;
}

[data-winlook="close"] .g2cell.is-won {
  border-color: var(--line);
  box-shadow: none;
  animation: none;
}

[data-winlook="close"] .g2vschip.is-won {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  animation: none;
}

[data-winlook="close"] .g2vschip.is-won i { color: var(--accent); }

/**
 * ⚠️ เกม 4 ระบายสีทองจากคีย์เฟรมล้วน ๆ — ปิดคีย์เฟรมก็กลับไปเป็นช่องธรรมดาเอง
 *
 * 🔴 **ต้องเว้นปุ่มเขียว (`.g4bridge`) — มันก็เป็น `.g4cell`**
 *    (เจ้าของแจ้ง 2026-09-26 "สีเขียวหาย" — ปุ่ม ส6 ชนะรอบที่แล้ว)
 *
 *    พื้นสีเขียวของปุ่มคือ **สีประจำปุ่ม** ไม่ใช่ไฮไลต์ตอนชนะ
 *    เขียนทับด้วย `transparent` = **ปุ่มหายทั้งปุ่ม** เหลือแค่เส้นขอบจาง ๆ
 *    หนักกว่านั้นคือมันหายจนกว่าจะเปิดรอบใหม่ — ผู้เล่นหาช่องโต๊ดไม่เจอทั้งรอบ
 */
[data-winlook="close"] .g4cell.is-won:not(.g4bridge) {
  animation: none;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07);
  background: transparent;
}

/** ⚠️ ปุ่มเขียว: ปิดแค่คีย์เฟรม ห้ามแตะพื้นกับเงา — `is-mine` (สีทอง) ต้องยังทำงาน */
[data-winlook="close"] .g4bridge.is-won { animation: none; }

/* ── จางลง ───────────────────────────────────────────────────────── */
[data-winlook="dim"] .gate.is-won {
  border-color: rgba(251, 191, 36, .45);
  box-shadow: none;
  animation: none;
}

[data-winlook="dim"] .g2cell.is-won {
  border-color: rgba(251, 191, 36, .45);
  box-shadow: none;
  animation: none;
}

[data-winlook="dim"] .g2vschip.is-won {
  border-color: rgba(251, 191, 36, .45);
  background: none;
  animation: none;
}

[data-winlook="dim"] .g4cell.is-won:not(.g4bridge) {
  animation: none;
  box-shadow: inset 0 0 0 1px rgba(251, 191, 36, .45);
  background: rgba(251, 191, 36, .07);
}

/** ⚠️ ปุ่มเขียวเล็กเกินกว่าจะใส่เส้นขอบข้างใน — ใช้วงทองรอบนอกแทน พื้นเขียวคงเดิม */
[data-winlook="dim"] .g4bridge.is-won {
  animation: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .5),
    inset 0 -2px 0 rgba(0, 0, 0, .25),
    0 0 0 2px rgba(251, 191, 36, .5);
}

/* ── หายเองใน 5 วินาที ──────────────────────────────────────────────
   🔴 **ต่อท้ายคีย์เฟรมเดิม ไม่ใช่แทนที่** — ท่าเด้งตอนออกผลยังต้องมี
      คนที่เพิ่งมองจอกลับมาถึงจะทันเห็นว่าช่องไหนชนะ

   ⚠️ **`forwards` เท่านั้น ห้าม `both`** — `both` จะเติมค่าย้อนหลังตั้งแต่
      วินาทีที่ 0 ทับของเดิมทิ้ง แล้วขอบทองจะหายทันทีแทนที่จะหายตอน 5 วินาที
   ────────────────────────────────────────────────────────────────── */
@keyframes wonGoneGate {
  0%   { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(251, 191, 36, .5), 0 0 34px rgba(251, 191, 36, .4); }
  100% { border-color: var(--line); box-shadow: none; }
}

[data-winlook="fade"] .gate.is-won {
  animation: winPulse 1s ease-out 3, wonGoneGate .9s ease 5s forwards;
}

@keyframes wonGoneCell {
  0%   { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold), 0 0 18px -4px var(--gold); }
  100% { border-color: var(--line); box-shadow: none; }
}

[data-winlook="fade"] .g2cell.is-won {
  animation: g2won .5s ease backwards, wonGoneCell .9s ease 5s forwards;
}

@keyframes wonGoneG4 {
  0%   { box-shadow: inset 0 0 0 2px var(--gold), 0 0 12px 0 rgba(251, 191, 36, .22); background: rgba(251, 191, 36, .12); }
  100% { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07); background: transparent; }
}

[data-winlook="fade"] .g4cell.is-won:not(.g4bridge) {
  animation: g4won 1s cubic-bezier(.2, 1.4, .4, 1) both, wonGoneG4 .9s ease 5s forwards;
}

/**
 * ⚠️ **ปุ่มเขียวกลับไปเขียว ไม่ใช่กลับไปโปร่งใส**
 *    `background` จากสีล้วนไปหาเกรเดียนต์ไม่ไล่สีให้ — มันสลับทีเดียวกลางทาง
 *    รับได้ เพราะตอนนั้นแสงทองจางจนแทบหมดแล้ว
 */
@keyframes wonGonePill {
  0% {
    box-shadow: inset 0 0 0 2px var(--gold), 0 0 12px 0 rgba(251, 191, 36, .22);
    background: rgba(251, 191, 36, .12);
  }

  100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, .5),
      inset 0 -2px 0 rgba(0, 0, 0, .25),
      0 2px 6px rgba(0, 0, 0, .45);
    background: linear-gradient(180deg, #58d9a0, var(--g4-pill));
  }
}

[data-winlook="fade"] .g4bridge.is-won {
  animation: g4won 1s cubic-bezier(.2, 1.4, .4, 1) both, wonGonePill .9s ease 5s forwards;
}

/**
 * ⚠️ **คนที่ขอให้ลดการเคลื่อนไหว** — ยังต้องได้ผลลัพธ์ของโหมดนี้
 *    (ขอบทองหายไปเอง) แต่ไม่ต้องมีท่าเด้งตอนออกผล
 */
@media (prefers-reduced-motion: reduce) {
  /**
   * 🔴 **ตรงนี้ต้อง `both` — กลับกันกับข้างบน**
   *    ที่นั่นมีคีย์เฟรมท่าเด้งค้างขอบทองไว้ให้ ที่นี่เหลือตัวเดียว
   *    ใช้ `forwards` = 5 วินาทีแรกไม่มีขอบทองเลย แล้วค่อยโผล่มาจางทีหลัง
   *    (เกม 4 ชัดสุด — สีทองมาจากคีย์เฟรมล้วน ๆ)
   */
  [data-winlook="fade"] .gate.is-won   { animation: wonGoneGate .9s ease 5s both; }
  [data-winlook="fade"] .g2cell.is-won { animation: wonGoneCell .9s ease 5s both; }
  [data-winlook="fade"] .g4cell.is-won:not(.g4bridge) { animation: wonGoneG4 .9s ease 5s both; }
  [data-winlook="fade"] .g4bridge.is-won { animation: wonGonePill .9s ease 5s both; }
}

/* ══════════════════════════════════════════════════════════════════
   ⭐ **จำนวนรอบที่เล่นมาแล้วที่คลับนี้** (เจ้าของสั่ง 2026-09-26)
      อยู่ใต้ชื่อ/รหัสสมาชิกในกรอบข้อมูลผู้เล่น

   ⚠️ **ต้องเล็กกว่ารหัสสมาชิกเสมอ** — เป็นข้อมูลอ้างอิงที่ดูแวบเดียว
      ใหญ่กว่านี้แล้วมันจะแย่งสายตากับเครดิต ซึ่งเป็นตัวที่ต้องเด่นที่สุด

   ⚠️ กรอบนี้แคบมากบนมือถือ — ตัดด้วย `…` ไม่ให้ดันชื่อจนล้น
   ══════════════════════════════════════════════════════════════════ */
.player__rounds {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: calc(11px * var(--fs, 1));
  line-height: 1.3;
  color: var(--ink-faint);
}

.player__rounds b {
  color: var(--ink-dim);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.player__roundsIco { font-size: calc(10px * var(--fs, 1)); opacity: .85; }

/** ⭐ รอบที่เราลงไว้เพิ่งถูกจ่าย — เด้งครั้งเดียวให้รู้ว่าตัวเลขขยับ */
.player__rounds.is-bump { animation: roundsBump .5s cubic-bezier(.34, 1.56, .64, 1); }

@keyframes roundsBump {
  0%   { transform: none; }
  35%  { transform: scale(1.12); color: var(--gold); }
  100% { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .player__rounds.is-bump { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════
   ⭐ **ปุ่ม "โปรไฟล์" + ปุ่ม "จอไลฟ์" (ปิดจอชั่วคราว)** — เจ้าของสั่ง 2026-10-14
   ══════════════════════════════════════════════════════════════════ */

/** ⭐ ไอคอนโปรไฟล์: หัว+ไหล่ในวงแหวนประที่หมุนช้า ๆ (ทุกเกมใช้ partial เดียวกัน) */
.ptool__ico--prof { width: 18px; height: 18px; }

.ptool__ico--prof .ptool__ring {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-dasharray: 3 4.2;
  opacity: .75;
  transform-origin: 12px 12px;
  animation: ptoolOrbit 9s linear infinite;
}

.ptool--hub { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.ptool--hub:hover .ptool__ring { animation-duration: 2.4s; opacity: 1; }

@keyframes ptoolOrbit { to { transform: rotate(360deg); } }

/** ⭐ ปุ่มจอไลฟ์: เส้นทแยงขึ้นเมื่อมีจอปิดอยู่ (บอกสถานะจากหางตา) */
.ptool--screen .ptool__slash {
  stroke-dasharray: 26;
  stroke-dashoffset: 26;
  transition: stroke-dashoffset .3s ease;
}

.ptool--screen.is-off { color: var(--gold); border-color: color-mix(in srgb, var(--gold) 55%, var(--line)); }
.ptool--screen.is-off .ptool__slash { stroke-dashoffset: 0; }

/** ⭐ เมนูลอยใต้ปุ่ม — สวิตช์ต่อจอ + ปิด/เปิดทั้งหมด */
.scrmenu {
  position: fixed;
  z-index: 72;
  padding: 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: 16px;
  background: linear-gradient(170deg, var(--panel-2), var(--panel));
  box-shadow: var(--shadow);
  animation: ctlRise .18s ease;
}

.scrmenu[hidden] { display: none; }

.scrmenu__h {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0 2px 8px;
  font-weight: 700;
}

.scrmenu__h small { color: var(--ink-faint); font-weight: 400; font-size: .76em; }

.scrmenu__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 46px;
  padding: 0 10px;
  margin-bottom: 6px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.scrmenu__row:hover { border-color: var(--accent); }

.scrmenu__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/** ⭐ สวิตช์แบบ iOS — เขียว = จอเปิด */
.scrmenu__sw {
  position: relative;
  flex: none;
  width: 40px;
  height: 24px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 18%, transparent);
  transition: background .2s ease;
}

.scrmenu__sw i {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .35);
  transition: transform .2s cubic-bezier(.2, .9, .3, 1.2);
}

.scrmenu__sw.is-on { background: var(--tie); }
.scrmenu__sw.is-on i { transform: translateX(16px); }

.scrmenu__all {
  width: 100%;
  min-height: 42px;
  border: 1px dashed color-mix(in srgb, var(--gold) 55%, var(--line));
  border-radius: 12px;
  background: none;
  color: var(--gold);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/** ⭐ จอที่ปิดอยู่: ภาพจางหาย + ม่านบอกว่าปิดเอง พร้อมปุ่มเปิดคืน */
.stream.is-off .stream__video,
.stream.is-off .stream__frame,
.stream.is-off [data-rtc-veil] { visibility: hidden; }

.stream__offVeil {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 6px;
  padding: 16px;
  text-align: center;
  color: var(--ink);
  background:
    radial-gradient(60% 60% at 50% 45%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%),
    repeating-linear-gradient(0deg, rgba(255, 255, 255, .025) 0 2px, transparent 2px 4px),
    #05080d;
  animation: ctlRise .25s ease;
}

.stream__offVeil[hidden] { display: none; }
.stream__offVeil b { font-size: 1em; }
.stream__offVeil small { max-width: 260px; color: var(--ink-faint); font-size: .76em; }

.stream__offIco {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
  animation: ctlPulse 2.6s ease-in-out infinite;
}

.stream__offIco svg { width: 28px; height: 28px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

.stream__offBtn {
  margin-top: 4px;
  min-height: 40px;
  padding: 0 18px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .ptool__ico--prof .ptool__ring,
  .stream__offIco,
  .stream__offVeil,
  .scrmenu { animation: none !important; }
}
