:root {
  --blue: #0c7fd0;
  --cyan: #23c7d9;
  --green: #03a85d;
  --felt: #047a50;
  --gold: #ffd45b;
  --ink: #ffffff;
  --panel: rgba(13, 19, 44, .82);
  --panel-strong: rgba(18, 21, 49, .95);
  --line: rgba(255, 255, 255, .18);
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  margin: 0;
  min-height: 100%;
  font-family: Arial, "Helvetica Neue", sans-serif;
  color: var(--ink);
  background: #07101f;
  /* "丢炸弹的时候像是背景在位移/桌子在位移" -- the bomb's explosion-burst
     photo (170px plus an 18px blur, the biggest fixed-position gift asset
     yet) can push past the viewport edge when it lands near the screen's
     border. Without this, WebKit counts that transformed fixed-position
     overflow toward the page's own scrollable width and rubber-bands the
     whole viewport sideways to compensate -- every earlier gift reaction
     photo (<=130px) was small enough to never trip it. */
  overflow-x: hidden;
}

body:has(.hundred-screen) {
  background: #0d5c46;
}

#app {
  position: relative;
  z-index: 1;
}

.interaction-layer {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
}

.interaction-control {
  position: relative;
  z-index: 51;
  pointer-events: auto;
}

.control-layer {
  position: relative;
  z-index: 52;
}

.hundred-big-winner-layer {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;
}

.background-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.game-background {
  width: min(100vw, 430px);
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0 auto;
  box-shadow: 0 0 70px rgba(0, 0, 0, .45);
}

.game-background-login {
  background:
    linear-gradient(rgba(5, 7, 26, .12), rgba(5, 7, 26, .72)),
    radial-gradient(circle at 80% 22%, rgba(58, 255, 131, .55), transparent 8%),
    radial-gradient(circle at 14% 72%, rgba(255, 203, 62, .72), transparent 12%),
    radial-gradient(circle at 50% 48%, rgba(91, 42, 180, .75), transparent 42%),
    linear-gradient(160deg, #080722, #122c6b 42%, #100719 75%);
}

/* Was a url("./assets/lobby-game-arena-v1.png")/room-game-arena-v1.png
   layer between the gradient and the base color -- that default-background
   image only ever shows when no custom skin is active for this scope (see
   updateBackgroundLayer's has-custom-skin branch), and every scope
   currently has one set, so the file was deleted as unused. Same solid-
   color-fallback treatment as the yacht layers above: the gradient + base
   color alone (already this same shape, see .game-background-login just
   above, which never had an image layer at all) reads fine on its own if a
   skin is ever cleared, instead of a missing image ("默认图用一张纯色背景
   吧"). */
.game-background-lobby {
  background:
    linear-gradient(180deg, rgba(3, 8, 34, .08), rgba(5, 4, 30, .28)),
    #080b2d;
}

.game-background-room {
  background:
    linear-gradient(180deg, rgba(2, 5, 28, .05), rgba(4, 4, 27, .24)),
    #070a2d;
}

/* Yacht room: structurally kept as independent layers (sky/ocean/foreground,
   only the ocean meant to drift) for a possible future revisit, but the
   source images themselves were an abandoned 百人场 animated-background
   attempt and got deleted -- each layer is a plain color instead of a
   missing url() so nothing 404s if useYachtLayers in app.js ever flips back
   on ("代码还是要保留，只是图片要清理掉" / "默认图用一张纯色背景吧"). */
.yacht-room-background {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: #79c9f0;
}

.yacht-room-sky,
.yacht-room-ocean,
.yacht-room-foreground {
  position: absolute;
  pointer-events: none;
}

.yacht-room-sky {
  inset: 0;
  background: #79c9f0;
}

.yacht-room-ocean {
  top: 38%;
  right: -4%;
  bottom: -4%;
  left: -4%;
  z-index: 1;
  background: #0f6fa8;
  transform: translate3d(-.45%, .1%, 0) scale(1.045);
  transform-origin: center;
  will-change: transform;
  animation: yacht-ocean-drift 12s ease-in-out infinite;
}

.yacht-room-foreground {
  inset: 0;
  z-index: 2;
  background: transparent;
}

.yacht-hundred-background .yacht-room-foreground {
  background: transparent;
}

@keyframes yacht-ocean-drift {
  0%,
  100% {
    transform: translate3d(-.45%, .1%, 0) scale(1.045);
  }
  50% {
    transform: translate3d(.45%, -.2%, 0) scale(1.06);
  }
}

@media (prefers-reduced-motion: reduce) {
  .yacht-room-ocean {
    animation: none;
    transform: translate3d(0, 0, 0) scale(1.045);
    will-change: auto;
  }
}

.game-background-hundred {
  background:
    linear-gradient(180deg, rgba(2, 5, 28, .05), rgba(4, 4, 27, .24)),
    #070a2d;
}

/* Texas gets its own green background instead of borrowing 三张牌's purple
   room-arena image/scope -- the mismatched purple behind an already-green
   felt read as an odd, uncomfortable clash. */
.game-background-texas {
  background:
    radial-gradient(circle at 50% 18%, rgba(80, 220, 150, .16), transparent 46%),
    linear-gradient(180deg, rgba(3, 20, 12, .2), rgba(2, 14, 9, .4)),
    linear-gradient(165deg, #0a3322, #062217 55%, #03130d);
}

.game-background.has-custom-skin {
  width: min(100vw, 430px);
  height: 100vh;
  height: 100dvh;
  margin: 0 auto;
  background: var(--skin-background) center / cover no-repeat;
}

.game-frame-overlay {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  z-index: 2;
  width: min(100vw, 430px);
  background: var(--frame-background) center / cover no-repeat;
  transform: translateX(-50%);
  pointer-events: none;
}

body.has-active-skin[data-skin-scope="lobby"] .lobby-screen,
body.has-active-skin[data-skin-scope="hundred"] .hundred-screen {
  background: transparent;
}

body.has-active-skin[data-skin-scope="lobby"] .lobby-bg {
  background: transparent;
}

body.has-active-skin .game-background,
body.has-active-skin .screen {
  box-shadow: none;
}

body.has-active-skin[data-skin-scope="room"] .table-screen {
  background: transparent;
}

button,
input {
  font: inherit;
}

button {
  border: 0;
  color: #fff;
  cursor: pointer;
}

.screen {
  min-height: 100vh;
  position: relative;
  overflow-x: hidden;
  overflow-y: auto;
  width: min(100vw, 430px);
  margin: 0 auto;
  background: radial-gradient(circle at 50% 15%, #1bd2c7 0, #085483 28%, #0a1432 76%);
  box-shadow: 0 0 70px rgba(0, 0, 0, .45);
  /* TEMPORARY debug outline so the game's actual box edges are visible on
     screen -- requested to visualize why width/height differ across
     monitors. Remove once confirmed. */
  outline: 3px solid #ff2d55;
  outline-offset: -3px;
}

.lobby-screen {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}

.login {
  display: flex;
  align-items: center;
  flex-direction: column;
  padding: 14px 14px 34px;
  background:
    linear-gradient(rgba(5, 7, 26, .12), rgba(5, 7, 26, .72)),
    radial-gradient(circle at 80% 22%, rgba(58, 255, 131, .55), transparent 8%),
    radial-gradient(circle at 14% 72%, rgba(255, 203, 62, .72), transparent 12%),
    radial-gradient(circle at 50% 48%, rgba(91, 42, 180, .75), transparent 42%),
    linear-gradient(160deg, #080722, #122c6b 42%, #100719 75%);
}

.login::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(255, 255, 255, .08), transparent 16%, transparent 84%, rgba(255, 255, 255, .08)),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, .04) 0 1px, transparent 1px 54px);
  pointer-events: none;
}

.login::after {
  content: "";
  position: absolute;
  left: -14%;
  right: -14%;
  bottom: 20%;
  height: 92px;
  background: rgba(0, 0, 0, .45);
  box-shadow: 0 0 36px rgba(124, 82, 255, .45);
  transform: skewY(-2deg);
  pointer-events: none;
}

.login-top {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding-top: max(2px, env(safe-area-inset-top));
}

.login-top button {
  height: 38px;
  min-width: 88px;
  padding: 0 12px;
  border-radius: 999px;
  color: #d9f8ff;
  font-size: 15px;
  font-weight: 900;
  background: linear-gradient(#b6c4d1, #344052 45%, #182235);
  border: 2px solid rgba(255, 255, 255, .28);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .55), 0 4px 12px rgba(0, 0, 0, .4);
  text-shadow: 0 2px 2px rgba(0, 0, 0, .7);
}

.login-spacer {
  position: relative;
  z-index: 2;
  height: 104px;
}

.login-check {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 20px 0 0;
  padding: 9px 10px;
  border-radius: 8px;
  color: rgba(255, 255, 255, .82);
  font-size: 13px;
  text-align: center;
  background: rgba(7, 5, 24, .34);
}

.login-check input {
  appearance: none;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  /* Was 14px -- too small a target to reliably tap on a real phone
     ("勾选项框弄大一点，避免玩家点不到"). */
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .88);
  border-radius: 5px;
  background: #fff;
  cursor: pointer;
}

.login-check input:checked {
  border-color: rgba(142, 255, 119, .9);
  background: linear-gradient(#2dff4c, #07a928);
}

.login-check input:checked::after {
  content: "✓";
  color: #fff;
  font-size: 16px;
  font-weight: 900;
  line-height: 1;
}

.terms-link {
  padding: 0;
  color: #ffe37c;
  font-weight: 900;
  text-decoration: underline;
  text-underline-offset: 3px;
  background: transparent;
}

.terms-mask {
  /* Fixed, not absolute -- #overlay-root (where this renders, see
     overlayRoot() in app.js) is a plain static sibling of #app appended
     straight to <body>, so an absolute mask's containing block is the
     initial containing block, sized/positioned off the *document*, not
     the live visual viewport. On real mobile Safari (dynamic address bar,
     safe-area insets) that let the mask's box end up short of the actual
     bottom of the screen -- the bottombar rendered there stayed fully lit
     and un-dimmed, and taps on it fell through to nothing instead of
     closing the modal. Fixed positioning ties this to the true viewport
     instead, matching every other full-screen overlay in this file
     (.interaction-layer, .hundred-big-winner-layer). */
  position: fixed;
  z-index: 90;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 18px;
  background: rgba(0, 0, 0, .58);
  animation: none;
}

.terms-mask.player-card-mask {
  animation: none;
}

.terms-mask.hundred-recharge-mask,
.hundred-recharge-mask .daily-reward-modal {
  animation: none;
}

.terms-mask.daily-reward-mask,
.terms-mask.shop-mask {
  animation: none;
}

.terms-mask.hundred-jackpot-mask {
  animation: none;
}

/* Shifted up (rather than the default vertical center) so it never covers
   the player's own seat frame, which sits near the bottom of the table --
   padding-top is set inline per-render (see lastHandTopOffset()) to line up
   with the live top-row seats; this is just a fallback before that runs. */
.last-hand-mask {
  align-items: flex-start;
  padding-top: 56px;
}

.terms-modal {
  width: min(360px, 100%);
  padding: 22px 18px 18px;
  border: 2px solid rgba(151, 132, 255, .9);
  border-radius: 18px;
  background:
    linear-gradient(rgba(22, 17, 60, .96), rgba(12, 9, 39, .98)),
    radial-gradient(circle at 82% 20%, rgba(253, 226, 112, .18), transparent 22%);
  box-shadow: inset 0 0 0 5px rgba(255, 255, 255, .04), 0 22px 60px rgba(0, 0, 0, .68), 0 0 24px rgba(128, 94, 255, .58);
}

.terms-modal h2 {
  margin: 0 0 14px;
  color: #ffe37c;
  text-align: center;
  text-shadow: 0 3px 0 rgba(0, 0, 0, .45);
}

.terms-modal p {
  margin: 0 0 10px;
  color: rgba(255, 255, 255, .82);
  font-size: 13px;
  line-height: 1.65;
}

.terms-ok {
  display: block;
  width: 170px;
  height: 46px;
  margin: 16px auto 0;
  border-radius: 12px;
  color: #fff;
  font-weight: 900;
  background: linear-gradient(#31ff49, #08bd2b 55%, #04791f);
  border: 2px solid rgba(143, 255, 145, .75);
  box-shadow: inset 0 3px 0 rgba(255, 255, 255, .5), 0 8px 20px rgba(0, 0, 0, .45);
}

.feedback-corner {
  position: absolute;
  z-index: 4;
  left: max(14px, calc((100% - 360px) / 2));
  top: max(72px, calc(env(safe-area-inset-top) + 72px));
  height: 34px;
  min-width: 72px;
  padding: 0 14px;
  border-radius: 999px;
  color: #d9f8ff;
  font-size: 14px;
  font-weight: 900;
  background: linear-gradient(#b6c4d1, #344052 45%, #182235);
  border: 2px solid rgba(255, 255, 255, .28);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .55), 0 4px 12px rgba(0, 0, 0, .4);
  text-shadow: 0 2px 2px rgba(0, 0, 0, .7);
}

/* Same shell as .feedback-corner, positioned directly below it (34px height
   + 8px gap) -- a separate persistent button, not a menu item inside
   feedback ("放在反馈按钮的下方位置，不是放进反馈菜单里面"). Only rendered
   at all when there's an admin-configured announcement to show (see
   renderLogin's own loginAnnouncement check), so it never appears as a
   dead/empty button. */
.announcement-corner {
  top: max(114px, calc(env(safe-area-inset-top) + 114px));
  background: linear-gradient(#ffe37c, #b8862e 45%, #5a3a10);
  border-color: rgba(255, 227, 124, .5);
  color: #3a2400;
  text-shadow: none;
}

.daily-reward-modal {
  width: min(380px, 100%);
  padding: 18px 16px;
  border: 2px solid rgba(151, 132, 255, .9);
  border-radius: 18px;
  background:
    linear-gradient(rgba(22, 17, 60, .96), rgba(12, 9, 39, .98)),
    radial-gradient(circle at 82% 20%, rgba(253, 226, 112, .18), transparent 22%);
  box-shadow: inset 0 0 0 5px rgba(255, 255, 255, .04), 0 22px 60px rgba(0, 0, 0, .68), 0 0 24px rgba(128, 94, 255, .58);
}

.daily-reward-modal header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 14px;
}

.daily-reward-modal h2 {
  margin: 0;
  color: #ffe37c;
  text-shadow: 0 3px 0 rgba(0, 0, 0, .45);
}

/* "百人场奖池后面显示数额即可，下面的当前奖池去掉" -- folds the amount
   into the title itself instead of its own separate status line below. */
.jackpot-modal-amount {
  color: #fff2c9;
  /* Was 14px -- "奖池金额字体再大一点". */
  font-size: 19px;
  font-weight: 700;
  text-shadow: none;
}

.daily-reward-modal header button {
  /* Was 30x30 -- under the ~44pt comfortable touch-target minimum, so a real
     thumb (not a pixel-precise mouse click) often needed a couple of tries
     to land on it. Shared by every modal built on .daily-reward-modal
     (shop, tasks, daily reward, leaderboard, game history, ...). */
  width: 40px;
  height: 40px;
  border-radius: 999px;
  color: #fff;
  font-size: 18px;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .2);
}

.daily-reward-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}

.daily-reward-cell {
  position: relative;
  display: grid;
  gap: 4px;
  place-items: center;
  padding: 10px 4px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, .16);
  background: rgba(0, 0, 0, .32);
}

.daily-reward-cell:nth-child(7) {
  grid-column: 4;
  grid-row: 1 / span 2;
}

.daily-reward-day {
  color: rgba(255, 255, 255, .72);
  font-size: 11px;
  font-weight: 900;
}

.daily-reward-amount {
  color: #fff3a6;
  font-size: 13px;
  font-weight: 900;
  text-align: center;
}

.daily-reward-cell.today {
  border-color: rgba(255, 227, 124, .9);
  background: rgba(255, 200, 60, .18);
  box-shadow: 0 0 14px rgba(255, 210, 65, .38);
}

.daily-reward-cell.claimed {
  opacity: .55;
}

.daily-reward-check {
  position: absolute;
  top: 4px;
  right: 6px;
  color: #8eff77;
  font-size: 10px;
  font-weight: 900;
}

.daily-reward-status {
  min-height: 18px;
  margin: 12px 0 4px;
  color: #8eff77;
  font-size: 12px;
  text-align: center;
}

.shop-modal {
  width: min(400px, 100%);
  max-height: 84vh;
  overflow-y: auto;
}

.shop-section-title {
  margin: 14px 0 8px;
  color: #ffe6a4;
  font-size: 13px;
}

.shop-tier-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.shop-tier-cell {
  display: grid;
  gap: 4px;
  place-items: center;
  padding: 12px 4px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, .16);
  background: rgba(0, 0, 0, .32);
  color: #fff;
}

.shop-tier-cell strong {
  color: #fff3a6;
  font-size: 14px;
}

.shop-tier-cell span {
  color: rgba(255, 255, 255, .7);
  font-size: 12px;
}

.shop-tier-cell:disabled {
  opacity: .5;
}

.shop-relief-row {
  display: grid;
  gap: 8px;
}

.shop-vip-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px;
  border-radius: 12px;
  border: 1px solid rgba(255, 215, 107, .5);
  background: rgba(255, 215, 107, .08);
}

.shop-vip-card.active {
  border-color: rgba(255, 215, 107, .9);
  box-shadow: 0 0 14px rgba(255, 215, 107, .35);
}

.shop-vip-info strong {
  color: #ffd76b;
}

.shop-vip-info p {
  margin: 4px 0;
  color: rgba(255, 255, 255, .75);
  font-size: 12px;
}

.shop-vip-perks {
  margin: 6px 0 0;
  padding-left: 16px;
  color: rgba(255, 255, 255, .65);
  font-size: 11px;
}

/* Green felt skin (matching .poker-table) instead of the shared modal's
   default purple-blue, since this popup is a miniature view of the table. */
.last-hand-modal {
  width: min(380px, 100%);
  height: min(374px, calc(100vh - 12px));
  padding: 14px 14px 20px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  border-color: rgba(31, 255, 124, .5);
  background:
    radial-gradient(ellipse at 50% 24%, rgba(137, 255, 169, .22), transparent 32%),
    linear-gradient(180deg, #0c8a54, #04502f 85%);
  box-shadow: inset 0 0 0 5px rgba(255, 255, 255, .04), 0 22px 60px rgba(0, 0, 0, .68), 0 0 24px rgba(31, 255, 124, .4);
}

/* Taller to fit the 4-row Texas grid (340px) plus header/padding -- "界面
   可以是长方形...比炸金花的界面要长一点". Fixed, not content-dependent, so
   it never resizes/jumps hand to hand ("大小固定...不要跳动"); centered via
   the shared .terms-mask flex centering, same as jinhua's own modal. */
.last-hand-modal.texas-last-hand-modal {
  height: min(420px, calc(100vh - 12px));
}

.last-hand-modal header {
  justify-content: center;
}

/* Mirrors the table's four-corner seating: a fixed 2x2 grid so the modal's
   overall size never changes with player count/name length -- an empty slot
   just renders as blank space of the same size instead of the whole modal
   shrinking, and a long nickname can't push the card column sideways. The
   center cross (::before/::after) visually separates the four quadrants
   since their content otherwise butts right up against the middle. */
.last-hand-grid {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 8px 18px;
  height: 190px;
  flex: 0 0 190px;
}

.last-hand-grid::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 6px;
  bottom: 6px;
  width: 2px;
  transform: translateX(-50%);
  background: linear-gradient(180deg, transparent, rgba(255, 215, 107, .55) 18%, rgba(255, 215, 107, .55) 82%, transparent);
  pointer-events: none;
}

.last-hand-grid::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 6px;
  right: 6px;
  height: 2px;
  transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, rgba(255, 215, 107, .55) 18%, rgba(255, 215, 107, .55) 82%, transparent);
  pointer-events: none;
}

.last-hand-quad {
  display: flex;
  gap: 4px;
  min-width: 0;
}

.last-hand-quad.empty {
  visibility: hidden;
}

.last-hand-quad.winner {
  color: #ffe37c;
}

/* Left-corner seats: avatar hugs the left edge, cards sit to its right
   (toward the center cross). Right-corner seats mirror this via row-reverse
   so the avatar hugs the right edge and cards sit to its left. Top/bottom
   rows anchor to their own edge (rather than centering) so the extra margin
   added to .last-hand-cards-wrap below reads as a deliberate stagger instead
   of dead space. */
.last-hand-quad.tl {
  flex-direction: row;
  justify-self: start;
  align-self: start;
  align-items: flex-start;
}

.last-hand-quad.tr {
  flex-direction: row-reverse;
  justify-self: end;
  align-self: start;
  align-items: flex-start;
}

.last-hand-quad.bl {
  flex-direction: row;
  justify-self: start;
  align-self: end;
  align-items: flex-end;
}

.last-hand-quad.br {
  flex-direction: row-reverse;
  justify-self: end;
  align-self: end;
  align-items: flex-end;
}

.last-hand-quad.self {
  justify-content: center;
  align-items: center;
}

/* Texas' own "上一局底牌" layout: a plain, uniform 2-column x 4-row grid
   (one vertical divider, three horizontal ones) instead of jinhua's 2x2 +
   separate self-section, per "排版需要调整一下，上下应该是四排，三条分割
   线隔开。左右一条分割线隔开，界面可以是长方形，但是大小固定，位置在中
   间，不要跳动" -- a taller, fixed-size rectangle ("比炸金花的界面要长一
   点"), centered, that never resizes regardless of how many of the five
   possible opponents (6-max, up to five besides me) are actually filled in.
   Seats reuse the live table's own t1/t3/t4/t5/t7 names directly as both
   the data slot and the CSS class (see texas_last_hand_corner_slot server-
   side and LAST_HAND_CORNER_ORDER in app.js) -- "me" now lives in this same
   grid too (row 4) instead of a separate section below it -- "排版方式不
   要变，还是按桌子的相对位置来展示对手上一局的底牌" still holds, just
   five slots instead of jinhua's four. See the row-by-row placement rules
   further below for the actual 1/2/2/1 row layout and the 王-shaped
   divider set. */
.last-hand-grid.texas-last-hand-grid {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(4, 1fr);
  gap: 0;
  height: 340px;
  flex: 0 0 340px;
}

/* The shared ::before/::after cross (meant for jinhua's 2x2 grid) would
   otherwise draw a redundant extra center cross on top of the dividers
   below. */
.texas-last-hand-grid::before,
.texas-last-hand-grid::after {
  content: none;
}

/* Row order is single/pair/pair/single -- t4 (directly across from me)
   alone on row 1, t5/t1 paired on row 2, t7/t3 paired on row 3, "me" alone
   on row 4. The vertical divider (see .texas-last-hand-vline) only spans
   rows 2-3, touching but never crossing past the outer two horizontal
   dividers -- so the whole divider set reads as 王: three horizontal
   strokes, one vertical stroke spanning between the top and bottom strokes
   without poking out past either -- "竖立的那条线不要越过上下两条横线
   了，因为排版是上面一个玩家，下面一个玩家，左右各两个玩家，不然就乱
   了" / "线条组成就像一个王字". */
.texas-last-hand-vline {
  position: absolute;
  top: 25%;
  left: 50%;
  width: 2px;
  height: 50%;
  transform: translateX(-50%);
  background: rgba(255, 215, 107, .55);
  pointer-events: none;
}

/* The 王 horizontal strokes -- real elements (not a background trick) so
   each can fade out at both ends like jinhua's own cross does, per "三条横
   线两端用淡出修饰". One per row boundary (25%/50%/75%), same 6px inset
   jinhua's cross uses. */
.texas-last-hand-hline {
  position: absolute;
  left: 6px;
  right: 6px;
  height: 2px;
  transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, rgba(255, 215, 107, .55) 18%, rgba(255, 215, 107, .55) 82%, transparent);
  pointer-events: none;
}

.texas-last-hand-hline.row1 {
  top: 25%;
}

.texas-last-hand-hline.row2 {
  top: 50%;
}

.texas-last-hand-hline.row3 {
  top: 75%;
}

.last-hand-quad.t4 {
  grid-row: 1;
  grid-column: 1 / -1;
  flex-direction: row;
  justify-self: center;
  align-self: center;
  align-items: center;
}

.last-hand-quad.t5 {
  grid-row: 2;
  grid-column: 1;
  flex-direction: row;
  justify-self: start;
  align-self: center;
  align-items: center;
}

.last-hand-quad.t1 {
  grid-row: 2;
  grid-column: 2;
  flex-direction: row-reverse;
  justify-self: end;
  align-self: center;
  align-items: center;
}

.last-hand-quad.t7 {
  grid-row: 3;
  grid-column: 1;
  flex-direction: row;
  justify-self: start;
  align-self: center;
  align-items: center;
}

.last-hand-quad.t3 {
  grid-row: 3;
  grid-column: 2;
  flex-direction: row-reverse;
  justify-self: end;
  align-self: center;
  align-items: center;
}

.texas-last-hand-grid .last-hand-quad.self {
  grid-row: 4;
  grid-column: 1 / -1;
  justify-content: center;
}

.last-hand-player {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 56px;
  flex: 0 0 56px;
}

.last-hand-avatar {
  width: 36px;
  height: 36px;
  border-radius: 9px;
}

.last-hand-name {
  max-width: 56px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 11px;
  text-align: center;
}

.last-hand-status {
  min-height: 12px;
  color: rgba(255, 227, 124, .92);
  font-size: 10px;
  font-weight: 800;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
}

.last-hand-cards-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 0;
}

.last-hand-quad.tl .last-hand-cards-wrap,
.last-hand-quad.tr .last-hand-cards-wrap {
  margin-top: 10px;
}

.last-hand-quad.bl .last-hand-cards-wrap,
.last-hand-quad.br .last-hand-cards-wrap {
  margin-bottom: 10px;
}

.last-hand-quad.self .last-hand-cards-wrap {
  margin: 0;
}

.last-hand-self-section {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 104px;
  flex: 0 0 104px;
  margin-top: 16px;
  padding-top: 18px;
  box-sizing: border-box;
  border-top: 2px solid transparent;
  border-image: linear-gradient(90deg, transparent, rgba(255, 215, 107, .55) 18%, rgba(255, 215, 107, .55) 82%, transparent) 1;
}

.last-hand-cards {
  display: flex;
  justify-content: center;
  gap: 4px;
}

.last-hand-cards .card {
  width: 30px;
  height: 42px;
  font-size: 17px;
}

.last-hand-hand-name {
  font-size: 12px;
  font-weight: 900;
  text-align: center;
}

.last-hand-hand-name.hand-red {
  color: #ff5a4d;
}

.last-hand-hand-name.hand-gold {
  color: #ffd45b;
}

.last-hand-hand-name.hand-blue {
  color: #5cb3ff;
}

.last-hand-hand-name.hand-white {
  color: #fff;
}

.last-hand-delta {
  min-height: 13px;
  font-size: 11px;
  font-weight: 900;
  line-height: 1.15;
  text-align: center;
}

.last-hand-delta.win {
  color: #ffd45b;
}

.last-hand-delta.loss {
  color: rgba(222, 232, 226, .72);
}

/* Reskinned to match the room chat popover's warm palette (see
   .chat-popover.jinhua-chat-popover) instead of its old generic purple-card
   look ("界面画风与目前的游戏画风不匹配，请做成聊天界面的画风"), and its
   stat rows collapsed into .player-card-head flanking the avatar so this
   whole header takes less vertical space, leaving more room below for the
   gift grid as more gifts get added ("不要占一大块空间，因为后期要放更多的礼物"). */
.player-card-modal {
  --card-accent: #ffd08d;
  --card-accent-soft: rgba(255, 164, 113, .25);
  --card-accent-faint: rgba(217, 96, 151, .22);
  width: min(290px, 100%);
  padding: 8px 14px 14px;
  border: 1px solid rgba(255, 190, 130, .5);
  border-radius: 18px;
  background:
    radial-gradient(ellipse 72% 42% at 50% -8%, var(--card-accent-soft), transparent 70%),
    linear-gradient(158deg, rgba(91, 54, 91, .98), rgba(35, 26, 54, .99));
  box-shadow:
    0 16px 35px rgba(0, 2, 28, .56),
    0 0 0 1px rgba(255, 255, 255, .06) inset,
    0 0 22px var(--card-accent-faint);
  /* backdrop-filter dropped -- same reasoning as .hundred-footer-bar's own
     comment elsewhere in this file: live blur-behind is one of the more
     GPU/battery-expensive CSS effects on mobile, and the .98/.99-alpha
     gradient underneath is already opaque enough that the difference is
     imperceptible ("找一下还有哪些因素会让玩家在游戏时手机发烫"). */
  text-align: center;
}

.player-card-modal header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
  padding: 0 1px 5px;
  border-bottom: 1px solid var(--card-accent-soft);
}

.player-card-modal header h2 {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  color: var(--card-accent);
  font-size: 14.5px;
  font-weight: 900;
  letter-spacing: .8px;
  text-shadow: 0 1px 8px var(--card-accent-soft);
}

.player-card-modal header h2::before {
  width: 5px;
  height: 15px;
  content: "";
  border-radius: 999px;
  background: linear-gradient(180deg, #fff8d9, var(--card-accent));
  box-shadow: 0 0 9px var(--card-accent);
}

/* "我的游戏ID放在玩家资料后面，ID：123456这样不会和胜率挤在一起" -- sits
   right after the title text inside the same flex header (h2's own
   display:flex + gap already spaces it out), muted/smaller than the gold
   title so it reads as a secondary label, not a second heading. */
.player-card-id {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0;
  color: rgba(255, 240, 220, .65);
  text-shadow: none;
}

.player-card-modal header button {
  width: 26px;
  height: 26px;
  border-radius: 999px;
  color: var(--card-accent);
  font-size: 16px;
  line-height: 1;
  background: linear-gradient(145deg, rgba(255, 255, 255, .15), var(--card-accent-faint));
  border: 1px solid var(--card-accent-soft);
}

.player-card-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 2px;
}

.player-card-stat-col {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.player-card-stat-col:first-child {
  align-items: flex-end;
}

.player-card-stat-col:last-child {
  align-items: flex-start;
}

.player-card-stat {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: 4px;
  min-width: 62px;
  padding: 4px 9px;
  border-radius: 10px;
  background: rgba(255, 255, 255, .06);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .05);
}

.player-card-stat-col:first-child .player-card-stat {
  text-align: right;
}

.player-card-stat-col:last-child .player-card-stat {
  text-align: left;
}

.player-card-stat span {
  color: rgba(255, 255, 255, .55);
  font-size: 10px;
}

.player-card-stat strong {
  color: var(--card-accent);
  font-size: 12.5px;
  font-weight: 900;
}

.player-card-avatar-wrap {
  flex: 0 0 auto;
}

.player-card-avatar {
  width: 52px;
  height: 52px;
  margin: 0 auto;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 24px;
  font-weight: 900;
  color: #143427;
  background:
    radial-gradient(circle at 34% 24%, rgba(255, 255, 255, .95), transparent 24%),
    linear-gradient(145deg, #fff6a4, #42efd0 58%, #1784d8);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  overflow: hidden;
  border: 2px solid rgba(255, 255, 255, .72);
}

.player-card-avatar.has-image span {
  display: none;
}

.player-card-avatar.has-image {
  cursor: pointer;
}

.report-mask {
  position: fixed;
  z-index: 100;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 18px;
  background: rgba(0, 0, 0, .58);
  animation: none;
}

.report-modal {
  width: min(300px, 100%);
  text-align: left;
}

.report-quick-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 12px;
}

.report-quick-btn {
  height: 36px;
  padding: 0 6px;
  border-radius: 10px;
  border: 1px solid var(--card-accent-soft, rgba(255, 164, 113, .25));
  color: var(--card-accent, #ffd08d);
  font-size: 12px;
  font-weight: 800;
  text-align: center;
  background: rgba(255, 255, 255, .06);
}

.report-quick-btn:disabled {
  opacity: .55;
}

.report-divider {
  position: relative;
  margin: 4px 0 12px;
  color: rgba(255, 255, 255, .5);
  font-size: 12px;
  text-align: center;
}

.report-divider::before,
.report-divider::after {
  content: "";
  position: absolute;
  top: 50%;
  width: calc(50% - 60px);
  border-top: 1px solid rgba(255, 255, 255, .14);
}

.report-divider::before { left: 0; }
.report-divider::after { right: 0; }

.report-success-modal {
  padding: 26px 22px 22px;
  text-align: center;
}

.report-success-icon {
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  margin: 0 auto 14px;
  border-radius: 50%;
  color: #17331c;
  font-size: 34px;
  font-weight: 900;
  background: linear-gradient(#caff9c, #66e37d);
  box-shadow: 0 0 18px rgba(102, 227, 125, .42);
}

.report-success-modal h2 {
  margin: 0 0 8px;
  color: #dfffd7;
}

.report-success-modal p {
  margin: 0 0 13px;
  color: rgba(255, 255, 255, .78);
  font-size: 13px;
}

.report-field {
  display: grid;
  gap: 5px;
  margin-bottom: 8px;
  color: #ffe6a4;
  font-size: 13px;
  font-weight: 900;
}

.report-field input,
.report-field textarea {
  width: 100%;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 10px;
  color: #fff;
  background: linear-gradient(#09071f, #171136);
  box-shadow: inset 0 3px 8px rgba(0, 0, 0, .65);
  outline: none;
  font-family: inherit;
}

.report-field input {
  height: 38px;
  padding: 0 12px;
}

.report-field textarea {
  resize: none;
  padding: 10px 12px;
}

.game-history-modal {
  width: min(360px, 100%);
  max-height: calc((100vh - 36px) / 2);
  max-height: calc((100dvh - 36px) / 2);
  display: flex;
  flex-direction: column;
  text-align: left;
}

.game-history-mask {
  z-index: 130;
}

/* Stacks the feedback-submitted confirm dialog above the feedback-history
   modal (.game-history-mask, z-index:130) it appears on top of. Combined
   with .room-confirm-mask (not standalone) for higher specificity -- that
   class's own z-index:98 rule is defined later in this file, so a
   same-specificity standalone rule here would lose the cascade tie despite
   coming "after" in intent. */
.room-confirm-mask.feedback-submit-success-mask {
  z-index: 135;
}

.game-history-list {
  min-height: 0;
  max-height: none;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: grid;
  flex: 1;
  gap: 6px;
}

.game-history-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 8px;
  align-items: center;
  padding: 8px 10px;
  border-radius: 10px;
  background: rgba(255, 255, 255, .05);
  font-size: 13px;
}

.game-history-head {
  flex: 0 0 auto;
  margin-bottom: 6px;
  background: transparent;
  color: rgba(255, 255, 255, .5);
  font-size: 12px;
  font-weight: 700;
  padding: 0 10px;
}

.game-history-head span:last-child {
  text-align: right;
  min-width: 68px;
}

.game-history-at {
  color: rgba(255, 255, 255, .68);
}

.game-history-room {
  color: #ffe6a4;
  font-weight: 700;
}

.game-history-delta {
  font-weight: 900;
  min-width: 68px;
  text-align: right;
}

.game-history-delta.positive {
  color: #7dffb0;
}

.game-history-delta.negative {
  color: #ff9d9d;
}

.feedback-history-modal {
  /* Was calc(100vh - 36px)/calc(100dvh - 36px) -- nearly the full screen
     height regardless of how little content there actually was, which read
     as an oversized, off-center page rather than a centered dialog card
     ("这个页面太大了...并居中显示"). .terms-mask already centers via
     place-items:center; a real height cap (with room breathing above/below
     it) is what actually makes that centering visible instead of the modal
     just touching both edges of the viewport. */
  width: min(420px, 100%);
  max-height: min(600px, calc(100vh - 72px));
  max-height: min(600px, calc(100dvh - 72px));
  /* .game-history-modal already sets display:flex/flex-direction:column;
     clipping overflow here (rather than letting the box grow past
     max-height, which max-height alone doesn't prevent without an overflow
     rule) pushes all scrolling down into .feedback-history-list below,
     which is the part that actually needs to scroll -- the form itself
     stays put instead of sliding out of view. */
  overflow: hidden;
}

.feedback-history-modal .report-field {
  margin-bottom: 10px;
}

.feedback-history-image-field input[type="file"] {
  color: rgba(255, 255, 255, .7);
  font-size: 12px;
}

.feedback-history-image-preview-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}

.feedback-history-image-preview-item {
  position: relative;
  flex: 0 0 auto;
}

.feedback-history-image-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .5);
  background: rgba(0, 0, 0, .72);
  color: #fff;
  font-size: 13px;
  line-height: 1;
}

.feedback-history-image-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}

.feedback-history-image-list .feedback-history-image {
  margin-top: 0;
}

.feedback-history-image-preview {
  width: 84px;
  height: 84px;
  object-fit: cover;
  border-radius: 10px;
  display: block;
}

.feedback-history-list {
  margin-top: 14px;
  /* min-height:0 is what actually lets a flex child shrink below its
     content size so overflow-y:auto can kick in -- without it this list
     just grew the whole (now flex/overflow:hidden) .feedback-history-modal
     past its own max-height instead of scrolling internally, same fix as
     .game-history-list's own flex:1/min-height:0 pairing. The form above
     (#feedbackHistoryForm) has no flex-grow, so it keeps its natural size
     and only this list actually scrolls. */
  min-height: 0;
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: grid;
  gap: 10px;
  align-content: start;
}

.feedback-history-item {
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(255, 255, 255, .05);
  font-size: 13px;
}

.feedback-history-item-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.feedback-history-at {
  color: rgba(255, 255, 255, .55);
  font-size: 12px;
}

.feedback-history-status {
  margin-left: auto;
  color: #ffe6a4;
  font-size: 12px;
  font-weight: 700;
}

.feedback-history-reason {
  white-space: pre-wrap;
  color: rgba(255, 255, 255, .9);
}

.feedback-history-image {
  margin-top: 8px;
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 8px;
  cursor: zoom-in;
  display: block;
}

.feedback-history-reply {
  margin-top: 8px;
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(126, 211, 255, .12);
  border: 1px solid rgba(126, 211, 255, .3);
}

.feedback-history-reply strong {
  color: #9be0ff;
  font-size: 12px;
}

.feedback-history-reply span {
  color: rgba(255, 255, 255, .5);
  font-size: 11px;
  margin-left: 4px;
}

.feedback-history-reply p {
  margin: 4px 0 0;
  white-space: pre-wrap;
  color: rgba(255, 255, 255, .9);
}

.avatar-viewer-mask {
  position: fixed;
  z-index: 120;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 24px;
  background: transparent;
  animation: overlayFadeIn .16s ease-out both;
}

.avatar-viewer-image {
  width: min(320px, 84vw);
  height: min(320px, 84vw);
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid rgba(255, 255, 255, .82);
  box-shadow: 0 22px 60px rgba(0, 0, 0, .35), 0 0 24px rgba(128, 94, 255, .45);
}

.player-card-balance {
  margin: 4px 0 2px;
  color: var(--card-accent, #ffd08d);
  font-size: 19px;
  font-weight: 900;
  text-shadow: 0 1px 8px rgba(255, 164, 113, .35);
}

.player-card-name {
  margin-bottom: 10px;
  color: #fff;
  font-size: 16px;
  font-weight: 900;
}

.player-card-hint {
  margin: 10px 0 0;
  color: rgba(255, 255, 255, .6);
  font-size: 12px;
}

.player-card-message {
  margin: 10px 0 0;
  color: #8eff77;
  font-size: 12px;
}

.player-card-gifts {
  /* Was flex-wrap -- with the charm label now inline on the name row (see
     giftCharmLabel), the paid gift buttons got wide enough that only one
     fit per row, stacking all four into four rows instead of two ("这里不
     要排四排，排两排"). A fixed 2-column grid keeps it at two rows
     regardless of how much text a button's name+charm label ends up with. */
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  margin-top: 8px;
}

/* "免费礼物一排显示四个" -- fixed 4-column grid instead of the plain
   wrap/center flex above, so the free row always reads as one full row
   (each button shrinks to fit) rather than wrapping onto two lines once a
   fourth free gift didn't fit the flex row's available width. */
.player-card-gifts-free {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.player-card-gifts-free .player-card-gift {
  padding-left: 4px;
  padding-right: 4px;
}

/* "下面一根分界线隔开...分界线两端淡出" -- separates the free row from the
   paid gifts below it, fading to transparent at both ends instead of a
   hard-edged line. */
.player-card-gift-divider {
  height: 1px;
  margin: 10px 2px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .32), transparent);
}

.player-card-gift {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-height: 30px;
  padding: 5px 10px;
  border-radius: 12px;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  background: linear-gradient(100deg, rgba(255, 255, 255, .11), rgba(255, 255, 255, .035));
  border: 1px solid rgba(255, 255, 255, .14);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .045);
}

.player-card-gift:active {
  border-color: var(--card-accent);
  background: var(--card-accent-soft);
}

/* Icon + price together on their own row, name (+ charm label) on the row
   below -- was one single "emoji name charm" line with cost stacked
   underneath, which read as the icon belonging with the name instead of
   the price ("你把图案和-金币放在一排啊，这样很不好看"). */
.player-card-gift-top {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

.player-card-gift-emoji {
  font-size: 15px;
  line-height: 1;
}

.player-card-gift-name {
  font-size: 11px;
}

.player-card-gift-cost {
  font-size: 10px;
  font-weight: 600;
  color: rgba(255, 255, 255, .6);
}

.player-card-gift-cost.free {
  color: #7dffb0;
}

.player-card-gift-charm {
  font-size: 10px;
  font-weight: 600;
}

.player-card-gift-charm.positive {
  color: #ff9ecb;
}

.player-card-gift-charm.negative {
  color: #9ea8ff;
}

.gift-flying-emoji {
  position: fixed;
  /* Anchored at the viewport origin and never touched again -- all actual
     movement happens through the transform (translate3d) playGiftFlight's
     tick() writes every frame, so this box's own layout position never
     changes and never needs a reflow mid-flight. */
  left: 0;
  top: 0;
  will-change: transform;
  z-index: 210;
  font-size: 32px;
  line-height: 1;
  pointer-events: none;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .45));
}

/* Tomato's flying state is a real photo (see GIFT_TOMATO_WHOLE_IMG) inside
   the same .gift-flying-emoji wrapper every other gift's plain emoji text
   sits in -- sized here since font-size doesn't affect an <img>. */
.gift-flying-emoji img {
  display: block;
  width: 40px;
  height: auto;
  /* Rose's bud->bloom swap (see playGiftFlight/GIFT_ROSE_BLOOM_IMG) rides
     this same transition to grow into place instead of snapping to its
     bigger size the instant .rose-bloom-img gets added. */
  transition: width 260ms ease;
}

.gift-flying-emoji img.rose-bloom-img {
  width: 56px;
}

/* "粑粑太小了，要有玩家头像那么大才对" / "还要再大2倍" -- width is set
   inline in playGiftFlight off the actual target avatar's rect times
   GIFT_POOP_SCALE, so this is only the fallback for the rare case that rect
   wasn't available (e.g. no avatar element resolved at all). */
.gift-flying-emoji img.poop-flying-img {
  width: 96px;
}

.gift-tomato-splat {
  position: fixed;
  /* Below .gift-flying-emoji's 210 -- the tomato itself sits on top of its
     own splash photo ("番茄...层级在汁液之上"), not the other way around. */
  z-index: 205;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* The real splash photo (GIFT_TOMATO_SPLAT_IMG) pops in at impact, holds
   fully opaque for ~2s ("免费的礼物都还要停留2秒再淡出" -- tomato is one of
   the four free/category-1 gifts), then drifts a little further down while
   fading out ("番茄汁液要向下淡出"). Total duration must match
   GIFT_TOMATO_SPLAT_LIFE_MS exactly so that removal (see
   spawnGiftTomatoSplat's wrap.remove()) lands right as the fade finishes,
   not mid-animation. */
.tomato-splat-img {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 100px;
  height: auto;
  filter: drop-shadow(0 3px 8px rgba(120, 20, 10, .4));
  transform: translate(-50%, -50%) scale(.3) translateY(0);
  opacity: 0;
  animation: tomato-splat-pop 2800ms cubic-bezier(.2, .9, .3, 1) forwards;
}

@keyframes tomato-splat-pop {
  0% { transform: translate(-50%, -50%) scale(.3) translateY(0); opacity: 0; }
  9% { transform: translate(-50%, -50%) scale(1.08) translateY(0); opacity: 1; }
  11% { transform: translate(-50%, -50%) scale(1) translateY(0); opacity: 1; }
  /* Holds here, fully opaque and still, until ~82% (~2s after settling). */
  72% { transform: translate(-50%, -50%) scale(1) translateY(0); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1) translateY(34px); opacity: 0; }
}

.gift-cigar-dog {
  position: fixed;
  /* Same layer as the tomato splat -- both are a one-shot reaction photo
     that pops in at the impact point and holds/fades, never coexisting
     with the flying-gift layer (210) by the time either shows up. */
  z-index: 205;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* The cigar-in-mouth reaction photo (GIFT_CIGAR_DOG_IMG) pops in at impact,
   holds fully opaque for ~2s ("免费的礼物都还要停留2秒再淡出" -- cigar is
   one of the four free/category-1 gifts), then drifts a little further down
   while fading out -- identical shape to .tomato-splat-img's own keyframes,
   just under its own name/constant (GIFT_CIGAR_DOG_LIFE_MS) so the two
   gifts' timings can be tuned independently later. Total duration must
   match GIFT_CIGAR_DOG_LIFE_MS exactly so spawnGiftCigarDog's wrap.remove()
   lands right as the fade finishes. The photo's own nose isn't at its
   geometric center (there's a wide smoke-trail margin on the left), so
   every translate below uses (-62%, -59%) instead of the usual
   (-50%, -50%) -- measured off the source photo's actual nose position --
   so the bear's nose lands on the target point instead of the image's
   blank middle ("送雪茄的图应该以人物鼻子为中心点，不然送出去有点歪"). */
.cigar-dog-img {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 130px;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .45));
  transform: translate(-62%, -59%) scale(.3) translateY(0);
  opacity: 0;
  animation: cigar-dog-pop 2800ms cubic-bezier(.2, .9, .3, 1) forwards;
}

@keyframes cigar-dog-pop {
  0% { transform: translate(-62%, -59%) scale(.3) translateY(0); opacity: 0; }
  9% { transform: translate(-62%, -59%) scale(1.08) translateY(0); opacity: 1; }
  11% { transform: translate(-62%, -59%) scale(1) translateY(0); opacity: 1; }
  82% { transform: translate(-62%, -59%) scale(1) translateY(0); opacity: 1; }
  100% { transform: translate(-62%, -59%) scale(1) translateY(34px); opacity: 0; }
}

.gift-bomb-burst {
  position: fixed;
  /* Above the face reaction -- the flash reads as happening in front of it. */
  z-index: 206;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* Quick pop, quick fade -- no hold phase, unlike every other reaction photo
   here -- since this is meant to read as the flash of the bang itself, not
   a lingering reaction. Total duration must match GIFT_BOMB_BURST_LIFE_MS
   exactly. The starburst shape is close enough to symmetric that a plain
   (-50%, -50%) anchor is accurate. */
.bomb-burst-img {
  position: absolute;
  left: 50%;
  top: 50%;
  /* "啤酒和炸弹最后再小一点，不然越过边界太多" -- trimmed from 170px. */
  width: 120px;
  height: auto;
  filter: drop-shadow(0 0 18px rgba(255, 140, 20, .6));
  transform: translate(-50%, -50%) scale(.4);
  opacity: 0;
  animation: bomb-burst-pop 500ms cubic-bezier(.15, .9, .3, 1) forwards;
}

@keyframes bomb-burst-pop {
  0% { transform: translate(-50%, -50%) scale(.4); opacity: 0; }
  20% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.3); opacity: 0; }
}

.gift-bomb-face {
  position: fixed;
  z-index: 205;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* The target's "got blown up" reaction photo -- pop/hold/fade shape like
   .cigar-dog-img's keyframes, but "人物还可以停留一会再向上淡出" stretched
   the hold phase out further and flipped the drift from down to up (like
   .kiss-hearts-img's own upward fade) instead of down. Smoke rising off the
   singed hair pulls the photo's own face down and off center the same way
   the cigar/poop photos' subjects were, so every translate below uses
   (-51%, -60%) instead of a plain (-50%, -50%), measured the same way.
   Total duration must match GIFT_BOMB_FACE_LIFE_MS exactly so
   spawnGiftBombFace's wrap.remove() lands right as the fade finishes. */
.bomb-face-img {
  position: absolute;
  left: 50%;
  top: 50%;
  /* "啤酒和炸弹最后再小一点，不然越过边界太多" -- trimmed from 130px. */
  width: 100px;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .45));
  transform: translate(-51%, -60%) scale(.3) translateY(0);
  opacity: 0;
  animation: bomb-face-pop 2400ms cubic-bezier(.2, .9, .3, 1) forwards;
}

@keyframes bomb-face-pop {
  0% { transform: translate(-51%, -60%) scale(.3) translateY(0); opacity: 0; }
  10% { transform: translate(-51%, -60%) scale(1.08) translateY(0); opacity: 1; }
  13% { transform: translate(-51%, -60%) scale(1) translateY(0); opacity: 1; }
  80% { transform: translate(-51%, -60%) scale(1) translateY(0); opacity: 1; }
  100% { transform: translate(-51%, -60%) scale(1) translateY(-34px); opacity: 0; }
}

.gift-kiss-hearts {
  position: fixed;
  /* Same layer as the tomato splat/cigar reaction -- a one-shot photo that
     pops in at the impact point and holds/fades. */
  z-index: 205;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* The rising-heart photo (GIFT_KISS_HEARTS_IMG) pops in at impact, holds
   fully opaque for ~2s ("免费的礼物都还要停留2秒再淡出" -- kiss is one of
   the four free/category-1 gifts), then drifts UP while fading out -- same
   shape as .cigar-dog-img's keyframes, just translateY negative instead of
   positive to read as floating up. This photo (爱心.png) is cropped tight
   around the single heart with its drop-shadow pulling the centroid
   down/left just a little, so every translate below uses (-48%, -42%)
   instead of a plain (-50%, -50%), measured the same way as the cigar's
   nose fix. Total duration must match GIFT_KISS_HEARTS_LIFE_MS exactly so
   spawnGiftKissHearts' wrap.remove() lands right as the fade finishes. */
.kiss-hearts-img {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 75px;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(120, 10, 20, .4));
  transform: translate(-48%, -42%) scale(.3) translateY(0);
  opacity: 0;
  animation: kiss-hearts-pop 2800ms cubic-bezier(.2, .9, .3, 1) forwards;
}

@keyframes kiss-hearts-pop {
  0% { transform: translate(-48%, -42%) scale(.3) translateY(0); opacity: 0; }
  9% { transform: translate(-48%, -42%) scale(1.08) translateY(0); opacity: 1; }
  11% { transform: translate(-48%, -42%) scale(1) translateY(0); opacity: 1; }
  82% { transform: translate(-48%, -42%) scale(1) translateY(0); opacity: 1; }
  100% { transform: translate(-48%, -42%) scale(1) translateY(-34px); opacity: 0; }
}

.gift-beer-mug img {
  display: block;
  width: 44px;
  height: auto;
}

.gift-beer-clink {
  position: fixed;
  /* Same layer as the tomato splat/cigar/kiss reactions. */
  z-index: 205;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* The clinked-mugs photo (GIFT_BEER_CLINK_IMG, splash droplets already
   baked in) pops in right as the two flying mugs meet, holds ~2s ("免费的礼
   物都还要停留2秒再淡出" -- beer is one of the four free/category-1 gifts),
   then drifts down while fading -- same pop/hold/fade shape as the cigar's
   reaction photo. This crop is already tight/symmetric around the mugs
   (no long trailing tail like the cigar/lips had), so a plain (-50%, -50%)
   anchor is accurate here. Total duration must match
   GIFT_BEER_CLINK_LIFE_MS exactly so spawnGiftBeerClink's wrap.remove()
   lands right as the fade finishes. */
.beer-clink-img {
  position: absolute;
  left: 50%;
  top: 50%;
  /* "啤酒和炸弹最后再小一点，不然越过边界太多" -- trimmed from 140px. */
  width: 100px;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .35));
  transform: translate(-50%, -50%) scale(.3) translateY(0);
  opacity: 0;
  animation: beer-clink-pop 2800ms cubic-bezier(.2, .9, .3, 1) forwards;
}

@keyframes beer-clink-pop {
  0% { transform: translate(-50%, -50%) scale(.3) translateY(0); opacity: 0; }
  9% { transform: translate(-50%, -50%) scale(1.08) translateY(0); opacity: 1; }
  11% { transform: translate(-50%, -50%) scale(1) translateY(0); opacity: 1; }
  82% { transform: translate(-50%, -50%) scale(1) translateY(0); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1) translateY(34px); opacity: 0; }
}

.gift-roses-wreath {
  position: fixed;
  /* Above the avatar it's draped over, below the flying-gift layer (210). */
  z-index: 208;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* "玫瑰花送到目标头像，给他带上花圈" -- pops/settles into place over the
   target's avatar (width set inline in spawnGiftRosesWreath, off the
   avatar's own rect), holds, then fades out. "花和大花应该停留五秒"
   stretched the hold phase out to 5s total -- must match
   GIFT_ROSES_WREATH_LIFE_MS exactly so wrap.remove() lands right as the
   fade finishes. */
.roses-wreath-img {
  position: absolute;
  left: 50%;
  top: 50%;
  height: auto;
  filter: drop-shadow(0 4px 10px rgba(120, 10, 20, .45));
  transform: translate(-50%, -50%) scale(.4) rotate(-8deg);
  opacity: 0;
  animation: roses-wreath-pop 5000ms cubic-bezier(.2, .9, .3, 1) forwards;
}

@keyframes roses-wreath-pop {
  0% { transform: translate(-50%, -50%) scale(.4) rotate(-8deg); opacity: 0; }
  8% { transform: translate(-50%, -50%) scale(1.08) rotate(2deg); opacity: 1; }
  10% { transform: translate(-50%, -50%) scale(1) rotate(0deg); opacity: 1; }
  90% { transform: translate(-50%, -50%) scale(1) rotate(0deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.03) rotate(0deg); opacity: 0; }
}

.gift-toast {
  position: fixed;
  left: 50%;
  bottom: 120px;
  transform: translateX(-50%);
  z-index: 205;
  max-width: min(280px, calc(100vw - 40px));
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .78);
  color: #fff6b8;
  font-size: 13px;
  font-weight: 700;
  text-align: center;
  pointer-events: none;
  transition: opacity .2s linear;
}

.player-card-report {
  width: 100%;
  height: 32px;
  margin-top: 8px;
  border-radius: 8px;
  color: rgba(255, 180, 180, .92);
  font-size: 12px;
  font-weight: 700;
  background: rgba(255, 80, 80, .12);
  border: 1px solid rgba(255, 120, 120, .35);
}

.hundred-history-modal {
  width: min(340px, 100%);
}

.hundred-jackpot-mask .hundred-history-modal {
  width: min(340px, calc(100vw - 24px));
  min-height: 410px;
  max-height: calc(100dvh - 24px);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
}

/* Was a fixed 234px + overflow:hidden -- three .jackpot-history-entry rows
   (min-height 90px + margin-bottom 6px each, after the "牌面放在线的上方"
   padding increases below) need ~288px, so that fixed height silently
   clipped the 3rd entry's cards/winner row off the bottom every time
   ("有显示三次记录，但是有一次牌面没显示出来") without ever overflowing
   visibly enough to notice by eye. History is already hard-capped to
   exactly 3 entries client-side (renderHundredJackpotModal's slice(0,3)),
   so there's nothing left to actually clip -- sizing to content instead of
   guessing a pixel number keeps this from silently breaking again the next
   time this padding changes. */
.hundred-jackpot-mask .game-history-list {
  flex: 0 0 auto;
  overflow: visible;
}

.jackpot-history-entry {
  position: relative;
  min-height: 90px;
  /* First pass (6px -> 14px) still wasn't enough room for the 38px-tall
     cards to clear the divider below them -- pushed further, and the hand
     row itself now gets real top clearance from the amount row above it
     too (see .jackpot-history-hand's own padding-top), not just the
     divider below ("牌面放在线的上方，不要压到线，这里需要把线和下方中奖
     记录拉开点距离才可以"). */
  padding-bottom: 22px;
  margin-bottom: 6px;
  border-bottom: 1px solid rgba(134, 247, 255, .32);
}

.jackpot-history-entry:last-child {
  border-bottom: 0;
}

.jackpot-history-hand {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 9px;
  padding: 10px 10px 0;
  color: #ffe88a;
  font-size: 12px;
}

.jackpot-history-cards {
  display: flex;
  gap: 4px;
}

.jackpot-history-cards .card {
  width: 28px;
  height: 38px;
  font-size: 11px;
}

.jackpot-history-winner {
  min-width: 0;
  overflow: hidden;
  color: rgba(255, 255, 255, .88);
  font-size: 12px;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jackpot-winners-toggle {
  margin: 0;
  padding: 4px 8px;
  border-radius: 999px;
  color: #ffe88a;
  font-size: 11px;
  background: rgba(255, 232, 138, .12);
  border: 1px solid rgba(255, 232, 138, .3);
}

.jackpot-winners-list {
  position: absolute;
  right: 8px;
  bottom: 4px;
  z-index: 2;
  display: grid;
  gap: 5px;
  width: min(190px, calc(100% - 16px));
  max-height: 112px;
  margin: 0;
  padding: 8px 10px;
  overflow-y: auto;
  border-radius: 8px;
  background: rgba(0, 0, 0, .22);
}

/* The winner list is a separate modal above the jackpot modal, not an
   expanding child of a history row. */
.terms-mask.hundred-jackpot-winners-mask {
  z-index: 110;
  background: rgba(0, 0, 0, .68);
}

.jackpot-winners-modal {
  width: min(300px, calc(100vw - 32px));
  max-height: min(360px, calc(100dvh - 32px));
  display: flex;
  flex-direction: column;
}

.jackpot-winners-modal .jackpot-winners-list {
  position: static;
  width: auto;
  max-height: 230px;
  margin: 0;
  overflow-y: auto;
}

/* Hundred-room jackpot panels use the felt-and-gold treatment of the table
   itself instead of the generic purple reward-modal skin. */
.hundred-jackpot-mask .hundred-history-modal,
.hundred-jackpot-winners-mask .jackpot-winners-modal {
  position: relative;
  overflow: hidden;
  border: 2px solid rgba(255, 211, 111, .78);
  background:
    radial-gradient(ellipse at 50% -18%, rgba(255, 221, 128, .2), transparent 47%),
    radial-gradient(ellipse at 50% 112%, rgba(20, 143, 91, .22), transparent 54%),
    linear-gradient(160deg, #183d35, #0b241f 58%, #071713);
  box-shadow: inset 0 0 0 1px rgba(255, 250, 214, .11), inset 0 16px 26px rgba(255, 235, 167, .04), 0 22px 56px rgba(0, 0, 0, .68), 0 0 26px rgba(239, 192, 89, .18);
}

.hundred-jackpot-mask .hundred-history-modal::before,
.hundred-jackpot-winners-mask .jackpot-winners-modal::before {
  content: "";
  position: absolute;
  top: 0;
  left: 15%;
  right: 15%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 245, 198, .9), transparent);
}

.hundred-jackpot-mask .hundred-history-modal h2,
.hundred-jackpot-winners-mask .jackpot-winners-modal h2 {
  color: #ffe7a2;
  letter-spacing: 1px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .45), 0 0 10px rgba(255, 208, 103, .22);
}

.hundred-jackpot-mask .hundred-history-modal header button,
.hundred-jackpot-winners-mask .jackpot-winners-modal header button {
  border: 1px solid rgba(255, 227, 152, .38);
  background: rgba(7, 28, 23, .68);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

.hundred-jackpot-mask .daily-reward-status,
.hundred-jackpot-winners-mask .daily-reward-status {
  margin: 0 0 10px;
  padding: 7px 10px;
  border: 1px solid rgba(255, 225, 145, .18);
  border-radius: 9px;
  color: #f8e7b7;
  background: rgba(3, 19, 15, .42);
}

.hundred-jackpot-mask .game-history-head {
  color: rgba(255, 231, 165, .68);
}

.hundred-jackpot-mask .game-history-row:not(.game-history-head) {
  border: 1px solid rgba(255, 230, 164, .12);
  background: linear-gradient(100deg, rgba(255, 244, 205, .07), rgba(4, 27, 20, .4));
}

.hundred-jackpot-mask .jackpot-history-entry {
  border-bottom-color: rgba(255, 221, 139, .18);
}

.hundred-jackpot-mask .jackpot-winners-toggle {
  color: #ffe7a2;
  border-color: rgba(255, 219, 130, .35);
  background: linear-gradient(180deg, rgba(107, 77, 37, .58), rgba(42, 32, 19, .72));
}

.hundred-jackpot-winners-mask .jackpot-winners-list {
  border: 1px solid rgba(255, 226, 149, .16);
  background: rgba(2, 17, 13, .5);
}

.hundred-jackpot-winners-mask .jackpot-winner-row + .jackpot-winner-row {
  padding-top: 6px;
  border-top: 1px solid rgba(255, 230, 164, .1);
}

/* Banker queue shares the same felt, gold trim, and layered cards as the
   jackpot panels while preserving its existing anchor below the banker row. */
.hundred-banker-queue-panel {
  overflow: hidden;
  border-color: rgba(255, 211, 111, .78);
  background:
    radial-gradient(ellipse at 50% -12%, rgba(255, 223, 133, .18), transparent 50%),
    linear-gradient(155deg, rgba(25, 67, 54, .99), rgba(7, 29, 23, .99) 68%, rgba(3, 17, 13, .99));
  box-shadow: inset 0 0 0 1px rgba(255, 250, 214, .1), inset 0 14px 24px rgba(255, 235, 167, .035), 0 18px 40px rgba(0, 0, 0, .58), 0 0 22px rgba(239, 192, 89, .16);
}

.hundred-banker-queue-panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 14%;
  right: 14%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 245, 198, .88), transparent);
}

.hundred-banker-queue-panel h2 {
  display: flex;
  align-items: center;
  gap: 7px;
  color: #ffe7a2;
  letter-spacing: 1px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .45), 0 0 10px rgba(255, 208, 103, .2);
}

/* Same small gold accent bar used before every .player-card-modal header
   title (bot/profile cards, feedback history, ...) -- carries that same
   "card header" signature onto this panel's title without touching its
   own felt-table color scheme, which stays intentionally matched to the
   百人场 table around it rather than the purple-gold card family
   ("上庄排队的界面画风要改"). */
.hundred-banker-queue-panel h2::before {
  width: 5px;
  height: 15px;
  content: "";
  border-radius: 999px;
  background: linear-gradient(180deg, #fff8d9, #ffe7a2);
  box-shadow: 0 0 9px rgba(255, 208, 103, .6);
}

.hundred-banker-queue-panel header button {
  border: 1px solid rgba(255, 227, 152, .38);
  background: rgba(6, 27, 21, .68);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

.hundred-banker-queue-panel .hundred-banker-queue-row {
  min-height: 42px;
  padding: 6px 8px;
  border: 1px solid rgba(255, 228, 154, .1);
  border-radius: 10px;
  background: linear-gradient(100deg, rgba(255, 244, 205, .07), rgba(3, 23, 17, .38));
}

.hundred-banker-queue-panel .hundred-banker-queue-row + .hundred-banker-queue-row {
  margin-top: 6px;
}

.hundred-banker-queue-panel .hundred-banker-queue-avatar {
  border: 1px solid rgba(255, 216, 121, .56);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .32);
}

.hundred-banker-queue-panel .hundred-banker-queue-row.is-me {
  border-color: rgba(255, 221, 132, .48);
  background: linear-gradient(100deg, rgba(255, 206, 98, .16), rgba(10, 44, 31, .46));
}

.hundred-banker-queue-panel .terms-ok {
  margin-top: 12px;
  border: 1px solid rgba(255, 229, 153, .6);
  color: #2d210b;
  background: linear-gradient(180deg, #ffe29a, #cf973d);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .65), 0 4px 10px rgba(0, 0, 0, .3);
}

/* Was a bespoke "felt-and-metal" treatment (warm brown for chat, teal for
   players) -- replaced with the exact same look as 炸金花's own chat button
   (green gradient + inset highlight) instead of a third, room-specific style
   ("聊天按钮好丑，弄成和炸金花表情一样的按钮吧"/"本局玩家的按钮好丑 改"). */
.hundred-screen .hundred-chat-btn,
.hundred-screen .hundred-players-btn {
  display: grid;
  place-items: center;
  overflow: hidden;
  border-width: 1px;
  border-radius: 12px;
  line-height: 1;
  color: #d5ffe0;
  font-weight: 900;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
  background: linear-gradient(#14913c, #04471f);
  border-color: rgba(255, 255, 255, .18);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .25), 0 6px 12px rgba(0, 0, 0, .28);
}

.hundred-screen .hundred-chat-btn:active,
.hundred-screen .hundred-players-btn:active {
  filter: brightness(.9);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, .38), 0 2px 5px rgba(0, 0, 0, .25);
}

.hundred-players-popover {
  border-color: rgba(255, 211, 111, .72);
  background:
    radial-gradient(ellipse at 50% -12%, rgba(255, 223, 133, .16), transparent 50%),
    linear-gradient(155deg, rgba(25, 67, 54, .98), rgba(7, 29, 23, .99) 68%, rgba(3, 17, 13, .99));
  box-shadow: inset 0 0 0 1px rgba(255, 250, 214, .1), inset 0 14px 24px rgba(255, 235, 167, .035), 0 18px 40px rgba(0, 0, 0, .58), 0 0 22px rgba(239, 192, 89, .15);
}

.hundred-players-popover::before {
  content: "";
  position: absolute;
  top: 0;
  left: 14%;
  right: 14%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 245, 198, .88), transparent);
}

.hundred-players-popover h2 {
  color: #ffe7a2;
  letter-spacing: 1px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .45), 0 0 10px rgba(255, 208, 103, .2);
}

.hundred-players-popover header button {
  border-color: rgba(255, 227, 152, .38);
  background: rgba(6, 27, 21, .68);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

.hundred-players-popover .hundred-players-popover-row {
  margin: 0 1px 6px;
  padding: 7px 8px;
  border: 1px solid rgba(255, 228, 154, .1);
  border-radius: 10px;
  background: linear-gradient(100deg, rgba(255, 244, 205, .07), rgba(3, 23, 17, .38));
}

.hundred-players-popover .hundred-players-popover-avatar {
  border: 1px solid rgba(255, 216, 121, .5);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .32);
}

.hundred-players-popover .hundred-players-popover-name {
  color: rgba(255, 248, 223, .94);
}

.hundred-players-popover .hundred-players-popover-balance {
  color: #ffd990;
}

/* Shop: a compact felt-and-gold storefront rather than the generic reward
   dialog, without changing tiers, prices, or purchase actions. */
.shop-modal {
  position: relative;
  overflow-x: hidden;
  border: 2px solid rgba(255, 211, 111, .78);
  background:
    radial-gradient(ellipse at 50% -14%, rgba(255, 221, 128, .22), transparent 44%),
    radial-gradient(ellipse at 50% 112%, rgba(20, 143, 91, .18), transparent 52%),
    linear-gradient(160deg, #1b4439, #0b251f 60%, #071713);
  box-shadow: inset 0 0 0 1px rgba(255, 250, 214, .1), inset 0 16px 26px rgba(255, 235, 167, .04), 0 22px 60px rgba(0, 0, 0, .68), 0 0 26px rgba(239, 192, 89, .18);
}

.shop-modal::before {
  content: "";
  position: absolute;
  top: 0;
  left: 15%;
  right: 15%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 245, 198, .92), transparent);
}

.shop-modal h2 {
  color: #ffe7a2;
  letter-spacing: 1px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .45), 0 0 10px rgba(255, 208, 103, .2);
}

.shop-modal header button {
  border: 1px solid rgba(255, 227, 152, .38);
  background: rgba(6, 27, 21, .68);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

.shop-section-title {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #ffe4a0;
  letter-spacing: .6px;
}

.shop-section-title::after {
  content: "";
  height: 1px;
  flex: 1;
  background: linear-gradient(90deg, rgba(255, 223, 142, .38), transparent);
}

.shop-tier-cell {
  position: relative;
  min-height: 82px;
  grid-template-rows: 1fr auto;
  overflow: hidden;
  border-color: rgba(255, 225, 145, .18);
  background:
    linear-gradient(145deg, rgba(255, 242, 198, .1), transparent 48%),
    linear-gradient(155deg, rgba(39, 78, 63, .94), rgba(10, 31, 25, .97));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 5px 10px rgba(0, 0, 0, .2);
}

.shop-tier-cell::before {
  content: "✦";
  position: absolute;
  top: 7px;
  left: 8px;
  color: rgba(255, 218, 124, .7);
  font-size: 10px;
}

.shop-tier-cell strong {
  position: relative;
  z-index: 1;
  color: #ffe7a2;
  font-size: 17px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .55);
}

.shop-tier-cell .shop-tier-price {
  position: relative;
  z-index: 1;
  color: rgba(230, 255, 239, .74);
  font-size: 16px;
  font-weight: 800;
}

.shop-tier-cell .shop-tier-value {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

.shop-tier-cell .shop-tier-coins {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-end;
  pointer-events: none;
}

.shop-tier-cell .shop-tier-coins i {
  display: block;
  width: 23px;
  height: 9px;
  margin-top: -4px;
  border: 1px solid rgba(255, 244, 193, .78);
  border-radius: 50%;
  background: radial-gradient(ellipse at 38% 24%, #fff6c5, #eab44a 42%, #8e571b 100%);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .38);
}

.shop-tier-cell .shop-tier-coins i:nth-child(2n) {
  transform: translateX(-3px);
}

.shop-tier-cell:not(:disabled):active {
  filter: brightness(.9);
  transform: translateY(1px);
}

.shop-relief-row,
.shop-vip-card {
  border-color: rgba(255, 225, 145, .2);
  background: linear-gradient(110deg, rgba(255, 239, 190, .07), rgba(3, 23, 17, .38));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
}

.shop-relief-row {
  padding: 10px;
  border: 1px solid rgba(255, 225, 145, .2);
  border-radius: 12px;
}

.shop-vip-card.active {
  border-color: rgba(255, 213, 107, .8);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 0 18px rgba(255, 204, 91, .18);
}

.shop-vip-info strong,
.shop-vip-info p,
.shop-vip-perks {
  color: #ffe6a4;
}

.shop-vip-info p,
.shop-vip-perks {
  color: rgba(240, 255, 244, .72);
}

.shop-modal .terms-ok {
  border: 1px solid rgba(255, 229, 153, .62);
  color: #2d210b;
  background: linear-gradient(180deg, #ffe29a, #cf973d);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .65), 0 4px 10px rgba(0, 0, 0, .3);
}

.shop-modal .daily-reward-status {
  color: rgba(240, 255, 244, .74);
}

/* Daily welfare follows the same felt-and-gold language as the shop, while
   keeping the seven-day reward layout and claim flow unchanged. */
.daily-welfare-modal {
  position: relative;
  overflow: hidden;
  border-color: rgba(255, 211, 111, .78);
  background:
    radial-gradient(ellipse at 50% -12%, rgba(255, 221, 128, .2), transparent 46%),
    radial-gradient(ellipse at 50% 112%, rgba(20, 143, 91, .18), transparent 54%),
    linear-gradient(160deg, #1b4439, #0b251f 60%, #071713);
  box-shadow: inset 0 0 0 1px rgba(255, 250, 214, .1), inset 0 16px 26px rgba(255, 235, 167, .04), 0 22px 60px rgba(0, 0, 0, .68), 0 0 26px rgba(239, 192, 89, .18);
}

.daily-welfare-modal::before {
  content: "";
  position: absolute;
  top: 0;
  left: 15%;
  right: 15%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 245, 198, .92), transparent);
}

.daily-welfare-modal h2 {
  color: #ffe7a2;
  letter-spacing: 1px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .45), 0 0 10px rgba(255, 208, 103, .2);
}

.daily-welfare-modal header button {
  border-color: rgba(255, 227, 152, .38);
  background: rgba(6, 27, 21, .68);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

.daily-welfare-modal .daily-reward-cell {
  min-height: 70px;
  overflow: hidden;
  border-color: rgba(255, 225, 145, .18);
  background:
    linear-gradient(145deg, rgba(255, 242, 198, .09), transparent 50%),
    linear-gradient(155deg, rgba(39, 78, 63, .94), rgba(10, 31, 25, .97));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .09), 0 4px 9px rgba(0, 0, 0, .2);
}

.daily-welfare-modal .daily-reward-cell:nth-child(7) {
  border-color: rgba(255, 213, 107, .58);
  background:
    radial-gradient(circle at 50% 30%, rgba(255, 232, 151, .2), transparent 50%),
    linear-gradient(155deg, rgba(101, 76, 38, .9), rgba(37, 30, 18, .97));
}

.daily-welfare-modal .daily-reward-day {
  color: rgba(240, 255, 244, .74);
  /* Was the shared .daily-reward-day's 11px -- "每日福利界面...字体再拉
     大，太小了看起来很累". Scoped to this modal only so the shop/jackpot
     modals sharing the same base classes don't also get bumped. */
  font-size: 13px;
}

.daily-welfare-modal .daily-reward-amount {
  color: #ffe7a2;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .55);
  /* Was 13px -- same reason as .daily-reward-day just above. */
  font-size: 16px;
}

.daily-welfare-modal .daily-reward-cell.today {
  border-color: rgba(255, 224, 133, .95);
  background:
    radial-gradient(circle at 50% 30%, rgba(255, 227, 140, .25), transparent 54%),
    linear-gradient(155deg, rgba(96, 72, 37, .94), rgba(39, 31, 19, .98));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 0 16px rgba(255, 204, 91, .28);
}

.daily-welfare-modal .daily-reward-check {
  padding: 2px 5px;
  border-radius: 999px;
  color: #d7ffe1;
  background: rgba(29, 129, 77, .78);
  /* Was 10px -- same "字体再拉大" reason as .daily-reward-day above. */
  font-size: 11px;
}

.daily-welfare-modal .daily-reward-status {
  padding: 7px 10px;
  border: 1px solid rgba(255, 225, 145, .16);
  border-radius: 9px;
  color: rgba(240, 255, 244, .76);
  background: rgba(3, 19, 15, .4);
  /* Was 12px -- same "字体再拉大" reason as .daily-reward-day above. */
  font-size: 14px;
}

.daily-welfare-modal .terms-ok {
  border: 1px solid rgba(255, 229, 153, .62);
  color: #2d210b;
  background: linear-gradient(180deg, #ffe29a, #cf973d);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .65), 0 4px 10px rgba(0, 0, 0, .3);
}

.task-modal { border-color: rgba(255, 211, 111, .78); background: linear-gradient(160deg, #1b4439, #0b251f 60%, #071713); }
.task-modal h2 { color: #ffe7a2; }
.task-list { display: grid; gap: 8px; }
.task-row { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:10px; border:1px solid rgba(255,225,145,.2); border-radius:12px; background:linear-gradient(110deg, rgba(255,239,190,.07), rgba(3,23,17,.38)); }
.task-row strong,.task-row small { display:block; }
.task-row strong { color:#ffe7a2; font-size:14px; }
.task-row small { margin-top:4px; color:rgba(240,255,244,.72); font-size:12px; }
.task-row .terms-ok { width:64px; flex:0 0 auto; border:1px solid rgba(255,229,153,.62); color:#2d210b; background:linear-gradient(180deg,#ffe29a,#cf973d); }

.jackpot-winner-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
}

.jackpot-winner-name {
  display: flex;
  align-items: center;
  gap: 5px;
  overflow: hidden;
  color: rgba(255, 255, 255, .9);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jackpot-winner-amount {
  flex: none;
  color: #8eff9e;
  font-weight: 900;
  font-size: 12px;
}

.leaderboard-modal {
  width: min(340px, 100%);
}

.leaderboard-tabs {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}

.leaderboard-tab {
  flex: 1;
  padding: 7px 0;
  border-radius: 999px;
  color: rgba(255, 255, 255, .7);
  font-size: 13px;
  font-weight: 700;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .12);
}

.leaderboard-tab.active {
  color: #1a1330;
  background: #ffe88a;
  border-color: #ffe88a;
}

.leaderboard-list {
  display: grid;
  gap: 6px;
  /* Sized to show exactly 10 rows before scrolling (10 * ~45px row + 9 *
     6px gap) instead of the old flat 60vh guess, which on a lot of real
     phone heights only fit 6-7 rows before you had to scroll --
     "排行榜长一点，显示十个玩家的信息，其他十个的向下滑动查看". Still capped
     by vh as a fallback so a genuinely short viewport doesn't get pushed
     off-screen. */
  max-height: min(504px, 60vh);
  overflow-y: auto;
  scrollbar-width: none;
}

.leaderboard-list::-webkit-scrollbar {
  display: none;
}

.leaderboard-row {
  /* Was a plain <div> -- now a <button> so tapping a row opens that
     player's profile card ("金币榜和魅力榜要能点开玩家资料"), which means
     undoing the browser's own default button chrome (opaque background,
     centered text) that the shared `button { border:0; color:#fff; }`
     reset above doesn't already cover. */
  display: flex;
  width: 100%;
  align-items: center;
  gap: 8px;
  padding: 6px 4px;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
  background: transparent;
  text-align: left;
}

.leaderboard-row:disabled {
  opacity: .7;
}

.leaderboard-rank {
  flex: 0 0 auto;
  width: 22px;
  color: rgba(255, 255, 255, .6);
  font-weight: 900;
  text-align: center;
}

.leaderboard-rank-1 { color: #ffd24a; }
.leaderboard-rank-2 { color: #d6d6e6; }
.leaderboard-rank-3 { color: #d9954e; }

.leaderboard-avatar {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
}

.leaderboard-name {
  flex: 1;
  overflow: hidden;
  color: rgba(255, 255, 255, .92);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.leaderboard-value {
  flex: none;
  color: #8eff9e;
  font-weight: 900;
  font-size: 13px;
}

.hundred-history-modal-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
}

/* Fixed at this modal's own bottom-right corner rather than tracking the
   newest dot's own element -- HUNDRED_HISTORY_MODAL_DOT_LIMIT is chosen so
   that dot always lands flush at the end of the last row anyway, right
   above this tag ("右下角要添加new，放在最近胜负走势的下面的固定位置"). */
.hundred-history-modal-new {
  position: absolute;
  right: 0;
  bottom: -14px;
  display: grid;
  place-items: center;
  min-width: 26px;
  height: 14px;
  padding: 0 4px;
  border-radius: 999px;
  color: #1e2a08;
  font-size: 8px;
  font-weight: 900;
  line-height: 1;
  background: #ffd45b;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .45);
}

.hundred-history-modal-label {
  flex: 0 0 auto;
  width: 28px;
  color: #86f7ff;
  font-weight: 900;
  text-align: center;
}

.hundred-history-modal-dots {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  overflow-x: auto;
}

.float-card {
  position: absolute;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 44px;
  height: 60px;
  border-radius: 8px;
  color: #20162e;
  font-weight: 900;
  background: linear-gradient(135deg, #fff, #bfc9ff);
  box-shadow: 0 8px 26px rgba(0, 0, 0, .5);
  opacity: .78;
}

.float-card.c1 {
  left: 28px;
  top: 96px;
  transform: rotate(-28deg);
}

.float-card.c2 {
  right: 26px;
  top: 214px;
  color: #bc1738;
  transform: rotate(21deg);
}

.coin {
  position: absolute;
  z-index: 1;
  width: 48px;
  height: 48px;
  border-radius: 999px;
  background: radial-gradient(circle at 35% 30%, #fff6aa, #ffc933 45%, #b16500 76%);
  box-shadow: 0 0 18px rgba(255, 214, 70, .78);
}

.coin-a {
  left: -12px;
  bottom: 210px;
}

.coin-b {
  right: 34px;
  top: 332px;
  width: 36px;
  height: 36px;
}

.login-panel {
  position: relative;
  z-index: 3;
  width: min(360px, 100%);
  padding: 38px 22px 26px;
  border: 2px solid rgba(151, 132, 255, .86);
  border-radius: 18px;
  background:
    linear-gradient(rgba(22, 17, 60, .9), rgba(12, 9, 39, .94)),
    radial-gradient(circle at 82% 20%, rgba(253, 226, 112, .2), transparent 22%);
  box-shadow: inset 0 0 0 6px rgba(255, 255, 255, .04), 0 22px 60px rgba(0, 0, 0, .62), 0 0 24px rgba(128, 94, 255, .58);
}

.brand {
  margin: 0 0 34px;
  color: #ffe37c;
  font-size: 31px;
  text-align: center;
  letter-spacing: 0;
  text-shadow: 0 3px 0 rgba(0, 0, 0, .45), 0 0 16px rgba(255, 220, 92, .35);
}

.language-picker-panel {
  position: relative;
  z-index: 3;
  width: min(360px, 100%);
  padding: 38px 22px 26px;
  border: 2px solid rgba(151, 132, 255, .86);
  border-radius: 18px;
  display: grid;
  gap: 12px;
  background:
    linear-gradient(rgba(22, 17, 60, .9), rgba(12, 9, 39, .94)),
    radial-gradient(circle at 82% 20%, rgba(253, 226, 112, .2), transparent 22%);
  box-shadow: inset 0 0 0 6px rgba(255, 255, 255, .04), 0 22px 60px rgba(0, 0, 0, .62), 0 0 24px rgba(128, 94, 255, .58);
}

.language-picker-panel .brand {
  font-size: 18px;
  margin-bottom: 12px;
}

.language-picker-option {
  height: 52px;
  border-radius: 12px;
  color: #1c1105;
  font-size: 17px;
  font-weight: 900;
  background: var(--gold);
  border: 2px solid rgba(255, 255, 255, .28);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .5), 0 8px 20px rgba(0, 0, 0, .35);
}

.language-picker-option:active {
  filter: brightness(.94);
}

.muted {
  color: rgba(255, 255, 255, .72);
}

.login-field {
  display: grid;
  grid-template-columns: 54px 1fr;
  align-items: center;
  height: 62px;
  margin-bottom: 18px;
  padding: 0 12px;
  border-radius: 10px;
  background: linear-gradient(#09071f, #171136);
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: inset 0 3px 8px rgba(0, 0, 0, .65), 0 1px 0 rgba(255, 255, 255, .08);
}

.login-field span {
  color: #ffe6a4;
  font-size: 18px;
  font-weight: 900;
}

.login-field input {
  width: 100%;
  height: 48px;
  margin: 0;
  padding: 0 4px;
  border: 0;
  border-radius: 0;
  color: #fff;
  font-size: 20px;
  background: transparent;
  outline: none;
}

/* Browser/OS password managers (Safari, Chrome, iOS AutoFill, etc.) draw
   their own key/reveal icon inside a password field's own right edge -- with
   only 4px of padding here, that icon sits right on top of the "•••" dots
   instead of next to them ("PASSWORD会和密码数字叠加显示在一起"). Reserving
   real space on the right is a display-only fix; the field's name/type/
   autocomplete (and everything else about how login actually works) is
   unchanged. */
.login-field input[type="password"] {
  padding-right: 40px;
}

.login-submit {
  display: block;
  width: min(260px, 100%);
  height: 68px;
  margin: 10px auto 12px;
  border-radius: 12px;
  color: #fff;
  font-size: 32px;
  font-weight: 900;
  background: linear-gradient(#31ff49, #08bd2b 55%, #04791f);
  border: 2px solid rgba(143, 255, 145, .75);
  box-shadow: inset 0 3px 0 rgba(255, 255, 255, .5), 0 0 22px rgba(0, 255, 87, .42), 0 10px 24px rgba(0, 0, 0, .45);
  text-shadow: 0 2px 2px rgba(0, 0, 0, .55);
}

.login-panel .login-field {
  width: 100%;
}

.login-methods {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: minmax(0, 220px);
  justify-content: center;
  width: min(360px, 100%);
  margin-top: 34px;
}

.method {
  height: 58px;
  border-radius: 999px;
  font-size: 22px;
  font-weight: 900;
  border: 3px solid rgba(255, 255, 255, .22);
  box-shadow: inset 0 3px 0 rgba(255, 255, 255, .32), 0 10px 24px rgba(0, 0, 0, .52);
  text-shadow: 0 2px 2px rgba(0, 0, 0, .65);
}

.method.guest {
  background: linear-gradient(#23e8cd, #058d78 58%, #03483f);
}

.method.email {
  background: linear-gradient(#ff62dd, #a018aa 58%, #4e0c67);
}

.login-tip {
  position: relative;
  z-index: 3;
  width: min(360px, 100%);
  margin: 0 auto;
  color: rgba(255, 255, 255, .6);
  font-size: 11px;
  line-height: 1.5;
  text-align: center;
}

.login-tip-top {
  margin-top: max(4px, env(safe-area-inset-top));
  padding: 8px 12px;
  border-radius: 999px;
  color: rgba(255, 255, 255, .78);
  font-size: 12px;
  background: rgba(5, 7, 26, .42);
}

.app-version {
  position: relative;
  z-index: 3;
  margin-top: 8px;
  color: rgba(255, 255, 255, .48);
  font-size: 11px;
  text-align: center;
}


.primary {
  height: 48px;
  min-width: 148px;
  padding: 0 22px;
  border-radius: 8px;
  color: #142700;
  font-weight: 800;
  background: linear-gradient(#ffe486, #ffb91e);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .55), 0 8px 24px rgba(255, 192, 46, .22);
}

.topbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  background: linear-gradient(180deg, rgba(8, 9, 26, .95), rgba(30, 28, 61, .88));
  border-bottom: 1px solid var(--line);
}

.topbar {
  position: relative;
  justify-content: center;
  min-height: 58px;
  /* 10px was only ever a fallback for browsers that don't report
     safe-area-inset-top at all -- it was never meant to be the real
     clearance on a Dynamic Island phone. That only actually mattered once
     the page could render edge-to-edge (viewport-fit=cover, added for the
     "Add to Home Screen" standalone mode), which is exactly when the
     avatar/name/settings row started colliding with the island. 46px is a
     safe floor even if env() under-reports for some reason; env() still
     wins whenever it's actually larger. */
  padding-top: max(46px, env(safe-area-inset-top));
}

.exit-btn,
.ghost {
  height: 40px;
  padding: 0 14px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .12);
}

.exit-btn {
  position: absolute;
  left: 10px;
  bottom: 9px;
  min-width: 58px;
  font-weight: 900;
  background: linear-gradient(#45415d, #17172d);
  border: 1px solid rgba(255, 255, 255, .18);
}

.room-menu-btn,
.hundred-menu-btn {
  width: 44px;
  min-width: 44px;
  padding: 0;
  color: #eef2f6;
  font-size: 27px;
  line-height: 1;
  background: transparent;
  border: 0;
  box-shadow: none;
}

.room-menu-popover {
  position: fixed;
  /* .room-menu-btn (jinhua/Texas) was moved further down to clear the
     announcement ticker -- this needs to clear the button's new lower
     position (top offset ~46px, 43px tall) too, or they overlap again. */
  top: calc(max(10px, env(safe-area-inset-top)) + 100px);
  left: max(10px, calc((100vw - 430px) / 2 + 10px));
  z-index: 95;
  width: 178px;
  overflow: hidden;
  border-radius: 8px;
  color: #f4f4f4;
  background: rgba(36, 36, 39, .98);
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: 0 14px 28px rgba(0, 0, 0, .42);
  animation: settingsPop .14s ease-out both;
}

.room-menu-popover button {
  display: grid;
  grid-template-columns: 52px 1fr;
  align-items: center;
  width: 100%;
  height: 66px;
  color: #f1f1f1;
  background: transparent;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}

.room-menu-popover button:last-child {
  border-bottom: 0;
}

.room-menu-popover span {
  color: #d9d9d9;
  font-size: 29px;
  text-align: center;
}

.room-menu-popover strong {
  font-size: 24px;
  font-weight: 500;
  letter-spacing: 0;
  text-align: left;
}

.room-menu-popover button:active {
  background: rgba(255, 255, 255, .08);
}

.room-menu-popover button:disabled {
  color: rgba(255, 255, 255, .42);
  cursor: not-allowed;
  filter: grayscale(.45);
}

.room-menu-popover button:disabled span,
.room-menu-popover button:disabled strong {
  color: rgba(255, 255, 255, .42);
}

.room-info-popover {
  position: fixed;
  top: calc(max(10px, env(safe-area-inset-top)) + 74px);
  left: 50%;
  z-index: 96;
  width: min(330px, calc(100vw - 26px));
  transform: translateX(-50%);
}

.room-info-popover section {
  padding: 14px 16px 16px;
  border-radius: 12px;
  color: #f8fbff;
  background: linear-gradient(180deg, rgba(31, 32, 38, .98), rgba(19, 21, 27, .98));
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow: 0 16px 30px rgba(0, 0, 0, .45);
  animation: overlayPanelIn .16s cubic-bezier(.2, .84, .24, 1) both;
}

.room-info-popover header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.room-info-popover strong {
  color: #ffe37c;
  font-size: 20px;
}

.room-info-popover button {
  width: 30px;
  height: 30px;
  color: #f4fbff;
  font-size: 24px;
  background: rgba(255, 255, 255, .1);
  border-radius: 8px;
}

.room-info-popover p {
  margin: 8px 0 0;
  color: #dce8e7;
  font-size: 14px;
  line-height: 1.45;
}

/* Warm gold/purple treatment (matches .player-card-modal/.chat-popover
   elsewhere) instead of the old flat gray box -- "界面画风有点单调，请做
   精致一点". Applies to all three rooms' rule panels (renderRoomInfoPanel/
   renderHundredInfoPanel/renderTexasInfoPanel share this one markup
   builder now). */
.rule-info-popover section {
  padding: 14px 16px 16px;
  border: 1px solid rgba(255, 190, 130, .5);
  border-radius: 16px;
  background:
    radial-gradient(ellipse 72% 42% at 50% -8%, rgba(255, 164, 113, .22), transparent 70%),
    linear-gradient(158deg, rgba(91, 54, 91, .98), rgba(35, 26, 54, .99));
  box-shadow: 0 16px 30px rgba(0, 0, 0, .5), 0 0 0 1px rgba(255, 255, 255, .05) inset;
}

.rule-info-popover .rule-info-icon {
  margin-right: 6px;
  color: #ffd08d;
  font-size: 16px;
}

.rule-info-popover header {
  margin-bottom: 4px;
}

.rule-info-popover header strong {
  flex: 1;
}

/* Fixed budget instead of letting the popover grow with however many rules
   a given room has -- scrolls internally past that ("界面不要做太长，可以
   让玩家滑动查看"). */
.rule-info-scroll {
  max-height: min(62vh, 480px);
  overflow-y: auto;
  margin: 0 -4px;
  padding: 0 4px;
}

.rule-hand-examples {
  display: grid;
  gap: 6px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(255, 255, 255, .12);
}

.rule-hand-example {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 2px;
}

.rule-hand-example-cards {
  display: flex;
  gap: 3px;
  flex: 0 0 auto;
}

.rule-hand-example-card {
  width: 20px;
  height: 28px;
  font-size: 12px;
}

.rule-hand-example-card .card-rank {
  left: 2px;
  top: 2px;
}

.rule-hand-example-card .card-suit {
  right: 2px;
  bottom: 1px;
}

.rule-hand-example-label {
  color: #ffe9c2;
  font-size: 14px;
  font-weight: 700;
}

.rule-info-text p:first-child {
  margin-top: 8px;
}

.room-confirm-mask {
  position: fixed;
  inset: 0;
  z-index: 98;
  display: grid;
  place-items: center;
  padding: 18px;
  background: rgba(0, 0, 0, .34);
  animation: overlayFadeIn .16s ease-out both;
}

.room-confirm-panel {
  width: min(320px, calc(100vw - 36px));
  padding: 18px;
  border-radius: 12px;
  color: #f7fbff;
  background: linear-gradient(180deg, rgba(36, 35, 49, .98), rgba(18, 20, 29, .98));
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: 0 18px 36px rgba(0, 0, 0, .48);
  animation: overlayPanelIn .16s cubic-bezier(.2, .84, .24, 1) both;
}

.room-confirm-panel strong {
  display: block;
  color: #ffe37c;
  font-size: 20px;
  text-align: center;
}

.room-confirm-panel p {
  margin: 10px 0 16px;
  color: #dbe5e5;
  font-size: 14px;
  line-height: 1.45;
  text-align: center;
}

.login-announcement-panel {
  max-height: min(70vh, 480px);
  overflow-y: auto;
}

.login-announcement-panel p {
  white-space: pre-line;
  text-align: left;
}

.room-confirm-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.room-confirm-actions button {
  height: 42px;
  border-radius: 9px;
  color: #fff;
  font-size: 15px;
  font-weight: 900;
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .22), 0 4px 0 rgba(0, 0, 0, .25);
}

.confirm-cancel {
  background: linear-gradient(#586070, #252b35);
}

.confirm-danger {
  background: linear-gradient(#ff7969, #b52325);
}

.texas-buyin-panel {
  position: relative;
  width: min(300px, calc(100vw - 36px));
  padding: 20px 18px 18px;
  border-radius: 14px;
  color: #f7fbff;
  background: linear-gradient(180deg, rgba(36, 35, 49, .98), rgba(18, 20, 29, .98));
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: 0 18px 36px rgba(0, 0, 0, .48);
  animation: overlayPanelIn .16s cubic-bezier(.2, .84, .24, 1) both;
}

.texas-buyin-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .08);
  color: #dbe5e5;
  font-size: 18px;
  line-height: 1;
  display: grid;
  place-items: center;
}

.texas-buyin-head {
  display: flex;
  justify-content: center;
  gap: 22px;
  margin-bottom: 16px;
}

.texas-buyin-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.texas-buyin-stat span {
  font-size: 12px;
  color: rgba(219, 229, 229, .68);
}

.texas-buyin-stat strong {
  font-size: 15px;
  color: #ffe37c;
  font-weight: 900;
}

.texas-buyin-amount {
  text-align: center;
  font-size: 26px;
  font-weight: 900;
  color: #ffe37c;
  margin-bottom: 14px;
  text-shadow: 0 2px 6px rgba(0, 0, 0, .4);
}

.texas-buyin-slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}

.texas-buyin-step {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: linear-gradient(#586070, #252b35);
  color: #fff;
  font-size: 18px;
  font-weight: 900;
  display: grid;
  place-items: center;
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .18), 0 3px 0 rgba(0, 0, 0, .25);
}

.texas-buyin-slider-row input[type="range"] {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, #ffce54 var(--value, 50%), rgba(255, 255, 255, .16) var(--value, 50%));
}

.texas-buyin-slider-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #ffce54;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .4);
}

.texas-buyin-slider-row input[type="range"]::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid #ffce54;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .4);
}

.texas-buyin-start {
  width: 100%;
  height: 44px;
  border-radius: 10px;
  color: #2a1600;
  font-size: 16px;
  font-weight: 900;
  background: linear-gradient(#ffe37c, #ffb64d);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .5), 0 4px 0 rgba(0, 0, 0, .25);
}

.texas-buyin-option {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 10px 0;
  color: #dbe5e5;
  font-size: 12px;
  line-height: 1.3;
}

.texas-buyin-option input[type="checkbox"] {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  accent-color: #ffb64d;
}

/* Reused inside .room-menu-popover (the texas-menu's in-room auto-buyin
   toggles) alongside its own <button> rows -- margin reset to match
   their own box-model instead of .texas-buyin-option's dialog-oriented
   spacing, plus the same row height/border/padding so it reads as one
   consistent list rather than a floating checkbox tacked on the end. */
.texas-menu-option {
  margin: 0;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, .08);
  font-size: 15px;
  color: #f1f1f1;
}

.texas-menu-option:last-child {
  border-bottom: 0;
}

.page-title {
  color: #ffe37c;
  font-size: 22px;
  font-weight: 900;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .65), 0 0 12px rgba(255, 220, 92, .32);
}

.balance {
  position: absolute;
  right: 10px;
  bottom: 9px;
  padding: 8px 12px;
  border-radius: 999px;
  color: var(--gold);
  font-weight: 900;
  background: rgba(0, 0, 0, .35);
}


.lobby-topbar {
  justify-content: center;
}

.lobby-stats {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  max-width: 190px;
}

.lobby-stats span {
  /* Was display:grid stacking a text label ("游戏豆"/"荣誉") under the
     number -- that label is gone now (see lobbyTopbar's own comment), just
     an icon beside the number, so this is a single-row flex pill instead
     ("按钮太丑了...不要游戏豆这三个字，不要荣誉这两个字，排版太乱"). */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 0;
  padding: 5px 10px;
  border-radius: 999px;
  text-align: center;
  background: rgba(2, 9, 28, .42);
  border: 1px solid rgba(255, 227, 124, .18);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 3px 10px rgba(0, 0, 0, .2);
}

.lobby-stats strong {
  overflow: hidden;
  color: #ffe37c;
  font-size: 14px;
  line-height: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-shadow: 0 1px 0 rgba(0, 0, 0, .52);
}

.lobby-stats em {
  font-style: normal;
  font-size: 13px;
  line-height: 1;
}

.profile-btn {
  position: static;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  padding: 3px;
  /* Was 14px (a rounded square) -- "玩家头像框应该做成圆的，因为头像也是
     圆的", matching every other circular avatar frame in the app. */
  border-radius: 50%;
  color: #efffff;
  background: linear-gradient(180deg, rgba(21, 55, 74, .94), rgba(8, 17, 40, .94));
  border: 1px solid rgba(134, 242, 255, .32);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 5px 14px rgba(0, 0, 0, .25);
}

.profile-avatar {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  /* Was 10px, matching the old rounded-square .profile-btn frame -- now
     circular to match it (see .profile-btn's own comment). */
  border-radius: 50%;
  color: #143427;
  font-size: 14px;
  font-weight: 900;
  background:
    radial-gradient(circle at 34% 24%, rgba(255, 255, 255, .95), transparent 24%),
    linear-gradient(145deg, #fff6a4, #42efd0 58%, #1784d8);
  border: 2px solid rgba(255, 255, 255, .82);
  background-size: cover;
  background-position: center;
}

.profile-avatar.has-image {
  color: transparent;
}

.settings-btn {
  position: static;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  color: #dffcff;
  font-size: 31px;
  line-height: 1;
  background: radial-gradient(circle at 34% 24%, rgba(255, 255, 255, .24), transparent 28%), linear-gradient(#21335c, #090e29);
  border: 1px solid rgba(143, 223, 255, .32);
  box-shadow: 0 0 14px rgba(103, 216, 255, .22), inset 0 1px 0 rgba(255, 255, 255, .18);
}

.settings-btn:active,
.profile-btn:active {
  transform: translateY(2px);
  filter: brightness(.95);
}

.settings-mask {
  position: fixed;
  top: 64px;
  right: max(10px, calc((100vw - 430px) / 2 + 10px));
  z-index: 100;
  width: min(300px, calc(100vw - 20px));
  pointer-events: none;
}

.settings-modal {
  width: 100%;
  max-height: calc(100vh - 138px);
  overflow: auto;
  padding: 10px 12px 12px;
  border-radius: 12px;
  color: #fff;
  pointer-events: auto;
  /* Was an indigo/cyan skin -- recolored to the same warm purple-gold
     .player-card-modal gradient as the profile/bot-card family so 设置 and
     玩家资料 (both opened from the same lobby avatar/gear buttons) read as
     one consistent app instead of two ("设置界面的画风与游戏不符"). */
  background:
    radial-gradient(ellipse 72% 42% at 50% -8%, rgba(255, 164, 113, .25), transparent 70%),
    linear-gradient(158deg, rgba(91, 54, 91, .98), rgba(35, 26, 54, .99));
  border: 1px solid rgba(255, 190, 130, .5);
  box-shadow: 0 16px 35px rgba(0, 2, 28, .56), 0 0 0 1px rgba(255, 255, 255, .06) inset, 0 0 22px rgba(217, 96, 151, .22);
}

.settings-modal.entering {
  animation: settingsPop .14s ease-out both;
}

.room-settings-mask {
  top: calc(max(10px, env(safe-area-inset-top)) + 74px);
  right: max(10px, calc((100vw - 430px) / 2 + 10px));
  width: min(330px, calc(100vw - 20px));
}

.room-settings-mask .settings-modal {
  padding: 4px 12px;
  border-radius: 10px;
}

.room-settings-mask .settings-list {
  gap: 0;
}

.room-settings-mask .settings-row {
  min-height: 58px;
}

.settings-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(255, 190, 130, .3);
}

.settings-head h2 {
  margin: 0;
  color: #ffd08d;
  font-size: 20px;
  line-height: 1;
  letter-spacing: 0;
  text-shadow: 0 1px 8px rgba(255, 164, 113, .25);
}

.settings-close {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  color: #ffd08d;
  font-size: 28px;
  line-height: 1;
  background: transparent;
}

.settings-list {
  display: grid;
}

.settings-row {
  display: grid;
  grid-template-columns: minmax(74px, .75fr) minmax(0, 1.25fr);
  align-items: center;
  min-height: 48px;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(255, 190, 130, .22);
  color: #fff;
  font-size: 14px;
  line-height: 1.15;
  text-align: left;
  background: transparent;
}

.upgrade-box {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
}

.account-upgrade-locked {
  color: rgba(255, 255, 255, .55);
  font-size: 12px;
  text-align: right;
  max-width: 160px;
}

.settings-input {
  width: 100%;
  min-width: 0;
  height: 30px;
  padding: 0 8px;
  border-radius: 6px;
  color: #172129;
  font-size: 12px;
  background: rgba(237, 255, 255, .9);
  border: 1px solid rgba(255, 255, 255, .68);
}

.settings-action {
  height: 32px;
  border-radius: 6px;
  color: #202332;
  font-size: 13px;
  font-weight: 900;
  border: 1px solid rgba(255, 255, 255, .62);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .48), 0 2px 0 rgba(0, 0, 0, .32);
}

.settings-action.pale {
  /* Was a pale cyan (#eaffff/#a7ccd2) -- warmed to match the panel's own
     recolored purple-gold skin instead of standing out as a leftover blue
     accent. */
  background: linear-gradient(180deg, #fff3e2, #d9b48f);
}

.settings-action.gold {
  background: linear-gradient(90deg, #986b1c, #fff17f 52%, #dfa72d);
}

.settings-action:disabled,
.settings-input:disabled {
  opacity: .62;
}

.volume-row {
  grid-template-columns: minmax(74px, .72fr) minmax(0, 1.28fr);
}

.volume-control {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 28px;
  align-items: center;
  gap: 7px;
}

.volume-control input {
  width: 100%;
  height: 22px;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
}

.volume-control input::-webkit-slider-runnable-track {
  height: 9px;
  border-radius: 999px;
  background: linear-gradient(90deg, #16d86b var(--value, 50%), rgba(6, 14, 40, .86) 0);
  border: 1px solid rgba(255, 255, 255, .28);
  box-shadow: inset 0 2px 3px rgba(255, 255, 255, .18), inset 0 -2px 3px rgba(0, 0, 0, .28);
}

.volume-control input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -8px;
  border-radius: 50%;
  background: linear-gradient(180deg, #fff, #c9c9c9);
  border: 3px solid rgba(255, 255, 255, .9);
  box-shadow: 0 3px 8px rgba(0, 0, 0, .42);
}

.volume-control input::-moz-range-track {
  height: 9px;
  border-radius: 999px;
  background: rgba(6, 14, 40, .86);
}

.volume-control input::-moz-range-progress {
  height: 9px;
  border-radius: 999px;
  background: #16d86b;
}

.volume-control input::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: linear-gradient(180deg, #fff, #c9c9c9);
  border: 3px solid rgba(255, 255, 255, .9);
}

.volume-control strong {
  color: #eaffff;
  font-size: 12px;
  text-align: right;
}

/* Replaced the native <select> -- its own open dropdown list is rendered by
   the OS/browser chrome, which CSS can barely touch (plain white background,
   default system font, no way to reach the game's own look) ("这个下拉菜单
   是网页默认的风格？太丑了，弄成与游戏风格一致"). This is a plain button +
   absolutely-positioned popover instead, fully styleable. */
.language-dropdown {
  position: relative;
  justify-self: end;
}

/* .settings-row siblings below (弹幕/游戏记录/...) are plain unpositioned
   flow content, but promoteInteractiveElements() (app.js) stamps every
   button/input in the modal with .interaction-control (z-index:51, see
   that class further up this file) on every render -- including the
   barrage row's own toggle button. z-index:50 here used to lose to that
   sibling's 51 and get visually covered by it ("下拉菜单弹出后，被下面的
   开关挡住了，导致无法点击"). Needs to clear .interaction-control's 51 (and
   .control-layer's 52), not just look high in isolation. */
.language-row-open {
  position: relative;
  z-index: 53;
}

.language-dropdown-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 10px;
  border-radius: 7px;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  background: linear-gradient(90deg, rgba(5, 14, 31, .88), rgba(21, 24, 61, .88));
  border: 1px solid rgba(255, 190, 130, .3);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .6);
}

.language-dropdown-btn b {
  color: var(--card-accent, #ffd08d);
  font-size: 10px;
}

/* Drops straight down from the button (never centered over the row) so it
   never covers the "语言选择" label it belongs to ("弹出来是往下弹出，不要
   居中弹出挡住原来的语言"). */
.language-dropdown-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  display: grid;
  min-width: 168px;
  overflow: hidden;
  border-radius: 10px;
  border: 1px solid rgba(255, 190, 130, .4);
  background: linear-gradient(158deg, rgba(91, 54, 91, .98), rgba(35, 26, 54, .99));
  box-shadow: 0 12px 28px rgba(0, 0, 0, .5);
}

.language-dropdown-option {
  /* Bumped from a ~33px-tall tap target (10px/12px padding, 13px text) up
     past the usual ~44px minimum comfortable touch-target size -- "语言选择
     的下拉菜单...要很小心才能点中". */
  min-height: 46px;
  padding: 13px 16px;
  color: #fff;
  font-size: 15px;
  font-weight: 700;
  text-align: left;
  background: transparent;
}

.language-dropdown-option.active {
  color: var(--card-accent, #ffd08d);
}

.language-dropdown-option:active {
  background: rgba(255, 255, 255, .1);
}

.language-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-width: 0;
  height: 32px;
  padding: 0 7px 0 10px;
  border-radius: 7px;
  color: #fff;
  font-size: 13px;
  background: linear-gradient(90deg, rgba(5, 14, 31, .88), rgba(21, 24, 61, .88));
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .6);
}

.language-chip b {
  display: grid;
  place-items: center;
  width: 32px;
  height: 26px;
  border-radius: 6px;
  color: #172044;
  font-size: 18px;
  background: linear-gradient(180deg, #fff, #bde9ff);
  box-shadow: 0 0 10px rgba(130, 226, 255, .8);
}

.settings-message {
  margin-top: 12px;
  padding: 7px 8px;
  border-radius: 7px;
  color: #fff7ae;
  font-size: 12px;
  text-align: center;
  background: rgba(0, 0, 0, .22);
}

.profile-popover {
  position: fixed;
  top: 64px;
  left: max(10px, calc((100vw - 430px) / 2 + 10px));
  /* Was 80 -- tied with .announcement-history-panel-floating's own z-index,
     and neither #overlay-root nor #announcement-floating-root establishes a
     stacking context of its own, so the tie was resolved purely by which
     root got (re)appended to <body> later, letting the announcement bar's
     floating dropdown cover the top of an already-open profile popover
     ("大厅玩家的资料点开后，会被公告栏遮挡"). An open profile panel should
     always win over a background announcement dropdown. */
  z-index: 85;
  width: min(300px, calc(100vw - 20px));
  pointer-events: none;
}

.profile-panel {
  width: 100%;
  padding: 11px 12px 12px;
  border-radius: 12px;
  color: #fff;
  pointer-events: auto;
  /* Was a cyan/navy skin, left over from before the profile/bot-card family
     ( .player-card-modal, used for other players' cards and this same
     player's own card when someone else opens it) settled on a warm
     purple-gold "chat popover" look -- opening this panel (your OWN
     profile) right after or instead of that one read as two different
     apps ("大厅玩家资料面板...画风与游戏不符"). Matched to
     .player-card-modal's own gradient/border here. */
  background:
    radial-gradient(ellipse 72% 42% at 50% -8%, rgba(255, 164, 113, .25), transparent 70%),
    linear-gradient(158deg, rgba(91, 54, 91, .98), rgba(35, 26, 54, .99));
  border: 1px solid rgba(255, 190, 130, .5);
  box-shadow: 0 16px 35px rgba(0, 2, 28, .56), 0 0 0 1px rgba(255, 255, 255, .06) inset, 0 0 22px rgba(217, 96, 151, .22);
}

.profile-panel.entering {
  animation: settingsPop .14s ease-out both;
}

.profile-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(255, 190, 130, .3);
}

.profile-panel-head strong {
  font-size: 18px;
  color: #ffd08d;
  text-shadow: 0 1px 8px rgba(255, 164, 113, .25);
}

.profile-panel-head button {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  color: #ffd08d;
  font-size: 25px;
  line-height: 1;
  background: transparent;
}

.profile-id-row,
.profile-edit-row {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  min-height: 42px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(99, 208, 236, .28);
  font-size: 14px;
}

.profile-id-row strong {
  color: #ffe37c;
  font-size: 15px;
  text-align: right;
}

.profile-edit-row input {
  width: 100%;
  min-width: 0;
  height: 31px;
  padding: 0 9px;
  border-radius: 7px;
  color: #132333;
  font-weight: 800;
  background: rgba(237, 255, 255, .92);
  border: 1px solid rgba(255, 255, 255, .68);
}

.profile-edit-hint {
  margin: -3px 0 5px 82px;
  color: #ffe88a;
  font-size: 12px;
}

.profile-gender-row {
  align-items: center;
}

.profile-gender-group {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 7px;
}

.profile-gender-choice {
  height: 31px;
  border-radius: 999px;
  color: #dffcff;
  font-size: 13px;
  font-weight: 900;
  background: linear-gradient(180deg, rgba(28, 50, 88, .94), rgba(12, 21, 51, .94));
  border: 1px solid rgba(255, 255, 255, .18);
}

.profile-gender-choice.active {
  color: #173421;
  background: linear-gradient(#fff7a5, #76ff93);
  box-shadow: 0 0 12px rgba(255, 227, 124, .34);
}

.profile-avatar-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding-top: 8px;
}

.profile-avatar-tab {
  height: 28px;
  padding: 0 12px;
  border-radius: 999px;
  color: rgba(255, 255, 255, .78);
  font-size: 12px;
  font-weight: 700;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .18);
}

.profile-avatar-tab.active {
  color: #173421;
  background: linear-gradient(#fff7a5, #76ff93);
  box-shadow: 0 0 12px rgba(255, 227, 124, .34);
  border-color: transparent;
}

.profile-avatar-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 7px;
  padding: 11px 0;
}

.profile-avatar-choice {
  display: grid;
  place-items: center;
  height: 38px;
  border-radius: 50%;
  color: #143427;
  font-size: 20px;
  font-weight: 900;
  background:
    radial-gradient(circle at 34% 24%, rgba(255, 255, 255, .95), transparent 24%),
    linear-gradient(145deg, #fff6a4, #42efd0 58%, #1784d8);
  background-size: cover;
  background-position: center;
  border: 2px solid rgba(255, 255, 255, .72);
  box-shadow: 0 4px 10px rgba(0, 0, 0, .28);
}

.profile-avatar-choice.active {
  outline: 3px solid rgba(255, 227, 124, .92);
  box-shadow: 0 0 16px rgba(255, 227, 124, .48), 0 4px 10px rgba(0, 0, 0, .28);
}

.profile-save {
  width: 100%;
  height: 36px;
  border-radius: 8px;
  color: #193113;
  font-size: 15px;
  font-weight: 900;
  background: linear-gradient(#fff7a5, #76ff93);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .5), 0 3px 0 rgba(0, 0, 0, .28);
}

.profile-message {
  margin-top: 9px;
  padding: 7px 8px;
  border-radius: 7px;
  color: #fff7ae;
  font-size: 12px;
  text-align: center;
  background: rgba(0, 0, 0, .22);
}

.profile-message:empty {
  display: none;
}

.profile-pending {
  margin-top: -5px;
  color: #ffe88a;
  font-size: 12px;
  text-align: right;
}

.profile-avatar-upload {
  display: flex;
  justify-content: center;
  margin: -4px 0 8px;
}

.profile-avatar-upload-btn {
  padding: 6px 14px;
  border-radius: 999px;
  color: #193113;
  font-size: 12px;
  font-weight: 900;
  background: linear-gradient(#fff7a5, #76ff93);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .5), 0 2px 0 rgba(0, 0, 0, .28);
  cursor: pointer;
}

.spectator-controls {
  display: flex;
  align-items: center;
  justify-content: center;
}

.spectator-note {
  min-width: 180px;
  padding: 12px 18px;
  border: 1px solid rgba(190, 255, 193, .34);
  border-radius: 10px;
  color: #dbffe1;
  font-size: 15px;
  font-weight: 900;
  text-align: center;
  background: rgba(5, 40, 34, .72);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 5px 12px rgba(0, 0, 0, .28);
}

@keyframes settingsPop {
  0% {
    opacity: 0;
    transform: translateY(-6px) scale(.96);
  }

  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes overlayFadeIn {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

@keyframes overlayPanelIn {
  0% {
    opacity: 0;
    transform: translateY(8px) scale(.985);
  }

  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes overlayPanelInCentered {
  0% {
    opacity: 0;
    transform: translateX(-50%) translateY(8px) scale(.985);
  }

  100% {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .terms-mask,
  .report-mask,
  .avatar-viewer-mask,
  .room-confirm-mask,
  .daily-reward-modal,
  .room-info-popover section,
  .room-confirm-panel {
    animation: none;
  }
}

/* The four standard rooms form a recessed board around a complete circular
   Happy room at their shared center. Each inner corner owns one clean arc so
   the circle reads as embedded instead of being clipped by the cards. */
.lobby-screen .game-tier-stage-jinhua {
  grid-template-rows: repeat(2, 120px);
  height: 250px;
  padding-bottom: 0;
  overflow: visible;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) {
  -webkit-mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) {
  -webkit-mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3) {
  -webkit-mask-image: radial-gradient(circle 64px at calc(100% + 5px) -5px, transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at calc(100% + 5px) -5px, transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4) {
  -webkit-mask-image: radial-gradient(circle 64px at -5px -5px, transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at -5px -5px, transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(odd):not(.accent-happy) .tier-motion img {
  left: -12px;
  right: auto;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(even) .tier-motion img {
  left: auto;
  right: -12px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(odd):not(.accent-happy) strong {
  left: 13px;
  right: 49px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(even):not(.accent-happy) strong {
  left: 49px;
  right: 13px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(odd):not(.accent-happy) .tier-sub {
  left: 8px;
  right: 43px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(even):not(.accent-happy) .tier-sub {
  left: 43px;
  right: 8px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 12;
  display: block;
  width: 120px;
  height: 120px;
  min-width: 120px;
  padding: 0;
  border-width: 3px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  -webkit-mask-image: none;
  mask-image: none;
  box-shadow:
    inset 0 0 0 3px rgba(255, 238, 170, .15),
    inset 0 -7px 15px rgba(62, 18, 40, .3),
    0 0 0 4px rgba(255, 177, 92, .18),
    0 10px 24px rgba(0, 0, 0, .52),
    0 0 24px rgba(255, 119, 82, .44);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-art {
  right: -10px;
  top: -8px;
  width: 108px;
  height: 108px;
  opacity: .1;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion {
  inset: 3px;
  border-radius: 50%;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion-happy img {
  left: auto;
  right: -27px;
  top: -38px;
  height: 202px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy:last-child:nth-child(odd) strong {
  left: 8px;
  right: 8px;
  bottom: 31px;
  font-size: 17px;
  white-space: nowrap;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy:last-child:nth-child(odd) .tier-sub {
  left: 4px;
  right: 4px;
  bottom: 15px;
  font-size: 9px;
  letter-spacing: -.2px;
  white-space: nowrap;
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua {
    grid-template-rows: repeat(2, 108px);
    height: 224px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) {
    -webkit-mask-image: radial-gradient(circle 58px at calc(100% + 4px) calc(100% + 4px), transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at calc(100% + 4px) calc(100% + 4px), transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) {
    -webkit-mask-image: radial-gradient(circle 58px at -4px calc(100% + 4px), transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at -4px calc(100% + 4px), transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3) {
    -webkit-mask-image: radial-gradient(circle 58px at calc(100% + 4px) -4px, transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at calc(100% + 4px) -4px, transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4) {
    -webkit-mask-image: radial-gradient(circle 58px at -4px -4px, transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at -4px -4px, transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy {
    width: 108px;
    height: 108px;
    min-width: 108px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion-happy img {
    right: -25px;
    top: -34px;
    height: 184px;
  }
}

/* Texas Hold'em table.  It intentionally has its own compact three-seat
   layout instead of borrowing the five-seat 三张牌 geometry above, so the
   board, pot and action controls remain readable on a phone-sized screen.
   (The actual topbar/padding overrides live further down, next to the
   shared .table-screen:not(.hundred-screen) rules they need to outrank --
   see .table-screen.texas-screen there.) */
.texas-screen {
  background: transparent;
}

.texas-screen .texas-table {
  position: relative;
  width: min(430px, 100vw);
  height: calc(100vh - 168px);
  height: calc(100dvh - 168px);
  min-height: 400px;
  margin: 0 auto;
  overflow: hidden;
}

/* Stretched into a tall stadium/racetrack shape (long straight sides, rounded
   caps only at the very top/bottom) instead of the old wide lens-shaped oval
   -- a small vertical border-radius keeps the sides straight for most of the
   table's height. The wood-brown rim (border) is a deliberately different
   color from the green felt so the table reads as its own object sitting on
   the screen background, not one continuous green wash. */
.texas-table-felt {
  position: absolute;
  left: 4.5%;
  right: 4.5%;
  top: 7%;
  bottom: 3%;
  z-index: 0;
  border: 10px solid #3c2415;
  border-radius: 50% / 9%;
  background:
    radial-gradient(ellipse at 50% 43%, rgba(113, 255, 193, .22), transparent 28%),
    repeating-conic-gradient(from 12deg at 50% 50%, rgba(255, 255, 255, .045) 0 7deg, transparent 7deg 26deg),
    linear-gradient(145deg, #0a8a68 0%, #06643f 52%, #033d2b 100%);
  box-shadow:
    inset 0 0 42px rgba(0, 20, 10, .55),
    inset 0 0 0 2px rgba(148, 255, 221, .14),
    0 0 0 3px rgba(15, 8, 4, .65),
    0 13px 30px rgba(0, 10, 5, .42);
}

.texas-table-felt::after {
  content: "";
  position: absolute;
  inset: 11px;
  border: 1px solid rgba(180, 255, 233, .18);
  border-radius: inherit;
  pointer-events: none;
}

/* Blinds ribbon banner, centered near the top of the felt (below the top
   seat) instead of a plain pill beside the community cards -- a small
   double-pointed flag shape via clip-path, echoing the reference layout. */
.texas-screen .texas-hud {
  position: absolute;
  left: 50%;
  top: 27%;
  z-index: 3;
  transform: translate(-50%, -50%);
}

.texas-hud span {
  display: block;
  padding: 6px 22px;
  clip-path: polygon(8px 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 8px 100%, 0 50%);
  color: #d9faff;
  font-size: 11px;
  font-weight: 800;
  line-height: 1;
  white-space: nowrap;
  background: linear-gradient(180deg, rgba(28, 52, 135, .88), rgba(9, 18, 67, .9));
  box-shadow: 0 4px 10px rgba(0, 2, 31, .24);
}

.texas-pot {
  position: absolute;
  left: 50%;
  top: 42%;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 6px;
  transform: translate(-50%, -50%);
  color: #baf8ff;
  font-size: 12px;
  font-weight: 800;
  text-align: center;
  text-shadow: 0 0 10px rgba(87, 221, 255, .44);
}

.texas-pot-icon {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff3d6, #ffb75d 55%, #c9541c);
  box-shadow: 0 0 6px rgba(255, 183, 93, .6);
}

.texas-pot strong {
  color: #fff0a5;
  font-size: 15px;
}

.texas-board {
  position: absolute;
  left: 50%;
  /* Sits between .pot (top:42%, moved down below t4) and the bottom-row
     (t3/t7, top:72%) seats now that the mid-row (t2/t6) is gone. Shifted
     up 6% along with every other texas table element, see the .seat.t1
     comment above. -9px follows .texas-side-pots' own tightened-up gap
     ("公共牌也跟着往上平移一点"). */
  top: calc(55% - 9px);
  z-index: 3;
  width: min(270px, 72vw);
  /* Horizontal centering only -- anchoring the vertical center too (the old
     translate(-50%,-50%)) meant the community-card row shifted position
     whenever the box's total height changed (e.g. the winning-hand line
     appearing at showdown), since the box's center point stays put but its
     top edge moves. Pinning just the top edge keeps the cards themselves at
     a fixed spot regardless of what renders below them. */
  transform: translateX(-50%);
  text-align: center;
}

/* Show the winning hand above the board without changing the community
   cards' fixed position. The center stays opaque for readability while the
   two ends dissolve into the felt. Shorter and pulled down snug against the
   board (was bottom:100%+10px with taller padding) -- at its old height/
   offset this band's top edge could reach up into .texas-side-pots' own
   box with 3-4 side pots on screen (an all-in against several different
   stack depths) -- "胜利牌型的不透明带太高了，有时候会与分池打架，不透明
   带矮一点，和胜利牌型一起往下贴近公共牌，但是不要打架". */
.texas-winning-hand {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 2px);
  z-index: 1;
  isolation: isolate;
  /* Keep this bar no wider than the five-card board itself.  At this
     vertical point the felt is already narrowing toward the table edge, so
     the old 270px strip could visibly run outside the table silhouette. */
  width: min(224px, 60vw);
  padding: 3px 16px;
  transform: translateX(-50%);
  color: #fff4c8;
  font-size: 14px;
  font-weight: 900;
  line-height: 18px;
  letter-spacing: .7px;
  white-space: nowrap;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .92), 0 0 8px rgba(255, 211, 104, .18);
}

.texas-winning-hand::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  background:
    linear-gradient(90deg, transparent 0%, rgba(18, 77, 60, .7) 15%, rgba(14, 94, 67, .96) 34%, rgba(14, 94, 67, .96) 66%, rgba(18, 77, 60, .7) 85%, transparent 100%),
    linear-gradient(90deg, transparent 0%, rgba(124, 231, 179, .22) 24%, rgba(223, 255, 211, .46) 50%, rgba(124, 231, 179, .22) 76%, transparent 100%);
  background-size: 100% 100%, 100% 1px;
  background-position: center, left bottom;
  background-repeat: no-repeat;
  filter: drop-shadow(0 2px 5px rgba(0, 35, 22, .3));
}

.texas-winning-hand::after {
  content: "";
  position: absolute;
  left: 24%;
  right: 24%;
  top: 1px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(214, 255, 222, .52), transparent);
}

.texas-community-cards,
.texas-hole-cards {
  display: flex;
  align-items: center;
  justify-content: center;
}

.texas-community-cards {
  gap: 4px;
}

.texas-community-cards .card {
  width: 39px;
  height: 56px;
  border-radius: 6px;
  font-size: 20px;
  border-color: rgba(210, 252, 246, .88);
  box-shadow: 0 3px 9px rgba(0, 4, 25, .42);
}

/* Reserves the later turn/river locations without drawing a card back, so
   the flop never shifts sideways when more board cards arrive. */
.texas-community-cards .texas-board-slot {
  display: block;
  flex: 0 0 39px;
  width: 39px;
  height: 56px;
  visibility: hidden;
}

.texas-community-cards .card-rank {
  left: 6px;
  top: 6px;
}

.texas-community-cards .card-suit {
  right: 6px;
  bottom: 4px;
}

/* Not-yet-dealt community slots read as a normal (if empty) card-back --
   only the blind-amount line above them is meant to stay understated, so
   these must NOT fade to match it or the two read as one washed-out block
   sitting on top of each other. */
.texas-community-cards .texas-board-back {
  opacity: .9;
}

/* Flop/turn/river deal-in -- a quick flip rather than the card just
   appearing. Perspective on the container gives the rotateY some actual
   depth instead of reading as a flat squash. */
.texas-community-cards {
  perspective: 500px;
}

.table-screen.texas-screen .seat.me .cards {
  perspective: 500px;
}

/* The opening hand uses a real two-sided flip. Its front is the normal
   .card face; the pseudo-element is the matching card back, visible only
   while the card turns over instead of cutting from one DOM node to another. */
.table-screen.texas-screen .seat.me .card.texas-hole-card-reveal {
  transform-style: preserve-3d;
  backface-visibility: hidden;
  animation: texasHoleCardReveal 520ms cubic-bezier(.2, .82, .24, 1) both;
}

.table-screen.texas-screen .seat.me .card.texas-hole-card-reveal::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 2;
  border: 2px solid rgba(255, 234, 216, .94);
  border-radius: inherit;
  background:
    radial-gradient(circle at 50% 50%, rgba(255, 201, 170, .2), transparent 28%),
    repeating-linear-gradient(45deg, rgba(255, 255, 255, .07) 0 2px, transparent 2px 7px),
    linear-gradient(145deg, #a34672, #653d80 55%, #35284f);
  box-shadow: 0 3px 8px rgba(0, 0, 0, .38), inset 0 0 10px rgba(255, 174, 146, .18);
  transform: rotateY(180deg);
  backface-visibility: hidden;
}

@keyframes texasHoleCardReveal {
  0% { transform: rotateY(180deg) scale(.96); }
  100% { transform: rotateY(0) scale(1); }
}

.texas-card-reveal {
  animation: texasCardReveal 420ms cubic-bezier(.2, .85, .24, 1) both;
}

@keyframes texasCardReveal {
  0% {
    opacity: .4;
    transform: rotateY(82deg) scale(.92);
  }

  55% {
    opacity: 1;
  }

  100% {
    opacity: 1;
    transform: rotateY(0) scale(1);
  }
}

/* Showdown-only: the cards that actually made the winning hand (community
   side here, the winner's own hole cards via the same classes in
   renderTexasSeat) stay lit; everything else fades back so the winning
   five cards are obvious at a glance. */
.card.card-dim {
  opacity: .38;
  filter: saturate(.5) brightness(.8);
  transition: opacity 260ms ease, filter 260ms ease;
}

.card.card-winning {
  box-shadow: 0 0 0 2px rgba(255, 219, 130, .85), 0 0 12px rgba(255, 200, 90, .55);
  transition: box-shadow 260ms ease;
}

.texas-seats {
  position: absolute;
  inset: 0;
  z-index: 4;
}

/* No boxed frame -- a bordered card around every seat ate up space and read
   as cramped once there were 8 of them. Each seat is just its own floating
   name tag / avatar / stack directly on the felt (see .texas-name-tag/
   .texas-avatar-wrap/.texas-balance-tag below); the turn/winner/all-in
   highlight rings moved from the old box border onto the avatar itself. */
.texas-seat {
  position: absolute;
  width: 132px;
  min-height: 82px;
  text-align: center;
}

/* Positioned by each seat's stable slot number relative to the viewer (see
   texasVisualSeatSlot in app.js), not by literal seat id -- a shared table
   can have any bot index or another real player's seat show up in any of
   these chairs, so the position has to follow "who's how many seats away
   from me," same as 三张牌's .seat.s1-.s4. 8-max layout: s1-s3 down the left
   side, s4 straight across the top, s5-s7 down the right side, "me" at the
   bottom -- 3 + 3 + top + bottom = 8 chairs total. Action goes s1->s2->...
   ->s7->me, so s1-3 on the left (going bottom to top) then s4 then s5-7 on
   the right (going top to bottom) traces a clockwise path around the table,
   matching real Hold'em's clockwise action order -- not an arbitrary choice. */
.texas-s1,
.texas-s2,
.texas-s3,
.texas-s5,
.texas-s6,
.texas-s7 {
  width: 108px;
}

.texas-s1,
.texas-s2,
.texas-s3 {
  left: 6px;
}

.texas-s5,
.texas-s6,
.texas-s7 {
  right: 6px;
}

.texas-s3,
.texas-s5 {
  top: 13%;
}

.texas-s2,
.texas-s6 {
  top: 43%;
}

.texas-s1,
.texas-s7 {
  top: 73%;
}

.texas-s4 {
  left: 50%;
  top: 13%;
  transform: translateX(-50%);
}

.texas-me {
  left: 50%;
  bottom: 11px;
  transform: translateX(-50%);
}

/* Highlight rings live on the avatar itself now (there's no seat box left
   to put a border on). */
.texas-seat.turn .texas-avatar {
  box-shadow: 0 0 0 2px rgba(91, 107, 255, .6), 0 0 16px rgba(75, 218, 255, .55);
}

.texas-seat.winner .texas-avatar {
  box-shadow: 0 0 0 2px rgba(255, 200, 65, .68), 0 0 16px rgba(255, 190, 61, .5);
}

.texas-seat.allin:not(.folded) .texas-avatar {
  box-shadow: 0 0 0 2px rgba(255, 90, 90, .68), 0 0 16px rgba(255, 90, 90, .48);
}

.texas-seat.folded {
  opacity: .6;
  filter: saturate(.4);
}

.texas-seat.folded .card,
.texas-seat.folded .card-back {
  filter: grayscale(1) saturate(.15);
}

.texas-name-tag,
.texas-balance-tag {
  display: block;
  margin: 0 auto;
  padding: 1px 4px;
  width: 52px;
  max-width: 52px;
  overflow: hidden;
  border-radius: 999px;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .85);
}

.texas-name-tag {
  margin-bottom: 1px;
  color: #f3fcff;
  font-size: 10px;
  font-weight: 900;
  background: rgba(8, 12, 36, .6);
}

.texas-balance-tag {
  margin-top: 1px;
  color: #9cf3ff;
  font-size: 10px;
  font-weight: 800;
  background: rgba(8, 12, 36, .5);
}

.texas-screen .texas-avatar {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  font-size: 16px;
  border-radius: 12px;
}

.texas-avatar-wrap {
  position: relative;
  width: 52px;
  margin: 0 auto;
}

/* Cards fan out overlapping the avatar's bottom corner instead of taking up
   their own row -- which corner depends on which side of the table the seat
   is on, so the fan always angles in toward the middle of the table rather
   than off toward the screen edge: left-side seats (s1-s3) fan from the
   bottom-right, right-side seats (s5-s7) fan from the bottom-left. */
.texas-fan-cards {
  position: absolute;
  bottom: -6px;
  z-index: 2;
  display: flex;
}

.texas-s1 .texas-fan-cards,
.texas-s2 .texas-fan-cards,
.texas-s3 .texas-fan-cards,
.texas-s4 .texas-fan-cards {
  right: -10px;
}

.texas-s5 .texas-fan-cards,
.texas-s6 .texas-fan-cards,
.texas-s7 .texas-fan-cards {
  left: -10px;
}

.texas-fan-cards .card {
  width: 16px;
  height: 23px;
  margin-left: -10px;
  border-radius: 3px;
  font-size: 9px;
  box-shadow: 0 3px 6px rgba(0, 0, 0, .4);
}

.texas-fan-cards .card:first-child {
  transform: rotate(-10deg);
}

.texas-fan-cards .card:last-child {
  transform: rotate(8deg);
}

.texas-fan-cards .card-rank {
  left: 3px;
  top: 2px;
}

.texas-fan-cards .card-suit {
  right: 3px;
  bottom: 1px;
}

/* "我" 的两张手牌不再叠在头像上 -- 单独放大显示在整张桌子下方（桌面外的
   深色区域），方便一眼看清点数和花色，牌型预览跟着显示在下面。 */
.texas-my-hand {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 0 max(10px, env(safe-area-inset-bottom));
}

.texas-my-cards {
  display: flex;
  gap: 10px;
}

.texas-my-cards .card {
  width: 56px;
  height: 80px;
  border-radius: 8px;
  font-size: 28px;
  box-shadow: 0 8px 16px rgba(0, 0, 0, .5);
}

.texas-my-cards .card-rank {
  left: 6px;
  top: 5px;
}

.texas-my-cards .card-suit {
  right: 6px;
  bottom: 4px;
}

.texas-my-hand-name {
  padding: 3px 14px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .4);
  color: #ffe6ad;
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
}

.texas-chip-bet {
  position: absolute;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px 3px 3px;
  border-radius: 999px;
  background: rgba(10, 14, 40, .88);
  border: 1px solid rgba(120, 210, 255, .55);
  color: #d6f4ff;
  font-size: 10px;
  font-weight: 900;
  white-space: nowrap;
  box-shadow: 0 4px 10px rgba(0, 0, 0, .38);
}

.texas-chip-icon {
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #d6f6ff, #35b6ff 45%, #0a5aa8 100%);
  border: 2px solid #d6f6ff;
  box-shadow: inset 0 0 0 1px rgba(6, 40, 80, .4);
}

/* Chips land on whichever edge of the seat faces the table center, not a
   fixed corner -- s1-s3 sit down the left side (center is to their right),
   s5-s7 down the right side (center to their left), s4 is straight across
   the top (center below it), "me" is at the bottom (center above it). "me"/
   s4 are offset off-center (not left:50%) so the bubble doesn't land right
   on top of the turn-timer ring, which is always centered above the seat. */
.texas-me .texas-chip-bet {
  right: -6px;
  top: -12px;
}

.texas-s4 .texas-chip-bet {
  right: -6px;
  bottom: -12px;
}

.texas-s1 .texas-chip-bet,
.texas-s2 .texas-chip-bet,
.texas-s3 .texas-chip-bet {
  left: calc(100% - 4px);
  top: 50%;
  transform: translateY(-50%);
}

.texas-s5 .texas-chip-bet,
.texas-s6 .texas-chip-bet,
.texas-s7 .texas-chip-bet {
  right: calc(100% - 4px);
  top: 50%;
  transform: translateY(-50%);
}

.texas-seat-meta {
  min-height: 13px;
  color: rgba(214, 244, 255, .78);
  font-size: 10px;
  font-weight: 800;
  line-height: 1.2;
}

.texas-hand-name {
  display: inline-block;
  margin-left: 4px;
  color: #ffe890;
}

/* Countdown is a ring around the avatar, not a number -- conic-gradient arc
   sized a few px bigger than the avatar on every side; the avatar itself
   (higher z-index) covers the ring's center, so only that few-px border
   actually reads as a ring. The arc depletes clockwise as --texas-ring-pct
   counts down each poll (recomputed in renderTexasSeat), the same "pie
   timer" look as a real countdown clock. */
.texas-turn-ring {
  position: absolute;
  inset: -2px;
  z-index: 1;
  border-radius: 13px;
  background: conic-gradient(from 0deg, #6cf0ff 0% var(--texas-ring-pct, 100%), rgba(255, 255, 255, .16) var(--texas-ring-pct, 100%) 100%);
  box-shadow: 0 0 4px rgba(108, 240, 255, .35);
}

.texas-avatar-wrap .texas-avatar {
  position: relative;
  z-index: 2;
}

.texas-role-badge {
  /* A direct child of .seat now (not .seat-profile) so it positions against
     the *whole* seat frame -- lets the per-slot rules below dock it just
     outside the seat, on whichever side faces the table's center, instead
     of overlapping the avatar. */
  position: absolute;
  /* t1/t3/t5/t7 corner-align this with .texas-bet-chip (same left/right
     edge, near enough the same line) -- one z-index above the chip so this
     smaller round badge stays visible, layered on top of the chip pill's
     corner, instead of the chip (declared later in the seat markup) fully
     covering it. */
  z-index: 7;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: #2a2110;
  font-size: 9px;
  font-weight: 900;
  border: 1px solid rgba(255, 255, 255, .75);
  box-shadow: 0 2px 5px rgba(0, 0, 0, .4);
}

.texas-role-badge.dealer {
  color: #333;
  background: #f4f4f4;
}

.texas-role-badge.sb {
  background: #ffd23f;
}

.texas-role-badge.bb {
  color: #fff;
  background: #4d8dff;
}

/* Bet-this-street indicator: one chip glyph + the raw number, docked beside
   the seat on its table-center-facing side (see per-slot placement below)
   so it's readable at a glance without competing with the seat frame. */
.texas-bet-chip {
  position: absolute;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px 2px 3px;
  border-radius: 999px;
  background: rgba(10, 14, 30, .6);
  border: 1px solid rgba(255, 255, 255, .2);
  color: #ffe37c;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .35);
  pointer-events: none;
}

/* Whenever a call/raise/all-in (or a blind post) just bumped this seat's
   roundBet, a little trailing string of coins flies in and lands on top of
   the icon -- the icon and the number both stay put throughout (no
   translate/scale on either), only these transient coins move, each
   staggered a beat behind the last (see the [0, 70, 140] delays in
   renderTexasSeat) so they read as a stream arriving rather than the whole
   badge popping. Each seat-position group above sets --coin-fly-x/-y to
   point back toward that seat's own body (the direction its badge got
   offset from the seat in the first place) -- otherwise every seat's coins
   flew in from the same fixed direction regardless of where its own avatar
   actually is. */
.texas-bet-chip-fly {
  position: absolute;
  left: 3px;
  top: 50%;
  width: 12px;
  height: 12px;
  margin-top: -6px;
  border-radius: 50%;
  /* Poker-chip look: a solid gold disc over a repeating-conic ring, so the
     outer rim reads as the classic alternating chip-edge stripes instead
     of a plain flat coin ("现在的只是一个金饼"). */
  background:
    radial-gradient(circle at 35% 30%, #fff5c8, #ffb648 55%, #b5651d 100%) 50% / 64% 64% no-repeat,
    repeating-conic-gradient(#ffe9a8 0deg 22.5deg, #b5651d 22.5deg 45deg);
  border: 1px solid rgba(255, 255, 255, .7);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
  pointer-events: none;
  animation: texasBetCoinFly 400ms cubic-bezier(.22, .81, .28, 1) both;
}

@keyframes texasBetCoinFly {
  0% {
    opacity: 0;
    scale: .5;
    translate: var(--coin-fly-x, 0px) var(--coin-fly-y, 22px);
  }
  55% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    scale: 1.05;
    translate: 0 0;
  }
}

.texas-bet-chip-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  border-radius: 50%;
  /* Poker-chip look, same layered pattern as .texas-bet-chip-fly/
     .pot-coin-icon: a solid gold disc over a repeating-conic ring, so the
     outer rim reads as alternating chip-edge stripes instead of a plain
     flat coin. */
  background:
    radial-gradient(circle at 35% 30%, #fff5c8, #ffb648 55%, #b5651d 100%) 50% / 64% 64% no-repeat,
    repeating-conic-gradient(#ffe9a8 0deg 22.5deg, #b5651d 22.5deg 45deg);
  border: 1px solid rgba(255, 255, 255, .65);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}

/* t1/t4/t5 (top row): bet chip docks below the seat frame (open table
   space there, instead of fighting the pot/each other on the seats' facing
   sides) -- badge joins it on that same line instead of docking separately
   at a frame corner, split left/right of center so the two don't overlap. */
.table-screen.texas-screen .seat.t1 .texas-role-badge,
.table-screen.texas-screen .seat.t4 .texas-role-badge,
.table-screen.texas-screen .seat.t5 .texas-role-badge {
  left: calc(30% - 8px);
  right: auto;
  top: calc(100% + 9px);
  bottom: auto;
  margin: 0;
  transform: translateX(-50%);
}

.table-screen.texas-screen .seat.t1 .texas-bet-chip,
.table-screen.texas-screen .seat.t4 .texas-bet-chip,
.table-screen.texas-screen .seat.t5 .texas-bet-chip {
  left: 70%;
  right: auto;
  top: 100%;
  bottom: auto;
  margin: 8px 0 0;
  transform: translateX(-50%);
  --coin-fly-x: 0px;
  --coin-fly-y: -22px;
}

/* t1/t5 only: the bet chip docks at the same corner as the D/SB/BB badge
   (see the override further below), left-edge-aligned with it on t1 and
   right-edge-aligned on t5, instead of splitting the line 30/70 -- "右上
   玩家...下注的金币往左移动一点，与玩家的bb sb d 左边对齐" /
   "左上玩家...下注的金币...移动到玩家人物框下方的右侧". Nudged back right
   from -14px to 1px per a follow-up "右上玩家的下注金币往右移动一点，避免
   与操作气泡打架" -- .texas-action-bubble's own lower-left corner spot
   (see further down this file) measured live to overlap it. */
.table-screen.texas-screen .seat.t1 .texas-bet-chip {
  left: 1px;
  right: auto;
  transform: none;
}

.table-screen.texas-screen .seat.t5 .texas-bet-chip {
  left: auto;
  /* Anchored from this fixed right edge, not a centered left%+translateX(-50%)
     -- a centered anchor re-centers (and visibly shifts) every time the bet
     amount's digit count changes width; pinning one edge keeps that edge
     dead still and only lets the *other* edge grow/shrink -- "金币图标不要
     因为居中跳动，固定在一个位置". Nudged further right (from 2px) per
     "左上玩家的下注金币再往右移动一点点". */
  right: -3px;
  transform: none;
}

/* t3/t7 (bottom row): bet chip docks above the seat frame instead (open
   table space up toward the pot) -- badge joins it on that same line,
   split left/right of center. */
.table-screen.texas-screen .seat.t3 .texas-role-badge {
  left: 70%;
  right: auto;
  top: auto;
  bottom: calc(100% + 9px);
  margin: 0;
  transform: translateX(-50%);
}

/* Left-edge-aligned with the D/SB/BB badge below (both dock on this seat's
   left/center-facing side) -- "右下玩家的下注的金币往左移动一点靠近bb sb
   d 标签". Nudged back right from -14px to 0px per a follow-up "右下玩家的
   下注金币，往右边移动一点，不要和操作气泡打架" -- .texas-action-bubble's
   own upper-left corner spot (see further down this file) now sits right
   where -14px used to reach, measured live to actually overlap it. */
.table-screen.texas-screen .seat.t3 .texas-bet-chip {
  left: 0px;
  right: auto;
  top: auto;
  bottom: 100%;
  transform: none;
  margin: 0 0 8px;
  --coin-fly-x: 0px;
  --coin-fly-y: -22px;
}

.table-screen.texas-screen .seat.t7 .texas-role-badge {
  left: 30%;
  right: auto;
  top: auto;
  bottom: calc(100% + 9px);
  margin: 0;
  transform: translateX(-50%);
}

/* Right-edge-aligned with the D/SB/BB badge below (both dock on this seat's
   right/center-facing side, its upper-right) -- "左下玩家的bb sb d标签放在
   人物框的右侧上方，下注的金币放在该座位现有未移动前标签的位置". */
.table-screen.texas-screen .seat.t7 .texas-bet-chip {
  left: auto;
  right: 2px;
  top: auto;
  bottom: 100%;
  transform: none;
  margin: 0 0 8px;
  --coin-fly-x: 0px;
  --coin-fly-y: -22px;
}

/* D/SB/BB badge, t3/t7 (bottom row): docked beside this seat's own crossed
   hole-cards, same treatment as t1/t5 below -- "把左下和右下玩家的标签也
   放在牌背的旁边，靠近桌子中线一侧". Cards for both slots already fan out
   toward that same center-facing side, so the badge docks flush against
   their outer edge with a couple px of clearance, same local-px values as
   t1/t3 t5/t7 mirror pairs share (t3 and t1 sit in the same local layout,
   t3 just isn't scaled -- see the t1/t5 rule's own comment). */
.table-screen.texas-screen .seat.t3 .texas-role-badge {
  left: -29px;
  right: auto;
  top: 8px;
  bottom: auto;
  transform: none;
}

.table-screen.texas-screen .seat.t7 .texas-role-badge {
  left: auto;
  right: -29px;
  top: 8px;
  bottom: auto;
  transform: none;
}

/* D/SB/BB badge, t1/t5 (top row): docked beside this seat's own crossed
   hole-cards (.texas-hole-cards.crossed) instead of below the bet chip --
   "把左上和右上玩家的标签放在自己牌背的旁边，靠近桌子中线一侧". Cards
   for both slots already fan out toward that same center-facing side (see
   .texas-hole-cards.crossed further down this file), so the badge just
   docks flush against their outer edge with a couple px of clearance --
   "但是不要重叠打架". Values are pre-transform local px (this .seat has
   its own scale(.86) applied, same coordinate space the cards' own
   left/top values below live in), tuned against the actual rendered card
   fan, not derived from the frame's box model. */
.table-screen.texas-screen .seat.t1 .texas-role-badge {
  left: -29px;
  right: auto;
  top: 8px;
  bottom: auto;
  transform: none;
}

.table-screen.texas-screen .seat.t5 .texas-role-badge {
  left: auto;
  right: -29px;
  top: 8px;
  bottom: auto;
  transform: none;
}

/* Bottom-center "me" seat: dealer/blind badge joins the bet chip on the
   same line above the frame instead of docking inside a corner -- split
   left/right of center so the two don't overlap. Nudged further left
   (30% -> 12%) along with the bet chip (see below) per "把自己的下注的
   金币和标签往左平移" -- frees up the frame's own top-right corner for
   .texas-action-bubble instead (see its own comment further down). top
   matched to the bet chip's own top (was -34px, back from when they sat
   on the same line together; then -19px; now -22px) per "bb往下移动，与
   下注金币对齐" / "bb标签与下注金币水平对齐" -- always kept equal to
   .texas-bet-chip's own top below, whatever that currently is. */
.table-screen.texas-screen .seat.me .texas-role-badge {
  left: 12%;
  top: -25px;
  bottom: auto;
  margin: 0;
  transform: translateX(-50%);
}

.table-screen.texas-screen .seat.s0 .texas-role-badge {
  left: 4px;
  top: 4px;
  transform: none;
}

.table-screen.texas-screen .seat.s0 .texas-bet-chip {
  left: 70%;
  bottom: 100%;
  transform: translateX(-50%);
  margin-bottom: 8px;
  --coin-fly-x: 0px;
  --coin-fly-y: 22px;
}

/* "Me" only: bet chip sits above the frame, left of the corner instead of
   just outside it (right:-8px -> 20px) per "把自己的下注的金币和标签往左
   平移" -- clears the frame's own top-right corner for
   .texas-action-bubble instead (see its own comment further down), which
   needed that spot free to actually sit flush against the frame. Nudged
   back down-right (top:-34px -> -16px, right:20px -> 8px) per "把玩家自己
   的下注金币位置往右下放移动一点" once the action bubble itself moved
   further up-left (see its own comment) -- the two no longer share the
   same height, chip sitting just above the frame with the bubble stacked
   above IT instead of side by side. Nudged a little further up-right again
   (top:-16px -> -19px, right:8px -> 4px) per "自己的的下注金币再往右上移
   动一点", then right again (4px -> 0 -> -6px -> -9px -> -14px -> -17px)
   per "玩家的下注金币再往右移动一点" / "自己的下注金币再往右上移动一点
   点" / "下注金币还要往右上移动" / "下注金币往右下移动一点点" -- still
   clear of the frame/avatar, just not pushed quite as far up as the
   previous pass. top -19px -> -22px -> -28px -> -25px alongside it (kept
   1:1 with .texas-role-badge's own top above, so BB stays level with the
   chip). */
.table-screen.texas-screen .seat.me .texas-bet-chip {
  left: auto;
  right: -17px;
  top: -25px;
  bottom: auto;
  transform: none;
  --coin-fly-x: 0px;
  --coin-fly-y: 22px;
}

.table-screen.texas-screen .seat.me .texas-allin-bubble {
  left: auto;
  right: 4px;
  top: -48px;
  transform: none;
}

.table-screen.texas-screen .seat.me .texas-allin-bubble::after {
  left: auto;
  right: 10px;
  transform: none;
}

.texas-settlement {
  position: absolute;
  left: 50%;
  top: -24px;
  z-index: 3;
  padding: 3px 8px;
  transform: translateX(-50%);
  border-radius: 999px;
  color: #fff;
  font-size: 12px;
  font-weight: 900;
  white-space: nowrap;
  text-shadow: 0 2px 2px rgba(0, 0, 0, .44);
  background: rgba(8, 13, 55, .84);
}

.texas-settlement.win {
  color: #ffe37b;
}

.texas-settlement.loss {
  color: #ff94a6;
}

.texas-message {
  position: absolute;
  left: 50%;
  top: 56%;
  z-index: 5;
  width: min(310px, 78vw);
  min-height: 22px;
  transform: translateX(-50%);
  color: rgba(238, 255, 250, .94);
  font-size: 12px;
  font-weight: 700;
  line-height: 1.35;
  text-align: center;
  text-shadow: 0 2px 3px rgba(0, 8, 29, .8);
}

.texas-message.texas-toast {
  top: 64%;
  padding: 7px 12px;
  border: 1px solid rgba(136, 223, 255, .42);
  border-radius: 10px;
  color: #e8fbff;
  background: rgba(11, 21, 76, .92);
  box-shadow: 0 8px 18px rgba(0, 2, 30, .4);
}

/* The action buttons live on the felt itself, clustered around "我" near the
   bottom, instead of a permanent bar under the table -- quick-raise chips on
   top, the round "+" raise button below that, fold/all-in/call in a row at
   the bottom. The whole cluster only actually pops into view once it's this
   seat's turn (see .active below and canAct in renderTexas()); fold/call
   slide in from their own side of the screen, the rest fade+rise. */
.texas-action-cluster {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 5%;
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  pointer-events: none;
}

.texas-action-cluster.texas-cluster-wait {
  bottom: 7%;
  padding: 10px 18px;
  border-radius: 999px;
  color: #cfe0ff;
  font-size: 13px;
  font-weight: 700;
  background: linear-gradient(180deg, rgba(20, 23, 83, .92), rgba(5, 8, 38, .95));
  border: 1px solid rgba(93, 157, 255, .34);
}

.texas-quickraise-row {
  display: flex;
  gap: 12px;
}

.texas-cluster-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  width: 82%;
}

.texas-round-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  pointer-events: auto;
}

.texas-round-label {
  color: #eafcff;
  font-size: 11px;
  font-weight: 800;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .7);
  white-space: nowrap;
}

.texas-round-btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 3px solid rgba(255, 255, 255, .38);
  border-radius: 50%;
  color: #fff;
  transform: translateY(0);
  transition: transform 130ms cubic-bezier(.2, .9, .24, 1), box-shadow 130ms ease, filter 130ms ease;
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .25), inset 0 -4px 0 rgba(0, 0, 0, .22), 0 6px 14px rgba(0, 0, 0, .4);
}

.texas-round-btn.big {
  width: 64px;
  height: 64px;
}

.texas-round-btn.small {
  width: 44px;
  height: 44px;
}

.texas-round-btn.quickraise {
  width: 50px;
  height: 50px;
  font-size: 10px;
}

.texas-round-icon {
  font-size: 22px;
  font-weight: 900;
  line-height: 1;
}

.texas-round-btn.quickraise .texas-round-icon {
  font-size: 12px;
}

.texas-round-btn.small .texas-round-icon {
  font-size: 11px;
}

/* Press-and-hold, matching every other texas control: pressing down only
   depresses the button (see startPressHold/pressHoldClass in app.js) -- the
   action itself doesn't fire until release while still over the button. */
.texas-round-btn.pressed:not(:disabled) {
  transform: translateY(3px) scale(.95);
  filter: brightness(.95) saturate(1.08);
}

.texas-round-btn.fold {
  background: linear-gradient(155deg, #ff8a9a, #d43a5f 55%, #831f3f);
  border-color: rgba(255, 190, 200, .55);
}

.texas-round-btn.call {
  background: linear-gradient(155deg, #6bf0d0, #1aa8a8 55%, #0b5a5a);
  border-color: rgba(180, 250, 235, .55);
}

.texas-round-btn.raise {
  background: linear-gradient(155deg, #6cc7ff, #1c7fe0 55%, #0d4a94);
  border-color: rgba(190, 230, 255, .6);
}

.texas-round-btn.raise.active {
  outline: 3px solid rgba(255, 227, 124, .88);
}

.texas-round-btn.allin {
  background: linear-gradient(155deg, #c68bff, #7a3ee0 55%, #401f8b);
  border-color: rgba(220, 195, 255, .55);
}

.texas-round-btn.quickraise {
  background: linear-gradient(155deg, #6bf0d0, #17a08c 55%, #0a5a4d);
  border-color: rgba(190, 250, 235, .5);
}

.texas-round-btn:disabled {
  opacity: .42;
  filter: saturate(.45);
}

.texas-round-col,
.texas-quickraise-row {
  transition: transform 220ms cubic-bezier(.22, .9, .28, 1), opacity 200ms ease;
}

.texas-action-cluster:not(.active) .fold-col {
  transform: translateX(-90px);
  opacity: 0;
  pointer-events: none;
}

.texas-action-cluster:not(.active) .call-col {
  transform: translateX(90px);
  opacity: 0;
  pointer-events: none;
}

.texas-action-cluster:not(.active) .allin-col,
.texas-action-cluster:not(.active) .raise-col,
.texas-action-cluster:not(.active) .texas-quickraise-row {
  transform: translateY(30px);
  opacity: 0;
  pointer-events: none;
}

.texas-raise-wrap {
  position: relative;
}

.texas-raise-panel {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  z-index: 53;
  transform: translate(-50%, -8px);
  opacity: 0;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 84px;
  padding: 12px 10px;
  border-radius: 16px;
  border: 1px solid rgba(255, 199, 110, .4);
  background: linear-gradient(180deg, rgba(48, 30, 10, .96), rgba(16, 10, 3, .98));
  box-shadow: 0 14px 30px rgba(0, 0, 0, .45);
  transition: transform 180ms ease, opacity 180ms ease;
}

.texas-raise-panel.open {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, 0);
}

.texas-raise-value {
  margin-bottom: 10px;
  color: #ffe6ad;
  font-size: 13px;
  font-weight: 900;
  text-align: center;
  white-space: nowrap;
}

/* Dragged up/down, not left/right -- press and hold the round handle on the
   track and drag the finger upward without releasing; the fill bar tracks
   how far up, and releasing commits whatever amount is currently showing
   (see the pointerdown/pointermove/pointerup handlers). No separate
   confirm/cancel buttons -- the release itself is the confirm, dragging back
   down past the handle's start cancels. */
.texas-raise-slider {
  position: relative;
  width: 28px;
  height: 110px;
}

.texas-raise-track {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: rgba(0, 0, 0, .38);
  border: 1px solid rgba(255, 199, 110, .3);
  overflow: hidden;
}

.texas-raise-fill {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0%;
  background: linear-gradient(180deg, #ffe486, #ffb75d 55%, #e06c36);
  transition: height 40ms linear;
}

/* The actual press-and-drag target -- sits on top of (not clipped by) the
   track so it can be grabbed even when the fill is at 0% or 100%. */
.texas-raise-handle {
  position: absolute;
  left: 50%;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff3d6, #ffb75d 55%, #c9541c);
  border: 2px solid rgba(255, 255, 255, .9);
  box-shadow: 0 4px 10px rgba(0, 0, 0, .5);
  transform: translate(-50%, 50%);
  touch-action: none;
  z-index: 2;
}

.texas-raise-hint {
  margin-top: 8px;
  color: rgba(255, 230, 173, .8);
  font-size: 10px;
  font-weight: 800;
  white-space: nowrap;
}

@media (max-height: 690px) {
  .texas-screen .texas-table {
    height: calc(100dvh - 138px);
    min-height: 360px;
  }

  .texas-table-felt {
    top: 6%;
    bottom: 3%;
  }

  .texas-s3,
  .texas-s5 {
    top: 12%;
  }

  .texas-s2,
  .texas-s6 {
    top: 41%;
  }

  .texas-s1,
  .texas-s7 {
    top: 71%;
  }

  .texas-board {
    top: 42%;
  }

  .texas-message {
    top: 57%;
  }

  .texas-action-cluster {
    gap: 10px;
    bottom: 3%;
  }

  .texas-round-btn {
    width: 48px;
    height: 48px;
  }

  .texas-round-btn.big {
    width: 56px;
    height: 56px;
  }

  .texas-round-btn.small {
    width: 38px;
    height: 38px;
  }

  .texas-round-btn.quickraise {
    width: 44px;
    height: 44px;
  }

  .texas-my-cards .card {
    width: 46px;
    height: 66px;
    font-size: 23px;
  }
}

.lobby-bg {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  padding: 0 14px;
  overflow: hidden;
  background:
    linear-gradient(rgba(2, 15, 34, .28), rgba(2, 15, 34, .68)),
    radial-gradient(circle at 23% 16%, rgba(255, 220, 120, .22), transparent 22%),
    radial-gradient(circle at 64% 5%, #2dd8ca, transparent 28%),
    linear-gradient(135deg, #102b5b, #064d43);
}

.lobby-content {
  position: relative;
  height: 100%;
  min-height: 0;
  max-width: 430px;
  margin: 0 auto;
  overflow: visible;
}

.lobby-host {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: visible;
}

.lobby-host img {
  position: absolute;
  left: var(--host-x, -128px);
  top: var(--host-y, -8px);
  width: var(--host-w, 510px);
  max-width: none;
  filter: drop-shadow(0 18px 22px rgba(0, 0, 0, .38));
  transform-origin: 44% 30%;
  /* Static now -- this animated both transform AND filter (multiple
     drop-shadows plus brightness/saturate) every frame, forever, on a
     510px hero image the whole time the lobby was open. Animating filter
     can't be cheaply GPU-composited the way a plain transform can; it
     forces a re-rasterize of the shadow/color effect on every tick. Likely
     the single biggest lobby animation cost given its size. */
  animation: none;
}

.lobby-stage {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.layout-toggle {
  position: absolute;
  right: 12px;
  top: 12px;
  z-index: 5;
  height: 30px;
  padding: 0 12px;
  border-radius: 999px;
  color: #173421;
  font-size: 12px;
  font-weight: 900;
  background: linear-gradient(#fff7a5, #78ff9a);
  box-shadow: 0 4px 10px rgba(0, 0, 0, .22);
}

.layout-toggle.active {
  background: linear-gradient(#9dfff5, #45d7ff);
}

.layout-toggle.host-toggle {
  top: 50px;
  background: linear-gradient(#ffe1a5, #ff9a5e);
}

.editing-layout .lobby-host,
.editing-layout .navitem {
  cursor: move;
}

.editing-layout .lobby-host {
  pointer-events: auto;
}

.editing-layout .lobby-host img {
  pointer-events: none;
}

.editing-layout .lobby-host::after,
.editing-layout .navitem::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 8;
  border: 2px dashed rgba(255, 255, 255, .78);
  border-radius: 12px;
  pointer-events: none;
}

.editing-layout .hundred-circle,
.editing-layout .game-switch-btn,
.editing-layout .game-tier-stage {
  outline: 2px dashed rgba(255, 255, 255, .55);
  outline-offset: 3px;
}

.layout-panel {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 8px;
  z-index: 6;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 10px;
  padding: 9px;
  border-radius: 8px;
  color: #eafff5;
  font-size: 11px;
  background: rgba(0, 16, 26, .78);
  border: 1px solid rgba(255, 255, 255, .18);
}

.layout-panel strong,
.layout-panel button,
.layout-panel .layout-hint {
  grid-column: 1 / -1;
}

.layout-panel label {
  display: grid;
  gap: 3px;
}

.layout-panel input {
  width: 100%;
}

.layout-panel button {
  height: 28px;
  border-radius: 999px;
  color: #102414;
  font-weight: 900;
  background: linear-gradient(#fff7a5, #8dff8a);
}

.hundred-circle {
  position: absolute;
  left: var(--hundred-x, 150px);
  top: var(--hundred-y, 150px);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: var(--hundred-w, 130px);
  height: var(--hundred-h, 130px);
  border-radius: var(--hundred-radius, 50%);
  border: 2px solid rgba(255, 237, 148, .72);
  color: #fff;
  text-align: center;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .5),
    inset 0 -2px 0 rgba(255, 255, 255, .12),
    0 0 22px rgba(255, 238, 138, .22),
    0 8px 18px rgba(0, 0, 0, .32);
  pointer-events: auto;
}

.hundred-circle::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 18% 8%, rgba(255, 255, 255, .38), transparent 42%),
    linear-gradient(155deg, rgba(255, 255, 255, .14), transparent 55%);
  pointer-events: none;
}

.hundred-circle.accent-hundred {
  background: linear-gradient(150deg, #29e8ec, #0a83b4 55%, #045678);
}

.hundred-icon {
  position: relative;
  flex: none;
  font-size: 30px;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35));
}

.hundred-text {
  position: relative;
  display: grid;
  gap: 3px;
  text-align: left;
}

.hundred-circle .entry-title {
  font-size: 19px;
  letter-spacing: .5px;
}

.hundred-circle .entry-desc {
  max-width: 160px;
  font-size: 11px;
  white-space: normal;
}

.entry-title {
  font-weight: 900;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .4);
}

.entry-desc {
  color: rgba(255, 255, 255, .82);
  font-size: 11px;
}

.game-tier-stage {
  position: absolute;
  left: var(--tier-x, 55px);
  top: var(--tier-y, 294px);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-content: flex-start;
  gap: 9px;
  width: var(--tier-stage-width, 320px);
  pointer-events: none;
}

.game-switcher {
  position: absolute;
  left: var(--switch-x, 135px);
  top: var(--switch-y, 520px);
  display: flex;
  gap: 16px;
  padding-right: 4px;
  pointer-events: none;
}

.game-switch-btn {
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
  gap: 5px;
  width: var(--switch-w, 72px);
  height: var(--switch-h, 72px);
  border-radius: var(--switch-radius, 18px);
  border: 2px solid rgba(255, 255, 255, .2);
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .3),
    0 8px 16px rgba(0, 0, 0, .32);
  transition: transform .1s ease;
  pointer-events: auto;
}

.game-switch-btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 28% 18%, rgba(255, 255, 255, .4), transparent 45%);
  pointer-events: none;
}

.game-switch-btn:active {
  transform: scale(.95);
}

.game-switch-btn.accent-jinhua {
  background: linear-gradient(150deg, #d24fff, #8a1fce 55%, #4d1075);
}

.game-switch-btn.accent-texas {
  background: linear-gradient(150deg, #4ff0d9, #12a89a 55%, #045448);
}

.game-switch-btn.active {
  border-color: rgba(255, 237, 148, .95);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .3),
    0 0 0 2px rgba(255, 237, 148, .4),
    0 8px 16px rgba(0, 0, 0, .32);
}

.switch-icon {
  position: relative;
  font-size: 22px;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .3));
}

.switch-title {
  position: relative;
  font-size: 11px;
  font-weight: 900;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .4);
}

.game-entry-tier {
  position: relative;
  overflow: hidden;
  display: grid;
  gap: 3px;
  place-items: center;
  height: var(--tier-h, 86px);
  padding: 10px 6px 9px;
  border-radius: var(--tier-radius, 14px);
  border: 1px solid rgba(255, 255, 255, .2);
  color: #fff;
  text-align: center;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .22),
    0 6px 12px rgba(0, 0, 0, .3);
  pointer-events: auto;
  transition: transform .1s ease;
}

.game-entry-tier::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 24% 14%, rgba(255, 255, 255, .3), transparent 46%);
  pointer-events: none;
}

.game-entry-tier:active {
  transform: scale(.94);
}

.tier-icon,
.game-entry-tier strong,
.tier-sub {
  position: relative;
}

.tier-icon {
  font-size: 18px;
  line-height: 1;
}

.game-entry-tier strong {
  font-size: 12px;
  line-height: 1.15;
}

.tier-sub {
  color: rgba(255, 255, 255, .74);
  font-size: 9px;
  line-height: 1.2;
}

.game-entry-tier.accent-tycoon {
  background: linear-gradient(160deg, rgba(181, 30, 230, .42), rgba(15, 10, 26, .88));
  border-color: rgba(217, 160, 255, .55);
}

.game-entry-tier.accent-expert {
  background: linear-gradient(160deg, rgba(41, 213, 199, .4), rgba(8, 20, 20, .88));
  border-color: rgba(110, 231, 221, .55);
}

.game-entry-tier.accent-advanced {
  background: linear-gradient(160deg, rgba(8, 199, 131, .4), rgba(6, 20, 15, .88));
  border-color: rgba(91, 232, 168, .55);
}

.game-entry-tier.accent-newbie {
  background: linear-gradient(160deg, rgba(10, 159, 234, .4), rgba(6, 16, 26, .88));
  border-color: rgba(108, 196, 255, .55);
}

.game-entry-tier.accent-happy {
  background: linear-gradient(160deg, rgba(255, 176, 46, .4), rgba(26, 15, 6, .88));
  border-color: rgba(255, 185, 94, .55);
}

.game-entry-tier.tier-soon {
  opacity: .58;
  filter: grayscale(.3);
}

.resize-handle {
  position: absolute;
  right: -6px;
  bottom: -6px;
  z-index: 7;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .92);
  background: rgba(0, 0, 0, .6);
  color: #fff;
  font-size: 13px;
  line-height: 1;
  cursor: nwse-resize;
  touch-action: none;
  pointer-events: auto;
}

.layout-hint {
  margin: 4px 2px 0;
  color: rgba(234, 255, 245, .7);
  font-size: 10px;
  text-align: center;
}

.room-rank {
  display: none;
}

.bottombar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: max(7px, env(safe-area-inset-bottom));
  z-index: 30;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  padding: 0 4px;
  justify-content: space-around;
  pointer-events: none;
  background: transparent;
}

.navitem {
  position: relative;
  display: grid;
  place-items: center;
  gap: 3px;
  min-height: 54px;
  border-radius: 10px;
  color: rgba(255, 255, 255, .88);
  font-size: 12px;
  font-weight: 900;
  pointer-events: auto;
  background: linear-gradient(180deg, rgba(16, 17, 42, .92), rgba(7, 9, 28, .92));
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    0 8px 18px rgba(0, 0, 0, .28);
}

.nav-reward {
  transform: translate(var(--navReward-x, 0), var(--navReward-y, 0));
}

.nav-rank {
  transform: translate(var(--navRank-x, 0), var(--navRank-y, 0));
}

.nav-task {
  transform: translate(var(--navTask-x, 0), var(--navTask-y, 0));
}

.nav-shop {
  transform: translate(var(--navShop-x, 0), var(--navShop-y, 0));
}

.navitem:active {
  filter: brightness(.94);
}

.nav-icon {
  font-size: 17px;
  line-height: 1;
}

@keyframes lobbyHostBreathe {
  0%,
  100% {
    transform: translateY(0) scale(1);
    filter: drop-shadow(2px 0 1px rgba(139, 255, 244, .25)) drop-shadow(0 18px 22px rgba(0, 0, 0, .42)) brightness(1) saturate(1);
  }

  34% {
    transform: translateY(-2px) scale(1.006, 1.01);
    filter: drop-shadow(2px 0 1px rgba(139, 255, 244, .28)) drop-shadow(0 18px 22px rgba(0, 0, 0, .42)) brightness(1.015) saturate(1.02);
  }

  58% {
    transform: translateY(-1px) scale(1.003, 1.006);
    filter: drop-shadow(2px 0 1px rgba(139, 255, 244, .3)) drop-shadow(0 18px 22px rgba(0, 0, 0, .42)) brightness(1.02) saturate(1.025);
  }
}

/* Lobby visual polish: keep the saved layout intact and unify the lobby's
   surfaces, icons and hierarchy into one restrained deep-sea theme. */
.lobby-screen {
  --lobby-gold: #f4d98a;
  --lobby-gold-bright: #fff0b2;
  --lobby-ice: #8ff6ee;
  --lobby-panel: rgba(6, 20, 40, .78);
  --lobby-panel-deep: rgba(3, 11, 28, .92);
}

.lobby-screen .lobby-topbar {
  z-index: 20;
  min-height: 0;
  /* No more shared bar strip behind these three buttons -- each one now
     carries its own background (see .profile-btn/.lobby-stats span/
     .settings-btn) and just floats over the lobby background image. */
  background: none;
  border-bottom: none;
  box-shadow: none;
  backdrop-filter: none;
  justify-content: space-between;
  padding: 10px 14px;
  /* This used to add a flat +30px on top of whatever env(safe-area-inset-
     top) already reports, from an earlier report that the real inset alone
     wasn't enough clearance on a Dynamic Island phone. But env() is exactly
     the value the OS computes per-device for this -- stacking a fixed
     constant on top of a device-*dependent* number is what actually causes
     the mismatch complained about later ("苹果17离手机岛还有一段距离，换
     个手机可能又不同"): whatever flat px happens to look right on one
     phone reads as a big dead gap on another. Trust env() itself (it's
     already correct per-device) and only add a small fixed breathing-room
     on top, not a second inset-sized gap. */
  padding-top: calc(env(safe-area-inset-top, 46px) + 8px);
}

.lobby-screen .profile-btn {
  background: linear-gradient(145deg, rgba(18, 47, 72, .96), rgba(5, 18, 39, .96));
  border-color: rgba(244, 217, 138, .42);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .16),
    0 0 0 1px rgba(49, 226, 219, .06),
    0 6px 16px rgba(0, 0, 0, .28);
}

.lobby-screen .profile-avatar {
  border-color: var(--lobby-gold-bright);
  box-shadow: 0 0 0 2px rgba(116, 235, 224, .2), 0 0 12px rgba(96, 233, 223, .22);
}

.lobby-screen .lobby-stats {
  gap: 9px;
}

.lobby-screen .lobby-stats span {
  min-width: 0;
  background: linear-gradient(180deg, rgba(12, 35, 60, .88), rgba(4, 15, 34, .92));
  border-color: rgba(244, 217, 138, .32);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 4px 12px rgba(0, 0, 0, .22);
}

.lobby-screen .lobby-stats strong {
  color: var(--lobby-gold-bright);
}

.lobby-screen .lobby-stats em {
  color: rgba(193, 232, 235, .74);
  letter-spacing: .8px;
}

.lobby-screen .settings-btn {
  width: 46px;
  height: 46px;
  color: #e7ffff;
  background:
    radial-gradient(circle at 32% 22%, rgba(255, 255, 255, .22), transparent 28%),
    linear-gradient(145deg, rgba(27, 65, 92, .98), rgba(5, 17, 39, .98));
  border-color: rgba(244, 217, 138, .4);
  box-shadow: 0 0 14px rgba(73, 221, 219, .16), inset 0 1px 0 rgba(255, 255, 255, .16);
}

.lobby-ui-icon {
  display: block;
  width: 1em;
  height: 1em;
  overflow: visible;
}

.settings-btn .lobby-ui-icon {
  width: 25px;
  height: 25px;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .42));
}

.lobby-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse at 50% 35%, transparent 24%, rgba(2, 13, 29, .13) 58%, rgba(2, 8, 21, .45) 100%),
    linear-gradient(180deg, rgba(5, 30, 50, .02), rgba(2, 12, 28, .28));
}

.lobby-content {
  z-index: 1;
}

.lobby-screen .game-announcement-wrap-lobby,
.lobby-screen .game-announcement-anchor-lobby {
  z-index: 21;
  margin-top: 7px;
}

.lobby-screen .game-announcement-lobby {
  border-color: rgba(244, 217, 138, .32);
  background: linear-gradient(180deg, rgba(12, 40, 58, .96), rgba(4, 21, 39, .96));
  color: #f7e8ae;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .09), 0 5px 14px rgba(0, 0, 0, .22);
}

.lobby-screen .game-announcement-track-vertical span.is-system-msg,
.lobby-screen .game-announcement-track-long-marquee.is-system-msg {
  color: #ff8d84;
  text-shadow: 0 1px 5px rgba(100, 0, 0, .26);
}

.lobby-screen .lobby-host img {
  filter:
    drop-shadow(2px 0 1px rgba(139, 255, 244, .28))
    drop-shadow(0 18px 24px rgba(0, 0, 0, .48));
}

.lobby-screen .hundred-circle {
  isolation: isolate;
  gap: 11px;
  border: 1px solid rgba(255, 240, 174, .96);
  color: #fff;
  background:
    radial-gradient(circle at 34% 12%, rgba(124, 255, 247, .26), transparent 34%),
    linear-gradient(145deg, rgba(18, 171, 189, .98), rgba(5, 92, 128, .98) 58%, rgba(3, 40, 75, .99));
  box-shadow:
    inset 0 0 0 2px rgba(255, 211, 102, .14),
    inset 0 1px 0 rgba(255, 255, 255, .52),
    0 0 0 2px rgba(36, 224, 213, .22),
    0 12px 25px rgba(0, 0, 0, .4),
    0 0 24px rgba(62, 223, 217, .18);
  transition: transform .14s ease, filter .14s ease;
}

.lobby-screen .hundred-circle::before {
  background:
    linear-gradient(115deg, transparent 15%, rgba(255, 255, 255, .18) 34%, transparent 48%),
    radial-gradient(circle at 18% 8%, rgba(255, 255, 255, .27), transparent 40%);
}

.lobby-screen .hundred-circle::after {
  content: "";
  position: absolute;
  inset: 5px;
  z-index: -1;
  border: 1px solid rgba(255, 231, 157, .25);
  border-radius: inherit;
  pointer-events: none;
}

.lobby-screen .hundred-circle:active {
  /* scale/translate here (the standalone properties, NOT transform:
     scale()/translateY()) on purpose: this button's resting position is
     centered via left:50%/left:<px> + transform:translateX(...) in several
     of the responsive breakpoint rules above/below (a different translateX
     per breakpoint). The shorthand transform property can only ever hold
     one value at a time, so a press effect written as transform:
     translateY()/scale() replaced that centering offset outright instead of
     combining with it, snapping the whole button sideways by ~half its own
     width the instant it was pressed and back on release ("点击的时
     候...往右滑动到边界以外，但又会弹回来"). The standalone scale/
     translate properties compose additively with transform instead of
     overwriting it, so this gives the same press feedback every other
     lobby tile has ("感觉还没和其他场一样") without touching position.
  */
  scale: .985;
  translate: 0 2px;
  filter: brightness(.96);
}

.lobby-screen .hundred-icon {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 12px;
  color: var(--lobby-gold-bright);
  font-size: 29px;
  background: linear-gradient(145deg, rgba(3, 29, 56, .54), rgba(3, 16, 36, .32));
  border: 1px solid rgba(255, 226, 144, .3);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 5px 12px rgba(0, 0, 0, .2);
}

.lobby-screen .hundred-circle .entry-title {
  color: #fffef3;
  letter-spacing: .8px;
}

.lobby-screen .hundred-circle .entry-desc {
  color: rgba(213, 248, 247, .82);
}

.lobby-screen .game-entry-tier {
  --tier-accent: #85e9dd;
  isolation: isolate;
  padding-inline: 8px;
  border: 1px solid color-mix(in srgb, var(--tier-accent) 48%, transparent);
  background:
    radial-gradient(circle at 22% 8%, color-mix(in srgb, var(--tier-accent) 15%, transparent), transparent 42%),
    linear-gradient(155deg, rgba(12, 39, 57, .89), rgba(3, 15, 32, .94) 68%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .12),
    inset 3px 0 0 color-mix(in srgb, var(--tier-accent) 54%, transparent),
    0 8px 18px rgba(0, 0, 0, .3);
  /* backdrop-filter dropped -- several of these tiles sit on screen at once
     the whole time the lobby is open, each paying for a live blur-behind
     ("手机发烫"); the .89/.94-alpha gradient underneath is already opaque
     enough that the difference is imperceptible. */
  transition: transform .14s ease, filter .14s ease, border-color .14s ease;
}

.lobby-screen .game-entry-tier::before {
  background: linear-gradient(120deg, rgba(255, 255, 255, .1), transparent 36%);
}

.lobby-screen .game-entry-tier.accent-tycoon { --tier-accent: #e2a9ff; }
.lobby-screen .game-entry-tier.accent-expert { --tier-accent: #ff9c78; }
.lobby-screen .game-entry-tier.accent-advanced { --tier-accent: #67e9c2; }
.lobby-screen .game-entry-tier.accent-newbie { --tier-accent: #75cfff; }
.lobby-screen .game-entry-tier.accent-happy { --tier-accent: #ffd06f; }

.lobby-screen .tier-icon {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  color: var(--tier-accent);
  font-size: 17px;
  background: color-mix(in srgb, var(--tier-accent) 11%, rgba(3, 15, 30, .72));
  border: 1px solid color-mix(in srgb, var(--tier-accent) 30%, transparent);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35));
}

.lobby-screen .game-entry-tier strong {
  color: #f9ffff;
  font-size: 13px;
  letter-spacing: .3px;
  text-shadow: 0 2px 5px rgba(0, 0, 0, .62);
}

.lobby-screen .tier-sub {
  color: rgba(204, 230, 232, .72);
}

.lobby-screen .game-entry-tier:active {
  transform: translateY(2px) scale(.975);
  filter: brightness(.95);
}

.lobby-screen .game-switch-btn {
  color: #dffdf9;
  border: 1px solid rgba(128, 230, 222, .36);
  background:
    radial-gradient(circle at 28% 16%, rgba(119, 242, 231, .14), transparent 44%),
    linear-gradient(155deg, rgba(11, 47, 63, .94), rgba(3, 17, 33, .97));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 8px 18px rgba(0, 0, 0, .34);
}

.lobby-screen .game-switch-btn::before {
  background: linear-gradient(130deg, rgba(255, 255, 255, .1), transparent 42%);
}

.lobby-screen .game-switch-btn.active {
  color: var(--lobby-gold-bright);
  border-color: rgba(255, 231, 157, .92);
  background:
    radial-gradient(circle at 30% 14%, rgba(255, 231, 157, .21), transparent 45%),
    linear-gradient(150deg, rgba(46, 28, 84, .96), rgba(16, 20, 51, .98));
  box-shadow:
    inset 0 0 0 2px rgba(255, 216, 111, .13),
    inset 0 1px 0 rgba(255, 255, 255, .16),
    0 0 0 2px rgba(247, 214, 126, .18),
    0 9px 20px rgba(0, 0, 0, .36);
}

.lobby-screen .switch-icon {
  display: grid;
  place-items: center;
  font-size: 24px;
}

.lobby-screen .navitem {
  color: rgba(237, 251, 250, .92);
  background:
    radial-gradient(circle at 50% 0, rgba(91, 232, 220, .1), transparent 48%),
    linear-gradient(180deg, rgba(10, 35, 56, .94), rgba(3, 13, 30, .96));
  border-color: rgba(130, 225, 217, .27);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .11), 0 8px 18px rgba(0, 0, 0, .3);
  /* backdrop-filter dropped -- same reasoning as .game-entry-tier above:
     several of these sit on screen at once for as long as the lobby is
     open, and the .94/.96-alpha gradient already looks the same without a
     live blur-behind ("手机发烫"). */
  transition: transform .12s ease, filter .12s ease;
}

.lobby-screen .nav-icon {
  display: grid;
  place-items: center;
  color: var(--lobby-gold);
  font-size: 20px;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .4));
}

.lobby-screen .layout-toggle {
  color: #dffaf7;
  background: linear-gradient(180deg, rgba(13, 53, 66, .94), rgba(5, 25, 39, .96));
  border: 1px solid rgba(135, 233, 220, .36);
  box-shadow: 0 5px 12px rgba(0, 0, 0, .26);
}

.lobby-screen .layout-toggle.active,
.lobby-screen .layout-toggle.host-toggle {
  color: #fff3c4;
  background: linear-gradient(180deg, rgba(77, 66, 38, .95), rgba(34, 27, 17, .97));
  border-color: rgba(244, 217, 138, .46);
}

/* Arena lobby theme: a competitive mobile-game hub rather than a casino
   lobby. It keeps the operator's saved module positions while changing the
   visual language to indigo esports panels, mode cards and game resources. */
.lobby-screen {
  --lobby-gold: #8ff8ff;
  --lobby-gold-bright: #dffcff;
  --lobby-ice: #80f5ff;
}

.lobby-screen .lobby-bg {
  background: transparent;
}

.lobby-screen .lobby-bg::before {
  background: none;
}

.lobby-screen .profile-btn,
.lobby-screen .lobby-stats span,
.lobby-screen .settings-btn {
  background:
    radial-gradient(circle at 32% 8%, rgba(112, 230, 255, .16), transparent 42%),
    linear-gradient(155deg, rgba(31, 38, 105, .98), rgba(8, 14, 54, .98));
  border-color: rgba(105, 213, 255, .42);
}

.lobby-screen .profile-avatar {
  border-color: #91f4ff;
  box-shadow: 0 0 0 2px rgba(105, 98, 255, .5), 0 0 16px rgba(79, 220, 255, .34);
}

.lobby-screen .lobby-stats strong {
  color: #eafcff;
}

.lobby-screen .lobby-stats em {
  color: rgba(136, 226, 255, .78);
}

.lobby-screen .game-announcement-lobby {
  border-color: rgba(101, 205, 255, .42);
  background: linear-gradient(90deg, rgba(18, 35, 105, .9), rgba(34, 18, 104, .94), rgba(18, 35, 105, .9));
  color: #c7f7ff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .11), 0 0 16px rgba(64, 73, 255, .2);
}

.lobby-screen .hundred-circle {
  border-color: rgba(130, 241, 255, .92);
  background:
    radial-gradient(circle at 26% 12%, rgba(104, 238, 255, .31), transparent 36%),
    linear-gradient(145deg, rgba(41, 93, 214, .98), rgba(33, 31, 144, .98) 58%, rgba(19, 12, 75, .99));
  box-shadow:
    inset 0 0 0 2px rgba(125, 233, 255, .14),
    inset 0 1px 0 rgba(255, 255, 255, .48),
    0 0 0 2px rgba(92, 92, 255, .24),
    0 12px 26px rgba(2, 3, 36, .48),
    0 0 27px rgba(62, 169, 255, .24);
}

.lobby-screen .hundred-circle::after {
  border-color: rgba(141, 241, 255, .28);
}

.lobby-screen .hundred-icon {
  color: #dcfbff;
  background: linear-gradient(145deg, rgba(88, 45, 177, .78), rgba(20, 32, 109, .7));
  border-color: rgba(141, 236, 255, .5);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 0 14px rgba(96, 68, 255, .32);
}

.lobby-screen .game-entry-tier {
  border-width: 1px;
  border-color: color-mix(in srgb, var(--tier-accent) 70%, #7398ff 30%);
  background:
    linear-gradient(155deg, color-mix(in srgb, var(--tier-accent) 24%, rgba(35, 29, 101, .96)), rgba(9, 13, 54, .97) 72%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .18),
    inset 0 -3px 0 color-mix(in srgb, var(--tier-accent) 32%, transparent),
    0 8px 20px rgba(1, 3, 30, .42),
    0 0 14px color-mix(in srgb, var(--tier-accent) 12%, transparent);
}

.lobby-screen .game-entry-tier::before {
  background:
    linear-gradient(132deg, rgba(255, 255, 255, .14), transparent 34%),
    linear-gradient(180deg, transparent 66%, rgba(0, 0, 25, .22));
}

.lobby-screen .game-entry-tier.accent-tycoon { --tier-accent: #a778ff; }
.lobby-screen .game-entry-tier.accent-expert { --tier-accent: #ff728f; }
.lobby-screen .game-entry-tier.accent-advanced { --tier-accent: #48e4d1; }
.lobby-screen .game-entry-tier.accent-newbie { --tier-accent: #4aa8ff; }
.lobby-screen .game-entry-tier.accent-happy { --tier-accent: #ffbf5e; }

.lobby-screen .tier-icon {
  color: #effeff;
  background: linear-gradient(145deg, color-mix(in srgb, var(--tier-accent) 48%, rgba(17, 24, 77, .72)), rgba(14, 17, 59, .88));
  border-color: color-mix(in srgb, var(--tier-accent) 72%, #d9f9ff 28%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .16), 0 0 12px color-mix(in srgb, var(--tier-accent) 24%, transparent);
}

.lobby-screen .game-entry-tier strong {
  color: #fff;
  font-size: 14px;
}

.lobby-screen .tier-sub {
  color: rgba(213, 229, 255, .78);
}

.lobby-screen .game-switch-btn,
.lobby-screen .game-switch-btn.accent-jinhua,
.lobby-screen .game-switch-btn.accent-texas {
  color: rgba(216, 238, 255, .84);
  border-color: rgba(104, 144, 255, .48);
  background:
    radial-gradient(circle at 28% 12%, rgba(113, 220, 255, .18), transparent 42%),
    linear-gradient(150deg, rgba(35, 43, 126, .96), rgba(12, 14, 58, .98));
}

.lobby-screen .game-switch-btn.active {
  color: #fff;
  border-color: rgba(135, 241, 255, .94);
  background:
    radial-gradient(circle at 25% 8%, rgba(146, 237, 255, .28), transparent 43%),
    linear-gradient(145deg, rgba(100, 45, 202, .98), rgba(39, 35, 149, .98) 58%, rgba(14, 18, 78, .99));
  box-shadow:
    inset 0 0 0 2px rgba(111, 231, 255, .12),
    inset 0 1px 0 rgba(255, 255, 255, .22),
    0 0 0 2px rgba(100, 92, 255, .2),
    0 9px 22px rgba(1, 3, 31, .45),
    0 0 18px rgba(97, 68, 255, .24);
}

.lobby-screen .bottombar {
  gap: 7px;
  padding: 7px 7px max(7px, env(safe-area-inset-bottom));
  border-top: 1px solid rgba(111, 171, 255, .22);
  /* Top stop bumped .58 -> .85 alpha to make up for the dropped
     backdrop-filter below -- this one's gradient wasn't as opaque as the
     other spots this same fix applied to elsewhere in this file, so
     removing the live blur-behind outright would have actually shown
     whatever's behind it through the top edge ("底部导航栏背景条的背景透
     明度不够高"). Bumping the alpha keeps the same look this had *with*
     the blur, without paying for it. */
  background: linear-gradient(180deg, rgba(19, 20, 71, .85), rgba(7, 8, 39, .94));
  box-shadow: 0 -10px 28px rgba(2, 3, 29, .34);
  /* backdrop-filter dropped -- same reasoning as .game-entry-tier/.navitem
     above: this bar sits on screen the whole time the lobby is open
     ("手机发烫"). */
}

.lobby-screen .navitem {
  min-height: 58px;
  color: rgba(225, 239, 255, .92);
  background:
    radial-gradient(circle at 50% 0, rgba(108, 225, 255, .14), transparent 45%),
    linear-gradient(180deg, rgba(32, 39, 111, .94), rgba(10, 12, 55, .97));
  border-color: rgba(99, 133, 255, .42);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .13), 0 7px 15px rgba(1, 3, 29, .34);
}

.lobby-screen .nav-icon {
  color: #8defff;
}

@media (prefers-reduced-motion: reduce) {
  .lobby-screen .lobby-host img {
    animation: none;
  }
}

.table-screen {
  min-height: 100vh;
  padding: 0 8px 164px;
  overflow: hidden;
  background:
    radial-gradient(ellipse at 50% 28%, rgba(39, 255, 142, .34), rgba(12, 78, 66, .88) 58%),
    linear-gradient(135deg, #362a54, #0f403a);
}

.hundred-screen {
  /* The base .table-screen rule right above this sets padding-bottom:164px
     and min-height:100vh (no cap), meant as a generic fallback. Jinhua/texas
     get their own :not(.hundred-screen) override with a real fixed height +
     the actual bottom-bar clearance they need, but hundred never got that
     same treatment -- it just inherited the leftover 164px, stacked on top
     of .hundred-table's own already-correct 132px bottom padding (sized to
     exactly clear .chips/.hundred-footer-bar, see their own rules). That
     doubled reservation was pure empty scroll space below content that
     already fit -- forcing a page scroll to reveal nothing. Locking this to
     a real 100dvh (like jinhua/texas) and dropping the redundant bottom
     padding to 0 fixes it without touching any of the actual game content. */
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  padding: 0;
  overflow: hidden;
  background: #0d5c46;
  box-shadow: none;
}

.poker-table {
  position: relative;
  width: min(400px, calc(100vw - 16px));
  height: calc(100vh - 234px);
  min-height: 570px;
  max-height: 760px;
  margin: 8px auto 0;
  border: 10px solid rgba(7, 38, 31, .9);
  border-radius: 46% / 10%;
  background:
    radial-gradient(ellipse at 50% 38%, rgba(137, 255, 169, .24), transparent 28%),
    repeating-conic-gradient(from 0deg at 50% 46%, rgba(255, 255, 255, .055) 0 7deg, transparent 7deg 38deg),
    linear-gradient(180deg, #0dc878, #03643d 74%, #2f170d 100%);
  box-shadow: inset 0 0 38px rgba(0, 0, 0, .62), 0 0 0 4px rgba(31, 255, 124, .14);
}

.poker-table-bg-overlay {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: var(--jinhua-table-skin, none) center / cover no-repeat;
  pointer-events: none;
}

body.has-active-skin[data-skin-scope="room"] .poker-table {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}

body.has-active-skin[data-skin-scope="room"] .poker-table-bg-overlay {
  display: none;
}

/* Room tier name (e.g. 高手场), displayed on the felt just above "my" seat as
   a small gold emblem. Kept as its own element (not baked into the table/
   background art) so it survives independently when the table skin is
   swapped out later. A soft dark backing glow behind the gold text is what
   keeps it legible over whatever busy background art sits underneath, while
   still reading as "part of the table" rather than a floating UI chip. */
.table-tier-label {
  position: absolute;
  left: 50%;
  top: 72%;
  transform: translateX(-50%);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
  pointer-events: none;
}

.table-tier-label-text {
  font-size: 23px;
  font-weight: 800;
  letter-spacing: 7px;
  opacity: .55;
  background: linear-gradient(180deg, #fff8dd 0%, #f3d78e 42%, #c99a48 78%, #8f6524 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .18);
  filter:
    drop-shadow(0 0 4px rgba(0, 0, 0, .7))
    drop-shadow(0 0 8px rgba(0, 0, 0, .4));
}

.table-hud {
  position: absolute;
  left: 50%;
  top: 8px;
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  gap: 8px;
  align-items: center;
  max-width: calc(100% - 88px);
  padding: 5px 10px;
  border-radius: 999px;
  color: #d7ffc5;
  font-size: 12px;
  font-weight: 900;
  background: rgba(0, 0, 0, .58);
  white-space: nowrap;
}

.side-tools {
  position: absolute;
  /* Moved up to the table's top-left corner -- bottom-left (its old spot)
     sits right in the middle of the 弃牌/看牌/比牌/全下/加注 button cluster
     that already lives around "我" own seat, and crowded easily gets
     mis-tapped. Nothing else occupies this corner (the topbar's own
     room-menu button is top-right). */
  left: 8px;
  top: 8px;
  bottom: auto;
  z-index: 41;
  display: grid;
  gap: 10px;
}

.side-tools button {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 12px;
  color: #d5ffe0;
  font-weight: 900;
  background: linear-gradient(#14913c, #04471f);
  border: 1px solid rgba(255, 255, 255, .18);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .25), 0 6px 12px rgba(0, 0, 0, .28);
}

.side-tools button:disabled {
  opacity: .42;
  filter: grayscale(.6);
}

/* 全下(top)/跟注+跟到底+弃牌(left)/加注+看牌+比牌(right) float just outside
   "我" own seat frame instead of living in the bottom .jinhua-controls bar
   -- see topActionsMarkup/leftActionsMarkup/rightActionsMarkup in
   renderRoom(). z-index matches .side-tools' own 41, for the same reason
   documented on .side-tools itself: needs to outrank the fixed
   .jinhua-controls bar's 40 wherever the two overlap. */
.seat-side-actions {
  position: absolute;
  top: 2px;
  z-index: 41;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Was 6px -- measured 27px of slack between the 3-button stack's bottom
     edge and "我" own seat frame's bottom edge (114px stack vs 144px
     frame, both starting at the frame's top), so there was real room to
     open this up. 14px keeps the stack's new bottom edge a good 12px
     short of the frame's own bottom instead of flush against it. */
  gap: 14px;
  /* 全下's own min-width:128px is now explicitly zeroed back out (see
     .seat-side-actions .allin-center below) instead of growing this
     container to match it, so this can just be picked for whatever reads
     well on its own -- trimmed back down from 128px, which read as too
     wide once every button in here was actually the same size. */
  width: 104px;
}

.seat-side-actions.left {
  right: 100%;
  margin-right: 8px;
}

.seat-side-actions.right {
  left: 100%;
  margin-left: 8px;
}

/* 已投入 alone, centered above the frame instead of stacked outside an
   edge -- overrides the shared column/stretch/width meant for the
   left/right stacks. */
.seat-side-actions.top {
  top: auto;
  bottom: 100%;
  left: 50%;
  align-items: center;
  width: auto;
  margin-bottom: 8px;
  transform: translateX(-50%);
}

/* Sized for a narrow side column instead of the wide bottom-bar cell these
   buttons used to fill -- needs the .table-screen:not(.hundred-screen)
   prefix to outrank that shared skin rule's own height:42px at equal
   specificity otherwise (see the same pattern's own comment further down
   this file, on .control-btn/.follow-toggle). */
.table-screen:not(.hundred-screen) .seat-side-actions .control-btn,
.table-screen:not(.hundred-screen) .seat-side-actions .follow-toggle {
  width: 100%;
  height: 34px;
  font-size: 12px;
  border-radius: 10px;
}

.table-screen:not(.hundred-screen) .seat-side-actions .control-btn {
  padding: 0 4px;
}

/* No padding here (unlike .control-btn above) -- any padding on this
   wrapper itself is a strip of pixels the input/.follow-toggle-call
   children don't cover, which a tap falls through to and gets misread as
   a circle-toggle (see .follow-toggle's own comment further down). */
.table-screen:not(.hundred-screen) .seat-side-actions .follow-toggle {
  padding: 0;
}

/* 全下 (now just another member of the left/right stacks, see
   rightActionsMarkup in renderRoom()) has its own min-width:128px/
   height:52px/font-size:18px on the base .allin-center rule, left over
   from when it floated alone above the frame and was meant to look
   bigger/more prominent -- that min-width floor wins over the shared
   width:100% above regardless of this container's own width (min-width
   always wins a conflict with width), so without this override 全下 was
   still rendering wider/taller than 弃牌/加注/跟注/看牌/比牌 beside it no
   matter how narrow .seat-side-actions itself got. Needs the same extra
   specificity as the rule above to outrank .allin-center's own. */
.table-screen:not(.hundred-screen) .seat-side-actions .allin-center {
  width: 100%;
  min-width: 0;
  height: 34px;
  padding: 0 4px;
  font-size: 12px;
  border-radius: 10px;
}

.seat-side-actions .follow-toggle {
  gap: 0;
}

.seat-side-actions .seat-bet {
  align-self: center;
  margin-top: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 11px;
}

.pot {
  position: absolute;
  top: 16%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border-radius: 999px;
  color: var(--gold);
  font-size: 21px;
  font-weight: 900;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .7);
  background: rgba(0, 0, 0, .18);
}

/* Side pot(s) from an all-in against unequal stacks -- sits just under
   .pot (top:48% for texas, see the texas-specific .pot override further
   down this file) instead of folding into the same number, one small pill
   per extra contribution level so 2+ side pots (multiple all-ins at
   different stack depths) each stay legible instead of collapsing into a
   single combined figure. Laid out as a 2-column grid (top-left/top-right/
   bottom-left/bottom-right) instead of one tall column, so 3-4 side pots
   still fit in a compact block under the main pot instead of trailing
   further and further down the table. */
.texas-side-pots {
  position: absolute;
  top: 42%;
  left: 50%;
  z-index: 4;
  /* Was translateY 26px, then 17px -- tightened again to ~2px clear of
     .pot's own bottom edge per a follow-up "分池还要靠近底池一点" (still
     clear of .pot itself, which sits ~22px tall at this same top:42%
     baseline). */
  transform: translate(-50%, 13px);
  display: grid;
  grid-template-columns: repeat(2, auto);
  justify-content: center;
  align-items: center;
  /* Was 4px 8px -- tightened per "分池与分池之间也要靠近点" (a 2nd row of
     side pots was reaching down far enough to fight the community cards'
     winning-hand-type label at settlement, "结算时...就会和公共牌的结算牌
     型显示打架"). Row-gap matters most here (it's what pushes a 2nd row
     down); column-gap trimmed to match. */
  gap: 2px 5px;
}

.texas-side-pot {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 2px 10px;
  border-radius: 999px;
  color: #ffe9b8;
  font-size: 11px;
  font-weight: 800;
  white-space: nowrap;
  background: rgba(0, 0, 0, .3);
  border: 1px solid rgba(255, 210, 130, .3);
}

.texas-side-pot .pot-coin-icon {
  width: 11px;
  height: 11px;
}

.pot-coin-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border-radius: 50%;
  /* Poker-chip look, same layered pattern as .texas-bet-chip-icon: a solid
     gold disc over a repeating-conic ring, so the outer rim reads as
     alternating chip-edge stripes instead of a plain flat coin
     ("总底池那里的金币样式也要改成筹码样式"). */
  background:
    radial-gradient(circle at 35% 30%, #fff5c8, #ffb648 55%, #b5651d 100%) 50% / 64% 64% no-repeat,
    repeating-conic-gradient(#ffe9a8 0deg 22.5deg, #b5651d 22.5deg 45deg);
  border: 1px solid rgba(255, 255, 255, .65);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}

/* All-in cap + round counter, docked directly under the pot/total-bet number
   as its subtitle line. */
.pot-allin-cap {
  position: absolute;
  top: 16%;
  left: 50%;
  transform: translate(-50%, calc(-50% + 26px));
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 10px;
  border-radius: 999px;
  color: rgba(213, 244, 255, .82);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  background: rgba(0, 0, 0, .16);
}

.pot-allin-cap span + span {
  padding-left: 8px;
  border-left: 1px solid rgba(255, 255, 255, .25);
}

.ready-button {
  position: absolute;
  left: 50%;
  top: 68%;
  z-index: 4;
  transform: translate(-50%, -50%);
  min-width: 104px;
  height: 34px;
  padding: 0 14px;
  border-radius: 999px;
  color: #7a3600;
  font-size: 14px;
  font-weight: 900;
  letter-spacing: 0;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .42);
  background: linear-gradient(#fff071, #ffae13 55%, #f06c00);
  border: 2px solid rgba(255, 255, 255, .52);
  box-shadow: inset 0 3px 0 rgba(255, 255, 255, .48), 0 9px 20px rgba(0, 0, 0, .38);
}

.ready-button:disabled {
  opacity: .72;
}

.ready-countdown {
  position: absolute;
  left: 50%;
  top: 72%;
  z-index: 4;
  transform: translate(-50%, -50%);
  min-width: 148px;
  padding: 11px 18px;
  border-radius: 999px;
  color: #fff3a6;
  font-weight: 900;
  text-align: center;
  background: rgba(0, 0, 0, .58);
  border: 1px solid rgba(255, 227, 124, .36);
}

.pot-chip-pile,
.landed-chip-layer,
.pot-return-layer,
.chip-animation-layer,
.chip-burst {
  pointer-events: none;
}

.game-animation-anchor {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.game-animation-layer {
  position: fixed;
  overflow: visible;
  pointer-events: none;
  z-index: 75;
}

.game-animation-layer-room {
  z-index: 75;
}

.pot-chip-pile {
  position: absolute;
  left: 50%;
  top: 57%;
  z-index: 20;
  width: 148px;
  height: 118px;
  transform: translate(-50%, -50%);
}

.landed-chip-layer {
  position: absolute;
  inset: 0;
  z-index: 20;
}

.pot-chip-pile.collecting,
.landed-chip-layer.collecting {
  opacity: .48;
  transition: opacity 180ms ease;
}

.pile-chip,
.landed-chip,
.flying-chip,
.return-chip {
  position: absolute;
  display: grid;
  place-items: center;
  --chip-main: #9c4dff;
  overflow: hidden;
  color: #fff;
  font-weight: 900;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .52);
  /* Real border instead of the two inset-ring box-shadow layers this used
     to carry (same look, drawn a cheaper way) -- the highlight itself
     moved into chip-shine.png (see .chip-small/medium/large below), same
     treatment as hundred's own chip piles/flying chips. Kept to just the
     two outer drop-shadow layers plus one soft inset for a little depth,
     down from six layers total. */
  /* Thickened back up -- the original look carried a hard 3px white ring
     plus a softer 6px halo beyond it (both inset box-shadow layers); a
     plain 2px border alone read as noticeably thinner. Drop-shadow/inset
     shading removed entirely per request -- just the white edge now. */
  border: 3px solid rgba(255, 255, 255, .96);
  box-shadow: none;
}

.pile-chip {
  left: var(--pile-x, 0);
  top: var(--pile-y, 0);
  transform: rotate(var(--pile-rot, 0deg));
}

.pile-chip.round,
.landed-chip.round,
.flying-chip.round,
.return-chip.round,
.pile-chip.chip-small,
.landed-chip.chip-small,
.flying-chip.chip-small,
.return-chip.chip-small {
  border-radius: 999px;
  width: 32px;
  height: 32px;
  font-size: 10px;
  background:
    url("./assets/chip-shine.png") center / cover,
    var(--chip-main);
}

.pile-chip.chip-medium,
.landed-chip.chip-medium,
.flying-chip.chip-medium,
.return-chip.chip-medium {
  width: 38px;
  height: 38px;
  border-radius: 999px;
  font-size: 11px;
  background:
    url("./assets/chip-shine.png") center / cover,
    var(--chip-main);
}

.pile-chip.rect,
.landed-chip.rect,
.flying-chip.rect,
.return-chip.rect,
.pile-chip.chip-large,
.landed-chip.chip-large,
.flying-chip.chip-large,
.return-chip.chip-large {
  width: 50px;
  height: 50px;
  border-radius: 999px;
  font-size: 12px;
  writing-mode: horizontal-tb;
  --chip-main: #c28a3c;
  background:
    url("./assets/chip-shine.png") center / cover,
    var(--chip-main);
}

.pile-chip.chip-diamond,
.landed-chip.chip-diamond,
.flying-chip.chip-diamond,
.return-chip.chip-diamond {
  width: 76px;
  height: 54px;
  border-radius: 0;
  border: none;
  color: transparent;
  font-size: 0;
  background: url("./assets/diamond-real.svg") center / contain no-repeat;
  clip-path: none;
  box-shadow: none;
  text-shadow: none;
  filter: drop-shadow(0 7px 8px rgba(0, 0, 0, .36));
}

/* VIP perk: a max-all-in bet from a VIP throws a phone instead of the plain
   diamond (see chipClassForValue) -- same treatment, just a taller/narrower
   box to match the artwork's portrait aspect ratio. */
.pile-chip.chip-phone,
.landed-chip.chip-phone,
.flying-chip.chip-phone,
.return-chip.chip-phone {
  width: 48px;
  height: 90px;
  border-radius: 0;
  border: none;
  color: transparent;
  font-size: 0;
  background: url("./assets/vip-allin-phone.png") center / contain no-repeat;
  clip-path: none;
  box-shadow: none;
  text-shadow: none;
  filter: drop-shadow(0 7px 8px rgba(0, 0, 0, .4));
}

.landed-chip {
  left: calc(50% + var(--land-x));
  /* Must match .pot-chip-pile's own top (this is a separate element shown
     while chips are still freshly tossed, before they consolidate into the
     pile) -- this had been left at the pile's very first value (43%) through
     several rounds of moving the pile itself, which is why the chips the
     user was actually seeing never appeared to move at all. */
  top: calc(57% + var(--land-y));
  transform: translate(-50%, -50%) rotate(var(--land-rot, 0deg));
}

.pile-chip.tone-1,
.landed-chip.tone-1,
.flying-chip.tone-1,
.return-chip.tone-1 {
  --chip-main: #cf55d6;
}

.pile-chip.tone-2,
.landed-chip.tone-2,
.flying-chip.tone-2,
.return-chip.tone-2 {
  --chip-main: #2f8fd8;
}

.pile-chip.tone-3,
.landed-chip.tone-3,
.flying-chip.tone-3,
.return-chip.tone-3 {
  --chip-main: #d9a538;
}

.pile-chip.tone-4,
.landed-chip.tone-4,
.flying-chip.tone-4,
.return-chip.tone-4 {
  --chip-main: #d84f7a;
}


.chip-animation-layer {
  position: absolute;
  inset: 0;
  z-index: 21;
  overflow: visible;
}

/* The VIP all-in phone's oversized zoom-in (see phoneAllInDrop) is meant to
   be a dramatic top-of-everything moment -- bumped above .compare-hint/
   .settlement-message (22/23) and .barrage-layer (21), which would otherwise
   sometimes paint over it since they share this same stacking area. Only
   applied while a phone piece is actually in flight (see renderChipAnimations)
   so normal chip flights keep the original stacking, which intentionally
   keeps status text readable above them. */
.chip-animation-layer.has-phone {
  z-index: 26;
}

.pot-return-layer {
  position: absolute;
  inset: 0;
  z-index: 22;
  overflow: visible;
}

.chip-burst {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.flying-chip {
  left: var(--origin-x);
  top: var(--origin-y);
  animation: chipFly 720ms cubic-bezier(.16, .84, .32, 1) var(--delay) both;
}

.return-chip {
  /* --start-x/--start-y come from potPoint(game) in app.js -- the real
     .pot element's measured position, not a hardcoded guess -- since the
     pot doesn't sit at the exact same spot in every layout (jinhua vs
     Texas, side pots, different screen sizes). Falls back to 50%/57% only
     if that measurement wasn't available. */
  left: var(--start-x, 50%);
  top: var(--start-y, 57%);
  /* Must match POT_COLLECT_MS in app.js. */
  animation: potReturn 1300ms cubic-bezier(.16, .84, .24, 1) var(--delay) both;
}

.return-chip.chip-diamond,
.return-chip.chip-phone {
  animation-name: diamondPotReturn;
}

/* VIP perk: instead of flying in from the seat like every other chip, the
   all-in phone materializes oversized right at its landing spot and shrinks
   down onto the table -- "a phone shrinks in from the surroundings and
   drops onto the table", per how this was asked for. */
.flying-chip.chip-phone {
  animation-name: phoneAllInDrop;
}

@keyframes chipFly {
  0% {
    opacity: 1;
    left: var(--origin-x);
    top: var(--origin-y);
    transform: translate(-50%, -50%) scale(.62) rotate(0deg);
  }

  18% {
    opacity: 1;
  }

  100% {
    left: calc(50% + var(--tx));
    top: calc(57% + var(--ty));
    opacity: 1;
    transform: translate(-50%, -50%) scale(1) rotate(var(--rot));
  }
}

@keyframes phoneAllInDrop {
  0% {
    left: calc(50% + var(--tx));
    top: calc(57% + var(--ty));
    opacity: 0;
    transform: translate(-50%, -50%) scale(4.5) rotate(0deg);
  }

  45% {
    opacity: 1;
  }

  78% {
    left: calc(50% + var(--tx));
    top: calc(57% + var(--ty));
    opacity: 1;
    transform: translate(-50%, -50%) scale(.82) rotate(var(--rot));
  }

  100% {
    left: calc(50% + var(--tx));
    top: calc(57% + var(--ty));
    opacity: 1;
    transform: translate(-50%, -50%) scale(1) rotate(var(--rot));
  }
}

/* Per request: the chip that flies to the winner is the exact same chip
   that was sitting there, at the exact same size, the whole time -- no
   extra scatter jitter at the start (that translate(var(--scatter-x),
   var(--scatter-y)) used to nudge it further apart from where it was
   really sitting, right as collection began) and no scale change at all
   (previously .92 -> .46 -> .42; even though that's a shrink, not a grow,
   it still read as "the pile changes shape/size, then gets collected" --
   "不要打散、放大再收，这样给人感觉筹码不是原来那堆"). Just moves from
   its real landed position to the winner and fades out there. */
@keyframes potReturn {
  0% {
    left: var(--start-x, 50%);
    top: var(--start-y, 57%);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  16% {
    opacity: 1;
  }

  88% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  100% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 0;
    transform: translate(-50%, -50%);
  }
}

@keyframes diamondPotReturn {
  0% {
    left: var(--start-x, 50%);
    top: var(--start-y, 57%);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  18% {
    opacity: 1;
  }

  88% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  100% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 0;
    transform: translate(-50%, -50%);
  }
}

/* Texas has no mid-table chip pile to fly out from (see renderTexas()) --
   the pot payout instead starts from the "总下注" pot label itself, same spot
   .pot sits at (top:48%, see the texas-specific .pot override -- this used
   to say 31%, matching .pot's OLD position before a real t4 seat took over
   that spot and .pot moved down to 48%; texasBetSweep's own "into the pot"
   flight got updated to match at the time, this "out of the pot to the
   winner" one didn't, so a showdown payout looked like chips bursting out of
   an empty spot on the felt instead of the pot itself -- "收筹码的时候，
   筹码应该从池子往赢家收，而不是凭空出现一堆筹码"), not 三张牌's pile
   position (57%). */
.table-screen.texas-screen .return-chip {
  /* --start-y comes from potPoint(game) in app.js, same as the generic
     .return-chip rule -- this override only existed to point Texas at its
     own top:42%-ish pot position instead of 三张牌's 57% pile spot, but it
     was still hardcoding that number instead of reading the same variable
     the base rule already receives, so it never picked up a real
     measurement. */
  top: var(--start-y, 42%);
  animation-name: texasPotReturn;
}

.table-screen.texas-screen .return-chip.chip-diamond,
.table-screen.texas-screen .return-chip.chip-phone {
  animation-name: texasDiamondPotReturn;
}

/* Same "no scatter jitter, no scale change" change as the shared
   potReturn/diamondPotReturn above -- kept as its own named keyframe only
   because .table-screen.texas-screen .return-chip switches to this name
   for the different top:42% origin, not because the motion itself differs. */
@keyframes texasPotReturn {
  0% {
    left: var(--start-x, 50%);
    top: var(--start-y, 42%);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  16% {
    opacity: 1;
  }

  88% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  100% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 0;
    transform: translate(-50%, -50%);
  }
}

@keyframes texasDiamondPotReturn {
  0% {
    left: var(--start-x, 50%);
    top: var(--start-y, 42%);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  18% {
    opacity: 1;
  }

  88% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  100% {
    left: var(--winner-x);
    top: var(--winner-y);
    opacity: 0;
    transform: translate(-50%, -50%);
  }
}

/* Street-advance chip sweep: each seat's roundBet chip (see .texas-bet-chip)
   flies from that seat into the pot the instant the server actually zeroes
   it out (texas_advance_street), instead of just vanishing. Destination is
   the same fixed 50%/48% spot .pot itself sits at (see the
   .table-screen.texas-screen .return-chip comment above) rather than a
   measured element rect, consistent with how the rest of this pot-flight
   family already works. */
.texas-bet-sweep-layer {
  position: absolute;
  inset: 0;
  z-index: 22;
  overflow: visible;
  pointer-events: none;
}

.texas-sweep-coin {
  position: absolute;
  left: var(--from-x);
  top: var(--from-y);
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff5c8, #ffb648 45%, #b5651d 100%);
  border: 1px solid rgba(255, 255, 255, .65);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
  animation: texasBetSweep 480ms cubic-bezier(.3, .72, .3, 1) both;
}

/* No scale change on the way in either -- same "the chip that moves is the
   exact same chip, at the exact same size, the whole time" rule already
   applied to the pot -> winner payout flight (see potReturn/texasPotReturn's
   own comment above). Used to grow .9 -> .45 shrinking into the pot, which
   read as the coin changing size mid-flight instead of just being collected
   at its own real size -- "德州收筹码的时候，底池的筹码图标不要放大再收，
   按照原大小收". */
@keyframes texasBetSweep {
  0% {
    left: var(--from-x);
    top: var(--from-y);
    opacity: 1;
    transform: translate(-50%, -50%);
  }

  60% {
    opacity: 1;
  }

  100% {
    /* --to-x/--to-y come from potPoint(game) in app.js -- the real .pot
       element's measured position, not a hardcoded guess that silently
       goes stale whenever the pot moves (as it just did -- see the
       .seat.t1 comment further down this file). */
    left: var(--to-x, 50%);
    top: var(--to-y, 42%);
    opacity: 0;
    transform: translate(-50%, -50%);
  }
}

.showdown-animation-layer {
  position: absolute;
  inset: 0;
  z-index: 70;
  pointer-events: none;
  overflow: visible;
}

.cards.card-just-revealed {
  animation: cardsRevealPop 220ms ease-out;
}

@keyframes cardsRevealPop {
  0% {
    opacity: 0;
    transform: scale(.82);
  }

  100% {
    opacity: 1;
    transform: scale(1);
  }
}

.battle-flash {
  position: absolute;
  inset: 0;
  animation: battlePop 2000ms ease var(--battle-delay, 0ms) both;
}

.battle-name {
  position: absolute;
  z-index: 3;
  min-width: 70px;
  padding: 4px 8px;
  border-radius: 999px;
  overflow: hidden;
  color: #fff;
  font-size: 13px;
  font-weight: 900;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(0, 0, 0, .58);
  text-shadow: 0 2px 3px rgba(0, 0, 0, .75);
}

.duel-from {
  left: var(--sx);
  top: var(--sy);
  color: #fff;
  transform: translate(-50%, -50%);
  border: 1px solid var(--bolt-from-main);
  box-shadow: 0 0 16px var(--bolt-from-glow);
}

.duel-to {
  left: var(--ex);
  top: var(--ey);
  color: #fff;
  transform: translate(-50%, -50%);
  border: 1px solid var(--bolt-to-main);
  box-shadow: 0 0 16px var(--bolt-to-glow);
}

.battle-vs {
  position: absolute;
  left: var(--mx);
  top: var(--my);
  z-index: 5;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  transform: translate(-50%, -50%);
  border-radius: 999px;
  color: #7a3200;
  font-size: 18px;
  font-weight: 900;
  background: radial-gradient(circle, #fff2a3, #ffb300 68%, #a45300);
  border: 2px solid rgba(255, 255, 255, .65);
  box-shadow: 0 0 18px rgba(255, 238, 118, .82), 0 0 28px var(--bolt-from-glow), 0 0 28px var(--bolt-to-glow);
  animation: vsShove 3000ms ease var(--battle-delay, 0ms) both;
}

.duel-axis {
  position: absolute;
  left: var(--sx);
  top: var(--sy);
  z-index: 4;
  width: var(--bolt-len);
  height: 0;
  transform: rotate(var(--angle));
  transform-origin: left center;
  pointer-events: none;
}

.duel-axis::before,
.duel-axis::after {
  content: "";
  position: absolute;
  top: 0;
  z-index: 6;
  width: 13px;
  height: 13px;
  border-radius: 999px;
  opacity: 0;
  background: radial-gradient(circle, #fff 0 24%, var(--bolt-from-light) 25% 48%, var(--bolt-from-main) 49% 72%, var(--bolt-from-deep) 73%);
  border: 2px solid rgba(255, 255, 255, .86);
  box-shadow: 0 0 10px var(--bolt-from-glow), 0 0 16px rgba(255, 255, 255, .45);
  animation: duelEndpointPulse 2000ms ease-out var(--battle-delay, 0ms) both;
}

.duel-axis::before {
  left: 0;
  --endpoint-shift-x: -50%;
  transform: translate(var(--endpoint-shift-x), -50%);
}

.duel-axis::after {
  right: 0;
  --endpoint-shift-x: 50%;
  transform: translate(var(--endpoint-shift-x), -50%);
  background: radial-gradient(circle, #fff 0 24%, var(--bolt-to-light) 25% 48%, var(--bolt-to-main) 49% 72%, var(--bolt-to-deep) 73%);
  box-shadow: 0 0 10px var(--bolt-to-glow), 0 0 16px rgba(255, 255, 255, .45);
}

/* VIP perk: a real element (not a pseudo-element like the plain dots above)
   sitting at the exact same spot, sized to fully cover whichever end's dot
   belongs to a VIP seat -- "covers" rather than replaces, per how this was
   asked for. .duel-cannon itself keeps the exact same positioning/pulse
   animation as the plain dots (both reuse duelEndpointPulse, keyed off the
   same --endpoint-shift-x custom property) so it lands in the same spot;
   the artwork + aim direction live on the nested ::before instead, since the
   shared animation's own "transform" keyframes would otherwise stomp any
   rotation declared directly on .duel-cannon. .duel-axis is a horizontal
   line (see its width/transform-origin above) rotated to point from
   challenger to target, so "aiming along the beam" in that local frame
   means the "from" cannon points toward local +X (right) and the "to"
   cannon points toward local -X (left) -- the artwork's native muzzle-up
   orientation needs +90deg / -90deg respectively to match that. */
.duel-cannon {
  position: absolute;
  top: 0;
  z-index: 7;
  width: 26px;
  height: 26px;
  opacity: 0;
  animation: duelEndpointPulse 2000ms ease-out var(--battle-delay, 0ms) both;
}

.duel-cannon::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("./assets/vip-cannon.png") center / contain no-repeat;
  filter: drop-shadow(0 0 6px rgba(255, 215, 107, .7));
}

.duel-cannon.from {
  left: 0;
  --endpoint-shift-x: -50%;
}

.duel-cannon.from::before {
  transform: rotate(90deg);
}

.duel-cannon.to {
  right: 0;
  --endpoint-shift-x: 50%;
}

.duel-cannon.to::before {
  transform: rotate(-90deg);
}

.duel-axis .battle-vs {
  left: var(--winner-pos);
  top: 0;
  overflow: hidden;
  animation: vsAxisShove 2000ms ease var(--battle-delay, 0ms) both;
}

.duel-axis .battle-explosion {
  left: var(--loser-pos);
  top: 0;
  z-index: 9;
  width: 34px;
  height: 34px;
  background:
    radial-gradient(circle, #fff 0 13%, #fff9a6 14% 26%, #ffb000 27% 45%, #ff4d00 46% 60%, rgba(255, 77, 0, .58) 61% 68%, transparent 69%),
    conic-gradient(from 0deg, transparent 0 5%, #fff 5% 9%, transparent 9% 16%, #ffdf45 16% 22%, transparent 22% 35%, #ff6a00 35% 41%, transparent 41% 52%, #fff 52% 57%, transparent 57% 70%, #ffe75a 70% 77%, transparent 77%);
  filter: drop-shadow(0 0 20px rgba(255, 130, 20, .72));
  animation: loserVsExplosion 2000ms ease-out var(--battle-delay, 0ms) both;
}

.battle-explosion {
  position: absolute;
  left: var(--fx);
  top: var(--fy);
  z-index: 5;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  opacity: 0;
  transform: translate(-50%, -50%) scale(.18);
  background:
    radial-gradient(circle, #fff 0 17%, #fff27f 18% 32%, #ff8a00 33% 52%, rgba(255, 53, 0, .82) 53% 65%, transparent 66%),
    conic-gradient(from 0deg, transparent 0 8%, #fff27f 8% 13%, transparent 13% 25%, #ff9d00 25% 31%, transparent 31% 46%, #fff 46% 51%, transparent 51% 67%, #ffda00 67% 73%, transparent 73%);
  filter: drop-shadow(0 0 12px #fff469);
  animation: explosionPop 3000ms ease-out var(--battle-delay, 0ms) both;
}

.battle-explosion::after {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: 999px;
  border: 2px solid rgba(255, 242, 127, .8);
  opacity: .8;
}

.duel-axis .battle-explosion::after {
  inset: -18px;
  border-color: rgba(255, 238, 118, .95);
  box-shadow: 0 0 18px rgba(255, 238, 118, .66);
}

.battle-result {
  position: absolute;
  left: var(--fx);
  top: calc(var(--fy) + 5%);
  z-index: 6;
  min-width: 88px;
  padding: 5px 10px;
  transform: translate(-50%, -50%) scale(.82);
  border-radius: 999px;
  color: #fff7a8;
  font-size: 13px;
  font-weight: 900;
  text-align: center;
  opacity: 0;
  background: rgba(0, 0, 0, .68);
  border: 1px solid rgba(255, 227, 124, .82);
  box-shadow: 0 0 18px rgba(255, 227, 124, .55);
  text-shadow: 0 2px 3px rgba(0, 0, 0, .75);
  animation: battleResultPop 2000ms ease var(--battle-delay, 0ms) both;
}

.duel-lightning-line {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
  width: 100%;
  height: 8px;
  transform: translateY(-50%);
  transform-origin: left center;
  border-radius: 999px;
  filter:
    drop-shadow(0 0 9px var(--bolt-from-glow))
    drop-shadow(0 0 9px var(--bolt-to-glow));
}

.duel-lightning-half {
  position: absolute;
  top: 50%;
  width: 100%;
  height: 8px;
  border-radius: 999px;
  opacity: 0;
  background: linear-gradient(90deg, transparent 0%, var(--half-deep) 8%, var(--half-main) 48%, var(--half-light) 78%, #fff 100%);
  box-shadow: 0 0 10px var(--half-glow), 0 0 18px rgba(255, 255, 255, .28);
  animation: lightningGather 2000ms cubic-bezier(.17, .84, .22, 1) var(--battle-delay, 0ms) both;
}

.duel-lightning-half.from {
  left: 0;
  --push-opacity: var(--from-push-opacity, 1);
  --half-deep: var(--bolt-from-deep);
  --half-main: var(--bolt-from-main);
  --half-light: var(--bolt-from-light);
  --half-glow: var(--bolt-from-glow);
  transform: translateY(-50%) scaleX(0);
  transform-origin: left center;
}

.duel-lightning-half.to {
  right: 0;
  --push-opacity: var(--to-push-opacity, 1);
  --half-deep: var(--bolt-to-deep);
  --half-main: var(--bolt-to-main);
  --half-light: var(--bolt-to-light);
  --half-glow: var(--bolt-to-glow);
  transform: translateY(-50%) scaleX(0);
  transform-origin: right center;
  background: linear-gradient(270deg, transparent 0%, var(--half-deep) 8%, var(--half-main) 48%, var(--half-light) 78%, #fff 100%);
}

.duel-lightning-line::before {
  content: "";
  position: absolute;
  left: 7%;
  right: 7%;
  top: 50%;
  height: 2px;
  transform: translateY(-50%) scaleX(0);
  transform-origin: center;
  opacity: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .48) 47%, rgba(255, 255, 255, .82) 50%, rgba(255, 255, 255, .48) 53%, transparent);
  animation: lightningCoreGather 2000ms ease-out var(--battle-delay, 0ms) both;
}

.duel-lightning-line::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 18px;
  transform: translate(-50%, -50%);
  border-radius: 999px;
  background: #fff;
  box-shadow:
    0 0 12px #fff,
    0 0 22px var(--bolt-from-glow),
    0 0 22px var(--bolt-to-glow);
  animation: duelCorePulse 2000ms ease var(--battle-delay, 0ms) both;
}

.lightning-bolt {
  position: absolute;
  z-index: 2;
  width: var(--bolt-len);
  height: 8px;
  transform-origin: left center;
  animation: lightningStrike 5200ms ease-out var(--battle-delay, 0ms) both;
}

.duel-bolt-from {
  left: var(--sx);
  top: var(--sy);
  transform: rotate(var(--angle)) translateY(-50%) skewX(-18deg);
  background: linear-gradient(90deg, var(--bolt-from-light), #fff 32%, var(--bolt-from-main) 58%, var(--bolt-from-deep) 82%, transparent);
  filter: drop-shadow(0 0 10px var(--bolt-from-glow));
}

.duel-bolt-to {
  left: var(--ex);
  top: var(--ey);
  z-index: 1;
  height: 7px;
  transform: rotate(var(--reverse-angle)) translateY(-50%) skewX(18deg);
  background: linear-gradient(90deg, var(--bolt-to-light), #fff 32%, var(--bolt-to-main) 58%, var(--bolt-to-deep) 82%, transparent);
  filter: drop-shadow(0 0 10px var(--bolt-to-glow));
}

.lightning-bolt::after {
  content: "";
  position: absolute;
  right: -4px;
  top: 50%;
  width: 0;
  height: 0;
  transform: translateY(-50%);
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 10px solid var(--bolt-from-tip);
  filter: drop-shadow(0 0 6px var(--bolt-from-glow));
}

.duel-bolt-to::after {
  border-left-color: var(--bolt-to-tip);
  filter: drop-shadow(0 0 6px var(--bolt-to-glow));
}

.allin-clash {
  position: absolute;
  inset: 0;
  z-index: 80;
  animation: battlePop 3000ms ease var(--battle-delay, 0ms) both;
}

.allin-bolt,
.allin-push-bolt {
  position: absolute;
  left: var(--sx);
  top: var(--sy);
  z-index: 82;
  width: var(--bolt-len);
  height: 6px;
  transform: rotate(var(--angle)) translateY(-50%) skewX(-14deg);
  transform-origin: left center;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--bolt-light), #fff 32%, var(--bolt-main) 58%, var(--bolt-deep) 82%, transparent);
  filter: drop-shadow(0 0 9px var(--bolt-glow));
  animation: allInLightning 3000ms ease-out var(--battle-delay, 0ms) both;
}

.allin-endpoint {
  position: absolute;
  left: var(--sx);
  top: var(--sy);
  z-index: 140;
  width: 18px;
  height: 18px;
  transform: translate(-50%, -50%);
  border: 2px solid rgba(255, 255, 255, .98);
  border-radius: 999px;
  background: radial-gradient(circle, #fff 0 24%, var(--bolt-light) 25% 52%, var(--bolt-main) 53% 72%, rgba(255, 255, 255, .18) 73%);
  box-shadow:
    0 0 0 3px rgba(31, 255, 236, .26),
    0 0 14px var(--bolt-glow),
    0 0 24px rgba(255, 255, 255, .48);
  pointer-events: none;
  animation: allInEndpointPulse 3000ms ease-out var(--battle-delay, 0ms) both;
}

.allin-endpoint.winner {
  background: radial-gradient(circle, #fff 0 24%, #fff8b8 25% 52%, #ffb000 53% 72%, rgba(255, 255, 255, .2) 73%);
  box-shadow:
    0 0 0 3px rgba(255, 246, 150, .34),
    0 0 16px rgba(255, 226, 55, .94),
    0 0 26px rgba(255, 139, 0, .62);
}

.allin-push-bolt {
  z-index: 4;
  height: 8px;
  background: linear-gradient(90deg, #fff, var(--bolt-light) 18%, var(--bolt-main) 52%, var(--bolt-deep) 82%, transparent);
  filter: drop-shadow(0 0 15px var(--bolt-glow));
  animation: allInPushLightning 3000ms ease-out var(--battle-delay, 0ms) both;
}

.allin-push-bolt.winner-push {
  z-index: 1;
  height: 12px;
  background:
    linear-gradient(90deg, rgba(255, 255, 255, .98), #fff8b8 18%, #ffe037 46%, #ff9500 72%, rgba(255, 47, 0, .9) 92%, rgba(255, 47, 0, .22)),
    linear-gradient(180deg, transparent 0 18%, rgba(255, 255, 255, .88) 42% 58%, transparent 82%);
  filter: drop-shadow(0 0 22px rgba(255, 160, 20, .78));
}

.allin-bolt.winner {
  z-index: 5;
  /* Same base 6px as every other contender's bolt -- thickening only
     happens through allInWinnerLightning's scaleY growth in its back half,
     so the winner isn't visibly fatter than the field from frame 0 (that
     was giving the result away before the reveal). */
  background: linear-gradient(90deg, #fff, #fff8b8 28%, #ffe037 58%, #ff8b00 84%, transparent);
  filter: drop-shadow(0 0 18px rgba(255, 180, 30, .7));
  animation-name: allInWinnerLightning;
}

.allin-bolt::after,
.allin-push-bolt::after {
  content: "";
  position: absolute;
  right: -4px;
  top: 50%;
  width: 0;
  height: 0;
  transform: translateY(-50%);
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 9px solid var(--bolt-tip);
}

.allin-bolt::before {
  content: none;
  position: absolute;
  left: -7px;
  top: 50%;
  z-index: 90;
  width: 14px;
  height: 14px;
  transform: translateY(-50%);
  border: 2px solid rgba(255, 255, 255, .96);
  border-radius: 999px;
  background: radial-gradient(circle, #fff 0 24%, var(--bolt-light) 25% 52%, var(--bolt-main) 53% 72%, rgba(255, 255, 255, .12) 73%);
  box-shadow:
    0 0 0 2px rgba(31, 255, 236, .22),
    0 0 12px var(--bolt-glow),
    0 0 20px rgba(255, 255, 255, .42);
}

.allin-bolt.winner::before {
  background: radial-gradient(circle, #fff 0 24%, #fff8b8 25% 52%, #ffb000 53% 72%, rgba(255, 255, 255, .16) 73%);
  box-shadow:
    0 0 0 2px rgba(255, 246, 150, .3),
    0 0 14px rgba(255, 226, 55, .9),
    0 0 22px rgba(255, 139, 0, .58);
}

.allin-push-bolt::after {
  border-top-width: 7px;
  border-bottom-width: 7px;
  border-left-width: 12px;
  filter: drop-shadow(0 0 8px var(--bolt-glow));
}

.allin-push-bolt.winner-push::after {
  opacity: 0;
}

.allin-vs,
.allin-core {
  position: absolute;
  left: var(--cx);
  top: var(--cy);
  /* Above every contender bolt/endpoint in this stack (.allin-endpoint is
     the highest at 140) so the converging laser lines never poke out from
     behind the center badge. */
  z-index: 150;
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  transform: translate(-50%, -50%);
  border-radius: 999px;
  color: #6d2500;
  font-size: 19px;
  font-weight: 900;
  background:
    radial-gradient(circle at 42% 34%, #fff 0 12%, #fff7af 13% 33%, #ffb91e 55%, #b04d00 86%),
    conic-gradient(from 20deg, #ff3d00, #fff27f, #ff8a00, #ff3d00);
  box-shadow: 0 0 18px rgba(255, 238, 118, .9), 0 0 32px rgba(37, 233, 255, .45), 0 0 0 rgba(255, 82, 0, 0);
  animation: allInVsPulse 3000ms ease var(--battle-delay, 0ms) both;
}

.allin-core::before {
  content: "全下";
  position: relative;
  z-index: 2;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .5), 0 0 8px rgba(255, 244, 126, .82);
}

.allin-core::after {
  content: "";
  position: absolute;
  inset: -18px;
  z-index: 1;
  border-radius: 999px;
  opacity: 0;
  background:
    radial-gradient(circle, rgba(255, 255, 255, .95) 0 13%, rgba(255, 242, 127, .9) 14% 28%, rgba(255, 138, 0, .76) 29% 46%, transparent 47%),
    conic-gradient(from 0deg, transparent 0 8%, rgba(255, 245, 153, .95) 8% 13%, transparent 13% 20%, rgba(255, 74, 0, .86) 20% 31%, transparent 31% 40%, rgba(255, 255, 255, .9) 40% 45%, transparent 45% 56%, rgba(255, 177, 0, .9) 56% 68%, transparent 68% 78%, rgba(255, 61, 0, .82) 78% 88%, transparent 88%);
  filter: drop-shadow(0 0 20px rgba(255, 170, 40, .75));
  animation: allInCoreFire 3000ms ease-out var(--battle-delay, 0ms) both;
}

.allin-explosion,
.allin-loss-explosion {
  position: absolute;
  left: var(--cx);
  top: var(--cy);
  z-index: 6;
  width: 24px;
  height: 24px;
  transform: translate(-50%, -50%) scale(.2);
  opacity: 0;
  border-radius: 999px;
  background:
    radial-gradient(circle, #fff 0 16%, #fff27f 17% 31%, #ff8a00 32% 52%, rgba(255, 53, 0, .8) 53% 64%, transparent 65%),
    conic-gradient(from 0deg, transparent 0 7%, #fff27f 7% 13%, transparent 13% 25%, #23e9ff 25% 31%, transparent 31% 46%, #fff 46% 51%, transparent 51% 67%, #ffda00 67% 73%, transparent 73%);
  filter: drop-shadow(0 0 16px #fff469);
  animation: allInExplosion 3000ms ease-out var(--battle-delay, 0ms) both;
}

.allin-loss-explosion {
  left: var(--ex);
  top: var(--ey);
  z-index: 7;
  width: 30px;
  height: 30px;
  animation: allInLossExplosion 3000ms ease-out var(--battle-delay, 0ms) both;
}

.allin-name,
.allin-winner {
  position: absolute;
  left: var(--sx);
  top: var(--sy);
  z-index: 4;
  max-width: 86px;
  padding: 4px 8px;
  transform: translate(-50%, -50%);
  overflow: hidden;
  border-radius: 999px;
  color: rgba(255, 255, 255, .82);
  font-size: 12px;
  font-weight: 900;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(0, 0, 0, .54);
  text-shadow: 0 2px 3px rgba(0, 0, 0, .78);
  border: 1px solid var(--bolt-main);
  box-shadow: 0 0 12px var(--bolt-glow);
}

.allin-name {
  animation: allInNameHold 3000ms ease var(--battle-delay, 0ms) both;
}

.allin-winner {
  color: #fff7a8;
  opacity: 0;
  box-shadow: 0 0 18px rgba(255, 243, 120, .58);
  animation: allInWinnerPop 3000ms ease var(--battle-delay, 0ms) both;
}

@keyframes battlePop {
  0% {
    opacity: 0;
  }

  20%,
  78% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@keyframes lightningStrike {
  0%,
  18% {
    opacity: 0;
    clip-path: inset(0 100% 0 0);
  }

  28%,
  72% {
    opacity: 1;
    clip-path: inset(0 0 0 0);
  }

  86%,
  100% {
    opacity: 0;
  }
}

@keyframes lightningGather {
  0%,
  10% {
    opacity: 0;
    transform: translateY(-50%) scaleX(0) scaleY(1);
  }

  22% {
    opacity: 1;
    transform: translateY(-50%) scaleX(.6) scaleY(1);
  }

  35%,
  50% {
    opacity: 1;
    transform: translateY(-50%) scaleX(1) scaleY(1);
  }

  /* Last second (50%-100% of the 2000ms total): the loser's half fades out
     in place via --push-opacity (0 for them, 1 for the winner), while the
     shared scaleX/scaleY growth below only reads as a winner-side "thicken
     and push toward the loser" because the loser is already invisible. */
  75% {
    opacity: var(--push-opacity);
    transform: translateY(-50%) scaleX(1.1) scaleY(2.2);
  }

  88% {
    opacity: calc(var(--push-opacity) * .95);
    transform: translateY(-50%) scaleX(1.15) scaleY(2.6);
  }

  100% {
    opacity: 0;
    transform: translateY(-50%) scaleX(1.1) scaleY(2);
  }
}

@keyframes lightningCoreGather {
  0%,
  16% {
    opacity: 0;
    transform: translateY(-50%) scaleX(0);
  }

  20%,
  62% {
    opacity: 1;
    transform: translateY(-50%) scaleX(1);
  }

  76% {
    opacity: 1;
    transform: translateY(-50%) scaleX(.72);
  }

  86%,
  100% {
    opacity: 0;
    transform: translateY(-50%) scaleX(.42);
  }
}

@keyframes duelEndpointPulse {
  0%,
  6% {
    opacity: 0;
    transform: translate(var(--endpoint-shift-x), -50%) scale(.58);
  }

  12%,
  72% {
    opacity: 1;
    transform: translate(var(--endpoint-shift-x), -50%) scale(1);
  }

  82% {
    opacity: 1;
    transform: translate(var(--endpoint-shift-x), -50%) scale(1.22);
  }

  100% {
    opacity: 0;
    transform: translate(var(--endpoint-shift-x), -50%) scale(.76);
  }
}

@keyframes duelCorePulse {
  0%,
  18% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.28);
  }

  28%,
  62% {
    opacity: .95;
    left: 50%;
    transform: translate(-50%, -50%) scale(1);
  }

  76% {
    opacity: 1;
    left: var(--loser-pos);
    transform: translate(var(--loser-shift), -50%) scale(1.22);
  }

  86% {
    opacity: .96;
    left: var(--loser-pos);
    transform: translate(var(--loser-shift), -50%) scale(1.55);
  }

  100% {
    opacity: 0;
    left: var(--loser-pos);
    transform: translate(var(--loser-shift), -50%) scale(.72);
  }
}

@keyframes allInLightning {
  0%,
  10% {
    opacity: 0;
    clip-path: inset(0 100% 0 0);
  }

  14% {
    opacity: 1;
    clip-path: inset(0 64% 0 0);
  }

  18% {
    opacity: 1;
    clip-path: inset(0 24% 0 0);
  }

  20%,
  52% {
    opacity: 1;
    clip-path: inset(0 0 0 0);
  }

  60%,
  100% {
    opacity: 0;
    clip-path: inset(0 0 0 0);
  }
}

@keyframes allInEndpointPulse {
  0%,
  9% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.48);
  }

  14%,
  58% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.08);
  }

  76% {
    opacity: .95;
    transform: translate(-50%, -50%) scale(1.2);
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.68);
  }
}

@keyframes allInPushLightning {
  0%,
  100% {
    opacity: 0;
    transform: rotate(var(--angle)) translateY(-50%) skewX(-14deg) scaleX(1);
  }
}

@keyframes allInWinnerLightning {
  0%,
  10% {
    opacity: 0;
    clip-path: inset(0 100% 0 0);
  }

  14% {
    opacity: 1;
    clip-path: inset(0 66% 0 0);
    transform: rotate(var(--angle)) translateY(-50%) skewX(-14deg) scaleX(.94) scaleY(1);
  }

  18% {
    opacity: 1;
    clip-path: inset(0 22% 0 0);
    transform: rotate(var(--angle)) translateY(-50%) skewX(-14deg) scaleX(1) scaleY(1);
  }

  20%,
  50% {
    opacity: 1;
    clip-path: inset(0 0 0 0);
    transform: rotate(var(--angle)) translateY(-50%) skewX(-14deg) scaleX(1.04) scaleY(1);
  }

  70% {
    opacity: 1;
    clip-path: inset(0 0 0 0);
    transform: rotate(var(--angle)) translateY(-50%) skewX(-14deg) scaleX(1.1) scaleY(2.1);
  }

  78%,
  100% {
    opacity: 0;
    transform: rotate(var(--angle)) translateY(-50%) skewX(-14deg) scaleX(.82) scaleY(1);
  }
}

@keyframes allInVsPulse {
  0%,
  12% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.28) rotate(-10deg);
    box-shadow: 0 0 14px rgba(255, 238, 118, .72), 0 0 22px rgba(37, 233, 255, .34), 0 0 0 rgba(255, 82, 0, 0);
  }

  18% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(.62) rotate(-4deg);
  }

  20% {
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% - 5px)) scale(1.34) rotate(9deg);
  }

  21% {
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% + 4px)) scale(1.45) rotate(-10deg);
  }

  22% {
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% + 5px)) scale(1.37) rotate(8deg);
  }

  23% {
    opacity: 1;
    transform: translate(calc(-50% - 7px), calc(-50% - 4px)) scale(1.48) rotate(-12deg);
  }

  24% {
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% - 2px)) scale(1.38) rotate(10deg);
  }

  25% {
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% + 6px)) scale(1.5) rotate(-9deg);
  }

  26% {
    opacity: 1;
    transform: translate(calc(-50% + 7px), calc(-50% + 2px)) scale(1.42) rotate(12deg);
  }

  27% {
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% - 6px)) scale(1.54) rotate(-13deg);
  }

  28% {
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% + 6px)) scale(1.44) rotate(8deg);
  }

  29% {
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% - 3px)) scale(1.56) rotate(-11deg);
  }

  30% {
    opacity: 1;
    transform: translate(calc(-50% + 7px), calc(-50% - 5px)) scale(1.46) rotate(13deg);
  }

  31% {
    opacity: 1;
    transform: translate(calc(-50% - 7px), calc(-50% + 2px)) scale(1.58) rotate(-12deg);
  }

  32% {
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% + 5px)) scale(1.48) rotate(10deg);
  }

  33% {
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% - 5px)) scale(1.6) rotate(-13deg);
  }

  34% {
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% + 1px)) scale(1.5) rotate(11deg);
  }

  35% {
    opacity: 1;
    transform: translate(calc(-50% - 4px), calc(-50% + 6px)) scale(1.62) rotate(-10deg);
  }

  36% {
    opacity: 1;
    transform: translate(calc(-50% + 7px), calc(-50% - 2px)) scale(1.52) rotate(13deg);
  }

  37% {
    opacity: 1;
    transform: translate(calc(-50% - 7px), calc(-50% - 4px)) scale(1.64) rotate(-14deg);
  }

  38% {
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% + 5px)) scale(1.54) rotate(10deg);
  }

  39% {
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% - 6px)) scale(1.66) rotate(-12deg);
  }

  40% {
    opacity: 1;
    transform: translate(calc(-50% + 8px), calc(-50% + 2px)) scale(1.56) rotate(14deg);
  }

  41% {
    opacity: 1;
    transform: translate(calc(-50% - 7px), calc(-50% + 5px)) scale(1.68) rotate(-13deg);
  }

  42% {
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% - 5px)) scale(1.58) rotate(11deg);
  }

  43% {
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% + 3px)) scale(1.7) rotate(-14deg);
  }

  44% {
    opacity: 1;
    transform: translate(calc(-50% + 7px), calc(-50% + 5px)) scale(1.62) rotate(12deg);
  }

  45% {
    opacity: 1;
    transform: translate(calc(-50% - 8px), calc(-50% - 3px)) scale(1.74) rotate(-15deg);
    box-shadow: 0 0 22px rgba(255, 238, 118, .95), 0 0 42px rgba(37, 233, 255, .58), 0 0 22px rgba(255, 82, 0, .42);
  }

  52% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.92) rotate(0deg);
    color: #fff9cf;
    box-shadow: 0 0 28px rgba(255, 244, 126, .98), 0 0 52px rgba(255, 111, 0, .72), 0 0 86px rgba(255, 61, 0, .38);
  }

  62% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(2.22) rotate(12deg);
    color: #fff;
    box-shadow: 0 0 34px rgba(255, 244, 126, 1), 0 0 70px rgba(255, 111, 0, .86), 0 0 110px rgba(255, 61, 0, .5);
  }

  72%,
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.9) rotate(0deg);
  }
}

@keyframes allInCoreFire {
  0%,
  46% {
    opacity: 0;
    transform: scale(.25) rotate(0deg);
  }

  52% {
    opacity: .82;
    transform: scale(1.04) rotate(18deg);
  }

  58% {
    opacity: 1;
    transform: scale(1.32) rotate(-16deg);
  }

  64% {
    opacity: .9;
    transform: scale(1.55) rotate(22deg);
  }

  78%,
  100% {
    opacity: 0;
    transform: scale(1.95) rotate(0deg);
  }
}

@keyframes allInExplosion {
  0%,
  48% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.18) rotate(0deg);
  }

  56% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.05) rotate(18deg);
  }

  66% {
    opacity: .94;
    transform: translate(-50%, -50%) scale(1.8) rotate(-12deg);
  }

  78%,
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(2.3) rotate(0deg);
  }
}

@keyframes allInLossExplosion {
  0%,
  70% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.18) rotate(0deg);
  }

  78% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.15) rotate(18deg);
  }

  90% {
    opacity: .96;
    transform: translate(-50%, -50%) scale(2.8) rotate(-12deg);
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(3.55) rotate(0deg);
  }
}

@keyframes allInNameHold {
  0%,
  16% {
    opacity: 0;
  }

  28%,
  80% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@keyframes allInWinnerPop {
  0%,
  78% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.82);
  }

  88%,
  96% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.12);
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(1);
  }
}

@keyframes settlementFloatIn {
  0% {
    opacity: 0;
    transform: translate(-50%, 7px) scale(.86);
  }

  100% {
    opacity: 1;
    transform: translate(-50%, 0) scale(1);
  }
}

@keyframes compareLoserCardHit {
  0%,
  68% {
    transform: translateY(0) rotate(0deg);
  }

  76% {
    transform: translateY(1px) rotate(-1.8deg);
  }

  84% {
    transform: translateY(-1px) rotate(1.2deg);
  }

  100% {
    transform: translateY(0) rotate(0deg);
  }
}

@keyframes loserHit {
  0%,
  72% {
    transform: translate(-50%, -50%) scale(1);
  }

  84% {
    transform: translate(calc(-50% + var(--hit-x)), calc(-50% + var(--hit-y))) scale(.9) rotate(3deg);
  }

  100% {
    transform: translate(-50%, -50%) scale(1);
  }
}

@keyframes vsShove {
  0%,
  12% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.48);
  }

  18% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }

  32% {
    opacity: 1;
    transform: translate(calc(-50% + var(--push-x)), calc(-50% + var(--push-y))) scale(1.04);
  }

  48% {
    opacity: 1;
    transform: translate(calc(-50% + var(--pull-x)), calc(-50% + var(--pull-y))) scale(.96);
  }

  62% {
    opacity: 1;
    transform: translate(calc(-50% + var(--push-x)), calc(-50% + var(--push-y))) scale(1.08);
  }

  76% {
    left: var(--mx);
    top: var(--my);
    opacity: 1;
    transform: translate(-50%, -50%) scale(.98);
  }

  86% {
    left: var(--fx);
    top: var(--fy);
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.18);
  }

  100% {
    left: var(--fx);
    top: var(--fy);
    opacity: 0;
    transform: translate(-50%, -50%) scale(1.45);
  }
}

@keyframes vsAxisShove {
  0%,
  12% {
    left: 50%;
    opacity: 0;
    transform: translate(-50%, -50%) rotate(var(--counter-angle)) scale(.28);
    background: radial-gradient(circle, #fff2a3, #ffb300 68%, #a45300);
    color: #7a3200;
    box-shadow: 0 0 18px rgba(255, 238, 118, .82), 0 0 28px var(--bolt-from-glow), 0 0 28px var(--bolt-to-glow);
  }

  18% {
    left: 50%;
    opacity: 1;
    transform: translate(-50%, -50%) rotate(calc(var(--counter-angle) - 3deg)) scale(.58);
  }

  20% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% - 4px)) rotate(calc(var(--counter-angle) + 9deg)) scale(1.34);
  }

  21% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% + 4px)) rotate(calc(var(--counter-angle) - 10deg)) scale(1.42);
  }

  22% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% + 3px)) rotate(calc(var(--counter-angle) + 8deg)) scale(1.36);
  }

  23% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% - 3px)) rotate(calc(var(--counter-angle) - 11deg)) scale(1.44);
  }

  24% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% - 3px)) rotate(calc(var(--counter-angle) + 7deg)) scale(1.34);
  }

  25% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 4px), calc(-50% + 5px)) rotate(calc(var(--counter-angle) - 9deg)) scale(1.46);
  }

  26% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% + 2px)) rotate(calc(var(--counter-angle) + 12deg)) scale(1.1);
  }

  27% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% - 5px)) rotate(calc(var(--counter-angle) - 12deg)) scale(1.2);
  }

  28% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% + 3px)) rotate(calc(var(--counter-angle) - 8deg)) scale(1.14);
  }

  29% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% - 4px)) rotate(calc(var(--counter-angle) + 11deg)) scale(1.17);
  }

  30% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 3px), calc(-50% + 6px)) rotate(calc(var(--counter-angle) - 7deg)) scale(1.11);
  }

  31% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% + 1px)) rotate(calc(var(--counter-angle) + 13deg)) scale(1.21);
  }

  32% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 3px), calc(-50% + 2px)) rotate(calc(var(--counter-angle) + 5deg)) scale(1.16);
  }

  33% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% - 6px)) rotate(calc(var(--counter-angle) - 10deg)) scale(1.22);
  }

  34% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% + 2px)) rotate(calc(var(--counter-angle) + 10deg)) scale(1.12);
  }

  35% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% + 5px)) rotate(calc(var(--counter-angle) - 13deg)) scale(1.2);
  }

  36% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% + 1px)) rotate(calc(var(--counter-angle) - 9deg)) scale(1.09);
  }

  37% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% - 4px)) rotate(calc(var(--counter-angle) + 12deg)) scale(1.23);
  }

  38% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 3px), calc(-50% + 6px)) rotate(calc(var(--counter-angle) - 8deg)) scale(1.12);
  }

  39% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% + 1px)) rotate(calc(var(--counter-angle) + 14deg)) scale(1.22);
  }

  40% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 4px), calc(-50% - 3px)) rotate(calc(var(--counter-angle) + 8deg)) scale(1.18);
  }

  41% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% - 5px)) rotate(calc(var(--counter-angle) - 12deg)) scale(1.24);
  }

  42% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 4px), calc(-50% + 6px)) rotate(calc(var(--counter-angle) + 9deg)) scale(1.14);
  }

  43% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% + 2px)) rotate(calc(var(--counter-angle) - 14deg)) scale(1.25);
  }

  44% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 3px), calc(-50% + 4px)) rotate(calc(var(--counter-angle) - 6deg)) scale(1.1);
  }

  45% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% - 5px)) rotate(calc(var(--counter-angle) + 13deg)) scale(1.24);
  }

  46% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% + 6px)) rotate(calc(var(--counter-angle) - 10deg)) scale(1.13);
  }

  47% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% + 2px)) rotate(calc(var(--counter-angle) + 15deg)) scale(1.26);
  }

  48% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% - 2px)) rotate(calc(var(--counter-angle) - 6deg)) scale(1.06);
  }

  49% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 4px), calc(-50% + 5px)) rotate(calc(var(--counter-angle) + 12deg)) scale(1.23);
  }

  50% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% - 4px)) rotate(calc(var(--counter-angle) - 13deg)) scale(1.16);
  }

  51% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% - 1px)) rotate(calc(var(--counter-angle) + 14deg)) scale(1.28);
  }

  52% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% + 2px)) rotate(calc(var(--counter-angle) + 10deg)) scale(1.2);
  }

  53% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% + 5px)) rotate(calc(var(--counter-angle) - 12deg)) scale(1.25);
  }

  54% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 3px), calc(-50% - 6px)) rotate(calc(var(--counter-angle) + 11deg)) scale(1.15);
  }

  55% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% + 1px)) rotate(calc(var(--counter-angle) - 15deg)) scale(1.29);
  }

  56% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 5px), calc(-50% - 4px)) rotate(calc(var(--counter-angle) - 10deg)) scale(1.12);
  }

  57% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 6px), calc(-50% + 5px)) rotate(calc(var(--counter-angle) + 14deg)) scale(1.28);
  }

  58% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 4px), calc(-50% - 6px)) rotate(calc(var(--counter-angle) - 13deg)) scale(1.18);
  }

  59% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 5px), calc(-50% + 3px)) rotate(calc(var(--counter-angle) + 16deg)) scale(1.31);
  }

  60% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 3px), calc(-50% + 3px)) rotate(calc(var(--counter-angle) + 7deg)) scale(1.24);
  }

  61% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% + 6px), calc(-50% - 3px)) rotate(calc(var(--counter-angle) - 16deg)) scale(1.32);
  }

  62% {
    left: 50%;
    opacity: 1;
    transform: translate(calc(-50% - 2px), calc(-50% + 1px)) rotate(calc(var(--counter-angle) + 4deg)) scale(1.22);
    color: #7a3200;
    background: radial-gradient(circle, #fff8bc 0 18%, #ffd332 45%, #ff7500 72%, #c23600);
    box-shadow: 0 0 24px rgba(255, 226, 55, .96), 0 0 40px rgba(255, 96, 0, .58);
  }

  76% {
    left: var(--loser-pos);
    opacity: 1;
    transform: translate(var(--loser-shift), -50%) rotate(var(--counter-angle)) scale(1.18);
    color: transparent;
    text-shadow: none;
    background:
      radial-gradient(circle, #fff 0 17%, #fff06a 18% 32%, #ff8400 33% 58%, #ff2f00 59% 76%, #8b1000 77%);
    box-shadow: 0 0 26px rgba(255, 226, 55, .9), 0 0 48px rgba(255, 72, 0, .72);
  }

  86% {
    left: var(--loser-pos);
    opacity: 1;
    transform: translate(var(--loser-shift), -50%) rotate(var(--counter-angle)) scale(1.34);
    color: transparent;
    text-shadow: none;
    background:
      radial-gradient(circle, #fff 0 13%, #fff06a 14% 28%, #ff7600 29% 54%, #ff1d00 55% 72%, transparent 73%);
  }

  100% {
    left: var(--loser-pos);
    opacity: 0;
    transform: translate(var(--loser-shift), -50%) rotate(var(--counter-angle)) scale(1.55);
    color: transparent;
  }
}

@keyframes explosionPop {
  0%,
  78% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(.18) rotate(0deg);
  }

  86% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.25) rotate(16deg);
  }

  94% {
    opacity: .92;
    transform: translate(-50%, -50%) scale(2.3) rotate(-10deg);
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(3.15) rotate(0deg);
  }
}

@keyframes loserVsExplosion {
  0%,
  72% {
    opacity: 0;
    transform: translate(var(--loser-shift), -50%) scale(.16) rotate(0deg);
  }

  78% {
    opacity: 1;
    transform: translate(var(--loser-shift), -50%) scale(1.05) rotate(14deg);
  }

  88% {
    opacity: .96;
    transform: translate(var(--loser-shift), -50%) scale(2.6) rotate(-10deg);
  }

  100% {
    opacity: 0;
    transform: translate(var(--loser-shift), -50%) scale(4.1) rotate(0deg);
  }
}

.table-message {
  position: absolute;
  left: 50%;
  top: 46%;
  /* Must clear .pot-chip-pile/.landed-chip-layer (z-index: 20) -- without
     this the "换桌频繁"/"换桌成功" toast and other room messages silently
     render underneath the chip pile whenever one happens to be sitting in
     the middle of the table at the same time. */
  z-index: 21;
  transform: translateX(-50%);
  width: min(218px, 52%);
  padding: 6px 10px;
  border-radius: 999px;
  color: #fff6b8;
  font-size: 12px;
  line-height: 1.35;
  text-align: center;
  background: rgba(0, 0, 0, .32);
}

/* room-toast holds at full opacity for most of its life then fades over the
   last ~15% -- animation-duration/animation-delay are set inline per toast
   (see roomToastAnimationStyle() in app.js) since different toasts hold for
   different lengths of time (2.2s-6s). */
.room-toast {
  animation-name: roomToastFade;
  animation-timing-function: ease-out;
  animation-fill-mode: forwards;
}

@keyframes roomToastFade {
  0%, 85% { opacity: 1; }
  100% { opacity: 0; }
}

/* On the hundred-room screen the zone grid sits in its own z-indexed layer
   (see .hundred-banker-row etc.) and would otherwise paint over this toast
   since .table-message itself has no z-index of its own. */
.hundred-screen .table-message {
  z-index: 20;
  background: rgba(0, 0, 0, .74);
}

/* Same problem on the lobby: .lobby-bg is `position: relative` and comes
   after this toast in the DOM, so without its own z-index it silently
   painted underneath .lobby-bg's opaque background — the toast never
   actually became visible, it just sat there until roomToastUntil expired
   (or got picked up later by the next room's own toast, looking like it
   "moved" there). */
.lobby-toast {
  z-index: 10;
}

.game-announcement-wrap {
  position: relative;
}

.game-announcement-anchor {
  height: 24px;
  pointer-events: none;
}

.game-announcement {
  display: block;
  width: 100%;
  appearance: none;
  padding: 0;
  overflow: hidden;
  height: 24px;
  border: 1px solid rgba(255, 227, 124, .52);
  background: linear-gradient(180deg, #167458, #0b5b45);
  color: #fff2b8;
  font-size: 12px;
  font-weight: 800;
  line-height: 22px;
  white-space: nowrap;
  text-align: left;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18), 0 6px 14px rgba(0, 0, 0, .2);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.game-announcement-track-marquee {
  display: inline-block;
  min-width: max-content;
  padding-left: 100%;
  animation: announcementMarquee var(--announcement-duration, 32s) linear infinite;
  animation-delay: var(--announcement-delay, 0ms);
}

.game-announcement-track-marquee-inline {
  padding-left: 14px;
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}

.game-announcement-track-vertical {
  position: relative;
  display: block;
  height: 100%;
}

.game-announcement-track-vertical span {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 14px;
  overflow: hidden;
  opacity: 0;
  text-overflow: ellipsis;
  transform: translateY(45%);
  animation: announcementVerticalRefresh var(--announcement-duration, 5560ms) ease-in-out infinite;
  animation-delay: var(--announcement-delay, 0ms);
}

.game-announcement-track-vertical-intro span {
  animation-name: announcementVerticalIntro;
  animation-fill-mode: both;
}

.game-announcement-track-long {
  position: relative;
  display: block;
  height: 100%;
  overflow: hidden;
  opacity: 0;
  transform: translateY(45%);
  animation: announcementVerticalIntro var(--announcement-reveal-duration, 5560ms) ease-in-out both;
  animation-delay: var(--announcement-reveal-delay, 0ms);
}

.game-announcement-track-long-marquee {
  display: inline-block;
  min-width: max-content;
  padding-left: 14px;
  animation: announcementMarquee var(--announcement-marquee-duration, 32s) linear 1 both;
  animation-delay: var(--announcement-marquee-delay, 5560ms);
}

.game-announcement-track-vertical span.is-system-msg,
.game-announcement-track-long-marquee.is-system-msg {
  color: #ff4d4f;
}

.game-announcement-wrap-lobby,
.game-announcement-anchor-lobby {
  position: relative;
  z-index: 10;
  margin: 8px 14px 0;
}

.game-announcement-wrap.is-open {
  z-index: 30;
}

.game-announcement-lobby {
  border-radius: 10px;
}

.game-announcement-wrap-room,
.game-announcement-anchor-room {
  /* Fixed to the real viewport (not .poker-table) so this reliably lands
     at the very top of the screen regardless of the table's own layout --
     updateAnnouncementTickerLayer() just copies this anchor's measured
     getBoundingClientRect() onto the detached ticker it actually renders,
     so the ticker follows wherever this anchor is positioned. Vertically
     centered on the room-menu-btn's own centerline (button: top offset
     max(8px,safe-area) + half its 43px height; this bar is 24px tall) so
     the two align, instead of this sitting flush against its own top edge. */
  position: fixed;
  left: 50%;
  top: calc(max(8px, env(safe-area-inset-top)) + 9.5px);
  z-index: 46;
  /* "88%" here used to mean 88% of .poker-table's own (phone-width-capped)
     box; now that this is position:fixed, percentages resolve against the
     real viewport instead, which is wider than the game's actual
     min(430px,100vw) column on anything but a phone-width viewport. Cap
     against that column's width directly instead of the raw viewport. */
  width: min(380px, calc(min(430px, 100vw) - 48px));
  transform: translateX(-50%);
}

.game-announcement-room {
  border-radius: 999px;
}

/* Same fixed/menu-aligned position as 三张牌's own -room variant (see
   .game-announcement-wrap-room above) -- per "make Texas look exactly
   like 三张牌". */
.game-announcement-wrap-texas,
.game-announcement-anchor-texas {
  position: fixed;
  left: 50%;
  top: calc(max(8px, env(safe-area-inset-top)) + 9.5px);
  z-index: 46;
  width: min(380px, calc(min(430px, 100vw) - 48px));
  transform: translateX(-50%);
}

.game-announcement-texas {
  border-radius: 999px;
}

/* Moved up to the same fixed-to-viewport line room/texas already use for
   their own announcement bar (see .game-announcement-anchor-room) -- this
   is exactly the horizontal line "多人竞技" used to sit on before that
   topbar bar was dropped (see .hundred-screen > .topbar). Still measured
   and copied onto the detached ticker by updateAnnouncementTickerLayer(),
   same as every other scope; only this anchor's own position changed. */
.game-announcement-wrap-hundred,
.game-announcement-anchor-hundred {
  position: fixed;
  left: 50%;
  top: calc(max(8px, env(safe-area-inset-top)) + 2px);
  z-index: 46;
  width: min(380px, calc(min(430px, 100vw) - 48px));
  transform: translateX(-50%);
}

.game-announcement-hundred {
  height: 24px;
  font-size: 12px;
  line-height: 22px;
  border-radius: 999px;
}

.game-announcement-wrap.game-announcement-wrap-floating,
.game-announcement-wrap.game-announcement-wrap-detached {
  position: fixed;
  right: auto;
  margin: 0;
  transform: none;
  z-index: 80;
}

.game-announcement-wrap.game-announcement-wrap-detached {
  pointer-events: auto;
}

.announcement-history-panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 3;
  max-height: min(236px, 42vh);
  overflow-y: auto;
  padding: 8px;
  border: 1px solid rgba(255, 227, 124, .46);
  border-radius: 10px;
  background: linear-gradient(180deg, rgba(22, 116, 88, .98), rgba(6, 64, 50, .98));
  color: #fff7cf;
  box-shadow: 0 12px 24px rgba(0, 0, 0, .32), inset 0 1px 0 rgba(255, 255, 255, .12);
}

.announcement-history-panel-floating {
  position: fixed;
  right: auto;
  z-index: 80;
}

.announcement-history-title {
  padding: 0 2px 6px;
  color: #ffe37c;
  font-size: 12px;
  font-weight: 900;
}

.announcement-history-item {
  display: block;
  padding: 7px 2px;
  border-top: 1px solid rgba(255, 255, 255, .14);
  font-size: 12px;
  font-weight: 750;
  line-height: 1.35;
  white-space: normal;
}

.announcement-history-text {
  overflow-wrap: anywhere;
}

.announcement-history-text.is-system-msg {
  color: #ff4d4f;
}

@keyframes announcementMarquee {
  0% {
    transform: translateX(0);
  }

  100% {
    transform: translateX(-50%);
  }
}

@keyframes announcementVerticalRefresh {
  0% {
    opacity: 0;
    transform: translateY(45%);
  }

  10% {
    opacity: 1;
    transform: translateY(0);
  }

  90% {
    opacity: 1;
    transform: translateY(0);
  }

  100% {
    opacity: 0;
    transform: translateY(-45%);
  }
}

@keyframes announcementVerticalIntro {
  0% {
    opacity: 0;
    transform: translateY(45%);
  }

  12% {
    opacity: 1;
    transform: translateY(0);
  }

  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes announcementHistoryDrop {
  0% {
    opacity: 0;
    transform: translateY(-6px);
  }

  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.table-message.settlement-message {
  top: 60%;
  z-index: 23;
  width: min(306px, 76%);
  padding: 8px 12px;
  border: 1px solid rgba(255, 227, 124, .36);
  color: #fff3a6;
  background: rgba(0, 0, 0, .58);
}

/* 58% (the previous value, chosen to clear .table-tier-label/top-row seats)
   still sat squarely inside .pot-chip-pile/.landed-chip-layer's own band
   (top:57%, 118px tall, centered -- so it spans roughly 45%-69% of the
   table height) -- whenever real chips were actually piled on the table
   (not just an empty felt) the hint text rendered right on top of them
   ("比牌的提示...不要放在中间和其他内容打架"). Measured via an actual
   rendered table (610px tall poker-table): pot-chip-pile's own bottom edge
   lands at ~67%, .table-tier-label sits at 72%-77%, and .seat.me (bottom:
   22px) starts at ~74% of that same 610px -- squeezed between the two,
   only ~78%-84% is genuinely empty felt with nothing else drawn there.
   Nudged from 80% to 78% (the very top of that empty band) to sit closer to
   .table-tier-label without actually touching it ("往上移动一点，贴近场
   次文字那里，但是不要重叠打架"). */
.table-message.compare-hint {
  top: 78%;
  z-index: 22;
}

.seat {
  position: absolute;
  width: 116px;
  text-align: center;
  padding: 4px 5px;
  border-radius: 12px;
  background: rgba(0, 31, 20, .46);
}

.empty-seat {
  display: grid;
  place-items: center;
  width: 116px;
  height: 74px;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.seat.turn {
  box-shadow: 0 0 0 3px rgba(169, 255, 38, .65), 0 0 24px rgba(137, 255, 39, .32);
}

/* Texas dropped the separate numeric countdown badge (it kept fighting the
   D/SB/BB badges for the same spot above the seat) in favor of a ring drawn
   just outside the seat frame that sweeps away clockwise over the turn's
   duration, like a clock hand eating into a pie -- the ring *is* the
   countdown now, instead of the whole frame just dimming in place. Built
   from a conic-gradient masked down to a thin band (the ring-via-mask
   trick) on ::after rather than animating box-shadow, since box-shadow has
   no way to sweep directionally.

   --texas-turn-elapsed/--texas-ring-color used to be driven by a
   continuous 10s CSS @keyframes animation (registered via @property so the
   browser could tween the conic-gradient smoothly) -- that meant a
   conic-gradient repainted through a mask-composite on every single frame
   for a full 10 seconds, every turn, every hand, and neither property is
   compositor-only. app.js's turnRingStyle() now computes the current
   percentage/color itself and sets it as a plain static inline custom
   property on every ~400-550ms poll rebuild instead (see TEXAS_TURN_SECONDS
   and turnRingStyle() in app.js) -- same sweep, same colors, just repainted
   ~2x/sec instead of 60x/sec.

   The "consumed" wedge is transparent and grows clockwise from 12 o'clock
   (0deg), with the still-colored remainder trailing behind it -- that's
   what makes the boundary between colored/transparent sweep clockwise as
   --texas-turn-elapsed rises. The earlier version colored the *first* wedge
   from 0deg instead and shrank it, which reads as the boundary receding
   counter-clockwise back toward 12 -- the opposite of what a clock hand
   actually looks like. */
.table-screen.texas-screen .seat.turn {
  box-shadow: 0 0 0 2px rgba(120, 255, 60, .55), 0 0 16px rgba(120, 255, 60, .3);
}

.table-screen.texas-screen .seat.turn::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: inherit;
  padding: 3px;
  pointer-events: none;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  background: conic-gradient(from 0deg, transparent 0% var(--texas-turn-elapsed, 0%), var(--texas-ring-color, #8dff4a) var(--texas-turn-elapsed, 0%) 100%);
}

/* All-in: a plain warm glow, same box-shadow technique as .seat.turn/
   .seat.winner above/below rather than the multi-layer gradient+mask+
   flicker-animation version this replaced ("全下火改成发光会比较简单一
   点吗" -- yes: box-shadow paints outside the border box for free (no
   masking needed to keep it off the frame's own interior) and stays with
   the element's own stacking (no z-index tricks needed either), at the
   cost of the animated flicker reading less like an actual flame. app.js's
   seatOnFire (seat.allIn && game.phase !== "showdown") still drives the
   class rather than raw seat.allIn, same reasoning as before -- seat.allIn
   alone stays true through the whole showdown reveal/payout, and the glow
   should be gone before that starts ("摊牌时或结算时全下火立即消失"). */
.table-screen.texas-screen .seat.allin {
  box-shadow: 0 0 0 2px rgba(255, 106, 26, .6), 0 0 16px rgba(255, 90, 20, .45);
}

/* Joining/leaving used to just pop the seat in or out between polls, which
   read as the table silently refreshing. These play from a JS-computed
   negative animation-delay (see app.js's SEAT_FLY_MS handling) so the
   animation keeps advancing correctly across renderRoom()'s full re-renders
   instead of restarting every ~400ms poll. */
@keyframes seatFlyInRight {
  from { transform: translateX(140px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

@keyframes seatFlyInLeft {
  from { transform: translateX(-140px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

@keyframes seatFlyOutRight {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(140px); opacity: 0; }
}

@keyframes seatFlyOutLeft {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(-140px); opacity: 0; }
}

.seat.seat-fly-in-right {
  animation: seatFlyInRight 550ms ease-out both;
}

.seat.seat-fly-in-left {
  animation: seatFlyInLeft 550ms ease-out both;
}

.seat.seat-fly-out-right {
  animation: seatFlyOutRight 550ms ease-in both;
  pointer-events: none;
}

.seat.seat-fly-out-left {
  animation: seatFlyOutLeft 550ms ease-in both;
  pointer-events: none;
}

/* t1/t4/t5 only: these three seats carry their own resting scale(.86) (see
   the .seat.t1/.t4/.t5 rules further down this file) -- the shared fly-in/
   out keyframes above only ever animate translateX, which as a `transform`
   animation fully replaces whatever static `transform` the seat itself
   carries, not layers on top of it. That left an entering/leaving t1/t4/t5
   rendering at full (unscaled) size, and t4 also missing its own
   translateX(-50%) centering, for the whole 550ms -- "左上右上和正上方玩
   家入场就座的动画，动画的人物框大小和位置与最终的大小和位置不符". Baking
   each seat's own resting transform into its own named keyframe keeps the
   slide-in motion but always composes with the size/position it actually
   settles into, instead of overriding it. Higher specificity than the
   shared .seat.seat-fly-in-*/.seat.seat-fly-out-* rules above (3 seat
   classes vs 2), so only animation-name needs overriding here -- duration/
   timing-function/fill-mode still come from those shared rules. */
@keyframes seatFlyInRightScaled {
  from { transform: translateX(140px) scale(.86); opacity: 0; }
  to { transform: translateX(0) scale(.86); opacity: 1; }
}

@keyframes seatFlyOutRightScaled {
  from { transform: translateX(0) scale(.86); opacity: 1; }
  to { transform: translateX(140px) scale(.86); opacity: 0; }
}

@keyframes seatFlyInLeftScaled {
  from { transform: translateX(-140px) scale(.86); opacity: 0; }
  to { transform: translateX(0) scale(.86); opacity: 1; }
}

@keyframes seatFlyOutLeftScaled {
  from { transform: translateX(0) scale(.86); opacity: 1; }
  to { transform: translateX(-140px) scale(.86); opacity: 0; }
}

@keyframes seatFlyInLeftScaledCentered {
  from { transform: translateX(calc(-50% - 140px)) scale(.86); opacity: 0; }
  to { transform: translateX(-50%) scale(.86); opacity: 1; }
}

@keyframes seatFlyOutLeftScaledCentered {
  from { transform: translateX(-50%) scale(.86); opacity: 1; }
  to { transform: translateX(calc(-50% - 140px)) scale(.86); opacity: 0; }
}

.table-screen.texas-screen .seat.t1.seat-fly-in-right {
  animation-name: seatFlyInRightScaled;
}

.table-screen.texas-screen .seat.t1.seat-fly-out-right {
  animation-name: seatFlyOutRightScaled;
}

.table-screen.texas-screen .seat.t5.seat-fly-in-left {
  animation-name: seatFlyInLeftScaled;
}

.table-screen.texas-screen .seat.t5.seat-fly-out-left {
  animation-name: seatFlyOutLeftScaled;
}

.table-screen.texas-screen .seat.t4.seat-fly-in-left {
  animation-name: seatFlyInLeftScaledCentered;
}

.table-screen.texas-screen .seat.t4.seat-fly-out-left {
  animation-name: seatFlyOutLeftScaledCentered;
}

.seat.spectator-self {
  opacity: .72;
  box-shadow: 0 0 0 2px rgba(180, 210, 215, .32);
}

.seat.spectator-self .cards {
  display: none;
}

.seat.spectator-self .seat-stack {
  display: none;
}

.seat.spectator-self .seat-bet::before {
  content: "观战中";
}

.seat.spectator-self .seat-bet {
  color: #d9f8ff;
  font-size: 12px;
}

.seat.spectator-self .seat-bet {
  font-size: 0;
}

.seat.spectator-self .seat-bet::before {
  font-size: 12px;
}

.seat.me {
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  width: 178px;
  padding: 7px 8px 6px;
  box-shadow: 0 0 0 3px rgba(145, 255, 47, .35);
}

.seat.s1 {
  right: 4px;
  top: 55%;
}

.seat.s0 {
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  width: 178px;
  padding: 7px 8px 6px;
}

.seat.s2 {
  right: 4px;
  top: 23%;
}

.seat.s3 {
  left: 4px;
  top: 23%;
}

.seat.s4 {
  left: 4px;
  top: 55%;
}

.seat-main {
  display: flex;
  /* flex-start, not center -- centering against .seat-profile's full height
     (avatar + name + stack) pulled the card row down below the avatar's own
     vertical center. Aligning both to the top edge keeps the cards level
     with the avatar itself. */
  align-items: flex-start;
  justify-content: center;
  gap: 5px;
}

.seat.s1 .seat-main,
.seat.s2 .seat-main {
  flex-direction: row-reverse;
}

.seat-profile {
  position: relative;
  /* Gives .avatar/.seat-name their own clean stacking comparison, immune to
     any z-index bleeding in from other floating badges elsewhere in this
     seat (.settlement-float, .card-status, etc. use much higher z-index
     values for their own unrelated purposes) -- belt-and-suspenders on top
     of .seat-name's own z-index so there's no ambiguity left about which of
     the two actually wins. */
  isolation: isolate;
  /* isolation alone only settles comparisons *inside* this box -- without
     its own explicit z-index, .seat-profile as a whole (auto/0) still
     loses to any positioned sibling that does have one, like "我" own
     .seat-side-actions.top (z-index:41, floating right above the frame in
     the exact same spot .settlement-float does) -- 全下 was painting over
     the win/loss bubble despite its internal z-index:60, because that 60
     never got compared against 41 directly, only against .seat-profile's
     own siblings *inside* seat-profile. */
  z-index: 52;
  min-width: 38px;
  max-width: 48px;
}

.seat.me .seat-profile,
.seat.s0 .seat-profile {
  min-width: 50px;
  max-width: 56px;
}

/* One quick hop whenever this seat calls/raises/compares/goes all-in (see
   enqueueChipEvent's jinhuaJumpAt tracking) -- ties the same chip-event hook
   used for the chip-fly/sound cues to a visible per-avatar cue too. */
@keyframes avatarJump {
  0%, 100% { transform: translateY(0); }
  35% { transform: translateY(-14px); }
  60% { transform: translateY(0); }
  78% { transform: translateY(-5px); }
}

.avatar.avatar-jump {
  animation: avatarJump 420ms ease-out both;
}

.avatar {
  /* Deliberately NOT position:relative -- matches .hundred-seat-avatar
     exactly (also has no "position" at all). A non-positioned (static)
     element is unambiguously behind ANY positioned sibling with an
     explicit z-index in every engine's paint order, no exceptions. Giving
     .avatar its own "position:relative, z-index:auto" status put it one
     category closer to .seat-name-wrap's explicit z-index, and that
     narrower gap is exactly what real devices got wrong. Hundred-room
     never had this bug because its avatar was never positioned at all. */
  display: inline-grid;
  place-items: center;
  width: 36px;
  height: 36px;
  overflow: hidden;
  border-radius: 999px;
  font-weight: 900;
  color: #08353d;
  background: linear-gradient(#d8ffff, #42cbd5);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  border: 0;
}

.avatar.has-image span {
  display: none;
}

/* VIP perk: exclusive avatar frame -- applied alongside .avatar/.hundred-seat-avatar/
   .profile-avatar (all differently-shaped/sized base classes), so this only adds a
   border+glow rather than owning position/size itself. */
.vip-avatar-frame {
  border: 2px solid #ffd76b !important;
  box-shadow: 0 0 0 2px rgba(20, 12, 0, .55), 0 0 10px rgba(255, 215, 107, .85);
}

/* VIP perk: exclusive chat text color -- applies to both the flying barrage
   bubbles and the jinhua chat panel's own history list. */
.barrage-item.vip-chat,
.chat-row.vip-chat {
  color: #ffd76b;
}

.barrage-item.vip-chat strong,
.chat-row.vip-chat strong {
  color: #fff2c4;
}

.avatar.clickable {
  cursor: pointer;
}

.seat.me .avatar,
.seat.s0 .avatar {
  width: 48px;
  height: 48px;
  border-radius: 999px;
  font-size: 16px;
}

/* Fade in from below, hold, fade out upward -- fixed 5.74s lifetime (see
   TEXAS_SETTLEMENT_*_MS in app.js, which the keyframe percentages below
   are computed from) driven by syncTexasSettlement's cached timestamp
   rather than however long the server itself happens to keep
   game.phase==="showdown" (texas_finish_hand can move on well before
   this elapses, at which point the live seat.settlementLabel field is
   already back to "" -- renderTexasSeat keeps rendering the cached copy
   regardless). Each keyframe re-includes the base rule's own
   translateX(-50%) centering since the animation's own transform
   property replaces it outright for the animation's duration. */
@keyframes texasSettlementFloat {
  0% {
    opacity: 0;
    transform: translate(-50%, 10px);
  }

  5.57% {
    opacity: 1;
    transform: translate(-50%, 0);
  }

  92.68% {
    opacity: 1;
    transform: translate(-50%, 0);
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -14px);
  }
}

.settlement-float {
  position: absolute;
  left: 50%;
  top: -23px;
  /* Was 8 -- got buried under the action clusters now floating around "我"
     own seat frame (.seat-side-actions, z-index:41) and the avatar's own
     JS-bumped z-index:51 (see promoteInteractiveElements), both of which
     sit right where this floats too. Needs to clearly beat everything else
     stacked around a seat so the win/loss result is never hidden the one
     moment it actually matters. */
  z-index: 60;
  min-width: 48px;
  padding: 2px 7px;
  transform: translateX(-50%);
  border-radius: 999px;
  font-size: 14px;
  font-weight: 900;
  line-height: 1.15;
  text-align: center;
  white-space: nowrap;
  /* Was .58 -- read as a washed-out gray smudge instead of a solid,
     obvious badge once it was actually visible on top of everything else
     (see z-index above), especially over lighter avatar art underneath. */
  background: rgba(0, 0, 0, .85);
  text-shadow: 0 2px 2px rgba(0, 0, 0, .72);
  animation: texasSettlementFloat 5740ms ease forwards;
}

.settlement-float.win {
  color: #ffd85c;
  border: 1px solid rgba(255, 216, 92, .72);
  box-shadow: 0 0 14px rgba(255, 216, 92, .52);
}

.settlement-float.loss {
  color: #dde3e8;
  border: 1px solid rgba(210, 218, 224, .55);
}

.seat-name-wrap {
  /* FOUND THE REAL CAUSE (via a live console dump of the actual rendered
     DOM): app.js's promoteInteractiveElements() adds class
     "interaction-control" to every #app [role="button"] element after
     each render -- and avatarHtml() always sets role="button" whenever it
     has a click action, i.e. on essentially every seat avatar. That class
     carries "position:relative; z-index:51" (see .interaction-control,
     styles.css line ~43), a totally unrelated app-wide "make sure clickable
     things sit above floating panels" mechanism. So .avatar wasn't really
     z-index:auto/static at paint time like its own rule declares -- JS was
     quietly bumping it to z-index:51 on every single render, which beat
     every z-index this wrapper was ever given (1, 5, 6 ...). None of the
     GPU-layer/compositing theories from earlier were the actual bug; this
     was. Must stay above 51. */
  position: relative;
  z-index: 55;
  /* VIP seats add .vip-avatar-frame's blurred box-shadow glow on top of
     .avatar -- a blurred box-shadow can implicitly promote an element to
     its own GPU compositing layer on real mobile browsers, which is
     exactly the same "layer vs. non-layer" mis-ordering bug we already
     hit once before (see .avatar's own comment). Forcing this wrapper
     onto its own layer too keeps any such comparison a fair layer-vs-layer
     one so the higher z-index actually wins, VIP glow or not. */
  transform: translateZ(0);
  will-change: transform;
  margin-top: -14px;
  padding: 1px 4px;
}

.seat-name-wrap::before {
  content: "";
  position: absolute;
  /* No horizontal overhang -- the band must not extend past the seat
     frame's own width, so it fades to transparent using the gradient's
     own stops instead of bleeding outside the wrap's box. */
  inset: -1px 0;
  z-index: -1;
  background: linear-gradient(90deg, transparent, rgba(8, 10, 18, .85) 24%, rgba(8, 10, 18, .85) 76%, transparent);
}

.seat-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .7);
  font-size: 11px;
}

.seat-stack {
  margin-top: 2px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .7);
  font-size: 11px;
}

.seat.me .seat-name,
.seat.s0 .seat-name,
.seat.me .seat-stack,
.seat.s0 .seat-stack {
  font-size: 12px;
}

.seat-bet {
  display: inline-block;
  max-width: 100%;
  margin-top: 2px;
  padding: 2px 10px;
  border-radius: 999px;
  color: #ffe37c;
  font-size: 11px;
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, .1) 14%, rgba(0, 0, 0, .34) 34%, rgba(0, 0, 0, .34) 66%, rgba(0, 0, 0, .1) 86%, transparent);
  white-space: nowrap;
}

/* Opponents only now (see renderRoom()'s opponentBetMarkup) -- rides right
   below the card faces in .card-status' own spot instead of its own row
   below the whole frame, so the frame no longer needs to reserve flow
   height for a separate 已投入 row. Never actually coincides with
   .card-status itself (see opponentBetMarkup -- it only renders when
   status doesn't), but shares that same "just under the cards" position
   either way. */
.cards .seat-bet {
  position: absolute;
  left: 50%;
  /* Pulled in from -15px -- closer to (overlapping slightly into) the
     card faces themselves instead of floating further below them. */
  bottom: -9px;
  z-index: 4;
  /* Capped well inside the opponent frame's own ~112-116px width (see
     .seat's width) instead of growing with the amount label -- "已投入"
     shortened to "已投" (see the seatBet translation) for the same reason,
     ellipsis as a last resort for anything still too wide to fit. */
  max-width: 100px;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 0;
  transform: translateX(-50%);
}

/* Only the cards themselves gray out on a fold/compare loss (see
   .seat.folded .card and .seat.compare-loser .card below) -- dimming the
   whole seat frame made the avatar/name look grayed out too, which read as
   "this player is gone" rather than "this player's hand is out for the
   hand." */

.seat.compare-selectable {
  cursor: pointer;
  box-shadow: 0 0 0 3px rgba(255, 227, 124, .82), 0 0 22px rgba(255, 227, 124, .45);
}

.seat.compare-selectable::after {
  content: "点此比牌";
  position: absolute;
  left: 50%;
  top: -22px;
  transform: translateX(-50%);
  padding: 2px 7px;
  border-radius: 999px;
  color: #2d1600;
  font-size: 11px;
  font-weight: 900;
  white-space: nowrap;
  background: #ffe37c;
}

.seat.winner .avatar {
  border-color: #ffe37c;
  box-shadow: 0 0 18px rgba(255, 227, 124, .75);
}

/* Countdown used to be a numeric badge (.seat-timer) floating above/beside
   the seat -- replaced with a ring around the whole frame instead, same
   idea as Texas's own turn ring (see .table-screen.texas-screen
   .seat.turn::after's own comment further up for the full mechanism) --
   reuses the same --texas-turn-elapsed/--texas-ring-color properties (both
   just generic countdown machinery despite the name, set directly by
   app.js's shared turnRingStyle() on every render, no CSS animation
   involved) instead of duplicating them for a second screen. Explicitly
   :not(.texas-screen) too, not just :not(.hundred-screen) -- both this
   selector and Texas's own resolve to identical specificity (four classes +
   a pseudo-element each), so without this exclusion whichever rule happened
   to land later in the file would silently win on Texas screens too and
   break its sync. */
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.turn::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: inherit;
  padding: 3px;
  pointer-events: none;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  background: conic-gradient(from 0deg, transparent 0% var(--texas-turn-elapsed, 0%), var(--texas-ring-color, #8dff4a) var(--texas-turn-elapsed, 0%) 100%);
}

.cards {
  position: relative;
  display: flex;
  justify-content: center;
  gap: 2px;
  margin-top: 0;
  min-width: 78px;
  min-height: 38px;
}

.seat:not(.me) .cards {
  min-width: 62px;
  min-height: 44px;
  margin-bottom: 16px;
}

/* Stamped directly on top of the card backs (centered over the row) instead
   of floating above them -- keeps it out of the way of the card row's own
   vertical alignment with the avatar. Only while still face-down (:has a
   .card-back child) -- once the actual faces are revealed (showdown, or a
   compare loss), seat.looked is still true but the badge has to get out of
   the way so the revealed rank/suit are actually readable.
   top is a fixed 15px, not 50%, of .cards -- opponent card-backs are only
   29px tall (see .seat:not(.me) .card) sitting top-aligned inside .cards'
   own taller 44px min-height box (the flex row's default cross-start
   alignment, since the fixed-height cards never stretch to fill it), so
   50% of the box lands ~7px below the card backs' own visual midline.
   15px (~half the card height) is the actual card-back center. */
.seat.looked:not(.me) .cards:has(.card-back)::after {
  content: "已看";
  position: absolute;
  left: 50%;
  top: 15px;
  z-index: 5;
  transform: translate(-50%, -50%);
  padding: 2px 6px;
  border-radius: 999px;
  color: #04362f;
  font-size: 10px;
  font-weight: 900;
  line-height: 1;
  white-space: nowrap;
  background: linear-gradient(#effff9, #73ffd7);
  border: 1px solid rgba(255, 255, 255, .88);
  box-shadow: 0 0 10px rgba(82, 255, 213, .76), 0 2px 5px rgba(0, 0, 0, .38);
}

.card {
  position: relative;
  z-index: 1;
  display: block;
  overflow: hidden;
  width: 25px;
  height: 35px;
  border-radius: 4px;
  color: #202833;
  font-family: "Avenir Next", "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  line-height: 1;
  font-weight: 500;
  background: #fffdf7;
  border: 1px solid #4da36f;
  box-shadow: 0 2px 7px rgba(0, 0, 0, .34);
}

.seat.compare-loser .card {
  animation: compareLoserCardHit 3000ms ease-out both;
}

.card-rank {
  position: absolute;
  left: 4px;
  top: 4px;
  z-index: 3;
  display: block;
  font-weight: 500;
}

.card-suit {
  position: absolute;
  right: 4px;
  bottom: 2px;
  z-index: 3;
  display: block;
  font-family: Georgia, "Times New Roman", serif;
  font-size: .9em;
  font-weight: 400;
  line-height: 1;
}

.seat:not(.me) .card {
  width: 20px;
  height: 29px;
  border-radius: 4px;
  font-size: 13px;
}

.seat:not(.me) .card-rank {
  left: 3px;
  top: 3px;
}

.seat:not(.me) .card-suit {
  right: 3px;
  bottom: 1px;
}

.seat:not(.me) .card-back {
  border-width: 1px;
}

.seat.looked:not(.me) .card-back {
  border-color: rgba(126, 255, 220, .96);
  box-shadow: 0 0 0 2px rgba(82, 255, 213, .42), 0 2px 8px rgba(0, 0, 0, .36);
}

.card-status {
  position: absolute;
  left: 50%;
  bottom: -15px;
  z-index: 4;
  transform: translateX(-50%);
  max-width: 74px;
  padding: 1px 8px 2px;
  border-radius: 999px;
  color: #fff;
  font-size: 11px;
  font-weight: 900;
  line-height: 1.25;
  white-space: nowrap;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .72);
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, .16) 14%, rgba(0, 0, 0, .58) 34%, rgba(0, 0, 0, .58) 66%, rgba(0, 0, 0, .16) 86%, transparent);
}

.card-status.blind {
  top: auto;
  bottom: -15px;
  transform: translateX(-50%);
}

.card-status.ready {
  top: auto;
  bottom: -15px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, .16) 14%, rgba(0, 0, 0, .58) 34%, rgba(0, 0, 0, .58) 66%, rgba(0, 0, 0, .16) 86%, transparent);
}

.seat.looked .card-status {
  bottom: -15px;
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, .18) 14%, rgba(0, 0, 0, .62) 34%, rgba(0, 0, 0, .62) 66%, rgba(0, 0, 0, .18) 86%, transparent);
  border: 1px solid rgba(255, 255, 255, .22);
}

.card-status.own-card-status,
.card-status.blind.own-card-status,
.card-status.ready.own-card-status {
  top: auto;
  bottom: -14px;
  transform: translateX(-50%);
  max-width: 88px;
  padding: 1px 12px 2px;
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, .16) 12%, rgba(0, 0, 0, .6) 30%, rgba(0, 0, 0, .6) 70%, rgba(0, 0, 0, .16) 88%, transparent);
}

/* 金花: 看牌状态/已弃牌/牌型徽标整体上移一点，和下方昵称行拉开间距，避免
   拥挤 -- 只收窄 Texas/百人各自的位置，用同一个 .card-status 类不受影响。 */
.table-screen:not(.texas-screen):not(.hundred-screen) .card-status,
.table-screen:not(.texas-screen):not(.hundred-screen) .card-status.blind,
.table-screen:not(.texas-screen):not(.hundred-screen) .card-status.ready,
.table-screen:not(.texas-screen):not(.hundred-screen) .seat.looked .card-status {
  bottom: -9px;
}

.table-screen:not(.texas-screen):not(.hundred-screen) .card-status.own-card-status,
.table-screen:not(.texas-screen):not(.hundred-screen) .card-status.blind.own-card-status,
.table-screen:not(.texas-screen):not(.hundred-screen) .card-status.ready.own-card-status {
  bottom: -8px;
}

/* Hand-type text color: the ancestor .cards bump gives these enough
   specificity to win over the neutral-background rules above regardless of
   source order, since those rules also set color on the same element. */
.cards .card-status.hand-red {
  color: #ff5a4d;
}

.cards .card-status.hand-gold {
  color: #ffd45b;
}

.cards .card-status.hand-blue {
  color: #5cb3ff;
}

.cards .card-status.hand-white {
  color: #fff;
}

.seat.me .cards {
  min-width: 112px;
  min-height: 54px;
  margin-bottom: 17px;
}

.seat.me .card,
.seat.s0 .card {
  width: 34px;
  height: 48px;
  border-radius: 5px;
  font-size: 22px;
}

.seat.me .card-rank,
.seat.s0 .card-rank {
  left: 5px;
  top: 5px;
}

.seat.me .card-suit,
.seat.s0 .card-suit {
  right: 5px;
  bottom: 3px;
  font-size: .92em;
}

.card.warm {
  color: #c8192e;
}

.card-back {
  color: transparent;
  background:
    radial-gradient(circle at 30% 28%, transparent 0 5px, rgba(255, 255, 255, .24) 6px 7px, transparent 8px),
    radial-gradient(circle at 74% 68%, transparent 0 5px, rgba(255, 255, 255, .20) 6px 7px, transparent 8px),
    repeating-radial-gradient(circle at 50% 50%, rgba(255, 255, 255, .16) 0 2px, transparent 3px 8px),
    linear-gradient(135deg, #2ea7ed, #72d6ff 48%, #2982d6);
  background-size: 18px 18px, 18px 18px, 28px 28px, auto;
  border: 2px solid rgba(246, 255, 255, .92);
}

.card-back::before {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 3px;
  border: 1px solid rgba(12, 108, 177, .38);
  box-shadow: inset 0 0 8px rgba(255, 255, 255, .2);
}

.seat.folded .card,
.seat.folded .card-back {
  color: #69737a;
  background:
    radial-gradient(circle at 50% 50%, rgba(255, 255, 255, .34), transparent 34%),
    linear-gradient(180deg, #edf1f3, #aab4bb);
  border-color: rgba(230, 235, 238, .72);
  box-shadow: 0 2px 7px rgba(0, 0, 0, .28), inset 0 1px 0 rgba(255, 255, 255, .48);
  filter: grayscale(1) saturate(.25);
}

.seat.folded .card-back::before {
  border-color: rgba(91, 101, 108, .36);
}

.room-controls,
.hundred-controls {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 8px;
}

/* No panel background any more (see the two skin overrides below, also
   stripped) -- 全下/弃牌/比牌 float directly on the felt as bare buttons
   instead of sitting inside a bordered bar. Flex row (not the old 4-column
   grid, which existed to line up 7 buttons across 2 rows) since there are
   only 3 buttons left after 跟注/跟到底/加注/看牌 moved out to flank the
   seat -- centering the group here lands 全下 (the middle child, see
   controlsMarkup's fold/allin/showdown order) right on the table's own
   centerline. */
.jinhua-controls {
  position: fixed;
  left: 50%;
  bottom: 18px;
  z-index: 40;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: min(100vw, 430px);
  padding: 10px 8px max(10px, env(safe-area-inset-bottom));
}

/* VIP-only "上一局底牌" -- lives inside "我" own seat frame's bottom-right
   corner (see lastHandMarkup in renderRoom()) instead of floating in the
   control bar, where it used to eat into a grid row that was otherwise
   empty dead space for every non-VIP player. */
.last-hand-btn {
  position: absolute;
  right: 4px;
  bottom: 4px;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border-radius: 50%;
  font-size: 13px;
  line-height: 1;
  background: radial-gradient(circle at 34% 24%, rgba(255, 255, 255, .3), transparent 28%), linear-gradient(#3a2a05, #150e02);
  border: 1px solid rgba(255, 215, 107, .6);
  box-shadow: 0 0 10px rgba(255, 215, 107, .45), inset 0 1px 0 rgba(255, 255, 255, .18);
}

.last-hand-btn:active {
  transform: translateY(2px);
}

.control-btn {
  position: relative;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  height: 42px;
  padding: 0 6px;
  border-radius: 999px;
  font-weight: 900;
  font-size: 14px;
  text-shadow: 0 2px 2px rgba(0, 0, 0, .62);
  border: 1px solid rgba(255, 255, 255, .25);
  transform: translateY(0);
  transition: transform 130ms cubic-bezier(.2, .9, .24, 1), box-shadow 130ms ease, filter 130ms ease;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .42),
    inset 0 -4px 0 rgba(0, 0, 0, .22),
    0 6px 0 rgba(0, 0, 0, .32),
    0 11px 18px rgba(0, 0, 0, .36);
}

.control-btn::before {
  content: "";
  position: absolute;
  left: 14%;
  right: 14%;
  top: 5px;
  height: 12px;
  border-radius: 999px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, 0));
}

.control-btn:not(:disabled):active,
.control-btn.pressed:not(:disabled) {
  transform: translateY(4px) scale(.985);
  filter: brightness(.96) saturate(1.08);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .3),
    inset 0 -1px 0 rgba(0, 0, 0, .28),
    0 2px 0 rgba(0, 0, 0, .32),
    0 5px 10px rgba(0, 0, 0, .28);
}

.control-btn:disabled {
  cursor: not-allowed;
  opacity: .62;
  filter: grayscale(.24) brightness(.88);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .18),
    inset 0 -2px 0 rgba(0, 0, 0, .18),
    0 3px 8px rgba(0, 0, 0, .18);
}

.control-btn.active {
  outline: 3px solid rgba(255, 227, 124, .88);
}

.control-btn:focus-visible {
  outline: 3px solid rgba(255, 255, 255, .72);
  outline-offset: 2px;
}

.control-btn.blue {
  background: linear-gradient(#4fe9ff, #0573cc);
}

.control-btn.gold {
  color: #fff;
  background: linear-gradient(#ffe486, #ffb91e);
}

.control-btn.red {
  background: linear-gradient(#ff7a86, #bd1838);
}

.control-btn.dark {
  background: linear-gradient(#5a5870, #1b1b2f);
}

.control-btn.violet {
  background: linear-gradient(#d96cff, #7415ad);
}

.control-btn.green {
  background: linear-gradient(#31ff49, #08bd2b 55%, #04791f);
}

.control-btn.wide {
  grid-column: 2 / 4;
}

/* Floats centered above "我" own seat frame now (see .seat-side-actions.top,
   which also re-applies this same height/min-width/font-size at higher
   specificity so it doesn't get shrunk down to the narrow left/right
   side-stack button size). */
.allin-center {
  height: 52px;
  min-width: 128px;
  font-size: 18px;
  box-shadow:
    inset 0 3px 0 rgba(255, 255, 255, .42),
    inset 0 -6px 0 rgba(0, 0, 0, .24),
    0 8px 0 rgba(0, 0, 0, .34),
    0 14px 22px rgba(0, 0, 0, .38);
}

/* allinGoldGlow used to run here: a 2.5s infinite box-shadow breathing
   glow, animating multiple blur-radius layers -- box-shadow blur can't be
   handed to the compositor like transform/opacity, so this repainted the
   whole button every frame for as long as all-in stayed legal (i.e. most
   of the time it's your turn). Dropped outright rather than simplified --
   in practice the glow sits behind the button's own opaque fill and
   whatever else is layered around it, so it wasn't visibly reading as a
   glow anyway. */

.allin-center:not(:disabled):active,
.allin-center.pressed:not(:disabled) {
  transform: translateY(5px) scale(.985);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .24),
    inset 0 -1px 0 rgba(0, 0, 0, .3),
    0 3px 0 rgba(0, 0, 0, .34),
    0 6px 12px rgba(0, 0, 0, .3);
}

/* Texas only shows 3 real actions (fold / call-or-check / raise -- see
   renderTexas()'s controlsMarkup comment on why all-in dropped its own
   dedicated button), outside "me"'s frame instead of over it: fold to the
   left, call/check to the right, raise centered above. Positions are all
   position:fixed (like .seat.me itself) rather than nested inside
   .poker-table, so they stay put regardless of the table's own
   height/scroll. */
.table-screen.texas-screen .texas-round-controls {
  position: fixed;
  inset: 0;
  z-index: 41;
  pointer-events: none;
}

.table-screen.texas-screen .texas-round-btn {
  position: fixed;
  pointer-events: auto;
  width: 52px;
  min-width: 52px;
  height: 52px;
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  color: #fff;
  font-weight: 900;
  border: 0;
  background: transparent;
  overflow: visible;
  transition: transform 130ms cubic-bezier(.2, .9, .24, 1), filter 130ms ease;
  box-shadow: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .55);
}

/* Native inline SVG keeps the outer ring, circular face, and transparent
   symbol cutout in one vector coordinate space. Unlike a CSS mask image,
   it is never rasterized at one size and rescaled to another. */
.table-screen.texas-screen .texas-round-visual {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 52px;
  height: 52px;
  overflow: visible;
  pointer-events: none;
  shape-rendering: geometricPrecision;
}

.table-screen.texas-screen .texas-round-ring {
  fill: none;
  stroke-width: 3;
}

.table-screen.texas-screen .texas-round-ring-outline {
  fill: none;
  stroke: rgba(0, 0, 0, .68);
  stroke-width: 3.6;
}

.table-screen.texas-screen .texas-round-btn:disabled {
  opacity: .5;
  filter: grayscale(.24) brightness(.88);
}

/* Avoided transform:scale for the pressed look -- fold/call already use
   transform to offset themselves from center (see .fold/.call below), and a
   plain "transform: scale(...)" here would clobber that positioning
   transform instead of combining with it. */
.table-screen.texas-screen .texas-round-btn.pressed {
  filter: brightness(.96) saturate(1.08);
  box-shadow: none;
}

.table-screen.texas-screen .texas-round-text {
  position: absolute;
  left: 50%;
  bottom: -7px;
  z-index: 4;
  transform: translateX(-50%);
  font-size: 11px;
  line-height: 1;
  white-space: nowrap;
  -webkit-text-stroke: .35px rgba(0, 0, 0, .86);
  paint-order: stroke fill;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .9);
}

.table-screen.texas-screen .texas-round-btn.fold {
  left: max(32px, calc(50% - 96px));
  bottom: 124px;
  transform: translateX(-50%);
}

.table-screen.texas-screen .texas-round-btn.call {
  left: min(calc(100% - 32px), calc(50% + 96px));
  bottom: 124px;
  transform: translateX(-50%);
}

/* Before it is my turn, pre-actions deliberately look like the live fold
   and check buttons. Selecting one adds a rotating ring; its negative
   animation delay comes from queuedAt so polling re-renders do not make the
   ring visibly restart every half-second. */
.table-screen.texas-screen .texas-round-btn.texas-auto-action {
  isolation: isolate;
}

.table-screen.texas-screen .texas-round-btn.texas-auto-action .texas-round-text.amount {
  font-size: 9px;
  letter-spacing: -.35px;
}

.table-screen.texas-screen .texas-round-btn.texas-auto-action.queued {
  filter: brightness(1.08) saturate(1.04);
}

.table-screen.texas-screen .texas-round-btn.texas-auto-action.queued::after {
  content: "";
  position: absolute;
  inset: -7px;
  z-index: 3;
  border: 3px solid rgba(255, 255, 255, .2);
  border-top-color: #fff4a4;
  border-right-color: #74f4dd;
  border-radius: 50%;
  pointer-events: none;
  box-shadow: 0 0 10px rgba(84, 240, 214, .34);
  animation: texasAutoActionSpin 1600ms linear infinite;
  animation-delay: var(--texas-auto-spin-delay, 0ms);
}

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

.table-screen.texas-screen .texas-round-raise-wrap .texas-quickraise-row {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 12px);
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(4, 46px);
  gap: 5px;
  transform: translateX(-50%);
  pointer-events: auto;
}

.table-screen.texas-screen .texas-quickraise {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 46px;
  min-width: 0;
  padding: 0;
  color: #dffdf7;
  background: transparent;
  border: 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .72);
}

.table-screen.texas-screen .texas-quickraise-disc {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border: 2px solid rgba(152, 255, 238, .68);
  border-radius: 50%;
  background: radial-gradient(circle at center, #43c9b9 0%, #31aa9d 58%, #1c7d73 100%);
  box-shadow: 0 2px 5px rgba(0, 0, 0, .24);
  transition: transform 120ms ease, filter 120ms ease;
}

.table-screen.texas-screen .texas-quickraise-disc strong {
  font-size: 13px;
  font-weight: 900;
  line-height: 1;
}

.table-screen.texas-screen .texas-quickraise-disc span {
  margin-top: 2px;
  font-size: 9px;
  font-weight: 800;
  line-height: 1;
}

.table-screen.texas-screen .texas-quickraise small {
  margin-top: 3px;
  color: rgba(230, 255, 249, .92);
  font-size: 9px;
  font-weight: 900;
  line-height: 1;
  white-space: nowrap;
}

.table-screen.texas-screen .texas-quickraise.allin .texas-quickraise-disc {
  border-color: rgba(218, 255, 165, .8);
  background: radial-gradient(circle at center, #98e574 0%, #75c661 58%, #438b48 100%);
}

.table-screen.texas-screen .texas-quickraise.pressed .texas-quickraise-disc {
  transform: translateY(3px) scale(.95);
  filter: brightness(.95) saturate(1.08);
}

.table-screen.texas-screen .texas-round-raise-wrap {
  position: fixed;
  left: 50%;
  bottom: 198px;
  transform: translateX(-50%);
  pointer-events: auto;
}

.table-screen.texas-screen .texas-round-raise-wrap .texas-round-btn.raise {
  position: static;
  transform: none;
}

.table-screen.texas-screen .texas-round-btn.active {
  outline: none;
}

.table-screen.texas-screen .texas-round-btn.fold.pressed {
  transform: translateX(-50%) translateY(4px);
}

.table-screen.texas-screen .texas-round-btn.call.pressed {
  transform: translateX(-50%) translateY(4px);
}

.table-screen.texas-screen .texas-round-raise-wrap .texas-round-btn.raise.pressed {
  transform: translateY(4px);
}

/* Empty row -- 弃牌 used to sit here (top row of the left stack) before it
   moved up to share 全下's row instead (see topActionsMarkup in
   renderRoom()); holding this row's height keeps 跟注 below it lined up
   with 比牌's row on the right instead of riding up to fill the gap. */
.seat-side-spacer {
  height: 34px;
}

/* Two independent click zones sharing one pill: the circle (see .follow-
   toggle input below) toggles 跟到底 auto-follow mode, everything else
   (see .follow-toggle-call) fires a single 跟 immediately, same action as
   the 跟注 button -- see the followToggle branch and the data-jinhua=call
   button below for how each half is wired up separately. */
/* Back to one shared pill, same colors/style as the original merged look
   -- the only thing "splitting" the circle from 跟注 is a single thin
   divider line (see .follow-toggle-call's own ::before below), not two
   differently-styled buttons. */
.follow-toggle {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  /* No gap and no padding here on purpose -- anything left unclaimed by
     the two real children (input / .follow-toggle-call) falls through to
     this wrapper div on click, which the tap-routing logic below reads as
     "hit the circle" (see the followToggle branch in handleTapAction).
     That used to leave a dead strip right where a thumb naturally lands
     going for 跟注, silently toggling 跟到底 instead. The two children now
     tile the whole pill edge-to-edge and recreate the old inset/divider
     look with their own margin/padding instead. */
  height: 42px;
  border-radius: 999px;
  color: #fff;
  font-size: 14px;
  font-weight: 900;
  background: linear-gradient(#31ff49, #08bd2b 55%, #04791f);
  border: 1px solid rgba(255, 255, 255, .25);
  transform: translateY(0);
  transition: transform 130ms cubic-bezier(.2, .9, .24, 1), box-shadow 130ms ease, filter 130ms ease;
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .42),
    inset 0 -4px 0 rgba(0, 0, 0, .25),
    0 6px 0 rgba(0, 0, 0, .32),
    0 11px 18px rgba(0, 0, 0, .36);
  text-shadow: 0 2px 2px rgba(0, 0, 0, .62);
  user-select: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.follow-toggle::before {
  content: "";
  position: absolute;
  left: 14%;
  right: 14%;
  top: 5px;
  height: 12px;
  border-radius: 999px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, .5), rgba(255, 255, 255, 0));
}

.follow-toggle:not(.disabled):active {
  transform: translateY(4px) scale(.985);
  filter: brightness(.96) saturate(1.08);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .3),
    inset 0 -1px 0 rgba(0, 0, 0, .28),
    0 2px 0 rgba(0, 0, 0, .32),
    0 5px 10px rgba(0, 0, 0, .28);
}

.follow-toggle-circle {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  appearance: none;
  /* Real <button> now (was <input type="checkbox">) so it fires through
     the same tap-routing path as every other real button in this pill
     instead of relying on native checkbox semantics -- reset the button
     default padding/border/font here since a checkbox never had any. */
  padding: 0;
  font: inherit;
  /* Wrapper no longer has its own left padding (see .follow-toggle above),
     so this margin now carries the full inset by itself -- same 4px from
     the pill's edge as before (old 7px wrapper padding - 3px nudge). */
  margin-left: 4px;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  border: 2px solid rgba(255, 255, 255, .78);
  background: rgba(0, 0, 0, .38);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, .45);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.follow-toggle-circle.checked {
  background: radial-gradient(circle at center, #fff 0 38%, transparent 42%), rgba(0, 0, 0, .38);
}

/* Fills the rest of the pill next to the circle, same as before --
   transparent/borderless so it still reads as part of the same pill.
   Its box now starts flush against the input (no gap/margin left unowned
   in between -- see .follow-toggle's own comment above) and stretches all
   the way to the pill's true right edge, so every one of those pixels
   counts as a 跟注 tap. The divider line and the old visual insets are
   recreated with padding/a pseudo-element instead of margin/border, since
   a real border would render right at this box's new (flush) left edge. */
.follow-toggle-call {
  position: relative;
  z-index: 1;
  flex: 1;
  height: 100%;
  padding: 0 7px 0 14px;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 900;
  text-shadow: inherit;
  text-align: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.follow-toggle-call::before {
  content: "";
  position: absolute;
  left: 8px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: rgba(255, 255, 255, .38);
  pointer-events: none;
}

.follow-toggle-call:active,
.follow-toggle-call.pressed {
  filter: brightness(.9);
}

.follow-toggle-call:disabled {
  cursor: not-allowed;
}

.follow-toggle.disabled {
  cursor: not-allowed;
  opacity: .46;
  filter: grayscale(.45);
}

.follow-toggle.disabled .follow-toggle-circle,
.follow-toggle.disabled .follow-toggle-call {
  cursor: not-allowed;
}

.follow-toggle.disabled .follow-toggle-circle {
  border-color: rgba(255, 255, 255, .38);
  background: rgba(0, 0, 0, .28);
}

.hundred-table {
  position: relative;
  width: min(410px, calc(100vw - 8px));
  min-height: calc(100vh - 146px);
  margin: 8px auto 0;
  padding: 16px 8px 132px;
}

.hundred-table::after {
  /* Faint full-bleed border/inset-shadow frame over the whole table --
     barely visible on its own, but redundant now that .hundred-track-bg
     is hidden and a new background image is going in underneath. */
  display: none;
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border: 1px solid rgba(198, 255, 222, .28);
  border-radius: 0;
  box-shadow: inset 0 0 0 1px rgba(5, 34, 24, .12);
  pointer-events: none;
}

.hundred-table-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: transparent;
}

.hundred-jackpot {
  position: absolute;
  left: 14px;
  top: 46px;
  z-index: 6;
  min-width: 70px;
  padding: 6px 10px 8px;
  text-align: center;
  border-radius: 8px;
  color: #ffe37c;
  font-size: 10px;
  font-weight: 900;
  line-height: 1.6;
  font-family: inherit;
  cursor: pointer;
  background: linear-gradient(180deg, #8a5a2c, #5c3814);
  border: 2px solid #d9a538;
  box-shadow: 0 4px 10px rgba(0, 0, 0, .35);
}

.hundred-recharge-btn {
  position: absolute;
  /* Moved under .hundred-jackpot (same left edge, stacked below it) instead
     of the top-right corner -- keeps it well clear of the fixed menu
     button there without needing to chase that button's own position. */
  left: 14px;
  top: 104px;
  z-index: 6;
  padding: 6px 16px;
  border-radius: 999px;
  color: #06371c;
  font-size: 12px;
  font-weight: 900;
  background: linear-gradient(180deg, #6dff8a, #1fb24a);
  border: 1px solid rgba(255, 255, 255, .5);
  box-shadow: 0 4px 10px rgba(0, 0, 0, .3);
}

.hundred-mascot {
  position: relative;
  z-index: 2;
  /* Pulled up close to the table's own top edge -- the announcement bar
     that used to anchor this offset now lives at a fixed viewport position
     above the table entirely (see .game-announcement-anchor-hundred), so
     nothing inside .hundred-table needs to clear it anymore. Everything
     that flows from here (banker-row, track-row/seats/zones) shifts up
     along with it for free. */
  margin: 8px auto 0;
  width: 70px;
  height: 70px;
  display: grid;
  place-items: center;
  font-size: 44px;
  /* translateY, not margin -- margin here would cascade into .hundred-
     banker-row/.hundred-track-row below (normal flow) and drag the seats
     and zones down with it, which isn't what was asked. A transform moves
     this purely visually ("庄家头像 昵称 排队上庄 按钮和牌面往下整体平
     移"). */
  transform: translateY(26px);
}

.hundred-mascot.has-banker .avatar {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  font-size: 26px;
  border: 3px solid var(--gold);
}

.hundred-banker-row {
  position: relative;
  /* Above .hundred-seats (z-index 3) so the anchored queue panel nested
     inside this row never renders behind the seat/zone grid. */
  z-index: 10;
  width: min(336px, calc(100% - 16px));
  min-height: 26px;
  margin: 2px auto 0;
  /* Moves down together with .hundred-mascot (same value/reasoning). */
  transform: translateY(26px);
}

.hundred-banker-queue-panel {
  position: absolute;
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  width: min(280px, 82vw);
  max-height: min(360px, 60vh);
  overflow-y: auto;
  /* Override .daily-reward-modal's purple-blue skin (same specificity, but
     this rule comes later in the cascade) with a felt-and-gold treatment
     that matches the rest of the 百人场 table instead of clashing with it. */
  padding: 16px 14px;
  border: 2px solid var(--gold);
  border-radius: 16px;
  background:
    radial-gradient(circle at 50% 0%, rgba(255, 212, 91, .16), transparent 62%),
    linear-gradient(180deg, #0c6e49, #06432c 68%, #042318);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08), 0 18px 40px rgba(0, 0, 0, .55), 0 0 22px rgba(4, 122, 80, .5);
}


.hundred-chat-btn {
  position: absolute;
  left: 22px;
  /* Was 172px -- didn't line up with .hundred-players-btn's 154px. Matched
     to that button's value so the two sit level with each other ("聊天按钮
     和本局玩家按钮对齐"). */
  bottom: 154px;
  z-index: 25;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  font-size: 18px;
  background: rgba(0, 0, 0, .5);
  border: 1px solid rgba(255, 255, 255, .3);
}

.hundred-my-avatar-anchor {
  position: relative;
  flex: 0 0 auto;
}

.hundred-my-avatar {
  width: 40px;
  height: 40px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(0, 0, 0, .5);
  border: 2px solid var(--gold);
  flex: 0 0 auto;
}

.hundred-my-avatar .avatar {
  width: 100%;
  height: 100%;
}

/* Shared chat popover component (hundred + jinhua) -- see .chat-popover and
   friends further down for the actual rules; this file keeps one definition
   per room only for position overrides (button placement differs between
   the two screens) and the barrage effects below. */

.hundred-banker-info {
  position: absolute;
  left: 50%;
  top: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: min(176px, calc(100% - 152px));
  transform: translate(-50%, -50%);
}

.hundred-banker-actions {
  position: absolute;
  right: 26px;
  top: 50%;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  transform: translateY(-50%);
}

.hundred-banker-name {
  min-width: 0;
  max-width: 104px;
  overflow: hidden;
  color: #fff;
  font-size: 12px;
  font-weight: 900;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hundred-banker-amount {
  flex: 0 0 auto;
  overflow: hidden;
  color: #fff3a6;
  font-size: 12px;
  font-weight: 900;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hundred-banker-btn {
  flex: 0 0 auto;
  height: 26px;
  width: 72px;
  padding: 0;
  border-radius: 999px;
  color: #2d210b;
  font-size: 11px;
  font-weight: 900;
  /* Was a flat var(--gold) fill -- gave it the same gradient + inset-
     highlight depth as the rest of the room's "real button" family instead
     of a plain flat pill ("上庄排队的按钮好丑，画风要改"). */
  background: linear-gradient(180deg, #ffe29a, #cf973d);
  border: 1px solid rgba(255, 229, 153, .6);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .65), 0 3px 8px rgba(0, 0, 0, .32);
}

.hundred-banker-btn:disabled {
  opacity: .5;
  background: #8a8a8a;
  color: #fff;
}

.hundred-banker-queue-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 4px;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
  font-size: 12px;
  color: rgba(255, 255, 255, .82);
}

.hundred-banker-queue-row span:first-of-type {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hundred-banker-queue-avatar {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
}

.hundred-banker-queue-avatar .avatar {
  width: 100%;
  height: 100%;
  border-radius: 50%;
}

.hundred-banker-queue-row.is-me {
  color: #ffe37c;
  font-weight: 900;
}

.hundred-banker-queue-badge {
  flex: 0 0 auto;
  height: 22px;
  width: 68px;
  padding: 0;
  overflow: hidden;
  border-radius: 999px;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: rgba(255, 255, 255, .16);
  border: 1px solid rgba(255, 255, 255, .3);
}

.hundred-track-row {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  /* Pulls the whole row (zones + both seat columns move together, staying
     in sync since they share this one flex container) up closer to
     .hundred-banker-row above. Bounded by .hundred-shared-cards/
     .hundred-shared-hand inside .hundred-track, which move up by the same
     amount -- if this ever needs to go further negative, check those don't
     start overlapping the banker-row nameplate above first. */
  margin-top: -6px;
}

.hundred-seats {
  position: relative;
  z-index: 3;
  flex: 0 0 auto;
  width: 58px;
  height: 402px;
  padding-top: 0;
}

/* "八个座位还可以往中间移动一点点，贴近格子边缘一点，但是不要重叠" then
   "两边座位再贴近格子一点，感觉还不够" -- pulls each column in toward
   .hundred-track's grid (.hundred-track has 10px of its own left/right
   padding before the grid cells start; 7px still leaves ~3px of clearance
   for the seat circle itself, not a hard touch). Matched by a smaller trim
   on .hundred-seat-info::before's own overhang below, so the
   semi-transparent name/chip band -- which sticks out past the seat's own
   58px width -- still leaves ~1px clear of the grid edge after this shift
   ("玩家的不透明带不要盖格子边缘，避免打架"). */
.hundred-seats-left {
  margin-right: -7px;
}

.hundred-seats-right {
  margin-left: -7px;
}

.hundred-seat {
  position: absolute;
  left: 50%;
  width: 58px;
  min-height: 76px;
  transform: translateX(-50%);
  display: grid;
  place-items: center;
  gap: 2px;
  text-align: center;
}

/* Spacing tightened from the original 98px-even steps (66/164/262/360) to
   90px -- "四周的座位间距再拉近一点点，但不要打架". Each seat is 76px tall
   (min-height above), so 90px still leaves a 14px buffer between them. */
.hundred-seat:nth-child(1) {
  top: 66px;
}

.hundred-seat:nth-child(2) {
  top: 156px;
}

.hundred-seat:nth-child(3) {
  top: 246px;
}

.hundred-seat:nth-child(4) {
  top: 336px;
}

.hundred-seat-avatar {
  width: 50px;
  height: 50px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  color: #fff;
  font-size: 14px;
  font-weight: 900;
  background:
    radial-gradient(circle at 34% 28%, rgba(255, 255, 255, .38), transparent 23%),
    linear-gradient(145deg, #7df2e6, #d953c8 54%, #3e1c76);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  overflow: hidden;
  border: 2px solid rgba(255, 255, 255, .72);
  box-shadow: 0 4px 10px rgba(0, 0, 0, .34);
}

.hundred-seat-avatar.has-image span {
  display: none;
}

.hundred-seat.me .hundred-seat-avatar {
  border-color: var(--gold);
}

.hundred-seat-info {
  position: relative;
  z-index: 1;
  margin-top: -14px;
  display: grid;
  gap: 1px;
  padding: 2px 4px 1px;
}

.hundred-seat-info::before {
  content: "";
  position: absolute;
  /* Trimmed from -8px to -2px on the left/right sides to offset the 7px
     inward shift on .hundred-seats-left/-right above -- leaves this band's
     outer edge ~1px clear of the grid instead of reaching or overlapping it
     ("玩家的不透明带不要盖格子边缘，避免打架"). */
  inset: -1px -2px;
  z-index: -1;
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, .48) 24%, rgba(0, 0, 0, .48) 76%, transparent);
}

.hundred-seat-name {
  max-width: 58px;
  overflow: hidden;
  color: rgba(255, 255, 255, .82);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.05;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .58);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hundred-seat-chip {
  color: rgba(255, 243, 166, .82);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.05;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .58);
}

.hundred-seat.empty .hundred-seat-avatar {
  width: 58px;
  height: 58px;
  position: relative;
  background:
    radial-gradient(circle at 38% 30%, rgba(255, 255, 255, .5), transparent 24%),
    rgba(255, 255, 255, .28);
  border: 0;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .28),
    inset 0 0 18px rgba(255, 255, 255, .12),
    0 5px 12px rgba(0, 0, 0, .24);
}

.hundred-seat-plus {
  position: relative;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: rgba(247, 255, 249, .5);
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, .46),
    0 4px 10px rgba(0, 0, 0, .32);
  color: transparent;
  font-size: 0;
  line-height: 0;
}

.hundred-seat-plus::before,
.hundred-seat-plus::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 22px;
  height: 3px;
  border-radius: 999px;
  background: #2a8f6b;
  box-shadow: none;
  transform: translate(-50%, -50%);
}

.hundred-seat-plus::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

.hundred-seat {
  cursor: pointer;
}

@keyframes hundredSeatBounce {
  0% { transform: translateY(0); }
  30% { transform: translateY(-8px); }
  55% { transform: translateY(0); }
  75% { transform: translateY(-4px); }
  100% { transform: translateY(0); }
}

.hundred-seat-avatar.betting-bounce {
  animation: hundredSeatBounce 420ms ease-out;
}

@keyframes settlementBubbleFloat {
  /* Holds fully visible for the first 25% (1000ms of the 4000ms total,
     "百人场的输赢气泡再多停留一秒再淡出") before floating up and fading out
     over the remaining 3000ms -- same float/fade motion and duration as
     before, just delayed behind the added hold. */
  0%, 25% { transform: translate(-50%, 0); opacity: 1; }
  100% { transform: translate(-50%, -24px); opacity: 0; }
}

.hundred-settlement-bubble {
  position: absolute;
  left: 50%;
  top: -20px;
  z-index: 3;
  padding: 2px 8px;
  border-radius: 999px;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 900;
  pointer-events: none;
  /* animation-delay is set inline per-frame from elapsed time (see
     hundredSettlementBubbleMarkup) so the float-up-and-fade keeps correct
     progress across full re-renders instead of restarting each time. */
  animation: settlementBubbleFloat 4000ms ease-out both;
}

.hundred-settlement-bubble.win {
  color: #3a2a00;
  background: linear-gradient(#ffe37c, #d9a52a);
  box-shadow: 0 0 10px rgba(255, 212, 91, .55), 0 3px 6px rgba(0, 0, 0, .3);
}

.hundred-settlement-bubble.lose {
  color: #fff;
  background: linear-gradient(#9099a6, #5b6270);
  box-shadow: 0 3px 6px rgba(0, 0, 0, .3);
}

@keyframes bankerSettlementBubbleFloat {
  from { transform: translate(0, -50%); opacity: 1; }
  to { transform: translate(0, calc(-50% - 24px)); opacity: 0; }
}

.hundred-mascot > .hundred-settlement-bubble {
  left: calc(100% + 6px);
  top: 50%;
  animation-name: bankerSettlementBubbleFloat;
}

/* Seats sit only ~22px apart (see .hundred-seat:nth-child positions above),
   so the shared -20px bubble offset reaches into the seat above instead of
   landing on this seat's own avatar. Pull it down onto the avatar itself. */
.hundred-seat .hundred-settlement-bubble {
  top: 6px;
}

.hundred-track {
  position: relative;
  flex: 1;
  min-width: 0;
  /* Top padding trimmed so the shared/banker cards sit closer to the
     banker nameplate above (.hundred-banker-row) now that the whole
     banker area has shifted up -- see .hundred-mascot's own reduced
     margin-top for the matching change one level up. */
  padding: 8px 10px 28px;
  isolation: isolate;
}

.hundred-track-bg {
  /* Temporarily hidden -- a new table background image is being swapped
     in and this oval felt graphic (green arc border + green gradient
     fill, plus its own ::before layer) just doubled up under it. */
  display: none;
  position: absolute;
  left: 50%;
  top: 49%;
  z-index: 0;
  transform: translate(-50%, -50%);
  width: calc(100% + 74px);
  height: calc(100% + 108px);
  border-radius: 52% 52% 44% 44% / 63% 63% 23% 23%;
  border: 2px solid rgba(236, 222, 100, .42);
  background:
    radial-gradient(ellipse 104% 72% at 50% 82%, rgba(30, 150, 96, .5), rgba(10, 86, 65, .18) 60%, transparent 78%),
    radial-gradient(ellipse 92% 18% at 50% 8%, rgba(255, 232, 104, .34), transparent 70%),
    rgba(10, 92, 67, .26);
  box-shadow:
    0 10px 18px rgba(0, 0, 0, .12),
    inset 0 -10px 18px rgba(0, 0, 0, .12),
    inset 0 16px 26px rgba(224, 255, 214, .08),
    inset 0 0 0 1px rgba(255, 255, 190, .18);
  pointer-events: none;
}

.hundred-track-bg::before,
.hundred-track-bg::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.hundred-track-bg::before {
  inset: 7px 12px 13px;
  border-radius: 52% 52% 43% 43% / 62% 62% 22% 22%;
  background:
    radial-gradient(ellipse 82% 34% at 50% 18%, rgba(145, 226, 165, .24), transparent 62%),
    radial-gradient(ellipse 92% 64% at 50% 78%, rgba(28, 148, 94, .56), rgba(11, 106, 76, .42) 52%, rgba(7, 80, 61, .34) 100%),
    rgba(14, 116, 78, .44);
  box-shadow:
    inset 0 0 12px rgba(255, 255, 255, .04),
    inset 0 -12px 20px rgba(0, 0, 0, .1);
}

.hundred-track-bg::after {
  display: none;
}

.hundred-shared-cards {
  position: relative;
  z-index: 18;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 38px;
  min-height: 38px;
  overflow: visible;
  /* Moves down together with .hundred-mascot/.hundred-banker-row (same
     value/reasoning -- "牌面往下整体平移"). */
  transform: translateY(26px);
}

.hundred-shared-cards::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 0;
  width: min(156px, 86%);
  height: 36px;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, .34) 18%, rgba(255, 255, 255, .42) 50%, rgba(255, 255, 255, .34) 82%, transparent 100%);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.hundred-shared-cards .card {
  position: relative;
  z-index: 1;
  width: 26px;
  height: 36px;
  font-size: 15px;
}

.hundred-screen .hundred-shared-cards .card:not(.card-back),
.hundred-screen .hundred-zone-cards .card:not(.card-back) {
  color: #101820;
  background: linear-gradient(180deg, #ffffff 0%, #fffdf3 100%);
  border-color: rgba(255, 255, 255, .96);
  box-shadow:
    0 2px 6px rgba(0, 0, 0, .24),
    inset 0 0 0 1px rgba(255, 255, 255, .72);
  filter: brightness(1.08) contrast(1.08) saturate(1.08);
}

.hundred-screen .hundred-shared-cards .card.warm:not(.card-back),
.hundred-screen .hundred-zone-cards .card.warm:not(.card-back) {
  color: #c21824;
}

/* Card-back (face-down) texture, hundred-only. The shared .card-back rule
   (jinhua/texas keep using it as-is, see .table-screen:not(.hundred-screen)
   .card-back's own full background override) draws its pattern from FOUR
   stacked gradients, one of them a repeating-radial-gradient tiling a dot
   pattern across the whole card -- repeating gradients are one of the
   pricier background types to compute, and dealing can put a dozen-plus
   card-backs on screen at once, all freshly created by the same
   poll-driven rebuild. Swapped for one cached bitmap that already bakes in
   the dot texture and the corner highlights. */
.hundred-screen .hundred-shared-cards .card-back,
.hundred-screen .hundred-zone-cards .card-back {
  background: url("./assets/hundred-card-back.png") center / cover;
}

/* .card-back::before drew the inner frame's border + a blurred inset
   glow via its own box-shadow -- also now baked into the bitmap above, so
   the pseudo-element itself is just dead weight here. */
.hundred-screen .hundred-shared-cards .card-back::before,
.hundred-screen .hundred-zone-cards .card-back::before {
  content: none;
}

.hundred-card-deal {
  transform-origin: 50% 70%;
  animation: hundredDealCard 860ms cubic-bezier(.18, .78, .18, 1) both;
  animation-delay: var(--deal-base-delay, 0ms);
}

.hundred-card-deal:nth-child(2) {
  animation-delay: calc(var(--deal-base-delay, 0ms) + 90ms);
}

.hundred-card-deal:nth-child(3) {
  animation-delay: calc(var(--deal-base-delay, 0ms) + 180ms);
}

.hundred-card-reveal {
  animation: hundredRevealCard 440ms cubic-bezier(.2, .85, .24, 1) both;
}

/* A single continuous ease from the dealt-from position to rest -- the
   cubic-bezier below already gives it a fast-start/gentle-settle feel on
   its own. There used to be an extra keyframe near the end that nearly
   reached the resting spot, held there a moment, then took a separate small
   hop (with its own scale/rotate bump) the rest of the way -- from the
   player's side that read as the card touching down twice instead of one
   clean landing. */
@keyframes hundredDealCard {
  0% {
    opacity: 0;
    transform: translate(var(--deal-x, 0), var(--deal-y, -120px)) rotate(var(--deal-rot, -8deg)) scale(.66);
  }

  24% {
    opacity: 1;
  }

  100% {
    opacity: 1;
    transform: translate(0, 0) rotate(0) scale(1);
  }
}

@keyframes hundredRevealCard {
  0% {
    opacity: .55;
    transform: rotateY(78deg) scale(.96);
  }

  100% {
    opacity: 1;
    transform: rotateY(0) scale(1);
  }
}

.hundred-shared-hand {
  /* Back to stacked below the cards (not beside them) -- "庄家的牌型显示
     还是放在牌面下方，贴近牌面". Normal flow again, not absolute, so it
     naturally follows right after .hundred-shared-cards; margin-top pulled
     down from the original 2px to hug the card row closer. Same
     translateY(26px) as .hundred-shared-cards so it stays visually in step
     with that row's own down-shift (transform doesn't affect a sibling's
     flow position, so without this it would render from the cards'
     UNtransformed spot instead of the actual visible row). */
  position: relative;
  z-index: 19;
  margin-top: -2px;
  transform: translateY(26px);
  box-sizing: border-box;
  /* Fixed height, not just min-height left to grow -- this element is empty
     most of the time and only gets real content (padding included, via the
     :not(:empty) rule below) right at 开牌结算/reveal, which is exactly
     when "押注的四个格子在开牌的时候会上下位移跳动" happened: without a
     capped height, gaining that padding grew this box by ~24.5px the
     instant the banker's hand-type text appeared, pushing .hundred-zones
     (and everything below it) down for the rest of that phase. */
  height: 24px;
  min-height: 24px;
  text-align: center;
  color: #ffe77a;
  font-size: 13px;
  font-weight: 900;
  letter-spacing: .16em;
  line-height: 1.35;
  text-shadow:
    0 1px 0 rgba(0, 0, 0, .95),
    0 0 6px rgba(0, 0, 0, .72),
    0 0 12px rgba(255, 215, 91, .72);
}

.hundred-shared-hand:not(:empty) {
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
  padding: 2px 12px 3px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .62);
  border: 1px solid rgba(255, 224, 91, .58);
  box-shadow:
    0 2px 8px rgba(0, 0, 0, .36),
    0 0 12px rgba(255, 209, 75, .22);
}

.hundred-zones {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Column-gap trimmed from 10px to 6px -- "左右两边的格子中间还有点距离，
     把东和南靠近一点，西和北再靠近一点" (HUNDRED_ZONES is row-major
     [east, south, west, north], so this one column-gap is exactly the
     east/south and west/north horizontal spacing, both rows at once). */
  gap: 12px 6px;
  width: 100%;
  /* Was 16px, then 40px, then 52px (too far -- "四个格子太下了"). Pulled
     back up to leave just an 8px gap under .hundred-shared-hand (measured
     live), close but not touching ("贴近牌型显示，但不要重叠"). */
  margin-top: 34px;
}

.hundred-phase-text {
  position: relative;
  z-index: 2;
  width: fit-content;
  margin: 22px auto 0;
  padding: 6px 20px;
  border-radius: 10px;
  border: 2px solid rgba(255, 224, 91, .7);
  text-align: center;
  color: var(--gold);
  font-size: 15px;
  font-weight: 900;
  white-space: nowrap;
  background: rgba(0, 0, 0, .55);
}

.hundred-players-btn {
  position: absolute;
  right: 22px;
  bottom: 154px;
  z-index: 25;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  font-size: 20px;
  background: rgba(0, 0, 0, .5);
  border: 2px solid rgba(255, 255, 255, .35);
}

.hundred-players-popover {
  position: fixed;
  right: max(22px, calc((100vw - 430px) / 2 + 22px));
  /* Measured directly via getBoundingClientRect at 390x844 (same method as
     .chat-popover.hundred-chat-popover's own comment): .hundred-players-btn
     renders at top:630/bottom:674, but a previous pass here (147px) put
     this popover's bottom edge at rect.bottom:697 -- *below* the button's
     own top edge, overlapping down onto it instead of sitting above it
     ("本局玩家的界面...本局玩家按钮的左上相对位置"). 222px puts the
     popover's bottom edge ~8px above the button's top. */
  bottom: 222px;
  /* Was 32 -- #overlay-root (where this renders) and #animation-root (the
     hundred chip-flight layer, .game-animation-layer, effectively z-index
     75-76 at the root -- see that class's own comment) are both plain
     siblings of <body> with no stacking context of their own, so their
     children's z-indexes compete directly against each other. 32 lost to
     that during the betting phase, so flying bet chips visibly passed in
     front of this open popover ("本局玩家的界面会闪...别让筹码在界面里
     飞"). Same fix .chat-popover already uses (z-index:80) -- 82 clears it
     too. */
  z-index: 82;
  width: min(270px, calc(100vw - 28px));
  max-height: 340px;
  padding: 12px 12px 10px;
  border-radius: 14px;
  border: 1px solid rgba(255, 255, 255, .28);
  background:
    linear-gradient(180deg, rgba(20, 44, 58, .94), rgba(9, 23, 31, .96)),
    rgba(0, 0, 0, .9);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .46), inset 0 1px 0 rgba(255, 255, 255, .14);
  overflow: hidden;
}

.hundred-players-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}

.hundred-players-tab {
  flex: 1;
  padding: 6px 0;
  border-radius: 8px;
  color: rgba(255, 255, 255, .7);
  font-size: 12px;
  font-weight: 700;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .16);
}

.hundred-players-tab.active {
  color: #08353d;
  background: linear-gradient(#d8ffff, #42cbd5);
  border-color: rgba(255, 255, 255, .75);
}

.hundred-players-popover header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}

.hundred-players-popover h2 {
  margin: 0;
  color: #ffe37c;
  font-size: 15px;
  font-weight: 900;
}

.hundred-players-popover header button {
  width: 28px;
  height: 28px;
  border-radius: 999px;
  color: #fff;
  font-size: 18px;
  line-height: 1;
  background: rgba(0, 0, 0, .38);
  border: 1px solid rgba(255, 255, 255, .2);
}

.hundred-players-popover-body {
  max-height: 270px;
  overflow-y: auto;
}

.chat-popover {
  --chat-accent: #83e5ff;
  --chat-accent-soft: rgba(91, 194, 255, .22);
  --chat-accent-faint: rgba(91, 194, 255, .1);
  --chat-panel-top: rgba(34, 51, 124, .98);
  --chat-panel-bottom: rgba(8, 14, 58, .985);
  --chat-border: rgba(131, 229, 255, .48);
  position: fixed;
  /* Was 32 -- #overlay-root (where this renders) and #animation-root (the
     flying-chip layer, .game-animation-layer, z-index:75/76 for the VIP
     all-in phone moment) are both plain siblings of <body> with no
     stacking context of their own, so their children's z-indexes compete
     directly against each other, not scoped inside #app the way the
     table's own internal 20-26 range is. 32 lost to that every time,
     so chips visibly flew across an open chat panel. 80 clears both. */
  z-index: 80;
  width: min(276px, calc(100vw - 28px));
  padding: 13px 13px 12px;
  overflow: hidden;
  isolation: isolate;
  border: 1px solid var(--chat-border);
  border-radius: 18px;
  background:
    radial-gradient(ellipse 72% 42% at 50% -8%, var(--chat-accent-soft), transparent 70%),
    linear-gradient(158deg, var(--chat-panel-top), var(--chat-panel-bottom));
  box-shadow:
    0 16px 35px rgba(0, 2, 28, .56),
    0 0 0 1px rgba(255, 255, 255, .06) inset,
    0 0 22px var(--chat-accent-faint);
  /* backdrop-filter dropped -- this popover gets fully torn down and
     rebuilt on every new message (renderOverlay's signature includes
     chat.length), which is exactly when a barrage line is also spawning --
     recreating a live blur-behind node produced a visible flash right at
     that moment ("弹幕出现时，有时候聊天界面会闪"), on top of being one of
     the more GPU/battery-expensive CSS effects on mobile every time it
     reruns ("手机发烫"). The --chat-panel-top/bottom gradient stops are
     already .98/.99 alpha, so the visual loss is imperceptible. */
}

.chat-popover::before {
  position: absolute;
  z-index: -1;
  inset: 1px;
  content: "";
  border-radius: 17px;
  opacity: .8;
  pointer-events: none;
  background:
    linear-gradient(110deg, rgba(255, 255, 255, .14), transparent 24%, transparent 76%, rgba(255, 255, 255, .05)),
    repeating-linear-gradient(135deg, rgba(255, 255, 255, .018) 0 1px, transparent 1px 5px);
}

.chat-popover > * {
  position: relative;
  z-index: 1;
}

/* Was its own cyan palette -- per "百人场的聊天界面画风改成与炸金花的聊天
   界面画风一致" this now matches .chat-popover.jinhua-chat-popover's warm
   palette exactly, so all three rooms' chat panels read as one component
   reskinned the same way instead of hundred looking like a different design. */
.chat-popover.hundred-chat-popover {
  --chat-accent: #ffd08d;
  --chat-accent-soft: rgba(255, 164, 113, .25);
  --chat-accent-faint: rgba(217, 96, 151, .22);
  --chat-panel-top: rgba(91, 54, 91, .98);
  --chat-panel-bottom: rgba(35, 26, 54, .99);
  --chat-border: rgba(255, 190, 130, .5);
}

.chat-popover.jinhua-chat-popover {
  --chat-accent: #ffd08d;
  --chat-accent-soft: rgba(255, 164, 113, .25);
  --chat-accent-faint: rgba(217, 96, 151, .22);
  --chat-panel-top: rgba(91, 54, 91, .98);
  --chat-panel-bottom: rgba(35, 26, 54, .99);
  --chat-border: rgba(255, 190, 130, .5);
}

/* Only applied on the render that actually opens the panel (see
   state.jinhuaChatJustOpened/hundredChatJustOpened) -- switching tabs while
   already open re-signatures renderOverlay() too, which fully replaces this
   node, and playing the pop-in every time that happened looked like a
   flash/flicker instead of a single clean open. */
.chat-popover.opening {
  animation: settingsPop .14s ease-out both;
}

/* The 💬 button sits bottom-left on the hundred screen (see .hundred-chat-btn)
   but the jinhua "聊" button lives in .side-tools, also bottom-left but at a
   different height -- each room anchors its own popover above its trigger. */
.chat-popover.hundred-chat-popover {
  left: max(22px, calc((100vw - 430px) / 2 + 22px));
  /* Measured directly via getBoundingClientRect at 390x844 rather than
     guessed again: .hundred-chat-btn's rendered rect is top:616/bottom:656,
     but this popover is position:fixed (viewport-relative) while the button
     is position:absolute inside .hundred-screen (not viewport-relative), so
     their "bottom" CSS values don't correspond 1:1 -- a previous pass here
     (164px) was tuned from a stale/wrong measurement and actually put this
     popover's bottom edge at rect.bottom:680, *below* the button's own top
     edge (616) -- i.e. overlapping down onto the button itself instead of
     sitting above it ("界面不要离按钮太远，放在聊天按钮的右上相对位置").
     236px puts the popover's bottom edge ~8px above the button's top. */
  bottom: 236px;
}

/* Opens up-and-to-the-right of .side-tools' own button instead of
   overlapping it ("聊天界面不要挡住聊天按钮了，界面应该在按钮的右上侧不应该
   与按钮本身打架") -- anchored off the button's own live rect (measured at
   390x844: x:55-93/top:559, after the button's own top:503px table-relative
   value above) rather than the table's edges, since the button itself moved
   to sit directly above 弃牌 earlier this session and the old right-anchored
   rule (tuned for its previous top-left-corner spot) no longer tracked it.
   left:103px is the button's own right edge (93) plus a 10px gap; the
   calc((100vw-430px)/2+...) half mirrors how .poker-table itself centers
   past the 430px column cap (see the button's own .side-tools rule) so this
   stays correctly aligned on wider screens too, and the outer min(...)
   keeps the whole 276px-wide panel from running past the viewport's right
   edge on narrower ones. bottom:295px anchors the panel's bottom edge
   ~10px above the button's own top edge (559 at a 844-tall viewport ->
   844-559+10) -- since height only grows upward from a fixed bottom line,
   this can never overlap the button below it regardless of how tall the
   panel's actual content makes it. */
.chat-popover.jinhua-chat-popover {
  left: min(max(103px, calc((100vw - 430px) / 2 + 103px)), calc(100vw - 284px));
  right: auto;
  bottom: 295px;
}

/* Texas: same "up-and-to-the-right of its own button, never overlapping
   it" placement as jinhua just above, computed the same way off this
   button's own live rect (measured at 390x844: x:33-71/top:612, up-left of
   the round 弃 button -- see .table-screen.texas-screen .side-tools).
   left:81px is the button's right edge (71) plus a 10px gap; bottom:242px
   is 10px above the button's own top edge (844-612+10). */
.chat-popover.texas-chat-popover {
  left: min(max(81px, calc((100vw - 430px) / 2 + 81px)), calc(100vw - 284px));
  right: auto;
  bottom: 242px;
}

/* Texas chat used to inherit .hundred-chat-popover's cyan palette by
   mistake (a leftover copy-paste in renderTexasChatPanel) -- gave it a
   visibly different look from jinhua's own chat panel ("德州的聊天界面画风
   与炸金花的聊天界面画风不一致"). Same warm palette as
   .chat-popover.jinhua-chat-popover now, so both card-game rooms' chat
   panels read as the same component reskinned per-room like hundred's does,
   not as two unrelated designs. */
.chat-popover.texas-chat-popover {
  --chat-accent: #ffd08d;
  --chat-accent-soft: rgba(255, 164, 113, .25);
  --chat-accent-faint: rgba(217, 96, 151, .22);
  --chat-panel-top: rgba(91, 54, 91, .98);
  --chat-panel-bottom: rgba(35, 26, 54, .99);
  --chat-border: rgba(255, 190, 130, .5);
}

.chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 29px;
  margin-bottom: 10px;
  padding: 0 1px 9px;
  border-bottom: 1px solid var(--chat-accent-soft);
}

.chat-header h2 {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  color: var(--chat-accent);
  font-size: 14.5px;
  font-weight: 900;
  letter-spacing: .8px;
  text-shadow: 0 1px 8px var(--chat-accent-soft);
}

.chat-header h2::before {
  width: 5px;
  height: 15px;
  content: "";
  border-radius: 999px;
  background: linear-gradient(180deg, #fff8d9, var(--chat-accent));
  box-shadow: 0 0 9px var(--chat-accent);
}

.chat-header button {
  width: 26px;
  height: 26px;
  border-radius: 999px;
  color: var(--chat-accent);
  font-size: 16px;
  line-height: 1;
  background: linear-gradient(145deg, rgba(255, 255, 255, .15), var(--chat-accent-faint));
  border: 1px solid var(--chat-accent-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

.chat-header button:active {
  transform: scale(.92);
  background: var(--chat-accent-soft);
}

.chat-messages {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 170px;
  margin-bottom: 11px;
  padding: 2px 3px 2px 1px;
  overflow-y: auto;
  /* Scrolling still works, just no visible track/thumb -- same pattern as
     .leaderboard-list ("聊天界面往下滑动的时候不要出现滑动条，把它隐藏"). */
  scrollbar-width: none;
}

.chat-messages::-webkit-scrollbar {
  display: none;
}

.chat-messages .muted {
  margin: 10px 0;
  color: rgba(255, 255, 255, .54);
  font-size: 11px;
  text-align: center;
}

.chat-row {
  padding: 6px 8px;
  border: 1px solid rgba(255, 255, 255, .06);
  border-radius: 10px;
  color: rgba(255, 255, 255, .88);
  font-size: 11.5px;
  line-height: 1.4;
  background: linear-gradient(100deg, rgba(255, 255, 255, .085), rgba(255, 255, 255, .035));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .035);
}

.chat-row strong {
  color: rgba(255, 255, 255, .65);
  font-weight: 700;
}

.chat-row.me {
  border-color: var(--chat-accent-soft);
  background: linear-gradient(100deg, var(--chat-accent-soft), rgba(255, 255, 255, .075));
}

.chat-row.me strong {
  color: var(--chat-accent);
}

.chat-tabs {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 1px 10px;
}

.chat-cooldown-hint {
  color: var(--chat-accent);
  font-size: 11px;
  white-space: nowrap;
  text-shadow: 0 0 8px var(--chat-accent-faint);
}

.chat-tab {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  color: rgba(255, 255, 255, .62);
  font-size: 17px;
  background: linear-gradient(145deg, rgba(255, 255, 255, .12), rgba(255, 255, 255, .035));
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
}

.chat-tab.active {
  color: #fffdf0;
  background: linear-gradient(145deg, var(--chat-accent-soft), rgba(255, 255, 255, .1));
  border-color: var(--chat-accent);
  box-shadow: 0 0 0 1px var(--chat-accent-soft), 0 0 12px var(--chat-accent-faint), inset 0 1px 0 rgba(255, 255, 255, .18);
}

/* A fixed height (not max-height) instead of shrinking to fit whichever tab
   is shorter -- with max-height the whole popover used to visibly grow/shrink
   on every tab switch. Sized to the phrase list (the taller of the two); the
   emoji tab just leaves empty space below its grid.

   Trimmed from 220px per "炸金花的聊天界面...有部分内容会被公告遮挡" --
   .chat-popover is anchored off each room's own chat-BUTTON via a fixed
   `bottom` (see .chat-popover.jinhua-chat-popover etc. further down), tuned
   at the reference 390x844 viewport, but jinhua's button sits 53px higher
   on screen than texas's own (top:559 vs 612, both measured live) while
   every room shares this exact same panel height -- so the *same* overall
   popover height that comfortably clears the top announcement banner under
   texas's button barely fit (and, doing the actual math on jinhua's own
   button position, came out 5px short) under jinhua's higher one. Shrinking
   this only pulls the popover's TOP edge down (its `bottom` anchor, and so
   its gap above the button, is untouched) -- 25px less here clears the
   announcement with a real margin on jinhua without moving anything else,
   and gives texas/hundred a bit more breathing room too. */
.chat-tab-panel {
  height: 195px;
  overflow-y: auto;
  scrollbar-width: none;
}

.chat-tab-panel::-webkit-scrollbar {
  display: none;
}

.chat-emoji-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 7px;
}

.chat-emoji-btn {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  font-size: 21px;
  border: 1px solid rgba(255, 255, 255, .11);
  border-radius: 11px;
  background: linear-gradient(145deg, rgba(255, 255, 255, .115), rgba(255, 255, 255, .035));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .055);
}

.chat-emoji-btn:active {
  transform: scale(.93);
  border-color: var(--chat-accent);
  background: var(--chat-accent-soft);
}

/* Vertical list, up-down swipe/scroll -- a horizontal swipe strip was tried
   here but didn't register as swipeable on the user's phone, and up-down
   scrolling is the more familiar gesture for a list like this anyway.
   -webkit-overflow-scrolling keeps the touch-drag feel smooth/native. */
.chat-phrase-list {
  display: flex;
  flex-direction: column;
  gap: 7px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.chat-phrase-row {
  width: 100%;
  padding: 9px 10px;
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 10px;
  color: rgba(255, 255, 255, .92);
  font-size: 12px;
  font-weight: 600;
  text-align: left;
  background: linear-gradient(100deg, rgba(255, 255, 255, .11), rgba(255, 255, 255, .035));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .045);
}

.chat-phrase-row:active {
  transform: translateX(2px);
  border-color: var(--chat-accent);
  background: var(--chat-accent-soft);
}

.chat-phrase-row:disabled {
  opacity: .4;
}

.chat-noble-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}

.chat-noble-btn {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  padding: 4px;
  border: 1px solid rgba(255, 214, 130, .28);
  border-radius: 12px;
  background: linear-gradient(145deg, rgba(255, 214, 130, .16), rgba(255, 255, 255, .035));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .055);
}

.chat-noble-btn img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}

.chat-noble-btn:active {
  transform: scale(.93);
  border-color: var(--chat-accent);
  background: var(--chat-accent-soft);
}

.chat-noble-btn:disabled {
  opacity: .4;
}

.chat-row.noble-chat {
  color: #ffd782;
}

.chat-row-noble-emoji {
  width: 40px;
  height: 40px;
  vertical-align: middle;
  object-fit: contain;
}

.noble-emoji-fx {
  position: fixed;
  z-index: 211;
  width: 104px;
  height: 104px;
  object-fit: contain;
  pointer-events: none;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .45));
}

.chat-custom-panel {
  display: flex;
  gap: 8px;
  align-items: center;
}

.chat-custom-panel input {
  flex: 1;
  min-width: 0;
  padding: 9px 10px;
  border: 1px solid var(--chat-accent-soft);
  border-radius: 10px;
  background: rgba(0, 0, 0, .16);
  color: #fff;
  font-size: 13px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2);
}

.chat-custom-panel input:focus {
  outline: none;
  border-color: var(--chat-accent);
  box-shadow: 0 0 0 2px var(--chat-accent-faint), inset 0 1px 3px rgba(0, 0, 0, .2);
}

.chat-custom-panel input:disabled {
  opacity: .4;
}

.btn-send {
  padding: 9px 16px;
  border: 1px solid rgba(255, 255, 255, .38);
  border-radius: 10px;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: .4px;
  background: linear-gradient(145deg, #fff5d7, var(--chat-accent));
  color: #18213b;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .68), 0 4px 9px rgba(0, 0, 0, .2);
  white-space: nowrap;
}

.btn-send:disabled {
  opacity: .4;
}

.barrage-layer {
  position: absolute;
  inset: 0;
  /* Same reasoning as .table-message above -- must clear the chip pile
     layers (z-index: 20) so bot/player chat bubbles aren't hidden behind
     chips sitting in the middle of the table. */
  z-index: 21;
  overflow: hidden;
  pointer-events: none;
}

/* translateX (composited, GPU-driven) instead of animating "left" (a layout
   property that has to be recomputed every frame) -- fixed px distances
   comfortably clear both edges of the table, which is capped around 400-
   430px wide everywhere this renders. animation-delay is set inline per-
   frame from elapsed time (see barrageMarkup) so a rebuilt DOM node (the
   table's innerHTML is fully replaced on every poll) resumes mid-flight
   instead of restarting from the right edge -- the flight itself is plain
   CSS so it stays smooth without a JS ticker forcing extra re-renders. */
@keyframes barrageFly {
  from { transform: translateX(460px); }
  to { transform: translateX(-320px); }
}

.barrage-item {
  position: absolute;
  left: 0;
  white-space: nowrap;
  padding: 4px 12px;
  border-radius: 999px;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  background: rgba(0, 0, 0, .48);
  border: 1px solid rgba(255, 255, 255, .2);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
  animation: barrageFly 6000ms linear both;
  will-change: transform;
}

.barrage-item strong {
  color: #ffe37c;
  margin-right: 2px;
}

/* A lone emoji sent from the emoji picker stands out much more than a
   sentence would, same convention most chat apps use for an emoji-only
   message -- see barrageMarkup's isEmoji check. */
.barrage-item .barrage-emoji {
  font-size: 28px;
  line-height: 1;
  vertical-align: middle;
}

.hundred-players-popover-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 2px;
  border-bottom: 1px solid rgba(255, 255, 255, .1);
}

.hundred-players-popover-row:last-child {
  border-bottom: none;
}

.hundred-players-popover-avatar {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 11px;
}

.hundred-players-popover-meta {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.hundred-players-popover-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: rgba(255, 255, 255, .9);
}

.hundred-players-popover-balance {
  font-size: 11px;
  color: #ffe37c;
  font-weight: 700;
}

.hundred-footer-bar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  width: min(100vw, 430px);
  bottom: 0;
  z-index: 19;
  height: max(48px, calc(44px + env(safe-area-inset-bottom)));
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(10, 86, 68, .42), rgba(3, 22, 19, .78)),
    rgba(3, 18, 16, .68);
  border-top: 1px solid rgba(105, 210, 170, .12);
  box-shadow: 0 -12px 28px rgba(0, 0, 0, .32);
  /* backdrop-filter dropped -- this bar is purely decorative (pointer-
     events:none, no content of its own) and gets fully rebuilt from
     scratch on every poll-driven re-render along with the rest of the
     table. Live blur-behind is one of the more GPU-expensive CSS effects
     on mobile, and the gradient/solid fill underneath is already opaque
     enough (.68-.78 alpha) that the visual difference without it is
     minor. */
}

.hundred-mood {
  /* Was capped at 50px to leave room for .hundred-balance sitting beside it
     (see .hundred-player-meta's own comment -- that row layout is now a
     column instead) -- stacked, the name can use the full width of
     .hundred-player-meta instead of splitting it with the balance, so
     nicknames that used to get ellipsis-truncated after a couple characters
     now actually fit ("尽量让昵称完全展示出来"). max-width/ellipsis stay as
     a safety net for a genuinely long name, just widened to match. */
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hundred-balance {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 2px;
  color: #ffe37c;
  font-size: 14px;
  font-weight: 900;
  white-space: nowrap;
}

.hundred-repeat-btn {
  position: relative;
  flex: 0 0 auto;
  width: 50px;
  height: 38px;
  padding: 0;
  border-radius: 999px;
  color: #fff;
  font-size: 12px;
  font-weight: 900;
  background:
    linear-gradient(180deg, rgba(48, 173, 146, .9), rgba(11, 91, 77, .92));
  border: 1px solid rgba(176, 255, 221, .3);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .08),
    0 6px 14px rgba(0, 0, 0, .26);
  /* Moves up together with .chip (same value) -- "重复投入按钮应该要和选
     择筹码一起平移". */
  transform: translateY(-36px);
}

.hundred-repeat-btn.active {
  color: #fff;
  background: linear-gradient(180deg, #21bd70, #087d5f);
  border-color: rgba(255, 238, 119, .95);
  box-shadow:
    0 0 0 3px rgba(255, 224, 91, .9),
    0 7px 16px rgba(0, 0, 0, .32);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .65);
}

.hundred-repeat-btn.active::after {
  content: "";
  position: absolute;
  right: 7px;
  top: 7px;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: #fff35f;
  box-shadow:
    0 0 0 2px rgba(4, 78, 58, .72),
    0 0 8px rgba(255, 243, 95, .72);
}

.hundred-repeat-btn:disabled {
  opacity: .5;
  color: rgba(255, 255, 255, .62);
  background: rgba(31, 72, 66, .78);
  border-color: rgba(255, 255, 255, .12);
  box-shadow: none;
}

.hundred-repeat-btn:disabled::after {
  display: none;
}

.hundred-zone-wrap {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Was 4px -- pulled tighter so the trend track sits closer under its own
     zone button ("胜负走势再往上移动一点，贴近对应的格子"). */
  gap: 1px;
  min-width: 0;
}


.hundred-zone {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  height: 132px;
  padding: 6px;
  border-radius: 12px;
  border: 2px solid rgba(255, 255, 255, .36);
  background: rgba(0, 77, 61, .56);
  transition: transform 120ms ease, border-color 120ms ease;
  overflow: hidden;
  contain: layout size;
}

.hundred-zone:not(:disabled):active,
.hundred-zone:not(:disabled).pressed {
  transform: scale(.97);
}

.hundred-zone:disabled {
  opacity: 1;
}

.hundred-zone.win {
  border-color: rgba(255, 255, 255, .36);
  box-shadow:
    0 0 18px rgba(255, 205, 65, .66),
    0 0 30px rgba(255, 205, 65, .34);
}

.hundred-zone.lose {
  border-color: rgba(255, 255, 255, .36);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(255, 255, 255, .02)),
    rgba(103, 76, 82, .32);
}

.hundred-zone-header {
  position: absolute;
  left: 10px;
  right: 10px;
  top: 4px;
  z-index: 6;
  min-height: 22px;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #86f7ff;
  font-size: 18px;
  font-weight: 900;
  border-radius: 999px;
  background: rgba(255, 255, 255, .22);
}

.hundred-zone-label {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: grid;
  place-items: center;
  color: rgba(189, 255, 234, .16);
  font-size: 48px;
  font-weight: 900;
  line-height: 1;
  pointer-events: none;
  text-shadow: 0 2px 8px rgba(0, 0, 0, .2);
}

.hundred-zone-total {
  position: relative;
  z-index: 6;
  color: #fff;
  font-size: 14px;
  font-weight: 900;
  line-height: 1;
  text-align: center;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .65);
}

.hundred-zone-cards {
  position: relative;
  z-index: 18;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  margin: 28px 0 2px;
  height: 42px;
  min-height: 42px;
  overflow: visible;
}

.hundred-zone-cards .card {
  width: 27px;
  height: 38px;
  font-size: 17px;
}

.hundred-chip-pile {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.hundred-chip-stack {
  position: relative;
  width: 86px;
  height: 52px;
}

.hundred-chip-stack .pile-chip {
  left: calc(50% + var(--pile-x, 0px));
  top: calc(50% + var(--pile-y, 0px));
  transform: translate(-50%, -50%) rotate(var(--pile-rot, 0deg));
  border: 2px solid rgba(255, 255, 255, .92);
  box-shadow: 0 2px 4px rgba(0, 0, 0, .3);
}

/* Hundred-only: swap the live two-radial-gradient background (still used
   as-is by jinhua's own .pile-chip/.landed-chip/etc, see those rules
   above) for a single cached bitmap. A betting zone here can carry 20+ of
   these, all rebuilt from scratch on every poll-driven re-render
   (renderHundred() replaces the whole table's innerHTML unconditionally,
   and does so on nearly every 1s poll tick since the round countdown
   alone changes the payload) -- recomputing two live gradients per chip,
   times that many chips, on every one of those rebuilds was real repaint
   cost for something that looks identical every time. A decoded bitmap
   costs the same to blit regardless of how many chips are on screen.
   Scoped to .hundred-chip-stack specifically because .pile-chip is shared
   with jinhua, which never had this problem (far fewer chips on screen at
   once) and looked worse with the image swap applied globally. */
.hundred-chip-stack .pile-chip.chip-small {
  width: 18px;
  height: 18px;
  font-size: 6px;
  background: url("./assets/chip-shine.png") center / cover, var(--chip-main);
}

.hundred-chip-stack .pile-chip.chip-medium {
  width: 21px;
  height: 21px;
  font-size: 7px;
  background: url("./assets/chip-shine.png") center / cover, var(--chip-main);
}

.hundred-chip-stack .pile-chip.chip-large {
  width: 26px;
  height: 26px;
  font-size: 8px;
  background: url("./assets/chip-shine.png") center / cover, var(--chip-main);
}

.hundred-flight-layer {
  position: absolute;
  inset: 0;
  z-index: 15;
  pointer-events: none;
}

.hundred-big-winner-panel {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 26px 14px 14px;
  border-radius: 999px;
  background: var(--gold);
  border: 2px solid #8a5a2c;
  box-shadow: 0 0 30px rgba(255, 200, 60, .6), 0 12px 30px rgba(0, 0, 0, .5);
  pointer-events: none;
  white-space: nowrap;
}

.hundred-big-winner-avatar {
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
}

.hundred-big-winner-avatar .avatar {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  font-size: 26px;
}

.hundred-big-winner-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hundred-big-winner-tag {
  color: #7a3d00;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 1px;
}

.hundred-big-winner-info strong {
  color: #3a2205;
  font-size: 17px;
  font-weight: 900;
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hundred-big-winner-amount {
  color: #5c2f00;
  font-size: 20px;
  font-weight: 900;
}

/* left/top used to be the two animated properties (0% at --fx/--fy, 100%
   at --tx/--ty) -- both force a layout pass on every frame, unlike
   transform, which the compositor can run on its own with no layout/paint
   at all. left/top are now set once here (the flight's fixed start point)
   and the keyframe below only moves a transform: translate() from there to
   the target -- same path, same 600ms, but compositor-only per frame.
   Landing rotation and the drop-shadow layer are dropped too (see
   hundredChipFly's own comment) -- straight fly, no other embellishment,
   per "直接飞...不加其他的特效，只要有最低的存在即可". */
.hundred-flying-chip {
  position: absolute;
  left: var(--fx);
  top: var(--fy);
  z-index: 30;
  border: 2px solid rgba(255, 255, 255, .92);
  animation: hundredChipFly 600ms cubic-bezier(.2, .74, .28, 1) var(--delay, 0ms) both;
}

/* Same cached-bitmap swap as .hundred-chip-stack .pile-chip (see its own
   comment) -- this class only exists on the hundred betting-chip toss
   animation (renderHundredChipFlights), never on jinhua's own chips, so it
   was missed the first pass even though a flying chip is if anything
   worse than a resting one: it's actively animating every frame AND was
   still paying for two live radial gradients on every one of those
   frames, for however many chips are mid-toss at once. */
.hundred-flying-chip.chip-small {
  width: 18px;
  height: 18px;
  font-size: 6px;
  background: url("./assets/chip-shine.png") center / cover, var(--chip-main);
}

.hundred-flying-chip.chip-medium {
  width: 21px;
  height: 21px;
  font-size: 7px;
  background: url("./assets/chip-shine.png") center / cover, var(--chip-main);
}

.hundred-flying-chip.chip-large {
  width: 26px;
  height: 26px;
  font-size: 8px;
  background: url("./assets/chip-shine.png") center / cover, var(--chip-main);
}

/* Pure transform move: 0% sits at this element's own left/top (--fx/--fy,
   the flight's start point) with no offset, 100% translates the rest of
   the way to the target (--tx/--ty) plus the same -50%/-50% centering
   both ends need. No rotate, no scale, no opacity fade -- just the move. */
@keyframes hundredChipFly {
  0% {
    transform: translate(-50%, -50%);
  }

  100% {
    transform: translate(calc(-50% + var(--tx) - var(--fx)), calc(-50% + var(--ty) - var(--fy)));
  }
}

.hundred-zone-hand {
  position: relative;
  z-index: 12;
  box-sizing: border-box;
  height: 20px;
  min-height: 20px;
  color: #fff1a8;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: .12em;
  line-height: 1.2;
  text-align: center;
  text-shadow:
    0 1px 0 rgba(0, 0, 0, .72),
    0 0 9px rgba(255, 215, 91, .42);
}

.hundred-zone-hand:not(:empty) {
  width: fit-content;
  min-width: 42px;
  margin: 1px auto 0;
  padding: 1px 8px 2px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .36);
  border: 1px solid rgba(255, 224, 91, .28);
}

.hundred-result {
  position: absolute;
  top: 0;
  right: 4px;
  z-index: 20;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 900;
  background: rgba(0, 0, 0, .42);
  text-shadow: 0 2px 4px rgba(0, 0, 0, .72);
}

.hundred-result.win {
  color: #ffd75b;
  border: 1px solid rgba(255, 215, 91, .58);
  box-shadow: 0 0 10px rgba(255, 205, 65, .38);
}

.hundred-result.lose {
  color: #fff;
  background: rgba(92, 96, 101, .88);
  border: 1px solid rgba(255, 255, 255, .28);
  box-shadow: 0 0 10px rgba(0, 0, 0, .24);
}

.hundred-my-bet {
  position: absolute;
  bottom: 2px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 12;
  color: #fff3a6;
  font-size: 11px;
  font-weight: 900;
  text-align: center;
  white-space: nowrap;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .72);
}

.hundred-zone-history {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  width: 100%;
  min-width: 0;
  overflow: hidden;
  /* Was min-height 22px / padding 5px 10px 7px 8px -- read as too tall next
     to its 16px dots ("胜负框走势再矮一点，不要太高"). Trimmed padding down
     close to what the dots themselves need. */
  min-height: 18px;
  padding: 3px 8px 3px 8px;
  border-radius: 12px;
  border: 1px solid rgba(255, 224, 91, .35);
  background: rgba(0, 0, 0, .32);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .3);
}

.hundred-history-track {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  flex: 1 1 auto;
  flex-wrap: nowrap;
  gap: 3px;
  min-width: 0;
  overflow: hidden;
  will-change: transform;
}

.hundred-zone-history.scrolling .hundred-history-track {
  animation: hundredHistorySlideIn .46s cubic-bezier(.18, .82, .22, 1) both;
}

/* Static "there's more" chevron, pinned as its own flex slot at the very
   right end of the 胜负框 (replaces the old separately-overlaid arrow image
   that sat outside the box's own border, which never quite fused with it --
   "把原来的胜负框下面的箭头去掉，用 > 放在胜负框最右边的框里"). Being a
   normal flex child (not absolutely positioned) means it can never poke
   outside the box regardless of content width ("不要出胜负走势框"), and the
   scrolling dots track shrinks to make room for it rather than overlapping. */
.hundred-history-arrow {
  flex: 0 0 auto;
  /* The "opening" left of the chevron -- separates it from the last dot
     instead of the two crowding together ("将 > 左边的口拉大一点"). */
  margin-left: 7px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  color: rgba(255, 224, 91, .85);
}

.hundred-history-item {
  position: relative;
  flex: 0 0 auto;
}

.hundred-history-new {
  display: none;
}

.hundred-history-dot {
  position: relative;
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  font-size: 9px;
  font-weight: 900;
  color: #fff;
}

.hundred-history-dot.latest {
  z-index: 2;
  box-shadow: 0 0 0 2px var(--gold);
}

.hundred-history-dot.latest::after {
  content: none;
}

.hundred-history-dot.win {
  color: #4a2c00;
  background: linear-gradient(180deg, #ffe77a, #d39a22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .55);
}

.hundred-history-dot.lose {
  color: #fff;
  background: rgba(92, 96, 101, .88);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .16);
}

@keyframes hundredHistorySlideIn {
  0% {
    transform: translateX(22px);
  }

  100% {
    transform: translateX(0);
  }
}

.chips {
  position: fixed;
  left: 50%;
  right: auto;
  z-index: 24;
  /* Nudged left off dead-center -- the row's content (avatar strip + chip
     buttons + repeat button, packed flex-start) is wider than a narrow
     phone's viewport on its own (see the shrunk .chip/.hundred-repeat-btn
     sizes and tighter gap below, which claw back some of that overflow),
     so on real devices the tail end used to run under the screen's rounded
     corner. Shifting the whole bar left gives that corner some clearance. */
  transform: translateX(calc(-50% - 10px));
  width: min(100vw, 430px);
  bottom: max(7px, env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: nowrap;
  gap: 4px;
  padding: 34px 8px 7px;
  overflow: visible;
  border-radius: 42px 42px 0 0 / 34px 34px 0 0;
  background: transparent;
}

/* Removed -- this was the OTHER layer still framing the avatar on its own
   (a distinct 214px circle sitting just behind it, separate from
   .hundred-player-strip's own ::before already removed above), which is
   why the frame was still visible after hiding that one alone ("玩家自己
   的人物框没有去掉"). .chips::after already covers this whole bar with one
   flat panel; nothing needs to fill in for this circle either. */
.chips::before {
  display: none;
}

.chips::after {
  content: "";
  position: absolute;
  left: -18px;
  right: -18px;
  top: -22px;
  bottom: 0;
  z-index: 0;
  border-radius: 50% 50% 0 0 / 72px 72px 0 0;
  background:
    radial-gradient(ellipse at 50% -24%, rgba(128, 237, 196, .22), transparent 56%),
    radial-gradient(ellipse at 18% 16%, rgba(31, 133, 103, .24), transparent 42%),
    linear-gradient(180deg, rgba(9, 82, 67, .9), rgba(3, 46, 38, .97) 58%, rgba(2, 25, 22, .99));
  border-top: 0;
  box-shadow:
    inset 0 1px 0 rgba(203, 255, 231, .08),
    0 -10px 22px rgba(0, 0, 0, .22);
  /* backdrop-filter dropped -- same reasoning as .hundred-footer-bar: this
     is the always-on operator panel background, rebuilt every poll tick,
     and the gradient underneath is already ~90-99% opaque. */
  pointer-events: none;
}

.chips .hundred-player-strip::after {
  display: none;
}

.chips > * {
  position: relative;
  z-index: 2;
}

.hundred-player-strip {
  position: relative;
  flex: 0 0 152px;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  margin-right: 0;
  align-self: flex-start;
  /* Pushed up further still -- fine for the avatar to visually poke out
     above .chips's own top edge (position:relative + z-index:2 on
     .chips > * already keeps it painting above the bar underneath it).
     -50 -> -40 and padding-left 3 -> 14: was sitting too close to the
     screen's left edge/corner ("头像和昵称再往右下移动一点，太靠边了"). */
  margin-top: -40px;
  padding-left: 14px;
  z-index: 3;
}

/* Removed -- this was its own separate oval "frame" behind the avatar,
   sitting right on top of .chips::before's own circular panel at nearly
   the same position, so the avatar read as boxed off in its own frame
   instead of sharing the one panel the chips already sit on ("把左下角玩
   家自己的人物框去掉，与旁边筹码共用一个界面"). .chips::before/::after
   already cover this area, so nothing needs to fill in for it. */
.hundred-player-strip::before {
  display: none;
}

.hundred-player-strip > * {
  position: relative;
  z-index: 1;
}

.chips .hundred-my-avatar {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
}

.hundred-player-meta {
  /* Was a row (name beside balance, name capped to 50px and ellipsis-
     truncated almost immediately, see .hundred-mood's own comment) --
     stacked into a column instead so the name gets the full width of this
     container on its own line, with the balance on the line below it
     ("百人场玩家自己的昵称和金币上下排列"). */
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;
}

.chip {
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  font-size: 12px;
  border-radius: 999px;
  color: #fff;
  font-weight: 900;
  background: var(--chip-color, #6d4fd8);
  box-shadow: inset 0 0 0 9px rgba(0, 0, 0, .18), 0 7px 16px rgba(0, 0, 0, .28);
  text-shadow: 0 2px 3px rgba(0, 0, 0, .8);
  /* Pulled up to sit level with .hundred-my-avatar (which pokes up above
     .chips' own top edge via .hundred-player-strip's -50px margin-top) --
     measured live, the two centers were 36px apart ("把选择的筹码往上移
     动，与玩家水平对齐"). */
  transform: translateY(-36px);
}

.chip.active {
  outline: 4px solid var(--gold);
}

.chip:disabled {
  opacity: .38;
  filter: grayscale(.7);
  box-shadow: none;
}

.error {
  min-height: 22px;
  color: #ffb6b6;
}

@media (max-width: 390px) {
  .lobby-bg {
    padding-inline: 10px;
  }

  .lobby-host img {
    left: var(--host-x, -124px);
    top: var(--host-y, -4px);
    width: var(--host-w, 468px);
  }

  .layout-panel {
    grid-template-columns: 1fr;
  }

  .entry-title {
    font-size: 15px;
  }

  .entry-desc {
    font-size: 10px;
  }

  .game-tier-stage {
    grid-template-columns: repeat(2, 1fr);
  }

  .seat {
    width: 112px;
  }

  .seat.me {
    width: 168px;
  }

  .seat.s1,
  .seat.s2 {
    right: 2px;
  }

  .seat.s3 {
    left: 2px;
  }

  .seat.s2,
  .seat.s3 {
    top: 24%;
  }

  .seat.s4 {
    left: 2px;
  }

.side-tools button {
    width: 34px;
    height: 34px;
  }

  .chip {
    width: var(--chip-size, 46px);
    height: var(--chip-size, 46px);
    font-size: 12px;
  }
}

.seat.empty-seat {
  width: 42px;
  height: 38px;
  min-width: 42px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  box-shadow: none;
}

.seat.empty-seat.me {
  width: 42px;
  bottom: 46px;
  padding: 0;
  box-shadow: none;
}

.seat.empty-seat.s1 {
  right: 41px;
  top: calc(55% + 18px);
}

.seat.empty-seat.s2 {
  right: 41px;
  top: calc(23% + 18px);
}

.seat.empty-seat.s3 {
  left: 41px;
  top: calc(23% + 18px);
}

.seat.empty-seat.s4 {
  left: 41px;
  top: calc(55% + 18px);
}

/* Competitive card-room v1. Logic and DOM contracts stay unchanged; only
   the room's spatial system and presentation are rebuilt around the arena
   background and a conventional mobile-game HUD. */
.table-screen:not(.hundred-screen) {
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  /* 156px reserved room for the old 2-row .jinhua-controls bar -- trimmed
     by the same 49px the bar itself lost (see .seat.me's bottom offset
     above for the full breakdown). */
  padding: 0 0 107px;
  background: transparent;
}

/* Neither jinhua nor Texas puts anything worth reading in this shared
   topbar (jinhua's own room-name title is dropped below; Texas already
   passed topbar("", ...)) -- the opaque purple bar just read as dead
   space above the table with a lone menu button (and, for Texas, a
   balance chip) crammed in the corner. Drop the bar itself and let those
   float straight over the table background; the room name is still one
   tap away via the menu's "info" entry (renderRoomMenu). Texas manages its
   own bottom spacing (no fixed 156px control bar like jinhua's), so only
   Texas also drops the base rule's bottom padding. */
.table-screen.texas-screen {
  padding: 0;
}

.table-screen:not(.hundred-screen) > .topbar {
  z-index: 45;
  min-height: 40px;
  background: none;
  border-bottom: none;
  box-shadow: none;
}

.table-screen:not(.hundred-screen) .page-title {
  display: none;
}

.table-screen:not(.hundred-screen) .room-menu-btn {
  /* position:fixed (rather than absolute, inherited from .exit-btn, which
     is only ever compared against siblings *inside* #app) so this escapes
     to the root stacking context and can actually out-rank the
     announcement ticker: the ticker is appended straight to <body> with
     its own root-level z-index:46 (see .game-announcement-anchor-room),
     while an absolutely-positioned descendant of #app is capped by #app's
     own (much lower) z-index no matter what z-index it declares locally. */
  position: fixed;
  left: auto;
  /* .screen (the actual phone-shaped game column) is width:min(430px,100vw)
     and centered -- on any viewport wider than 430px that leaves empty
     space on both sides, and a plain "right:10px" (relative to the REAL,
     wider viewport now that this is position:fixed) lands past the
     column's own right edge, out past the visible game art entirely. This
     offsets from the column's real right edge instead. */
  right: calc(50vw - min(430px, 100vw) / 2 + 10px);
  bottom: auto;
  /* Dropped below the announcement ticker's band (was aligned to its
     centerline, but sitting right next to it read as cluttered) -- ticker
     sits roughly 17.5-41.5px down, so this starts clear of it. */
  top: calc(max(8px, env(safe-area-inset-top)) + 46px);
  z-index: 47;
  display: grid;
  place-items: center;
  width: 43px;
  height: 43px;
  border-radius: 50%;
  color: #dffaff;
  font-size: 23px;
  background: linear-gradient(145deg, rgba(45, 55, 145, .96), rgba(9, 15, 62, .98));
  border: 1px solid rgba(110, 221, 255, .46);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 0 14px rgba(67, 148, 255, .22);
}

.table-screen:not(.hundred-screen) .balance {
  bottom: auto;
  top: max(8px, env(safe-area-inset-top));
  right: 62px;
}

.table-screen:not(.hundred-screen) .poker-table {
  width: min(430px, 100vw);
  height: calc(100vh - 210px);
  height: calc(100dvh - 210px);
  min-height: 560px;
  max-height: none;
  margin: 0 auto;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.table-screen:not(.hundred-screen) .table-hud {
  /* Moved down to sit just above .pot-chip-pile (top:57%, ~118px tall,
     centered via translate(-50%,-50%) so its own visual top edge is
     roughly 57% minus half its height) without overlapping it, and raised
     above the pile's z-index:20 so it stays legible even if they ever
     brush against each other. Capped to a narrower max-width (rather than
     nearly full table width) plus dropping the "基础值" span so the whole
     bar fits within the gap between .seat.s2/.seat.s3 at this same height,
     instead of running under them. */
  top: 47%;
  z-index: 21;
  gap: 6px;
  max-width: 190px;
  padding: 6px 11px;
  color: rgba(213, 244, 255, .88);
  font-size: 10px;
  letter-spacing: .2px;
  background: linear-gradient(180deg, rgba(24, 37, 112, .9), rgba(7, 13, 56, .9));
  border: 1px solid rgba(92, 182, 255, .34);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .09), 0 6px 14px rgba(0, 2, 30, .3);
}

.table-screen:not(.hundred-screen) .pot {
  /* Was 31%, then 25% -- with .pot-allin-cap's subtitle line right under
     it, 31% put the cap's own bottom edge ~6px *below* the top of the
     s2/s3 seat frames underneath (36%), i.e. actually overlapping them.
     Now 19%: the whole cluster (this, .pot-allin-cap, .pot-chip-pile, and
     the 4 seats) shifted up together ~6 points to fix a real-phone report
     that the whole seat row read as crowded against the bottom controls. */
  top: 19%;
  padding: 6px 13px;
  color: #9af4ff;
  font-size: 16px;
  letter-spacing: .5px;
  text-shadow: 0 0 10px rgba(72, 205, 255, .48);
  background: linear-gradient(180deg, rgba(28, 52, 133, .78), rgba(8, 18, 67, .82));
  border: 1px solid rgba(100, 224, 255, .36);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .09), 0 5px 15px rgba(0, 2, 34, .3);
}

.table-screen:not(.hundred-screen) .pot-allin-cap {
  top: 19%;
  transform: translate(-50%, calc(-50% + 27px));
  color: rgba(154, 244, 255, .82);
  background: rgba(8, 18, 67, .5);
  border: 1px solid rgba(100, 224, 255, .22);
}

.table-screen:not(.hundred-screen) .seat {
  width: 112px;
  padding: 5px 6px;
  border: 1px solid rgba(87, 164, 255, .36);
  border-radius: 13px;
  background:
    radial-gradient(circle at 20% 0, rgba(91, 224, 255, .12), transparent 44%),
    linear-gradient(155deg, rgba(24, 35, 107, .9), rgba(7, 12, 48, .92));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 7px 16px rgba(0, 2, 28, .34);
  /* backdrop-filter dropped -- every seat (up to 5 in jinhua, 7 in Texas)
     carried this, and renderRoom()/renderTexas() rebuild the whole seat
     subtree from scratch on most poll ticks during an active hand (turn
     countdown, chip flights, etc.) -- several simultaneous live-blur nodes
     recreated multiple times a second is a heavy, sustained GPU/battery
     cost ("手机发烫"). The .9/.92-alpha gradient underneath already reads
     the same without it. */
}

.table-screen:not(.hundred-screen) .seat.turn {
  border-color: rgba(117, 242, 255, .92);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .13), 0 0 0 2px rgba(90, 104, 255, .48), 0 0 22px rgba(73, 203, 255, .38);
}

.table-screen:not(.hundred-screen) .seat.me,
.table-screen:not(.hundred-screen) .seat.s0 {
  /* .seat's base rule is position:absolute, anchored to .poker-table --
     but .poker-table reserves ~210px of empty space below its own bottom
     edge before the fixed .jinhua-controls bar even starts, so shrinking
     "bottom" here alone could only ever close part of that gap, not get
     visibly near the bar. Switching to position:fixed anchors directly to
     the real viewport/control-bar instead, so this now sits just above the
     bar regardless of .poker-table's own height. */
  position: fixed;
  /* .jinhua-controls no longer shows anything for a seated player (see its
     own comment in renderRoom()) -- nothing left down there to clear, so
     this sits close to the real bottom edge instead of leaving room for
     that bar's old height. Nudged up by one button's own footprint (34px
     height + 6px gap, same as the left/right stacks' own button spacing)
     per its own explicit ask, on top of that. */
  bottom: calc(40px + max(8px, env(safe-area-inset-bottom)));
  /* Narrowed from the old 176px (sized for avatar-beside-cards) down to
     roughly the 3-card row's own width (.seat.me .cards' 112px min-width)
     plus this frame's own padding -- now that .seat.me .seat-main stacks
     avatar-then-cards vertically instead (see below), the frame only ever
     needs to be as wide as the wider of those two stacked pieces. */
  width: 124px;
  /* Fixed, not auto -- .cards collapses to its own smaller min-height
     whenever seat.cardCount is 0 (ready phase, no hand dealt yet) instead
     of the taller height once real cards land, which otherwise made the
     whole frame visibly grow/shrink at the start of every hand (same
     issue Texas's own .seat.me height fixes, see its own comment). */
  height: 144px;
  /* Tighter than the shared skin's 5px top padding -- pulls the avatar
     (first thing in flow, see .seat-main below) up closer to the frame's
     own top edge instead of leaving as much breathing room above it. */
  padding-top: 2px;
  border-color: rgba(115, 229, 255, .48);
  background:
    radial-gradient(circle at 20% 0, rgba(111, 226, 255, .15), transparent 45%),
    linear-gradient(155deg, rgba(36, 44, 128, .94), rgba(9, 12, 54, .96));
}

/* Avatar-then-cards stacked vertically instead of the shared .seat-main's
   default side-by-side row (see the base rule's own comment on why that
   row exists) -- now that the frame itself is narrow (see width above),
   there's no longer room for both side by side. .seat-profile's own
   contents (avatar/nickname/balance) already lay out as a centered column
   internally (block flow + inherited text-align:center from .seat), so
   centering it as a whole here is what actually centers the avatar in the
   frame. */
.table-screen:not(.hundred-screen) .seat.me .seat-main,
.table-screen:not(.hundred-screen) .seat.s0 .seat-main {
  flex-direction: column;
  align-items: center;
}

/* 已投入 moved out of the frame entirely -- see .seat-side-actions .seat-bet
   in the top action cluster's own CSS instead, riding level with 全下. */

/* 看上一局 sits in the frame's own top-right corner. */
.table-screen:not(.hundred-screen) .seat.me .last-hand-btn,
.table-screen:not(.hundred-screen) .seat.s0 .last-hand-btn {
  top: 4px;
  bottom: auto;
  /* .last-hand-btn's base z-index (3) loses to .settlement-float's (60) --
     harmless everywhere else this button is docked, but up here in the top
     corner the win/loss bubble (also top, horizontally centered) can
     overlap it on narrower seats, and the bubble being on top silently ate
     every tap on the eye icon for the whole ~5.7s it's showing ("结算冒结算
     气泡时间段点不了眼睛"). Matches the z-index the Texas-specific corner
     override below already uses for the same reason. */
  z-index: 61;
}

/* Texas only: docked at the AVATAR's own bottom-right corner instead of the
   frame's top-right the way jinhua's does -- "眼睛放在头像右下角，人物框
   内". Nested inside .seat-profile in the markup (already position:relative
   and, for "me", sized to just the avatar -- see its own comment further
   down) rather than a direct child of .seat, so this positions against the
   avatar's own box, not the whole (much taller, nickname/cards included)
   frame. More classes than the jinhua rule above (adds .seat-profile), so
   this wins outright regardless of source order. */
.table-screen.texas-screen .seat.me .seat-profile .last-hand-btn {
  top: auto;
  right: -4px;
  bottom: -4px;
  left: auto;
  z-index: 60;
}

/* 金币 (balance) moved out of .seat-profile's narrow avatar column (see
   renderRoom()'s isMe branch -- stackMarkup is now a direct child of .seat,
   the ">" combinator here only matches that, not opponents' still-nested
   .seat-stack) into the frame's own top-left corner instead, now that
   nickname (see .seat-name-wrap, back in its normal default spot right
   under the avatar) took over the spot .seat-stack used to overlap. */
.table-screen:not(.hundred-screen) .seat.me > .seat-stack,
.table-screen:not(.hundred-screen) .seat.s0 > .seat-stack {
  position: absolute;
  left: 4px;
  top: 4px;
  margin-top: 0;
  z-index: 2;
}

/* Opponents only: 金币 floats above the avatar, overlapping its top edge a
   little, instead of sitting in normal flow right after it -- 昵称 moved
   out to its own full-width row below the whole frame instead (see
   nameMarkup's own rule further down), so this no longer needs to dodge
   it, just needed its own spot that reads clearly against the avatar
   art/photo underneath. z-index:52 needed to actually paint over the
   avatar (JS bumps clickable avatars to z-index:51, see
   promoteInteractiveElements), not just sit behind it unseen. */
.table-screen:not(.hundred-screen) .seat:not(.me):not(.s0) .seat-stack {
  position: absolute;
  left: 50%;
  bottom: 100%;
  top: auto;
  z-index: 52;
  margin: 0 0 -8px;
  transform: translateX(-50%);
}

/* Opaque band behind it, same fading-at-the-edges look as 昵称's own
   ::before (see the shared .seat-name-wrap::before rule) -- plain
   text-shadow wasn't enough once this started overlapping avatar art
   instead of sitting against the felt/dark seat background. */
.table-screen:not(.hundred-screen) .seat:not(.me):not(.s0) .seat-stack::before {
  content: "";
  position: absolute;
  inset: -1px -6px;
  z-index: -1;
  border-radius: 4px;
  background: linear-gradient(90deg, transparent, rgba(8, 10, 18, .85) 24%, rgba(8, 10, 18, .85) 76%, transparent);
}

/* Other players' balance badge overlaps the shared -23px position, but the
   old -48px correction pulled the result too far away from the avatar.
   -36px preserves a readable gap while keeping the bubble visually tied to
   its own seat frame. */
.table-screen:not(.hundred-screen):not(.texas-screen) .seat:not(.me):not(.s0) .settlement-float {
  top: -36px;
}

/* 昵称 moved out of .seat-profile's narrow ~48px avatar column (see
   renderRoom()'s isMe branch -- nameMarkup is now a direct child of .seat
   for opponents, rendered right after .seat-main closes) into its own
   full-width row spanning the whole frame instead, so a long name gets
   the frame's full ~112-116px to work with instead of truncating down to
   a handful of characters. Nudged down from the shared rule's -14px
   pull-up (tuned for sitting right under the avatar in that narrow
   column), then pulled back in further still to sit close to 已投 (which
   itself already hangs a little below the card faces, see .cards
   .seat-bet's own bottom offset) without the two actually touching. */
.table-screen:not(.hundred-screen) .seat > .seat-name-wrap {
  margin-top: -3px;
  padding: 1px 4px;
  max-width: 100%;
}

.table-screen:not(.hundred-screen) .seat > .seat-name-wrap .seat-name {
  max-width: 100%;
}

/* Gold band, same tone as Texas's own opponent nickname strip (see
   .table-screen.texas-screen .seat:not(.me) > .seat-name-wrap::before)
   instead of the shared rule's dark navy -- matches Texas's look now that
   this is a full-width strip along the frame edge too, not the narrow
   in-column tag it used to be. */
.table-screen:not(.hundred-screen) .seat > .seat-name-wrap::before {
  background: linear-gradient(90deg, transparent, rgba(146, 96, 10, .82) 24%, rgba(146, 96, 10, .82) 76%, transparent);
}

/* "我" own nickname stays nested inside .seat-profile, still between the
   avatar and the cards (see renderRoom()'s isMe branch) unlike opponents'
   own copy right above, so this needs its own descendant selector rather
   than reusing that ">" direct-child one -- same gold band color as
   opponents. */
.table-screen:not(.hundred-screen) .seat.me .seat-name-wrap::before,
.table-screen:not(.hundred-screen) .seat.s0 .seat-name-wrap::before {
  background: linear-gradient(90deg, transparent, rgba(146, 96, 10, .82) 24%, rgba(146, 96, 10, .82) 76%, transparent);
}

/* -3px instead of the shared rule's -14px -- just enough to overlap the
   avatar's own bottom edge/border a little instead of the -14px default
   riding much further up onto it. Still in normal flow (not absolute) as
   part of .seat-profile, so the extra height this adds over the old -14px
   still pushes .cards -- the next sibling down in "我"'s column layout --
   down along with it, moving the two together instead of just the name on
   its own.
   Negative left/right margins let it grow past .seat-profile's own narrow
   ~50-56px box (sized just for the avatar) symmetrically on both sides
   instead of being clipped to it, so a long name actually gets close to
   the full ~124px frame width to work with -- max-width caps it a few px
   short of that frame edge as a hard stop, ellipsis (see the shared
   .seat-name rule) as the last resort for anything still too long even
   at that width. */
.table-screen:not(.hundred-screen) .seat.me .seat-name-wrap,
.table-screen:not(.hundred-screen) .seat.s0 .seat-name-wrap {
  margin-top: -3px;
  margin-left: -33px;
  margin-right: -33px;
  max-width: 120px;
}

.table-screen:not(.hundred-screen) .seat.me .seat-name-wrap .seat-name,
.table-screen:not(.hundred-screen) .seat.s0 .seat-name-wrap .seat-name {
  max-width: 100%;
}

.table-screen:not(.hundred-screen) .seat.s2,
.table-screen:not(.hundred-screen) .seat.s3 {
  /* Was 36% -- on a real phone (not just the desktop-emulated viewport
     this was tuned against) the whole opponent-seat row read as crowded
     against the bottom controls, so the whole cluster (these 4 seats plus
     the pot/all-in-cap/chip-pile block) shifted up ~6 points, roughly half
     an empty-seat frame's height (.empty-seat is 74px tall). */
  top: 30%;
}

.table-screen:not(.hundred-screen) .seat.s1,
.table-screen:not(.hundred-screen) .seat.s4 {
  /* "My" seat is now position:fixed and anchored to the real control bar
     (see .seat.me/.seat.s0 above), so it sits lower than before and there's
     room to bring these down a bit further without crossing it. Was 65%,
     shifted up ~6 points along with s2/s3 -- see that rule's own comment. */
  top: 59%;
}

/* Pulled in from 5px -- the whole frame (and everything positioned
   relative to it -- 昵称/金币/已看/已投/etc, all absolute children of
   .seat or its own descendants) was sitting close enough to the table's
   own edge to read as crowding the phone's screen edge too. Everything
   inside rides along automatically since it's positioned relative to this
   same box. */
.table-screen:not(.hundred-screen) .seat.s1,
.table-screen:not(.hundred-screen) .seat.s2 {
  right: 10px;
}

.table-screen:not(.hundred-screen) .seat.s3,
.table-screen:not(.hundred-screen) .seat.s4 {
  left: 10px;
}

/* Texas is 6-max (one seat per side + one directly across from "me")
   instead of 三张牌's 5-max (two per side) -- reuses the exact same .seat
   shell/sizing, just with its own t1/t3/t4/t5/t7 position slots (see
   texasVisualSeatSlot) so it never collides with 三张牌's own s1-s4 rules
   above. t1/t3 down the right side, t5/t7 down the left side, t4 top-center;
   t1/t5 angle inward toward t4 instead of hugging the table's outer edge
   like t3/t7 do. (t2/t6 rules below are unused leftovers from the previous
   7-max mid-row layout -- kept since they're harmless and still
   comma-grouped with live selectors elsewhere.) */
/* A thin outline around every occupied seat frame -- without it the frame's
   own translucent dark-green fill (see the shared .seat rule) read as a
   flat, overly dark blob against the felt. .empty-seat already forces
   border:0 via a higher-specificity selector further down this file, so
   this leaves the empty placeholder slots untouched. */
.table-screen.texas-screen .seat {
  border: 1px solid rgba(255, 214, 140, .28);
}

.table-screen.texas-screen .seat.t1,
.table-screen.texas-screen .seat.t2,
.table-screen.texas-screen .seat.t3,
.table-screen.texas-screen .seat.t4,
.table-screen.texas-screen .seat.t5,
.table-screen.texas-screen .seat.t6,
.table-screen.texas-screen .seat.t7 {
  position: absolute;
}

.table-screen.texas-screen .seat.t2,
.table-screen.texas-screen .seat.t3 {
  right: 14px;
}

.table-screen.texas-screen .seat.t6,
.table-screen.texas-screen .seat.t7 {
  left: 14px;
}

/* All five opponent seats (t1-t7), the community board and the pot/side-
   pots below moved up together by ~6% of the table height -- half a
   scaled opponent-seat frame's own rendered height (measured ~78px tall
   against a ~634px-tall table at a 390-wide viewport) -- per "其他五位玩
   家的人物框再往上移动半个人物框的高度，公共牌和总底池及其他桌面显示也
   要相应的跟着移动". Every value below is still the same percentage-of-
   table-height unit the rest of this layout already uses throughout, so
   this scales the same way across different phone sizes as everything
   else here already does -- nothing here switches to a fixed px offset. */
.table-screen.texas-screen .seat.t1 {
  right: 10%;
  top: 35%;
  transform: scale(.86);
  transform-origin: top right;
}

.table-screen.texas-screen .seat.t5 {
  left: 10%;
  top: 35%;
  transform: scale(.86);
  transform-origin: top left;
}

/* Directly across from "me", where .pot used to sit -- .pot itself moves
   down below this seat (see the texas-specific .pot override further down
   this file). */
.table-screen.texas-screen .seat.t4 {
  left: 50%;
  top: 20%;
  transform: translateX(-50%) scale(.86);
  transform-origin: top center;
}

.table-screen.texas-screen .seat.t2,
.table-screen.texas-screen .seat.t6 {
  top: 49%;
}

.table-screen.texas-screen .seat.t3,
.table-screen.texas-screen .seat.t7 {
  /* Was 72% -- nudged up a bit per "左下和右下玩家再往上移动一点". */
  top: 68%;
}

/* Bring "my" seat down close to the viewport's bottom edge -- there's no
   fixed bottom control bar for texas any more (the old .texas-controls-row
   bar was replaced by the round pop-up buttons, positioned independently
   around this seat), so "close to the edge" now just means the actual
   screen edge, not a bar. Same specificity as the shared
   (.table-screen:not(.hundred-screen)) rule that otherwise positions this
   seat (4 classes each), so this only wins because it's declared later in
   the cascade. */
.table-screen.texas-screen .seat.me,
.table-screen.texas-screen .seat.s0 {
  bottom: 14px;
}

/* Narrower now that the seat is a vertical stack (avatar over cards, see
   .seat.me .seat-main above) instead of the wider side-by-side row every
   other seat still uses -- the shared 176px width above was sized for that
   row layout and left a lot of empty space on both sides once stacked. */
.table-screen.texas-screen .seat.me {
  width: 116px;
  /* Avatar is the first thing in this vertical stack now (see
     renderTexasSeat's isMe branch) and sits flush against the frame's own
     top edge -- just the shared base .seat padding, no extra top room
     reserved (that was only ever needed back when the nickname used to
     be first instead). */
  padding-top: 4px;
  /* Fixed height, not just auto -- .cards collapses to its own 38px
     min-height (see .cards) whenever seat.cardCount is 0 (ready phase, no
     hand dealt yet) instead of the real 58px card height once cards land,
     which otherwise made this whole frame visibly grow/shrink every time a
     new hand starts. Locking the height keeps it constant regardless of
     the cards row's own content. */
  height: 152px;
}

/* Right-side seats (t1-t3) sit flush against the table's right edge -- flip
   the avatar/cards order so the avatar lands on the outer (right) side and
   the cards tuck in toward the table center, facing inward like 三张牌's own
   s1/s2 right-side seats already do. */
.table-screen.texas-screen .seat.t1 .seat-main,
.table-screen.texas-screen .seat.t2 .seat-main,
.table-screen.texas-screen .seat.t3 .seat-main {
  flex-direction: row-reverse;
}

/* "My" own seat: avatar centered at the top of the frame, balance
   overlapping its bottom edge, nickname below that, hole cards below the
   whole profile block -- vertical instead of the generic side-by-side
   row every other seat uses. */
.table-screen.texas-screen .seat.me .seat-main {
  flex-direction: column;
  align-items: center;
  gap: 0;
}

/* Small gap off the balance pill overlapping the avatar above -- the
   nickname that used to sit between them is gone now (hidden entirely
   for "me", see renderTexasSeat's isMe branch), so this no longer needs
   to be pulled up to close a gap to it; just enough clearance to keep
   the cards from covering the pill. */
.table-screen.texas-screen .seat.me .cards {
  margin-top: 6px;
}

/* Wider than the shared 56px cap (sized for the old side-by-side row,
   where seat-profile only ever needed to fit the avatar) -- now that the
   nickname/avatar-row need closer to the full seat width to actually show
   fully instead of ellipsis-truncating. Left-aligned (not centered) so the
   avatar row anchors to the frame's own left edge instead of floating in
   the middle. */
.table-screen.texas-screen .seat.me .seat-profile {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  width: 100%;
  max-width: 108px;
  gap: 2px;
  /* Shifts the avatar and the balance pill riding on it (position:
     absolute relative to this box, so it moves right along with a
     transform on the box itself) up together as one unit, so it pokes
     out past the frame's top border. transform (not a negative margin)
     so seat-main's own flow height/layout is untouched -- only the
     paint position moves, the cards below don't shift to fill any gap. */
  transform: translateY(-16px);
  /* Any transform (like the one right above) makes an element start its
     own stacking context -- the avatar's own z-index:51 (see
     promoteInteractiveElements) only ranks it within THIS box's context
     from then on, it no longer competes directly with .seat.turn::after
     (the turn ring drawn on .seat itself, still plain z-index:auto) at the
     outer level. Since ::after paints last among .seat's own children by
     default, the ring was winning that comparison and visibly sweeping
     across the avatar wherever it overflows above the frame's top edge
     ("座位框外的绿色扫描光圈挡住了头像"). Giving this whole box an
     explicit z-index moves it back above the ring, same as the avatar
     already sits above everything else. */
  position: relative;
  z-index: 52;
}

/* Avatar centered in the frame (seat-profile's own align-items:center
   handles that, now that it's the lone flow item -- no more separate
   avatar-row wrapper needed) with the balance pill overlapping its
   bottom edge, same pattern opponents' seats already use -- no override
   left here to cancel that shared positioning. The pill itself is
   position:absolute (out of flow), so the nickname right after the
   avatar in source order would otherwise render starting at the same
   spot the pill visually overlaps down to -- this margin just clears
   that pill's own height so the two don't collide. */
.table-screen.texas-screen .seat.me .seat-name {
  max-width: none;
  overflow: visible;
  text-overflow: clip;
}

/* Gold (not black) opaque-in-the-middle, fade-at-both-ends band -- same
   shape as opponents' (see .seat:not(.me) > .seat-name-wrap::before). */
.table-screen.texas-screen .seat.me .seat-name-wrap::before {
  background: linear-gradient(90deg, transparent, rgba(146, 96, 10, .82) 24%, rgba(146, 96, 10, .82) 76%, transparent);
}

/* Dropped the shared .seat-name-wrap's -14px upward pull (meant for
   opponents' name to ride up over their card row) since it was instead
   pulling this straight up onto the avatar/stack above it -- +1px
   instead (just enough to still clear the balance pill, which is
   pinned to a fixed offset off the avatar -- see .seat-stack below --
   rather than riding on seat-profile's own total flow height). No
   align-self override left here -- centered, following seat-profile's
   own align-items:center same as the avatar above it, now that both are
   centered rather than the avatar being left-anchored. Padding trimmed
   from the shared rule's 1px 4px to just the horizontal component -- the
   opaque band behind the text (.seat-name-wrap::before, sized off this
   same box) was reading taller than the text actually needed. */
.table-screen.texas-screen .seat.me .seat-name-wrap {
  margin-top: -6px;
  padding-top: 0;
  padding-bottom: 0;
}

/* Pinned to a fixed px offset off the avatar's own 48px height instead
   of the shared rule's top:100% (of seat-profile's *total* flow height)
   -- with top:100%, growing the nickname's own margin-top above to add
   clearance also grew seat-profile's flow height by the same amount
   (nickname is the last in-flow child), which pushed this pill down by
   that same amount right along with it, so the two never actually
   gained any separation no matter how big that margin got. Fixing this
   to the avatar's own height breaks that loop -- overlapping 8px into
   the avatar's lower half, same idea as opponents' own -16px version
   (trimmed back from a 14px overlap so more of the avatar stays visible
   under it, and pulled down close to the nickname strip below without
   quite touching it). */
.table-screen.texas-screen .seat.me .seat-stack {
  top: 48px;
  margin-top: -8px;
  /* Same fade-at-both-ends band as opponents' own stack (see
     .seat:not(.me) .seat-stack below) instead of the shared rule's solid
     bordered pill -- matches the nickname strip's look right below it. */
  padding: 1px 10px;
  border: none;
  border-radius: 0;
  background: linear-gradient(90deg, transparent, rgba(10, 8, 6, .78) 20%, rgba(10, 8, 6, .78) 80%, transparent);
}

.table-screen.texas-screen .seat.me .seat-name-wrap .seat-name {
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
}

/* Texas-only card enlargement (jinhua's own card sizing is left alone) --
   opponents' face-down backs and "my" own hand both scaled up a step
   further from 三张牌's baseline sizes. */
.table-screen.texas-screen .cards .card {
  width: 31px;
  height: 43px;
  font-size: 19px;
}

.table-screen.texas-screen .seat.me .card,
.table-screen.texas-screen .seat.s0 .card {
  width: 42px;
  height: 58px;
  font-size: 26px;
}

.table-screen:not(.hundred-screen) .seat.empty-seat {
  width: 42px;
  height: 38px;
  border: 0;
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
}

.table-screen:not(.hundred-screen) .avatar {
  color: #e8fcff;
  background: linear-gradient(145deg, #6577e8, #27c4df);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Used to just set border-color with no border-width/style to go with
     it, so nothing actually rendered (same latent bug Texas already had,
     see .table-screen.texas-screen .avatar's own comment) -- now a real
     thin white ring around every seat's avatar, mine included. */
  border: 1px solid rgba(255, 255, 255, .85);
  box-shadow: 0 0 0 2px rgba(84, 84, 220, .38), 0 4px 10px rgba(0, 2, 29, .38);
}

.table-screen:not(.hundred-screen) .seat-name {
  color: #f2fbff;
}

.table-screen:not(.hundred-screen) .seat-stack {
  color: #8feeff;
}

.table-screen:not(.hundred-screen) .seat-bet {
  color: #c4f7ff;
  background: linear-gradient(90deg, transparent, rgba(27, 54, 140, .62) 28%, rgba(27, 54, 140, .62) 72%, transparent);
}

.table-screen:not(.hundred-screen) .card-back {
  background:
    radial-gradient(circle at 50% 50%, rgba(117, 241, 255, .28), transparent 28%),
    repeating-linear-gradient(45deg, rgba(255, 255, 255, .08) 0 2px, transparent 2px 7px),
    linear-gradient(145deg, #5939c7, #174dc1 55%, #082c78);
  border-color: rgba(216, 251, 255, .94);
  box-shadow: 0 3px 8px rgba(0, 2, 30, .35), inset 0 0 10px rgba(93, 225, 255, .24);
}

.table-screen:not(.hundred-screen) .ready-button {
  top: 65%;
  height: 42px;
  min-width: 136px;
  border-radius: 12px;
  color: #f5ffff;
  text-shadow: 0 2px 3px rgba(0, 2, 36, .5);
  background: linear-gradient(145deg, #6a57f5, #237ed7 58%, #0b4a9f);
  border-color: rgba(167, 244, 255, .72);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 7px 0 rgba(5, 18, 71, .74), 0 12px 21px rgba(0, 2, 31, .42);
}

/* Sits below the s4 opponent seat (left:5px/top:65%, see .seat.s4's own
   skin override), centered under its footprint (same left offset + width
   as that seat, plus justify-items:center below), instead of "我" own seat
   frame -- s4 always exists in this slot (even empty, see .empty-seat)
   regardless of who's actually seated there, so this stays put no matter
   who sits down or leaves. 93px is an estimate of that seat's own rendered
   height + clearance (bumped up from 78px, which was still crowding the
   seat frame above it); nudge further if it's still too tight/loose. */
/* Jinhua: directly above 弃牌 (fold) -- 弃牌 (part of the player's own
   action-button cluster, .seat-side-actions.left) is deliberately the
   anchor here, not the table/seat layout in general, per "这个聊天按钮是
   玩家自己的操作按钮，应该与自己的操作按钮为基准来确定位置". Centered over
   弃牌's own horizontal center (measured live: 弃牌 spans x:22-126 ->
   center 74, so this button's left is 74 - half its own 38px width = 55).
   top:503px widens the gap above 弃牌's own top edge (measured top:655 in a
   634px-tall/56px-offset table -> table-relative 599) to 58px -- per one
   more follow-up nudge ("还要往上移动一点点") on top of the previous 18px
   -> 38px widening; still comfortably short of the s4 opponent seat further
   up (bottom ~461 table-relative), and well clear of "我" own seat frame
   (starts at x:133) either way. */
.table-screen:not(.texas-screen):not(.hundred-screen) .side-tools {
  left: 55px;
  width: 38px;
  top: 503px;
  bottom: auto;
  justify-items: center;
}

/* Texas: upper-left of 弃牌 (the round fold button in the action cluster
   around "me" own seat) instead of below the t7 opponent -- per follow-up
   screenshot feedback ("德州的聊天按钮放在弃牌的左上方一点，随弃牌位置
   变化，不要和左下玩家人物框打架"). .texas-round-btn.fold itself uses
   position:fixed (viewport-relative, not table-relative like .seat/
   .side-tools) -- measured live at 390x844: left:max(32px,calc(50% - 96px))
   = 99 (its center, before its own translateX(-50%)) so its rendered
   box is x:73-125, and bottom:124px means top:844-124-52=668. Converted
   into .side-tools' own table-relative coordinate space (table starts at
   viewport top:56/left:0) that's x:73-125/top:612-664. left:33px/top:556px
   (nudged +8/-8 from the previous 25/564 per "还要往右上移一点点，不要太多")
   keeps this button's bottom-right corner just up-left of that box's own
   top-left corner, still comfortably clear of both "我" own seat frame
   (starts at table-relative top:622/x:137) and the t7 opponent seat above
   it (bottom ~509 at the seat.t3/t7 top:68% rule). */
.table-screen.texas-screen .side-tools {
  left: 33px;
  width: 38px;
  top: 556px;
  bottom: auto;
  justify-items: center;
}

.table-screen:not(.hundred-screen) .side-tools button {
  width: 38px;
  height: 38px;
  border-radius: 11px;
  color: #dffbff;
  background: linear-gradient(145deg, rgba(50, 62, 159, .96), rgba(9, 17, 70, .98));
  border-color: rgba(111, 217, 255, .42);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .13), 0 5px 12px rgba(0, 2, 28, .34);
}

.table-screen:not(.hundred-screen) .game-announcement-room {
  border-color: rgba(106, 194, 255, .38);
  background: linear-gradient(90deg, rgba(20, 34, 109, .9), rgba(48, 24, 116, .92), rgba(20, 34, 109, .9));
  color: #cff9ff;
}

.table-screen:not(.hundred-screen) .jinhua-controls {
  bottom: 0;
  gap: 7px;
  padding: 8px 8px max(9px, env(safe-area-inset-bottom));
}

.table-screen:not(.hundred-screen) .control-btn,
.table-screen:not(.hundred-screen) .follow-toggle {
  height: 42px;
  border-radius: 12px;
  border-color: rgba(190, 241, 255, .34);
  text-shadow: 0 2px 3px rgba(0, 2, 35, .55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), inset 0 -3px 0 rgba(2, 5, 34, .28), 0 5px 0 rgba(3, 8, 43, .72), 0 9px 15px rgba(0, 2, 28, .3);
}

.table-screen:not(.hundred-screen) .control-btn::before,
.table-screen:not(.hundred-screen) .follow-toggle::before {
  left: 8%;
  right: 8%;
  top: 3px;
  height: 8px;
  opacity: .45;
}

.table-screen:not(.hundred-screen) .control-btn.green,
.table-screen:not(.hundred-screen) .follow-toggle {
  background: linear-gradient(145deg, #32d9e9, #177abd 58%, #0b498a);
}

.table-screen:not(.hundred-screen) .control-btn.blue {
  background: linear-gradient(145deg, #58a9ff, #3157d8 58%, #1c2f95);
}

.table-screen:not(.hundred-screen) .control-btn.gold {
  background: linear-gradient(145deg, #ffb45b, #e26b35 58%, #94372d);
}

.table-screen:not(.hundred-screen) .control-btn.violet {
  background: linear-gradient(145deg, #b66aff, #663bd4 58%, #38218b);
}

.table-screen:not(.hundred-screen) .control-btn.red {
  background: linear-gradient(145deg, #ff7187, #cf365f 58%, #851f49);
}

.table-screen:not(.hundred-screen) .allin-center {
  border-radius: 13px;
  animation: none;
}

.table-screen:not(.hundred-screen) .allin-center:not(:disabled) {
  animation: none;
}

.table-screen:not(.hundred-screen) .control-btn.active {
  outline-color: rgba(117, 237, 255, .9);
  box-shadow: inset 0 0 0 2px rgba(123, 239, 255, .22), 0 0 18px rgba(77, 135, 255, .38);
}

.room-menu-popover {
  left: auto;
  right: max(10px, calc((100vw - 430px) / 2 + 10px));
  width: 184px;
  border-color: rgba(101, 193, 255, .34);
  border-radius: 13px;
  background: linear-gradient(160deg, rgba(28, 32, 105, .98), rgba(7, 10, 46, .99));
  box-shadow: 0 16px 34px rgba(0, 2, 29, .48), inset 0 1px 0 rgba(255, 255, 255, .1);
}

.room-menu-popover button {
  color: #ebfaff;
  border-bottom-color: rgba(107, 182, 255, .16);
}

.room-menu-popover span {
  color: #8feaff;
}

/* Multiplayer arena uses the same visual system as the classic room. */
.hundred-screen {
  background: transparent;
}

/* The "多人竞技" bar itself is dropped (same treatment jinhua/texas already
   got, see the "Neither jinhua nor Texas..." comment below) -- only the
   menu button survives, now floating fixed over the table background
   (see .hundred-screen .hundred-menu-btn) so the reclaimed vertical space
   goes to the announcement bar and everything below it. */
.hundred-screen > .topbar {
  z-index: 45;
  min-height: 40px;
  background: none;
  border-bottom: none;
  box-shadow: none;
}

.hundred-screen .page-title {
  display: none;
  color: #eefcff;
  font-size: 18px;
  letter-spacing: 1px;
  text-shadow: 0 0 12px rgba(82, 211, 255, .38);
}

.hundred-screen .hundred-menu-btn {
  /* Same fixed/root-stacking-context trick as jinhua/texas's .room-menu-btn
     (see .table-screen:not(.hundred-screen) .room-menu-btn) -- escapes
     #app's stacking context so it can out-rank the announcement ticker
     (appended straight to <body>, z-index 46), and stays anchored to the
     centered game column's real right edge even on viewports wider than
     the 430px column itself. */
  position: fixed;
  left: auto;
  right: calc(50vw - min(430px, 100vw) / 2 + 10px);
  bottom: auto;
  /* Dropped below the announcement ticker's band, same offset jinhua/texas
     already use -- ticker sits roughly 17.5-41.5px down, this clears it. */
  top: calc(max(8px, env(safe-area-inset-top)) + 46px);
  z-index: 47;
  display: grid;
  place-items: center;
  width: 43px;
  height: 43px;
  border-radius: 50%;
  color: #dffaff;
  font-size: 23px;
  background: linear-gradient(145deg, rgba(45, 55, 145, .96), rgba(9, 15, 62, .98));
  border: 1px solid rgba(110, 221, 255, .46);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 0 14px rgba(67, 148, 255, .22);
}

.hundred-screen .hundred-table {
  width: min(430px, 100vw);
  margin-top: 0;
  background: transparent;
}

.hundred-screen .hundred-table::after {
  inset: 8px;
  border-color: rgba(91, 178, 255, .16);
  border-radius: 20px;
  box-shadow: inset 0 0 34px rgba(50, 85, 210, .08);
}

.hundred-screen .hundred-jackpot,
.hundred-screen .hundred-recharge-btn {
  border-radius: 11px;
  color: #e7fbff;
  background: linear-gradient(145deg, rgba(74, 57, 181, .96), rgba(19, 30, 102, .98));
  border: 1px solid rgba(121, 220, 255, .48);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 6px 15px rgba(0, 2, 30, .35);
}

.hundred-screen .hundred-recharge-btn {
  font-size: 10px;
}

.hundred-screen .hundred-mascot {
  width: 64px;
  height: 64px;
  padding: 13px;
  border-radius: 20px;
  color: #b6f6ff;
  font-size: 38px;
  background: linear-gradient(145deg, rgba(73, 55, 181, .82), rgba(14, 27, 99, .88));
  border: 1px solid rgba(119, 222, 255, .48);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .13), 0 0 18px rgba(79, 80, 255, .26);
}

.hundred-screen .hundred-mascot.has-banker {
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}

.hundred-screen .hundred-banker-row {
  min-height: 32px;
  border: 1px solid rgba(94, 172, 255, .3);
  border-radius: 11px;
  background: linear-gradient(180deg, rgba(27, 41, 123, .84), rgba(8, 16, 63, .88));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08), 0 5px 13px rgba(0, 2, 29, .28);
}

.hundred-screen .hundred-banker-amount {
  color: #8eefff;
}

.hundred-screen .hundred-banker-btn {
  color: #2d210b;
  background: linear-gradient(180deg, #ffe29a, #cf973d);
  border: 1px solid rgba(255, 229, 153, .6);
}

.hundred-screen .hundred-banker-queue-badge {
  color: #efffff;
  background: linear-gradient(145deg, #6863e8, #2c4db7);
  border: 1px solid rgba(139, 229, 255, .4);
}

.hundred-screen .hundred-zone {
  border: 1px solid rgba(102, 181, 255, .44);
  background:
    radial-gradient(circle at 30% 0, rgba(83, 216, 255, .13), transparent 42%),
    linear-gradient(150deg, rgba(31, 45, 132, .82), rgba(8, 16, 62, .9));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 7px 18px rgba(0, 2, 29, .32);
}

.hundred-screen .hundred-zone-header {
  color: #c7faff;
  background: linear-gradient(90deg, rgba(63, 61, 188, .64), rgba(30, 108, 176, .66));
}

.hundred-screen .hundred-zone-label {
  color: rgba(118, 224, 255, .11);
}

.hundred-screen .hundred-zone.win {
  border-color: rgba(113, 244, 255, .78);
  box-shadow: 0 0 18px rgba(69, 203, 255, .5), 0 0 30px rgba(83, 79, 255, .28);
}

.hundred-screen .hundred-zone.lose {
  border-color: rgba(144, 117, 206, .34);
  background: linear-gradient(150deg, rgba(60, 41, 100, .7), rgba(17, 13, 47, .86));
}

.hundred-screen .hundred-phase-text,
.hundred-screen .hundred-shared-hand:not(:empty) {
  color: #a8f4ff;
  border-color: rgba(108, 226, 255, .5);
  background: linear-gradient(180deg, rgba(36, 45, 139, .86), rgba(9, 16, 65, .9));
  box-shadow: 0 5px 14px rgba(0, 2, 29, .32), 0 0 13px rgba(71, 108, 255, .18);
}

.hundred-screen .hundred-chat-btn,
.hundred-screen .hundred-players-btn {
  color: #d5ffe0;
  background: linear-gradient(#14913c, #04471f);
  border-color: rgba(255, 255, 255, .18);
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, .25), 0 6px 12px rgba(0, 0, 0, .28);
}

.hundred-screen .chips {
  bottom: 0;
  border-radius: 0;
  background: linear-gradient(180deg, rgba(24, 27, 92, .92), rgba(5, 8, 39, .99));
  border-top: 1px solid rgba(93, 158, 255, .34);
  box-shadow: 0 -12px 28px rgba(0, 2, 29, .38);
}

.hundred-screen .chips::before,
.hundred-screen .chips::after,
.hundred-screen .hundred-player-strip::before {
  background: transparent;
  border: 0;
  box-shadow: none;
}

.hundred-screen .hundred-player-strip {
  /* Matches the base .hundred-player-strip rule's own value -- this
     -screen-scoped rule has higher specificity and always wins, so it has
     to be kept in sync by hand whenever that one changes. */
  margin-top: -40px;
  /* padding/border-radius/background/border removed -- this (and the other
     .hundred-screen .hundred-player-strip rule further down) was the ACTUAL
     source of the rectangle behind the avatar, not the ::before layers
     already hidden above. Both of those were only ever cosmetic overlays;
     this one draws a real background+border directly on the element itself
     ("头像框下层的矩形框去掉...还是一样，头像框还在" -- turned out to be
     this, not what was fixed earlier). */
  background: transparent;
  border: 0;
}

.hundred-screen .hundred-my-avatar {
  border-color: #87efff;
}

.hundred-screen .chip {
  /* Shrunk from 48x44 -- see .chips's own comment on why (the row's
     content is wider than a narrow phone viewport and was running under
     the screen's rounded corner on the right). This -screen-scoped rule
     is the one that actually wins (higher specificity than the plain
     .chip rule above), so the size has to change here. */
  width: 40px;
  height: 40px;
  border-radius: 13px;
  background: linear-gradient(145deg, color-mix(in srgb, var(--chip-color) 70%, #5368ff 30%), color-mix(in srgb, var(--chip-color) 42%, #16184f 58%));
  border: 1px solid rgba(212, 248, 255, .45);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), inset 0 -3px 0 rgba(0, 2, 30, .22), 0 5px 10px rgba(0, 2, 29, .3);
}

.hundred-screen .chip.active {
  outline: 3px solid #86efff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .24), 0 0 16px rgba(78, 190, 255, .42);
}

.hundred-screen .hundred-repeat-btn {
  color: #edfcff;
  background: linear-gradient(145deg, #675ee2, #2d48ad);
  border-color: rgba(137, 228, 255, .42);
}

/* Lobby mode-card layout v2: large game modules and a horizontal stage
   carousel, replacing the old two-column utility-card grid. */
.lobby-screen .game-switcher {
  left: 16px;
  right: 16px;
  top: 14px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  width: auto;
  padding: 0;
}

.lobby-screen .game-switch-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: auto;
  height: 50px;
  border-radius: 14px;
}

.lobby-screen .game-switch-btn .switch-icon {
  font-size: 22px;
}

.lobby-screen .game-switch-btn .switch-title {
  font-size: 13px;
  letter-spacing: .4px;
}

.lobby-screen .hundred-circle {
  left: 16px;
  right: 16px;
  top: 79px;
  justify-content: flex-start;
  width: auto;
  height: 116px;
  padding: 18px 22px;
  border-radius: 19px;
  text-align: left;
}

.lobby-screen .hundred-circle::after {
  inset: 6px;
  border-radius: 14px;
}

.lobby-screen .hundred-icon {
  width: 62px;
  height: 62px;
  border-radius: 18px;
  font-size: 37px;
}

.lobby-screen .hundred-text {
  gap: 6px;
}

.lobby-screen .hundred-circle .entry-title {
  font-size: 22px;
}

.lobby-screen .hundred-circle .entry-desc {
  font-size: 12px;
}

.lobby-screen .game-tier-stage {
  left: 0;
  right: 0;
  top: 215px;
  display: flex;
  align-items: stretch;
  gap: 12px;
  width: auto;
  padding: 0 18px 20px;
  overflow-x: auto;
  overflow-y: visible;
  pointer-events: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 18px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
}

.lobby-screen .game-tier-stage::-webkit-scrollbar {
  display: none;
}

.lobby-screen .game-entry-tier {
  flex: 0 0 174px;
  display: grid;
  grid-template-rows: 92px auto 1fr;
  align-content: start;
  place-items: center;
  gap: 10px;
  width: 174px;
  height: 238px;
  padding: 20px 14px 16px;
  border-radius: 19px;
  scroll-snap-align: start;
  contain: layout paint;
}

.lobby-screen .game-entry-tier::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 11px;
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(90deg, transparent, var(--tier-accent), transparent);
  opacity: .62;
  box-shadow: 0 0 12px var(--tier-accent);
  pointer-events: none;
}

.lobby-screen .game-entry-tier .tier-icon {
  width: 78px;
  height: 78px;
  font-size: 43px;
  border-width: 2px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .2),
    0 10px 24px rgba(0, 2, 31, .35),
    0 0 24px color-mix(in srgb, var(--tier-accent) 34%, transparent);
}

.lobby-screen .game-entry-tier strong {
  font-size: 19px;
  letter-spacing: .8px;
}

.lobby-screen .game-entry-tier .tier-sub {
  align-self: start;
  max-width: 145px;
  font-size: 11px;
  line-height: 1.45;
  text-align: center;
}

.lobby-screen .game-entry-tier.accent-tycoon {
  background:
    radial-gradient(circle at 50% 24%, rgba(167, 120, 255, .34), transparent 35%),
    linear-gradient(155deg, rgba(91, 45, 178, .97), rgba(24, 20, 83, .98) 68%);
}

.lobby-screen .game-entry-tier.accent-expert {
  background:
    radial-gradient(circle at 50% 24%, rgba(255, 114, 143, .32), transparent 35%),
    linear-gradient(155deg, rgba(151, 44, 94, .97), rgba(45, 15, 66, .98) 68%);
}

.lobby-screen .game-entry-tier.accent-advanced {
  background:
    radial-gradient(circle at 50% 24%, rgba(72, 228, 209, .3), transparent 35%),
    linear-gradient(155deg, rgba(20, 117, 138, .97), rgba(8, 35, 75, .98) 68%);
}

.lobby-screen .game-entry-tier.accent-newbie {
  background:
    radial-gradient(circle at 50% 24%, rgba(74, 168, 255, .32), transparent 35%),
    linear-gradient(155deg, rgba(35, 91, 180, .97), rgba(12, 27, 82, .98) 68%);
}

.lobby-screen .game-entry-tier.accent-happy {
  background:
    radial-gradient(circle at 50% 24%, rgba(255, 191, 94, .32), transparent 35%),
    linear-gradient(155deg, rgba(160, 84, 39, .97), rgba(57, 26, 54, .98) 68%);
}

.lobby-screen .game-entry-tier.tier-soon {
  opacity: .68;
}

.lobby-screen .bottombar {
  bottom: 0;
}

@media (max-height: 760px) {
  .lobby-screen .hundred-circle {
    top: 72px;
    height: 100px;
  }

  .lobby-screen .game-tier-stage {
    top: 188px;
  }

  .lobby-screen .game-entry-tier {
    height: 215px;
    grid-template-rows: 78px auto 1fr;
  }

  .lobby-screen .game-entry-tier .tier-icon {
    width: 66px;
    height: 66px;
    font-size: 36px;
  }
}

/* Docked right beside the avatar instead of beside Settings -- "把金币和
   魅力按钮靠近头像，横向排列不要打架". Now that both pills are just an
   icon plus a number (no more "游戏豆"/"荣誉" text label under each), they're
   narrow enough to sit right after the 46px avatar (14px left padding +
   46px avatar + a small gap) without reaching the room-tier content or the
   Settings button on the right. */
.lobby-screen .lobby-stats {
  position: absolute;
  left: 68px;
  bottom: 9px;
  max-width: none;
}

@media (max-width: 370px) {
  .lobby-screen .lobby-stats {
    left: 64px;
    gap: 5px;
  }

  .lobby-screen .lobby-stats span {
    padding-inline: 7px;
  }

  .lobby-screen .lobby-stats strong {
    font-size: 13px;
  }

  .lobby-screen .lobby-stats em {
    font-size: 11px;
  }
}

/* Keep every Classic Three room discoverable at a glance. The horizontal
   carousel hid valid rooms off-screen, so the same large-card visual system
   now uses a compact two-column mode board with the final odd card spanning
   the full row. */
.lobby-screen .game-tier-stage {
  left: 16px;
  right: 16px;
  top: 210px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  width: auto;
  padding: 0 0 82px;
  overflow: visible;
  mask-image: none;
  -webkit-mask-image: none;
  scroll-snap-type: none;
}

.lobby-screen .game-entry-tier {
  width: auto;
  height: 112px;
  min-width: 0;
  padding: 11px 10px 10px;
  grid-template-rows: 46px auto 1fr;
  gap: 4px;
  border-radius: 16px;
  scroll-snap-align: none;
}

.lobby-screen .game-entry-tier .tier-icon {
  width: 46px;
  height: 46px;
  font-size: 25px;
}

.lobby-screen .game-entry-tier strong {
  font-size: 14px;
}

.lobby-screen .game-entry-tier .tier-sub {
  max-width: 100%;
  color: rgba(226, 242, 255, .9);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.35;
  white-space: normal;
}

.lobby-screen .game-entry-tier:last-child:nth-child(odd) {
  grid-column: 1 / -1;
  grid-template-columns: 58px 1fr 58px;
  grid-template-rows: auto auto;
  column-gap: 12px;
  height: 86px;
  padding: 10px 20px;
  text-align: center;
}

.lobby-screen .game-entry-tier:last-child:nth-child(odd) .tier-icon {
  grid-column: 1;
  grid-row: 1 / 3;
}

.lobby-screen .game-entry-tier:last-child:nth-child(odd) strong {
  grid-column: 2;
  grid-row: 1;
  justify-self: center;
  font-size: 18px;
  text-align: center;
}

.lobby-screen .game-entry-tier:last-child:nth-child(odd) .tier-sub {
  grid-column: 2;
  grid-row: 2;
  justify-self: center;
  font-size: 12px;
  text-align: center;
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage {
    top: 186px;
    gap: 8px;
  }

  .lobby-screen .game-entry-tier {
    height: 101px;
    grid-template-rows: 40px auto 1fr;
  }

  .lobby-screen .game-entry-tier .tier-icon {
    width: 40px;
    height: 40px;
    font-size: 22px;
  }

  .lobby-screen .game-entry-tier:last-child:nth-child(odd) {
    height: 76px;
  }
}

.lobby-screen .hundred-circle .entry-desc {
  color: rgba(224, 248, 255, .9);
  font-size: 13px;
  font-weight: 700;
}

.lobby-screen .lobby-stats em {
  font-size: 11px;
}

/* Room palette v2: keep the arena artwork cool, but move interactive modules
   to graphite surfaces with warm and multicolor game accents. Jinhua/Texas's
   topbar bar was intentionally dropped (see the "Neither jinhua nor Texas..."
   comment above) -- don't let this later same-specificity rule bring its
   background/border back. Hundred's own topbar bar is now dropped the same
   way (see the updated comment above its base rule) so it needs the same
   treatment here too -- otherwise this later same-specificity rule would
   repaint the bar this v2 palette pass was never meant to touch. */
.hundred-screen > .topbar {
  background: none;
  border-bottom: none;
  box-shadow: none;
}

.table-screen:not(.hundred-screen) .room-menu-btn,
.hundred-screen .hundred-menu-btn {
  color: #fff4e8;
  background: linear-gradient(145deg, rgba(92, 57, 76, .98), rgba(29, 25, 38, .99));
  border-color: rgba(255, 184, 125, .44);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), 0 0 14px rgba(222, 104, 117, .16);
}

.table-screen:not(.hundred-screen) .table-hud {
  color: rgba(255, 239, 222, .9);
  background: linear-gradient(180deg, rgba(53, 44, 54, .93), rgba(22, 20, 27, .95));
  border-color: rgba(225, 163, 115, .32);
}

.table-screen:not(.hundred-screen) .pot {
  color: #ffd79a;
  text-shadow: 0 0 10px rgba(255, 164, 66, .35);
  background: linear-gradient(180deg, rgba(96, 58, 43, .88), rgba(37, 27, 27, .92));
  border-color: rgba(255, 183, 105, .48);
}

.table-screen:not(.hundred-screen) .pot-allin-cap {
  color: rgba(255, 215, 154, .82);
  background: rgba(37, 27, 27, .5);
  border-color: rgba(255, 183, 105, .28);
}

.table-screen:not(.hundred-screen) .seat {
  border-color: rgba(212, 167, 140, .32);
  background:
    radial-gradient(circle at 20% 0, rgba(255, 185, 132, .1), transparent 44%),
    linear-gradient(155deg, rgba(54, 47, 55, .94), rgba(20, 19, 25, .96));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .09), 0 7px 16px rgba(0, 0, 0, .34);
}

.table-screen:not(.hundred-screen) .seat.me,
.table-screen:not(.hundred-screen) .seat.s0 {
  border-color: rgba(255, 181, 116, .44);
  background:
    radial-gradient(circle at 20% 0, rgba(255, 177, 107, .12), transparent 45%),
    linear-gradient(155deg, rgba(66, 48, 56, .96), rgba(23, 20, 28, .98));
}

/* Reassert texas' own brighter gold border over this shared warm-brown
   seat theme -- the theme's border-color (above) has the same selector
   specificity, so without this coming later it silently won and the
   earlier gold .table-screen.texas-screen .seat border ended up almost
   invisible against the seat's own dark brown fill. */
.table-screen.texas-screen .seat {
  border-color: rgba(255, 200, 90, .7);
}

.table-screen.texas-screen .seat.me,
.table-screen.texas-screen .seat.s0 {
  border-color: rgba(255, 200, 90, .85);
}

.table-screen:not(.hundred-screen) .seat.turn {
  border-color: rgba(108, 237, 194, .9);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 0 0 2px rgba(49, 176, 132, .38), 0 0 22px rgba(65, 224, 166, .32);
}

.table-screen:not(.hundred-screen) .avatar {
  background: linear-gradient(145deg, #d87967, #7b4a85);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  border-color: rgba(255, 230, 208, .88);
  box-shadow: 0 0 0 2px rgba(148, 75, 102, .32), 0 4px 10px rgba(0, 0, 0, .4);
}

.table-screen:not(.hundred-screen) .seat-stack,
.table-screen:not(.hundred-screen) .seat-bet {
  color: #ffd6a0;
}

.table-screen:not(.hundred-screen) .seat-bet {
  background: linear-gradient(90deg, transparent, rgba(115, 66, 57, .56) 28%, rgba(115, 66, 57, .56) 72%, transparent);
}

/* This is the rule that actually wins (see the earlier, dead
   .table-screen:not(.hundred-screen) .card-back block above -- same
   selector/specificity, this one just comes later in source order) --
   background used to be 3 stacked gradients including a
   repeating-linear-gradient tiling a diagonal stripe across the whole
   card, recomputed live same as hundred's card-back was before that got
   swapped for a cached bitmap (see .hundred-screen .hundred-shared-cards
   .card-back's own comment). Same fix here: one cached image baking in
   the stripe texture, the warm corner glow, and the ::before pseudo's
   inset frame (which is why ::before is neutralized just below instead of
   left drawing a redundant blurred border on top of the bitmap). */
.table-screen:not(.hundred-screen) .card-back {
  background: url("./assets/card-back-classic.png") center / cover;
  border-color: rgba(255, 234, 216, .94);
  box-shadow: 0 3px 8px rgba(0, 0, 0, .38), inset 0 0 10px rgba(255, 174, 146, .18);
}

.table-screen:not(.hundred-screen) .card-back::before {
  content: none;
}

.table-screen:not(.hundred-screen) .ready-button {
  background: linear-gradient(145deg, #ffb25f, #e36d47 58%, #963b43);
  border-color: rgba(255, 232, 195, .72);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 7px 0 rgba(88, 35, 35, .74), 0 12px 21px rgba(0, 0, 0, .42);
}

.table-screen:not(.hundred-screen) .side-tools button {
  color: #fff2e5;
  background: linear-gradient(145deg, rgba(91, 58, 72, .97), rgba(29, 25, 35, .99));
  border-color: rgba(255, 184, 127, .38);
}

.table-screen:not(.hundred-screen) .control-btn.green,
.table-screen:not(.hundred-screen) .follow-toggle {
  background: linear-gradient(145deg, #52d7aa, #268c73 58%, #185846);
}

.table-screen:not(.hundred-screen) .control-btn.blue {
  background: linear-gradient(145deg, #d378d2, #8d4a9f 58%, #593368);
}

.table-screen:not(.hundred-screen) .control-btn.gold {
  background: linear-gradient(145deg, #ffbf69, #df7b3f 58%, #914533);
}

.room-menu-popover {
  border-color: rgba(234, 159, 116, .32);
  background: linear-gradient(160deg, rgba(59, 45, 58, .99), rgba(19, 18, 25, .995));
}

.room-menu-popover span {
  color: #ffbf91;
}

.hundred-screen .hundred-jackpot {
  color: #fff2d6;
  background: linear-gradient(145deg, rgba(156, 85, 43, .97), rgba(65, 38, 35, .99));
  border-color: rgba(255, 191, 106, .5);
}

.hundred-screen .hundred-recharge-btn {
  color: #f5fff9;
  background: linear-gradient(145deg, rgba(45, 145, 111, .97), rgba(24, 66, 59, .99));
  border-color: rgba(112, 233, 184, .48);
}

.hundred-screen .hundred-banker-row {
  border-color: rgba(226, 157, 116, .28);
  background: linear-gradient(180deg, rgba(58, 45, 54, .92), rgba(20, 19, 25, .96));
}

/* .hundred-player-strip used to share the rule above (same background/
   border-color as .hundred-banker-row) -- split out and neutralized here,
   same reasoning as the other .hundred-screen .hundred-player-strip rule
   above ("头像框下层的矩形框去掉"). */
.hundred-screen .hundred-player-strip {
  border-color: transparent;
  background: transparent;
}

.hundred-screen .hundred-banker-amount {
  color: #ffd39a;
}

/* Keep the banker nameplate inside the table's upper arc. The row itself
   remains full-width solely as the positioning anchor for the queue button,
   so that button does not shift when the visible nameplate is narrowed. */
.hundred-screen .hundred-banker-row {
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.hundred-screen .hundred-banker-info {
  width: min(136px, calc(100% - 168px));
  min-height: 30px;
  gap: 6px;
  padding: 0 8px;
  border: 1px solid rgba(236, 188, 121, .34);
  border-radius: 999px;
  background:
    linear-gradient(180deg, rgba(255, 232, 185, .13), transparent 42%),
    linear-gradient(180deg, rgba(53, 43, 49, .96), rgba(17, 18, 23, .97));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12), 0 4px 11px rgba(0, 0, 0, .3);
}

.hundred-screen .hundred-banker-info .hundred-banker-name {
  flex: 0 1 auto;
  max-width: 76px;
}

.hundred-screen .hundred-zone {
  border-color: rgba(255, 255, 255, .22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 7px 18px rgba(0, 0, 0, .34);
}

.hundred-screen .hundred-zone[data-bet-area="east"] {
  background: linear-gradient(150deg, rgba(31, 132, 112, .88), rgba(18, 47, 45, .94));
}

.hundred-screen .hundred-zone[data-bet-area="south"] {
  background: linear-gradient(150deg, rgba(167, 65, 79, .88), rgba(65, 29, 40, .94));
}

.hundred-screen .hundred-zone[data-bet-area="west"] {
  background: linear-gradient(150deg, rgba(177, 105, 50, .9), rgba(69, 42, 31, .95));
}

.hundred-screen .hundred-zone[data-bet-area="north"] {
  background: linear-gradient(150deg, rgba(111, 70, 157, .9), rgba(48, 31, 71, .95));
}

.hundred-screen .hundred-zone-header {
  color: #fff7ed;
  background: rgba(25, 22, 28, .36);
}

.hundred-screen .hundred-zone-label {
  color: rgba(255, 255, 255, .11);
}

.hundred-screen .hundred-phase-text,
.hundred-screen .hundred-shared-hand:not(:empty) {
  color: #ffe0b2;
  border-color: rgba(246, 170, 112, .42);
  background: linear-gradient(180deg, rgba(76, 50, 52, .9), rgba(27, 22, 29, .94));
}

.hundred-screen .chips {
  border-top-color: rgba(217, 136, 110, .28);
  background: linear-gradient(180deg, rgba(48, 39, 50, .95), rgba(15, 14, 20, .995));
  box-shadow: 0 -12px 28px rgba(0, 0, 0, .42);
}

.hundred-screen .hundred-my-avatar {
  border-color: #ffbf8c;
}

.hundred-screen .chip.active {
  outline-color: #ffb66e;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .24), 0 0 16px rgba(255, 143, 78, .38);
}

.hundred-screen .hundred-repeat-btn {
  color: #fff3e6;
  background: linear-gradient(145deg, #a55d75, #65394f);
  border-color: rgba(255, 181, 142, .38);
}

/* Compact profile editor: fixed outer size, horizontal identity summary and
   an independently scrolling avatar library. */
.profile-popover {
  width: min(340px, calc(100vw - 20px));
}

.profile-panel {
  display: flex;
  flex-direction: column;
  height: min(540px, calc(100vh - 84px));
  height: min(540px, calc(100dvh - 84px));
  overflow: hidden;
}

.profile-panel-head {
  flex: 0 0 auto;
}

.profile-summary {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: 86px minmax(0, 1fr);
  align-items: start;
  gap: 12px;
  padding: 10px 0 7px;
  border-bottom: 1px solid rgba(99, 208, 236, .28);
}

.profile-avatar-column {
  min-width: 0;
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 6px;
}

.profile-avatar-column .player-card-avatar {
  width: 72px;
  height: 72px;
  margin: 0;
}

.profile-avatar-column .profile-avatar-upload {
  margin: 0;
}

.profile-avatar-column .profile-avatar-upload-btn {
  padding: 5px 10px;
  font-size: 11px;
  white-space: nowrap;
}

.profile-avatar-column .profile-pending {
  margin: 0;
  font-size: 10px;
  text-align: center;
}

.profile-summary-fields {
  min-width: 0;
  display: grid;
  align-content: start;
}

.profile-summary-fields .profile-id-row,
.profile-summary-fields .profile-edit-row {
  grid-template-columns: 54px minmax(0, 1fr);
  min-height: 34px;
  gap: 6px;
  padding: 4px 0;
  font-size: 12px;
}

.profile-summary-fields .profile-id-row strong {
  overflow: hidden;
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-summary-fields .profile-edit-row input {
  height: 28px;
  padding-inline: 7px;
  font-size: 12px;
}

.profile-summary-fields .profile-edit-hint {
  margin: 2px 0 3px 60px;
  font-size: 10px;
  line-height: 1.25;
}

.profile-summary-fields .profile-pending {
  margin: 0 0 2px 60px;
  font-size: 10px;
  text-align: left;
}

.profile-panel > .profile-gender-row {
  flex: 0 0 auto;
  min-height: 40px;
  padding-block: 5px;
}

.profile-avatar-picker {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.profile-avatar-picker .profile-avatar-tabs {
  flex: 0 0 auto;
  padding-top: 7px;
}

.profile-avatar-picker .profile-avatar-grid {
  flex: 1 1 auto;
  min-height: 0;
  align-content: start;
  overflow-x: hidden;
  overflow-y: auto;
  padding: 9px 2px 10px 0;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.profile-panel .profile-save {
  flex: 0 0 auto;
  margin-top: 8px;
}

.profile-panel .profile-message {
  flex: 0 0 auto;
}

/* Lobby contrast v3: cool arena backdrop, warm saturated mode modules. */
.lobby-screen .game-switch-btn,
.lobby-screen .game-switch-btn.accent-jinhua,
.lobby-screen .game-switch-btn.accent-texas {
  color: rgba(246, 239, 244, .82);
  border-color: rgba(214, 171, 190, .34);
  background:
    radial-gradient(circle at 28% 12%, rgba(255, 196, 159, .1), transparent 42%),
    linear-gradient(150deg, rgba(57, 42, 59, .96), rgba(21, 19, 31, .98));
}

.lobby-screen .game-switch-btn.active {
  color: #fff9ec;
  border-color: rgba(255, 214, 133, .92);
  background:
    radial-gradient(circle at 24% 8%, rgba(255, 225, 159, .32), transparent 43%),
    linear-gradient(145deg, rgba(213, 92, 80, .98), rgba(151, 57, 115, .98) 55%, rgba(71, 35, 91, .99));
  box-shadow:
    inset 0 0 0 2px rgba(255, 221, 149, .13),
    inset 0 1px 0 rgba(255, 255, 255, .25),
    0 0 0 2px rgba(255, 172, 103, .15),
    0 9px 22px rgba(0, 0, 0, .4),
    0 0 20px rgba(255, 104, 115, .24);
}

.lobby-screen .hundred-circle {
  border-color: rgba(255, 222, 148, .96);
  background:
    radial-gradient(circle at 24% 10%, rgba(255, 225, 148, .34), transparent 34%),
    linear-gradient(135deg, rgba(237, 117, 62, .98), rgba(190, 55, 111, .98) 52%, rgba(79, 40, 139, .99));
  box-shadow:
    inset 0 0 0 2px rgba(255, 224, 153, .16),
    inset 0 1px 0 rgba(255, 255, 255, .48),
    0 0 0 2px rgba(255, 132, 101, .19),
    0 12px 27px rgba(0, 0, 0, .42),
    0 0 28px rgba(255, 91, 116, .22);
}

.lobby-screen .hundred-icon {
  color: #fff8df;
  background: linear-gradient(145deg, rgba(255, 174, 72, .82), rgba(153, 49, 102, .86));
  border-color: rgba(255, 235, 185, .62);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .24), 0 7px 17px rgba(89, 22, 68, .34);
}

.lobby-screen .game-entry-tier {
  height: 128px;
  padding: 10px 10px 33px;
  grid-template-rows: 48px auto 1fr;
  border-width: 2px;
  border-color: color-mix(in srgb, var(--tier-accent) 82%, #fff 18%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .22),
    inset 0 -4px 0 color-mix(in srgb, var(--tier-accent) 32%, transparent),
    0 10px 22px rgba(0, 0, 0, .42),
    0 0 18px color-mix(in srgb, var(--tier-accent) 21%, transparent);
}

.lobby-screen .game-entry-tier::after {
  display: none;
}

.lobby-screen .game-entry-tier:not(.accent-happy)::after {
  display: none;
}

.lobby-screen .game-entry-tier:not(.accent-happy) strong {
  position: absolute;
  left: 32px;
  right: 32px;
  bottom: 14px;
  z-index: 4;
  display: grid;
  place-items: center;
  min-height: 23px;
  padding: 0;
  border: 0;
  border-radius: 7px;
  color: #ffe8a8;
  font-size: 17px;
  line-height: 1.1;
  text-align: center;
  -webkit-text-fill-color: #ffe8a8;
  text-shadow: 0 2px 0 rgba(44, 20, 24, .9), 0 0 6px #fff4c1, 0 0 13px var(--tier-accent);
  background: none;
  box-shadow: none;
}

.lobby-screen .game-entry-tier:not(.accent-happy) .tier-sub {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 10px;
  z-index: 4;
  display: grid;
  place-items: center;
  min-height: 18px;
  padding: 0 6px;
  border: 0;
  border-radius: 6px;
  color: #44394b;
  font-size: 12px;
  font-weight: 800;
  line-height: 1.15;
  text-align: center;
  text-shadow: none;
  background: none;
  box-shadow: none;
}

.lobby-screen .game-entry-tier .tier-icon {
  width: 50px;
  height: 50px;
  font-size: 27px;
  color: #fffaf2;
  background: linear-gradient(145deg, color-mix(in srgb, var(--tier-accent) 82%, #fff 18%), color-mix(in srgb, var(--tier-accent) 62%, #2a1830 38%));
  border-color: rgba(255, 245, 226, .72);
}

.lobby-screen .game-entry-tier strong {
  color: #fffaf4;
  font-size: 15px;
}

.lobby-screen .game-entry-tier.accent-tycoon {
  --tier-accent: #f0a93f;
  background:
    radial-gradient(circle at 50% 22%, rgba(255, 204, 105, .34), transparent 36%),
    linear-gradient(150deg, rgba(159, 85, 37, .98), rgba(73, 40, 42, .99) 68%);
}

.lobby-screen .game-entry-tier.accent-expert {
  --tier-accent: #f05f7c;
  background:
    radial-gradient(circle at 50% 22%, rgba(255, 132, 152, .34), transparent 36%),
    linear-gradient(150deg, rgba(158, 48, 76, .98), rgba(65, 29, 50, .99) 68%);
}

.lobby-screen .game-entry-tier.accent-advanced {
  --tier-accent: #3dc995;
  background:
    radial-gradient(circle at 50% 22%, rgba(91, 230, 177, .32), transparent 36%),
    linear-gradient(150deg, rgba(27, 126, 99, .98), rgba(22, 55, 52, .99) 68%);
}

.lobby-screen .game-entry-tier.accent-newbie {
  --tier-accent: #9872e8;
  background:
    radial-gradient(circle at 50% 22%, rgba(181, 145, 255, .32), transparent 36%),
    linear-gradient(150deg, rgba(99, 64, 159, .98), rgba(45, 33, 77, .99) 68%);
}

.lobby-screen .game-entry-tier.accent-happy {
  --tier-accent: #f17b55;
  height: 100px;
  padding-bottom: 30px;
  background:
    radial-gradient(circle at 34% 14%, rgba(255, 174, 106, .32), transparent 38%),
    linear-gradient(135deg, rgba(190, 91, 49, .98), rgba(140, 54, 79, .98) 58%, rgba(67, 34, 64, .99));
}

@media (max-height: 760px) {
  .lobby-screen .game-entry-tier {
    height: 116px;
    padding-bottom: 29px;
  }

  .lobby-screen .game-entry-tier .tier-icon {
    width: 44px;
    height: 44px;
    font-size: 24px;
  }

  .lobby-screen .game-entry-tier.accent-happy {
    height: 90px;
  }
}

/* Thumb-first lobby composition: game-family tabs sit above the bottom nav,
   while illustrated room cards replace the old text-heavy entry strips. */
.lobby-screen .game-switcher {
  top: auto;
  bottom: 76px;
}

.lobby-screen .hundred-circle {
  top: 14px;
}

.lobby-screen .game-tier-stage {
  top: 145px;
}

.lobby-screen .game-entry-tier {
  height: 120px;
  padding: 8px 10px;
  grid-template-rows: 50px auto 1fr;
  gap: 3px;
}

.lobby-screen .game-entry-tier.accent-happy {
  height: 92px;
  padding: 9px 20px;
}

.lobby-screen .tier-art {
  position: absolute;
  right: -10px;
  top: 10px;
  z-index: 0;
  width: 92px;
  height: 92px;
  color: var(--tier-accent);
  opacity: .14;
  transform: rotate(-11deg);
  filter: drop-shadow(0 0 16px var(--tier-accent));
  pointer-events: none;
}

.lobby-screen .tier-art .lobby-ui-icon {
  width: 100%;
  height: 100%;
  stroke-width: 1.2;
}

.lobby-screen .game-entry-tier.accent-happy .tier-art {
  right: 18px;
  top: 2px;
  width: 86px;
  height: 86px;
  opacity: .12;
}

.lobby-screen .tier-motion {
  position: absolute;
  overflow: hidden;
  inset: 3px;
  z-index: 2;
  width: auto;
  height: auto;
  pointer-events: none;
  filter: drop-shadow(0 7px 10px rgba(0, 0, 0, .28));
}

.lobby-screen .tier-motion::before {
  content: "";
  position: absolute;
  right: 0;
  bottom: 5px;
  width: 122px;
  height: 22px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--tier-accent) 44%, transparent);
  filter: blur(8px);
  transform: scale(.82);
  /* Static now -- animating scale/opacity on a blurred element forces the
     browser to re-rasterize the blur every frame, and this glow (plus the
     character sway and spark twinkle below) ran continuously on every
     lobby tier card the whole time the lobby screen was open, not just
     during some specific moment. */
  animation: none;
}

.lobby-screen .tier-motion img {
  position: absolute;
  left: auto;
  right: -4px;
  top: -8px;
  bottom: auto;
  width: auto;
  height: 190px;
  max-width: 94%;
  object-fit: contain;
  object-position: top center;
  opacity: .86;
  transform: none;
  transform-origin: 50% 90%;
  animation: none;
}

.lobby-screen .motion-spark {
  position: absolute;
  z-index: 4;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #fff4bd;
  box-shadow: 0 0 8px #fff1a2, 0 0 15px var(--tier-accent);
  animation: none;
}

.lobby-screen .motion-spark-a {
  left: 6px;
  top: 7px;
}

.lobby-screen .motion-spark-b {
  right: 5px;
  top: 19px;
  animation-delay: -.9s;
}

.lobby-screen .tier-motion-expert img {
  height: 194px;
}

.lobby-screen .tier-motion-advanced img {
  height: 178px;
}

.lobby-screen .tier-motion-newbie img {
  height: 154px;
}

.lobby-screen .tier-motion-happy img {
  right: -7px;
  top: -30px;
  height: 190px;
}

.lobby-screen .game-entry-tier.accent-happy .tier-motion {
  inset: 3px;
  width: auto;
  transform: none;
}

.lobby-screen .game-entry-tier.accent-happy:last-child:nth-child(odd) strong {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 25px;
  z-index: 4;
  display: block;
  color: #fffaf1;
  font-size: 18px;
  line-height: 1.15;
  text-align: center;
  text-shadow: 0 2px 5px rgba(0, 0, 0, .9), 0 0 10px rgba(0, 0, 0, .52);
}

.lobby-screen .game-entry-tier.accent-happy:last-child:nth-child(odd) .tier-sub {
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 8px;
  z-index: 4;
  display: block;
  max-width: none;
  color: rgba(255, 247, 231, .96);
  font-size: 12px;
  font-weight: 800;
  line-height: 1.1;
  text-align: center;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .92);
  white-space: nowrap;
}

.lobby-screen .game-entry-tier strong,
.lobby-screen .game-entry-tier .tier-sub {
  z-index: 4;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .92), 0 0 9px rgba(0, 0, 0, .6);
}

.lobby-screen .game-entry-tier::before {
  z-index: 1;
  background:
    radial-gradient(circle at 86% 16%, color-mix(in srgb, var(--tier-accent) 22%, transparent), transparent 34%),
    repeating-linear-gradient(135deg, rgba(255, 255, 255, .035) 0 1px, transparent 1px 12px),
    linear-gradient(125deg, rgba(255, 255, 255, .12), transparent 38%);
}

.lobby-screen .game-entry-tier::after {
  display: none;
}

/* Place the multiplayer entry just under the room-card area (near 富豪场),
   with the four utility buttons directly above it instead of at the screen
   bottom. The game-family switcher keeps the lower slot. */
.lobby-screen .bottombar {
  top: 160px;
  bottom: auto;
}

.lobby-screen .hundred-circle {
  top: 232px;
  bottom: auto;
}

.lobby-screen .game-tier-stage {
  top: 90px;
}

@media (max-height: 650px) {
  .lobby-screen .bottombar {
    top: 150px;
  }

  .lobby-screen .hundred-circle {
    top: 220px;
    height: 98px;
    padding: 14px 18px;
  }

  .lobby-screen .game-tier-stage {
    top: 90px;
  }
}

@keyframes tierCharacterFloat {
  0%, 100% { transform: translateY(2px) rotate(-1.5deg); }
  50% { transform: translateY(-5px) rotate(1.5deg); }
}

@keyframes tierHeroineSway {
  0%, 100% { transform: translateY(2px) rotate(-2deg); }
  50% { transform: translateY(-4px) rotate(2deg); }
}

@keyframes tierMascotBounce {
  0%, 100% { transform: translateY(3px) scale(1); }
  46% { transform: translateY(-7px) scale(1.04); }
  58% { transform: translateY(-5px) scale(.98); }
}

@keyframes tierChestPop {
  0%, 100% { transform: translateY(3px) scale(.96); }
  50% { transform: translateY(-3px) scale(1.05); }
}

@keyframes tierPartyJump {
  0%, 100% { transform: translateY(4px) rotate(-3deg); }
  45% { transform: translateY(-8px) rotate(3deg); }
  58% { transform: translateY(-5px) rotate(-1deg); }
}

@keyframes tierGlowPulse {
  0%, 100% { opacity: .36; transform: scale(.82); }
  50% { opacity: .82; transform: scale(1.08); }
}

@keyframes tierSpark {
  0%, 100% { opacity: .18; transform: translateY(5px) scale(.65); }
  50% { opacity: 1; transform: translateY(-5px) scale(1.15); }
}

.lobby-screen .hundred-motion {
  position: absolute;
  overflow: hidden;
  inset: 3px;
  z-index: 1;
  width: auto;
  height: auto;
  pointer-events: none;
}

.lobby-screen .hundred-motion img {
  position: absolute;
  left: auto;
  right: 4px;
  top: -10px;
  bottom: auto;
  z-index: 2;
  width: auto;
  height: 184px;
  max-width: 46%;
  object-fit: contain;
  object-position: top center;
  opacity: .88;
  filter: drop-shadow(0 8px 11px rgba(35, 12, 65, .4));
  transform-origin: 50% 90%;
  animation: none;
}

.lobby-screen .hundred-motion-ring {
  position: absolute;
  left: auto;
  right: 8px;
  bottom: 2px;
  width: 68px;
  height: 20px;
  border: 2px solid rgba(255, 235, 139, .72);
  border-radius: 50%;
  box-shadow: 0 0 16px rgba(255, 179, 83, .65);
  transform: scale(.72);
  animation: none;
}

.lobby-screen .hundred-motion-star {
  position: absolute;
  right: -2px;
  top: 8px;
  z-index: 4;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #fff5b5;
  box-shadow: 0 0 8px #fff, 0 0 18px #ffca5d;
  animation: none;
}

.lobby-screen .hundred-text {
  z-index: 4;
  text-shadow: 0 2px 5px rgba(0, 0, 0, .86), 0 0 10px rgba(0, 0, 0, .48);
}

@keyframes hundredMascotBounce {
  0%, 100% { transform: translateY(3px) rotate(-2deg); }
  45% { transform: translateY(-6px) rotate(2deg); }
  58% { transform: translateY(-4px) rotate(0); }
}

@keyframes hundredRingPulse {
  0% { opacity: .2; transform: scale(.72); }
  55% { opacity: .82; }
  100% { opacity: 0; transform: scale(1.18); }
}

@media (max-height: 760px) {
  .lobby-screen .game-switcher {
    bottom: 72px;
  }

  .lobby-screen .bottombar {
    top: 150px;
    bottom: auto;
  }

  .lobby-screen .hundred-circle {
    top: 220px;
    height: 100px;
  }

  .lobby-screen .game-tier-stage {
    top: 90px;
  }

  .lobby-screen .game-entry-tier {
    height: 108px;
    padding: 8px 10px;
    grid-template-rows: 44px auto 1fr;
  }

  .lobby-screen .game-entry-tier.accent-happy {
    height: 82px;
    padding: 9px 20px;
  }

  .lobby-screen .tier-motion {
    height: auto;
    transform: none;
  }

  .lobby-screen .tier-art {
    width: 78px;
    height: 78px;
  }

  .lobby-screen .game-entry-tier.accent-happy .tier-motion {
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lobby-screen .tier-motion::before,
  .lobby-screen .tier-motion img,
  .lobby-screen .motion-spark,
  .lobby-screen .hundred-motion img,
  .lobby-screen .hundred-motion-ring,
  .lobby-screen .hundred-motion-star {
    animation: none;
  }
}

/* Final cascade guard for the recessed four-card board. */
.lobby-screen .game-tier-stage-jinhua {
  grid-template-rows: repeat(2, 120px);
  height: 250px;
  padding-bottom: 0;
  overflow: visible;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 12;
  display: block;
  width: 120px;
  height: 120px;
  min-width: 120px;
  padding: 0;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua {
    grid-template-rows: repeat(2, 108px);
    height: 224px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy {
    width: 108px;
    height: 108px;
    min-width: 108px;
  }
}

/* Sculpted lobby board: the card shell owns the recessed arc, while artwork
   remains free to rise above the outer frame for a layered game-poster look. */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) {
  overflow: visible;
  contain: none;
  border-color: transparent;
  background: transparent;
  box-shadow: none;
  -webkit-mask-image: none;
  mask-image: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy)::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  box-sizing: border-box;
  border: 2px solid color-mix(in srgb, var(--tier-accent) 78%, #fff 22%);
  border-radius: 16px;
  box-shadow: inset 0 -4px 0 color-mix(in srgb, var(--tier-accent) 28%, transparent);
  filter: drop-shadow(0 9px 12px rgba(0, 0, 0, .35));
  pointer-events: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-tycoon::before {
  background: radial-gradient(circle at 72% 18%, rgba(255, 204, 105, .34), transparent 34%), linear-gradient(150deg, rgba(159, 85, 37, .98), rgba(73, 40, 42, .99) 68%);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-expert::before {
  background: radial-gradient(circle at 28% 18%, rgba(255, 132, 152, .34), transparent 34%), linear-gradient(150deg, rgba(158, 48, 76, .98), rgba(65, 29, 50, .99) 68%);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-advanced::before {
  background: radial-gradient(circle at 72% 18%, rgba(91, 230, 177, .32), transparent 34%), linear-gradient(150deg, rgba(27, 126, 99, .98), rgba(22, 55, 52, .99) 68%);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-newbie::before {
  background: radial-gradient(circle at 28% 18%, rgba(181, 145, 255, .32), transparent 34%), linear-gradient(150deg, rgba(99, 64, 159, .98), rgba(45, 33, 77, .99) 68%);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1)::before {
  -webkit-mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2)::before {
  -webkit-mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3)::before {
  -webkit-mask-image: radial-gradient(circle 64px at calc(100% + 5px) -5px, transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at calc(100% + 5px) -5px, transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4)::before {
  -webkit-mask-image: radial-gradient(circle 64px at -5px -5px, transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at -5px -5px, transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) strong,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) .tier-sub {
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) strong {
  bottom: 14px;
  color: #ffe8a8;
  font-size: 18px;
  -webkit-text-fill-color: #ffe8a8;
  text-shadow: 0 2px 0 rgba(44, 20, 24, .9), 0 0 6px #fff4c1, 0 0 13px var(--tier-accent);
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .65));
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) .tier-sub {
  color: rgba(255, 249, 230, .98);
  font-size: 12px;
  text-shadow: 0 2px 2px rgba(0, 0, 0, .96), 0 0 7px var(--tier-accent);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .75));
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) strong {
  left: 2px;
  right: 70px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) .tier-sub {
  left: 2px;
  right: 58px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) strong {
  left: 70px;
  right: 2px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) .tier-sub {
  left: 58px;
  right: 2px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3) strong,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4) strong {
  left: 16px;
  right: 16px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3) .tier-sub,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4) .tier-sub {
  left: 8px;
  right: 8px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) .tier-motion,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) .tier-motion {
  overflow: visible;
  top: -18px;
  bottom: 0;
  -webkit-clip-path: polygon(0 -80%, 100% -80%, 100% 100%, 0 100%);
  clip-path: polygon(0 -80%, 100% -80%, 100% 100%, 0 100%);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) .tier-motion img {
  left: -6px;
  right: auto;
  top: -7px;
  height: 218px;
  opacity: 1;
  filter: brightness(1.14) saturate(1.12) contrast(1.04) drop-shadow(0 8px 9px rgba(0, 0, 0, .34));
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) .tier-motion img {
  left: auto;
  right: -6px;
  top: -7px;
  height: 222px;
  opacity: 1;
  filter: brightness(1.14) saturate(1.12) contrast(1.04) drop-shadow(0 8px 9px rgba(0, 0, 0, .34));
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) .motion-spark-a {
  left: 80px;
  top: 57px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) .motion-spark-b {
  display: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) .motion-spark-a {
  left: 111px;
  top: 58px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) .motion-spark-b {
  display: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1) .motion-spark,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2) .motion-spark {
  z-index: 6;
  width: 12px;
  height: 12px;
  border-radius: 0;
  background: #fff;
  -webkit-clip-path: polygon(50% 0, 59% 39%, 100% 50%, 59% 61%, 50% 100%, 41% 61%, 0 50%, 41% 39%);
  clip-path: polygon(50% 0, 59% 39%, 100% 50%, 59% 61%, 50% 100%, 41% 61%, 0 50%, 41% 39%);
  box-shadow: 0 0 4px #fff, 0 0 9px #fff2a4, 0 0 15px var(--tier-accent);
  animation: none;
}

@keyframes eyeStarTwinkle {
  0%, 68%, 100% { opacity: 0; transform: scale(.2) rotate(0); }
  75% { opacity: 1; transform: scale(1.28) rotate(45deg); }
  82% { opacity: .7; transform: scale(.72) rotate(72deg); }
  88% { opacity: 0; transform: scale(.25) rotate(90deg); }
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1)::after,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2)::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 7;
  display: block;
  box-sizing: border-box;
  border: 0;
  border-bottom: 3px solid color-mix(in srgb, var(--tier-accent) 74%, #fff 26%);
  border-radius: 0 0 16px 16px;
  background: transparent;
  filter: drop-shadow(0 1px 1px rgba(255, 245, 215, .7)) drop-shadow(0 0 4px var(--tier-accent));
  pointer-events: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1)::after {
  -webkit-mask-image: radial-gradient(circle 61px at calc(100% + 5px) calc(100% + 5px), transparent 0 60px, #000 61px);
  mask-image: radial-gradient(circle 61px at calc(100% + 5px) calc(100% + 5px), transparent 0 60px, #000 61px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2)::after {
  -webkit-mask-image: radial-gradient(circle 61px at -5px calc(100% + 5px), transparent 0 60px, #000 61px);
  mask-image: radial-gradient(circle 61px at -5px calc(100% + 5px), transparent 0 60px, #000 61px);
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1)::after {
    -webkit-mask-image: radial-gradient(circle 55px at calc(100% + 4px) calc(100% + 4px), transparent 0 54px, #000 55px);
    mask-image: radial-gradient(circle 55px at calc(100% + 4px) calc(100% + 4px), transparent 0 54px, #000 55px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2)::after {
    -webkit-mask-image: radial-gradient(circle 55px at -4px calc(100% + 4px), transparent 0 54px, #000 55px);
    mask-image: radial-gradient(circle 55px at -4px calc(100% + 4px), transparent 0 54px, #000 55px);
  }
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy {
  overflow: hidden;
  border: 3px solid #ffd87e;
  box-shadow: inset 0 0 0 3px rgba(255, 246, 196, .16), 0 0 0 1px rgba(255, 235, 167, .52), 0 10px 24px rgba(0, 0, 0, .52), 0 0 20px rgba(255, 119, 82, .48);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion {
  inset: 0;
}

.lobby-screen .game-tier-stage-jinhua {
  isolation: isolate;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) {
  z-index: 1;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy {
  z-index: 100 !important;
  isolation: isolate;
  contain: none;
  opacity: 1;
  filter: none;
  backdrop-filter: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy::before {
  display: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-art {
  display: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion {
  z-index: 2;
  opacity: 1;
  filter: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion img {
  z-index: 3;
  right: -50px;
  top: -40px;
  height: 230px;
  opacity: 1;
  filter: brightness(1.08) saturate(1.1) contrast(1.03);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy strong,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-sub {
  z-index: 6;
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion img {
    right: -46px;
    top: -37px;
    height: 210px;
  }
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1)::before {
    -webkit-mask-image: radial-gradient(circle 58px at calc(100% + 4px) calc(100% + 4px), transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at calc(100% + 4px) calc(100% + 4px), transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2)::before {
    -webkit-mask-image: radial-gradient(circle 58px at -4px calc(100% + 4px), transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at -4px calc(100% + 4px), transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3)::before {
    -webkit-mask-image: radial-gradient(circle 58px at calc(100% + 4px) -4px, transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at calc(100% + 4px) -4px, transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4)::before {
    -webkit-mask-image: radial-gradient(circle 58px at -4px -4px, transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at -4px -4px, transparent 0 57px, #000 58px);
  }
}

/* Keep the room board attached to the thumb-zone game switcher. */
.lobby-screen .game-tier-stage {
  top: auto;
  bottom: 136px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion img {
  right: -14px;
  top: -8px;
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage {
    top: auto;
    bottom: 132px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-happy .tier-motion img {
    right: -12px;
    top: -7px;
  }
}

/* Open-top shells for Master/Expert. The sculpted-board revision introduced
   an opaque ::before surface whose straight upper edge looked like a stray
   light. Keep only the side/bottom frame and fade the surface in below it. */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1)::before,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2)::before {
  border: 2px solid color-mix(in srgb, var(--tier-accent) 78%, #fff 22%);
  border-radius: 16px;
  filter: drop-shadow(0 9px 12px rgba(0, 0, 0, .35));
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1)::before {
  background: radial-gradient(circle at 72% 18%, rgba(255, 204, 105, .34), transparent 34%), linear-gradient(150deg, rgba(159, 85, 37, .98), rgba(73, 40, 42, .99) 68%);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2)::before {
  background: radial-gradient(circle at 28% 18%, rgba(255, 132, 152, .34), transparent 34%), linear-gradient(150deg, rgba(158, 48, 76, .98), rgba(65, 29, 50, .99) 68%);
}

/* Master/Expert portraits are direct card children. Their own image box crops
   the lower body, so no full-width motion wrapper can leave a compositing seam. */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-direct {
  position: absolute;
  top: 2px;
  z-index: 20;
  width: 112px;
  height: 116px;
  max-width: none;
  object-fit: cover;
  object-position: top center;
  opacity: 1 !important;
  mix-blend-mode: normal;
  filter: brightness(1.14) saturate(1.12) contrast(1.04);
  pointer-events: none;
  transform-origin: 50% 90%;
  animation: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-direct ~ .motion-spark {
  display: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-tycoon,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-expert {
  /* Both source PNGs include transparent pixels above the hair. Move the
     actual artwork far enough up to visibly break the card's top edge. */
  top: -34px;
  width: 144px;
  height: 150px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-tycoon {
  left: -28px;
  animation-delay: -.65s;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-expert {
  right: -28px;
  animation-delay: -1.85s;
}

/* Keep the high-stakes room names clear of their portrait art. */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier > strong.tier-name-visible {
  z-index: 21;
  /* Keep the high-stakes labels above the centre 欢乐场 circle. */
  bottom: 64px;
  min-height: 0;
  padding: 0;
  color: #fff3c6;
  font-size: 17px;
  line-height: 1.1;
  white-space: nowrap;
  background: none;
  box-shadow: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-tycoon > strong.tier-name-visible {
  left: 76px;
  right: 5px;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-expert > strong.tier-name-visible {
  left: 5px;
  right: 76px;
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-direct {
    width: 102px;
    height: 104px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-tycoon,
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-expert {
    top: -30px;
    width: 132px;
    height: 138px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-tycoon {
    left: -26px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-expert {
    right: -26px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier > strong.tier-name-visible {
    bottom: 56px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-direct {
    animation: none;
  }
}

/* The outline source is masked independently, then stroked by its unmasked
   wrapper. That keeps every line outside the panel, including the concave arc. */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy)::before {
  border: 0 !important;
  box-shadow: none !important;
  filter: none !important;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy)::after {
  display: none !important;
  border: 0 !important;
  box-shadow: none !important;
  filter: none !important;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) .tier-outline-filter {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  pointer-events: none;
  filter:
    drop-shadow(1.5px 0 0 color-mix(in srgb, var(--tier-accent) 84%, #fff 16%))
    drop-shadow(-1.5px 0 0 color-mix(in srgb, var(--tier-accent) 84%, #fff 16%))
    drop-shadow(0 1.5px 0 color-mix(in srgb, var(--tier-accent) 84%, #fff 16%))
    drop-shadow(0 -1.5px 0 color-mix(in srgb, var(--tier-accent) 84%, #fff 16%));
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:not(.accent-happy) .tier-outline-source {
  position: absolute;
  inset: 0;
  display: block;
  border-radius: 16px;
  background: #fff;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1):not(.accent-happy) .tier-outline-source {
  -webkit-mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2):not(.accent-happy) .tier-outline-source {
  -webkit-mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3):not(.accent-happy) .tier-outline-source {
  -webkit-mask-image: radial-gradient(circle 64px at calc(100% + 5px) -5px, transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at calc(100% + 5px) -5px, transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4):not(.accent-happy) .tier-outline-source {
  -webkit-mask-image: radial-gradient(circle 64px at -5px -5px, transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at -5px -5px, transparent 0 63px, #000 64px);
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1):not(.accent-happy) .tier-outline-source {
    -webkit-mask-image: radial-gradient(circle 58px at calc(100% + 4px) calc(100% + 4px), transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at calc(100% + 4px) calc(100% + 4px), transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2):not(.accent-happy) .tier-outline-source {
    -webkit-mask-image: radial-gradient(circle 58px at -4px calc(100% + 4px), transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at -4px calc(100% + 4px), transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(3):not(.accent-happy) .tier-outline-source {
    -webkit-mask-image: radial-gradient(circle 58px at calc(100% + 4px) -4px, transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at calc(100% + 4px) -4px, transparent 0 57px, #000 58px);
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(4):not(.accent-happy) .tier-outline-source {
    -webkit-mask-image: radial-gradient(circle 58px at -4px -4px, transparent 0 57px, #000 58px);
    mask-image: radial-gradient(circle 58px at -4px -4px, transparent 0 57px, #000 58px);
  }
}

/* 德州扑克场次卡片：房间名和盲注文字过去共用炸金花那套绝对定位，字号偏小、
   颜色偏暗，两行经常挤在一起。这里把两者放回正常文档流各自成行，加大字号
   并换成更亮眼的配色，与背景形成明显对比。选择器多带一层 .lobby-screen 前缀
   只是为了保证特异度高于原有的 .lobby-screen .game-entry-tier strong 规则，
   不需要触碰/删除那些规则本身（炸金花卡片继续用原样式）。 */
.lobby-screen .game-tier-stage-texas .game-entry-tier strong,
.lobby-screen .game-tier-stage-texas .game-entry-tier .tier-sub {
  position: static;
  display: block;
  width: 100%;
  max-width: none;
  text-align: center;
  white-space: nowrap;
  overflow: visible;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .92), 0 0 8px rgba(0, 0, 0, .7);
}

.lobby-screen .game-tier-stage-texas .game-entry-tier strong {
  margin-top: 3px;
  color: #fff6d0;
  font-size: 15px;
  font-weight: 900;
  line-height: 1.15;
}

.lobby-screen .game-tier-stage-texas .game-entry-tier .tier-sub {
  margin-top: 5px;
  color: #7cffc4;
  font-size: 12px;
  font-weight: 800;
  line-height: 1.2;
}

/* Texas only: with a real seat (t4) now sitting where .pot used to be
   (top:25%, shared with 三张牌 via .table-screen:not(.hundred-screen) .pot),
   the pot itself moves down inside the table, just below that seat, instead
   of overlapping it. Same specificity as that shared rule (3 classes
   each), so this only wins because it's declared later in the cascade. */
.table-screen.texas-screen .pot {
  top: 42%;
  width: 172px;
  display: grid;
  grid-template-columns: 72px 16px 72px;
  gap: 6px;
  padding: 0;
  border: none;
  background: none;
  box-shadow: none;
  /* Above .pot-return-layer/.texas-bet-sweep-layer (both z-index:22) -- the
     winner-collect flight's chips originate from this exact spot (see
     potPoint(game) in app.js), and a chip-large piece (50px) starting right
     on top of a ~22px-tall pot label fully covered "总底池" for the first
     stretch of its flight -- "赢家收筹码的时候，收筹码的动画会出现一个巨
     大的筹码挡住总底池信息". Chips still fly past/through this spot exactly
     as before; only the pot text itself now stays legible on top of them. */
  z-index: 23;
}

/* Fixed symmetric columns keep the label and center coin still while the
   amount changes width after each bet. */
.table-screen.texas-screen .pot-label-text {
  justify-self: end;
  white-space: nowrap;
}

/* The pot AMOUNT TEXT itself gets a slight perspective warp (narrow top,
   wide bottom) instead of a background/box shape behind it -- just the
   letters leaning back into the table's own receding-oval perspective, no
   opaque band reintroduced behind them. Subtle on purpose (a few degrees),
   this is a small accent next to the pot icon, not a big 3D showpiece. */
.table-screen.texas-screen .pot-amount-text {
  display: inline-block;
  justify-self: start;
  white-space: nowrap;
  transform: perspective(60px) rotateX(10deg);
  transform-origin: center bottom;
}

/* Texas only: the per-seat hand-type pill (only ever shown at real
   showdown now, see renderTexasSeat's status line) gets a solid opaque
   backing instead of 三张牌's own fade-to-transparent-at-the-edges
   gradient -- it's a one-off result label here, not a strip riding under a
   long-lived status like "已弃牌"/"准备中…", so it reads better as a
   plain badge. Light/warm, not dark -- the seat frame itself is already a
   dark gradient, so a dark pill on top of it just blends in instead of
   standing out. */
.table-screen.texas-screen .card-status {
  background: rgba(255, 244, 214, .94);
  border: 1px solid rgba(154, 106, 22, .4);
  color: #2a1c08;
  text-shadow: none;
}

/* "全下" used to ride through .card-status too, centered right on the
   card faces same as the hand-type badge -- moved to its own speech
   bubble above the frame instead (see renderTexasSeat's allInBubble) so
   it never covers the cards. A small triangular tail (::after) points
   back down at the seat it belongs to, same idea as a chat bubble. */
.texas-allin-bubble {
  position: absolute;
  left: 50%;
  top: -26px;
  z-index: 9;
  transform: translateX(-50%);
  padding: 2px 10px;
  border-radius: 999px;
  background: rgba(255, 244, 214, .94);
  border: 1px solid rgba(154, 106, 22, .4);
  color: #2a1c08;
  font-size: 11px;
  font-weight: 900;
  white-space: nowrap;
}

.texas-allin-bubble::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -5px;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: rgba(255, 244, 214, .94);
}

/* t3/t7 used to have their "全下" bubble flipped to sit BELOW the frame
   (the D/SB/BB badge and bet chip claimed the above-frame corners back
   then) -- reverted per "德州的左下和右下玩家的全下气泡，应该显示在正上方，
   与其他玩家一样": "我" own persistent all-in bubble now just falls through
   to the shared .texas-allin-bubble default (top:-26px, tail pointing
   down) like every seat used to. Opponents get a separate, transient
   bubble instead -- see .texas-action-bubble below.  */

/* Opponent-only, transient version of the speech-bubble idea above --
   shows whichever of 弃牌/过牌/跟注/加注/全下 a seat just did (see
   renderTexasSeat's actionBubble), gone again the moment the next seat
   acts or the hand reaches settlement. Anchored at a genuine CORNER of the
   frame (not a centered edge like .texas-allin-bubble) so it never crosses
   the frame's own horizontal/vertical centerline ("不要与人物框的水平线和
   竖线重叠") -- each seat's own corner (below) is picked to land wherever
   the D/SB/BB badge and bet chip AREN'T (see their own per-seat overrides
   further up this file), so the two never fight for the same spot. t4
   (directly across the table) used to get its own special "plain right
   side" treatment, but per a follow-up "正上方玩家的气泡也要换，把气泡放
   在右上角即可" it now just joins t7's own upper-right-corner group below
   instead of staying a one-off case.
   Renders as a real drawn asset (texas-action-bubble.png, an image the
   user supplied) instead of a pure-CSS shape -- every CSS version of the
   tail (rotated border-triangle, rotated+cropped diamond) kept leaving a
   visible gap where it met the bubble's own border-radius:999px pill,
   because that curve never actually reaches its own bounding-box corner
   no matter how precisely the tail's anchor point was tuned ("三角形融合
   的不太好" / "别用我的方法，用你自己认为最好的方法"). A real asset
   sidesteps the whole problem: body and tail are one continuous painted
   shape, so there's no seam to get wrong. The source art's own tail points
   down-left (t7/t4's shared orientation); the other three diagonal
   directions are that same image mirrored via scaleX/scaleY(-1) on a
   background layer (kept on ::before, separate from the actual text node,
   so the label itself never flips backwards). */
.texas-action-bubble {
  position: absolute;
  z-index: 9;
  color: #2a1c08;
  font-size: 14px;
  font-weight: 900;
  white-space: nowrap;
  /* No fixed lifetime any more -- app.js picks which of these two
     animations + the matching negative animation-delay each instance
     renders with (see renderTexasSeat's isActiveActionBubble/
     actionBubbleStyle): fade in and hold indefinitely while this seat
     owns the bubble slot, or fade out over TEXAS_ACTION_BUBBLE_FADE_OUT_MS
     once a different seat's action (or showdown) takes it away
     ("消失还是要淡出" -- it used to just pop in and get yanked out with no
     transition at all). The negative delay resumes whichever animation at
     the right point on every render instead of restarting from 0% -- same
     trick showRoomToast's own fade-out already uses elsewhere in this
     file. */
}

/* Longer English action words ("Compare", "All In") vs. the 2-character
   Chinese labels (比牌/全下) this bubble's fixed-size box was sized around --
   see actionBubbleCompact/jinhuaActionBubbleCompact in app.js, which add
   this class instead of resizing the bubble itself. */
.texas-action-bubble.texas-action-bubble-compact {
  font-size: 10px;
  letter-spacing: -.2px;
}

@keyframes texasActionBubbleIn {
  0% { opacity: 0; transform: scale(.85); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes texasActionBubbleOut {
  0% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(.9); }
}

/* All five corners share this image-backed box -- sized and padded off
   the source art's own measured interior rectangle (the safe-for-text
   area, excluding its drawn border and tail: left 8%/top 4.5%/right
   3.4%/bottom 38.7% of the image, converted to px at this 50x35 display
   size) so the label centers inside the drawn rectangle instead of
   drifting into the border or down into the tail. A brief 58x40 pass
   (scaling the whole box up alongside the font) read as too big -- per
   "气泡本身不要放大，只是字体变大" the BOX is back down near its original
   48x33 size (just 2px taller, to keep the now-bigger 14px font from
   feeling cramped); .texas-action-bubble's own font-size is what's still
   doing the "read bigger" work now, not the box. */
.table-screen.texas-screen .seat.me .texas-action-bubble,
.table-screen.texas-screen .seat.t1 .texas-action-bubble,
.table-screen.texas-screen .seat.t3 .texas-action-bubble,
.table-screen.texas-screen .seat.t4 .texas-action-bubble,
.table-screen.texas-screen .seat.t5 .texas-action-bubble,
.table-screen.texas-screen .seat.t7 .texas-action-bubble {
  width: 50px;
  height: 35px;
  padding: 2px 2px 14px 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.table-screen.texas-screen .seat.me .texas-action-bubble::before,
.table-screen.texas-screen .seat.t1 .texas-action-bubble::before,
.table-screen.texas-screen .seat.t3 .texas-action-bubble::before,
.table-screen.texas-screen .seat.t4 .texas-action-bubble::before,
.table-screen.texas-screen .seat.t5 .texas-action-bubble::before,
.table-screen.texas-screen .seat.t7 .texas-action-bubble::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url("./assets/texas-action-bubble.png") center / 100% 100% no-repeat;
}

/* "我" own seat: used to sit flush at the corner exactly like t7/t4 below,
   but that put it right at the same height as the bet chip with only a
   left/right gap between them ("下注金币在玩家自己的操作气泡下方" asks for
   a stacked layout instead). Nudged up-left off the flush corner (left/
   bottom via calc() instead of transform -- app.js's fade in/out animation
   already owns this element's `transform` via texasActionBubbleIn/Out's
   scale(), so a static transform here would just get clobbered the instant
   that animation is running, i.e. almost always) per "操作气泡往左上移动
   一点，指向自己的头像" -- the source art's tail still points down-left
   from its new higher spot, so it now aims back down at the avatar instead
   of just touching the frame's corner tip. That 18px lift is exactly what
   opens up clean room for the bet chip (35px bubble height + 2px gap) to
   sit directly below it instead of beside it -- see .seat.me
   .texas-bet-chip above. Pulled left further still (14px -> 28px -> 36px
   -> 42px) per "操作气泡再往左移动，让尖尖贴近玩家头像" / "操作气泡再往
   左移动一点" (x2) -- the avatar sits toward the frame's horizontal
   center, not its right edge, so the tail needed more than the first pass
   gave it to actually reach back toward it. Lifted another 6px
   (18px -> 24px) to keep pace with .texas-bet-chip's own matching 6px push
   (top:-22px -> -28px) per "下注金币还要往右上移动" -- the chip moving up
   without this would've closed the gap between the two back down to a
   near-touch. Left untouched since (24px) even though the chip has since
   settled back down slightly (-28px -> -25px) -- that direction only opens
   the gap further, not closes it. */
.table-screen.texas-screen .seat.me .texas-action-bubble {
  left: calc(100% - 42px);
  right: auto;
  top: auto;
  bottom: calc(100% + 24px);
  margin: 0;
}

/* t7 (bottom-left)/t4 (directly across): both anchor at their own frame's
   top-right corner and grow up-and-right from there, per "左下的玩家的气
   泡放在人物框的右上角"/"正上方玩家...把气泡放在右上角即可". Source art's
   tail already points down-left -- this is its original, un-mirrored
   orientation. margin:0 (down from an earlier 1px, before that 4px/8px)
   per "整体还要贴近人物框一点，气泡的尖尖要与人物框碰触为最好" -- the
   source art's own tail tip sits right at its image's edge (measured
   ~0.5%/99% in from the corner), so a 0 margin lands it flush against the
   frame instead of leaving any visible gap. bottom split out per-seat below
   -- t7 alone got pulled down a few px further, t4 wasn't part of that ask
   and keeps the original flush 100%. */
.table-screen.texas-screen .seat.t7 .texas-action-bubble,
.table-screen.texas-screen .seat.t4 .texas-action-bubble {
  left: 100%;
  right: auto;
  top: auto;
  margin: 0;
}

.table-screen.texas-screen .seat.t4 .texas-action-bubble {
  bottom: 100%;
}

/* t7/t3 (bottom row) reach up-and-in toward the table's open center from
   their own frame -- which is also roughly where the community board/pot
   sit, so pulled back down a few px toward their own frame per "左下和右
   下玩家的操作气泡再往下移动一点" to trade a little of that reach for more
   clearance from the board. */
.table-screen.texas-screen .seat.t7 .texas-action-bubble {
  bottom: calc(100% - 6px);
}

/* t3 (bottom-right): mirror of t7 above -- upper-left corner, per "右下玩
   家的气泡放在人物框的左上角". Flipped horizontally so the tail points
   down-right instead; padding's left/right swap along with it so the text
   stays centered in the (now mirrored) interior rectangle. */
.table-screen.texas-screen .seat.t3 .texas-action-bubble {
  left: auto;
  right: 100%;
  top: auto;
  bottom: calc(100% - 6px);
  margin: 0;
  padding: 2px 4px 14px 2px;
}

.table-screen.texas-screen .seat.t3 .texas-action-bubble::before {
  transform: scaleX(-1);
}

/* t5 (top-left): lower-right corner, per "左上玩家的气泡放在人物框的右下
   角". Flipped vertically so the tail points up-left instead of down-left;
   padding's top/bottom swap along with it. Pulled back up a few px toward
   its own frame per "左上...玩家的操作气泡再往上移动一点" -- same
   board-clearance trade as t7/t3 above, just in the opposite direction
   since this pair's bubble grows down-and-in instead of up-and-in. */
.table-screen.texas-screen .seat.t5 .texas-action-bubble {
  left: 100%;
  right: auto;
  top: calc(100% - 6px);
  bottom: auto;
  margin: 0;
  padding: 14px 2px 2px 4px;
}

.table-screen.texas-screen .seat.t5 .texas-action-bubble::before {
  transform: scaleY(-1);
}

/* t1 (top-right): mirror of t5 above -- lower-left corner, per "右上玩家
   的气泡放在人物框的左下角". Both flips at once, so the tail points
   up-right. Same up-nudge as t5, per "...右上玩家的操作气泡再往上移动一
   点". */
.table-screen.texas-screen .seat.t1 .texas-action-bubble {
  left: auto;
  right: 100%;
  top: calc(100% - 6px);
  bottom: auto;
  margin: 0;
  padding: 14px 4px 2px 2px;
}

.table-screen.texas-screen .seat.t1 .texas-action-bubble::before {
  transform: scale(-1, -1);
}

/* Jinhua: same action-bubble feature ported over from Texas above (see
   renderRoom's own jinhuaActionBubble* block for the JS half) per "把操作
   气泡应用到炸金花的房间里". Reuses the exact same class/asset/keyframes/
   fade-timing constants Texas already set up -- none of that is actually
   Texas-specific -- but gets its own duplicate box-sizing/position rules
   below (scoped to jinhua's screen, which has no distinguishing class of
   its own -- see the shared :not(.hundred-screen):not(.texas-screen)
   pattern already used elsewhere in this file, e.g. .seat.turn::after)
   rather than broadening Texas's already-tuned selector list above, so
   none of this can ever touch that room's own rendering. */
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.me .texas-action-bubble,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s1 .texas-action-bubble,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s2 .texas-action-bubble,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s3 .texas-action-bubble,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s4 .texas-action-bubble {
  width: 50px;
  height: 35px;
  padding: 2px 2px 14px 4px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.table-screen:not(.hundred-screen):not(.texas-screen) .seat.me .texas-action-bubble::before,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s1 .texas-action-bubble::before,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s2 .texas-action-bubble::before,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s3 .texas-action-bubble::before,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s4 .texas-action-bubble::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url("./assets/texas-action-bubble.png") center / 100% 100% no-repeat;
}

/* Every earlier pass at this block placed the bubble off a corner of the
   *frame's own box* (0/100% left|right|top|bottom) and just guessed at
   which corner "looked right" from the CSS alone -- which is how it kept
   drifting: .seat.s1/.s2 mirror .seat-main into flex-direction:row-reverse
   (see that rule above), so .avatar actually sits flush against the FRAME'S
   OWN RIGHT edge on those two seats, not the left edge a plain ".seat-
   profile is the first flex child" reading would suggest, and every
   previous pass here reasoned from that wrong assumption. This pass instead
   used a real headless-Chrome render of a live 炸金花 table
   (getBoundingClientRect on .seat/.avatar) to measure where .avatar (36x36)
   actually lands inside each 112px-wide frame: top offset 6px on all four,
   left offset 4px on s3/s4 (left column, not reversed), left offset 72px
   (i.e. 40px in from the RIGHT edge) on s1/s2 (right column, reversed). All
   four bubbles now anchor off THAT avatar corner via plain px instead of a
   frame-relative 100%, landing the tail tip on the avatar itself regardless
   of which corner of the wider frame box it happens to sit in:
   - s3/s4 (avatar's near/left edge at x=4, center-facing edge at x=40):
     anchored at the center-facing edge (left:40px), natural tail (down-left
     tip) pointing back at it from up-and-right.
   - s1/s2 (avatar's center-facing edge 40px in from the frame's right edge,
     i.e. right:40px): mirrored scaleX(-1) tail (down-right tip) pointing
     back at it from up-and-left.
   top:-35px on all four = the bubble's own 35px height pulled up from that
   avatar-top y:6px offset PLUS a further 6px per "所有玩家的操作气泡再往上
   移动一点，与人物框拉开一点距离" -- was -29px (bottom edge flush on y:6,
   right at the avatar) -- so now there's a small visible 6px gap between the
   tail tip and the avatar instead of the two touching directly. */
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s3 .texas-action-bubble,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s4 .texas-action-bubble {
  left: 40px;
  right: auto;
  top: -35px;
  bottom: auto;
  margin: 0;
}

.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s1 .texas-action-bubble,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s2 .texas-action-bubble {
  left: auto;
  right: 40px;
  top: -35px;
  bottom: auto;
  margin: 0;
  padding: 2px 4px 14px 2px;
}

.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s1 .texas-action-bubble::before,
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.s2 .texas-action-bubble::before {
  transform: scaleX(-1);
}

/* "我": parking this up by 全下 (previous pass, left:166px/top:-42px) was
   provably clear of both 已投 and the frame, but per "放在已投的右边留一点
   距离" that just moved the collision problem into a new one -- disconnected,
   floating way off on its own with nothing readable nearby. Real player
   speech bubbles read left-to-right off whatever they're reacting to, so
   this now sits directly beside 已投 instead of above 全下: measured
   已投's own right edge across a spread of realistic amounts (seat-relative
   x:94-96 for "已投 200"/"已投 11M"/"已投 1.8K", up to 102.9 only for an
   extreme "已投 999.9M") -- left:100px leaves a small gap after the common
   case and only just brushes the pill on that one outlier. top:-35px
   vertically centers the bubble on 已投's own y:[-27,-7] band (bubble
   height 35, pill center -17 -> top -17-17.5≈-35) so the two read as one
   row instead of stacked at different heights, and its bottom edge lands
   flush on the frame's own top edge (0), not into it. Natural (unmirrored)
   tail points back down-left toward 已投/the frame, same as before. */
.table-screen:not(.hundred-screen):not(.texas-screen) .seat.me .texas-action-bubble {
  left: 100px;
  right: auto;
  top: -35px;
  bottom: auto;
  margin: 0;
}

/* Texas only: room-toast ("换桌成功" etc.) docks just above "me"'s own seat
   instead of .table-message's shared top:46% default -- that default now
   lands right on top of .pot (top:48% for texas, see above), so the two
   fought for the same spot. Fixed positioning (like .seat.me/the round
   controls) rather than a %-based .poker-table anchor, and clears the
   raise button plus its quick-raise row instead of sitting on top of it. */
.table-screen.texas-screen .room-toast {
  position: fixed;
  top: auto;
  left: 50%;
  bottom: 328px;
  transform: translateX(-50%);
}

/* Balance/stack overlaps the avatar's own bottom edge instead of sitting in
   normal flow below it, with a solid opaque pill behind it so it stays
   legible over whatever's underneath (avatar art, cards, felt). Applies to
   every texas seat ("my" own included) -- .seat-profile is the positioning
   parent (already position:relative), and the seat-stack is that column's
   only remaining flow child once the nickname moves out/above (see
   below), so nothing else shifts around when this comes out of flow. */
.table-screen.texas-screen .seat-stack {
  position: absolute;
  left: 50%;
  top: 100%;
  bottom: auto;
  transform: translateX(-50%);
  margin: -10px 0 0;
  padding: 1px 7px;
  border-radius: 999px;
  /* Black opaque, gold text -- more eye-catching against the felt/seat
     background than a light pill with a dark-gold fill. */
  background: rgba(10, 8, 6, .88);
  border: 1px solid rgba(255, 190, 60, .4);
  color: #ffb300;
  font-weight: 900;
  text-shadow: none;
  white-space: nowrap;
  /* Higher than 51, not the usual z-index:5 -- app.js's promoteInteractive
     Elements() bumps every clickable .avatar to z-index:51 after each
     render (see .seat-name-wrap's own comment on this same quirk), which
     otherwise paints right over this pill despite it being declared later
     in the DOM. */
  z-index: 52;
}

/* Opponent seats only: same black opaque-in-the-middle, fade-at-both-ends
   band as the nickname strip (see .seat-name-wrap::before) -- "my" own
   stack gets the same fade treatment via its own override above (see
   .seat.me .seat-stack), just with a slightly higher opacity to stay
   legible against the busier felt behind it. */
.table-screen.texas-screen .seat:not(.me) .seat-stack {
  padding: 1px 10px;
  border: none;
  border-radius: 0;
  background: linear-gradient(90deg, transparent, rgba(10, 8, 6, .55) 18%, rgba(10, 8, 6, .55) 82%, transparent);
}

/* Moved down off the avatar's own top edge (the shared
   .seat:not(.me):not(.s0) .seat-stack rule's bottom:100%/margin:0 0 -8px,
   further up this file) to just above the nickname strip below instead --
   per "把金币携带显示移下来放在昵称上方，与头像下方有点重叠一点没关系".
   :not(.s0) here is redundant for texas (these seats are never .s0) but
   matches that shared rule's own specificity so this genuinely overrides
   it instead of just losing on source order. top:36px is measured live
   (t3, unscaled): .seat-profile itself starts at 493px/frame-relative 6px
   -- 36px lands this pill's own bottom edge just above the nickname's new
   bottom:2px position, overlapping ~6px into the avatar's own lower edge
   along the way, which is the bit of overlap the request said was fine. */
.table-screen.texas-screen .seat:not(.me):not(.s0) .seat-stack {
  top: 36px;
  bottom: auto;
  margin: 0;
}

/* Opponent seats only: the hand-type badge (the only thing that still
   reaches this rule now -- "全下" moved to its own .texas-allin-bubble
   above the frame, and "已弃牌"/"准备中…" were dropped entirely, see
   renderTexasSeat's status computation) rides on a strip just below the
   enlarged "revealed" cards instead of centered on top of the card
   faces, so it doesn't visually fight the rank/suit for the same space.
   "my" own status (.own-card-status) is left on its usual spot since it
   wasn't part of this ask. */
.table-screen.texas-screen .seat:not(.me) .card-status:not(.own-card-status) {
  bottom: auto;
  top: 100%;
  margin-top: 4px;
  transform: translateX(-50%);
}

/* Opponent seats only: nickname moves out of the narrow seat-profile
   column (see renderTexasSeat's isMe branch -- .seat-name-wrap is now a
   direct child of .seat for opponents, not nested in seat-profile) to a
   full-width strip along the whole frame's own bottom edge, so it has
   enough room to actually show the whole name instead of ellipsis-
   truncating inside that column. */
/* Extra bottom padding makes room for the nickname strip below the cards,
   fully inside the frame's own border/background instead of poking out
   under it -- .cards itself doesn't move (padding doesn't affect its
   position), this just grows the seat box to actually contain the extra
   content. */
/* Narrowed by one card's width (31px, texas' own enlarged .cards .card
   width below) now that the hole cards no longer need their own side-by-
   side slot next to the avatar (see .texas-hole-cards below) -- at the
   old 116px width, five occupied seats plus the narrowed "me" seat read
   as crowded against the felt. */
.table-screen.texas-screen .seat:not(.me) {
  width: 76px;
  padding-bottom: 25px;
}

/* Opponent hole cards shrink down and cross over each other (a closed-
   hand look) right in their normal slot beside the avatar while idle --
   the avatar stays fully visible in this state. At reveal (showdown, or
   an early all-in reveal -- both driven purely off whether the server
   has actually sent real rank/suit data yet, see renderTexasSeat's
   "revealed" flag, no separate field needed) the cards unfold, enlarge,
   and move to cover the avatar instead, which hides for the moment (see
   the :has() rule further below). Kept at the same width:height ratio as
   every other card size in this file (roughly 5:7) rather than stretched
   to some arbitrary box, so they read as normal cards, just bigger.

   .texas-hole-cards sits as a sibling of .seat-profile (both children of
   .seat-main -- see renderTexasSeat), not nested inside it. .seat-profile
   itself is left completely untouched here -- still normal flow, still
   the thing that gives .seat-main (and so the frame) its real height --
   only the cards come out of flow via position:absolute (anchored to
   .seat, already position:absolute per the shared base rule, so no
   position override needed on .seat-main either). With .seat-profile as
   .seat-main's only flow child, justify-content:center just centers it
   directly -- true dead-center, not "centered as a pair with the cards"
   -- and the frame's height stays exactly what the avatar alone needs,
   regardless of which card state is showing. */
.table-screen.texas-screen .seat:not(.me) .cards.texas-hole-cards {
  position: absolute;
  top: 6px;
  min-width: 0;
  min-height: 0;
  margin-bottom: 0;
  gap: 0;
}

.table-screen.texas-screen .seat:not(.me) .texas-hole-cards.crossed {
  left: calc(50% + 14px);
}

/* Right-side seats (t1-t3) already flip the avatar to the outer/right
   edge and face the cards inward per the row-reverse rule further up
   this file -- true centering means that inward direction is now simply
   "toward the frame's own left half" instead of "wherever the row's
   second flex slot happened to land", so the crossed cards mirror to
   the left of center here to match. */
.table-screen.texas-screen .seat.t1 .texas-hole-cards.crossed,
.table-screen.texas-screen .seat.t2 .texas-hole-cards.crossed,
.table-screen.texas-screen .seat.t3 .texas-hole-cards.crossed {
  left: auto;
  right: calc(50% + 14px);
}

.table-screen.texas-screen .seat:not(.me) .texas-hole-cards.crossed .card {
  width: 16px;
  height: 22px;
  border-radius: 3px;
}

.table-screen.texas-screen .seat:not(.me) .texas-hole-cards.crossed .card:nth-child(1) {
  transform: rotate(-16deg) translate(3px, 1px);
}

.table-screen.texas-screen .seat:not(.me) .texas-hole-cards.crossed .card:nth-child(2) {
  transform: rotate(16deg) translate(-7px, 1px);
}

/* Takes itself out of the flex row entirely (position:absolute) so
   enlarging/uncrossing at reveal doesn't grow .seat-main's flow content
   and shove the avatar off its centered spot -- anchors straight to
   .seat itself (already position:absolute per the shared base rule, so
   it's a valid positioned ancestor without .seat-main needing its own
   position override) and centers over the whole frame width, covering
   the avatar whatever its exact position is. */
.table-screen.texas-screen .seat:not(.me) .texas-hole-cards.revealed {
  position: absolute;
  left: 50%;
  top: 8px;
  transform: translateX(-50%);
  gap: 2px;
  z-index: 53;
}

/* Tall enough to also cover the balance pill overlapping the avatar's
   own bottom edge (see .seat-stack's -10px overlap margin) -- at the
   avatar's own 42px height, anything shorter left the pill's last few
   pixels peeking out underneath. Width scaled up to match, keeping the
   same ~5:7 ratio every other card size in this file uses, so they read
   as normal cards rather than a stretched/squashed shape. */
.table-screen.texas-screen .seat:not(.me) .texas-hole-cards.revealed .card {
  width: 34px;
  height: 48px;
  font-size: 18px;
}

/* Avatar AND the balance pill overlapping its bottom edge both hide for
   as long as this seat's hole cards are standing up in the enlarged
   "revealed" layout above -- explicit opacity instead of relying on the
   cards' own z-index to visually cover the pill, since the pill isn't
   guaranteed to sit fully within the cards' shorter box in every case. */
.table-screen.texas-screen .seat:not(.me) .seat-main:has(.texas-hole-cards.revealed) .avatar,
.table-screen.texas-screen .seat:not(.me) .seat-main:has(.texas-hole-cards.revealed) .seat-stack {
  opacity: 0;
  pointer-events: none;
}

@keyframes texasFoldAway {
  0% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }

  100% {
    opacity: 0;
    transform: translateY(16px) scale(.68);
  }
}

/* Folding plays this once instead of just leaving the crossed card-backs
   sitting there indefinitely -- pushes them toward the table (down, into
   the felt) while they fade out. Duration must match TEXAS_FOLD_ANIM_MS
   in app.js, which also clamps the negative animation-delay so an
   already-long-folded seat (rejoining this render well past the window)
   renders straight into the 100% end state instead of replaying. */
.table-screen.texas-screen .seat:not(.me) .texas-hole-cards.texas-folded {
  animation: texasFoldAway 520ms ease forwards;
  pointer-events: none;
}

.table-screen.texas-screen .seat:not(.me) > .seat-name-wrap {
  position: absolute;
  left: 2px;
  right: 2px;
  /* Was 6px -- nudged closer to the frame's own bottom edge per "把德州其他
     玩家的昵称再往下移动一点点，贴近人物框下面水平线". */
  bottom: 2px;
  text-align: center;
}

/* Bigger than the shared 36px default -- the frame narrowed but the
   avatar shouldn't shrink along with it, it's the one thing that always
   stays fully visible now that the hole cards are just a small crossed
   pair beside it. */
.table-screen.texas-screen .seat:not(.me) .avatar {
  width: 42px;
  height: 42px;
}

/* Gold backing band, fading to transparent at both ends -- same tone as
   "my" own nickname band above. */
.table-screen.texas-screen .seat:not(.me) > .seat-name-wrap::before {
  background: linear-gradient(90deg, transparent, rgba(146, 96, 10, .82) 24%, rgba(146, 96, 10, .82) 76%, transparent);
}

/* A long nickname now clips at the wrap's own edge (which itself is
   inset to the frame's width -- left:2px/right:2px above) instead of
   spilling out past the frame -- max-width:none + overflow:visible used
   to let it show in full even when wider than the frame, which read as
   text just floating loose off both sides of the seat. */
.table-screen.texas-screen .seat:not(.me) > .seat-name-wrap .seat-name {
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
  max-width: 100%;
  overflow: hidden;
  text-overflow: clip;
  font-size: 12px;
}

/* Keep Texas opponents' result bubble clear of the balance pill without
   letting it float so far above the avatar that it looks disconnected. */
.table-screen.texas-screen .settlement-float {
  top: -36px;
}

/* Thin white ring around every seat's avatar (mine included) -- .avatar's
   own border-color was already set further up this file but never
   actually had a border-width/style to go with it, so nothing rendered. */
.table-screen.texas-screen .avatar {
  border: 1px solid rgba(255, 255, 255, .85);
}

/* Responsive lobby stacking: multiplayer is anchored above the room-card
   stage, and the utility bar is anchored above multiplayer. This keeps the
   same visual gap on every phone height instead of relying on fixed offsets. */
@supports (anchor-name: --lobby-anchor) {
  .lobby-screen .game-tier-stage {
    anchor-name: --lobby-room-cards;
  }

  .lobby-screen .hundred-circle {
    position-anchor: --lobby-room-cards;
    top: auto;
    bottom: anchor(top);
    margin-bottom: 24px;
    anchor-name: --lobby-multiplayer;
  }

  .lobby-screen .bottombar {
    position-anchor: --lobby-multiplayer;
    top: auto;
    bottom: anchor(top);
    margin-bottom: 12px;
  }
}

/* The lobby utilities are four standalone controls, not a toolbar sitting
   on a dark strip. */
.lobby-screen .bottombar {
  left: 50%;
  right: auto;
  width: min(372px, calc(100vw - 64px));
  transform: translateX(-50%);
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
}

.lobby-screen .navitem {
  min-height: 60px;
  border: 1px solid rgba(143, 229, 239, .56);
  border-radius: 15px;
  color: #e8fbff;
  background:
    radial-gradient(circle at 50% 0, rgba(149, 245, 255, .2), transparent 48%),
    linear-gradient(155deg, rgba(27, 77, 90, .96), rgba(9, 27, 42, .98));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), inset 0 -6px 12px rgba(0, 0, 0, .2), 0 6px 13px rgba(0, 0, 0, .36);
}

.lobby-screen .navitem:nth-child(2) {
  border-color: rgba(255, 220, 134, .58);
  background:
    radial-gradient(circle at 50% 0, rgba(255, 230, 148, .2), transparent 48%),
    linear-gradient(155deg, rgba(91, 70, 38, .96), rgba(34, 27, 20, .98));
}

.lobby-screen .navitem:nth-child(3) {
  border-color: rgba(143, 238, 181, .6);
  background:
    repeating-linear-gradient(135deg, rgba(255, 255, 255, .055) 0 1px, transparent 1px 8px),
    linear-gradient(155deg, rgba(28, 93, 66, .98), rgba(8, 37, 29, .99));
}

.lobby-screen .navitem:nth-child(4) {
  border-color: rgba(224, 161, 255, .6);
  background:
    radial-gradient(circle at 20% 18%, rgba(255, 218, 255, .18), transparent 32%),
    linear-gradient(155deg, rgba(78, 50, 110, .98), rgba(29, 18, 57, .99));
}

.lobby-screen .navitem .nav-icon {
  color: #a8f4ff;
  text-shadow: 0 0 8px rgba(142, 236, 255, .38);
}

.lobby-screen .navitem:nth-child(2) .nav-icon {
  color: #ffe29a;
  text-shadow: 0 0 8px rgba(255, 211, 105, .34);
}

.lobby-screen .navitem:nth-child(3) .nav-icon {
  color: #a9ffd2;
  text-shadow: 0 0 8px rgba(104, 255, 173, .35);
}

.lobby-screen .navitem:nth-child(4) .nav-icon {
  color: #f0c5ff;
  text-shadow: 0 0 8px rgba(231, 159, 255, .35);
}

.lobby-screen .navitem:active {
  filter: brightness(.88);
  transform: translateY(1px);
}

/* 德州与经典三张的场次板使用相同高度和底部基线。德州不再保留
   额外的底部内边距，切换时多人竞技便不会跟着上下移动。 */
.lobby-screen .game-tier-stage-texas {
  height: 250px;
  padding-bottom: 0;
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-texas {
    height: 224px;
  }
}

/* 两个游戏场次面板常驻，只切换可见状态，避免彼此重绘。 */
.lobby-screen .game-tier-stage {
  opacity: 1;
  visibility: visible;
  transition: opacity 180ms ease;
}

.lobby-screen .game-tier-stage.is-menu-hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 180ms ease, visibility 0s linear 180ms;
}

/* 大厅工具区收紧为轻量入口，给顶部海景和游艇留出完整视野。 */
.lobby-screen .hundred-circle {
  left: 50%;
  right: auto;
  width: min(232px, calc(100vw - 156px));
  height: 72px;
  padding: 10px 14px;
  border-radius: 16px;
  transform: translateX(-50%);
}

.lobby-screen .hundred-circle::after {
  inset: 4px;
  border-radius: 12px;
}

.lobby-screen .hundred-circle .entry-title {
  font-size: 23px;
}

.lobby-screen .hundred-text {
  margin-left: 32px;
}

.lobby-screen .hundred-motion img {
  top: -18px;
  right: 0;
  height: 112px;
  max-width: 46%;
}

.lobby-screen .hundred-motion-ring {
  right: 5px;
  transform: scale(.76);
}

.lobby-screen .hundred-motion-star {
  right: -6px;
  top: 2px;
  transform: scale(.76);
}

.lobby-screen .bottombar {
  left: min(calc(50% + 122px), calc(100vw - 68px));
  right: auto;
  width: 60px;
  grid-template-columns: 1fr;
  gap: 5px;
  transform: none;
}

.lobby-screen .navitem {
  min-height: 38px;
  border-radius: 11px;
  gap: 1px;
  font-size: 10px;
}

.lobby-screen .navitem .nav-icon {
  font-size: 16px;
}

/* 大厅主构图：顶部风景留白，中部场次，下方才放辅助操作。 */
.lobby-screen .bottombar {
  left: 50%;
  right: auto;
  top: auto;
  bottom: 8px;
  width: min(372px, calc(100vw - 48px));
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  transform: translateX(-50%);
}

.lobby-screen .navitem {
  min-height: 48px;
  border-radius: 13px;
  gap: 2px;
  font-size: 11px;
}

.lobby-screen .navitem .nav-icon {
  font-size: 17px;
}

/* 所有大厅操作模块共用同一上移量，保持彼此相对位置不变。 */
.lobby-screen .lobby-stage {
  transform: translateY(-42px);
}

.lobby-screen .bottombar {
  transform: translate(-50%, -42px);
}

/* 排行榜以紧凑列表呈现，保留滚动查看完整名次。 */
.leaderboard-modal {
  width: min(320px, 100%);
  padding: 14px 14px;
}

.leaderboard-modal header {
  margin-bottom: 9px;
}

.leaderboard-modal h2 {
  font-size: 20px;
}

.leaderboard-tabs {
  gap: 6px;
  margin-bottom: 6px;
}

.leaderboard-tab {
  padding: 5px 0;
  font-size: 12px;
}

.leaderboard-list {
  gap: 3px;
  /* Same "fit 10 rows" sizing as the base rule above, just against this
     breakpoint's smaller avatar/padding (10 * ~37px row + 9 * 3px gap). */
  max-height: min(40vh, 397px);
}

.leaderboard-row {
  gap: 6px;
  padding: 4px 2px;
}

.leaderboard-avatar {
  width: 28px;
  height: 28px;
}

/* 商城与救济模块保持完整功能，但以紧凑高度显示并支持内部滚动。
   之前这里写的是 max-height:none + overflow:visible -- 跟这条注释本身说的
   "紧凑高度 + 内部滚动" 正好相反，导致商城不再有高度上限，卡片/救济区/VIP
   区叠起来有多高就长多高，顶部经常会伸到公告栏那一片区域，看着"界面太
   大"，关闭时手指落点也容易先碰到公告栏而不是商城自己的关闭区域
   ("商城界面太大"/"关闭商城的时候很容易误点公告拦")。改回真正的定高 +
   内部滚动。 */
.shop-modal {
  width: min(350px, 100%);
  max-height: 66vh;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 14px 14px;
}

.shop-modal header {
  margin-bottom: 9px;
}

.shop-section-title {
  margin: 9px 0 5px;
  font-size: 12px;
}

.shop-tier-grid {
  gap: 6px;
}

.shop-tier-cell {
  min-height: 68px;
  padding: 7px 5px;
}

.shop-relief-row {
  gap: 5px;
  padding: 7px 8px;
  border-radius: 10px;
}

.shop-relief-row .daily-reward-status {
  margin: 0;
  font-size: 11px;
  line-height: 1.35;
}

.shop-relief-row .daily-reward-status span {
  display: block;
}

.shop-relief-row .terms-ok {
  width: 132px;
  min-height: 30px;
  height: 30px;
  margin: 0 auto;
  padding: 5px 10px;
  font-size: 12px;
}

/* 每日福利缩成紧凑签到卡，保留七日格与领取操作。 */
.daily-welfare-modal {
  width: min(340px, 100%);
  padding: 14px 14px;
}

.daily-welfare-modal header {
  margin-bottom: 9px;
}

.daily-welfare-modal .daily-reward-grid {
  gap: 6px;
}

.daily-welfare-modal .daily-reward-cell {
  min-height: 58px;
  gap: 2px;
  padding: 7px 3px;
}

.daily-welfare-modal .daily-reward-day,
.daily-welfare-modal .daily-reward-amount {
  font-size: 11px;
}

.daily-welfare-modal .daily-reward-status {
  margin: 7px 0;
  padding: 5px 8px;
  font-size: 11px;
  line-height: 1.35;
}

.daily-welfare-modal .terms-ok {
  min-height: 32px;
  padding: 6px 12px;
  font-size: 12px;
}

/* 大厅辅助面板统一为金色会所风格。 */
.daily-welfare-modal,
.leaderboard-modal,
.task-modal,
.shop-modal {
  border-color: rgba(255, 213, 122, .86);
  background:
    radial-gradient(ellipse at 50% 0, rgba(255, 211, 116, .19), transparent 42%),
    radial-gradient(circle at 100% 100%, rgba(136, 72, 28, .2), transparent 36%),
    linear-gradient(155deg, rgba(68, 43, 23, .98), rgba(28, 17, 12, .99) 64%, rgba(13, 10, 8, .995));
  box-shadow:
    inset 0 1px 0 rgba(255, 246, 208, .22),
    inset 0 0 0 1px rgba(255, 211, 119, .1),
    0 18px 46px rgba(0, 0, 0, .72),
    0 0 22px rgba(255, 183, 74, .18);
}

.daily-welfare-modal::before,
.shop-modal::before {
  background: linear-gradient(90deg, transparent, rgba(255, 222, 147, .92), transparent);
}

.daily-welfare-modal h2,
.leaderboard-modal h2,
.task-modal h2,
.shop-modal h2 {
  color: #ffe4a0;
  text-shadow: 0 2px 0 rgba(39, 20, 5, .9), 0 0 12px rgba(255, 192, 72, .25);
}

.daily-welfare-modal header button,
.leaderboard-modal header button,
.task-modal header button,
.shop-modal header button {
  color: #ffeec5;
  border-color: rgba(255, 219, 145, .48);
  background: linear-gradient(180deg, rgba(115, 73, 35, .92), rgba(48, 29, 16, .96));
}

.daily-welfare-modal .daily-reward-cell,
.shop-tier-cell,
.task-row,
.shop-relief-row,
.shop-vip-card,
.leaderboard-row {
  border-color: rgba(255, 220, 151, .2);
  background: linear-gradient(135deg, rgba(255, 223, 159, .1), rgba(56, 33, 17, .5) 56%, rgba(15, 11, 8, .66));
}

.daily-welfare-modal .daily-reward-cell.today,
.leaderboard-tab.active {
  color: #3a2109;
  border-color: #ffe09a;
  background: linear-gradient(180deg, #ffe9ab, #d99a3d);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 0 13px rgba(255, 194, 78, .22);
}

.leaderboard-tab {
  color: rgba(255, 237, 202, .72);
  border-color: rgba(255, 218, 145, .24);
  background: rgba(54, 33, 18, .64);
}

.leaderboard-name,
.task-row strong,
.shop-vip-info strong {
  color: #fff0c6;
}

.leaderboard-value,
.task-row small,
.shop-vip-info p,
.shop-vip-perks {
  color: #ffd78b;
}

.daily-welfare-modal .terms-ok,
.task-modal .terms-ok,
.shop-modal .terms-ok {
  color: #3b2007;
  border-color: rgba(255, 236, 178, .86);
  background: linear-gradient(180deg, #fff0b4, #e4ae54 58%, #b87125);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .72), 0 4px 10px rgba(0, 0, 0, .34);
}

/* 每日任务采用更紧凑的三行任务卡，领取后不会触发遮罩淡入。 */
.task-modal {
  width: min(320px, 100%);
  padding: 12px 13px;
}

.task-modal header {
  margin-bottom: 7px;
}

.task-modal > .daily-reward-status {
  margin: 0 0 7px;
  padding: 4px 7px;
  font-size: 11px;
}

.task-list {
  gap: 5px;
}

.task-row {
  gap: 7px;
  padding: 7px 8px;
  border-radius: 10px;
}

.task-row strong {
  font-size: 12px;
}

.task-row small {
  margin-top: 2px;
  font-size: 10px;
}

.task-row .terms-ok {
  width: 58px;
  min-height: 30px;
  height: 30px;
  margin: 0;
  padding: 3px 4px;
  font-size: 11px;
}

/* 进阶场角色提高可读性，保留原来的弹跳节奏。 */
.lobby-screen .game-tier-stage-jinhua .tier-motion-advanced img {
  opacity: 1;
  filter: brightness(1.14) saturate(1.12) contrast(1.05);
}

/* 多人竞技改为紧凑的金色活动卡，不再沿用大面积粉紫横幅。 */
.lobby-screen .hundred-circle {
  width: min(246px, calc(100vw - 142px));
  height: 68px;
  padding: 8px 12px;
  border: 1px solid rgba(255, 220, 139, .9);
  border-radius: 15px;
  background:
    radial-gradient(circle at 80% 22%, rgba(255, 229, 154, .26), transparent 31%),
    linear-gradient(145deg, rgba(116, 71, 28, .98), rgba(54, 31, 20, .98) 56%, rgba(27, 22, 27, .99));
  box-shadow:
    inset 0 1px 0 rgba(255, 247, 210, .3),
    inset 0 -5px 10px rgba(15, 8, 4, .24),
    0 7px 16px rgba(0, 0, 0, .36),
    0 0 14px rgba(255, 188, 79, .18);
}

.lobby-screen .hundred-circle::before {
  background: linear-gradient(115deg, transparent 14%, rgba(255, 238, 184, .2) 35%, transparent 51%);
}

.lobby-screen .hundred-circle::after {
  inset: 3px;
  border-radius: 11px;
  border-color: rgba(255, 232, 169, .23);
}

.lobby-screen .hundred-text {
  margin-left: 18px;
}

.lobby-screen .hundred-circle .entry-title {
  color: #fff0bf;
  font-size: 22px;
  letter-spacing: .4px;
  text-shadow: 0 2px 2px rgba(31, 15, 4, .72);
}

.lobby-screen .hundred-motion img {
  top: -19px;
  right: 2px;
  height: 108px;
  max-width: 43%;
  opacity: 1;
  filter: drop-shadow(0 5px 8px rgba(24, 12, 3, .48));
}

.lobby-screen .hundred-motion-ring,
.lobby-screen .hundred-motion-star {
  display: none;
}

/* 炸金花座位余额与德州使用同一套粗体数字表现。 */
.table-screen:not(.texas-screen):not(.hundred-screen) .seat-stack {
  font-family: inherit;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
}

/* 富豪场/高手场人物保持浮动，但卡框描边始终压在人物下半身前面。
   因此人物可以从上方探出，运动时仍像站在卡框里面。 */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-tycoon,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-expert {
  isolation: isolate;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-direct {
  z-index: 10;
  transform-origin: 50% 100%;
  animation: none;
}

/* 原有 outline-filter 带有实心描边源，继续留在底层；前景框另用
   透明伪元素绘制，避免遮住整张卡。 */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-tycoon .tier-outline-filter,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-expert .tier-outline-filter {
  z-index: -1;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-tycoon::after,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-expert::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 22;
  display: block !important;
  box-sizing: border-box;
  border: 2px solid color-mix(in srgb, var(--tier-accent) 84%, #fff 16%) !important;
  border-radius: 16px;
  background: transparent !important;
  box-shadow: none !important;
  filter: none !important;
  pointer-events: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(1).accent-tycoon::after {
  -webkit-mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at calc(100% + 5px) calc(100% + 5px), transparent 0 63px, #000 64px);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier:nth-child(2).accent-expert::after {
  -webkit-mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
  mask-image: radial-gradient(circle 64px at -5px calc(100% + 5px), transparent 0 63px, #000 64px);
}

/* 分层人物：外层统一浮动，下面一层只在卡框内显示，上面一层只显示
   卡框外的部分。避免动画过程穿出卡框底部或依赖一条额外的覆盖线。 */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-tycoon::after,
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-expert::after {
  display: none !important;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-split {
  --portrait-in-frame: 34px;
  position: absolute;
  top: -34px;
  z-index: 10;
  width: 144px;
  height: 150px;
  pointer-events: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-split.tier-portrait-tycoon {
  left: -28px;
  --portrait-delay: -.65s;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-split.tier-portrait-expert {
  right: -28px;
  --portrait-delay: -1.85s;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-split .tier-portrait-direct {
  position: absolute;
  inset: 0;
  z-index: auto;
  width: 100%;
  height: 100%;
  transform-origin: 50% 100%;
  animation: none;
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-under {
  clip-path: inset(var(--portrait-in-frame) 0 0 0);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-over {
  z-index: 2;
  clip-path: inset(0 0 calc(100% - var(--portrait-in-frame)) 0);
}

/* 给人物留出视觉空间：富豪场的山光上移，高手场的山光移到左上。 */
.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-tycoon::before {
  background:
    radial-gradient(ellipse 66% 90% at 92% -24%, rgba(255, 224, 134, .68), transparent 66%),
    linear-gradient(146deg, transparent 0 55%, rgba(255, 231, 161, .28) 55.5% 57%, transparent 57.5%),
    linear-gradient(132deg, transparent 0 62%, rgba(255, 210, 109, .2) 62.5% 64%, transparent 64.5%),
    linear-gradient(150deg, rgba(159, 85, 37, .98), rgba(73, 40, 42, .99) 68%);
}

.lobby-screen .game-tier-stage-jinhua > .game-entry-tier.accent-expert::before {
  background:
    radial-gradient(ellipse 66% 90% at 8% -24%, rgba(255, 190, 207, .62), transparent 66%),
    linear-gradient(34deg, transparent 0 55%, rgba(255, 216, 224, .27) 55.5% 57%, transparent 57.5%),
    linear-gradient(48deg, transparent 0 62%, rgba(255, 164, 184, .22) 62.5% 64%, transparent 64.5%),
    linear-gradient(150deg, rgba(158, 48, 76, .98), rgba(65, 29, 50, .99) 68%);
}

@media (max-height: 760px) {
  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-split {
    --portrait-in-frame: 30px;
    top: -30px;
    width: 132px;
    height: 138px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-split.tier-portrait-tycoon {
    left: -26px;
  }

  .lobby-screen .game-tier-stage-jinhua > .game-entry-tier .tier-portrait-split.tier-portrait-expert {
    right: -26px;
  }
}

@keyframes tierPortraitInFrameFloat {
  0%, 100% { transform: translateY(1px) rotate(-.8deg); }
  50% { transform: translateY(-4px) rotate(.8deg); }
}

/* 百人场庄家牌面底带改为深蓝紫金，避免与绿色桌面融在一起。 */
.hundred-screen .hundred-shared-cards::after {
  width: min(260px, 100%);
  background: linear-gradient(90deg,
    rgba(43, 47, 116, 0) 0%,
    rgba(43, 47, 116, .08) 20%,
    rgba(68, 55, 144, .68) 50%,
    rgba(43, 47, 116, .08) 80%,
    rgba(43, 47, 116, 0) 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 20%, #000 80%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 20%, #000 80%, transparent 100%);
  box-shadow: none;
}
