/* ═══════════════════════════════════════════════════════════════
   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%; }

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

/* ⭐ ไลฟ์แนวตั้ง — js ติดคลาสนี้ให้เองหลังอ่านขนาดจริงจาก <video> */
.stream.is-portrait .stream__video { object-fit: cover; }

/**
 * ⭐ **สัดส่วนของ 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 ไม่ต้องย่อ — `object-fit` จัดการให้อยู่แล้ว
   ตั้งซ้ำจะทำให้กรอบดำเกินจำเป็นบนจอที่สัดส่วนไม่ตรง */
.stream.is-portrait  .stream__video,
.stream.is-landscape .stream__video,
.stream.is-square    .stream__video { width: 100%; height: 100%; }

.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);
}

.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); }

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

   🔴 **ทุกกฎในบล็อกนี้อยู่ใต้ `[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;
}

.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)); }

/* ── 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; }
}

/* ══════════════════════════════════════════════════════════════
   ⭐ ผังหน้าจอของโต๊ะ — รอบขยาย 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); }
}

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

.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; }
}


/* ══════════════════════════════════════════════════════════════
   ⭐ **โหมดคอนทราสต์สูง** (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); }
}
