/* admin.css — Admin Console (/p/admin-[slug]) shared shell + Login + Overview.
   Design source: WEBSYS_MD/MEMBERSHIP_REQUIREMENTS.md §10 / §15.11. Desktop-first (1280px);
   no mobile stacking yet — see P_DEV_STATUS §6 for the Phase 5 build order. */

/* Body background painted immediately (not dependent on JS mounting
   .adm-shell/.adm-login-page) — every other /p page already does this;
   admin.css was the gap, causing a white flash while requireAdmin() +
   Supabase queries resolve (2026-07-08 fix). */
body { margin: 0; min-height: 100vh; min-height: 100dvh; background: var(--space-gradient), var(--bg-primary); background-repeat: no-repeat; background-attachment: fixed; }

/* ── Login (standalone, no sidebar) ─────────────────────────────────────── */
.adm-login-page {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--space-gradient), var(--navbar-bg);
  background-repeat: no-repeat;
  background-attachment: fixed;
  font-family: var(--font-thai);
  color: var(--text-primary);
  padding: 24px;
}
.adm-login-card {
  width: 100%;
  max-width: 380px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-3xl);
  padding: 32px 28px;
  text-align: center;
}
.adm-login-logo { font-size: 32px; margin-bottom: 12px; display: inline-flex; color: var(--accent-gold-text); }
.adm-login-title { font-size: 20px; font-weight: 700; margin-bottom: 4px; }
.adm-login-sub { font-size: 13px; color: var(--text-secondary); margin-bottom: 24px; }
.adm-login-field { text-align: left; margin-bottom: 14px; }
.adm-login-field label { display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; }
.adm-login-field input, .adm-login-field select {
  width: 100%;
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 11px 12px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 14px;
}
.adm-login-field input:focus, .adm-login-field select:focus { outline: none; border-color: var(--accent-gold); }
.adm-login-pass { position: relative; }
.adm-login-pass input { padding-right: 44px; }
.adm-login-eye {
  position: absolute; top: 0; right: 0; bottom: 0; width: 44px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer; color: var(--text-secondary); font-size: 16px;
}
@media (hover: hover) { .adm-login-eye:hover { color: var(--text-primary); } }
/* Dark-mode native option-list popup (2026-07-25 fix, same reasoning as the
   date-input calendar icon below) — a bare <select> defaults to a light
   OS-native dropdown that clashes hard against this page's dark theme. */
.adm-login-field select { color-scheme: dark; cursor: pointer; }
/* appearance:none (2026-07-25 fix — "พื้นหลังขาว ข้อความขาว") — without it,
   several browsers keep the native <select> box's own chrome for the CLOSED
   control and simply ignore the custom background/border above, leaving
   white-on-white. Same fix already used by .nt-select (giveaway.css) for
   the exact same reason; the chevron below replaces the native arrow that
   appearance:none also removes. */
.adm-login-field select { appearance: none; padding-right: 30px; }
.adm-select-wrap { position: relative; }
.adm-select-wrap::after {
  content: '▾';
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 10px;
  color: var(--text-secondary);
  pointer-events: none;
}
/* Native calendar-picker icon on <input type="date"> (2026-07-24 fix) — the
   browser renders that icon black by default regardless of our own
   color/background CSS (it's inside the input's UA shadow DOM, not
   something a plain `color` rule reaches); color-scheme tells the browser
   to use its dark-mode variant of that icon instead, matching the dark
   field it sits in. */
.adm-login-field input[type="date"] { color-scheme: dark; }
.adm-login-error {
  background: rgba(var(--status-red-rgb),0.12);
  color: var(--status-red);
  border-radius: var(--radius-md);
  padding: 8px 10px;
  font-size: 12px;
  margin-bottom: 14px;
  text-align: left;
}
.adm-login-btn {
  width: 100%;
  background: var(--accent-gold);
  color: var(--brand-gold-ink);
  border: none;
  border-radius: var(--radius-md);
  padding: 12px;
  font-family: inherit;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  margin-top: 4px;
}
.adm-login-btn:disabled { opacity: 0.6; cursor: default; }
/* Secondary/back-button variant (2026-08-12 ask — Turnstile verify-step
   redesign) — same treatment as .auth-btn--ghost on the main site. */
.adm-login-btn--ghost {
  background: rgba(var(--white-rgb),0.06);
  color: var(--text-primary);
  border: 1px solid var(--border);
}
@media (hover: hover) { .adm-login-btn--ghost:hover:not(:disabled) { background: rgba(var(--white-rgb),0.1); } }

/* X login (2026-07-08 ask) — admins who signed up via X have no password to
   use in the form below, so this is offered as an alternative up top. */
.adm-login-x-btn {
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: #000; color: var(--white);
  border: 1px solid rgba(var(--white-rgb), 0.15);
  border-radius: var(--radius-md);
  padding: 12px;
  font-family: inherit;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  margin-bottom: 16px;
}
.adm-login-x-btn:disabled { opacity: 0.6; cursor: default; }
.adm-login-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 4px 0 16px;
  font-size: 11px;
  color: var(--text-secondary);
}
.adm-login-divider::before, .adm-login-divider::after { content: ''; flex: 1; height: 0; border-top: 1px solid var(--border); }
.adm-login-note {
  font-size: 11px;
  color: var(--text-secondary);
  margin-top: 18px;
  line-height: 1.5;
}
.adm-login-slug {
  font-size: 11px;
  color: var(--text-secondary);
  opacity: 0.6;
  margin-top: 6px;
  font-family: monospace;
}

/* ── Shell (sidebar + topbar) ────────────────────────────────────────────── */
.adm-shell {
  display: flex;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--space-gradient), var(--bg-primary);
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--text-primary);
  font-family: var(--font-thai);
}
.adm-sidebar {
  width: 240px;
  flex-shrink: 0;
  background: var(--navbar-bg);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 20px 0;
  transition: width 0.15s ease;
  /* Pinned to the viewport instead of stretching to the right column's
     height (2026-07-12 fix) — otherwise `.adm-sidebar__foot` (username +
     logout) drifted down past the fold on any page taller than one screen,
     since it just sits at the end of the sidebar's own flex column. */
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
}
.adm-sidebar__brand {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 0 20px 20px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 12px;
}
.adm-sidebar__brand-icon { font-size: 18px; flex-shrink: 0; line-height: 1.4; }
.adm-sidebar__brand-text { flex: 1; line-height: 1.4; }
.adm-sidebar__brand-title { font-size: 14px; font-weight: 700; color: var(--text-primary); }
/* Matches nav.css .ppw-nav__brand-tag exactly (small gold text) — the main
   site's global nav renders its event name the same way. */
.adm-sidebar__brand-tag { font-size: 10px; color: var(--accent-gold-text); }
.adm-sidebar__collapse {
  flex-shrink: 0;
  width: 22px; height: 22px;
  display: none; /* desktop-only — shown in the >=769px block below */
  align-items: center; justify-content: center;
  background: rgba(var(--white-rgb),0.05);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-size: 11px;
  cursor: pointer;
}
/* min-height:0 + overflow-y:auto (2026-08-05 fix — sidebar now has 14
   top-level items + 2 nested; `.adm-sidebar` is pinned to a fixed
   100vh/100dvh height with no scroll mechanism anywhere, so past a certain
   item count the bottom entries were clipped outside the viewport with no
   way to reach them at all, not just visually cramped). Brand header and
   `.adm-sidebar__foot` stay fixed in place; only this middle nav section
   scrolls internally when it doesn't fit. */
.adm-sidebar__nav { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; padding: 0 12px; }
.adm-sidebar__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  position: relative;
}
@media (hover: hover) { .adm-sidebar__item:hover { background: rgba(var(--white-rgb),0.04); } }
.adm-sidebar__item.is-active { background: var(--bg-elevated); color: var(--text-primary); font-weight: 600; }
.adm-sidebar__item.is-disabled { opacity: 0.4; pointer-events: none; }
.adm-sidebar__item-icon { flex-shrink: 0; }
/* Submenu row (2026-07-15 ask) — indented + smaller, nested directly under
   its parent (e.g. "จัดการผู้ใช้"), same interaction/active-state as a
   top-level item otherwise. */
.adm-sidebar__item--child { padding-left: 34px; font-size: 12px; }
.adm-sidebar.is-collapsed .adm-sidebar__item--child { display: none; } /* icon-only rail has no room for a nested label */
.adm-sidebar__badge {
  margin-left: auto;
  background: var(--accent-gold);
  color: var(--brand-gold-ink);
  font-size: 10px;
  font-weight: 700;
  border-radius: var(--radius-lg);
  padding: 1px 7px;
}
.adm-sidebar__foot {
  padding: 12px 20px 0;
  border-top: 1px solid var(--border);
  margin-top: 12px;
  font-size: 12px;
}
.adm-sidebar__profile { display: flex; align-items: center; gap: 8px; color: var(--text-secondary); margin-bottom: 8px; }
.adm-sidebar__logout {
  display: flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: none;
  color: var(--like-red);
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
  padding: 0;
}

/* ── Desktop sidebar collapse (icon-only rail) ─────────────────────────────
   Mobile already has its own off-canvas drawer pattern (see the mobile
   media query below) — this is a separate, desktop-only affordance, so it's
   scoped to >=769px and never interacts with `.is-open`/the mobile drawer. */
@media (min-width: 769px) {
  .adm-sidebar__collapse { display: flex; }
  .adm-sidebar.is-collapsed { width: 64px; }
  .adm-sidebar.is-collapsed .adm-sidebar__brand { flex-direction: column; align-items: center; gap: 8px; padding: 0 8px 16px; }
  .adm-sidebar.is-collapsed .adm-sidebar__brand-text { display: none; }
  .adm-sidebar.is-collapsed .adm-sidebar__item { justify-content: center; padding: 10px; }
  .adm-sidebar.is-collapsed .adm-sidebar__item-label { display: none; }
  .adm-sidebar.is-collapsed .adm-sidebar__badge { position: absolute; top: 4px; right: 4px; margin-left: 0; }
  .adm-sidebar.is-collapsed .adm-sidebar__foot { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 12px 8px 0; }
  .adm-sidebar.is-collapsed .adm-sidebar__profile-text,
  .adm-sidebar.is-collapsed .adm-sidebar__logout-label { display: none; }
}

.adm-main { flex: 1; min-width: 0; }
.adm-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 28px;
  border-bottom: 1px solid var(--border);
}
.adm-topbar__title-th { font-size: 18px; font-weight: 700; }
.adm-topbar__title-en { font-size: 12px; color: var(--text-secondary); }
.adm-topbar__search {
  position: relative;
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 13px;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 260px;
}
.adm-topbar__search-icon { flex-shrink: 0; }
.adm-topbar__search-input {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  outline: none;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
}
.adm-topbar__search-input::placeholder { color: var(--text-secondary); }
.adm-topbar__search:focus-within { border-color: var(--focus-ring); box-shadow: 0 0 0 3px rgba(var(--accent-gold-rgb),0.12); }
.adm-topbar__search kbd {
  margin-left: auto;
  background: rgba(var(--white-rgb),0.08);
  border-radius: var(--radius-xs);
  padding: 1px 6px;
  font-size: 10px;
  flex-shrink: 0;
}
.adm-search-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0; right: 0;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 6px;
  max-height: 320px;
  overflow-y: auto;
  z-index: 50;
  box-shadow: 0 12px 32px rgba(var(--black-rgb),0.4);
}
.adm-search-group-label { font-size: 11px; color: var(--text-secondary); font-weight: 600; padding: 6px 8px 4px; }
.adm-search-item {
  display: block;
  padding: 8px;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  text-decoration: none;
  font-size: 13px;
}
@media (hover: hover) { .adm-search-item:hover { background: rgba(var(--white-rgb),0.06); } }
.adm-search-empty { padding: 12px 8px; color: var(--text-secondary); font-size: 12px; text-align: center; }
.adm-content { padding: 24px 28px; }
.adm-state { color: var(--text-secondary); font-size: 14px; padding: 40px 0; text-align: center; }
/* Spacing sweep 2026-10-25 (bible §6.165): .tk-back is inline-flex in a block
   .adm-content, so the title/state under it sat flush (0px) on 6 admin pages;
   public pages get their gap from a flex parent instead. */
.adm-content > .tk-back { margin-bottom: 14px; }
/* A lone checkbox chip placed straight in a form (Fan Forge needs_review)
   gets the same 14px a field leaves under itself. */
.adm-form > label.adm-checkbox-item { margin-bottom: 14px; }
.adm-state--error { color: var(--status-red); }

.adm-mobile-toggle {
  display: none; /* shown only under the mobile breakpoint below */
  width: 36px; height: 36px;
  align-items: center; justify-content: center;
  background: rgba(var(--white-rgb),0.05);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-size: 16px;
  cursor: pointer;
  flex-shrink: 0;
}
.adm-scrim {
  display: none;
  position: fixed; inset: 0;
  background: rgba(var(--black-rgb),0.6);
  z-index: 499;
}

/* Every <select>'s closed box already gets --text-primary from its own rule,
   but the open option list is native/OS-rendered and ignores that unless
   <option> itself is styled — left unstyled it fell back to the browser
   default (white background), which combined with our inherited white text
   made every dropdown's open list unreadable. */
.adm-shell select option,
.adm-login-page select option,
.adm-select-wrap select option {
  background: var(--bg-elevated);
  color: var(--text-primary);
}

/* ── Seat Overview ───────────────────────────────────────────────────────── */
.adm-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}
.adm-stat {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px;
}
.adm-stat__head { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
.adm-stat__dot { width: 8px; height: 8px; border-radius: var(--radius-full); flex-shrink: 0; }
.adm-stat__v { font-size: 22px; font-weight: 700; }

/* User Management's "ผู้ใช้ทั้งหมด"/"ผู้ใช้ที่ถูกลบ" tabs (2026-07-10) — a
   self-contained copy of modals.css's .my-tabbar (Edit Profile's tab bar),
   which no admin page loads. Same bug class as the earlier .adm-delete-input
   fix: emitting a class from a stylesheet the page never links. */
/* Explanatory line above a table whose rows need context to read correctly —
   e.g. the "X ล็อกอินค้าง" tab, where the right action is "none, it resolves
   itself" and an admin would otherwise reasonably start deleting accounts. */
.adm-note {
  font-size: 13px;
  line-height: 1.7;
  color: var(--text-secondary);
  background: rgba(var(--accent-teal-rgb), 0.08);
  border: 1px solid rgba(var(--accent-teal-rgb), 0.25);
  border-radius: var(--radius-lg);
  padding: 12px 14px;
  margin-bottom: 16px;
}

.adm-tabbar {
  display: flex; gap: 6px;
  padding: 4px;
  margin-bottom: 16px;
  background: rgba(var(--white-rgb), 0.05);
  border-radius: var(--radius-xl);
  max-width: 420px;
}
.adm-tabbar__btn {
  flex: 1;
  padding: 9px 6px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-secondary);
  font: 600 12px var(--font-thai);
  cursor: pointer;
}
.adm-tabbar__btn.is-active {
  background: rgba(var(--brand-gold-rgb), 0.14);
  border-color: var(--accent-gold);
  color: var(--accent-gold-text);
}

.adm-filters {
  display: flex;
  gap: 10px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.adm-filters input, .adm-filters select {
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px 10px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
}
.adm-filters input { flex: 1; min-width: 180px; }
/* Wraps a bare .adm-filters search <input> so search-clear.js's × button has
   a positioning anchor, without disturbing the input's own flex sizing
   above (2026-07-26 ask — every /p search box gets a clear button). */
.adm-filters .search-input-wrap { position: relative; flex: 1; min-width: 180px; }
.adm-filters .search-input-wrap input { width: 100%; }

.adm-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  font-size: 13px;
}
.adm-table th, .adm-table td {
  text-align: left;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}
.adm-table th { color: var(--text-secondary); font-weight: 600; font-size: 12px; }
.adm-table tr:last-child td { border-bottom: none; }
.adm-table tr.adm-row--clickable { cursor: pointer; }
@media (hover: hover) { .adm-table tr.adm-row--clickable:hover { background: rgba(var(--white-rgb),0.03); } }
.adm-table__total td { font-weight: 700; background: rgba(var(--white-rgb),0.04); border-top: 1px solid var(--border); }
.adm-table__owner { display: flex; align-items: center; gap: 8px; }
.adm-table__owner a { color: var(--text-primary); font-weight: 600; text-decoration: none; }
@media (hover: hover) { .adm-table__owner a:hover { color: var(--accent-gold-text); text-decoration: underline; } }
.adm-link { color: var(--accent-teal); text-decoration: none; }
@media (hover: hover) { .adm-link:hover { text-decoration: underline; } }
/* A username inside a table reads as content, not as a call to action, so it
   keeps the row's own text color and announces itself on hover instead
   (2026-08-16). Added after three `el('a')` calls with no class at all were
   found rendering browser-default blue inside admin tables — admin-users,
   admin-verification and admin-zone. Any new anchor in an admin table needs
   one of these two classes; a bare <a> inherits nothing and shows up blue. */
.adm-link--plain { color: var(--text-primary); }
@media (hover: hover) { .adm-link--plain:hover { color: var(--accent-teal); text-decoration: underline; } }
.adm-table__owner .my-badge { padding: 3.5px 9px; border-radius: var(--radius-md); font-size: 10px; }
.adm-table__avatar {
  width: 24px; height: 24px; border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; border: 1px solid var(--border); flex-shrink: 0;
}
.adm-table__empty { color: var(--text-secondary); }
.adm-table__sub { font-size: 10.5px; color: var(--text-secondary); margin-top: 1px; }

/* Assist-needs chip list (admin-assist.js) — one chip per need, category on
   top + severity detail below, same two-weight pattern as /p/my's own chip
   (my.css .my-assist__chip) so admin and member-facing severity text read
   the same way. */
.ada-chip-list { display: flex; flex-direction: column; gap: 6px; }
.ada-chip {
  display: flex; flex-direction: column; gap: 2px;
  padding: 5px 9px;
  background: rgba(var(--accent-gold-rgb), 0.06);
  border: 1px solid rgba(var(--accent-gold-rgb), 0.18);
  border-radius: var(--radius-md);
}
.ada-chip__top { display: flex; align-items: center; gap: 5px; color: var(--accent-gold-text); font: 600 11px var(--font-thai); }
.ada-chip__severity { color: var(--text-secondary); font-size: 10.5px; }

/* ── Ticket Verification Hub ─────────────────────────────────────────────── */
/* Uploaded Proof / No Proof as two fully separate views behind a tab bar
   (2026-07-08 — replaced an earlier stacked-both-at-once layout). Each tab
   keeps its own pagination in JS; only the active tab's list is in the DOM. */
.adv-tabs { display: flex; gap: 6px; padding: 4px; margin-bottom: 16px; background: rgba(var(--white-rgb), 0.05); border-radius: var(--radius-xl); }
.adv-tabs__btn {
  flex: 1;
  padding: 10px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-secondary);
  font: 600 13px var(--font-thai);
  cursor: pointer;
}
.adv-tabs__btn.is-active { background: rgba(var(--brand-gold-rgb), 0.14); border-color: var(--accent-gold); color: var(--accent-gold-text); }

/* Ticket Verification tab bar (2026-08-10 redesign — replaced the earlier
   2026-08-09 two-stacked-boxes layout after it read as "two different
   components"): "รอตรวจสอบ" (the actual work queue) sits inside the SAME
   pill background as the other 4 reference/history tabs, separated only by
   a thin `.adv-tabs__divider` — one visual container, not two. Everything
   below is scoped to `.adv-tabs--verification` (or its child wrapper
   classes, which only exist on this page) so admin-fanproject.js's plain
   single-row `.adv-tabs` bar is completely unaffected. */
.adv-tabs__primary-wrap { display: flex; flex: 1.2 1 0; }
.adv-tabs__primary-wrap .adv-tabs__btn { flex: 1; }
.adv-tabs__secondary-wrap { display: flex; gap: 6px; flex: 4 1 0; }
.adv-tabs__divider { flex: 0 0 auto; width: 1px; align-self: stretch; margin: 2px 4px; background: var(--border); }
.adv-tabs__btn--primary { border-color: rgba(var(--brand-gold-rgb), 0.35); color: var(--accent-gold-text); }
.adv-tabs__btn--primary.is-active { border-color: var(--accent-gold); }

/* Desktop: count drops to its own line under icon+label, smaller and not
   bold, so the label reads as the primary content and the count as a
   lighter secondary detail. `.adv-tabs__btn-count` has `flex-basis: 100%`
   so it wraps onto a 2nd row on its own while icon+label stay together on
   row 1 — no separate wrapper markup needed. */
@media (min-width: 769px) {
  .adv-tabs--verification .adv-tabs__btn { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; column-gap: 4px; }
  .adv-tabs--verification .adv-tabs__btn-count { flex: 1 0 100%; margin-top: 2px; font-size: 11px; font-weight: 400; }
}

/* Mobile: "รอตรวจสอบ" keeps its full icon+label+count on its own full-width
   row; divider drops to a horizontal rule; the 4 secondary tabs go into an
   equal 4-column grid with just icon+count (label hidden — 4 Thai labels
   don't fit 4 columns at any readable size). Still one `.adv-tabs`
   background box, just wrapped instead of scrolled — no separate boxes. */
@media (max-width: 768px) {
  .adv-tabs--verification { flex-wrap: wrap; }
  .adv-tabs--verification .adv-tabs__primary-wrap { flex: 1 1 100%; }
  .adv-tabs--verification .adv-tabs__divider { flex: 1 1 100%; width: auto; height: 1px; align-self: auto; margin: 6px 2px; }
  .adv-tabs--verification .adv-tabs__secondary-wrap { flex: 1 1 100%; display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
  .adv-tabs--verification .adv-tabs__secondary-wrap .adv-tabs__btn {
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: 8px 4px; font-size: 11px;
  }
  .adv-tabs--verification .adv-tabs__secondary-wrap .adv-tabs__btn-label { display: none; }
}
.adv-list { display: flex; flex-direction: column; gap: 10px; }
.adv-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 12px 16px;
  flex-wrap: wrap;
}
.adv-thumb {
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-md);
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  color: var(--text-primary);
  font-size: 18px;
  cursor: pointer;
  flex-shrink: 0;
}
.adv-row__info { flex: 1; min-width: 200px; }
.adv-row__seat { font-weight: 600; font-size: 13px; margin-bottom: 4px; }

/* Ticket OCR UI (2026-10-21/22) — queue chips, the proof-lightbox panel, the
   solid approve buttons. THEME-FOLLOWING throughout (owner ask 2026-10-22):
   radius, surfaces, text and status colours all come from p/tokens.css, so a
   theme change reaches this UI. The lightbox panel paints its own
   var(--bg-elevated) surface, which is what lets its text use theme tokens
   over the lightbox's fixed dark backdrop. The one literal is the warn amber —
   tokens.css has no warning token; it is the pending badge's colour (my.css). */
.adv-ocr, .adv-ocr-lb { --ocr-warn: #FABF59; --ocr-warn-rgb: 250, 191, 89; }

/* Approve = the one solid button on this page — the primary action must never
   read like the outlined reject beside it. */
.tk-btn--blue.tk-btn--solid { background: var(--accent-blue); border-color: var(--accent-blue); color: #fff; }
@media (hover: hover) { .tk-btn--blue.tk-btn--solid:hover { background: var(--accent-blue); border-color: var(--accent-blue); filter: brightness(0.9); } }
.tk-btn--blue.tk-btn--solid:disabled { opacity: 0.55; }

/* Queue row: one compact line (ocrBadges). tone = ocrScore().tone. */
.adv-ocr { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; margin-top: 6px; }
.adv-ocr__chip { font-size: 10.5px; line-height: 1.3; padding: 1px 7px; border-radius: var(--radius-pill); background: rgba(var(--surface-rgb),0.06); color: var(--text-secondary); white-space: nowrap; }
.adv-ocr__chip--ok { background: rgba(var(--status-green-rgb),0.16); color: var(--success-text); font-weight: 600; }
.adv-ocr__chip--warn { background: rgba(var(--ocr-warn-rgb),0.16); color: var(--ocr-warn); font-weight: 600; }
.adv-ocr__chip--bad, .adv-ocr__chip--dup { background: rgba(var(--status-red-rgb),0.16); color: var(--danger-text); font-weight: 600; }
.adv-ocr__last3 { font-size: 10.5px; color: var(--text-secondary); white-space: nowrap; }

/* Lightbox panel (ocrLightboxInfo). Scoped with :has(.adv-ocr-lb) so every
   other lightbox caller is untouched; the extra .ppw-lightbox beats
   lightbox.js's own <style>, injected after this file. Phone (default):
   bottom sheet, details collapsed. Desktop (≥900px): side panel. */
.adv-ocr-lb { display: flex; flex-direction: column; gap: 8px; text-align: left; color: var(--text-primary); }
.ppw-lightbox .adv-ocr-lb__head .ppw-lightbox__info-title { font-size: 13px; color: var(--text-primary); }
.adv-ocr-lb__dup { font-size: 12px; font-weight: 600; color: var(--danger-text); background: rgba(var(--status-red-rgb),0.14); border: 1px solid rgba(var(--status-red-rgb),0.4); border-radius: var(--radius-md); padding: 5px 8px; }
.adv-ocr-lb__details { display: flex; flex-direction: column; gap: 6px; }
.adv-ocr-lb__summary { display: flex; align-items: center; gap: 10px; cursor: pointer; list-style: none; user-select: none; }
.adv-ocr-lb__summary::-webkit-details-marker { display: none; }
.adv-ocr-lb__score { flex: 0 0 auto; min-width: 48px; text-align: center; font-size: 16px; font-weight: 700; line-height: 1; padding: 6px; border-radius: var(--radius-md); background: rgba(var(--surface-rgb),0.08); color: var(--text-secondary); }
.adv-ocr-lb__score--ok { color: var(--success-text); background: rgba(var(--status-green-rgb),0.14); }
.adv-ocr-lb__score--warn { color: var(--ocr-warn); background: rgba(var(--ocr-warn-rgb),0.14); }
.adv-ocr-lb__score--bad { color: var(--danger-text); background: rgba(var(--status-red-rgb),0.14); }
.adv-ocr-lb__sum-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.adv-ocr-lb__sum-main { font-size: 13px; font-weight: 600; }
.adv-ocr-lb__sum-sub { font-size: 11px; color: var(--text-secondary); }
.adv-ocr-lb__chev { flex: 0 0 auto; font-size: 11px; color: var(--text-secondary); border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 2px 8px; }
.adv-ocr-lb__details[open] .adv-ocr-lb__chev { color: var(--text-primary); }
.adv-ocr-lb__fields { display: flex; flex-direction: column; }
.adv-ocr-lb__field { display: grid; grid-template-columns: 58px minmax(0, 1fr) minmax(0, 1fr) 18px; align-items: center; gap: 8px; padding: 4px 6px; border-radius: var(--radius-md); font-size: 12px; }
.adv-ocr-lb__field + .adv-ocr-lb__field { border-top: 1px solid var(--border); }
.adv-ocr-lb__field--head { font-size: 10.5px; color: var(--text-secondary); padding-top: 0; padding-bottom: 2px; }
.adv-ocr-lb__field--mismatch { background: rgba(var(--ocr-warn-rgb),0.12); border-top-color: transparent; }
.adv-ocr-lb__label { font-weight: 600; color: var(--text-secondary); }
.adv-ocr-lb__val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adv-ocr-lb__val--read { color: var(--text-secondary); }
.adv-ocr-lb__field--mismatch .adv-ocr-lb__val--read { color: var(--ocr-warn); font-weight: 600; }
.adv-ocr-lb__status { display: inline-flex; width: 14px; height: 14px; justify-self: end; justify-content: center; color: var(--text-secondary); font-size: 12px; line-height: 14px; }
.adv-ocr-lb__status svg { width: 100%; height: 100%; }
.adv-ocr-lb__field--match .adv-ocr-lb__status { color: var(--success-text); }
.adv-ocr-lb__field--mismatch .adv-ocr-lb__status { color: var(--ocr-warn); }
.adv-ocr-lb__note { font-size: 10.5px; color: var(--text-secondary); overflow-wrap: anywhere; }
.adv-ocr-lb__note--nums { color: var(--text-primary); }

/* Approve / reject inside the panel; reject opens the shared reason form
   (buildRejectPanel) inline below, styled by the page's own .adv-reject. */
.adv-ocr-lb__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.adv-ocr-lb__actions .tk-btn, .adv-ocr-lb__actions .ppw-lightbox__confirm-btn { justify-content: center; width: 100%; margin: 0; }
/* lightbox.js hard-codes 8px and its own blue on the confirm button. */
.ppw-lightbox .adv-ocr-lb__actions .ppw-lightbox__confirm-btn { border-radius: var(--radius-md); background: var(--accent-blue); color: #fff; }
.adv-reject--lightbox { margin-top: 0; }
.adv-reject__select option { color: var(--text-primary); background: var(--bg-elevated); }
.ppw-lightbox .ppw-lightbox__footer:has(.adv-ocr-lb) { width: 100%; max-width: 560px; }
.ppw-lightbox .ppw-lightbox__bar:has(.adv-ocr-lb) { background: var(--bg-elevated); border-color: var(--border); border-radius: var(--radius-lg); flex-direction: column; flex-wrap: nowrap; align-items: stretch; width: 100%; max-height: 50vh; max-height: 50dvh; overflow-y: auto; overscroll-behavior: contain; padding: 10px 12px; gap: 8px; }
.ppw-lightbox .ppw-lightbox__info:has(.adv-ocr-lb) { flex: 0 0 auto; }

@media (min-width: 900px) {
  .ppw-lightbox:has(.adv-ocr-lb) { flex-direction: row; align-items: stretch; }
  .ppw-lightbox:has(.adv-ocr-lb) .ppw-lightbox__img-wrap { flex: 1 1 auto; width: auto; min-width: 0; }
  /* The panel stays put while zoomed: the floating-footer mode is for the
     bar-under-photo layout, not a side panel. */
  .ppw-lightbox .ppw-lightbox__footer:has(.adv-ocr-lb),
  .ppw-lightbox .ppw-lightbox__footer--floating:has(.adv-ocr-lb) { position: static; transform: none; flex: 0 0 320px; width: 320px; max-width: 320px; justify-content: flex-start; padding-top: 56px; }
  .ppw-lightbox .ppw-lightbox__bar:has(.adv-ocr-lb) { max-height: none; height: 100%; justify-content: flex-start; }
  /* Zoom/close belong to the photo: pin them to its top-right corner, left of
     the 320px panel. z-index:auto on the panel — as a flex item it would
     honour lightbox.js's floating z-index:20 and cover them once zoomed. */
  .ppw-lightbox:has(.adv-ocr-lb) .ppw-lightbox__topbar { right: calc(16px + 330px + var(--safe-right)); }
  .ppw-lightbox:has(.adv-ocr-lb) .ppw-lightbox__footer { z-index: auto; padding-top: 0; }
  .adv-ocr-lb__summary { cursor: default; }
  .adv-ocr-lb__chev { display: none; }
}
.adv-row__owner { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-secondary); margin-bottom: 2px; }
.adv-row__time { font-size: 11px; color: var(--text-secondary); opacity: 0.7; }
.adv-row__actions { display: flex; gap: 8px; }
.adv-reject {
  width: 100%;
  background: rgba(var(--status-red-rgb),0.08);
  border: 1px solid rgba(var(--status-red-rgb),0.3);
  border-radius: var(--radius-md);
  padding: 12px;
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.adv-reject__select, .adv-reject__free {
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
}
.adv-reject__free.is-invalid { border-color: var(--status-red); }
.adv-reject__note { font-size: 11px; color: var(--text-secondary); }
.adv-reject__btns { display: flex; gap: 8px; justify-content: flex-end; }

/* ── User Management ────────────────────────────────────────────────────── */
.adu-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.adu-chip {
  font-size: 11px;
  font-weight: 600;
  border-radius: var(--radius-sm);
  padding: 2px 8px;
}
.adu-chip--x { background: rgba(var(--brand-teal-rgb),0.16); color: var(--accent-teal); }
.adu-chip--manual { background: rgba(var(--white-rgb),0.08); color: var(--text-secondary); }
.adu-chip--privacy { background: rgba(160,160,160,0.16); color: #B0B0B0; }
.adu-chip--admin { background: rgba(var(--brand-gold-rgb),0.16); color: var(--accent-gold-text); }

/* ── Marketplace Moderation ──────────────────────────────────────────────── */
.adm-info-banner {
  background: rgba(var(--brand-teal-rgb),0.08);
  border: 1px solid rgba(var(--brand-teal-rgb),0.25);
  color: var(--accent-teal);
  border-radius: var(--radius-md);
  padding: 10px 14px;
  font-size: 12px;
  margin-bottom: 16px;
}
.adm-info-banner--amber {
  background: rgba(var(--brand-gold-rgb),0.08);
  border-color: rgba(var(--brand-gold-rgb),0.25);
  color: var(--accent-gold-text);
}

/* ── Test Parameters ─────────────────────────────────────────────────────── */
.adm-code {
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
  font-size: 12px;
  background: rgba(var(--white-rgb),0.08);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
  color: var(--accent-teal);
  white-space: nowrap;
}
/* 2026-08-15 redesign — was a 3-column <table>, but a คำอธิบาย running 200+
   chars stole width from the other two columns until none of them were
   readable. One card per parameter instead: the parameter lives in a
   full-width readonly input (select-all on focus + the copy button beside
   it) so a long one scrolls inside its own box rather than squeezing its
   neighbours, and the description is a bullet list free to run as long as
   it needs. Text sizes keep the 2026-07-10 ask's 10%-down scale (11.7px
   body / 9.9px chips). */
.tp-group { margin-bottom: 28px; }
.tp-group__title {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
  font-weight: 700;
  margin-bottom: 9px;
}
.tp-group__count {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  background: rgba(var(--white-rgb),0.08);
  border-radius: var(--radius-pill);
  padding: 2px 9px;
}
/* 2–3 cards per row on desktop, 1 on mobile (2026-08-15 ask). auto-fill +
   minmax means the column count follows the available width instead of a
   fixed breakpoint. 300px is tuned so a ~1150px-wide window (sidebar
   collapsed) reaches 3 columns and a normal one sits at 2 — the parameter
   input scrolls its own text, so a narrower card costs nothing.
   align-items:stretch (2026-08-15 ask) — every card in a row shares the
   tallest one's height, so the row reads as one band. A short card just
   carries trailing space under its last bullet; that was preferred over
   the ragged bottoms `start` produced. */
.tp-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; align-items: stretch; }
.tp-card {
  background: rgba(var(--white-rgb),0.03);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px;
}
.tp-card__top { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
/* min-width:0 lets the input actually shrink inside the flex row (a text
   input's default intrinsic width would otherwise push the copy button off
   the card on narrow screens). */
.tp-card__input {
  flex: 1;
  min-width: 0;
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
  font-size: 12.5px;
  color: var(--accent-teal);
  background: rgba(var(--brand-teal-rgb),0.07);
  border: 1px solid rgba(var(--brand-teal-rgb),0.22);
  border-radius: var(--radius-md);
  padding: 9px 11px;
}
.tp-card__input:focus { outline: none; border-color: var(--accent-teal); }
.tp-copy-btn { flex-shrink: 0; font-size: 12px; padding: 7px 9px; }
.tp-pages { margin-bottom: 10px; }
.tp-pages .adu-chip { font-size: 9.9px; }
.tp-card__bullets { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.tp-card__bullets li { font-size: 11.7px; color: var(--text-secondary); line-height: 1.55; }
.tp-card__bullets li::marker { color: var(--accent-teal); }
.tp-desc__warn { margin-top: 10px; font-size: 11.7px; color: var(--accent-gold-text); }

/* ── Password Reset ──────────────────────────────────────────────────────── */
/* Autocomplete (2026-07-09): input search dropped its own "ค้นหา" button in
   favor of a live dropdown as the admin types — .adp-search is the anchor
   for the absolutely-positioned .adp-result-list overlay below it, so the
   dropdown floats over the page instead of shoving content down. */
.adp-search { position: relative; max-width: 360px; margin-bottom: 16px; }
.adp-search input { width: 100%; background: rgba(var(--white-rgb),0.04); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 8px 10px; color: var(--text-primary); font-family: inherit; font-size: 13px; }
.adp-result-list {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20;
  display: flex; flex-direction: column; gap: 4px;
  background: var(--dropdown-bg); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 6px; max-height: 260px; overflow-y: auto;
  box-shadow: 0 8px 24px rgba(var(--black-rgb),0.35);
}
.adp-result-item {
  display: flex; align-items: center; gap: 10px;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-md);
  padding: 8px 10px; color: var(--text-primary); font-family: inherit; font-size: 13px;
  cursor: pointer; text-align: left;
}
@media (hover: hover) { .adp-result-item:hover { background: rgba(var(--white-rgb),0.05); border-color: rgba(var(--brand-gold-rgb),0.4); } }
.adp-result-item > span:nth-child(2) { flex: 1; }
.adp-card {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-xl);
  padding: 18px; margin-bottom: 20px; max-width: 420px;
}
.adp-card__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.adp-card__username { font-size: 16px; font-weight: 700; }
.adp-card__chips { display: flex; gap: 6px; margin-bottom: 14px; }
.adp-blocked { font-size: 12px; color: var(--status-red); background: rgba(var(--status-red-rgb),0.08); border: 1px solid rgba(var(--status-red-rgb),0.25); border-radius: var(--radius-md); padding: 10px 12px; margin-bottom: 12px; }
/* Revoke-X-link action (2026-07-08): block-level + its own top gap so it
   never sits flush against the warning box above or a temp-password panel
   that might already be showing below. */
.adp-revoke-btn { display: flex; width: 100%; margin-top: 4px; padding: 10px 12px; font-size: 12px; }
.adp-success {
  background: rgba(var(--status-green-rgb),0.08); border: 1px solid rgba(var(--status-green-rgb),0.3); border-radius: var(--radius-xl);
  padding: 18px; max-width: 420px;
}
.adp-success__head { font-size: 14px; font-weight: 700; color: var(--status-green); margin-bottom: 12px; }
.adp-success__box {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: rgba(var(--black-rgb),0.25); border-radius: var(--radius-md); padding: 10px 14px; margin-bottom: 12px;
}
.adp-success__box code { font-family: monospace; font-size: 16px; font-weight: 700; letter-spacing: 0.5px; color: var(--text-primary); }
.adp-success__note { font-size: 11px; color: var(--text-secondary); }
.adm-report-detail {
  background: rgba(var(--status-red-rgb),0.06);
  padding: 12px 16px !important;
}
.adm-report-detail__item { padding: 6px 0; border-bottom: 1px solid var(--border); }
.adm-report-detail__item:last-child { border-bottom: none; }
.adm-report-detail__reason { font-size: 12px; font-weight: 600; }
.adm-report-detail__note { font-size: 12px; color: var(--text-secondary); margin-top: 2px; }
.adm-report-detail__time { font-size: 11px; color: var(--text-secondary); opacity: 0.7; margin-top: 2px; }
.adm-report-detail__item .tk-btn { margin-top: 6px; }

.adm-table__actions { display: flex; gap: 10px; justify-content: flex-end; }
.adm-table th.adm-th--right { text-align: right; }
/* Seat Overview "อัปเดตล่าสุด" column — 10% smaller, faded white (2026-07-10 ask). */
.adm-updated-cell { font-size: 11.7px; color: rgba(var(--white-rgb),0.5); }

.adm-remove-confirm {
  background: rgba(var(--status-red-rgb),0.06);
  border: 1px solid rgba(var(--status-red-rgb),0.25);
  border-radius: var(--radius-lg);
  padding: 14px 16px !important;
}
.adm-remove-confirm__warn { font-size: 12px; color: var(--status-red); margin-bottom: 10px; }
.adm-remove-confirm select { margin-bottom: 10px; width: 100%; max-width: 420px; display: block; }
/* 2026-07-10 — type-"DELETE"-to-confirm, replacing the old two-click
   confirmation for seat/user deletion. Self-contained styling (not relying
   on the shared `.auth-input` class) — auth.css isn't loaded on any admin
   page, so `.adm-delete-input` previously rendered as an unstyled browser
   default input, clashing with the dark theme around it. */
.adm-remove-confirm input.adm-delete-input {
  margin-bottom: 10px;
  width: 100%;
  max-width: 420px;
  display: block;
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px 10px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
}
.adm-remove-confirm input.adm-delete-input:focus { border-color: var(--status-red); outline: none; }
.adm-remove-confirm__hint { font-size: 11px; color: var(--text-secondary); margin: -6px 0 10px; }

/* ── Phase Control ───────────────────────────────────────────────────────── */
.adm-phase-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}
.adm-phase-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 18px;
}
.adm-phase-card--emergency {
  margin-bottom: 24px;
  border-color: rgba(var(--brand-gold-rgb),0.35);
}
/* Site-wide maintenance switch (2026-08-16) — sits above the emergency card
   and reads harder than it: this one closes the entire site, so it keeps a
   red border while it is ON instead of blending in with the feature cards. */
.adm-phase-card--maintenance {
  margin-bottom: 16px;
  border-color: rgba(var(--status-red-rgb),0.35);
}
.adm-phase-card--maintenance.is-on {
  border-color: rgba(var(--status-red-rgb),0.85);
  background:
    linear-gradient(0deg, rgba(var(--status-red-rgb),0.10), rgba(var(--status-red-rgb),0.10)),
    var(--bg-elevated);
}
/* READ-ONLY lock (2026-08-27) — sits between the maintenance card and the
   emergency one, which is also its order of severity: maintenance closes the
   whole site, this closes every write but leaves Temptation Chart + Merch Vault
   fully usable, the emergency card only slows live sync down. Gold rather
   than red, and unlike the emergency card it carries a lit `is-on` state so
   the two are never confused at a glance. */
.adm-phase-card--readonly {
  margin-bottom: 16px;
  border-color: rgba(var(--brand-gold-rgb),0.35);
}
.adm-phase-card--readonly.is-on {
  border-color: rgba(var(--brand-gold-rgb),0.85);
  background:
    linear-gradient(0deg, rgba(var(--brand-gold-rgb),0.10), rgba(var(--brand-gold-rgb),0.10)),
    var(--bg-elevated);
}
.adm-phase-card__head { display: flex; gap: 12px; margin-bottom: 16px; }
.adm-phase-card__icon {
  width: 40px; height: 40px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(var(--white-rgb),0.05); border-radius: var(--radius-lg); font-size: 18px;
}
.adm-phase-card__name-row { display: flex; align-items: center; gap: 8px; }
.adm-phase-card__name { font-weight: 700; font-size: 14px; }
.adm-phase-card__chip { font-size: 10px; color: var(--text-secondary); background: rgba(var(--white-rgb),0.06); border-radius: var(--radius-sm); padding: 1px 6px; }
.adm-phase-card__desc { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
/* Bulleted variant (2026-08-16) — only the maintenance card uses it so far.
   `padding-left` carries the markers, which sit outside the text box. */
.adm-phase-card__desc--list { margin: 6px 0 0; padding-left: 16px; line-height: 1.7; }
.adm-phase-card__desc--list li { list-style: disc; }
.adm-phase-card__mult { flex: 0 0 auto; margin-bottom: 0; }
.adm-phase-card__mult .adv-tabs__btn { padding: 8px 12px; white-space: nowrap; }
.adm-phase-card__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 14px; border-top: 1px solid var(--border); }
.adm-phase-card__label { font-size: 12px; color: var(--text-secondary); }

.adm-switch {
  position: relative; flex-shrink: 0;
  width: 44px; height: 24px;
  border: none; border-radius: var(--radius-xl);
  background: rgba(var(--white-rgb),0.15);
  cursor: pointer;
}
.adm-switch::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  /* Knob follows the track's corner (2026-10-23): JASP.ER's --radius-xl is
     4px, so a round knob in a near-square track looked mismatched. */
  width: 20px; height: 20px; border-radius: max(0px, calc(var(--radius-xl) - 2px));
  background: var(--white); transition: left 0.15s ease;
}
.adm-switch.is-on { background: var(--status-green); }
.adm-switch.is-on::after { left: 22px; }
.adm-switch:disabled { opacity: 0.6; cursor: default; }

/* ── Activity Logs (plain/functional per §10.8) ─────────────────────────── */
.adm-table--plain th, .adm-table--plain td { font-family: var(--font-thai); font-size: 12px; }
.adm-table--plain tr.is-zebra { background: rgba(var(--white-rgb),0.02); }
.adm-log-type {
  /* Exception: the Type chip keeps its own font regardless of the table's
     font-family, per explicit request — it reads as a status code, not prose. */
  font-family: monospace;
  font-size: 10px; font-weight: 700;
  background: rgba(var(--white-rgb),0.06);
  border-radius: var(--radius-sm); padding: 2px 6px;
}
.adm-log-type--approve, .adm-log-type--auth { background: rgba(var(--status-green-rgb),0.16); color: var(--status-green); }
.adm-log-type--reject, .adm-log-type--delete { background: rgba(var(--status-red-rgb),0.16); color: #FF8A8A; }   /* 2026-10-23: --status-red on its own tint measured 4.34:1 at 10px; same shade as .my-badge--rejected */
.adm-log-type--phase, .adm-log-type--reset { background: rgba(var(--brand-gold-rgb),0.16); color: var(--accent-gold-text); }
.adm-log-sentence { font-size: 13px; }
.adm-log-sentence__meta { color: var(--text-secondary); font-size: 11px; margin-top: 2px; }
.adm-log-ref { font-size: 11px; color: var(--text-secondary); cursor: help; }

/* ── Shared detail-card primitives (used by the User Profile page below) ── */
.adt-card {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 16px;
  margin-bottom: 14px;
}
.adt-card__title { font-size: 13px; font-weight: 700; margin-bottom: 10px; }
.adt-card__title-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.adt-card__title-row .adt-card__title { margin-bottom: 0; }
.adt-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; font-size: 13px; }
.adt-row__label { color: var(--text-secondary); }
.adt-row__value { text-align: right; }

/* ── User Profile (new page, 2026-07-07 — topbar search + User Management's
   "ดูข้อมูล" both land here; read-only, no approve/reject/delete controls). ── */
.adup-hero { display: flex; align-items: center; gap: 14px; margin: 16px 0 20px; }
/* Plain section divider (2026-07-15 ask) — was .my-divider's constellation
   line+star; generous top margin gives each section's heading real
   breathing room instead of sitting flush under the line above it. */
.adup-divider { height: 0; border-top: 1px solid var(--border); margin: 28px 0 20px; }
.adup-hero__avatar {
  width: 56px; height: 56px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 26px;
  border-radius: var(--radius-full);
  border: 3px solid var(--border);
  background: var(--bg-elevated);
}
.adup-hero__name { font-size: 18px; font-weight: 700; margin-bottom: 6px; }
.adup-ticket-list { display: flex; flex-direction: column; gap: 10px; }
.adup-ticket {
  display: flex;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.adup-ticket__rail { width: 4px; flex-shrink: 0; }
.adup-ticket__body { flex: 1; padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; }
.adup-ticket__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.adup-ticket__seat { font-weight: 700; font-size: 13px; }
.adup-ticket__meta { display: flex; gap: 14px; font-size: 12px; color: var(--text-secondary); }
.adup-ticket__dates { display: flex; flex-direction: column; gap: 2px; font-size: 10px; color: var(--text-secondary); opacity: 0.75; }

/* ── Zone Summary + Zone Detail (new pages, 2026-07-07) ───────────────────── */
.adz-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
}
.adz-card {
  display: block;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 16px;
  text-decoration: none;
  color: var(--text-primary);
  transition: border-color 0.12s ease, transform 0.12s ease;
}
@media (hover: hover) { .adz-card:hover { border-color: rgba(var(--brand-gold-rgb),0.4); transform: translateY(-1px); } }
.adz-card__head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.adz-card__swatch { width: 12px; height: 12px; border-radius: var(--radius-full); flex-shrink: 0; }
.adz-card__code { font-size: 18px; font-weight: 700; }
.adz-card__chip {
  font-size: 10px; color: var(--text-secondary);
  background: rgba(var(--white-rgb),0.06);
  border-radius: var(--radius-sm); padding: 2px 7px;
}
.adz-card__section { font-size: 12px; color: var(--text-secondary); margin-bottom: 10px; }
.adz-card__counts { display: flex; flex-wrap: wrap; gap: 6px; font-size: 12px; margin-bottom: 10px; }
.adz-card__counts-label { font-weight: 300; color: var(--text-secondary); }
.adz-card__counts b { color: var(--text-primary); font-weight: 700; }
.adz-card__counts-sep { color: var(--border); }
.adz-card__stats { display: flex; flex-wrap: wrap; gap: 6px; }
.adz-stat-chip { font-size: 10px; font-weight: 700; border-radius: var(--radius-sm); padding: 2px 6px; }
.adz-stat-chip--verified { background: rgba(var(--status-green-rgb),0.16); color: var(--status-green); }
.adz-stat-chip--pending { background: rgba(250,191,89,0.16); color: #FABF59; }
.adz-stat-chip--unverified { background: rgba(var(--white-rgb),0.08); color: var(--text-secondary); }
.adz-stat-chip--rejected { background: rgba(var(--status-red-rgb),0.16); color: var(--status-red); }
.adz-stat-chip--forsale { background: rgba(var(--brand-teal-rgb),0.16); color: var(--accent-teal); }

.adz-hero { display: flex; align-items: center; gap: 10px; margin: 16px 0 4px; flex-wrap: wrap; }
.adz-hero__swatch { width: 20px; height: 20px; border-radius: var(--radius-full); flex-shrink: 0; border: 2px solid var(--border); }
.adz-hero__code { font-size: 24px; font-weight: 700; }
.adz-hero__chip {
  font-size: 11px; color: var(--text-secondary);
  background: rgba(var(--white-rgb),0.06);
  border-radius: var(--radius-md); padding: 3px 9px;
}
.adz-section { font-size: 13px; color: var(--text-secondary); margin-bottom: 16px; }

/* ── Mobile (<768px, MOBILE_STRATEGY §B2 breakpoint) ────────────────────────
   Placed last on purpose — CSS cascade order matters here as much as the
   media query itself: several unconditional `.adm-*` rules earlier in this
   file would otherwise win over same-specificity rules declared inside an
   earlier media query. Sidebar becomes an off-canvas drawer instead of
   squeezing the 240px column into a narrow viewport; topbar drops the
   search box. Tables keep their real <table> structure (2026-07-07 decision
   — a stacked-card/accordion pass was tried and reverted) and instead
   scroll horizontally inside their own container, so the rest of the page
   never scrolls sideways. */
@media (max-width: 768px) {
  .adm-shell { flex-direction: column; }

  .adm-sidebar {
    position: fixed; top: 0; left: 0; bottom: 0;
    /* 2026-08-22 — pads the drawer's own content past the status bar and
       home indicator now that viewport-fit=cover lets it reach both. */
    padding-top: var(--safe-top);
    padding-bottom: var(--safe-bottom);
    padding-left: var(--safe-left);
    z-index: 500;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: 4px 0 24px rgba(var(--black-rgb),0.4);
  }
  .adm-sidebar.is-open { transform: translateX(0); }
  .adm-scrim.is-open { display: block; }

  .adm-mobile-toggle { display: flex; }
  /* justify-content:flex-start, not the desktop space-between — with the
     search box hidden below, space-between had only 2 children left
     (hamburger, title) and stretched them to opposite ends, making the
     page title read as right-aligned instead of sitting next to the menu
     button. */
  .adm-topbar { padding: 14px 16px; gap: 12px; justify-content: flex-start; }
  .adm-topbar__search { display: none; }
  .adm-content { padding: 16px; }

  /* Stat cards: fit as many as the viewport allows (usually 3) and only
     drop to fewer once a card gets too narrow for its own content. */
  .adm-stats { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); }
  .adm-phase-grid { grid-template-columns: 1fr; }

  /* Search sits alone on its own row; every other filter control (selects,
     the Export CSV button, etc.) wraps onto the row(s) below and splits
     into 2–3 columns instead of scrolling off-screen. Giving the text input
     flex-basis:100% forces the wrap point right after it. */
  .adm-filters { flex-wrap: wrap; overflow-x: visible; }
  .adm-filters > input[type="text"], .adm-filters > .search-input-wrap { flex: 1 1 100%; min-width: 0; }
  .adm-filters > select, .adm-filters > button { flex: 1 1 30%; min-width: 0; }

  /* Table scrolls horizontally within its own box; nothing else on the page
     moves sideways. min-width keeps columns from squeezing illegibly thin
     before the scrollbar kicks in. white-space:nowrap stops long cell text
     (e.g. a timestamp) from line-wrapping inside a squeezed column — the
     whole point of the horizontal scroll is that columns keep their natural
     width instead of wrapping awkwardly. No gradient/fade affordance here
     by request — the scrollbar itself (tokens.css) is the only cue.
     touch-action:pan-x tells the browser this element only ever handles
     horizontal drags — vertical swipes starting on the table pass straight
     through to the page instead of the gesture being ambiguous. */
  .adm-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; touch-action: pan-x; }
  .adm-table { min-width: 640px; }
  .adm-table th, .adm-table td { white-space: nowrap; }
}

/* ── Sponsor Giveaways admin forms (brand / campaign+prizes) ───────────────
   Reuses .adm-login-field's input styling (generic, not scoped to the login
   card) for text/select/textarea/date fields instead of inventing a second
   field style. */
.adm-login-field textarea, .adm-login-field select {
  width: 100%;
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 11px 12px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 14px;
}
.adm-login-field textarea:focus, .adm-login-field select:focus { outline: none; border-color: var(--accent-gold); }
.adm-login-field textarea { resize: vertical; min-height: 90px; }
.adm-login-field--toggle { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

.adm-form {
  max-width: 720px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 22px;
  margin-bottom: 20px;
}
.adm-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.adm-form-section-title { font-size: 13px; font-weight: 700; color: var(--accent-gold-text); margin: 22px 0 10px; }
.adm-form-section-title:first-child { margin-top: 0; }
.adm-form-actions { display: flex; gap: 10px; margin-top: 8px; }

.adm-checkbox-group { display: flex; flex-wrap: wrap; gap: 8px; }
.adm-checkbox-item {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(var(--white-rgb),0.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 7px 10px;
  font-size: 12px;
  cursor: pointer;
  /* Button-safe reset (2026-07-24 ask) — some callers now render this as a
     plain <button> instead of <label><input type=checkbox>, since the chip
     itself is already the whole clickable control; buttons don't inherit
     color/font the way labels do by default, so restate both explicitly. */
  font-family: inherit;
  color: var(--text-primary);
}
.adm-checkbox-item input { margin: 0; }
.adm-checkbox-item.is-checked { border-color: var(--accent-gold); color: var(--accent-gold-text); background: rgba(var(--brand-gold-rgb),0.1); }
/* A checkbox chip inside an .adm-login-field (2026-10-25, Giveaway item
   "จำกัดต่อคน"): undo the field's block label + full-width input rules. */
.adm-login-field label.adm-checkbox-item--inline { display: inline-flex; margin-bottom: 0; color: var(--text-primary); font-size: 13px; }
.adm-login-field label.adm-checkbox-item--inline.is-checked { color: var(--accent-gold-text); }
.adm-login-field .adm-checkbox-item--inline input { width: auto; padding: 0; border: 0; background: none; accent-color: var(--accent-gold); }

/* Chevron hint on a text input backed by a <datalist> (2026-07-24 ask) — a
   plain text box gives no visual cue that typing opens suggestions; same
   arrow treatment as .nt-select-wrap (giveaway.css), just for a text input
   instead of a real <select>. */
.adm-suggest-wrap { position: relative; }
.adm-suggest-wrap input { padding-right: 30px; }
.adm-suggest-wrap::after {
  content: '▾';
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 9px;
  color: var(--text-secondary);
  pointer-events: none;
}

/* Locked/disabled input with the lock icon INSIDE the field itself
   (2026-07-24 ask), not just next to its label — a clearer "you can't type
   here" cue right where the cursor would otherwise go. */
.adm-locked-input-wrap { position: relative; }
.adm-locked-input-wrap input { padding-right: 34px; }
.adm-locked-input-wrap__icon {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  /* text-secondary (139,143,168) on its own already reads fine against the
     dark input; the extra opacity:0.7 stacked on top (2026-07-24 fix, was
     dimming it further, looked closer to sunken-black than a visible icon)
     is gone. */
  color: var(--text-secondary);
  pointer-events: none;
}
.adm-locked-input-wrap__icon svg { width: 12px; height: 12px; }

.adm-logo-upload { display: flex; align-items: center; gap: 14px; }
.adm-logo-upload__preview {
  width: 64px; height: 64px; flex-shrink: 0;
  border-radius: var(--radius-lg); border: 1px dashed var(--border);
  background: rgba(var(--white-rgb),0.04) center/cover no-repeat;
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; color: var(--text-secondary); text-align: center;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
@media (hover: hover) { .adm-logo-upload__preview:hover { border-color: var(--accent-gold); } }
/* Icon + short label instead of a bare "IMG" placeholder string (2026-07-24
   fix) — reads as an upload affordance, not an unfinished placeholder. Only
   shown when the preview has no background-image set (empty state). */
/* opacity:0.6 removed (2026-07-24 fix) — stacked on top of the already-muted
   text-secondary color it inherits, this read as a dark/sunken icon rather
   than a clear upload affordance against the dark preview box. */
.adm-logo-upload__preview-empty { display: flex; flex-direction: column; align-items: center; gap: 4px; color: var(--text-secondary); pointer-events: none; }
.adm-logo-upload__preview-empty svg { width: 18px; height: 18px; }

/* Full-width dropzone variant (2026-07-24 ask, giveaway-form.js only — the
   base 64px square elsewhere, e.g. admin-fanproject-edit.js's own logo
   upload, is unaffected since it never adds this modifier). */
.adm-logo-upload--wide { display: block; }
.adm-logo-upload__preview--wide {
  width: 100%;
  height: 140px;
  flex-direction: column;
  gap: 6px;
}
.adm-logo-upload__preview--wide .adm-logo-upload__preview-empty { flex-direction: row; font-size: 13px; gap: 8px; }
.adm-logo-upload__preview--wide .adm-logo-upload__preview-empty svg { width: 22px; height: 22px; }

.adm-prize-row {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px;
  margin-bottom: 12px;
  background: rgba(var(--white-rgb),0.02);
}
.adm-prize-row__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.adm-prize-row__title { font-size: 12px; font-weight: 700; color: var(--text-secondary); }
.adm-prize-row__actions { display: flex; align-items: center; gap: 8px; }
.adm-prize-row__actions .tk-btn--icon:disabled { opacity: 0.35; cursor: not-allowed; }
.adm-prize-row__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }

@media (max-width: 768px) {
  .adm-form { padding: 16px; max-width: 100%; }
  .adm-form-grid, .adm-prize-row__grid { grid-template-columns: 1fr; }
}

/* Ride Pact reports (admin-marketplace.js — §13, folded into this same
   page rather than a separate admin screen). List, not a table, since a
   ride_shares post has a different shape than a ticket listing. */
.adm-gohome-reports { margin-top: 24px; }
.adm-gohome-reports__head { margin-bottom: 12px; }
.adm-gohome-reports__title { font-size: 14px; font-weight: 700; }
.adm-gohome-reports__empty { font-size: 12px; color: var(--text-secondary); padding: 12px 0; }
.adm-gohome-report {
  position: relative; /* anchors .adm-gohome-report__corner-btn (2026-07-15 ask) */
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 12px 44px 12px 14px; margin-bottom: 10px;
  background: rgba(var(--white-rgb),0.02);
}
/* Delete in the card's bottom-right corner (2026-07-15 ask) — was its own
   full-width button row under every card. */
.adm-gohome-report__corner-btn { position: absolute; right: 10px; bottom: 10px; }
.adm-gohome-report__route { font-size: 13px; font-weight: 700; }
.adm-gohome-report__meta { font-size: 11px; color: var(--text-secondary); margin-top: 4px; }
.adm-gohome-report__reason { font-size: 12px; color: var(--like-red); margin-top: 6px; font-weight: 600; }
.adm-gohome-report__detail { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
.adm-gohome-report__time { font-size: 10px; color: var(--text-secondary); margin-top: 6px; }

/* admin-health.js redesign (2026-07-26 ask — "too cluttered", full page
   redesign): the on/off toggle collapses to one row instead of its own
   card, and the add form is a disclosure instead of always taking space. */
.adh-toggle-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 16px;
  background: var(--bg-elevated, rgba(var(--white-rgb),0.03));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin-bottom: 20px;
}
.adh-toggle-row__label { font-size: 13px; font-weight: 700; }
.adh-toggle-row__desc { font-size: 11px; color: var(--text-secondary); margin-top: 2px; }

.adh-add-toggle {
  display: block;
  margin: 4px 0 20px;
  padding: 10px 16px;
  background: rgba(var(--brand-gold-rgb), 0.12);
  border: 1px solid rgba(var(--brand-gold-rgb), 0.4);
  border-radius: var(--radius-md);
  color: var(--accent-gold-text);
  font: 700 13px var(--font-thai);
  cursor: pointer;
}
.adh-form-panel:not([hidden]) { margin-bottom: 20px; }

.adh-source-badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 600;
}
.adh-source-badge--self { background: rgba(var(--brand-teal-rgb), 0.14); color: var(--accent-teal); }
.adh-source-badge--manual { background: rgba(140, 140, 140, 0.22); color: #C8C8C8; }

/* admin-homesafe.js — identity cell (avatar + name/flag + handle) reused
   inside an .adm-table row, same visual language as .hs-card's left side
   (homesafe.css) scaled down for a table cell. */
.adh-user-cell { display: flex; align-items: center; gap: 8px; }
.adh-user-cell__avatar {
  width: 28px; height: 28px; border-radius: var(--radius-full); flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px;
}
.adh-user-cell__name { display: flex; align-items: center; gap: 6px; font-weight: 600; }
.adh-user-cell__handle { font-size: 11px; color: var(--text-secondary); }

/* Membership switch card (2026-10-23) — tinted accent border so the one card
   that opens/closes all of /p stands apart from the module grid. */
.adm-phase-card--membership { margin-bottom: 16px; border-color: var(--border); }
/* Closed is the normal pre-launch state → neutral border, not the red family
   the maintenance (danger) card uses; open → green (2026-10-23, conflict K3). */
.adm-phase-card--membership.is-on { border-color: rgba(var(--status-green-rgb), 0.55); }
/* Module card whose page sits under /p while the membership system is closed
   (conflict K2): still switchable (pre-configure), visibly not live. */
.adm-phase-card.is-dependent-off { border-style: dashed; }   /* no opacity: it would drop text contrast */
.adm-phase-card__dep { font-size: 11px; color: var(--text-secondary); margin-top: 2px; }

.adm-phase-card--test.is-on { border-color: rgba(var(--accent-gold-rgb), 0.55); }
