/* Neutral black/graphite base (was a navy-purple dark theme) with a single
   restrained metallic accent pair — warm gold for primary actions, cool
   slate for secondary — rather than the old purple/teal duo, for a more
   composed, premium read. success/danger are untouched: those carry
   win/lose meaning throughout the game and shouldn't shift with a reskin. */
:root {
  --bg: #0a0a0b;
  --bg-elevated: #141416;
  --card: #1a1a1d;
  --card-border: #2b2b2f;
  --text: #f2f2f0;
  --text-dim: #8b8b90;
  --accent: #c9a24b;
  --accent-2: #6b7280;
  --success: #34c77b;
  --danger: #ff5c72;
  --radius: 16px;
}

* { box-sizing: border-box; }

.platform-blocked {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  padding: 32px;
  text-align: center;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

.platform-blocked-icon { font-size: 44px; margin-bottom: 14px; }
.platform-blocked-title { font-size: 18px; font-weight: 700; margin-bottom: 8px; }
.platform-blocked-text { font-size: 14px; color: var(--text-dim); line-height: 1.6; }

/* ---- Boot splash — see js/splash.js and its index.html placement ---- */
.splash {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  transition: opacity 0.35s ease;
}
.splash.splash-hide {
  opacity: 0;
  pointer-events: none;
}
.splash-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* contain, not cover — the uploaded artwork is a decorative bordered
     portrait, not a photo meant to bleed to the screen edges; cover would
     crop most of it away on a tall phone viewport. */
  object-fit: contain;
}
.splash-spinner {
  position: relative;
  z-index: 1;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: var(--accent);
  animation: splash-spin 0.8s linear infinite;
}
@keyframes splash-spin {
  to { transform: rotate(360deg); }
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  /* Locks the outer page so only .content (below) scrolls — without this,
     some mobile WebViews (especially in Telegram's fullscreen mode) let the
     whole document rubber-band/scroll instead of just the inner area. */
  overflow: hidden;
  position: fixed;
  inset: 0;
  overscroll-behavior: none;
  background-color: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
}

.app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  max-width: 480px;
  margin: 0 auto;
  overflow: hidden;
}

.topbar {
  position: relative;
  padding: calc(16px + var(--tg-content-safe-top, env(safe-area-inset-top, 0px))) 20px 12px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--card-border);
}

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

.brand-title {
  font-size: 17px;
  font-weight: 500;
}

.content {
  flex: 1;
  overflow-y: auto;
  padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
  -webkit-overflow-scrolling: touch;
}

/* ---- Tab bar ---- */
.tabbar {
  flex-shrink: 0;
  display: flex;
  border-top: 1px solid var(--card-border);
  background: var(--bg-elevated);
  padding-bottom: env(safe-area-inset-bottom);
}

.tab-btn {
  position: relative;
  flex: 1;
  background: none;
  border: none;
  color: var(--text-dim);
  padding: 10px 4px 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.tab-icon { width: 22px; height: 22px; }

.tab-btn.active {
  color: var(--accent-2);
}

.tab-dot {
  display: none;
  position: absolute;
  top: 6px;
  left: 50%;
  margin-left: 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--danger);
  border: 1.5px solid var(--bg-elevated);
}
.tab-dot.show { display: block; }

/* Small inline dot used on chat chips and support-inbox rows to flag an
   unread conversation — deliberately reuses .tab-dot's color, not its
   absolute positioning. Named separately from .unread-dot below (home.js's
   own, older, teal announcement-unread dot) — same-name classes with
   different colors would just silently fight over the cascade. */
.notice-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--danger);
  margin-left: 5px;
  vertical-align: middle;
}

/* ---- Shared card/list styles ---- */
.card {
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 12px;
}

/* ---- 当日佣金 dashboard (推广 page) ---- */

.commission-dash-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
}
.commission-dash-arrow {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 20px;
  line-height: 1;
  padding: 4px 8px;
  cursor: pointer;
}
.commission-dash-date { font-size: 14px; font-weight: 600; }

.commission-dash-total-label {
  text-align: center;
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 14px;
}
.commission-dash-total-value {
  text-align: center;
  font-size: 34px;
  font-weight: 800;
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}

.commission-dash-note {
  text-align: center;
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 4px;
}

.commission-dash-rows {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--card-border);
}
.commission-dash-row {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  padding: 6px 0;
}
.commission-dash-row span:first-child { color: var(--text-dim); }
.commission-dash-row span:last-child { font-weight: 600; font-variant-numeric: tabular-nums; }
.commission-dash-rate { font-size: 11px; opacity: 0.75; }

.commission-dash-footer {
  display: flex;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--card-border);
}
.commission-dash-footer-item { flex: 1; text-align: center; }
.commission-dash-footer-value { font-size: 18px; font-weight: 700; }
.commission-dash-footer-label { font-size: 11px; color: var(--text-dim); margin-top: 2px; }

.section-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 14px 4px 6px;
}

.section-title:first-child { margin-top: 0; }

.empty-state {
  text-align: center;
  color: var(--text-dim);
  font-size: 13px;
  padding: 30px 10px;
}

/* ---- Home / notifications ---- */
.notif-item {
  position: relative;
  display: flex;
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--card-border);
}
.notif-item:last-child { border-bottom: none; }

.notif-icon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: var(--bg-elevated);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  flex-shrink: 0;
}

.notif-body { flex: 1; min-width: 0; }

/* Floats over the content instead of taking up its own flex column, so the
   announcement image/text can use the row's full width. */
.notif-delete {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.notif-delete:hover { background: rgba(255,92,114,0.4); color: #fff; }

.notif-title {
  font-size: 14px;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 6px;
}

.notif-text {
  font-size: 13px;
  color: var(--text-dim);
  margin-top: 3px;
  line-height: 1.4;
}

.notif-time {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 4px;
}

.notif-media {
  display: block;
  width: 100%;
  max-height: 220px;
  object-fit: cover;
  border-radius: 10px;
  margin-top: 8px;
  border: 1px solid var(--card-border);
  background: #000;
}

.notif-media-img { cursor: zoom-in; }

/* Announcement photo: stands alone with its own rounded corners (no card
   background behind it) — only the delete button (staff/admin) overlays it. */
.notif-media-wrap { position: relative; }
.notif-media-wrap .notif-media {
  margin-top: 0;
  border: none;
  border-radius: 0;
  /* The base .notif-media rule crops to fill a fixed box (object-fit:
     cover) — fine for a landscape screenshot, but an admin-uploaded promo
     poster is often a tall portrait image, and cover was slicing its top
     and bottom off. object-fit: contain instead means the whole photo is
     always visible, just letterboxed rather than cropped when its aspect
     ratio doesn't match this box — and unlike leaving height fully
     unconstrained, capping it here is what keeps the rest of Home (热门游戏,
     我的账户, ...) fitting on one screen without a promo banner alone
     pushing everything else below the fold. */
  width: 100%;
  height: 190px;
  object-fit: contain;
}

/* ---- Home / announcement carousel ----
   Bleeds out of .content's own 16px side padding so the photo runs flush to
   both screen edges — a rectangle, not a card floating in a padded frame. */
.announce-carousel-wrap { margin: 0 -16px 8px; }

.announce-carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.announce-carousel::-webkit-scrollbar { display: none; }

.announce-slide {
  flex: 0 0 100%;
  scroll-snap-align: start;
  min-width: 0;
}

.announce-slide .notif-item {
  display: flex;
  flex-direction: column;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  border-bottom: none;
}

/* No card background here — the photo carries its own rounded corners
   (see .notif-media-wrap .notif-media above), and the time caption below it
   is plain text with no box around it. A legacy title/text-only announcement
   with no photo (from before announcements became photo-only) still gets a
   little breathing room in place of the missing image. */
.announce-slide .notif-body {
  padding: 8px 16px 0;
}
.announce-slide .notif-item:not(:has(.notif-media-wrap)) .notif-body {
  padding-top: 4px;
}

.announce-dots {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin-top: 4px;
}

.announce-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--card-border);
  cursor: pointer;
  transition: all 0.2s ease;
}

.announce-dot.active {
  width: 18px;
  background: var(--accent-2);
}

.unread-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-2);
  flex-shrink: 0;
  margin-bottom: 2px;
}

/* ---- 热门游戏 (Home) — see renderHotGamesSection in home.js ---- */
.hot-games-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.hot-game-card {
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.hot-game-banner {
  aspect-ratio: 16 / 9;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hot-game-banner img { width: 100%; height: 100%; object-fit: cover; }
.hot-game-body { padding: 6px 6px 8px; }
.hot-game-name {
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hot-game-sub { font-size: 9px; color: var(--text-dim); margin-top: 1px; }
.hot-game-actions { display: flex; gap: 4px; margin-top: 6px; }
.hot-game-enter {
  flex: 1;
  min-width: 0;
  border: none;
  border-radius: 8px;
  padding: 6px 0;
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hot-game-enter:active { transform: scale(0.97); }

/* ---- Wallet ---- */
.balance-card {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border-radius: var(--radius);
  padding: 22px 20px;
  margin-bottom: 16px;
}

.balance-label {
  font-size: 12px;
  color: rgba(255,255,255,0.85);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.balance-value {
  font-size: 32px;
  font-weight: 800;
  color: #fff;
  margin-top: 6px;
}

.balance-frozen {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.85);
  margin-top: 6px;
}

.wallet-actions {
  display: flex;
  gap: 12px;
  margin-top: 18px;
}

.wallet-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: none;
  border-radius: 12px;
  padding: 12px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  background: rgba(255,255,255,0.18);
  color: #fff;
}

.wallet-btn svg { flex-shrink: 0; }

.wallet-btn:active { transform: scale(0.97); }
.wallet-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.history-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--card-border);
}
.history-item:last-child { border-bottom: none; }

.proof-thumb {
  display: block;
  width: 100%;
  max-height: 160px;
  object-fit: cover;
  border-radius: 10px;
  margin: 4px 0 12px;
  cursor: zoom-in;
  border: 1px solid var(--card-border);
}

/* 充值 payment-method picker — bank/TNG/USDT details shown before the
   proof upload, and the same rows reused in the admin settings modal. */
.payment-detail-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 0;
}
.payment-detail-row + .payment-detail-row { border-top: 1px solid var(--card-border); }
.payment-detail-text {
  font-size: 14px;
  color: var(--text);
  word-break: break-all;
}
.payment-detail-label {
  color: var(--text-dim);
  margin-right: 8px;
}
.payment-copy-btn {
  flex-shrink: 0;
  padding: 4px 12px;
  font-size: 13px;
}
.payment-qr-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 4px 0 10px;
}
.payment-qr-img {
  display: block;
  width: 160px;
  height: 160px;
  object-fit: cover;
  border-radius: 10px;
  margin: 0 auto 10px;
  border: 1px solid var(--card-border);
}
.payment-qr-tap { cursor: zoom-in; }
.payment-qr-hint {
  font-size: 12px;
  color: var(--text-dim);
  text-align: center;
  margin-top: 8px;
}

/* USDT tab's own admin-set rate/fee/需支付 card — see renderUsdtRateInfo in
   wallet.js. Sits above the QR/address rows, same card visual language as
   .commission-dash-rows elsewhere. */
.usdt-rate-info {
  background: var(--bg-elevated);
  border-radius: 10px;
  padding: 4px 12px;
  margin-bottom: 12px;
}
.usdt-rate-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  padding: 7px 0;
}
.usdt-rate-row + .usdt-rate-row { border-top: 1px solid var(--card-border); }
.usdt-rate-row span:first-child { color: var(--text-dim); }
.usdt-rate-row b { font-variant-numeric: tabular-nums; }
.usdt-pay-row b { color: var(--accent); font-size: 15px; }

.history-icon {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  flex-shrink: 0;
}
.history-icon.topup { background: rgba(52,199,123,0.15); color: var(--success); }
.history-icon.withdraw { background: rgba(255,92,114,0.15); color: var(--danger); }

/* 待处理申请: the requesting user's avatar, tappable to jump straight into
   a support-chat conversation with them (see openSupportThreadForUser in
   chat.js). Stripped of default button chrome so it reads as just the
   avatar, same footprint as .history-icon it replaces. */
.request-avatar-btn {
  flex-shrink: 0;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  border-radius: 50%;
  line-height: 0;
}
.request-avatar-btn:active { transform: scale(0.94); }
/* freeze/game_result: the 抢庄牛牛 hold + settlement pair. Gold rather than
   green/red — a freeze isn't a real gain or loss yet, and a game_result's
   sign already gets its own up/down color on the amount itself. */
.history-icon.freeze,
.history-icon.game_result { background: rgba(232,180,90,0.15); color: #e8b45a; }
/* turnover_rebate/referral_commission: always a gain, so green like a
   topup — these are incentive spend, not part of the game's own result. */
.history-icon.turnover_rebate,
.history-icon.referral_commission { background: rgba(52,199,123,0.15); color: var(--success); }

.history-body { flex: 1; min-width: 0; }
.history-type { font-size: 14px; font-weight: 700; }
.history-time { font-size: 11px; color: var(--text-dim); margin-top: 2px; }

.history-amount { font-size: 14px; font-weight: 700; }
.history-amount.topup { color: var(--success); }
.history-amount.withdraw { color: var(--danger); }
.history-amount.freeze { color: #e8b45a; }
.history-amount.up { color: var(--success); }
.history-amount.down { color: var(--danger); }

.status-pill {
  font-size: 10px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  margin-top: 4px;
  display: inline-block;
}
.status-pill.pending { background: rgba(255,193,7,0.15); color: #ffc107; }
.status-pill.approved { background: rgba(52,199,123,0.15); color: var(--success); }
.status-pill.rejected { background: rgba(255,92,114,0.15); color: var(--danger); }
.status-pill.staff { background: rgba(111,107,255,0.15); color: var(--accent); }

.appeal-btn {
  margin-top: 6px;
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  background: rgba(111,107,255,0.12);
  border: none;
  border-radius: 999px;
  padding: 3px 10px;
  cursor: pointer;
  display: inline-block;
}

/* ---- Chat ---- */
.chat-page { position: relative; display: flex; flex-direction: column; height: 100%; overflow: hidden; }

/* Edge tab that slides out the group-info sidebar — sits on the right edge
   of the conversation, half-tucked in like a pull tab. Stays visible (rather
   than disappearing) once open, sliding over to sit against the panel's own
   left edge so it doubles as the close control. */
.group-sidebar-tab {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  width: 20px;
  height: 48px;
  border-radius: 8px 0 0 8px;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-right: none;
  color: var(--text-dim);
  font-size: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none;
  z-index: 10;
  transition: right 0.25s ease;
}
.group-sidebar-tab.open { right: 40%; }

.group-sidebar-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  z-index: 8;
}
.group-sidebar-backdrop.show { opacity: 1; pointer-events: auto; }

.group-sidebar-panel {
  position: absolute;
  top: 20px;
  right: 0;
  width: 40%;
  box-sizing: border-box;
  background: var(--bg-elevated);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 12px;
  transform: translateX(100%);
  transition: transform 0.25s ease;
  z-index: 9;
}
.group-sidebar-panel.open { transform: translateX(0); }

/* Square tiles, 2 per row: 排行榜/对战记录/每日奖励 for everyone, plus a 4th
   (抢庄牛牛) for staff/admin in a group with a table — see renderGroupSidebar
   in chat.js. 3 tiles just leaves the grid's last cell empty, which reads
   fine rather than needing a different layout of its own. */
.sidebar-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.sidebar-grid-btn {
  /* No more aspect-ratio:1 — the tile now holds an icon badge AND a label
     underneath, which a strict square can't always fit at this panel's
     narrow width (40% of the screen); height instead follows the content,
     via padding, so a 2-line label never gets clipped. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 6px;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  color: var(--text);
  cursor: pointer;
}
.sidebar-grid-btn:active { background: var(--bg-elevated); transform: scale(0.97); }

/* Icon badge — a gold-gradient circle behind the SVG, echoing the
   reference's illustrated icon tiles without needing custom artwork. */
.sidebar-grid-icon {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  flex-shrink: 0;
}

/* Admin-uploaded artwork tile — see SidebarIconSettings / sidebarIconUrls
   in rewards.js and the sidebarTile/tile() comment on why this replaces
   the icon-badge+label pair above entirely rather than sitting inside it:
   the image already is a full badge-with-text design, so it fills the
   whole tile edge to edge instead of being cropped into the small circle. */
.sidebar-grid-btn-custom {
  display: block;
  padding: 0;
  overflow: hidden;
  aspect-ratio: 4 / 5;
}
.sidebar-grid-custom-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Label banner under the icon — the reference's ribbon label, baked into
   the tile instead of only a hover title so it's readable at a glance.
   Narrow tiles (this panel is only 40% of the screen) can't always fit
   4 CJK characters on one line at a comfortable size, so this wraps
   rather than overflowing or forcing the font down further. */
.sidebar-grid-label {
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--accent);
  padding: 2px 8px;
  border-radius: 10px;
  background: rgba(201, 162, 75, 0.12);
  border: 1px solid rgba(201, 162, 75, 0.35);
  text-align: center;
}

/* Small round "×" — used both on a group row in the chat list and, via
   .chat-header-delete, floated to the end of a group conversation's header. */
.group-chip-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(0,0,0,0.18);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
}

.group-chip-delete:hover { background: rgba(255,92,114,0.4); }

.chat-header-delete { margin-left: auto; }

.chat-menu-btn {
  margin-left: auto;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 8px;
}

.chat-menu-dropdown {
  position: fixed;
  z-index: 50;
  min-width: 140px;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}

.chat-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 12px 16px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--card-border);
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
}
.chat-menu-item:last-child { border-bottom: none; }
.chat-menu-item:active { background: var(--bg-elevated); }
.chat-menu-item.danger { color: var(--danger); }

.chat-page .subpage-header { flex-shrink: 0; }

/* Group header's avatar is the small (38px) size, not the big profile one —
   scale the edit badge down to match instead of the default 24px. */
.chat-page .avatar-edit-btn {
  width: 16px;
  height: 16px;
  border-width: 1.5px;
}

#chatBody {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
}

/* A sidebar info page (群组信息/群公告/群成员/群组统计) — plain block content,
   not the flex chat-conversation layout, so it relies on #chatBody's own
   overflow-y for scrolling. */
.subpage-back-inline {
  display: block;
  background: none;
  border: none;
  color: var(--accent-2);
  font-size: 14px;
  font-weight: 600;
  padding: 0 0 14px;
  cursor: pointer;
}

.chat-conversation {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Long-press anywhere here means "reply" (see the pointerdown handler in
     renderConversationBody), never "select text" — without this, a
     long-press landing on a message, the gap between two, a day divider, or
     even the reply bar itself could still trigger the phone's own native
     selection/copy callout, which shows up as a stray empty bar until it
     finishes drawing. #chatInput keeps its own native text editing either
     way — browsers give form controls independent selection behavior that
     an ancestor's user-select: none does not reach. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.support-header {
  flex-shrink: 0;
  font-weight: 700;
  font-size: 14px;
  color: var(--accent-2);
  padding-bottom: 10px;
  border-bottom: 1px solid var(--card-border);
  margin-bottom: 10px;
}

.inbox-item {
  display: flex;
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--card-border);
  cursor: pointer;
}
.inbox-item:last-child { border-bottom: none; }
.inbox-item:hover .notif-title { color: var(--accent-2); }

.chat-messages {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-bottom: 8px;
  overflow-y: auto;
  /* Slim, dim scrollbar: the platform default is a wide light bar that sits
     on top of the message cards' right edge in a desktop browser, which
     reads as content running into the frame. Mobile draws nothing either
     way, so this only ever helps. */
  scrollbar-width: thin;
  scrollbar-color: var(--card-border) transparent;
}
.chat-messages::-webkit-scrollbar { width: 6px; }
.chat-messages::-webkit-scrollbar-track { background: transparent; }
.chat-messages::-webkit-scrollbar-thumb {
  background: var(--card-border);
  border-radius: 999px;
}

.msg-row { display: flex; gap: 8px; align-items: flex-end; }
.msg-row.mine { justify-content: flex-end; }
.msg-row.mine .msg-avatar { display: none; }

.msg-bubble {
  max-width: 75%;
  padding: 10px 13px;
  border-radius: 14px;
  font-size: 14px;
  line-height: 1.4;
  background: var(--card);
  border: 1px solid var(--card-border);
}

.msg-row.mine .msg-bubble {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  border: none;
}

.msg-sender {
  font-size: 11px;
  font-weight: 700;
  color: var(--accent-2);
  margin-bottom: 3px;
}

.msg-row.mine .msg-sender { display: none; }

.msg-time {
  font-size: 10px;
  color: var(--text-dim);
  margin-top: 4px;
  text-align: right;
}
.msg-row.mine .msg-time { color: rgba(255, 255, 255, 0.75); }

.chat-day-divider {
  align-self: center;
  margin: 6px 0 2px;
  padding: 3px 12px;
  font-size: 11px;
  color: var(--text-dim);
  background: var(--card);
  border-radius: 999px;
}

/* @mention dropdown — sits right above the input bar, scrollable once it
   has more matches than fit on screen. Built/torn down on every keystroke
   (see renderConversationBody's "input" listener on #chatInput), so this
   only needs to style whatever's currently inside it. */
.mention-picker {
  flex-shrink: 0;
  max-height: 160px;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  margin-bottom: 8px;
}
.mention-picker-item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border: none;
  background: none;
  color: var(--text);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.mention-picker-item:active { background: var(--bg-elevated); }
.mention-picker-item + .mention-picker-item { border-top: 1px solid var(--card-border); }

/* Reply-to bar — appears above the input once a message is long-pressed
   (see the pointerdown handler in renderConversationBody), same slot as
   .mention-picker above but never shown at the same time as it. */
.reply-bar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  margin-bottom: 8px;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-left: 3px solid var(--accent-2);
  border-radius: var(--radius);
}
/* The class's own `display: flex` above otherwise beats the browser's
   built-in `display: none` for the `hidden` attribute (author CSS always
   wins over the user-agent default, regardless of selector specificity) —
   without this, setting .hidden = true left an empty padded/bordered box
   on screen instead of actually removing it from layout. */
.reply-bar[hidden] {
  display: none;
}
.reply-bar-body { flex: 1; min-width: 0; }
.reply-bar-name {
  font-size: 11px;
  font-weight: 700;
  color: var(--accent-2);
  margin-bottom: 2px;
}
.reply-bar-text {
  font-size: 12px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.reply-bar-cancel {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

/* Quoted preview of the original message, shown inside a reply's own
   bubble — same left-accent-bar idea as .reply-bar above, just sized to
   sit inline above the reply's own text. */
.msg-reply-quote {
  border-left: 3px solid currentColor;
  opacity: 0.75;
  padding: 3px 8px;
  margin-bottom: 6px;
  font-size: 12px;
}
.msg-reply-quote-name { font-weight: 700; }
.msg-reply-quote-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-inputbar {
  flex-shrink: 0;
  display: flex;
  gap: 8px;
  padding-top: 10px;
}

.chat-input {
  flex: 1;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: 999px;
  padding: 11px 16px;
  color: var(--text);
  font-size: 14px;
  outline: none;
}

.chat-send {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  flex-shrink: 0;
}

/* 客服 chat's image-attach button — same 44px circle as .chat-send, just a
   quieter fill since it isn't the primary action. */
.chat-attach-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--card-border);
  background: var(--card);
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}

.chat-attach-btn.disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* An image message drops the bubble's own padding/background so the photo
   sits flush inside its rounded corners, like a normal chat image. The
   second, more specific rule is needed to beat .msg-row.mine .msg-bubble's
   gradient fill for your own sent images. */
.msg-bubble-image {
  padding: 4px;
  background: transparent;
  border: none;
}

.msg-row.mine .msg-bubble.msg-bubble-image {
  background: transparent;
  border: none;
}

.msg-image {
  display: block;
  max-width: 200px;
  max-height: 260px;
  border-radius: 12px;
  cursor: zoom-in;
}

/* ---- Me page ---- */
.profile-header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 6px 0 18px;
}

/* ---- Me page: stats row ---- */
.stats-row {
  display: flex;
  gap: 12px;
  margin-bottom: 12px;
}

.stat-card {
  flex: 1;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 16px;
  text-align: center;
}

.stat-value {
  font-size: 22px;
  font-weight: 800;
  color: var(--accent-2);
}

.stat-label {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 4px;
}

/* ---- Me page: menu list ---- */
.card.menu-list { padding: 4px 0; }

.menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  cursor: pointer;
  border-bottom: 1px solid var(--card-border);
}
.menu-item:last-child { border-bottom: none; }
.menu-item:active { background: var(--bg-elevated); }

.menu-icon {
  width: 20px;
  height: 20px;
  color: var(--accent-2);
  flex-shrink: 0;
}

.menu-label {
  flex: 1;
  font-size: 14px;
  font-weight: 600;
}

.menu-arrow {
  color: var(--text-dim);
  font-size: 20px;
  line-height: 1;
}

/* Pending-count badge on a menu item (e.g. 实名认证审核) — sits between the
   label and the arrow, only shown while something actually needs review. */
.menu-count-badge {
  flex-shrink: 0;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

/* ---- Me page: sub-page header (推广/交易记录/设置) ---- */
.subpage-header {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 16px;
}

.subpage-back {
  background: none;
  border: none;
  color: var(--text);
  font-size: 28px;
  line-height: 1;
  cursor: pointer;
  padding: 0 6px 4px 0;
}

.subpage-title {
  font-size: 17px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Game-group conversation header ("暴富牛牛" etc.) — bleeds edge-to-edge and
   carries a dark felt-and-gold treatment (radial gold glow + faint dot
   texture) so the room title reads as the game's own banner rather than a
   plain list-page header. Only applied via variant:"group" in
   renderChatShell — support/inbox/me.js sub-pages keep the plain header. */
.subpage-header-game {
  position: relative;
  margin: -16px -16px 16px;
  padding: 18px 16px 14px;
  background:
    radial-gradient(circle at 18% -30%, rgba(201, 162, 75, 0.35), transparent 55%),
    radial-gradient(circle at 100% 120%, rgba(107, 114, 128, 0.25), transparent 60%),
    linear-gradient(135deg, #1d1a10 0%, #16151a 45%, #0a0a0b 100%);
  border-bottom: 1px solid var(--card-border);
  border-radius: 0 0 18px 18px;
  overflow: hidden;
}

.subpage-header-game::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(201, 162, 75, 0.16) 1px, transparent 1px);
  background-size: 15px 15px;
  opacity: 0.6;
  pointer-events: none;
}

/* Staff/admin-uploaded banner (see openBackgroundModal in chat.js) replaces
   the designed gradient's background-image inline — this just fits the
   photo and drops the dot texture, which would fight a real image. */
.subpage-header-game-photo {
  background-size: cover;
  background-position: center;
}

.subpage-header-game-photo::before {
  display: none;
}

.subpage-header-game .subpage-title {
  background: linear-gradient(90deg, #f2f2f0 0%, #e6cd94 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  letter-spacing: 0.2px;
}

.avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  flex-shrink: 0;
  overflow: hidden;
}

img.avatar, img.msg-avatar {
  object-fit: cover;
  display: block;
}

.avatar-fallback {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  font-weight: 800;
  color: #fff;
}

.avatar-sm {
  width: 38px;
  height: 38px;
}
.avatar-sm.avatar-fallback { font-size: 15px; }

.avatar-wrap {
  position: relative;
  flex-shrink: 0;
}

.avatar-edit-btn {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  cursor: pointer;
}

/* Touch 'n Go 实名认证 approved — a small checkmark on the profile avatar
   (我's main view only; never alongside .avatar-edit-btn, which only shows
   on the 设置 subpage's own avatar). Same corner/size as that badge. */
.avatar-verified-badge {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}

.msg-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  flex-shrink: 0;
  overflow: hidden;
  font-size: 13px;
}

/* Rectangular banner preview in openBackgroundModal (chat.js) — same
   avatar-wrap/avatar-edit-btn edit-badge pattern as the round avatars above,
   just widescreen to match the header it previews. */
.bg-preview-wrap {
  position: relative;
  margin: 10px 0 16px;
}

.bg-preview {
  width: 100%;
  aspect-ratio: 16 / 6;
  border-radius: 12px;
  background: var(--bg-elevated);
  background-size: cover;
  background-position: center;
  border: 1px solid var(--card-border);
  display: flex;
  align-items: center;
  justify-content: center;
}

.bg-preview-empty {
  color: var(--text-dim);
  font-size: 13px;
}

.bg-preview-edit-btn {
  bottom: 8px;
  right: 8px;
}

.profile-name { font-size: 18px; font-weight: 800; }
.profile-id { font-size: 12px; color: var(--text-dim); margin-top: 2px; }

.field-row { margin-bottom: 14px; }

.field-label {
  font-size: 12px;
  color: var(--text-dim);
  font-weight: 600;
  margin-bottom: 6px;
}

.field-input {
  width: 100%;
  background: var(--bg-elevated);
  border: 1px solid var(--card-border);
  border-radius: 10px;
  padding: 11px 13px;
  color: var(--text);
  font-size: 14px;
  outline: none;
}

.field-input:focus { border-color: var(--accent); }

.btn-primary {
  width: 100%;
  border: none;
  border-radius: 14px;
  padding: 14px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  margin-top: 4px;
}

.btn-primary:active { transform: scale(0.98); }

/* Mid-submit state (see onSubmitClick in telegram.js) — visibly "busy"
   rather than looking identical to idle, which is what made a slow
   request read as a dead button worth clicking again. */
.btn-primary:disabled, .btn-ghost:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.btn-primary:disabled:active { transform: none; }

.save-toast {
  text-align: center;
  font-size: 12px;
  color: var(--success);
  margin-top: 10px;
  min-height: 16px;
}

/* ---- Modal (topup/withdraw demo) ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 50;
}

.modal-sheet {
  width: 100%;
  max-width: 480px;
  background: var(--bg-elevated);
  border-radius: 20px 20px 0 0;
  padding: 22px 20px calc(22px + env(safe-area-inset-bottom));
  animation: slideUp 0.25s ease;
  /* Capped and internally scrollable rather than growing past the viewport:
     without this, content taller than the screen (e.g. topup's method
     picker — bank's 3 rows vs. TNG/USDT's QR + rows) pushed the sheet's
     TOP off-screen with no way to scroll back to it, so switching methods
     looked like the whole sheet "jumped" — the title was cut off for the
     taller ones and fully visible for the shorter one. Capping height
     keeps the sheet's top (and its title) anchored at the same spot no
     matter which tab is active; only the content inside scrolls. */
  max-height: 85vh;
  overflow-y: auto;
}

@keyframes slideUp {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

.modal-title { font-size: 16px; font-weight: 800; margin-bottom: 4px; }
.modal-title-icon { display: flex; align-items: center; gap: 8px; }
.modal-title-icon svg { color: var(--accent); flex-shrink: 0; }

/* Staff/admin-only "更换图标" button next to a sheet's own title — see
   sidebarIconEditBtnHtml/openSidebarIconUploadModal in rewards.js. */
.icon-edit-btn {
  margin-left: auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--card-border);
  background: var(--card);
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}
.icon-edit-btn svg { color: inherit; }
/* pre-wrap: harmless for a short one-line subtitle (nothing to preserve),
   but load-bearing for a long one (e.g. a group's 游戏介绍 rulebook via
   openGameInfoModal) — without it every line break in that text collapses
   into a single run-on paragraph. */
.modal-sub { font-size: 12px; color: var(--text-dim); margin-bottom: 16px; white-space: pre-wrap; }

.modal-actions { display: flex; gap: 10px; margin-top: 16px; }
.modal-actions .btn-primary { margin-top: 0; }

.btn-ghost {
  flex: 0 0 90px;
  border: 1px solid var(--card-border);
  background: transparent;
  color: var(--text-dim);
  border-radius: 14px;
  font-weight: 700;
  cursor: pointer;
}

.demo-banner {
  font-size: 11px;
  color: #ffc107;
  background: rgba(255,193,7,0.12);
  border: 1px solid rgba(255,193,7,0.3);
  border-radius: 10px;
  padding: 8px 10px;
  margin-bottom: 12px;
}

/* ---- Image lightbox (proof-of-payment full view) ---- */
/* Fully opaque, unlike the shared .modal-overlay's 55%-alpha backdrop —
   this is also used to zoom payment QR codes, and a translucent backdrop
   let whatever sheet/QR was open behind it show through the zoomed image,
   producing visual noise that could confuse a scanner reading a screenshot
   of it. */
.modal-overlay.lightbox { align-items: center; background: var(--bg); }

.lightbox-img {
  max-width: 92vw;
  max-height: 85vh;
  border-radius: 12px;
  object-fit: contain;
}

/* ---- 抢庄牛牛 ---- */

/* The console sheet: the same bottom sheet as every other modal, capped so a
   long player list scrolls inside it instead of pushing the actions off
   screen. It sits over the conversation, which stays visible above it. */
.game-console {
  max-height: 84vh;
  overflow-y: auto;
}

.game-console-close {
  flex: none;
  width: 100%;
  padding: 12px;
  margin-top: 16px;
  font-size: 14px;
}

/* A claim tapped straight from a red-envelope card has no console to put an
   error in, so it gets one of these instead. Same shape/position as
   .app-toast below (see showToast in telegram.js) — one floating pill style
   shared by both a game-action error and any other inline edit with
   nowhere of its own to report a failure. */
.game-toast,
.app-toast {
  position: fixed;
  left: 50%;
  bottom: calc(84px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 60;
  max-width: 80%;
  padding: 10px 16px;
  border-radius: 999px;
  background: rgba(0,0,0,0.82);
  border: 1px solid var(--card-border);
  color: var(--text);
  font-size: 13px;
  animation: slideUp 0.18s ease;
}

.game-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 6px 0 14px;
}

.game-phase { font-size: 20px; font-weight: 700; }
.game-hint { font-size: 12px; color: var(--text-dim); margin-top: 3px; }

.game-head-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

.game-countdown {
  font-size: 26px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-2);
  min-width: 56px;
  text-align: right;
}

/* Staff/admin only — toggles the table auto-restarting rounds back to back
   without waiting for a real player's first bid. */
.game-autorun-btn {
  font-size: 11px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid var(--card-border);
  background: var(--card);
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
}
.game-autorun-btn.on {
  border-color: transparent;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
}
/* Envelope-payout's "off" state — real payout is the unremarkable default,
   so this flags the exception (no money moving) instead. */
.game-autorun-btn.warn {
  border-color: transparent;
  background: var(--danger);
  color: #fff;
}

.game-dealer { display: flex; align-items: center; gap: 12px; }
.game-dealer-name { font-size: 15px; font-weight: 600; }
.game-dealer-bid { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.game-dealer-empty { text-align: center; color: var(--text-dim); font-size: 13px; }

.game-action { display: flex; gap: 8px; align-items: center; }
.game-action .field-input { flex: 1; }
.game-action .btn-primary { width: auto; flex: 0 0 88px; margin-top: 0; }

.game-note {
  font-size: 12px;
  color: var(--text-dim);
  margin: 8px 2px 0;
  line-height: 1.5;
}

.game-error { font-size: 12px; color: var(--danger); margin: 6px 2px 0; min-height: 16px; }
.game-error.success { color: var(--success); }

.game-claim { font-size: 17px; padding: 18px; }

.game-player {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--card-border);
}
.game-player:last-child { border-bottom: none; }
.game-player-name { flex: 1; font-size: 14px; }
.game-player-stake { font-size: 13px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.game-player-claim { width: 22px; text-align: right; font-size: 12px; }

.game-you { text-align: center; }
.game-you-head { font-size: 15px; font-weight: 600; }
.game-you-stake { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
.game-you-delta {
  font-size: 28px;
  font-weight: 700;
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.game-you.win .game-you-delta { color: var(--success); }
.game-you.lose .game-you-delta,
.game-you.noclick .game-you-delta { color: var(--danger); }
.game-you-balance { font-size: 12px; color: var(--text-dim); margin-top: 4px; font-variant-numeric: tabular-nums; }
.game-you-short { font-size: 12px; color: var(--text-dim); margin-top: 8px; }

.game-result-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--card-border);
}
.game-result-row:last-child { border-bottom: none; }
.game-result-name { flex: 1; font-size: 14px; }
.game-result-label { font-size: 12px; color: var(--text-dim); }
.game-result-delta { font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }
.game-result-delta.up { color: var(--success); }
.game-result-delta.down { color: var(--danger); }

.game-balance {
  text-align: center;
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 14px;
  font-variant-numeric: tabular-nums;
}

/* ---- The table's announcements, as cards in the message list ----
   Plain-text style, matching the reference bot: no icon chip, no colored
   left stripe, no colored win/lose badges. What's left is the plain-text
   content plus the banner image, the countdown pill, and the claim button.

   ONE SHAPE, every phase. Every card is header + body + footer in that
   order, header always 第 N 局 · phase / countdown pill — a repeated shape
   is what keeps a scrolled conversation reading as one table running
   through the chat rather than a pile of differently-shaped notices.

   Everything time-varying inside them (the countdown, the claim button,
   the .expired collapse) is updated in place by paintGameCards. */

/* Every real announcement (not the void/cancelled-round pill, see below) is
   wrapped in .game-msg-row: the game table's own avatar in the same 30px
   gutter an ordinary received message uses, its name above the card the
   same way .msg-sender sits above .msg-bubble — so the whole table reads as
   one persona (staff/admin-configurable, see chat.js's 游戏机器人 menu item)
   posting into the room, not an unattributed system notice. */
.game-msg-row {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  /* Stopping short of the right edge, as a percentage so a 320px phone
     still gives the settlement grid its columns back. */
  margin-right: 8%;
}
.game-msg-row .msg-avatar { margin-top: 17px; } /* level with the card, not the name label above it */

.game-msg-col { flex: 1; min-width: 0; }

.game-msg-sender-name {
  font-size: 11px;
  font-weight: 700;
  color: var(--accent-2);
  margin: 0 0 3px 2px;
}

.game-msg {
  position: relative;
  border: 1px solid var(--card-border);
  background: var(--card);
  border-radius: 14px;
  padding: 13px 15px;
  font-size: 13px;
  transition: opacity 0.18s ease, background 0.18s ease;
}

/* ---- phase banners (富二代牛牛 mascot art, one per phase transition) ---- */
/* Bleeds out of .game-msg's own 13px/15px padding so the photo sits flush
   against the card's edges instead of floating in a padded frame — rounded
   on just the top two corners since real card content still follows below. */
.game-msg-banner {
  display: block;
  width: calc(100% + 30px);
  margin: -13px -15px 10px;
  /* Matches the banner art's own 2000x667 pixel size exactly (3:1) —
     object-fit: cover only avoids cropping when the box ratio equals the
     image's own. */
  aspect-ratio: 3 / 1;
  object-fit: cover;
  border-radius: 13px 13px 0 0;
}

/* betting_open/claim_closed cards are nothing BUT the banner — drop the
   card's own padding/border/background/stripe entirely so the photo IS the
   bubble, full width and rounded on all four corners, not a panel with an
   image inset inside it. */
.game-msg-betting-open,
.game-msg-claim-closed {
  padding: 0;
  border: none;
  background: transparent;
  box-shadow: none;
}
.game-msg-betting-open .game-msg-banner,
.game-msg-claim-closed .game-msg-banner {
  width: 100%;
  margin: 0;
  border-radius: 13px;
}

/* Plain lines ("庄家：X · 余额 Y"), not a label/value grid — matches the
   settlement card's own flowing-text style right below it. */
.game-msg-summary-row {
  font-size: 12px;
  padding: 4px 0;
  font-variant-numeric: tabular-nums;
}

/* The console's own 本局玩家 list, reused here — same .game-player rows,
   just sitting in a chat card instead of the console sheet. The console's
   own copy keeps its avatar+name/stake row layout (out of scope here); this
   overrides it to a single flowing line, name · stake, for just this card. */
.game-msg-summary-players {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--card-border);
}
.game-msg-summary-players-title {
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 4px;
}
.game-msg-summary-players .game-player {
  display: block;
  padding: 5px 0;
  font-size: 13px;
  border-bottom: none;
}

/* ---- header ---- */

.game-msg-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* min-width:0 so a long note ellipsises instead of shoving the timer pill
   off the card — the pill is the one thing that must never move. */
.game-msg-titles { flex: 1; min-width: 0; }

.game-msg-title {
  font-size: 14px;
  font-weight: 700;
  line-height: 1.25;
}

/* Wraps rather than ellipsises: on a narrow phone this line carries the bid
   floor and the pot, and truncating "出价 500.00 以上" is worse than a second
   line. The timer pill is protected from the wrap by its own flex-shrink:0
   plus min-width:0 on .game-msg-titles, so it never gets pushed off. */
.game-msg-note {
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.35;
  margin-top: 2px;
}

.game-msg-timer {
  flex-shrink: 0;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--bg-elevated);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.game-msg:not(.expired) .game-msg-timer {
  background: rgba(107, 114, 128, 0.18); /* var(--accent-2) at low opacity */
  color: var(--accent-2);
}

.game-msg-timer.urgent {
  background: rgba(255, 92, 114, 0.16);
  color: var(--danger);
}

/* ---- body + footer ---- */

.game-msg-body {
  color: var(--text-dim);
  line-height: 1.5;
  margin-top: 8px;
}

.game-msg-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  font-size: 12px;
  color: var(--text-dim);
}

/* 庄家走势 — this dealer's own last 8 envelope draws, on the 结算 card. A
   plain wrapping row rather than .game-msg-foot's single line: 8 amounts
   plus arrows routinely runs longer than a phone-width card. */
.game-dealer-trend {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.game-dealer-trend-chain {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.game-dealer-trend-chain .current { color: var(--accent-2); font-weight: 700; }
.game-dealer-trend-arrow { color: var(--text-dim); font-size: 10px; }

.game-msg-btn {
  border: none;
  border-radius: 999px;
  padding: 9px 20px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.game-msg-btn:active { transform: scale(0.97); }
.game-msg-btn:disabled {
  background: var(--bg-elevated);
  color: var(--text-dim);
  cursor: default;
  transform: none;
}

/* ---- history ----
   A phase that has passed keeps its header and nothing else. The prompt and
   its dead button are pure noise once the clock has run out, and three
   spent rounds of them in a row was most of the clutter on screen. */

.game-msg.expired { opacity: 0.55; }
.game-msg.expired .game-msg-body,
.game-msg.expired .game-msg-note,
.game-msg.expired .game-msg-foot,
.game-msg.expired .game-msg-dealer-row { display: none; }

/* Exception: the envelope card's footer holds the claim button, which is
   where "已抢到 8.88 · 豹子" is reported back to the player. Keep it, but
   shrink it from a full-width call to action to a compact receipt chip. */
.game-msg-envelope.expired .game-msg-foot { display: flex; margin-top: 10px; }
.game-msg-envelope.expired .envelope-open {
  width: auto;
  padding: 6px 14px;
  font-size: 12px;
  border-radius: 999px;
}

/* ---- 庄家 ---- */

.game-msg-dealer-row {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-top: 11px;
  padding: 9px 11px;
  border-radius: 11px;
  background: var(--bg-elevated);
}
.game-msg-dealer-name { font-size: 14px; font-weight: 600; }
.game-msg-dealer-bid {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

/* ---- the red envelope ----
   The one card people actually tap. Red and gold on purpose: it has to be
   findable in a scrolling conversation. Once spent it steps back down to a
   muted maroon, so a dead envelope stops being the loudest thing on screen. */

.game-msg-envelope {
  background: linear-gradient(160deg, #b4241f, #7d1512);
  border-color: #e8b45a;
}
.game-msg-envelope .game-msg-timer,
.game-msg-envelope:not(.expired) .game-msg-timer {
  background: rgba(0, 0, 0, 0.25);
  color: #ffe6a8;
}
.game-msg-envelope .game-msg-timer.urgent {
  background: #ffe6a8;
  color: #7d1512;
}

.game-msg-envelope.expired {
  background: linear-gradient(160deg, #4a1a17, #2e100e);
  border-color: rgba(232, 180, 90, 0.35);
  opacity: 0.8;
}

/* Nothing printed on the packet itself but the packet — round/pool/player
   info moved out to the envelope_info card right above it (see
   .game-msg-envelope-info below). The timer stays, tucked into its own
   corner row, since players still need to see the claim window closing. */
.game-msg-envelope-head {
  display: flex;
  justify-content: flex-end;
}
.envelope-pure {
  text-align: center;
  padding: 10px 0 4px;
}
.envelope-pure-icon { font-size: 34px; line-height: 1; }
.envelope-pure-title {
  margin-top: 8px;
  font-size: 16px;
  font-weight: 800;
  color: #ffe6a8;
}
.envelope-pure-caption {
  margin-top: 3px;
  font-size: 12px;
  color: rgba(255, 230, 168, 0.78);
}

.envelope-open {
  width: 100%;
  padding: 13px;
  border-radius: 12px;
  background: linear-gradient(135deg, #ffd77a, #e8a93c);
  color: #6b1310;
  font-size: 15px;
  font-weight: 800;
}

.envelope-open:disabled {
  background: rgba(0, 0, 0, 0.28);
  color: rgba(255, 230, 168, 0.7);
}

/* ---- the red envelope popup ----
   The actual "拆红包" moment, styled like a WeChat packet: same red/gold
   from the chat card, but centered and full-attention rather than inline,
   because this is the one beat in the whole round a player should feel. */

.envelope-popup-overlay { align-items: center; }

.envelope-popup {
  width: 100%;
  max-width: 340px;
  margin: 0 20px;
  padding: 28px 20px 20px;
  border-radius: 20px;
  background: linear-gradient(160deg, #b4241f, #7d1512);
  border: 1px solid #e8b45a;
  text-align: center;
  position: relative;
  animation: envelopeOpen 0.35s cubic-bezier(0.2, 0.8, 0.3, 1.1);
}

@keyframes envelopeOpen {
  from { transform: scale(0.75) rotateX(25deg); opacity: 0; }
  to { transform: scale(1) rotateX(0); opacity: 1; }
}

.envelope-popup-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.25);
  color: rgba(255, 230, 168, 0.85);
  font-size: 13px;
  cursor: pointer;
}

.envelope-popup-icon { font-size: 40px; line-height: 1; }

/* Unopened state: bigger, gently bobbing, inviting the tap — the amount
   stays hidden until the 开 button below is actually pressed. */
.envelope-popup-icon-unopened {
  font-size: 56px;
  margin-top: 4px;
  animation: envelopeBob 1.6s ease-in-out infinite;
}

@keyframes envelopeBob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-4px) rotate(-3deg); }
}

.envelope-popup-open {
  margin-top: 16px;
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 2px solid #ffe6a8;
  background: linear-gradient(160deg, #f3c766, #d99a2b);
  color: #7d1512;
  font-size: 26px;
  font-weight: 800;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

.envelope-popup-open:disabled { opacity: 0.6; cursor: default; }

#envelopePopupContent .game-error {
  margin-top: 10px;
  min-height: 0;
}

.envelope-popup-amount {
  margin-top: 10px;
  font-size: 32px;
  font-weight: 800;
  color: #ffe6a8;
  font-variant-numeric: tabular-nums;
}

.envelope-popup-label {
  margin-top: 2px;
  font-size: 14px;
  font-weight: 700;
  color: #ffe6a8;
}

.envelope-popup-sub {
  margin-top: 6px;
  font-size: 12px;
  color: rgba(255, 230, 168, 0.7);
}

.envelope-popup-list {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px dashed rgba(232, 180, 90, 0.35);
  max-height: 240px;
  overflow-y: auto;
  text-align: left;
}

.envelope-popup-list-title {
  font-size: 12px;
  color: rgba(255, 230, 168, 0.7);
  margin-bottom: 8px;
}

.envelope-popup-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
}

.envelope-popup-row.me { background: rgba(232, 180, 90, 0.12); border-radius: 10px; }

.envelope-popup-row-name {
  flex: 1;
  font-size: 13px;
  font-weight: 600;
  color: #ffe6a8;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.envelope-popup-row-amount {
  font-size: 13px;
  font-weight: 700;
  color: #ffe6a8;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.envelope-popup-row-amount span {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: rgba(255, 230, 168, 0.65);
}

/* ---- settlement ----
   Plain flowing lines per player — name, then hand/stake/outcome/delta all
   inline on one wrapping line, then the balance line under it — rather than
   a two-column grid with the delta pulled hard right. Matches the reference
   bot's plain-text settlement (a name line, an inline result line, a
   before→after line), separated by a blank gap instead of a divider rule. */

.game-result-rows {
  margin-top: 10px;
}

.game-msg-result .game-result-row {
  display: block; /* overrides the base .game-result-row's flex row (used by other pages reusing this class name) */
  padding: 9px 0;
  border-bottom: none; /* blank space, not a divider line, separates entries — see the + selector below */
}
.game-msg-result .game-result-row + .game-result-row { margin-top: 6px; }
.game-msg-result .game-result-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.game-result-hand {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 3px;
}
/* Gold, matching the envelope card it came out of. */
.game-result-env {
  font-size: 12px;
  font-weight: 700;
  color: #e8b45a;
  font-variant-numeric: tabular-nums;
}
.game-result-env.forfeit {
  color: var(--text-dim);
  font-weight: 600;
  text-decoration: line-through;
}
/* Plain word ("牛7"/"对子"/...), not a pill — same treatment as
   .game-result-outcome just below it. */
.game-msg-result .game-result-label {
  font-size: 11px;
  font-weight: 600;
}
/* Won, but the dealer ran dry before reaching them — 喝水. Invisible on the
   card until now, which made a short-paid winner's delta look like a bug. */
.game-result-short {
  color: var(--danger);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.game-msg-result .game-result-delta {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.game-msg-result .game-result-row.dealer .game-result-name { font-weight: 700; }

/* How much they put in, and which side of the delta they landed on — the
   number alone answers "how much", these answer "on what" and "won or
   lost", the two things a bystander scrolling back through chat can't
   otherwise reconstruct from a bare +/-12.34. */
.game-result-stake {
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* A plain word ("赢"/"输"/"平"), not a colored pill — the delta's own +/-
   color already carries which side of the result this is. */
.game-result-outcome {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-dim);
}

/* Before → after, in the same line the delta already sits on the right —
   so "how much I bet, win or lose, and what my balance actually did" all
   read in one glance without doing the arithmetic on the delta yourself. */
.game-result-balance {
  margin-top: 3px;
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Separates the player rows above from the dealer's own line + round-wide
   stats below — a plain rule, not another labeled row, so it reads as a
   break in the list rather than one more piece of data. */
.game-result-divider {
  border-top: 1px dashed var(--card-border);
  margin: 10px 0;
}

.game-result-dealer-stats {
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.game-result-dealer-stats div:not(:first-child) { margin-top: 2px; }

/* ---- 本局取消 ---- */

.game-msg-void {
  /* A cancelled round is a service notice, not an announcement from the
     table: centred pill, out of the indent, no phase stripe. */
  align-self: center;
  margin: 2px 0;
  max-width: 84%;
  text-align: center;
  color: var(--text-dim);
  background: transparent;
  border-style: dashed;
  border-radius: 999px;
  box-shadow: none;
  padding: 7px 15px;
  font-size: 12px;
}
.game-msg-void .game-msg-body { margin-top: 0; }

/* ---- 财务报告 (admin) + rejection reasons ---- */

.report-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--card-border);
  font-size: 14px;
}
.report-row:last-child { border-bottom: none; }
.report-row span { color: var(--text-dim); }
.report-row b { font-variant-numeric: tabular-nums; font-weight: 600; }

.history-reason {
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 4px;
  line-height: 1.5;
}

/* ---- 排行榜 / 对战记录 / 每日奖励 (rewards.js) ---- */

/* A fixed height, not max-height -- this sheet is pinned to the bottom of
   the screen (.modal-sheet's border-radius: 20px 20px 0 0), so a shorter
   tab shrinking it moves its own top edge down, reading as the whole thing
   jumping/scrolling even though nothing actually scrolled. Fixed height
   means a tab with less content (or none yet) just leaves blank space
   inside the same footprint instead of resizing it. */
.rewards-sheet {
  height: 84vh;
  overflow-y: auto;
}

/* 本群奖励图 -- see openRewardGallery/renderRewardGalleryItemsHtml in rewards.js.
   One full-width photo per row (not a cropped thumbnail grid) -- these are
   often dense reward-tier posters meant to be read, not browsed at a glance.
   A generous gap plus each card's own padded frame + header (图片 N · time)
   is what keeps consecutive photos from visually bleeding into one long
   image -- the original edge-to-edge, tightly-stacked layout made two
   distinct uploads read as a single screenshot. */
.reward-gallery-grid {
  display: flex;
  flex-direction: column;
  gap: 22px;
  margin: 10px 0;
}
.reward-gallery-item {
  position: relative;
  border-radius: 12px;
  background: var(--card);
  border: 1px solid var(--card-border);
  padding: 8px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}
.reward-gallery-item-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 0 2px 8px;
}
.reward-gallery-item-index { font-size: 12px; font-weight: 700; }
.reward-gallery-item-time { font-size: 10px; color: var(--text-dim); }
.reward-gallery-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
  cursor: pointer;
}
.reward-gallery-delete {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}
.reward-gallery-delete:hover { background: rgba(255,92,114,0.4); }

/* 排行榜 */
.lb-tabs {
  display: flex;
  gap: 8px;
  margin: 10px 0 2px;
}
.lb-tab {
  flex: 1;
  padding: 8px;
  border-radius: 10px;
  border: 1px solid var(--card-border);
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.lb-tab.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border-color: transparent;
  color: #fff;
}

.topup-amount-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 2px 0 10px;
}
.topup-amount-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 4px;
  border-radius: 10px;
  border: 1px solid var(--card-border);
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.topup-amount-btn.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border-color: transparent;
  color: #fff;
}
.topup-amount-bonus {
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
}
.topup-amount-btn.active .topup-amount-bonus { color: #fff; }

.topup-section-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-dim);
  margin: 16px 0 8px;
}
.topup-section-title:first-of-type { margin-top: 4px; }

.topup-bonus-hint {
  font-size: 12px;
  color: var(--accent);
  margin: -4px 0 10px;
}
.topup-bonus-notice {
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.4;
  margin: -2px 0 10px;
}

.lb-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 4px;
  border-bottom: 1px solid var(--card-border);
}
.lb-row:last-child { border-bottom: none; }
.lb-row.mine { background: rgba(201, 162, 75, 0.12); border-radius: 10px; } /* var(--accent) at low opacity */

.lb-rank {
  width: 22px;
  flex: none;
  text-align: center;
  font-weight: 700;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.lb-rank.top { font-size: 16px; }
.lb-rank.top-1 { color: #f3c766; }
.lb-rank.top-2 { color: #c9ccd8; }
.lb-rank.top-3 { color: #cd8a5a; }

.lb-name {
  flex: 1;
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lb-points {
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Padding-out placeholder rows (see LB_ROW_SLOTS in rewards.js) — same
   layout as a real row so the sheet's height never changes between tabs,
   just faded down to read as "not filled yet" rather than real data. */
.lb-row-empty { opacity: 0.35; }
.lb-row-empty .lb-name, .lb-row-empty .lb-points { color: var(--text-dim); font-weight: 500; }
.lb-avatar-blank { background: var(--card-border); }

/* 对战记录 */
.bl-row {
  padding: 10px 4px;
  border-bottom: 1px solid var(--card-border);
  border-left: 3px solid var(--card-border);
  padding-left: 10px;
}
.bl-row:last-child { border-bottom: none; }
.bl-row.win { border-left-color: var(--success); }
.bl-row.lose { border-left-color: var(--danger); }

.bl-row-head {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--text-dim);
}
.bl-row-body {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 4px;
  flex-wrap: wrap;
}
.bl-label { font-size: 14px; font-weight: 700; }
.bl-stake { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.bl-vs { font-size: 12px; color: var(--text-dim); flex: 1; }
.bl-delta { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; margin-left: auto; }
.bl-delta.win { color: var(--success); }
.bl-delta.lose { color: var(--danger); }

/* 每日奖励 */
.dr-tier {
  padding: 10px 12px;
  border-radius: 12px;
  background: var(--card);
  border: 1px solid var(--card-border);
  margin-bottom: 8px;
}
.dr-tier.won { border-color: var(--success); }

.dr-tier-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}
.dr-tier-titlewrap { min-width: 0; }
.dr-tier-name { font-size: 14px; font-weight: 800; }
.dr-tier-sub { font-size: 11px; color: var(--text-dim); margin-top: 1px; }
.dr-tier-rewardwrap { flex: 0 0 auto; text-align: right; }
.dr-tier-reward { font-size: 15px; font-weight: 800; color: var(--accent); white-space: nowrap; }
.dr-tier-slots { font-size: 11px; color: var(--text-dim); margin-top: 1px; }
.dr-tier-detail { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.dr-tier-progress { font-size: 13px; font-weight: 600; margin-top: 6px; }
.dr-tier-note { font-size: 12px; color: var(--text-dim); }
.dr-won-badge { color: var(--success); }

/* 达标玩家 -- who has already met a card's condition */
.dr-winners-label { font-size: 12px; color: var(--text-dim); margin-top: 8px; }
.dr-winners { display: flex; flex-wrap: wrap; gap: 8px 14px; margin-top: 6px; }
.dr-winner { display: flex; align-items: center; gap: 5px; font-size: 12px; }
.dr-winner span {
  max-width: 110px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.avatar-xs { width: 22px; height: 22px; }
.avatar-xs.avatar-fallback { font-size: 10px; }

.dr-editor-group {
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--card-border);
}
.dr-editor-group:last-of-type { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }

.dr-editor-row {
  display: flex;
  gap: 6px;
  align-items: center;
}
.dr-editor-row-secondary { margin-top: 6px; }
.dr-editor-tier {
  flex: 0 0 40px;
  font-size: 13px;
  font-weight: 700;
}
.dr-editor-row .dr-input { flex: 1; min-width: 0; padding: 8px; font-size: 12px; }

/* 庄家奖励/特别奖励 cards -- the requiredCount/targetAmounts "chips" row */
.dr-chips { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0; }
.dr-chip {
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--card-border);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* One chip per required draw/amount — lit as the player actually reaches
   each one, so "1/3 done" reads at a glance instead of only from the
   progress text underneath. */
.dr-chip.lit {
  border-color: transparent;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
}

/* 特别奖励 editor -- pick which of the 9 preset amounts a card requires */
.dr-amount-picker-label { font-size: 12px; opacity: 0.75; margin-bottom: 6px; }
.dr-amount-picker { display: flex; gap: 6px; flex-wrap: wrap; }
.dr-amount-chip {
  position: relative;
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--card-border);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  user-select: none;
}
.dr-amount-chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.dr-amount-chip:has(input:checked) {
  border-color: transparent;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
}

/* 庄家奖励 -- the 做庄 N 次 milestone ladder */
.dealer-ladder { display: flex; flex-direction: column; margin: 10px 0; }
.dealer-ladder-step {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  position: relative;
}
.dealer-ladder-step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 22px;
  bottom: -6px;
  width: 2px;
  background: var(--card-border);
}
.dealer-ladder-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--card-border);
  flex: 0 0 auto;
  z-index: 1;
  background: var(--bg);
}
.dealer-ladder-step.claimed .dealer-ladder-dot { background: var(--success); border-color: var(--success); }
.dealer-ladder-step.next .dealer-ladder-dot { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(201, 162, 75, 0.25); }
.dealer-ladder-label { flex: 1; font-size: 13px; font-weight: 700; }
.dealer-ladder-reward { font-size: 13px; font-weight: 800; color: var(--accent); }
.dealer-ladder-step.claimed .dealer-ladder-reward { color: var(--success); }

.history-detail {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}

/* ---- 机器人玩家 (chat.js's openRobotsModal) ----
   Same tall-scrolling-sheet shape as .rewards-sheet, since this is the same
   kind of "list of admin-editable entries" screen. */
.robots-sheet {
  max-height: 84vh;
  overflow-y: auto;
}

.robot-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--card-border);
}
.robot-row:last-child { border-bottom: none; }

.robot-row-body { flex: 1; min-width: 0; }
.robot-row-body .robot-name-input {
  padding: 6px 8px;
  font-size: 13px;
  font-weight: 600;
}

.robot-row-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 5px;
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.robot-bet-label { display: flex; align-items: center; gap: 3px; }
.robot-bet-input,
.robot-sh-input,
.robot-balance-input {
  width: 40px;
  background: var(--bg-elevated);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  padding: 2px 4px;
  color: var(--text);
  font-size: 11px;
  text-align: center;
}
.robot-balance-label { display: flex; align-items: center; gap: 3px; }
.robot-balance-input { width: 56px; }
.robot-mode-select {
  background: var(--bg-elevated);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  padding: 2px 4px;
  color: var(--text);
  font-size: 11px;
}

/* .game-autorun-btn's own on/off pill, reused verbatim (see style.css's
   game console section) — "游戏中"/"未加入" is just this robot's own
   version of that same auto-run switch. */
.robot-toggle-btn { flex-shrink: 0; }

.robot-delete-btn {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid var(--card-border);
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}
.robot-delete-btn:active { transform: scale(0.94); }

/* Same footprint as .robot-delete-btn (icon-only, 30px square) — whether
   this robot's own row shows on 积分榜 or reads as "隐藏玩家" there. */
.robot-hide-btn {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid var(--card-border);
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
}
.robot-hide-btn:active { transform: scale(0.94); }
.robot-hide-btn.on {
  border-color: transparent;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
}
