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

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);
  /* Whole-app backdrop — see webapp/assets/app-background.svg. Most of it
     sits behind opaque .card/.content elements; it only shows through page
     padding and empty space, which is why it's designed low-contrast. */
  background-image: url("assets/app-background.svg");
  background-size: cover;
  background-position: top center;
  background-repeat: no-repeat;
  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: 18px 4px 8px;
}

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

.section-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 18px 4px 8px;
}
.section-title-row .section-title { margin: 0; }
.section-title-row: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: var(--radius);
  /* The base .notif-media rule caps height at 220px and crops to fill it
     (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. Announcements show one full-width
     slide at a time with nothing competing for the space, so there's no
     need to force a box at all: let the image sit at its own natural
     height so the whole photo is always visible. */
  max-height: none;
  height: auto;
  object-fit: contain;
}

/* Slide position ("1/N"), shown once next to the section title rather than
   on the photo itself — see #announceCounter in home.js. */
.announce-counter {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
}

/* ---- Home / announcement carousel ---- */
.announce-carousel-wrap { margin-bottom: 16px; }

.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 2px;
  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 2px 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: 10px;
}

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

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

.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); }
/* 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 {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 18px;
  font-weight: 700;
  cursor: pointer;
}
.sidebar-grid-btn:active { background: var(--bg-elevated); }

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

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

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

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

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

@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; }
.modal-sub { font-size: 12px; color: var(--text-dim); margin-bottom: 16px; }

.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) ---- */
.modal-overlay.lightbox { align-items: center; }

.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. */
.game-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;
  aspect-ratio: 4 / 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-title { color: #ffe6a8; }
.game-msg-envelope .game-msg-note { color: rgba(255, 230, 168, 0.78); }
.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;
}

.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) ---- */

.rewards-sheet {
  max-height: 84vh;
  overflow-y: auto;
}

/* 排行榜 */
.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;
}

.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-name { font-size: 14px; font-weight: 800; }
.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); }

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

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