/*
 * glass.css — Glassmorphism + accent-backlight design system
 *
 * Adopted by: admin/subscriptions.php
 *             admin/my_subscription_detail.php
 *             admin/dashboard.php
 *
 * Companion: assets/app.css
 *   Cascade order: <link app.css> FIRST, <link glass.css> SECOND.
 *   glass.css purposefully overrides .modal-overlay/.modal-box and a few
 *   table cell rules where they exist in app.css, so order matters.
 *
 * Token dependencies on app.css:
 *   --text, --bg, --accent, --success, --warn, --danger  (hex values, app.css owns these)
 *
 * This file declares its own RGB-triplet versions (--*-rgb) used for
 * rgba(var(--*-rgb), alpha) compositions. Triplets mirror the hex
 * values in app.css; if app.css ever changes, update them here too.
 *
 * Themes: html[data-theme="dark"] (default) and html[data-theme="light"].
 * Light overrides are placed ADJACENT to their dark counterparts, not
 * collected in a separate block — single-source maintenance.
 *
 * AUTO-DECISION: html[data-theme="light"] (not bare [data-theme="light"])
 *   for selector-form consistency with app.css. Specificity tie matters
 *   when extra root-level selectors get layered later.
 *
 * Naming:
 *   .glass-*       — surface containers (card, row, divider, …)
 *   .btn-glass     — buttons with backlight glow
 *   .badge-glass   — pill badges with status-coloured borders
 *   Other classes are subcomponents (kebab-case under their parent).
 */

/* ───────────────────────── 1. RGB triplet tokens ───────────────────────── */
/* RGB-triplet tokens (--accent-rgb / --success-rgb / --warn-rgb / --danger-rgb)
   are defined in app.css for both dark and light themes in comma-separated
   form, so every `rgba(var(--*-rgb), alpha)` call site in glass.css resolves
   correctly. Earlier versions of this file redefined those tokens with
   space-separated values, which broke legacy `rgba(...,a)` consumers. */

/* ───────────────────────── 1a. Theme tokens + a11y primitives ───────────── */
/* UI-0035: themed token for sticky table headers. Was a hardcoded RGBA literal
   in dashboard.php that painted opaquely over operator-set custom backgrounds.
   Default (and the dark theme) use the dark surface; light theme overrides. */
:root, html[data-theme="dark"] { --surface-thead-sticky: rgba(20,22,32,0.85); }
html[data-theme="light"]       { --surface-thead-sticky: rgba(255,255,255,0.88); }

/* UI-0003: site-wide keyboard focus ring. Many controls set outline:none on
   :focus and replace it with a box-shadow ring; those keep their own ring
   (higher specificity). This adds a visible token-based outline to every other
   focusable surface (links, buttons, row-as-link, badges, menu items, anything
   with [tabindex]) so keyboard users never lose the focus indicator (WCAG
   2.4.7). Outline (not box-shadow) so it never fights existing shadow rings. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible,
[role="menuitem"]:focus-visible,
[role="radio"]:focus-visible,
.glass-row:focus-visible{
  outline: 2px solid var(--ring);       /* Phase 5 stage 2: focus ring token */
  outline-offset: 2px;
  border-radius: var(--r-1);
}

/* UI-0004: skip-to-content link. Visually hidden until focused, then pinned to
   the top so keyboard users can bypass the brand + nav + impersonate bar. */
.skip-link{
  position: absolute;
  inset-inline-start: 12px;
  top: -60px;
  z-index: 2000;
  padding: 10px var(--sp-4);
  border-radius: 0 0 12px 12px;
  background: rgba(var(--accent-rgb), 0.95);
  color: #fff;
  font-weight: 600;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus{
  top: 0;
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* ───────────────────────── 1b. Form-control box-sizing reset ─────────────
   Every `.glass-*` form control declares `width: 100%` plus inner padding.
   Without box-sizing: border-box the padding adds to the 100% and the
   control overflows its parent — visible most cleanly on the wallet
   top-up modal's amount input (which carries 20 px horizontal padding +
   1.5rem font, so the overflow is ~40 px at 320 px viewport widths).
   Setting box-sizing here once covers every glass control without
   needing per-rule edits. */
.glass-amount-input,
.glass-textarea,
.glass-select,
.glass-input-group input,
.glass-input-group textarea,
.glass-stepper .stepper-input,
.form-control,
.btn-glass.block{
  box-sizing: border-box;
}

/* ───────────────────────── 2. Glass card ─────────────────────────────── */
.glass-card{
  background: var(--glass-card-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  box-shadow: var(--shadow);
  padding: var(--sp-5);
}
/* RULE-9 (Phase 6): .sm modifier — slim card variant for filter/control bars
   and inline notification cards. Smaller radius + tighter padding. */
.glass-card.sm{
  padding: var(--sp-3) var(--sp-3-5);
  border-radius: var(--r-2);
}

/* ── Commit 4 (#29/#36): `.list-shell` — no card-in-card on phone ──────────
   representatives.php and deposits.php both wrap their listing in a .glass-card.
   On desktop that shell IS the card around a real table — correct. But ≤768px
   the contents become cards in their own right (rep cards; table rows via the
   card-collapse), so the shell stacked a SECOND border + background + shadow
   around them: two nested surfaces that ate horizontal room and read heavy.
   On phone the shell drops to a layout-only wrapper — the item is the only
   visible card. Opt-in via the class, so other .glass-card users are untouched. */
@media (max-width: 1023px){
  .glass-card.list-shell,
  html[data-theme="light"] .glass-card.list-shell{
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    padding: 0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* ───────────────────────── Icon system (lib/Icons.php) ────────────────
   Inline SVG glyphs that replaced the emoji set. Sized in `em` so they
   scale with the surrounding text and inherit color via currentColor.
   Standalone glyphs inside fixed chips (nav/stat/action) size in px. */
.icon{
  width: 1.15em; height: 1.15em;
  display: inline-block;
  vertical-align: -0.18em;
  flex-shrink: 0;
}
.icon-sm{ width: 1em;   height: 1em; }
.icon-lg{ width: 1.5em; height: 1.5em; }
.icon-xl{ width: 2rem;  height: 2rem; }
/* Inline icon that leads a heading/label — small trailing gap, optical lift */
.icon-inline{ margin-inline-end: 6px; vertical-align: -0.16em; }
/* Fixed-size contexts */
.nav-icon .icon        { width: 24px; height: 24px; }
/* Stat-card glyphs take their tile's accent (falls back to text color for
   an unvariant card) — color stays on the icon, not the whole surface. */
.stat-icon .icon       { width: 22px; height: 22px; color: var(--stat-accent, currentColor); }
.rep-action-glyph .icon,
.rep-menu-glyph .icon,
.action-glyph .icon    { width: 20px; height: 20px; }
.empty-icon .icon      { width: 40px; height: 40px; }
.btn-glass .icon       { width: 18px; height: 18px; }
.btn-glass.icon-only   { padding: 10px; }
.theme-toggle .icon,
.auth-theme-toggle .icon { width: 20px; height: 20px; }
.glass-confirm-icon .icon { width: 30px; height: 30px; }
.toast-close .icon     { width: 16px; height: 16px; }
/* Semantic tints for standalone status icons (color used sparingly) */
.icon.text-success { color: var(--success); }
.icon.text-warn    { color: var(--warn); }
.icon.text-danger  { color: var(--danger); }
.icon.text-accent  { color: var(--accent); }
.icon.text-muted   { color: var(--text-muted); }

/* ───────────────────────── 3. Backlight button ───────────────────────── */
.btn-glass{
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--glass-btn-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-1);            /* 8px — control radius (Phase 5 stage 2) */
  padding: var(--sp-3) var(--sp-5);
  font-weight: 600; font-size: 0.9rem; font-family: inherit;
  color: var(--text);
  cursor: pointer; text-decoration: none;
  /* Phase 5 stage 2: transition color/bg/border only (no transform lift) */
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
  z-index: 1; min-height: 44px;
}
.btn-glass:hover { border-color: rgba(var(--accent-rgb),0.35); background: var(--hover); }
.btn-glass:active { transform: translateY(0); }
.btn-glass:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* Primary — solid accent, the one prominent action per view */
.btn-glass.primary{
  /* --accent-cta, not --accent: white-on-accent was 3.61:1 in dark / 4.30:1
     in light, both under WCAG AA 4.5 for normal text. See app.css. */
  background: var(--accent-cta);
  border-color: transparent;
  color: #fff;
}
/* Hover DARKENS rather than brightens. The old brightness(1.06) lightened the
   fill to 4.14:1 and pushed the button back under AA exactly while the pointer
   was on it; 0.94 gives the same "something changed" feedback at 4.98:1. */
.btn-glass.primary:hover{ background: var(--accent-cta); filter: brightness(0.94); border-color: transparent; }


.btn-glass.block { display: flex; width: 100%; }
.btn-glass.sm    { padding: var(--sp-2) var(--sp-3-5); min-height: 36px; font-size: var(--fs-1); }

/* Semantic tonal variants — success (approve / activate) + danger (reject / delete).
   Stage 3c fix: these classes were USED across the app (deposits تایید/رد, subscription
   activate/delete, sudo_admins, queue, confirm-modal OK, …) but had NO rule — every
   `.btn-glass.success`/`.danger` rendered as the neutral base, so positive vs
   destructive actions were indistinguishable. Recipe mirrors .badge-glass / .glass-alert
   (rgba semantic wash + colored text/border), reusing the per-theme tokens. Kept TONAL,
   never solid — `.primary` stays the single solid action per view (no conflict with the
   locked "one solid-primary per view" rule). Defined HERE (before .cta-deposit further
   down) on purpose: a button carrying both `success cta-deposit` (wallet top-up) keeps
   its solid-primary cta-deposit look because the later rule wins at equal specificity. */
.btn-glass.success{
  background: rgba(var(--success-rgb), 0.10);
  border-color: rgba(var(--success-rgb), 0.40);
  color: rgb(var(--success-text-rgb));
}
.btn-glass.danger{
  background: rgba(var(--danger-rgb), 0.10);
  border-color: rgba(var(--danger-rgb), 0.40);
  color: rgb(var(--danger-text-rgb));
}
/* PHASE5-WARN: `.warn` is the third tonal variant and had the identical bug —
   9 call sites, no rule, so every cautionary button read as a plain one. Same
   recipe/tokens as `.badge-glass.warn`; app.css redefines both per theme, so
   light needs no override. Same (0,2,0) and same block as its siblings, so the
   `.cta-*` protection above covers it too. */
.btn-glass.warn{
  background: rgba(var(--warn-rgb), 0.10);
  border-color: rgba(var(--warn-rgb), 0.40);
  color: rgb(var(--warn-text-rgb));
}

/* ───────────────────────── 4. Glass badge ────────────────────────────── */
.badge-glass{
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-full);
  font-size: 0.8rem; font-weight: 600; line-height: 1; /* Commit 1 (#11): 12px status-badge floor */
  background: var(--glass-card-bg);
  border: 1px solid var(--badge-border-color, rgba(255,255,255,0.12));
  color: var(--badge-text-color, var(--text));
}

/* Commit 1 (#13/#54): toman() wraps value+unit in .money-value so the amount and
   "تومان" never break across a line and stay a single readable group. */
.money-value{ white-space: nowrap; }
.badge-glass.active  { --badge-border-color: rgba(var(--success-rgb), 0.5); --badge-text-color: rgb(var(--success-text-rgb)); }
.badge-glass.warn    { --badge-border-color: rgba(var(--warn-rgb), 0.5);    --badge-text-color: rgb(var(--warn-text-rgb)); }
.badge-glass.danger  { --badge-border-color: rgba(var(--danger-rgb), 0.5);  --badge-text-color: rgb(var(--danger-text-rgb)); }
.badge-glass.deleted { --badge-border-color: rgba(156,163,175,0.5);        --badge-text-color: rgb(209,213,219); }
.badge-glass.neutral { --badge-border-color: rgba(var(--accent-rgb), 0.4);  --badge-text-color: rgb(160,178,255); }
/* Light-theme text colours — the dark-mode pales above fail WCAG AA contrast
   against the white card the badge sits on. Mirrors .glass-tx-badge's
   light-theme overrides (see §32). */
html[data-theme="light"] .badge-glass.active  { --badge-text-color: rgb(var(--success-text-rgb)); }
html[data-theme="light"] .badge-glass.warn    { --badge-text-color: rgb(var(--warn-text-rgb)); }
html[data-theme="light"] .badge-glass.danger  { --badge-text-color: rgb(var(--danger-text-rgb)); }
html[data-theme="light"] .badge-glass.deleted { --badge-text-color: rgb(75,85,99); }
html[data-theme="light"] .badge-glass.neutral { --badge-text-color: rgb(67,56,202); }

/* RETIRED: pulse-glow (2.4s infinite, opacity 1 → 0.55 → 1) on .badge-glass.active.
   The earlier AUTO-DECISION traded readability for attention: because it animated
   the element's OPACITY, it dragged the badge TEXT down with it, and at the 0.55
   trough the label fell to ~2.5:1 in light theme — under half the AA floor — twice
   every 2.4 seconds, indefinitely, on a persistent status label rather than a
   transient alert. Reviewed with the operator against a live side-by-side; the
   static badge was chosen. The success-tinted ring below still distinguishes the
   live state, so the signal survives without the readability cost.
   If a pulse is ever wanted back, animate box-shadow/outline — never the opacity
   of an element whose own text has to stay legible. */
.badge-glass.active {
  box-shadow: 0 0 0 3px rgba(var(--success-rgb), 0.25);
}

/* ───────────────────────── 5. Glass row (list) ───────────────────────── */
/* Commit 5 (#69): deterministic column widths.
   Every .glass-row is its OWN grid, so `auto` tracks were sized per row against
   that row's content — measured live, the status column ranged 51–67px and the
   volume column 52–61px across three rows. The list therefore looked like a
   table whose columns never lined up, and no header could align to it. Fixing
   the non-flexible tracks makes rows align with each other AND lets
   .glass-row-head (below) sit exactly above them. Widths are the measured
   content maxima plus headroom for the longest Persian status label. */
.glass-row{
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) 92px 84px 188px 84px 24px;
  gap: var(--sp-4);
  align-items: center;
  padding: var(--sp-3-5) var(--sp-4-5);
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--r-2);
  margin-bottom: 10px;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
  cursor: pointer; text-decoration: none; color: inherit;
}
/* AUTO-DECISION: entire row clickable (anchor wrapper). Simpler than a
   per-row dropdown, friendlier on mobile where small action affordances
   regress. Actions live on the detail page. */
.glass-row:hover{
  background: rgba(255,255,255,0.06);
  border-color: rgba(255,255,255,0.12);
  /* AUTO-DECISION: small leftward nudge feels like a hover tick — physical
     coords (transform is direction-agnostic) chosen deliberately. */
  transform: translateX(-2px);
}
html[data-theme="light"] .glass-row{
  border-color: rgba(0,0,0,0.05);
}
html[data-theme="light"] .glass-row:hover{
  background: rgba(0,0,0,0.05);
  border-color: rgba(0,0,0,0.10);
}
/* The status track is fixed now, so the pill must not stretch to fill it.
   (The ≤720px rule further down re-pins it to the inline-end on mobile.) */
.glass-row > .badge-glass{ justify-self: start; }

/* Commit 5 (#69): column header for the desktop list. Same grid template and
   horizontal padding as .glass-row (+1px for the row's border) so the labels
   sit exactly above their values. aria-hidden: each row is a single <a> whose
   text already reads id → name → status → volume → date in order, so this is a
   visual aid, not an accessibility tree the SR needs to re-traverse. Hidden at
   ≤720px, where the row collapses to its stacked mobile layout with in-card
   labels. */
.glass-row-head{
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) 92px 84px 188px 84px 24px;
  gap: var(--sp-4);
  align-items: center;
  padding: 0 calc(var(--sp-4-5) + 1px) var(--sp-2);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.2px;
  opacity: 0.55;
}
@media (max-width: 1023px){ .glass-row-head{ display: none; } }

/* Commit 6 (#71): keep column context during long scrolls. With per-page set to
   50 the list runs several thousand pixels, and once the header scrolls away the
   values lose their meaning (is the left number volume or date?). The header
   pins to the top of the viewport; an opaque page-background is required so rows
   don't show through as they pass beneath. Desktop only — the mobile layout
   carries its labels inside each card. */
@media (min-width: 1024px){
  .glass-row-head{
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--bg);
    padding-top: var(--sp-2);
  }
}

.glass-row .row-id   { font-family: ui-monospace, monospace; opacity: 0.55; font-size: var(--fs-1); }
.glass-row .row-name { font-weight: 600; }
.glass-row .row-pg   { font-family: ui-monospace, monospace; font-size: 0.78rem; opacity: 0.7; direction: ltr; }
.glass-row .row-vol  { font-size: 0.85rem; opacity: 0.85; white-space: nowrap; }
.glass-row .row-date { font-size: 0.80rem; opacity: 0.60; white-space: nowrap; }
.glass-row .row-chev { opacity: 0.4; font-size: 1.1rem; }
/* «ردیف» — the subscription's ordinal for its own owner, rendered inside the
   name cell rather than as a new column: the grid template above is shared with
   .glass-row-head, which bots.php also uses, so an 8th track would misalign that
   page's header. Sized to sit beside the username without competing with it. */
.glass-row .row-seq{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 1px 6px;
  margin-inline-end: 6px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,0.07);
  border: 1px solid rgba(255,255,255,0.10);
  font-size: 0.74rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
  vertical-align: 1px;
}
html[data-theme="light"] .glass-row .row-seq{
  background: rgba(0,0,0,0.05);
  border-color: rgba(0,0,0,0.10);
  opacity: 0.8;
}
@media (max-width: 1023px) {
  .glass-row { grid-template-columns: auto 1fr auto; row-gap: 6px; padding: var(--sp-3) var(--sp-3-5); }
  .glass-row .row-id   { grid-row: 1; grid-column: 1; }
  .glass-row .row-name { grid-row: 1; grid-column: 2; }
  /* The status badge shares column 3 with .row-date; the (wider) date sizes the
     column, so the auto-placed badge would stretch (default justify-self) and its
     centred text would drift to the inline-start edge. Pin it and hug its content
     so the pill stays content-width with the label centred. */
  .glass-row .badge-glass { grid-row: 1; grid-column: 3; justify-self: end; }
  .glass-row .row-pg   { grid-row: 2; grid-column: 1 / -1; }
  .glass-row .row-vol  { grid-row: 3; grid-column: 1 / 3; }
  .glass-row .row-date { grid-row: 3; grid-column: 3; }
  .glass-row .row-meters { grid-row: 4; grid-column: 1 / -1; }
  .glass-row .row-chev { display: none; }
}

/* ───────────────────────── 6. Filter toolbar (shared) ────────────────────
   Phase 5 stage 3b: ONE filter pattern for representatives / deposits /
   subscriptions.
     • Desktop: a single inline toolbar row — search (icon inside) · selects ·
       result count · clear. Not a card.
     • Mobile: full-width search + one "فیلترها" button opening a bottom sheet
       that holds the selects; active filters render as chips under the search.
   It is ONE <form>: submit gathers every named field wherever it sits visually,
   so request handling / query params are unchanged (the selects only MOVE
   between an inline row and a fixed sheet — they never leave the form). */
.filters-glass{
  display: flex; gap: var(--sp-3);
  padding: 0; background: none; border: 0; border-radius: 0;
  margin-bottom: var(--sp-5);
  flex-wrap: wrap;
  /* Items in this bar have unequal heights: a labelled field is
     label(17px)+gap(4)+control, while the search box, the submit button and the
     result count are bare controls. Under `align-items: center` those unequal
     boxes were centred against each other, so the CONTROLS — the things the eye
     actually lines up on — landed on three different baselines (measured 336 /
     324 / 316 on deposits: a 20px spread), which read as the buttons sitting
     above/below the selects. `flex-end` puts every control on one bottom edge
     and lets the labels stack upward; this is the same fix search.php already
     uses for its own control bar. */
  align-items: flex-end;
  /* One height for every control in the bar, so the shared bottom edge is also
     a shared top edge for the unlabelled ones (select was 41px, button 38px). */
  --filter-ctrl-h: 42px;
}
.filters-glass .glass-input,
.filters-glass .glass-select,
.filters-glass .glass-scope-field,
.filters-glass .btn-glass{
  min-height: var(--filter-ctrl-h);
  height: var(--filter-ctrl-h);
  box-sizing: border-box;
}
/* The actions group holds a button + the result-count text; give it the control
   height so the text centres within the same band instead of floating. */
.filters-glass .filter-actions{
  min-height: var(--filter-ctrl-h);
  align-items: center;
}
.filters-glass label { display: block; font-size: 0.8rem; opacity: 0.7; margin-bottom: 4px; } /* Commit 1 (#11): 12px floor */
.filters-glass input,
.filters-glass select{
  /* §2a fix: background-COLOR (not the `background:` shorthand) so the canonical
     .glass-select chevron background-image survives — the shorthand was wiping it,
     leaving the selects looking like plain text inputs at rest. */
  background-color: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: var(--sp-2) var(--sp-3);
  color: var(--text); font-size: 0.9rem; font-family: inherit;
  min-height: 40px;
}
/* §2a fix: keep the .glass-select dropdown chevron (+ its inline-start reserve,
   which the padding shorthand above resets) so a filter select reads as a
   dropdown, distinct from the search input. Tuned tighter for the 40px toolbar. */
.filters-glass select.glass-select{
  padding-inline-start: 34px;
  background-position: left 12px center;
}
.filters-glass input:focus,
.filters-glass select:focus{
  outline: none;
  border-color: rgba(var(--accent-rgb), 0.5);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15);
}
html[data-theme="light"] .filters-glass input,
html[data-theme="light"] .filters-glass select{
  background-color: #fff; border-color: rgba(0,0,0,0.10);
}

/* search field — search icon sits inside the input */
.filter-search{ position: relative; flex: 1 1 240px; min-width: 0; display: flex; }
.filters-glass .filter-search input{ width: 100%; padding-inline-start: 38px; }
.filter-search-icon{
  position: absolute; inset-inline-start: 12px; top: 50%; transform: translateY(-50%);
  display: inline-flex; opacity: 0.5; pointer-events: none;
}
.filter-search-icon svg{ width: 18px; height: 18px; }

/* the select group: inline on desktop (display:contents flattens the wrappers
   into the toolbar row), a bottom sheet on mobile. */
.filter-fields{ display: contents; }
.filter-sheet{ display: contents; }
.filter-field{ display: flex; flex-direction: column; }
/* `.field-caption` is a <span> that LOOKS like a field label but labels no form
   control — used where the "field" is a read-only note (the L1 scope display on
   representatives.php). A bare <label> there is what Chrome reports as
   "label has neither for= nor a nested input", and it is honest markup: a
   <label> that labels nothing is a lie to a screen reader. Same declarations,
   so the swap is invisible. */
.filter-field > label,
.filter-field > .field-caption{ font-size: 0.72rem; opacity: 0.7; margin-bottom: 4px; }
.filter-sheet-head, .filter-sheet-foot{ display: none; }   /* mobile-only sheet chrome */

/* trailing actions: submit (where the backend needs it) · result count · clear */
/* Commit 5 (#35/#72): `margin-inline-start:auto` shoved this group to the far
   opposite end of the toolbar. On deposits — which has no growing search field
   to absorb the slack — the selects sat against one edge and the apply button +
   result count against the other, with a large empty gap between: three visually
   separate islands for what is one operation. The actions now follow the fields
   directly, forming a single coherent toolbar. Pages that DO have a search field
   still spread naturally, because .filter-search keeps its flex-grow. */
.filter-actions{ display: flex; align-items: center; gap: var(--sp-2); }
.filter-result-count{ font-size: 0.85rem; color: var(--muted-foreground); white-space: nowrap; }

/* Commit 5 (#74): role/scope is faint metadata beside the title — deliberately
   NOT a pill, so it stops competing with the result-count badge next to it. */
.head-sub .head-scope{ font-size: 0.85rem; color: var(--muted-foreground); }

/* Commit 6 (#57): buy.php already ships class="wrap wrap-narrow", but app.css's
   @media(min-width:1024px) rule `.wrap{ max-width: var(--container-max) }` sits
   LATER in source at equal specificity (0,1,0) — so it silently overrode
   .wrap-narrow's 720px and the purchase form rendered at the full 1200px shell.
   That override is what made the form read as empty and stretched on desktop
   (the audit's "narrow column" premise was actually inverted). A compound
   selector outranks it; 960px matches the 880–960 target for form-heavy pages.
   Deliberately NOT reflowing volume/duration into two columns: at 960px the
   five duration buttons already sit comfortably on one row, and a 2-up split
   would cramp them for no gain. */
@media (min-width: 1024px){
  .wrap.wrap-narrow{ max-width: 960px; }
}

/* Commit 5 (#7): wider shell for DATA-heavy pages on large screens.
   The shared --container-max (1200px) left ~700px of empty gutter at 1920 while
   dense listings (representatives carries 10+ columns) were still cramped inside
   it. Opt-in via `.wide` on the four list pages only — form and detail pages keep
   the narrower measure, where a long line length actively hurts readability.
   Gated to ≥1280px so laptop widths are completely unaffected. */
@media (min-width: 1280px){
  .wrap.wide{ max-width: 1400px; }
}

/* mobile-only affordances (hidden on desktop) */
.filter-toggle{ display: none; }
.filter-chips{ display: none; }
.filter-chip{
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: var(--r-full);
  background: rgba(var(--accent-rgb), 0.12);
  border: 1px solid rgba(var(--accent-rgb), 0.30);
  color: var(--text); font-size: 0.8rem; text-decoration: none; white-space: nowrap;
}
.filter-chip .filter-chip-x{ opacity: 0.7; font-weight: 700; line-height: 1; }
html[data-theme="light"] .filter-chip{ background: rgba(var(--accent-rgb), 0.10); }

@media (max-width: 768px){
  .filters-glass{ gap: var(--sp-2); }
  .filter-search{ flex: 1 1 auto; }         /* fills row 1 beside the compact toggle */
  .filter-toggle{ display: inline-flex; flex: 0 0 auto; align-items: center; gap: 6px; }
  .filter-toggle .nb{ margin-inline-start: 2px; }
  .filter-actions{ display: none; }         /* submit/clear move into the sheet on mobile */

  /* selects → bottom sheet */
  .filter-fields{ display: none; }
  .filter-fields.open{
    display: flex; position: fixed; inset: 0; z-index: 1150;
    align-items: flex-end; background: rgba(0,0,0,0.45);
  }
  .filter-fields.open .filter-sheet{
    /* Part A (3b + f4): controls 48px, field-to-field 16px (8px base gap + an 8px
       margin between adjacent .filter-field), but the CHROME gaps (head↔fields,
       fields↔foot) are the tighter 8px and the outer padding is trimmed, so a
       2-field sheet stays compact. Height is content-driven with an 80dvh cap. */
    display: flex; flex-direction: column; gap: var(--sp-2);
    width: 100%; background: var(--popover);
    border-top-left-radius: var(--r-3); border-top-right-radius: var(--r-3);
    box-shadow: 0 -12px 40px rgba(0,0,0,0.40);
    padding: 8px var(--sp-4) calc(10px + env(safe-area-inset-bottom, 0));
    max-height: 80dvh; overflow-y: auto;
    animation: nav-more-sheet-in 260ms cubic-bezier(0.32,0.72,0,1);
  }
  .filter-fields.open .filter-sheet-head{
    display: flex; align-items: center; gap: 4px;
    font-weight: 700; font-size: var(--fs-3);
  }
  /* title pushes the reset + close actions to the row end (RTL-aware). */
  .filter-fields.open .filter-sheet-head > span{ margin-inline-end: auto; }
  .filter-fields.open .filter-sheet-foot{ display: flex; gap: var(--sp-2); margin-top: 0; }
  .filter-fields.open .filter-sheet-foot > *{ flex: 1; justify-content: center; }
  .filter-fields.open .filter-field{ width: 100%; }
  /* restore the 16px field-to-field rhythm on top of the 8px base gap (f4). */
  .filter-fields.open .filter-field + .filter-field{ margin-top: var(--sp-2); }
  .filter-fields.open .filter-field select,
  .filter-fields.open .filter-field input{ width: 100%; min-height: 48px; }
  .filter-sheet-close{
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; padding: 0; border: none; background: transparent;   /* f4: 44px tap target; the × glyph stays centered/small */
    color: var(--text); border-radius: var(--r-1); cursor: pointer;
  }
  /* Part B (3b): low-emphasis TEXT reset — JS-injected into the head, clears the
     sheet fields to their unfiltered defaults (no submit, no close). Deliberately
     subordinate to the solid "اعمال فیلتر": muted text, no fill, no border. */
  .filter-sheet-reset{
    border: 0; background: transparent;
    padding: 6px 10px; min-height: 44px;
    color: var(--muted-foreground);
    font-family: inherit; font-size: 0.85rem; font-weight: 500;
    cursor: pointer; border-radius: var(--r-1); white-space: nowrap;
  }
  .filter-sheet-reset:hover{ color: var(--text); }
  .filter-sheet-reset:focus-visible{ outline: 2px solid var(--ring); outline-offset: 2px; }
  .filter-chips:not(:empty){ display: flex; flex: 1 1 100%; flex-wrap: wrap; gap: 6px; }
}

.glass-pager{
  display: flex; align-items: center; justify-content: center;
  gap: var(--sp-3-5); flex-wrap: wrap;
  margin-top: 18px;
}
.glass-pager .pager-status{ font-size: 0.85rem; opacity: 0.75; }

/* ─────────────────── 6b. Glass filter bar (in-card) ───────────────────── */
/* Canonical filter/control ROW for forms that live INSIDE a .glass-card
   (the card already supplies the chrome — unlike .filters-glass above, which
   is the self-chromed standalone variant). Aligns labelled .glass-input-group
   controls and trailing .btn-glass actions onto a single baseline with a
   shared 44px height, so a label-less button never floats off the line and
   every control reads as one row.

   Why this exists: a .glass-input-group carries a default margin-bottom:12px;
   dropped into an align-items:flex-end row next to a bare button, that margin
   lifts the inputs 12px above the button's baseline (the exact misalignment
   this class neutralises). The selects/inputs and the button are also forced
   to the same 44px height. Layout-only — no markup semantics, names, ids, or
   behaviour change. Mirrors the page-scoped .search-form pattern. */
.glass-filter-bar{
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-3);
}
.glass-filter-bar .glass-input-group{
  margin-bottom: 0;
  flex: 1 1 160px;   /* equal-width fields that share the bar and fill it when wrapped */
  min-width: 0;      /* permit shrink below intrinsic width so wrapping is clean */
}
.glass-filter-bar .glass-input,
.glass-filter-bar .glass-select{ min-height: 44px; }
.glass-filter-bar .btn-glass{ flex: 0 0 auto; min-height: 44px; }
@media (max-width: 560px){
  /* Narrow screens: trailing action buttons take their own full-width row so
     they never wedge beside a half-width field. */
  .glass-filter-bar .btn-glass{ flex: 1 1 100%; width: 100%; }
}

/* ───────────────────────── 7. URL copy box ───────────────────────────── */
/* fixes-merged-02: drop the explicit copy button — the card number /
   crypto address are one-tap-selectable via user-select: all. Row
   stays a flex container only so the layout matches other elements
   that might still include a trailing meta chip. */
.url-copy-glass{
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 10px var(--sp-3-5);
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-2);
  /* A-L: no flex-wrap — children own `min-width: 0` + a scroll container,
     so the URL scrolls within its own box instead of pushing the copy
     button onto a second line on narrow viewports. */
  flex-wrap: nowrap;
  min-width: 0;
}
.url-copy-glass > .url-text,
.url-copy-glass > .card-number,
.url-copy-glass > .crypto-address{
  flex: 1 1 auto;
  min-width: 0;
  /* user-select: all — a single click/tap selects the entire string so
     the user can ⌘C / Ctrl-C / mobile-select it without an explicit
     copy affordance. The card-number / crypto-address already render
     with monospace + dir:ltr; this just adds the selection behaviour. */
  -webkit-user-select: all;
  -moz-user-select: all;
  user-select: all;
  cursor: text;
  /* Commit 5 (#65): was overflow:hidden + ellipsis — the tail of a subscription
     URL was simply unreadable, with no scroll and no tooltip; copy was the only
     way to see it. Horizontal scroll restores full inspection in place (the
     value stays on one line, and copy remains the primary action). Call sites
     also carry title="…" so a hover reveals it without scrolling. */
  overflow-x: auto;
  overflow-y: hidden;
  /* MUST stay `clip` — do NOT "restore" the ellipsis. #65 left
     `text-overflow: ellipsis` behind when it switched this box to
     overflow-x:auto, and the two cannot coexist: ellipsis paints only the
     first clientWidth of glyphs, so the ellipsized stub is narrower than
     the scroll range and scrolls straight out of the scrollport — past
     ~clientWidth the field renders EMPTY and the tail is unreachable by
     scrolling at all. Verified in Chromium at 390px: 223px visible /
     814px scrollWidth, blank at scrollLeft=581. `clip` is what makes the
     scroll #65 added actually usable. */
  text-overflow: clip;
  white-space: nowrap;
  scrollbar-width: thin;
  overscroll-behavior-inline: contain;
}
/* Keep the inline scrollbar unobtrusive on WebKit. Covers all three scroll
   children — .card-number / .crypto-address scroll too since the shadowing
   `overflow: hidden` was removed from the .glass-method-card rules below. */
.url-copy-glass > .url-text::-webkit-scrollbar,
.url-copy-glass > .card-number::-webkit-scrollbar,
.url-copy-glass > .crypto-address::-webkit-scrollbar{ height: 4px; }
.url-copy-glass > .url-text::-webkit-scrollbar-thumb,
.url-copy-glass > .card-number::-webkit-scrollbar-thumb,
.url-copy-glass > .crypto-address::-webkit-scrollbar-thumb{
  background: rgba(128,128,128,0.45); border-radius: 2px;
}
.url-copy-glass > button,
.url-copy-glass > .btn-glass{
  flex-shrink: 0;
}
.url-copy-glass .url-text{
  font-family: ui-monospace, monospace;
  font-size: var(--fs-1);
  direction: ltr; color: var(--text);
}
html[data-theme="light"] .url-copy-glass{
  border-color: rgba(0,0,0,0.08);
}

/* ───────────────────────── 7b. Usage progress bar ────────────────────── */
/* H4.3: per-row usage bar used in admin/my_subscriptions.php for the
   "مصرف" column. Width of the outer track is set inline by the caller
   (e.g. style="width:70px"); the inner <span> width is the percentage.
   Inline-axis is RTL-aware: anchoring the fill via `inset-inline-start`
   keeps it growing from the row's leading edge in both LTR and RTL. */
.usage-bar{
  position: relative;
  display: inline-block;
  height: 6px;
  max-width: 100%;
  min-width: 40px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,0.08);
  overflow: hidden;
  vertical-align: middle;
}
.usage-bar > span{
  position: absolute;
  top: 0; bottom: 0;
  inset-inline-start: 0;
  display: block;
  /* UI-0112: fill width comes from a CSS custom property set in the markup
     (style="--usage-pct: NN%"), not an inline width declaration — keeps the
     bar working under a strict style-src CSP that forbids `width:%`. */
  width: var(--usage-pct, 0%);
  max-width: 100%;
  border-radius: var(--r-full);
  background: linear-gradient(
    90deg,
    rgba(var(--accent-rgb), 0.85),
    rgba(var(--accent-rgb), 0.55));
  transition: width .25s var(--ease, ease);
}
html[data-theme="light"] .usage-bar{
  background: rgba(0,0,0,0.08);
}

/* ───────────────────────── 8. QR card ────────────────────────────────── */
.qr-glass{
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sp-6);
  border-radius: var(--r-2);
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.10);
  box-shadow: 0 0 40px rgba(255,255,255,0.04);
}
/* QR image: must NOT be rounded — rounded corners clip the finder patterns
   and quiet zone, breaking scanner detection. White background and padding
   double as the QR quiet-zone (≥4 modules), so keep them. Size bumped from
   200 → 256 so modules land closer to whole-pixel boundaries at common DPRs
   (image is rendered as ~294–342px SVG natively). */
.qr-glass img{
  width: 288px; height: 288px;
  background: #fff; padding: 0;
  border-radius: 0;
  box-sizing: content-box;
  /* Native SVG (scale=10) is ~430–530px → display always downsamples.
     Combined with shape-rendering="auto" on the SVG, downsample produces
     uniform anti-aliased module edges. The hints below have no negative
     effect on SVG-in-img and stay for safety per scan-quality checklist. */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.qr-glass .qr-caption{ margin-top: 12px; font-size: 0.85rem; opacity: 0.7; }

/* QR zoom modal — desktop ≈ one-third of viewport, mobile full-screen.
   Same scanner-safety rules apply: image stays square-cornered and sits on
   a white tile so the quiet zone is preserved. */
.modal-overlay.glass-modal.qr-zoom-modal .modal-box{
  width: clamp(360px, 36vw, 560px);
  max-width: calc(100vw - 24px);
  padding: var(--sp-5);
}
.qr-zoom-body{
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sp-2) 0 var(--sp-1);
}
.qr-zoom-img{
  width: 100%;
  max-width: 480px;
  height: auto;
  aspect-ratio: 1 / 1;
  /* UI-0371: in narrow landscape the 1:1 aspect made height follow width and
     overflow the short viewport. Clamp by viewport height (dvh + vh fallback)
     so the square shrinks to fit instead of spilling. */
  max-height: 80vh;
  max-height: 80dvh;
  background: #fff;
  padding: 0;
  border-radius: 0;
  box-sizing: content-box;
  /* See comment on .qr-glass img — native SVG is much larger than display,
     so downsampling + shape-rendering="auto" yields clean uniform modules. */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.qr-zoom-caption{ margin-top: 14px; font-size: 0.9rem; opacity: 0.75; }

/* Mobile = full-screen (opts out of the global bottom-sheet style applied
   to all .glass-modal at ≤768px further down in this file). */
@media (max-width: 768px){
  .modal-overlay.glass-modal.qr-zoom-modal{
    align-items: center;
    padding: 0;
  }
  .modal-overlay.glass-modal.qr-zoom-modal .modal-box{
    width: 100vw;
    max-width: 100vw;
    /* A-M: 100vh fallback for iOS Safari <15.4 / Chrome <108. */
    height: 100vh;
    height: 100dvh;
    max-height: 100vh;
    max-height: 100dvh;
    margin: 0;
    border-radius: 0;
    border-inline: 0;
  }
  .qr-zoom-body{
    flex: 1;
    justify-content: center;
    padding: var(--sp-4);
  }
  .qr-zoom-img{ max-width: min(90vmin, 540px); }
}

/* ───────────────────────── 9. Modal (glass) ──────────────────────────── */
/* fixes-merged-02 / Issue 1: the modal needs a single, reliable scroll
   container. Pre-fix the .modal-box inherited overflow-y:auto + max-height:
   90vh from app.css §20, but glass.css §9 redeclared the box without
   re-asserting those rules, and the mobile bottom-sheet at the bottom of
   this file capped to 92vh. On mobile, 1vh > visible vh while the URL bar
   is showing, so the bottom of the modal lived BELOW the visible area and
   no scroll affordance could reach it. The dvh unit handles the URL-bar
   case; flex + min-height:0 + overflow:hidden on the overlay restricts
   scrolling to the box; overscroll-behavior:contain prevents body-scroll
   leak when the touch reaches the box's edge. */
@keyframes modal-in {
  0%   { opacity: 0; transform: scale(0.96); }
  100% { opacity: 1; transform: scale(1); }
}
.modal-overlay.glass-modal{
  /* Closed by default — opened via .open / .active / [data-open="true"]
     which app.js's openModal() toggles. Pre-fix, this selector was
     display:flex (always-on) with no position:fixed/z-index, so every
     modal rendered permanently inline at the bottom of the document.
     Page-scoped workarounds existed on representatives.php and
     deposits.php; this rule supersedes them. */
  display: none;
  position: fixed;
  inset: 0;
  /* STACKING FIX: raised 1000→1100 so a dialog always outranks a menu.
     The portaled rep-menu panel is z-1050 (§ `body > .rep-menu-panel`), so at
     1000 every modal opened FROM that menu — the cost/reset-password modals
     and the glass confirm dialog — rendered underneath it, and on mobile was
     buried entirely by the sheet's 100vmax scrim shadow. app.js now also
     closes the menu on selection; this is the defence-in-depth half, so a
     lingering menu can never occlude a dialog. Stays below .nav-more (1200),
     which closes on selection of its own accord. */
  z-index: 1100;
  background: rgba(0,0,0,0.6);
  /* Overlay itself never scrolls; the box does. Without overflow:hidden
     here the page can scroll behind the open modal on iOS Safari. */
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* dvh = dynamic viewport height: collapses by the URL bar's height on
     mobile, unlike vh which references the largest viewport. A-M:
     100vh precedes 100dvh so iOS Safari <15.4 / Chrome <108 still
     pin the overlay to the viewport. */
  height: 100vh;
  height: 100dvh;
  max-height: 100vh;
  max-height: 100dvh;
}
.modal-overlay.glass-modal.open,
.modal-overlay.glass-modal.active,
.modal-overlay.glass-modal[data-open="true"]{
  display: flex;
}
.modal-overlay.glass-modal.open .modal-box,
.modal-overlay.glass-modal.active .modal-box,
.modal-overlay.glass-modal[data-open="true"] .modal-box{
  animation: modal-in 220ms ease-out;
}
.modal-overlay.glass-modal .modal-box{
  background: var(--popover);           /* opaque elevated surface (shadcn) */
  border: 1px solid var(--border);
  border-radius: var(--r-3);            /* 16px — dialog radius */
  box-shadow: 0 24px 64px rgba(0,0,0,0.55);  /* shadows are for dialogs */
  /* fixes-merged-02 / Issue 1: scroll container. Flex column + min-height:0
     lets long-content children shrink so overflow-y:auto on the box itself
     actually scrolls. dvh handles the mobile URL-bar gap. */
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* UI-0044: canonical modal-box padding. Page modals override this with
     ID-scoped rules (higher specificity), so existing modals are unchanged;
     this gives any future generic .modal-box a sane default AND a real basis
     for the .modal-actions full-bleed `margin: 0 -24px` below (which used to
     cancel a padding that did not exist). */
  padding: 22px var(--sp-6);
  /* A-M: 100vh fallback before 100dvh for iOS Safari <15.4. */
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.modal-overlay.glass-modal .modal-header{
  /* Header never compresses when the body grows. */
  flex-shrink: 0;
  /* Canonical header layout: title on one side, the ✕ close control on the
     other. Page modals that ship an id-scoped .modal-header (deposits.php,
     subscription_detail.php) or an inline style (payment_methods.php) win on
     specificity and stay byte-for-byte unchanged; this gives the modals that
     DON'T (wallet top-up, representatives, my_subscription_detail) the same
     row instead of a stacked, default-flow header. */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: 0;
}
.modal-overlay.glass-modal .modal-header h3{ margin: 0; }

/* Canonical glass close control — the icon-only "✕" button in every glass
   modal header. Without this the bare <button> fell back to raw UA chrome
   (grey box, default font) and clashed with the glass theme; that was the
   wallet top-up complaint. The same declarations were duplicated id-scoped
   in deposits.php / subscription_detail.php — hoisting them to the design
   system makes the wallet top-up, representatives and my_subscription_detail
   modals match too. Scoped :not(.btn-glass) so a "لغو/Cancel" button that
   only borrows .modal-close for its close BEHAVIOUR keeps its .btn-glass
   styling. Purely visual — the JS hook (app.js binds '.modal-close') is
   untouched, so every close button still works. */
.modal-close:not(.btn-glass){
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--r-1);
  color: var(--text);
  font-family: inherit;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s ease;
}
.modal-close:not(.btn-glass):hover{ background: rgba(255,255,255,0.08); }
html[data-theme="light"] .modal-close:not(.btn-glass):hover{ background: rgba(0,0,0,0.06); }
html[data-theme="light"] .modal-overlay.glass-modal .modal-box{
  /* bg + border now come from --popover / --border; only the shadow is lighter */
  box-shadow: 0 24px 64px rgba(0,0,0,0.18);
}

/* ───────────────────────── 10. Layout utilities ──────────────────────── */
/* A-E: bare .page-head rules deleted — every admin page uses
   .glass-page-head (the canonical declaration is just below at §10b),
   and the duplicate here + app.css:365 combined kept producing
   inconsistent alignment depending on cascade order. */

.empty-glass{ text-align: center; padding: 56px var(--sp-6); }
.empty-glass .empty-icon { font-size: var(--fs-hero); opacity: 0.6; margin-bottom: 12px; }
.empty-glass h3 { margin: 0 0 6px; font-weight: 600; }
.empty-glass p  { margin: 0; opacity: 0.65; font-size: 0.88rem; }

/* UI-0511: single-column mobile track MUST be minmax(0,1fr), not the implicit
   `auto`. With an `auto` track the column's min size = its min-content size,
   and the non-wrapping subscription URL in .url-copy-glass (white-space:nowrap)
   has a min-content as wide as the full URL (~800px) — blowing the cards far
   past the viewport on phones. The `html,body{overflow-x:hidden}` guard then
   CLIPS the spill instead of scrolling it, so on iPhone 12 Pro Max the page
   looked "zoomed-in / cut off". minmax(0,1fr) lets the track shrink to the
   viewport so the URL scrolls inside its own box (it already has min-width:0
   + overflow-x:auto). Note the containment comes from min-width:0 + this
   track, NOT from text-overflow — that property is paint-only and never
   affects layout, so §7 switching ellipsis→clip cannot regress this.
   The ≥900px two-column rule overrides this and is unaffected. */
.detail-grid{ display: grid; gap: var(--sp-4-5); grid-template-columns: minmax(0, 1fr); }
.detail-grid > * { min-width: 0; }
@media (min-width: 900px) {
  .detail-grid { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); align-items: start; }
}

.info-kv{ display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3-5) 22px; font-size: 0.9rem; }
.info-kv .k { opacity: 0.6; font-size: 0.78rem; margin-bottom: 2px; }
.info-kv .v { font-weight: 600; }
@media (max-width: 480px) { .info-kv { grid-template-columns: 1fr; } }

.action-stack{ display: flex; flex-direction: column; gap: 10px; }

.glass-divider{
  height: 1px;
  background: linear-gradient(to left, transparent, rgba(255,255,255,0.10), transparent);
  margin: 16px 0;
}
html[data-theme="light"] .glass-divider{
  background: linear-gradient(to left, transparent, rgba(0,0,0,0.08), transparent);
}

.refund-preview{
  background: rgba(var(--success-rgb), 0.08);
  border: 1px solid rgba(var(--success-rgb), 0.30);
  border-radius: var(--r-2);
  padding: var(--sp-3-5) var(--sp-4);
  margin-bottom: 12px;
  font-size: 0.9rem; line-height: 1.7;
}
.refund-preview .small { display: block; margin-top: 6px; font-size: 0.78rem; opacity: 0.65; }

.card-title{
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin: 0 0 14px; font-size: var(--fs-3); font-weight: 700;
}
.card-title .sub-id{ font-family: ui-monospace, monospace; opacity: 0.6; font-weight: 500; }

.wallet-balance{ font-size: 1.6rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.wallet-balance.low{ color: rgb(var(--warn-rgb)); }
.wallet-hint{ font-size: 0.8rem; opacity: 0.65; margin-top: 4px; }

.bcrumb{ display: flex; align-items: center; gap: var(--sp-2); font-size: 0.85rem; opacity: 0.75; margin-bottom: 12px; flex-wrap: wrap; }
.bcrumb a{ color: inherit; text-decoration: none; }
.bcrumb a:hover{ text-decoration: underline; }
.bcrumb .sep{ opacity: 0.4; }

/* ═══════════════════════════════════════════════════════════════════════
   DASHBOARD EXTENSIONS — added in Phase 3 of the dashboard refactor session
   ═══════════════════════════════════════════════════════════════════════ */

/* ───────────────────────── 11. Glass stat card ───────────────────────── */
.glass-stat-card{
  position: relative;
  display: flex; flex-direction: column; gap: 6px;
  padding: var(--sp-5) 22px;
  background: var(--glass-card-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-2);
  box-shadow: var(--shadow);
  overflow: hidden;
  isolation: isolate;
}

/* AUTO-DECISION: accent strip uses inset-inline-start (NOT inset-inline-end).
   In RTL the inline-start direction is the right edge — i.e. the leading
   edge of the reading direction. The brief's wording said "leading edge
   (right side in RTL)" but specified inset-inline-end, which would put
   the strip on the trailing edge. We honor the stated visual intent. */
.glass-stat-card::before{
  content:"";
  position: absolute;
  inset-block: 12%;
  inset-inline-start: 0;
  width: 3px;
  border-radius: var(--r-full);
  background: var(--stat-accent, transparent);
  /* Phase 5 stage 2: dropped the colored box-shadow glow — no glow on cards.
     The 3px bar stays as a flat status/category accent (color for data). */
}

.glass-stat-card .stat-icon { font-size: 1.6rem; opacity: 0.85; line-height: 1; }
.glass-stat-card .stat-val  { font-size: 2rem; font-weight: 800; line-height: 1.1; }
.glass-stat-card .stat-lbl  { font-size: 0.85rem; opacity: 0.7; }
.glass-stat-card .stat-sub  { font-size: 0.78rem; opacity: 0.55; margin-top: 4px; }
.glass-stat-card a          { color: inherit; text-decoration: none; display: contents; }

/* AUTO-DECISION: 6 variants cover the dashboard's 6 source colors plus a
   neutral default. info ≈ blue/cyan, purple kept distinct from accent. */
.glass-stat-card.accent  { --stat-accent: rgb(var(--accent-rgb));  --stat-accent-soft: rgba(var(--accent-rgb), 0.12); }
.glass-stat-card.success { --stat-accent: rgb(var(--success-rgb)); --stat-accent-soft: rgba(var(--success-rgb), 0.12); }
.glass-stat-card.warn    { --stat-accent: rgb(var(--warn-rgb));    --stat-accent-soft: rgba(var(--warn-rgb), 0.12); }
.glass-stat-card.danger  { --stat-accent: rgb(var(--danger-rgb));  --stat-accent-soft: rgba(var(--danger-rgb), 0.12); }
/* AUTO-DECISION: .info + .purple folded into the single accent family —
   the old sky-blue/violet were part of the "inconsistent colors" issue. */
.glass-stat-card.info    { --stat-accent: rgb(var(--accent-rgb));   --stat-accent-soft: rgba(var(--accent-rgb), 0.12); }
.glass-stat-card.purple  { --stat-accent: rgb(142, 160, 255);       --stat-accent-soft: rgba(142, 160, 255, 0.12); }

/* AUTO-DECISION: dashboard's .stat-grid container (defined in app.css)
   already handles the responsive grid. We override only its mobile gap
   for visual harmony with .actions-toolbar. */
.stat-grid { gap: var(--sp-3-5); }
/* UI-0023: status-filter tiles wrap an <a> around .glass-stat-card; surface a
   visible keyboard focus ring on the card when its anchor is focused. */
.stat-grid a:focus-visible { outline: none; }
.stat-grid a:focus-visible .glass-stat-card {
  outline: 2px solid rgba(var(--accent-rgb), 0.65);
  outline-offset: 2px;
}
/* UI-0116: mark the active status-filter tile (the link carries
   aria-current="page") so the current filter is visually distinguished. */
.stat-grid a[aria-current="page"] .glass-stat-card {
  outline: 2px solid rgba(var(--accent-rgb), 0.55);
  outline-offset: 2px;
}
@media (max-width: 480px) {
  .glass-stat-card { padding: var(--sp-4-5); }
  .glass-stat-card .stat-val { font-size: 1.7rem; }
}

/* Commit 6 (#8/#28) — SUPERSEDED by the breakpoint unification below.
   This used to hide the five `.lb-extra` leaderboard columns in the 769–1023px
   band, because that band rendered a full desktop table at tablet width. With
   the table→card collapse now extended to ≤1023px (see §"card collapse"), the
   leaderboard is a stacked card there instead, and every field is a labelled
   row — so hiding .lb-extra would DELETE data from the card rather than merely
   compress a column. The rule is therefore removed; the class stays in
   dashboard.php purely as a semantic marker of which columns are secondary. */

/* ───────────────────────── 12. Leaderboard table ─────────────────────── */
/* AUTO-DECISION: scoped to .glass-card descendants so it doesn't bleed
   into the legacy tables in other admin pages still using app.css. */
.glass-card .leaderboard{
  width: 100%;
  border-collapse: collapse;
  background: transparent;
}
.glass-card .leaderboard th{
  text-align: start; /* RTL leading edge (logical: right in RTL, left in LTR) */
  padding: 10px var(--sp-3);
  font-size: var(--fs-1); font-weight: 600;
  color: var(--muted-foreground); opacity: 1;   /* muted meta, token-driven */
  background: var(--muted);                       /* distinct table-header step */
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.glass-card .leaderboard td{
  padding: var(--sp-3);
  border-bottom: 1px solid rgba(255,255,255,0.04);
  font-size: 0.9rem;
}
.glass-card .leaderboard tr:last-child td { border-bottom: none; }
.glass-card .leaderboard tr { transition: background 0.2s ease; }
.glass-card .leaderboard tbody tr:hover { background: var(--hover); }

html[data-theme="light"] .glass-card .leaderboard th { border-bottom-color: rgba(0,0,0,0.08); }
html[data-theme="light"] .glass-card .leaderboard td { border-bottom-color: rgba(0,0,0,0.05); }
/* leaderboard row hover uses the theme-independent --hover token (above) */

.glass-card .leaderboard .lb-rank{
  display: inline-flex;
  align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: rgba(255,255,255,0.06);
  font-weight: 700; font-size: 0.85rem;
}
/* AUTO-DECISION: gold/silver/bronze hardcoded RGB rather than tokens —
   medal colors are universally recognized and need to stay consistent
   across themes; tying them to brand tokens would weaken the metaphor.
   gold = warn token, silver = neutral gray, bronze = amber-orange. */
.glass-card .leaderboard tbody tr:nth-child(1) .lb-rank { background: rgba(var(--warn-rgb), 0.25);     color: rgb(252, 211, 77); }
.glass-card .leaderboard tbody tr:nth-child(2) .lb-rank { background: rgba(156, 163, 175, 0.25);      color: rgb(229, 231, 235); }
.glass-card .leaderboard tbody tr:nth-child(3) .lb-rank { background: rgba(217, 119, 6, 0.25);        color: rgb(251, 146, 60); }
/* The three medal inks above are dark-theme values, and the comment's premise
   ("need to stay consistent across themes") does not survive contact with the
   light theme: on the white card they measured 1.10, 1.01 and 1.73 — the
   silver digit was literally invisible — while rank 4 onward, which uses the
   normal text token, sat at 15.49. The top three rows, the ones that matter
   most, were the only unreadable ones.
   Darkened per theme, hues preserved so the gold/silver/bronze metaphor still
   reads: 5.23, 6.16 and 5.58, all past AA. Note these are measured against the
   PILL's own tinted background (rgb(246,221,193) / rgb(230,232,235)), not the
   white card — against white they look ~1.3 stops better than they really are.
   The pill backgrounds are unchanged; only the digit is re-inked. Dark keeps
   the original values. */
html[data-theme="light"] .glass-card .leaderboard tbody tr:nth-child(1) .lb-rank { color: #854d0e; }
html[data-theme="light"] .glass-card .leaderboard tbody tr:nth-child(2) .lb-rank { color: #4b5563; }
html[data-theme="light"] .glass-card .leaderboard tbody tr:nth-child(3) .lb-rank { color: #9a3412; }

.glass-card .leaderboard .lb-num{ font-variant-numeric: tabular-nums; }

/* ───────────────────────── 13. Quick-actions toolbar ─────────────────── */
/* Commit 5 (#26): this was a full-width card holding 3 inline-start buttons, so
   most of its surface was empty — on a 1200px shell the emptiness read as a
   layout bug rather than a toolbar. Padding is trimmed to match the content, and
   from 769px up the surface shrinks to fit its buttons: a deliberate control
   cluster instead of a mostly-blank card. Mobile keeps full width, where the
   buttons wrap and actually use it. */
.actions-toolbar{
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: var(--sp-2) var(--sp-3);
}
@media (min-width: 769px){
  .actions-toolbar{ width: fit-content; max-width: 100%; }
}
.actions-toolbar .btn-glass{
  padding: 10px var(--sp-4);
  font-size: 0.9rem;
  min-height: 40px; /* tighter than the standard 44px since these are secondary actions */
}

/* ───────────────────────── 14. Glass page header ─────────────────────── */
.glass-page-head{
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  /* Phase 5 stage 3a: un-carded — a compact page header (title/count + primary
     action) separated by a bottom border, not wrapped in a big card. ~72-80px. */
  padding: var(--sp-4) 0;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  margin-bottom: var(--sp-5);
}
.glass-page-head .head-title { font-size: 1.4rem; font-weight: 700; margin: 0; }
.glass-page-head .head-sub   { font-size: 0.9rem; opacity: 0.7; margin-top: 4px; }
.glass-page-head .head-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* Compact variant — used on narrow-wrap pages where the standard height
   would dominate above-the-fold content. */
.glass-page-head.compact { padding: var(--sp-3) 0; margin-bottom: var(--sp-4); }
.glass-page-head.compact .head-title { font-size: 1.1rem; }

/* ═══════════════════════════════════════════════════════════════════════
   PURCHASE FLOW — components specific to admin/buy.php (and any future
   page that mirrors the same numbered-step + stepper + summary pattern).
   ═══════════════════════════════════════════════════════════════════════ */

/* ───────────────────────── 15. Glass info bar ────────────────────────── */
.glass-info-bar{
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4-5);
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--r-2);
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.glass-info-bar .info-cell{ display: flex; align-items: center; gap: var(--sp-2); font-size: 0.88rem; }
.glass-info-bar .info-cell strong{ font-weight: 700; color: var(--text); }
.glass-info-bar .info-cell.faint{ opacity: 0.7; }
html[data-theme="light"] .glass-info-bar{
  border-color: rgba(0,0,0,0.05);
}

/* ───────────────────────── 16. Glass alert ───────────────────────────── */
/* Inline page-level message. Composable with existing utility classes
   like .mt-2/.mb-2. Element can stay hidden via inline display:none or
   a co-class (e.g. .buy-vol-warn from app.css) and be toggled by JS. */
.glass-alert{
  position: relative;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-2);
  border: 1px solid var(--alert-border, rgba(255,255,255,0.10));
  background: var(--alert-bg, var(--glass-card-bg));
  color: var(--text);
  margin-bottom: 12px;
  font-size: 0.9rem;
}
.glass-alert.error,
.glass-alert.danger{
  --alert-bg: rgba(var(--danger-rgb), 0.10);
  --alert-border: rgba(var(--danger-rgb), 0.35);
  color: rgb(248,113,113);
}
.glass-alert.warn{
  --alert-bg: rgba(var(--warn-rgb), 0.10);
  --alert-border: rgba(var(--warn-rgb), 0.35);
  color: rgb(252,211,77);
}
.glass-alert.info{
  --alert-bg: rgba(96,165,250, 0.10);
  --alert-border: rgba(96,165,250, 0.35);
  color: rgb(147,197,253);
}
.glass-alert.success{
  --alert-bg: rgba(var(--success-rgb), 0.10);
  --alert-border: rgba(var(--success-rgb), 0.35);
  color: rgb(74,222,128);
}
html[data-theme="light"] .glass-alert.error,
html[data-theme="light"] .glass-alert.danger  { color: rgb(185, 28, 28); }
html[data-theme="light"] .glass-alert.warn    { color: rgb(146, 64, 14); }
html[data-theme="light"] .glass-alert.info    { color: rgb(29, 78, 216); }
html[data-theme="light"] .glass-alert.success { color: rgb(21, 128, 61); }

/* ───────────────────────── 17. Glass form section ────────────────────── */
.glass-form-section{
  position: relative;
  padding: var(--sp-5) 22px;
  background: rgba(20,22,32,0.55);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-3);
  margin-bottom: 14px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.05);
}
.glass-form-section .section-title{
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  font-size: 1rem;
  font-weight: 700;
}
/* AUTO-DECISION: numbered cue is a plain inline element rather than a CSS
   counter — counters drift if a section is hidden/reordered, and we want
   the number to be a stable, screen-reader-readable token in the DOM. */
.glass-form-section .section-num{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: rgba(var(--accent-rgb), 0.15);
  border: 1px solid rgba(var(--accent-rgb), 0.35);
  color: rgb(160,178,255);
  font-weight: 700; font-size: 0.85rem;
}
.glass-form-section .section-title-hint{
  margin-inline-start: auto;
  font-size: 0.78rem;
  font-weight: 400;
  opacity: 0.6;
}
html[data-theme="light"] .glass-form-section{
  background: rgba(255,255,255,0.70);
  border-color: rgba(0,0,0,0.06);
  box-shadow: 0 8px 32px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.5);
}
html[data-theme="light"] .glass-form-section .section-num{ color: rgb(67,56,202); }

/* ── Commit 3 (#41): inside a MODAL, steps share the dialog's single surface ──
   The charge modal stacked a bordered+shadowed card per step on top of the
   modal surface (and another for the nested crypto block) — three layers deep,
   off-pattern for the flat shadcn dialog. Inside .glass-modal a step is just a
   heading + spacing, separated from the next by a hairline divider. Scoped to
   modals ONLY, so the standalone buy/wallet page forms keep their cards.
   Light-theme selector is spelled out because html[data-theme="light"]
   .glass-form-section would otherwise out-rank a modal-scoped rule. */
.modal-overlay.glass-modal .glass-form-section,
html[data-theme="light"] .modal-overlay.glass-modal .glass-form-section{
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: var(--sp-4) 0;
  margin-bottom: 0;
}
.modal-overlay.glass-modal .glass-form-section + .glass-form-section{
  border-top: 1px solid var(--border, rgba(255,255,255,0.10));
}
.modal-overlay.glass-modal .glass-form-section:first-of-type{ padding-top: 0; }

/* ───────────────────────── 18. Glass stepper ─────────────────────────── */
.glass-stepper{
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: stretch;
  margin-bottom: 10px;
  width: 100%;
  max-width: 100%;
}
.glass-stepper .stepper-input{
  /* min-width:0 + width:100% lets the grid 1fr track shrink below the
     <input>'s intrinsic ~20ch min-content width — otherwise the stepper
     overflows its container on narrow viewports. */
  min-width: 0;
  width: 100%;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--r-2);
  padding: var(--sp-3-5) var(--sp-4-5);
  font-size: 1.4rem;
  font-weight: 700;
  text-align: center;
  color: var(--text);
  direction: ltr;
  font-family: inherit;
}
.glass-stepper .stepper-input:focus{
  outline: none;
  border-color: rgba(var(--accent-rgb), 0.5);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15);
}
/* fixes-merged-02 / Section A: stepper buttons render bare "+" / "−" glyphs
   (no digit). The bare minus specifically reads anemic, so we bump font-size
   noticeably and keep weight at 600 — too heavy a weight makes the glyphs
   look stamped rather than confident. line-height:1 + display:inline-flex
   on .btn-glass already centres the glyph vertically. */
.glass-stepper .btn-glass{
  min-width: 56px;
  font-size: 1.7rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  white-space: nowrap;
}
html[data-theme="light"] .glass-stepper .stepper-input{
  border-color: rgba(0,0,0,0.08);
}
@media (max-width: 480px){
  .glass-stepper{ gap: 6px; }
  .glass-stepper .stepper-input{ padding: var(--sp-3) 10px; font-size: var(--fs-4); }
  /* fixes-merged-02 / Section A: keep the glyph confident at narrow
     widths — the bare "−" reads anemic if it shrinks too aggressively.
     Merged from the original §18 mobile rule and my own override; the
     original's font-size:1rem was the bug (made the glyph 16px). */
  /* UI-0086: also pin a 44px min-height so the glyph buttons keep a full
     WCAG touch target on very narrow phones (≤360px), not just the width. */
  .glass-stepper .btn-glass{ min-width: 44px; min-height: 44px; padding: 10px var(--sp-3); font-size: 1.55rem; }
}

/* ─────────────── 18b. Quick-step chips (renew / add-volume modal) ───────
   subscription_detail.php + my_subscription_detail.php render the renew form
   as .stepper-wrap > (.stepper-input-row + .stepper-btns > .stepper-btn).
   These are DISTINCT from the §18 .glass-stepper component above (an inline
   3-column +/− stepper). Without these rules the quick-add chips (-1 -0.5
   +0.5 +1 +5 / -7 +7 +30) fall back to raw browser-default buttons — match
   the glass chip look already used by §19 .dur-btn. JS (initSteppers in
   app.js) keys off [data-stepper] / [data-step], NOT these class names, so
   this is purely cosmetic and never alters the renew behaviour. */
.stepper-wrap{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.stepper-input-row{ width: 100%; }
.stepper-btns{
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.stepper-btn{
  flex: 1 1 auto;
  min-width: 52px;
  padding: var(--sp-2) 10px;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  color: var(--text);
  font-weight: 600; font-size: 0.85rem;
  font-family: inherit;
  direction: ltr;            /* keep "+5" / "-0.5" glyph order stable in RTL */
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.stepper-btn:hover{
  background: rgba(255,255,255,0.07);
  border-color: rgba(255,255,255,0.14);
  transform: translateY(-1px);
}
.stepper-btn:active{ transform: translateY(0); }
.stepper-btn:disabled{ opacity: 0.45; cursor: not-allowed; transform: none; }
html[data-theme="light"] .stepper-btn{
  border-color: rgba(0,0,0,0.06);
}
html[data-theme="light"] .stepper-btn:hover{
  background: rgba(0,0,0,0.05);
  border-color: rgba(0,0,0,0.12);
}
@media (max-width: 480px){
  /* keep a full WCAG touch target on narrow phones */
  .stepper-btn{ min-width: 44px; min-height: 40px; }
}

/* ───────────────────────── 19. Glass duration toggle ─────────────────── */
.glass-duration-toggle{ display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.glass-duration-toggle .dur-btn{
  flex: 1 1 calc(33.333% - 6px);
  min-width: 80px;
  padding: 10px var(--sp-3-5);
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-2);
  color: var(--text);
  font-weight: 600; font-size: 0.9rem;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.glass-duration-toggle .dur-btn:hover{
  background: rgba(255,255,255,0.07);
  border-color: rgba(255,255,255,0.14);
  transform: translateY(-1px);
}
.glass-duration-toggle .dur-btn.active{
  background: rgba(var(--accent-rgb), 0.18);
  border-color: rgba(var(--accent-rgb), 0.5);
  color: rgb(199,210,254);
  box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.2), 0 0 20px rgba(var(--accent-rgb), 0.25);
}
html[data-theme="light"] .glass-duration-toggle .dur-btn{
  border-color: rgba(0,0,0,0.06);
}
html[data-theme="light"] .glass-duration-toggle .dur-btn:hover{
  background: rgba(0,0,0,0.05);
  border-color: rgba(0,0,0,0.12);
}
html[data-theme="light"] .glass-duration-toggle .dur-btn.active{
  background: rgba(var(--accent-rgb), 0.12);
  color: rgb(67,56,202);
  /* border-color MUST be restated: `html[data-theme="light"] … .dur-btn`
     above is (0,3,1) and outranks `.dur-btn.active` (0,3,0), so without
     this the selected duration keeps the plain 0.06 black edge and loses
     its accent outline in light theme. */
  border-color: rgba(var(--accent-rgb), 0.45);
}

/* ───────────────────────── 20. Glass price line ──────────────────────── */
.glass-price-line{
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  margin-top: 10px;
  border-radius: var(--r-2);
  background: rgba(var(--accent-rgb), 0.06);
  border: 1px solid rgba(var(--accent-rgb), 0.18);
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}
.glass-price-line .price-label{ opacity: 0.7; font-weight: 500; font-size: 0.88rem; }
.glass-price-line .price-value{ font-weight: 800; color: rgb(199,210,254); }
/* AUTO-DECISION: .flash class name preserved as the IIFE's toggle target;
   only the visual effect changes (old: opacity dim; new: accent glow). */
.glass-price-line.flash{
  background: rgba(var(--accent-rgb), 0.16);
  border-color: rgba(var(--accent-rgb), 0.45);
  box-shadow: 0 0 24px rgba(var(--accent-rgb), 0.30);
}
html[data-theme="light"] .glass-price-line .price-value{ color: rgb(67,56,202); }

/* ───────────────────────── 21. Glass summary dl ──────────────────────── */
.glass-summary-dl{
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 10px var(--sp-4-5);
  padding: var(--sp-3-5) var(--sp-4);
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--r-2);
  margin-bottom: 14px;
}
.glass-summary-dl dt{ font-size: 0.85rem; opacity: 0.7; align-self: center; }
.glass-summary-dl dd{ margin: 0; text-align: end; font-weight: 600; font-size: var(--fs-2); }
.glass-summary-dl dd.total{ font-size: 1.15rem; font-weight: 800; color: rgb(199,210,254); }
html[data-theme="light"] .glass-summary-dl{
  background: rgba(0,0,0,0.02);
  border-color: rgba(0,0,0,0.05);
}
html[data-theme="light"] .glass-summary-dl dd.total{ color: rgb(67,56,202); }

/* ───────────────────────── 22. Glass buy CTA ─────────────────────────── */
.btn-glass.cta-buy{
  width: 100%;
  padding: var(--sp-4) var(--sp-6);
  font-size: var(--fs-3);
  font-weight: 800;
  /* Phase 5 stage 2: SOLID primary (was a decorative gradient) — this is the
     ONE main action of the buy page. */
  background: var(--primary);
  color: var(--primary-foreground);
  border-color: transparent;
  letter-spacing: 0.3px;
  min-height: 52px;
}
.btn-glass.cta-buy:hover{ filter: brightness(1.06); }
.btn-glass.cta-buy:disabled,
.btn-glass.cta-buy[disabled]{
  /* UI-0065: 0.45 dropped the label below AA, especially in light theme.
     0.6 keeps it clearly "disabled" (no glow, not-allowed cursor, no lift)
     while leaving the text legible. */
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   WALLET / DEPOSIT FLOW — financial-UI components for admin/wallet.php.
   ═══════════════════════════════════════════════════════════════════════ */

/* ───────────────────────── 23. Glass balance card ────────────────────── */
.glass-balance-card{
  position: relative;
  text-align: center;
  padding: 36px var(--sp-6) 28px;
  background: rgba(20,22,32,0.80);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--r-2);
  margin-bottom: 18px;
  overflow: hidden;
  isolation: isolate;
}
.glass-balance-card .balance-label{ font-size: 0.88rem; opacity: 0.7; margin-bottom: 6px; }
/* AUTO-DECISION: direction:ltr + unicode-bidi:isolate around the balance
   number so the ASCII digits render in their natural reading order inside
   the RTL flow — without this, large numbers can flip past surrounding
   punctuation. The label/currency siblings are RTL Persian and stay so. */
.glass-balance-card .balance-value{
  font-size: var(--fs-hero); font-weight: 800;
  letter-spacing: -0.5px;
  line-height: 1.1;
  color: rgb(187,247,208);
  /* UI-0457: a 24px/0.35 glow in the digits' own color haloed the numerals
     and softened the edges. A tighter, fainter glow keeps the accent without
     blurring readability. */
  text-shadow: 0 0 10px rgba(var(--success-rgb), 0.18);
  direction: ltr;
  unicode-bidi: isolate;
  margin-bottom: 4px;
}
.glass-balance-card .balance-currency{
  font-size: 1rem; font-weight: 600; opacity: 0.85;
  margin-inline-start: 6px;
}
.glass-balance-card .balance-reserved{
  margin-top: 14px;
  padding: var(--sp-2) var(--sp-3-5);
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-1);
  background: rgba(var(--warn-rgb), 0.10);
  border: 1px solid rgba(var(--warn-rgb), 0.30);
  border-radius: var(--r-full);
  color: rgb(252,211,77);
}
.glass-balance-card .balance-actions{
  display: flex; justify-content: center; gap: 10px;
  margin-top: 22px;
  flex-wrap: wrap;
}
html[data-theme="light"] .glass-balance-card{
  background: rgba(255,255,255,0.70);
  border-color: rgba(0,0,0,0.06);
}
html[data-theme="light"] .glass-balance-card .balance-value{
  color: rgb(22,101,52);
  text-shadow: 0 0 24px rgba(var(--success-rgb), 0.18);
}
html[data-theme="light"] .glass-balance-card .balance-reserved{
  background: rgba(var(--warn-rgb), 0.08);
  color: rgb(146,64,14);
}

/* H5.3: negative-balance variant + wholesale credit-limit indicator.
   Triggered by `.is-negative` on the balance card (wallet.php sets it
   when Wallet::get()['available'] is < 0). The credit-limit line is
   rendered only when the wallet row carries a non-NULL negative_limit. */
.glass-balance-card.is-negative .balance-value{
  color: rgb(252,165,165);
  /* UI-0457: tighter glow (see positive variant). */
  text-shadow: 0 0 10px rgba(var(--danger-rgb), 0.18);
}
html[data-theme="light"] .glass-balance-card.is-negative .balance-value{
  color: rgb(159,18,57);
  text-shadow: 0 0 24px rgba(var(--danger-rgb), 0.18);
}
.glass-balance-card .balance-credit-limit{
  margin-top: 10px;
  padding: 6px var(--sp-3);
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 0.8rem;
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--r-full);
  opacity: 0.9;
}
.glass-balance-card .balance-credit-limit.breached{
  background: rgba(var(--danger-rgb), 0.12);
  border-color: rgba(var(--danger-rgb), 0.35);
  color: rgb(254,202,202);
}
.glass-balance-card .balance-credit-limit .credit-used{
  margin-inline-start: 4px;
  font-weight: 700;
}
html[data-theme="light"] .glass-balance-card .balance-credit-limit{
  background: rgba(0,0,0,0.04);
  border-color: rgba(0,0,0,0.08);
}
html[data-theme="light"] .glass-balance-card .balance-credit-limit.breached{
  background: rgba(var(--danger-rgb), 0.08);
  color: rgb(159,18,57);
}

/* ───────────────────────── 24. Glass select ──────────────────────────── */
.glass-select{
  width: 100%;
  background-color: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--r-2);
  padding: var(--sp-3) var(--sp-3-5);
  color: var(--text);
  font-size: var(--fs-2);
  font-weight: 500;
  font-family: inherit;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-opacity='0.6'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: left 14px center; /* RTL: chevron sits on the trailing edge (left) */
  padding-inline-start: 38px;
  cursor: pointer;
}
.glass-select:focus{
  outline: none;
  border-color: rgba(var(--accent-rgb), 0.5);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15);
}
/* Commit 3 (#19): read-only "scope" field — a value the user cannot change but
   must see. Matches .glass-select's height/padding/border so it lines up beside
   real controls, but deliberately has NO chevron and no pointer cursor, because
   it is not a select. Used for sudo's fixed L1 level on representatives.php. */
.glass-scope-field{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: var(--sp-3) var(--sp-3-5);
  background-color: rgba(255,255,255,0.03);
  border: 1px dashed rgba(255,255,255,0.16);
  border-radius: var(--r-2);
  color: var(--text);
  font-size: var(--fs-2);
  font-weight: 500;
  cursor: default;
}
.glass-scope-field svg{ opacity: 0.65; flex-shrink: 0; }
html[data-theme="light"] .glass-scope-field{
  background-color: rgba(0,0,0,0.02);
  border-color: rgba(0,0,0,0.16);
}

/* Commit 3 (#18): pointer-device hover so a select reads as interactive BEFORE
   click. Deliberately subtler than :focus (border tint + faint wash, no ring)
   and suppressed while focused so the two states never compound. */
@media (pointer: fine){
  .glass-select:hover:not(:disabled):not(:focus){
    border-color: rgba(255,255,255,0.22);
    background-color: rgba(255,255,255,0.05);
  }
  html[data-theme="light"] .glass-select:hover:not(:disabled):not(:focus){
    border-color: rgba(0,0,0,0.22);
    background-color: rgba(0,0,0,0.03);
  }
}
html[data-theme="light"] .glass-select{
  border-color: rgba(0,0,0,0.08);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='2' stroke-opacity='0.5'><polyline points='6 9 12 15 18 9'/></svg>");
}

/* ───────────────────────── 25. Glass method card ─────────────────────── */
.glass-method-card{
  position: relative;
  padding: var(--sp-4) var(--sp-4-5);
  margin-top: 12px;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-2);
}
.glass-method-card .method-title{
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: 0.88rem; font-weight: 700;
  margin-bottom: 12px;
  color: rgb(199,210,254);
}
.glass-method-card .method-meta{
  margin-top: 10px;
  font-size: var(--fs-1); opacity: 0.75;
  display: flex; align-items: center; gap: 6px;
  flex-wrap: wrap;
}
/* AUTO-DECISION: card-method and crypto-method use different border
   accents — bank-card (indigo, our brand accent) vs crypto (amber).
   Visual semantic helps users register WHICH method they picked in
   the moments after clicking, before they've read the label. */
.glass-method-card.card-method{ border-color: rgba(var(--accent-rgb), 0.20); }
.glass-method-card.card-method .method-title{ color: rgb(160,178,255); }
.glass-method-card.crypto-method{ border-color: rgba(245,158,11, 0.25); }
.glass-method-card.crypto-method .method-title{ color: rgb(252,211,77); }

.glass-method-card .card-number{
  font-family: ui-monospace, monospace;
  /* User feedback rev 4: needs to fit cleanly inside a 320-px viewport
     even with the .url-copy-glass padding (10/14) and method-card
     inner padding. Smaller font + tighter letter-spacing keep the
     19-char string ("6104 3378 9992 1010") inside ~230 px so it
     never breaks or overflows on the narrowest mobile target. */
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 1.5px;
  /* Safety — if any narrower context still squeezes us, allow shrink
     before overflow. */
  min-width: 0;
  max-width: 100%;
  /* Overflow/wrapping deliberately NOT set here. This rule and
     `.url-copy-glass > .card-number` have equal specificity (0,2,0), so the
     `overflow: hidden` this used to declare won on source order alone and
     silently shadowed the scroll box ~1100 lines earlier — the card number
     was hard-truncated with no scroll and no way to read its tail. Scroll
     behaviour has exactly one owner: the .url-copy-glass rule. Both call
     sites (admin/wallet.php) are inside .url-copy-glass, so it always
     matches; keep it that way rather than re-adding overflow here. */
  direction: ltr;
  unicode-bidi: isolate;
}
.glass-method-card .crypto-address{
  font-family: ui-monospace, monospace;
  font-size: 0.85rem;
  direction: ltr;
  unicode-bidi: isolate;
  /* Overflow/wrapping owned by `.url-copy-glass > .crypto-address` — see the
     note on .card-number above. Mattered most here: a truncated, unscrollable
     crypto deposit address that admin/wallet.php renders with no copy button
     could not be visually verified before sending funds. */
}
html[data-theme="light"] .glass-method-card{
  border-color: rgba(0,0,0,0.06);
}
html[data-theme="light"] .glass-method-card.card-method .method-title{ color: rgb(67,56,202); }
html[data-theme="light"] .glass-method-card.crypto-method .method-title{ color: rgb(146,64,14); }

/* ───────────────────────── 26. Glass amount input ────────────────────── */
.glass-amount-input{
  width: 100%;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--r-1);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-5);
  font-weight: 700;
  text-align: center;
  color: var(--text);
  font-family: inherit;
  direction: ltr;
  unicode-bidi: isolate;
  letter-spacing: 0.5px;
}
.glass-amount-input::placeholder{
  /* A-H: match input font-size so the first keystroke doesn't cause a
     visible vertical reflow in the wallet top-up modal. Hint contrast
     is carried by opacity + font-weight instead of size. */
  font-size: var(--fs-5);
  font-weight: 500;
  opacity: 0.40;
  letter-spacing: 0;
}
.glass-amount-input:focus{
  outline: none;
  border-color: rgba(var(--success-rgb), 0.5);
  box-shadow: 0 0 0 3px rgba(var(--success-rgb), 0.12),
              0 0 30px rgba(var(--success-rgb), 0.20);
}
html[data-theme="light"] .glass-amount-input{
  border-color: rgba(0,0,0,0.08);
}

/* ───────────────────────── 27. Glass input group ─────────────────────── */
.glass-input-group{ margin-bottom: 12px; }
/* `.field-caption`: see the note at §"filter toolbar". Here it covers the
   caption that sits ABOVE a control which is already implicitly labelled by a
   wrapping <label> (the file-upload dropzones). Two labels on one control is
   its own a11y violation, so the caption gives up its <label> tag and keeps
   only the styling. */
.glass-input-group label,
.glass-input-group .field-caption{
  display: block;
  font-size: 0.85rem; font-weight: 600;
  margin-bottom: 6px;
  opacity: 0.85;
}
.glass-input-group label .faint,
.glass-input-group .field-caption .faint{
  font-weight: 400; opacity: 0.6;
  margin-inline-start: 4px;
}
.glass-input-group input,
.glass-input-group textarea{
  width: 100%;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: 10px var(--sp-3);
  color: var(--text);
  font-size: 0.9rem;
  font-family: inherit;
}
.glass-input-group input:focus,
.glass-input-group textarea:focus{
  outline: none;
  border-color: rgba(var(--accent-rgb), 0.45);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.12);
}
html[data-theme="light"] .glass-input-group input,
html[data-theme="light"] .glass-input-group textarea{
  border-color: rgba(0,0,0,0.08);
}

/* Nested form section — visually subordinate to a primary .glass-form-section. */
.glass-form-section.nested{
  background: rgba(255,255,255,0.03);
  padding: var(--sp-3-5) var(--sp-4);
  margin-bottom: 10px;
  border-radius: var(--r-2);
  box-shadow: none;
}
html[data-theme="light"] .glass-form-section.nested{
  background: rgba(0,0,0,0.02);
}

/* ───────────────────────── 28. Glass file upload ─────────────────────── */
/* AUTO-DECISION: <label class="glass-file-upload"> wraps a position:absolute
   invisible <input type="file">. We do NOT use <label for=""> because the
   absolute-inside-label approach keeps the entire drop zone clickable on
   older mobile browsers where label-target click can mis-fire on tap. */
.glass-file-upload{
  position: relative;
  display: flex; align-items: center; gap: var(--sp-3-5);
  padding: var(--sp-4) var(--sp-4-5);
  background: var(--glass-card-bg);
  border: 1px dashed rgba(255,255,255,0.18);
  border-radius: var(--r-1);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.glass-file-upload:hover{
  background: rgba(255,255,255,0.06);
  border-color: rgba(var(--accent-rgb), 0.40);
}
.glass-file-upload input[type="file"]{
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  opacity: 0;
  cursor: pointer;
}
.glass-file-upload .file-icon{ font-size: 1.6rem; opacity: 0.7; }
.glass-file-upload .file-info{ flex: 1; }
.glass-file-upload .file-prompt{ font-size: 0.92rem; font-weight: 600; margin-bottom: 2px; }
.glass-file-upload .file-hint  { font-size: 0.78rem; opacity: 0.6; }
.glass-file-upload .file-name  { font-size: var(--fs-1); opacity: 0.85; word-break: break-all; margin-top: 2px; }
/* UI-0042: visible drag-over affordance — JS toggles .is-dragging in
   pg_bindDropZone (assets/app.js) but the design system lacked a rule. */
.glass-file-upload.is-dragging{
  border-style: solid;
  border-color: rgba(var(--accent-rgb), 0.60);
  background: rgba(var(--accent-rgb), 0.06);
}
.glass-file-upload.has-file{
  border-style: solid;
  border-color: rgba(var(--success-rgb), 0.40);
  background: rgba(var(--success-rgb), 0.08);
}
.glass-file-upload.has-file .file-icon{ color: rgb(74,222,128); }
html[data-theme="light"] .glass-file-upload{
  border-color: rgba(0,0,0,0.18);
}
html[data-theme="light"] .glass-file-upload:hover{ background: rgba(0,0,0,0.04); }
/* The rule above is (0,2,1) and outranks both .is-dragging and .has-file
   (0,2,0), so in light theme the drop zone kept its plain grey edge: the
   drag-over affordance and the "file attached" confirmation were both
   invisible. Restate the state borders under the light scope. The state
   background fills need no restatement — the light base rule doesn't set
   `background`, so they already survive. */
html[data-theme="light"] .glass-file-upload.is-dragging{
  border-color: rgba(var(--accent-rgb), 0.55);
}
html[data-theme="light"] .glass-file-upload.has-file{
  border-color: rgba(var(--success-rgb), 0.45);
}
html[data-theme="light"] .glass-file-upload.has-file .file-icon{
  /* rgb(74,222,128) is a dark-theme mint — it washes out on a white card. */
  color: rgb(var(--success-rgb));
}

/* ───────────────────────── 29. Glass textarea ────────────────────────── */
.glass-textarea{
  width: 100%;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-2);
  padding: var(--sp-3) var(--sp-3-5);
  color: var(--text);
  font-size: 0.9rem;
  font-family: inherit;
  resize: vertical;
  min-height: 64px;
}
.glass-textarea:focus{
  outline: none;
  border-color: rgba(var(--accent-rgb), 0.45);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.12);
}
html[data-theme="light"] .glass-textarea{
  border-color: rgba(0,0,0,0.08);
}

/* ───────────────────────── 30. Glass deposit CTA ─────────────────────── */
.btn-glass.cta-deposit{
  width: 100%;
  padding: var(--sp-4) var(--sp-6);
  font-size: var(--fs-3);
  font-weight: 800;
  /* Phase 5 stage 2: SOLID primary (was a decorative success gradient) —
     one main action per view, defined once. */
  background: var(--primary);
  color: var(--primary-foreground);
  border-color: transparent;
  letter-spacing: 0.3px;
  min-height: 52px;
}
.btn-glass.cta-deposit:hover{ filter: brightness(1.06); }
.btn-glass.cta-deposit:disabled,
.btn-glass.cta-deposit[disabled]{
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}
html[data-theme="light"] .btn-glass.cta-deposit{ color: rgb(20,83,45); }

/* ── Commit 3 (#43/#55): disabled primary CTAs use a NEUTRAL token ─────────
   Both CTAs kept `background: var(--primary)` when disabled and only dropped
   opacity, so a disabled button still read as the blue primary in dark theme
   (and, with the light-theme green text above, as a *success* state). A
   disabled control must not look like the page's main action. One shared
   neutral surface, readable label, no glow, not-allowed cursor — identical
   semantics in both themes. Placed after both CTA blocks so it wins on order;
   `.btn-glass.cta-*:disabled` also outranks the generic `.btn-glass:disabled`. */
.btn-glass.cta-buy:disabled,      .btn-glass.cta-buy[disabled],
.btn-glass.cta-buy[aria-disabled="true"],
.btn-glass.cta-deposit:disabled,  .btn-glass.cta-deposit[disabled],
.btn-glass.cta-deposit[aria-disabled="true"]{
  background: rgba(255,255,255,0.07);
  color: rgba(255,255,255,0.55);
  -webkit-text-fill-color: rgba(255,255,255,0.55);
  border-color: rgba(255,255,255,0.12);
  opacity: 1;              /* the neutral surface carries the state, not a wash */
  cursor: not-allowed;
  box-shadow: none;
  filter: none;
  transform: none;
}
html[data-theme="light"] .btn-glass.cta-buy:disabled,
html[data-theme="light"] .btn-glass.cta-buy[disabled],
html[data-theme="light"] .btn-glass.cta-buy[aria-disabled="true"],
html[data-theme="light"] .btn-glass.cta-deposit:disabled,
html[data-theme="light"] .btn-glass.cta-deposit[disabled],
html[data-theme="light"] .btn-glass.cta-deposit[aria-disabled="true"]{
  background: rgba(0,0,0,0.06);
  color: rgba(0,0,0,0.42);
  -webkit-text-fill-color: rgba(0,0,0,0.42);
  border-color: rgba(0,0,0,0.12);
}

/* ───────────────────────── 31. Glass transaction table ───────────────── */
/* AUTO-DECISION: kept as <table> rather than glass-row stack — ledger data
   is genuinely tabular (date, type, signed amount, balance-after, note),
   and aligning numeric columns for at-a-glance scanning matters here. */
.glass-tx-table{
  width: 100%;
  border-collapse: collapse;
  background: transparent;
}
.glass-tx-table th{
  text-align: start;
  padding: 10px var(--sp-3);
  font-size: 0.78rem; font-weight: 600;
  color: var(--muted-foreground); opacity: 1;
  background: var(--muted);                       /* distinct table-header step */
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.glass-tx-table td{
  padding: var(--sp-3);
  border-bottom: 1px solid rgba(255,255,255,0.04);
  font-size: 0.88rem;
  vertical-align: middle;
}
.glass-tx-table tbody tr{ transition: background 0.2s ease; }
.glass-tx-table tbody tr:hover{ background: var(--hover); }
.glass-tx-table tr:last-child td{ border-bottom: none; }
/* UI-0047: tabular figures so the amount / balance columns align cleanly
   row-to-row (Vazirmatn renders proportional digits by default). */
.glass-tx-table td[data-label="مبلغ"],
.glass-tx-table td[data-label="موجودی پس از"]{ font-variant-numeric: tabular-nums; }

/* ── §4 shared desktop-table states ───────────────────────────────────────
   Every list table (representatives is the reference) shares: a result count
   (in the page head + filter toolbar), footer pagination, row hover (above),
   keyboard focus, and an empty state via .empty-glass. Because the panel is
   server-rendered — a filter submit is a full-page navigation, and query
   errors surface as the page-level .glass-alert — "loading"/"error" are not
   client-side table phases here; .table-state below is the reusable text
   surface (empty today, error-ready) so any future async table stays
   consistent instead of inventing a one-off. */
/* 3b-2c (a11y): a focused child control's own .btn-glass:focus-visible ring
   (glass.css §"btn-glass") is the SINGLE focus indicator. The row must NOT add a
   second full-width outline (which read as a selection state), and it carries no
   background tint here either — :focus-within's tint equalled :hover (rule above)
   and would misread as a hovered/selected row. The <tr> is not itself focusable
   (no tabindex/role), so no tr:focus-visible is needed for direct row focus. */
.glass-tx-table tbody tr:focus-within{ outline: none; }
.table-state{ text-align: center; padding: 40px var(--sp-4); color: var(--muted-foreground); }
.table-state .table-state-icon{ font-size: var(--fs-hero); opacity: 0.5; margin-bottom: 10px; }
.table-state.is-error{ color: rgb(248,113,113); }
html[data-theme="light"] .table-state.is-error{ color: rgb(185,28,28); }

.glass-tx-table .tx-amount-positive,
.leaderboard .tx-amount-positive,
.tx-amount-positive{ color: rgb(74,222,128); font-weight: 700; direction: ltr; unicode-bidi: isolate; }
.glass-tx-table .tx-amount-negative,
.leaderboard .tx-amount-negative,
.tx-amount-negative{ color: rgb(248,113,113); font-weight: 700; direction: ltr; unicode-bidi: isolate; }
.glass-tx-table .tx-balance-after  { font-weight: 600; opacity: 0.85; direction: ltr; unicode-bidi: isolate; }
.glass-tx-table .tx-date           { font-size: 0.78rem; opacity: 0.65; direction: ltr; unicode-bidi: isolate; white-space: nowrap; }
/* H5.2: cap note width and force long unbroken strings to wrap so a
   single verbose note can't blow out the column width on desktop or
   inflate one mobile card (the table-to-card-collapse at ≤768px
   inherits these constraints — `display:flex` per cell keeps the
   label aligned). max-width is the desktop ceiling; min-width:0
   lets the cell shrink inside the flex card-collapse row. */
.glass-tx-table .tx-note{
  opacity: 0.75;
  font-size: var(--fs-1);
  max-width: 38ch;
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
}
@media (max-width: 768px){
  /* Inside the card-collapse the cell is display:flex with the label
     on the leading edge — give the note plenty of room but still cap
     it so a single 400-char note can't render an unbounded card. */
  .glass-tx-table .tx-note{ max-width: 100%; }
}

html[data-theme="light"] .glass-tx-table th{ border-bottom-color: rgba(0,0,0,0.08); }
html[data-theme="light"] .glass-tx-table td{ border-bottom-color: rgba(0,0,0,0.05); }
/* tx-table row hover uses the theme-independent --hover token (above) */
html[data-theme="light"] .glass-tx-table .tx-amount-positive,
html[data-theme="light"] .leaderboard .tx-amount-positive,
html[data-theme="light"] .tx-amount-positive{ color: rgb(22,101,52); }
html[data-theme="light"] .glass-tx-table .tx-amount-negative,
html[data-theme="light"] .leaderboard .tx-amount-negative,
html[data-theme="light"] .tx-amount-negative{ color: rgb(185,28,28); }

/* ───────────────────────── 32. Glass tx badge ────────────────────────── */
.glass-tx-badge{
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 3px 10px;
  border-radius: var(--r-full);
  font-size: 0.75rem; font-weight: 600;
  background: rgba(255,255,255,0.06);
  border: 1px solid var(--tx-badge-border, rgba(255,255,255,0.10));
  color: var(--tx-badge-text, var(--text));
}
.glass-tx-badge.deposit    { --tx-badge-border: rgba(var(--success-rgb), 0.5); --tx-badge-text: rgb(74,222,128); }
.glass-tx-badge.purchase   { --tx-badge-border: rgba(var(--warn-rgb), 0.5);    --tx-badge-text: rgb(252,211,77); }
.glass-tx-badge.refund     { --tx-badge-border: rgba(96,165,250, 0.5);         --tx-badge-text: rgb(147,197,253); }
.glass-tx-badge.adjustment { --tx-badge-border: rgba(156,163,175, 0.4);        --tx-badge-text: rgb(209,213,219); }

html[data-theme="light"] .glass-tx-badge.deposit    { --tx-badge-text: rgb(22,101,52); }
html[data-theme="light"] .glass-tx-badge.purchase   { --tx-badge-text: rgb(146,64,14); }
html[data-theme="light"] .glass-tx-badge.refund     { --tx-badge-text: rgb(29,78,216); }
html[data-theme="light"] .glass-tx-badge.adjustment { --tx-badge-text: rgb(75,85,99); }

/* ───────────────────────── 33. Glass deposit row ─────────────────────── */
.glass-deposit-row{
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-3) var(--sp-4);
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--r-2);
  margin-bottom: 8px;
}
.glass-deposit-row .dep-amount{
  font-size: 1rem;
  font-weight: 700;
  direction: ltr;
  unicode-bidi: isolate;
}
.glass-deposit-row .dep-meta{
  font-size: 0.78rem;
  opacity: 0.6;
  margin-top: 2px;
}
.glass-deposit-row .dep-actions{
  display: flex; gap: 6px; align-items: center;
}
html[data-theme="light"] .glass-deposit-row{
  border-color: rgba(0,0,0,0.05);
}

/* ═══════════════════════════════════════════════════════════════════════
   REPRESENTATIVES MANAGEMENT — tier badges, margin cells, actions menu,
   and the form-row helper used by the create-rep + create-child modals.
   ═══════════════════════════════════════════════════════════════════════ */

/* ───────────────────────── 34. Glass tier badge ──────────────────────── */
/* AUTO-DECISION: indigo / blue / purple = depth gradient. L1 sits closest
   to sudo (brand accent indigo), L2 mid (blue), L3 deepest (purple). The
   colors are subtle but scannable: a column of mixed-tier rows reads as
   a visible depth map at a glance. */
.glass-tier-badge{
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 38px;
  padding: 3px 10px;
  border-radius: var(--r-1);
  font-size: 0.78rem; font-weight: 700;
  background: var(--glass-card-bg);
  border: 1px solid var(--tier-border-color, rgba(255,255,255,0.10));
  color: var(--tier-text-color, var(--text));
  direction: ltr;
  unicode-bidi: isolate;
  letter-spacing: 0.5px;
}
.glass-tier-badge.l1{
  --tier-border-color: rgba(var(--accent-rgb), 0.45);
  --tier-text-color: rgb(199,210,254);
  background: rgba(var(--accent-rgb), 0.10);
}
.glass-tier-badge.l2{
  --tier-border-color: rgba(96,165,250, 0.45);
  --tier-text-color: rgb(147,197,253);
  background: rgba(96,165,250, 0.10);
}
.glass-tier-badge.l3{
  --tier-border-color: rgba(168,85,247, 0.45);
  --tier-text-color: rgb(216,180,254);
  background: rgba(168,85,247, 0.10);
}
html[data-theme="light"] .glass-tier-badge.l1{ --tier-text-color: rgb(67,56,202); }
html[data-theme="light"] .glass-tier-badge.l2{ --tier-text-color: rgb(29,78,216); }
html[data-theme="light"] .glass-tier-badge.l3{ --tier-text-color: rgb(126,34,206); }

/* ───────────────────────── 35. Glass margin cell ─────────────────────── */
/* AUTO-DECISION: negative margins get a stronger glow than positive ones.
   Healthy margins should fade into the data; anomalies (Q9 inversion
   surface) should grab attention. */
.glass-margin-cell{
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-weight: 700;
  direction: ltr; unicode-bidi: isolate;
  padding: 2px var(--sp-2);
  border-radius: 6px;
  font-size: 0.88rem;
  transition: box-shadow 0.3s ease;
}
.glass-margin-cell.positive{
  color: rgb(74,222,128);
  background: rgba(var(--success-rgb), 0.08);
  box-shadow: 0 0 12px rgba(var(--success-rgb), 0.12);
}
.glass-margin-cell.negative{
  color: rgb(248,113,113);
  background: rgba(var(--danger-rgb), 0.10);
  box-shadow: 0 0 14px rgba(var(--danger-rgb), 0.20);
}
html[data-theme="light"] .glass-margin-cell.positive{
  color: rgb(22,101,52);
  background: rgba(var(--success-rgb), 0.10);
}
html[data-theme="light"] .glass-margin-cell.negative{
  color: rgb(185,28,28);
  background: rgba(var(--danger-rgb), 0.12);
}
/* UI-0250: a break-even margin (0) is neither profit nor loss — render it
   muted instead of reusing the red "negative" treatment. */
.glass-margin-cell.neutral{
  color: var(--text-d);
  background: rgba(127,127,127, 0.10);
}

/* ───────────────────────── 36. Glass actions menu — REMOVED ──────────
   RULE-7 (Phase 6): the .glass-actions-* dropdown rules (~95 lines) were
   removed along with the legacy .actions-* rules in app.css. The
   replacement is the .row-actions / .btn-glass.icon-only flexbox added
   at the bottom of this file. No markup references the dropdown classes
   anymore (verified via grep).
   ────────────────────────────────────────────────────────────────── */

/* ───────────────────────── 37. Glass form row ────────────────────────── */
.glass-form-row{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
}
@media (max-width: 600px) {
  .glass-form-row { grid-template-columns: 1fr; }
}

/* ───────────────────────── 38. Glass input (standalone) ──────────────── */
/* Same input chrome as .glass-input-group input but usable outside a
   .glass-input-group container — handy in the filters bar where the
   search field doesn't need its own label wrapper. */
.glass-input{
  width: 100%;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: 10px var(--sp-3);
  color: var(--text);
  font-size: 0.9rem;
  font-family: inherit;
}
.glass-input:focus{
  outline: none;
  border-color: rgba(var(--accent-rgb), 0.45);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.12);
}
html[data-theme="light"] .glass-input{
  border-color: rgba(0,0,0,0.08);
}

/* ═══════════════════════════════════════════════════════════════════════
   AUTH / LOGIN — centered entrance card + page-level atmosphere.

   AUTO-DECISION: cooperation Option B with app.css's --auth-* token block
   (defined at app.css:52-64 dark / :114-126 light). These classes consume
   --auth-bg, --auth-blob1/2/3, --auth-card, --auth-card-border,
   --auth-card-glow rather than hardcoded RGBs — theme switching continues
   to flow through the existing token toggle.
   ═══════════════════════════════════════════════════════════════════════ */

/* ───────────────────────── 41. Glass auth card ───────────────────────── */
/* AUTO-DECISION: co-classed onto .login-card; uses --auth-card / -border /
   -glow tokens so theme switching flows through. The visual upgrade
   layered on top is the outer accent halo (::after radial glow) — the
   detail that gives an auth surface presence without being loud. */
.glass-auth-card{
  position: relative;
  width: 100%;
  max-width: 440px;
  padding: var(--sp-7) 28px;
  background: var(--auth-card, rgba(20,22,32,0.65));
  border: 1px solid var(--auth-card-border, rgba(255,255,255,0.10));
  border-radius: 22px;
  box-shadow: var(--auth-card-glow, 0 20px 60px rgba(0,0,0,0.45)),
              inset 0 1px 0 rgba(255,255,255,0.08);
  z-index: 1;
}
.glass-auth-card .auth-title{
  font-size: 1.4rem;
  font-weight: 800;
  margin-bottom: 6px;
  text-align: center;
}
.glass-auth-card .auth-subtitle{
  font-size: 0.88rem;
  opacity: 0.65;
  text-align: center;
  margin-bottom: 22px;
}
.glass-auth-card .auth-form > * + *{ margin-top: 14px; }
.glass-auth-card .auth-footer{
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px solid rgba(255,255,255,0.06);
  text-align: center;
  font-size: var(--fs-1);
  opacity: 0.6;
}
html[data-theme="light"] .glass-auth-card .auth-footer{
  border-top-color: rgba(0,0,0,0.06);
}

/* ───────────────────────── 42. Auth submit CTA ───────────────────────── */
/* AUTO-DECISION: .auth-cta is a smaller-padding variant of .cta-buy/.cta-deposit.
   Login isn't a financial CTA — full cta-buy padding (16px 24px, 52px min-height)
   would overweight the auth surface. 14px 20px / 48px min-height matches the
   visual rhythm of the form's inputs. */
.btn-glass.auth-cta{
  width: 100%;
  padding: var(--sp-3-5) var(--sp-5);
  font-size: 1rem;
  font-weight: 700;
  background: linear-gradient(135deg,
    rgba(var(--accent-rgb), 0.18),
    rgba(var(--accent-rgb), 0.08));
  border-color: rgba(var(--accent-rgb), 0.35);
  min-height: 48px;
  letter-spacing: 0.3px;
}
.btn-glass.auth-cta:hover{ transform: translateY(-1px); }
.btn-glass.auth-cta:disabled,
.btn-glass.auth-cta[disabled]{
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   MOBILE BOTTOM NAVIGATION — fixed glass bar visible at ≤ 768px.
   Replaces the legacy .mobile-nav / .mbn-* block in app.css:571-603.

   AUTO-DECISION: height uses the existing --mob-nav-h token (68px). The
   universal body padding rule at app.css:146 already accounts for this
   token; matching it keeps the bottom padding correct without touching
   any page-level markup.

   AUTO-DECISION: body padding strategy is opt-out (mirrors existing
   `body.auth-page { padding-bottom: 0 }` at app.css:150) rather than the
   brief's opt-in `body.has-bottom-nav`. Touching every page that
   includes nav.php (8+ pages) to add a class would be churn for zero
   functional gain — the universal rule already exists.
   ═══════════════════════════════════════════════════════════════════════ */

/* ───────────────────────── 43. Bottom-nav surface ────────────────────── */
.glass-bottom-nav{
  position: fixed;
  inset: auto 0 0 0;
  display: none; /* shown on mobile + tablet (≤1023px) via the media query below */
  z-index: 150;  /* matches legacy .mobile-nav z-index */
  background: var(--glass-nav-bg);
  border-top: 1px solid var(--border);
  box-shadow: 0 -6px 24px rgba(0,0,0,0.18);
  padding-bottom: env(safe-area-inset-bottom, 0);
}

/* AUTO-DECISION: `display: none` outside the breakpoint rather than
   `visibility: hidden` — visibility:hidden still claims layout space at
   the bottom of every desktop page; display:none drops it cleanly.
   BREAKPOINT FIX: raised 768→1023 so the bottom nav stays present across the
   whole tablet range. The desktop top-nav only appears at ≥1024px (app.css
   §.nav min-width:1024), so with the old 768 ceiling the 769–1023px band had
   NO navigation at all (breakpoint dead zone). The contract is now gapless:
   bottom nav ≤1023px, desktop nav ≥1024px — exactly one nav at every width. */
@media (max-width: 1023px) {
  .glass-bottom-nav { display: grid; }
}

html[data-theme="light"] .glass-bottom-nav{
  box-shadow: 0 -6px 24px rgba(30,40,70,0.07);
}

/* AUTO-DECISION: extend the existing universal `body { padding-bottom:
   var(--mob-nav-h) }` (app.css:146) with safe-area-inset on mobile so the
   iOS home-indicator area gets glass background and bottom content gets
   breathing room. Auth pages opt out via the existing `body.auth-page`
   rule at app.css:150.
   BREAKPOINT FIX: raised 768→1023 to match the bottom-nav display rule above,
   so tablet-width content clears the now-visible bottom nav. */
@media (max-width: 1023px) {
  body:not(.auth-page) {
    /* Phase 4 §3: +16px (was +8px) so the last card, its buttons, and any
       open menu clear the fixed bottom nav + iOS home indicator at 390px. */
    padding-bottom: calc(var(--mob-nav-h, 68px) + 16px + env(safe-area-inset-bottom, 0));
  }
}

/* ───────────────────────── 44. Nav items layout ──────────────────────── */
/* AUTO-DECISION: grid-auto-flow:column + grid-auto-columns:1fr distributes
   N items equally without needing to know N. Flex space-around with
   `flex: 1` does the same; grid is one declaration shorter and aligns
   with the rest of the design system's grid usage. */
.glass-bottom-nav .nav-items{
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  height: var(--mob-nav-h, 68px);
}

.glass-nav-item{
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  text-decoration: none;
  color: var(--text-soft);
  font-size: 0.7rem;
  font-weight: 600;
  padding: 6px var(--sp-1);
  transition: color 0.2s ease, transform 0.12s ease, opacity 0.12s ease;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.glass-nav-item:active{
  transform: scale(0.96);
  opacity: 0.85;
}
.glass-nav-item .nav-icon{
  font-size: 22px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 1;
}
.glass-nav-item .nav-label{
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 64px;
}
/* Phase 1 item 1: icon-only bottom nav. Inactive tabs = icon only; the ACTIVE
   tab reveals its short label. This is what makes 5 slots survive 320px (only
   ever one label on screen) and keeps the bar Blu-calm. aria-label on each tab
   carries the full name, so hiding the visible label costs no a11y.
   Icon-only is scoped to the bottom nav ONLY (A9). */
.glass-bottom-nav .nav-label{ display: none; }
.glass-bottom-nav .glass-nav-item.active .nav-label{ display: block; max-width: 100%; }

/* Phase 5 stage 3a: theme toggle relocated off the floating FAB.
   Desktop: a compact square ghost icon-button in the header nav. */
.nav-theme-toggle{ width: 40px; padding-inline: 0; }
.nav-theme-toggle .icon{ margin: 0; }

/* ══ "بیشتر" overflow — desktop popover + mobile bottom sheet (Phase 5 3b) ══
   One markup (#nav-more-menu), two presentations chosen by initNavMore() at
   open time via .is-desktop / .is-mobile:
     • desktop (≥769px): anchored dropdown popover near the trigger, NO overlay,
       flips up when short on room, viewport-clamped.
     • mobile  (≤768px): compact bottom sheet + ~45% backdrop (no heavy blur),
       focus trap + body scroll lock.
   Rows are plain 48px menu items (icon · label · optional trailing badge). The
   shared modal stack is intentionally NOT reused — it stays a centered dialog
   for its other consumers. */
/* The mode classes are added by JS at open time and are declared LATER in this
   file at the SAME specificity (0,2,0) as `.nav-more[hidden]`, so
   `.nav-more.is-mobile{ display: flex }` was beating `[hidden]{ display:none }`
   on source order. Once the sheet had been opened once on a phone, the hidden
   attribute could no longer hide it: close() correctly set `hidden` and removed
   `.open`, but the sheet stayed on screen at full opacity with its backdrop, so
   the menu simply never closed — tapping the X ran close() to no visible effect.
   Desktop hid the same failure because `.is-desktop` collapses the wrapper to
   0x0 with pointer-events:none.
   Listing the mode classes here makes the rule win on specificity (0,3,0)
   rather than on source order, so re-ordering or a new mode can't resurrect it. */
.nav-more[hidden],
.nav-more[hidden].is-mobile,
.nav-more[hidden].is-desktop{ display: none; }
.nav-more{ position: fixed; inset: 0; z-index: 1200; }
/* desktop: the wrapper must not cover the page — the sheet is a floating
   popover positioned (position:fixed) by JS; the wrapper is inert scaffolding. */
.nav-more.is-desktop{ inset: auto; width: 0; height: 0; pointer-events: none; }
.nav-more.is-desktop .nav-more-backdrop,
.nav-more.is-desktop .nav-more-head{ display: none; }

.nav-more-backdrop{
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.45);   /* ~45%, no heavy blur (RULE-9 direction) */
}

.nav-more-sheet{
  background: var(--popover);      /* opaque elevated surface (shadcn) */
  border: 1px solid var(--border);
  color: var(--text);
  display: flex; flex-direction: column;
}

/* ─ desktop popover shape ─ */
.nav-more.is-desktop .nav-more-sheet{
  position: fixed;
  pointer-events: auto;
  width: 264px; max-width: calc(100vw - 24px);
  border-radius: var(--r-2);
  box-shadow: 0 16px 48px rgba(0,0,0,0.45);
  padding: 6px; gap: 2px;
  max-height: calc(100vh - 24px);
  overflow-y: auto; overscroll-behavior: contain;
}
.nav-more.is-desktop.open .nav-more-sheet{ animation: rep-menu-in 160ms ease-out; }
html[data-theme="light"] .nav-more-sheet{ box-shadow: 0 16px 48px rgba(0,0,0,0.14); }

/* ─ mobile bottom-sheet shape ─ */
.nav-more.is-mobile{ display: flex; align-items: flex-end; }
.nav-more.is-mobile .nav-more-sheet{
  position: relative;
  width: 100%; margin-top: auto;
  border-radius: var(--r-3) var(--r-3) 0 0;
  box-shadow: 0 -12px 40px rgba(0,0,0,0.40);
  padding: 8px var(--sp-2) calc(var(--sp-3) + env(safe-area-inset-bottom, 0));
  gap: 2px;
  max-height: 82dvh; overflow-y: auto; overscroll-behavior: contain;
}
.nav-more.is-mobile.open .nav-more-sheet{ animation: nav-more-sheet-in 260ms cubic-bezier(0.32,0.72,0,1); }
@keyframes nav-more-sheet-in { from { transform: translateY(100%); } to { transform: translateY(0); } }

/* mobile header: grabber (::before) + title + close */
.nav-more-head{
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px var(--sp-3) 8px;
}
.nav-more.is-mobile .nav-more-sheet::before{
  content: ""; position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
  width: 36px; height: 4px; border-radius: var(--r-full); background: var(--border);
}
.nav-more-title{ display: inline-flex; align-items: center; font-weight: 700; font-size: var(--fs-3); }
.nav-more-close{
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; background: transparent; border: none;
  border-radius: var(--r-1); color: var(--text); cursor: pointer;
}
.nav-more-close:hover{ background: var(--hover); }

/* groups + section labels + separators */
.nav-more-group{ display: flex; flex-direction: column; gap: 2px; }
.nav-more-group + .nav-more-group{
  border-top: 1px solid var(--border); margin-top: 6px; padding-top: 6px;
}
.nav-more-group-title{
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.3px; /* Commit 1 (#11): 12px floor */
  color: var(--muted-foreground); padding: 4px var(--sp-3) 2px;
}
.nav-more-logout{ margin: 0; }

/* menu rows — icon · label · trailing badge, 48px min tap target */
.nav-more-item{
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 48px;
  padding: 8px var(--sp-3);
  background: transparent; border: none; border-radius: 10px;
  color: var(--text); font-family: inherit; font-size: 0.92rem; font-weight: 500;
  text-align: start; text-decoration: none; cursor: pointer;
}
.nav-more-item > svg,
.nav-more-item .icon-inline{ flex-shrink: 0; opacity: 0.9; margin-inline-end: 0; }
/* Commit 6 (#20): the count badge used to be pushed to the far edge of the row
   (`margin-inline-start:auto` + a growing label), leaving a measured 199px gap
   between "اعلان" and its "1" — the number read as unrelated to the item. The
   label now hugs its text so the badge sits directly beside it, inside the
   row's normal 10px gap. */
.nav-more-item .nav-more-label{ flex: 0 1 auto; min-width: 0; }
.nav-more-item .nb{ margin-inline-start: 0; flex-shrink: 0; }

/* Commit 6 (#22): auto-approve is a SETTING, not a one-shot action — the row
   showed only a verb, so the current state had to be inferred from which verb
   was displayed. Stable name + explicit on/off token; aria-pressed already
   carried the state for AT and the aria-label still names the action. */
.rep-menu-item .rep-menu-state{
  margin-inline-start: auto;
  flex-shrink: 0;
  font-size: 0.8rem;              /* Commit 1 (#11): 12px floor */
  font-weight: 700;
  line-height: 1.4;
  padding: 1px 8px;
  border-radius: var(--r-full);
  border: 1px solid transparent;
}
.rep-menu-item .rep-menu-state.is-on{
  color: rgb(var(--success-text-rgb));
  border-color: rgba(var(--success-rgb), 0.45);
}
.rep-menu-item .rep-menu-state.is-off{
  color: var(--muted-foreground);
  border-color: var(--border, rgba(255,255,255,0.14));
}
.nav-more-item:hover,
.nav-more-item:focus-visible{ background: var(--hover); outline: none; }
.nav-more-item.danger{ color: rgb(248,113,113); }
.nav-more-item.danger:hover,
.nav-more-item.danger:focus-visible{ background: rgba(var(--danger-rgb), 0.12); }
html[data-theme="light"] .nav-more-item.danger{ color: rgb(185,28,28); }

/* theme-toggle row: label is ::after because app.js overwrites the button
   innerHTML with the sun/moon icon (a .nav-more-label span would be erased). */
.nav-more-theme::after{ content: "تغییر تم"; }

/* body scroll lock — MOBILE sheet only (desktop popover never locks). */
body.nav-more-lock{ overflow: hidden; }

/* ───────────────────────── 45. Active state ──────────────────────────── */
/* PERF/SIMPLIFY: this state used to be "over-determined" with three
   composing signals (color + radial glow + top pill) -- the radial
   backlight glow behind the icon was removed as a pure decorative
   layer; color + the top-edge pill below remain as the active-state
   indicator, which is already sufficient to be glanceable at arm's
   length. */
.glass-nav-item.active{ color: var(--accent); }
.glass-nav-item.active .nav-icon{ color: var(--accent); }

/* AUTO-DECISION: top-edge pill animates IN on every page load (forwards
   fill mode). The user perceives navigation as a discrete jump from page
   to page; an animation each time reinforces that the new page has
   "received" them. CSS-only — no JS sequencing required. */
.glass-nav-item.active::before{
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%) scaleX(0);
  width: 32px;
  height: 3px;
  border-radius: 0 0 4px 4px;
  background: rgb(var(--accent-rgb));
  box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.6);
  animation: nav-pill-in 220ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes nav-pill-in {
  0%   { transform: translateX(-50%) scaleX(0);    opacity: 0; }
  60%  { transform: translateX(-50%) scaleX(1.08); opacity: 1; }
  100% { transform: translateX(-50%) scaleX(1);    opacity: 1; }
}

html[data-theme="light"] .glass-nav-item.active        { color: rgb(67,56,202); }
html[data-theme="light"] .glass-nav-item.active .nav-icon{ color: rgb(67,56,202); }

/* ───────────────────────── 46. Notification count badge ──────────────── */
/* Carried over from legacy .mbn-badge — same red dot with count, just
   re-styled to match the glass aesthetic. */
/* H1.14: anchor the badge with logical positioning so the placement
   flips correctly with locale and never overlaps the icon on narrow
   (sub-72px) nav cells. The cell already establishes position:
   relative; inset-inline-end picks the trailing edge in either
   reading direction. The offset is tuned so the badge hugs the
   icon's upper-trailing corner without crossing the cell center —
   width-agnostic, so it stays readable on 50–96 px cells. */
.glass-nav-badge{
  position: absolute;
  top: 2px;
  inset-inline-end: calc(50% - 18px);
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--r-full);
  background: linear-gradient(180deg,
    rgba(var(--danger-rgb), 0.95),
    rgba(var(--danger-rgb), 0.78));
  color: #fff;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.2px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px rgba(20,22,32,0.85),
              0 2px 6px rgba(var(--danger-rgb), 0.35);
  z-index: 2;
}
html[data-theme="light"] .glass-nav-badge{
  box-shadow: 0 0 0 2px rgba(255,255,255,0.92),
              0 2px 6px rgba(var(--danger-rgb), 0.25);
}

/* ══════════════════════════════════════════════════════════════════
   RULE-7 (Phase 6) — Inline row actions (replaces the ⋯ dropdown).
   ══════════════════════════════════════════════════════════════════
   The .row-actions container flows the per-row action buttons
   horizontally in the same <td> as the row content. Each action is
   a .btn-glass.icon-only (emoji-only, with title=/aria-label=).
   .row-actions-cell is applied to the parent <td> so Rule-8 mobile
   CSS can target it for the card-collapsed layout. */
.row-actions{
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
}
.row-action-form{
  /* Each action that needs to POST is wrapped in its own <form>; this
     keeps the form display inline so it doesn't break the flex row. */
  display: inline-flex;
  margin: 0;
}
.btn-glass.icon-only{
  /* AUTO-DECISION (Phase 6 §8 #11): padding + content-box + min-width/height
     trick keeps the visual size small (28×28 desktop, 36×36 mobile) while
     expanding the actual click target to 44×44 — WCAG mobile touch-target. */
  padding: var(--sp-1);
  box-sizing: content-box;
  width: 28px;
  height: 28px;
  min-width: 44px;
  min-height: 44px;
  font-size: var(--fs-2);
  line-height: 1;
  /* Reset rounded-pill from base .btn-glass — square-ish chip looks
     better when multiple icon buttons sit side-by-side. */
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
@media (max-width: 768px){
  .btn-glass.icon-only{
    width: 36px;
    height: 36px;
    font-size: var(--fs-3);
  }
}
/* UI-0017: on a fine pointer (desktop mouse) the 44px touch-target minimum is
   not required (WCAG 2.5.8 allows 24px for pointer input). Relax the icon
   buttons there so a long action row (up to ~9 icons) stops forcing the rep
   table into horizontal scroll on 1366px laptops. Coarse-pointer / mobile
   keeps the 44px target above. The .row-actions container already wraps. */
@media (pointer: fine) and (min-width: 769px){
  .btn-glass.icon-only{
    min-width: 32px;
    min-height: 32px;
  }
}

/* UI-0036: when the buy page can't be afforded, the pay CTA is disabled and
   demoted to a muted treatment so the (actionable) recharge link reads as the
   primary action instead of two competing primaries. */
.btn-glass.cta-buy.cta-muted{
  background: rgba(255,255,255,0.05);
  border-color: rgba(255,255,255,0.12);
  box-shadow: none;
  opacity: 0.55;
}
html[data-theme="light"] .btn-glass.cta-buy.cta-muted{
  background: rgba(0,0,0,0.04);
  border-color: rgba(0,0,0,0.10);
}

/* ══════════════════════════════════════════════════════════════════
   RULE-8 (Phase 6) — Mobile-first responsive overhaul
   ══════════════════════════════════════════════════════════════════
   Breakpoints (consistent across all CSS additions in this rule):
     ≥ 1024px  desktop      tables full-width
     768–1023  tablet       tables scroll inside .table-wrap (existing)
     < 768     phone        tables card-collapse, modals slide from bottom

   Every <td> in body rows must carry data-label="…" matching its column
   header for the card-collapse to render readable rows. Pages touched in
   this rule:
     admin/representatives.php
     admin/dashboard.php  (leaderboard)
     admin/wallet.php     (transactions list)
     admin/deposits.php
     admin/transactions.php
     admin/transfer.php
     admin/queue.php
     admin/subscriptions.php  (already a vertical row-list — verify)
     admin/my_subscriptions.php
*/

/* ─── Table → card collapse on phone ─────────────────────────────── */
/* Selector list covers every table flavor in admin/:
     .glass-tx-table              — representatives, wallet
     .leaderboard                 — dashboard (sudo + rep variants)
     .table-wrap > table          — deposits, transactions, queue, transfer,
                                    my_subscriptions (bare <table>)
   To opt OUT of card-collapse, add class="no-collapse" to the <table>.
   PHASE5-TBL: each `.tbl-wrap > table…` twin was deleted — zero call sites
   (those pages all wrap in `.table-wrap`), and every deleted line had an
   identical `.table-wrap` sibling in the same list. */
@media (max-width: 1023px){
  .glass-tx-table thead,
  .leaderboard thead,
  .table-wrap > table:not(.no-collapse) thead{ display: none; }

  .glass-tx-table,
  .glass-tx-table tbody,
  .glass-tx-table tr,
  .glass-tx-table td,
  .leaderboard,
  .leaderboard tbody,
  .leaderboard tr,
  .leaderboard td,
  .table-wrap > table:not(.no-collapse),
  .table-wrap > table:not(.no-collapse) tbody,
  .table-wrap > table:not(.no-collapse) tr,
  .table-wrap > table:not(.no-collapse) td{
    display: block; width: 100%;
  }

  .glass-tx-table tr,
  .leaderboard tr,
  .table-wrap > table:not(.no-collapse) tr{
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 14px;
    padding: var(--sp-3);
    margin-bottom: 12px;
    background: rgba(20,22,32,0.65);
  }

  .glass-tx-table td,
  .leaderboard td,
  .table-wrap > table:not(.no-collapse) td{
    border: none;
    padding: 6px 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-3);
  }
  .glass-tx-table td::before,
  .leaderboard td::before,
  .table-wrap > table:not(.no-collapse) td::before{
    content: attr(data-label);
    font-size: 0.8rem;
    opacity: 0.65;
    flex-shrink: 0;
  }
  /* .row-actions-cell (Rule 7): sit at the bottom of the stacked card,
     separated by a hairline divider. No label prefix; buttons go full
     width on the trailing edge. */
  .glass-tx-table .row-actions-cell,
  .leaderboard .row-actions-cell{
    padding-top: 10px;
    border-top: 1px solid rgba(255,255,255,0.06);
    margin-top: 8px;
  }
  .glass-tx-table .row-actions-cell::before,
  .leaderboard .row-actions-cell::before{ display: none; }
  .glass-tx-table .row-actions,
  .leaderboard .row-actions{ width: 100%; justify-content: flex-end; }
}
html[data-theme="light"] .glass-tx-table tr,
html[data-theme="light"] .leaderboard tr,
html[data-theme="light"] .table-wrap > table tr{
  background: rgba(255,255,255,0.65);
}

/* ── Stage 3c (deposits) ─────────────────────────────────────────────────
   Inline "جزئیات بیشتر" disclosure inside a .glass-alert. Native <details>
   (no JS — same mechanism as queue.php / representatives.php pricing); the
   summary reads as an inline link and the tip flows after it on expand.
   Minimal, scoped to alerts — not a reusable accordion component. */
.glass-alert .alert-more{ display: inline; }
.glass-alert .alert-more > summary{
  display: inline; cursor: pointer;
  color: rgb(var(--accent-rgb)); font-weight: 600;
  list-style: none;
}
.glass-alert .alert-more > summary::-webkit-details-marker{ display: none; }
.glass-alert .alert-more[open] > summary{ display: block; margin-block-end: 4px; }

/* Mobile deposit-card hierarchy (≤768 card-collapse). Amount + status are what
   a reviewer scans first → primary; user → secondary; date/# → tertiary. Scoped
   to .dep-table (deposits-only — verified) so no other .glass-tx-table consumer
   is affected. Labels keep their fixed 0.8rem ::before, so these only scale the
   VALUE side. Status/type stay as color badges (already prominent). */
@media (max-width: 1023px){
  .dep-table td[data-label="مبلغ"]{ font-size: 1.05rem; font-weight: 700; }
  .dep-table td[data-label="کاربر"]{ font-weight: 500; }
  .dep-table td[data-label="تاریخ"],
  .dep-table td[data-label="#"]{ font-size: 0.78rem; opacity: 0.6; }
}

/* ─── Modal: bottom-sheet on phone ────────────────────────────────── */
/* fixes-merged-02 / Issue 1: bottom-sheet uses 100dvh to honor the mobile
   URL bar, and pins padding-bottom to safe-area-inset so the iOS home
   indicator doesn't cover the CTA. The flex column inherited from §9
   ensures the body scroll happens INSIDE the box. */
@media (max-width: 768px){
  .glass-modal{ align-items: flex-end; }
  .glass-modal .modal-box{
    /* Commit 3 (#47): the sheet was edge-to-edge (width:100%), so at 315px the
       dialog touched both viewport edges with no breathing room. It now floats
       just above the bottom with a 12px gutter on all three free sides — which
       also means all four corners round, not just the top two. */
    border-radius: var(--r-3);
    /* A-M: 92vh fallback before 92dvh for iOS Safari <15.4. */
    max-height: 92vh;
    max-height: 92dvh;
    margin: auto auto 12px;
    width: calc(100% - 24px);
    max-width: 560px;
    /* H4.12: mirror the bottom safe-area padding on the top edge so
       the iOS URL bar / notch can't overlap the modal header. The
       12px baseline matches the existing modal-header offset on
       phone-sized layouts; safe-area-inset-top stays 0 on browsers
       without the notch, so desktop / Android-without-cutout are
       unaffected. */
    padding-top: max(12px, env(safe-area-inset-top, 12px));
    padding-bottom: max(16px, env(safe-area-inset-bottom, 16px));
  }
}

/* ─── Desktop top header: hide below the desktop breakpoint ──────────────
   The bar holds exactly two things: .brand and #desktop-nav. #desktop-nav is
   already hidden under 1024px (app.css `.nav`), so between 769–1023px this
   rendered as a ~53px strip containing nothing but the brand — visible chrome
   with no function, on the range where the bottom nav owns navigation.
   Raised 768 → 1023 so it retires on the same boundary as everything else
   (nav, table→card, row restack): under 1024 = compact shell, 1024+ = desktop.
   Nothing is lost: the impersonation banner is a SIBLING emitted before this
   bar (nav.php) so it still shows, and the theme toggle has its own entry in
   the «بیشتر» sheet (.nav-more-theme) for exactly this range. */
@media (max-width: 1023px){
  .header{ display: none; }
}

/* ─── Sticky CTA: buy + wallet topup submit buttons ─────────────────
   H4.1: bump `bottom` so the CTA clears the bottom-nav (var(--mob-nav-h)
   = 68px) on mobile, and raise z-index above the nav (which sits at
   150). Without this the primary buy/deposit button is hidden under
   the nav strip on phones. */
@media (max-width: 768px){
  .btn-glass.cta-buy,
  .btn-glass.cta-deposit{
    position: sticky;
    bottom: calc(var(--mob-nav-h, 68px) + 12px + env(safe-area-inset-bottom, 0px));
    z-index: 160;
  }
}

/* PHASE5-PAYLOAD: the floating theme-toggle FAB (`.theme-fab`, ~10 rules here
   plus one in app.css §responsive and a `body.modal-open` hide) is deleted.
   nav.php stage 3a removed the element — "the floating theme-FAB is removed"
   (nav.php, near the bottom-nav partial) — and the live control is
   `.nav-theme-toggle` / `.nav-more-theme`. Zero `theme-fab` tokens in any
   .php/.js/.html. The comment that stood here claimed nav.php rendered it on
   dashboard.php, which had not been true since that stage — an ownership
   claim in a stylesheet is a hint to verify, never an answer. */



/* ═══════════════════════════════════════════════════════════════════════
   §47  REPRESENTATIVES MOBILE CARD — fixes-merged-02 / Issue 2 (Stage 1)
   ═══════════════════════════════════════════════════════════════════════
   Card layout that replaces the table at ≤ 768 px. Desktop ≥ 769 px keeps
   the existing .glass-tx-table. Two CSS hooks the patch adds on the PHP
   side:
     - .glass-rep-card-list   wrapper around the foreach
     - .rep-list-table        added to the existing <div class="table-wrap">
   This file owns the visibility swap; the PHP file owns the markup.
   ═══════════════════════════════════════════════════════════════════════ */

.glass-rep-card-list{ display: none; }
@media (max-width: 1023px){
  .glass-rep-card-list{
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-2);
    padding: var(--sp-1) 0;
  }
  /* Hide the desktop table on phone. The .glass-tx-table card-collapse
     rules from RULE-8 above would otherwise render a SECOND copy of the
     listing alongside the new cards. */
  .glass-card > .table-wrap.rep-list-table{ display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   §47a REPRESENTATIVES DESKTOP TABLE — Phase 4 (fixed row height + column
   triage, REDESIGN_PROMPT). Everything here is scoped under `.rep-list-
   table` (the wrapper unique to this page's desktop table — AUDIT
   confirmed no other page uses it) so the shared `.glass-tx-table` base
   styling used by wallet/deposits/queue/etc. is untouched.

   Visible columns stay at the max-6 target (نماینده, سطح, وضعیت, موجودی,
   فروش, عملیات); the rest (#, والد, زیرشاخه, عمده, سود, حاشیه, واریز) are
   marked `.rep-col-extra` in the PHP — kept in the DOM (never removed),
   just CSS-hidden here. They surface per row via `.rep-row-detail`, a
   colspan-spanning row toggled by `.rep-row-expand-btn` (plain inline
   onclick in the PHP — no assets/app.js edit for Phase 4). A genuine
   in-place column reveal isn't used for the expander: unhiding one row's
   `.rep-col-extra` cell while every other row keeps that column hidden
   would desync the table's column model across rows (browsers derive
   column count from ALL rows) — the detail row re-presents the same
   values safely instead. */
.rep-list-table .rep-col-extra{ display: none; }

/* §6: intentional column widths via table-layout:fixed (scoped). The hidden
   .rep-col-extra columns are display:none so they claim no track; the six
   visible columns take the target proportions. Non-sudo drops سطح — the
   browser rescales the remaining widths to fill 100%. */
.rep-list-table table{ table-layout: fixed; width: 100%; }
.rep-list-table .rep-w-name  { width: 30%; }
.rep-list-table .rep-w-tier  { width: 8%; }
.rep-list-table .rep-w-status{ width: 10%; }
.rep-list-table .rep-w-bal   { width: 18%; }
.rep-list-table .rep-w-sales { width: 12%; }
.rep-list-table .rep-w-act   { width: 22%; }

/* §6: row height target 56–60px (min — a 2-line name grows the row a little
   but stays well under the ≤72px guard rail). */
.rep-list-table tbody tr:not(.rep-row-detail){ height: 58px; }
.rep-list-table td, .rep-list-table th{ padding-top: 8px; padding-bottom: 8px; }
/* Phase 5 stage 2: table-header as a distinct muted step (ladder layer). */
.rep-list-table thead th{ background: var(--muted); color: var(--muted-foreground); border-bottom: 1px solid var(--border); }
/* Phase 5 stage 2: the desktop table row action (شارژ / هزینه) is OUTLINE, not
   solid blue — repeated row actions must not compete as solid primaries (the
   mobile card action is already the approved tonal/outline; this matches it).
   Overrides .btn-glass.primary here only (equal specificity, later in source). */
.rep-list-table .rep-action-primary{
  background: transparent;
  border-color: rgba(var(--accent-rgb), 0.45);
  color: var(--text);
}
.rep-list-table .rep-action-primary:hover,
.rep-list-table .rep-action-primary:focus-visible{
  background: var(--hover);
  border-color: rgba(var(--accent-rgb), 0.70);
  filter: none;
}
.rep-list-table td{ overflow: hidden; text-overflow: ellipsis; }

/* §6: sticky header — DEFERRED (Phase 5 candidate, see PHASE_LOG). A sticky
   thead here is inert: this page's global `body{overflow:hidden auto}` (in the
   base stylesheet, not glass.css) makes <body> the sticky scroll-container, but
   <html> is what actually scrolls — so position:sticky detaches for the WHOLE
   page (the existing `.header` desktop nav sticky is equally non-functional
   here, verified on the render). Fixing it means changing the global body/html
   overflow model — out of this phase's 2-file scope. Left un-stuck to avoid a
   half-engaged floating-header artifact; the header stays a clean natural row. */

/* §6: financial columns — tabular figures + no-wrap so the number and its
   trailing ت unit stay on one line, aligned row-to-row. */
.rep-list-table td[data-label="موجودی"],
.rep-list-table td[data-label="فروش"],
.rep-list-table td.rep-col-extra{ font-variant-numeric: tabular-nums; }
.rep-list-table td[data-label="موجودی"],
.rep-list-table td[data-label="فروش"]{ white-space: nowrap; }

/* Row-identity cell: expander toggle + name/username. min-width:0 + ellipsis
   let a long name truncate inside the fixed 30% column instead of overflowing. */
.rep-list-table .rep-row-identity{ min-width: 0; overflow: hidden; }
.rep-list-table .rep-row-name,
.rep-list-table .rep-row-username{ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Phase 5 stage 2: bidi-isolate Latin identifiers (usernames / ids) so an
   @handle or latin_username never reorders against the surrounding RTL row. */
.rep-row-username, .rep-card-username, .glass-row .row-id{ unicode-bidi: isolate; }
/* ROW-HEIGHT FIX: this was `.rep-row-name-cell{ display:flex }` — i.e. flex set
   directly on the <td>. A non-table `display` takes the cell OUT of the table's
   cell layout: the browser wraps it in an anonymous table-cell that stretches to
   the row, while the flex box itself stays content-height and top-aligned. The
   نماینده cell therefore measured 45px inside a 62px row, so
   `.rep-row-active` / hover painted its background over only the top 45px and
   left a 392x17 untinted RECTANGLE under that one column (plainly visible the
   moment a row is expanded), and the name rendered ~8px above the badges in its
   own row. A rep with a logo + display_name has a two-line identity that filled
   the cell (68 of 69px), which is exactly why the branded row looked correct and
   every other row did not. The cell stays a real table-cell; the flex context
   moves to an inner wrapper (see admin/representatives.php). */
.rep-row-name-cell{ vertical-align: middle; }
.rep-row-name-inner{ display: flex; align-items: center; gap: 6px; min-width: 0; }
.rep-row-expand-btn{
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  border-radius: var(--r-2, 8px);
  border: 1px solid rgba(255,255,255,0.12);
  background: rgba(255,255,255,0.04);
  color: inherit;
  cursor: pointer;
  padding: 0;
}
.rep-row-expand-btn:hover,
.rep-row-expand-btn:focus-visible{ background: rgba(255,255,255,0.08); }
.rep-row-expand-btn .icon{ width: 16px; height: 16px; transition: transform 0.15s ease; transform: rotate(-90deg); }
.rep-row-expand-btn.is-open .icon{ transform: rotate(90deg); }
html[data-theme="light"] .rep-row-expand-btn{
  border-color: rgba(0,0,0,0.1);
  background: rgba(0,0,0,0.03);
}
html[data-theme="light"] .rep-row-expand-btn:hover,
html[data-theme="light"] .rep-row-expand-btn:focus-visible{ background: rgba(0,0,0,0.06); }

/* §1/§4: FULL-WIDTH, content-height, accordion detail panel. The PHP colspan
   spans every visible column (the old colspan="<bdi>N</bdi>" was invalid and
   collapsed to 1, stranding content in a right-edge strip). One row opens at a
   time (pgRepExpand); the open row gets .rep-row-active and the panel below it
   shares that tint + a clear accent top separator, so the two read as one
   joined block. Responsive key/value grid: 4 cols desktop → 2 cols tablet.
   Phase 5 stage 2: NEUTRAL surfaces (color only for the thin selection bar).
   Active row = neutral hover; expanded panel = the muted surface step, so it
   reads as a distinct layer without a blue wash. The left accent bar (a thin
   selection/primary marker) stays as the only color. */
.rep-list-table{
  --rep-active:      var(--hover);
  --rep-detail-bg:   var(--muted);
  --rep-detail-line: var(--border);
}
/* active parent row — subtle highlight tying it to the panel below */
.rep-list-table tr.rep-row-active > td{ background: var(--rep-active); }
.rep-row-detail > td{
  /* The inset was on the <dl> alone, so the «جزئیات …» heading — a direct child
     of this zero-padded cell — started at the very cell edge and its first glyph
     was clipped by the 3px inset accent bar (and by `.rep-list-table td
     {overflow:hidden}`). Moving the same inset onto the cell puts the heading on
     the grid's alignment edge; total panel height is unchanged because the <dl>
     gives its padding up in exchange. */
  padding: var(--sp-3) var(--sp-5);
  background: var(--rep-detail-bg);
  border-top: 2px solid var(--rep-detail-line);   /* clear top separator */
  box-shadow: inset -3px 0 0 var(--accent, #6366f1); /* leading accent bar */
}
.rep-row-detail-grid{
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-1) var(--sp-6);
  margin: 0;
  padding: 0;             /* inset now lives on .rep-row-detail > td (above) */
  align-content: start;   /* height follows content, no vertical stretch */
}
@media (max-width: 1023.98px){
  .rep-row-detail-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.rep-row-detail-grid > div, .rep-row-detail-tail > div{
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: 6px 0;
  border-bottom: 1px solid rgba(255,255,255,0.05);  /* subtle dividers */
}
html[data-theme="light"] .rep-row-detail-grid > div, html[data-theme="light"] .rep-row-detail-tail > div{ border-bottom-color: rgba(0,0,0,0.06); }
/* Phase 4 polish: trailing remainder row (see representatives.php) — spans
   the full grid width and distributes its items evenly via flex, instead of
   the ragged short row a fixed 4-col grid leaves when the count isn't a
   multiple of 4 (non-sudo detail panel: 7 fields, not 8). */
.rep-row-detail-tail{
  grid-column: 1 / -1;
  display: flex;
  gap: var(--sp-1) var(--sp-6);
}
.rep-row-detail-tail > div{ flex: 1 1 0; min-width: 0; }
.rep-row-detail-grid dt{ font-size: 0.75rem; opacity: 0.55; margin: 0; flex-shrink: 0; min-width: 62px; }
.rep-row-detail-grid dd{ margin: 0; font-size: 0.9rem; font-weight: 600; font-variant-numeric: tabular-nums; }

.glass-rep-card{
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--sp-2) var(--sp-3);
  /* Dark theme's own surface (unchanged from pre-Phase-3-review) — clearly
     lighter than --bg (#0f1220) so the card keeps a visible edge. Light
     theme gets an independently-tuned override below; the two are NOT
     shared values, since dark and light need different separation math
     against very different page backgrounds. */
  background: rgba(20,22,32,0.55);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-2);
  box-shadow: 0 6px 24px rgba(0,0,0,0.30), inset 0 1px 0 rgba(255,255,255,0.05);
  /* Overflow visible so the rep-menu panel can render outside the card
     when the portal append is NOT loaded. */
  overflow: visible;
}
html[data-theme="light"] .glass-rep-card{
  /* Near-white surface against the light page bg (--bg: #f5f7fb) + a clear
     neutral border — no colored/decorative tint (Phase 3 review). Shadow is
     soft and shallow, neutral navy-gray at low opacity (matches how the
     rest of the system tints shadows), not black or accent-colored. */
  background: rgba(255,255,255,0.92);
  border-color: rgba(20,30,50,0.12);
  box-shadow: 0 4px 14px rgba(20,30,50,0.10);
}

/* Row 1 — identity: avatar + name/username + status/tier chip */
.rep-card-head{
  display: grid;
  /* Phase 4 §8b: 4th `auto` track holds the detail affordance (.rep-card-
     detail-btn) inline — without it the extra child wrapped to a second
     implicit grid row and blew the card past the ≤120px budget. On desktop
     the button is display:none so the track collapses to 0. */
  grid-template-columns: 32px minmax(0, 1fr) auto auto;
  gap: 10px;
  align-items: center;
}
.rep-card-avatar{
  width: 32px; height: 32px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 800;
  font-size: 0.92rem;
  letter-spacing: 0;
  /* Phase 5 stage 3a: no glow/shadow on avatars; calmer (less saturated) fill. */
  box-shadow: none;
  filter: saturate(0.85);
  flex-shrink: 0;
}
.rep-card-identity{
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.rep-card-name{
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-weight: 700;
  font-size: 0.9rem;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Commit 4 (#30): the ID moved out of .rep-card-name into .rep-card-submeta,
   so it's no longer descendant-scoped to the name line. */
.rep-card-id{
  font-family: ui-monospace, monospace;
  opacity: 0.55;
  font-weight: 600;
  font-size: 0.8rem;   /* Commit 1 (#11): 12px mobile-metadata floor */
  flex-shrink: 0;      /* the username ellipsizes first, never the ID */
}
/* Secondary identity line: @username (ellipsizes) + #id (fixed). */
.rep-card-submeta{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}
.rep-card-username{
  font-family: ui-monospace, monospace;
  font-size: 0.8rem;   /* Commit 1 (#11): 12px mobile-metadata floor */
  color: var(--muted-foreground);       /* Phase 5 stage 2: AA-safe muted meta */
  direction: ltr;
  unicode-bidi: isolate;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Commit 4 (#30): status + level are ONE metadata group, so they sit side by
   side. Stacked in a column they formed a tall narrow rail on the card's far
   edge, which is what made the row read as "spread across two sides" — and it
   set the card's height for no informational gain. */
.rep-card-flags{
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--sp-1);
  flex-shrink: 0;
}
/* Safeguard (not a fix for an observed break): laying the flags out in a ROW
   above turned them into flex siblings competing for width, and a flex item's
   default `flex-shrink: 1` would let a pill be squeezed below its content width
   and break its label across two lines. Pills keep their intrinsic width; if the
   row genuinely runs out of space, `flex-wrap` above moves one to the next line,
   which is the correct degradation. Column-stacked flags could not hit this. */
.rep-card-flags > *{
  flex-shrink: 0;
  white-space: nowrap;
}
.rep-card-flags .badge-glass,
.rep-card-flags .glass-tier-badge{ font-size: 0.8rem; padding: 2px var(--sp-2); } /* Commit 1 (#11): 12px floor */

/* Row 2 — primary metric (balance) + actions */
.rep-card-row2{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}
.rep-card-balance{
  min-width: 0;
  font-weight: 700;
  font-size: 0.92rem;
  direction: ltr;
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Actions */
.rep-card-actions{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
  /* Visible overflow lets the rep-menu panel hang off the card when
     the portal append isn't loaded. */
  position: relative;
}
.rep-card-actions .btn-glass{ min-height: 44px; padding: var(--sp-2) var(--sp-3-5); font-size: 0.84rem; }
/* هزینه/شارژ repeats on every one of ~25 rows — a solid accent fill at that
   repetition rate reads as a wall of blue and drowns out name/balance/status
   (the actually-important data). Tonal/outlined instead: calm at rest, still
   unambiguously clickable (border + icon+word), a real hover/active step so
   it "feels alive", and a visibly flatter :disabled so a dead state never
   gets mistaken for the calm default. Dark and light are independently
   tuned (not shared values) — see the .glass-rep-card comment above for why. */
.rep-card-actions .rep-action-primary{
  background: rgba(70,90,200,0.14);
  border-color: #5267C9;
  color: #E4E9FF;
}
/* This panel is touch-first — hover barely exists on a phone, :active is the
   feedback users actually feel. :active is deliberately the STRONGEST step,
   stronger than :hover (a desktop nicety), while staying tonal/outlined —
   never a solid fill. */
.rep-card-actions .rep-action-primary:hover,
.rep-card-actions .rep-action-primary:focus-visible{
  background: rgba(70,90,200,0.20);
  border-color: #6478D0;
}
.rep-card-actions .rep-action-primary:active{
  background: rgba(70,90,200,0.38);
  border-color: #8296F0;
}
/* Moderated, not erased: label/icon and border stay readable so a disabled
   row still reads as "a real control, currently off" rather than "no button
   here" — alpha-dimmed, not swapped to a near-invisible gray. */
.rep-card-actions .rep-action-primary:disabled{
  background: rgba(255,255,255,0.07);
  border-color: rgba(255,255,255,0.24);
  color: rgba(228,233,255,0.55);
  cursor: not-allowed;
}
html[data-theme="light"] .rep-card-actions .rep-action-primary{
  background: #F4F6FF;
  border-color: #8FA2FF;
  color: #3550C8;
}
html[data-theme="light"] .rep-card-actions .rep-action-primary:hover,
html[data-theme="light"] .rep-card-actions .rep-action-primary:focus-visible{
  background: #ECF0FF;
  border-color: #7C90F5;
}
html[data-theme="light"] .rep-card-actions .rep-action-primary:active{
  background: #D2DBFF;
  border-color: #5267C9;
}
html[data-theme="light"] .rep-card-actions .rep-action-primary:disabled{
  background: rgba(20,30,50,0.06);
  border-color: rgba(20,30,50,0.22);
  color: rgba(53,80,200,0.55);
  cursor: not-allowed;
}
.rep-action-glyph{ font-size: 1rem; line-height: 1; }

/* Overflow trigger sits at the trailing edge of the actions row. */
.rep-card-actions .rep-menu{
  flex-shrink: 0;
}

/* Overflow panel — when the portal append is loaded, it lives in <body>;
   when it's NOT, it lives inside the card. Both code paths share these
   visual rules via .rep-menu-panel. */
.rep-menu-panel{
  position: absolute;
  z-index: 50;
  min-width: 200px;
  margin-top: 8px;
  inset-inline-end: 0;
  background: var(--popover);           /* opaque elevated surface (shadcn) */
  border: 1px solid var(--border);
  border-radius: var(--r-2);            /* 12px — menu radius */
  box-shadow: 0 16px 48px rgba(0,0,0,0.45);  /* shadow OK: this is a dropdown */
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* The HTML [hidden] attribute is the closed-state contract: the panel
   ships hidden and app.js's RepMenu toggles it (removeAttribute on open,
   setAttribute on close). But [hidden] only hides via the UA rule
   [hidden]{display:none}, which any author `display` declaration beats
   regardless of specificity — so `.rep-menu-panel{display:flex}` above
   was leaving every panel visible on load (piled under the cards) and
   defeating the JS close path. Re-assert the closed state at higher
   specificity (class + attribute) so it wins over the base rule and
   `body > .rep-menu-panel` alike. */
.rep-menu-panel[hidden]{ display: none; }
/* When portaled to <body>, the JS controls top/left in px using
   viewport-relative coords from getBoundingClientRect(). Switch to
   position:fixed so those coords land near the trigger instead of being
   interpreted document-relative (which placed the panel near the top
   of the page once the user scrolled). */
body > .rep-menu-panel{
  position: fixed;
  inset-inline-end: auto;
  margin-top: 0;
}
html[data-theme="light"] .rep-menu-panel{
  /* bg + border now come from --popover / --border; only the shadow is lighter */
  box-shadow: 0 16px 48px rgba(0,0,0,0.14);
}

.rep-menu-form{ margin: 0; }
.rep-menu-item{
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;     /* WCAG 2.5.5: 44 px touch target */
  padding: 10px var(--sp-3);
  background: transparent;
  border: none;
  border-radius: 10px;
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.rep-menu-item:hover,
.rep-menu-item:focus-visible{
  background: rgba(255,255,255,0.06);
  outline: none;
}
html[data-theme="light"] .rep-menu-item:hover,
html[data-theme="light"] .rep-menu-item:focus-visible{
  background: rgba(0,0,0,0.04);
}
.rep-menu-glyph{ font-size: var(--fs-3); line-height: 1; opacity: 0.95; flex-shrink: 0; }
.rep-menu-item.danger{
  color: rgb(248,113,113);
}
.rep-menu-item.danger:hover,
.rep-menu-item.danger:focus-visible{
  background: rgba(var(--danger-rgb), 0.12);
  color: rgb(248,113,113);
}
html[data-theme="light"] .rep-menu-item.danger{ color: rgb(185,28,28); }
html[data-theme="light"] .rep-menu-item.danger:hover,
html[data-theme="light"] .rep-menu-item.danger:focus-visible{
  color: rgb(185,28,28);
  /* `html[data-theme="light"] .rep-menu-item:hover` is (0,3,1) and beats
     `.rep-menu-item.danger:hover` (0,3,0), so hovering a destructive action
     in light theme gave the same neutral grey wash as a benign one — the
     red warning tint never appeared. */
  background: rgba(var(--danger-rgb), 0.10);
}

/* Open-state polish — fade the panel in. The native <details> toggle
   handles the visibility; we just animate the entry. */
details.rep-menu[open] > .rep-menu-panel,
body > .rep-menu-panel{
  animation: rep-menu-in 160ms ease-out;
}
@keyframes rep-menu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Stacking-context fix — each .glass-rep-card establishes its own
   stacking context (backdrop-filter), so a menu panel inside card N
   would otherwise be painted UNDER card N+1 regardless of the panel's
   own z-index. Lift the whole card that holds the open menu above its
   siblings. Pair :has() with a relative + isolation baseline so we
   never depend on source order. */
.glass-rep-card{ isolation: isolate; }
.glass-rep-card:has(details.rep-menu[open]){ z-index: 60; }
/* Bring the actions row of the open card above the next card too —
   the trigger sits in this row and the panel anchors off it. */
.glass-rep-card:has(details.rep-menu[open]) .rep-card-actions{ z-index: 2; }

/* ═══════════════════════════════════════════════════════════════════════
   §48  4-STEP PROGRESS BAR — fixes-merged-02 / Issue 3b
   ═══════════════════════════════════════════════════════════════════════
   Used by admin/buy.php. Four .step-seg pills inside .glass-step-progress,
   each transitioning between default / .active / .done states. The bar is
   purely visual — it tracks progress, it doesn't gate submission. */

.glass-step-progress{
  display: flex;
  gap: 6px;
  padding: var(--sp-2) 10px;
  margin-bottom: 14px;
  background: var(--glass-card-bg);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--r-2);
}
html[data-theme="light"] .glass-step-progress{
  border-color: rgba(0,0,0,0.05);
}
/* fixes-merged-02 / Section C: three states must be VISUALLY DISTINCT.
   The previous revision blended active (purple→green gradient) into done
   (green) — they read as the same colour. Fix: pure tokens.
     - default  low-contrast grey, ~30-40% opacity
     - done     saturated --success green, full opacity
     - active   pure --accent purple, full opacity + inner glow
   A side-by-side done+active row must read as two different colours. */
/* Commit 3 (#52): each segment is now a labelled step — the coloured bar moved
   to ::before and the segment carries its number underneath, so the colours are
   decodable against the numbered section headings. The bar's own geometry and
   the done/active colours are unchanged; only the element carrying them moved. */
.glass-step-progress .step-seg{
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  background: none;          /* the bar is ::before now */
}
.glass-step-progress .step-seg::before{
  content: '';
  display: block;
  width: 100%;
  height: 7px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,0.10);
  transition:
    background 320ms cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 320ms cubic-bezier(0.4, 0, 0.2, 1);
}
html[data-theme="light"] .glass-step-progress .step-seg::before{
  /* Slightly deeper than the dark-theme track: on a white card an 0.10 black
     wash is nearly invisible, so the "not yet reached" steps read as empty. */
  background: rgba(0,0,0,0.13);
}
.glass-step-progress .step-seg.done::before{
  background: rgb(var(--success-rgb));
  box-shadow: 0 0 10px rgba(var(--success-rgb), 0.55);
}
.glass-step-progress .step-seg.active::before{
  /* Pure accent purple (NOT a gradient that bleeds toward green). */
  background: rgb(var(--accent-rgb));
  box-shadow:
    0 0 14px rgba(var(--accent-rgb), 0.70),
    inset 0 0 6px rgba(255,255,255,0.22);
}
/* Light theme — the state fills must be re-declared here, NOT just tuned.
   `html[data-theme="light"] … .step-seg::before` above is (0,3,2) and so
   OUTRANKS `.step-seg.done::before` (0,3,1): without these rules the light
   theme paints every segment with the neutral track and only the blurred
   glow carries colour, which is why done/active read as pale grey smudges.
   Colour choices differ from dark on purpose:
     - fills are the light tokens (5,150,105 / 79,107,255) — both are dark
       enough to hold contrast against the white card, unlike the brighter
       dark-theme tokens;
     - glows are tightened (smaller radius, ~half the alpha). A wide bright
       halo disappears into a light background and just smears the pill edge,
       where on the dark card it reads as a clean emissive glow;
     - the active pill's white inset glow is dropped — on light it bleaches
       the centre of the bar and flattens the accent. */
html[data-theme="light"] .glass-step-progress .step-seg.done::before{
  background: rgb(var(--success-rgb));
  box-shadow: 0 1px 4px rgba(var(--success-rgb), 0.30);
}
html[data-theme="light"] .glass-step-progress .step-seg.active::before{
  background: rgb(var(--accent-rgb));
  box-shadow: 0 1px 6px rgba(var(--accent-rgb), 0.38);
}
.glass-step-progress .step-seg-num{
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  /* 0.40 rendered the not-yet-reached step numbers at 3.61:1 — under AA and
     genuinely hard to read at 12px. 0.55 clears it (5.64:1) while staying
     clearly recessed against the done/active numbers, which sit at 1.0. */
  opacity: 0.55;
  transition: opacity 220ms ease, color 220ms ease;
}
/* Light needs a higher floor than dark for the SAME perceived recession: the
   light ink rgb(29,36,53) on a white card only reaches 3.62:1 at 0.55, where
   the dark ink hits 5.64:1. 0.65 clears AA (4.91:1) and still reads as muted
   next to the done/active numbers at full opacity. */
html[data-theme="light"] .glass-step-progress .step-seg-num{ opacity: 0.65; }
.glass-step-progress .step-seg.done .step-seg-num{ opacity: 1; color: rgb(var(--success-rgb)); }
.glass-step-progress .step-seg.active .step-seg-num{ opacity: 1; color: rgb(var(--accent-rgb)); }
/* Light theme re-inks these from the *-rgb fill tints to the *-text-rgb inks.
   --success-rgb / --accent-rgb are tuned for low-alpha fills and borders; used
   as SOLID TEXT on the white bar they land at 3.77:1 and 4.30:1, both under AA.
   The -text- tokens are the same hues tuned for legible text: 7.13:1 and 7.90:1.
   Dark needs no equivalent — it already measures 7.96:1 and 4.77:1 there, which
   is exactly why this is scoped rather than applied to the base rules.
   Specificity (0,5,1) > the base state rules (0,4,1), and each rule targets its
   OWN state, so neither clobbers the other. */
html[data-theme="light"] .glass-step-progress .step-seg.done .step-seg-num{
  color: rgb(var(--success-text-rgb));
}
html[data-theme="light"] .glass-step-progress .step-seg.active .step-seg-num{
  color: rgb(var(--accent-text-rgb));
}

/* ═══════════════════════════════════════════════════════════════════════
   §49  LEGACY PAGE CLASSES — carried forward from fixes-merged
   ═══════════════════════════════════════════════════════════════════════
   Kept so pages that haven't migrated to the glass system (search,
   payment_methods, settings, plus a handful of helper classes referenced
   by representatives.php / wallet.php / buy.php inline styles) continue
   to render with the new design tokens.
   ═══════════════════════════════════════════════════════════════════════ */

/* search.php's grid container */
.toolbox-grid {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 768px)  { .toolbox-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .toolbox-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* Persian faint helpers — used by wallet.php, buy.php, representatives.php */
.faint       { opacity: 0.7; }
.faint-deep  { opacity: 0.55; }

/* A-E: pre-glass .page-head rule removed — bare .page-head is unused
   in admin/, and this rule was fighting the canonical .glass-page-head
   declaration above when the same element carried both classes. */
.page-title h2 { margin-top: 0; }
.page-title p  { opacity: 0.7; font-size: 0.88rem; margin-top: 4px; }

/* A-F: the .mt-N / .mb-N duplicates were removed — app.css declares the same
   utilities via design tokens. The old glass.css copy used `!important` with
   different magnitudes (14 vs 16), forcing every spacing utility to 14px.
   CSS-UTILS: a `.mt-0 { margin-top: 0 !important }` also sat here under a line
   claiming app.css did not define `.mt-0`. It did; both had zero call sites,
   both are gone. `.text-center` stays — unused too, but a generic helper.
   PARSE BUG, fixed here: line 1 used to spell the comment-close sequence
   (star hard against slash, `.mt-*` joined to `.mb-*`). Comments do not nest,
   so ~790 chars of prose became the next rule's PRELUDE and Blink dropped the
   rule. Never put a star directly against a slash. */
.text-center { text-align: center; }

/* representatives.php — .form-control is referenced by the original
   username input and a few inputs in modals. */
.form-control{
  width: 100%;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: 10px var(--sp-3);
  color: var(--text);
  font-size: 0.9rem;
  font-family: inherit;
}
.form-control:focus{
  outline: none;
  border-color: rgba(var(--accent-rgb), 0.45);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.12);
}
html[data-theme="light"] .form-control{
  background: rgba(0,0,0,0.025);
  border-color: rgba(0,0,0,0.08);
}

/* buy.php — .buy-vol-warn is hidden by default, shown via .show.
   The IIFE at the bottom of buy.php toggles the .show class. */
.glass-alert.warn.buy-vol-warn { display: none; }
.glass-alert.warn.buy-vol-warn.show { display: flex; }

/* buy.php §1 — auto-username glass toggle.
   Compact row: a Persian label + a small square "checkbox" pill. The real
   <input type=checkbox> is visually hidden but stays keyboard/SR-accessible.
   Checkbox is OUTSIDE the <label> in source order so we can drive both the
   label row's hover/focus state AND the sibling illustration block from one
   `:checked` selector. */
.auto-username-check{
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
.auto-username-row{
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px 6px 6px;
  border-radius: var(--r-2);
  cursor: pointer;
  user-select: none;
  color: var(--text);
  font-weight: 600;
  font-size: 0.92rem;
  font-family: inherit;
  transition: background 0.18s ease, color 0.18s ease;
}
.auto-username-row:hover{ background: rgba(255,255,255,0.04); }
.auto-username-text{
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.4;
}
/* ── Commit 3 (#50): a real switch, not a glowing check-square ─────────────
   The control was a 26px rounded square that filled green with a tick + glow —
   it read as a decorative checkbox and its on/off state wasn't obvious. Now a
   track + sliding thumb. RTL note: the thumb rests at the inline-start (right)
   and travels LEFT when on; translateX is physical and this UI is RTL-only.
   Travel = 44 track − 18 thumb − 3 − 3 inset = 20px. Glow deliberately dropped. */
.auto-username-mark{
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 44px; height: 26px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.22);
  flex-shrink: 0;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.auto-username-mark::after{
  content: '';
  position: absolute;
  inset-inline-start: 3px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.35);
  transition: transform 0.2s ease;
}
.auto-username-check:checked ~ .auto-username-row .auto-username-mark{
  background: rgb(var(--success-rgb));
  border-color: rgb(var(--success-rgb));
}
.auto-username-check:checked ~ .auto-username-row .auto-username-mark::after{
  transform: translateX(-20px);
}
/* Visible on/off token — #50 asked for a clear textual state beside the switch. */
.auto-username-state{
  font-size: 0.8rem;
  font-weight: 600;
  opacity: 0.6;
}
.auto-username-state::after{ content: 'خاموش'; }
.auto-username-check:checked ~ .auto-username-row .auto-username-state::after{ content: 'روشن'; }
/* Commit 3 (#51): in auto mode the manual field is already disabled AND ignored
   server-side ($rawUser = $auto ? null : …), so leaving a full-size input on
   screen advertised an editable control that wasn't one — and it dominated the
   section. Collapse it; unchecking the switch restores it in place. */
.auto-username-check:checked ~ .buy-input-wrap,
.auto-username-check:checked ~ .buy-username-hint{ display: none; }
.auto-username-check:focus-visible ~ .auto-username-row .auto-username-mark{
  outline: 2px solid rgba(255,255,255,0.32);
  outline-offset: 2px;
}
/* A white ring is invisible against the white light-theme card, so keyboard
   focus on the auto-username switch simply vanished. Match the accent ring
   the rest of the system uses. Light-scoped so dark is untouched. */
html[data-theme="light"] .auto-username-check:focus-visible ~ .auto-username-row .auto-username-mark{
  outline-color: rgba(var(--accent-rgb), 0.65);
}
/* Manual username input fades while auto is on. */
.form-control.is-auto-locked,
.form-control:disabled{
  opacity: 0.55;
  cursor: not-allowed;
}
html[data-theme="light"] .auto-username-mark{
  background: rgba(0,0,0,0.03);
  border-color: rgba(0,0,0,0.18);
}
html[data-theme="light"] .auto-username-row:hover{ background: rgba(0,0,0,0.03); }

/* buy.php / wallet.php — form-hint utility */
.form-hint{
  font-size: var(--fs-1);
  opacity: 0.7;
  margin: 4px 0 0;
}

/* End of legacy compat block. */


/* ═══════════════════════════════════════════════════════════════════════
   §50  DEFAULT PAGE BACKGROUND
   ═══════════════════════════════════════════════════════════════════════
   The page background is a single flat solid colour — the fixed design
   system. Two decorative predecessors were removed: a three-layer blurred
   radial-gradient "blob" background, and the sudo-configurable per-theme
   background-image / gradient-preset / overlay feature (body.has-app-bg,
   the admin/settings.php "تنظیمات ظاهری" card). Nothing composites behind
   the page any more. */
body{ background-color: var(--bg, #0f1220); }

/* ═══════════════════════════════════════════════════════════════════════
   §51  STICKY MODAL CTA — fixes-merged-02 / Section B (Issue 1)
   ═══════════════════════════════════════════════════════════════════════
   The three modal-submit CTAs that exist in the unmodified files pin to
   the bottom of the scrolling .modal-box so the user never has to scroll
   to discover the submit. All three selectors below match button classes
   ALREADY present in the unpatched files — no markup change required:

     .cta-deposit             admin/wallet.php  top-up submit
     .wallet-adjust-submit    admin/representatives.php  adjust modal submit
     .cta-buy                 admin/buy.php  (not in a modal — it's a
                              page-level form. Mobile sticky already exists
                              via the RULE-8 sticky-CTA rule at the bottom
                              of the file. Excluded here.)

   Forward-compatible: a future <div class="modal-actions"> wrapper —
   if and when it ships — gets the full glass-strip treatment via the
   trailing rule. Both forms coexist; the wrapper is optional.

   NOTE (rev 4): §53 below narrows the sticky CTA to a centred pill
   (max-width 280/320 px). The sticky position + box-shadow below still
   applies (no backdrop-filter on this rule any more); only the width
   is overridden.
   ═══════════════════════════════════════════════════════════════════════ */

.modal-overlay.glass-modal .modal-box .btn-glass.cta-deposit,
.modal-overlay.glass-modal .modal-box .btn-glass.wallet-adjust-submit{
  position: sticky;
  bottom: 0;
  z-index: 8;
  /* Hairline above + soft drop below + inset highlight along the top
     edge. Together these read as a pinned glass bar that "catches light"
     where it meets the scrolling content above. */
  box-shadow:
    0 -1px 0 0 rgba(255,255,255,0.10),
    0 -14px 28px -10px rgba(0,0,0,0.55);
}
/* PERF: split out of the combined rule above — .modal-actions is a
   repeated generic button-row wrapper (not a single sticky CTA). Neither
   this rule nor the two CTA buttons above carry backdrop-filter any
   more; the box-shadow alone does the "pinned bar" visual work now.
   Same position/box-shadow so that visual is unchanged. */
.modal-overlay.glass-modal .modal-box .modal-actions{
  position: sticky;
  bottom: 0;
  z-index: 8;
  box-shadow:
    0 -1px 0 0 rgba(255,255,255,0.10),
    0 -14px 28px -10px rgba(0,0,0,0.55);
}
html[data-theme="light"] .modal-overlay.glass-modal .modal-box .btn-glass.cta-deposit,
html[data-theme="light"] .modal-overlay.glass-modal .modal-box .btn-glass.wallet-adjust-submit,
html[data-theme="light"] .modal-overlay.glass-modal .modal-box .modal-actions{
  box-shadow:
    0 -1px 0 0 rgba(0,0,0,0.08),
    0 -14px 28px -10px rgba(0,0,0,0.14);
}
html[data-theme="light"] .modal-overlay.glass-modal .modal-box .btn-glass.cta-deposit{
  /* Light theme — slightly lower alpha so the green stays bright on white. */
  background-color: rgba(var(--success-rgb), 0.42);
}
html[data-theme="light"] .modal-overlay.glass-modal .modal-box .btn-glass.wallet-adjust-submit,
html[data-theme="light"] .modal-overlay.glass-modal .modal-box .btn-glass.cta-buy{
  background-color: rgba(var(--accent-rgb), 0.42);
}

/* .modal-actions — forward-compatible wrapper. If a future patch wraps
   the modal submit in <div class="modal-actions">, this rule turns it
   into a full-width frosted strip spanning the modal-box edges. Optional
   in this package — see patches/wallet.php.md for the recommended
   minimal markup change. */
.modal-overlay.glass-modal .modal-box .modal-actions{
  display: flex;
  gap: 10px;
  align-items: stretch;
  /* Cancel the modal-box's 24px inner padding so the strip touches the side
     borders. UI-0044: that 24px is now the canonical .modal-box padding
     defined in §9 above (it previously referenced a non-existent app.css
     rule, so this -24px was cancelling padding that did not exist). */
  margin: 0 -24px;
  padding: var(--sp-3-5) var(--sp-6) max(var(--sp-3-5), env(safe-area-inset-bottom, var(--sp-3-5)));
  background: rgba(18,20,30,0.72);
  border-top: 1px solid rgba(255,255,255,0.08);
}
html[data-theme="light"] .modal-overlay.glass-modal .modal-box .modal-actions{
  background: rgba(255,255,255,0.82);
  border-top-color: rgba(0,0,0,0.06);
}
.modal-overlay.glass-modal .modal-box .modal-actions > .btn-glass{ flex: 1; }

/* UI-0158: the deposit-review popup is short and never scrolls, so the sticky
   frosted-strip treatment above reads as a stray bar floating mid-box. Reset
   it to a plain button row. Moved here from an inline <style> in
   admin/deposits.php (the page <style> loaded after glass.css and silently
   overrode the strip); the ID selector keeps the same specificity/behavior. */
#modal-deposit-review .modal-actions{
  position: static;
  display: flex;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  background: transparent;
  border-top: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
#modal-deposit-review .modal-actions > .btn-glass{ flex: 1; }

/* ═══════════════════════════════════════════════════════════════════════
   §52  REDUCED-TRANSPARENCY / NO-BACKDROP-FILTER FALLBACK
   ═══════════════════════════════════════════════════════════════════════
   Disables blur + blobs and collapses surfaces to opaque tinted versions.
   Form factor + dimensions unchanged — only the glass effect is gone.
   ═══════════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-transparency: reduce) {
  .glass-card, .glass-stat-card, .glass-page-head, .glass-info-bar,
  .glass-form-section, .glass-balance-card, .glass-method-card,
  .glass-deposit-row, .glass-tx-table, .glass-rep-card, .rep-menu-panel,
  .filters-glass, .btn-glass, .glass-bottom-nav, .glass-amount-input,
  .glass-textarea, .glass-step-progress, .glass-stepper .stepper-input,
  .modal-overlay.glass-modal .modal-box,
  .modal-overlay.glass-modal .modal-box .btn-glass.cta-deposit,
  .modal-overlay.glass-modal .modal-box .btn-glass.wallet-adjust-submit,
  .modal-overlay.glass-modal .modal-box .modal-actions{
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }

  .glass-card, .glass-stat-card, .glass-page-head, .glass-info-bar,
  .glass-form-section, .glass-balance-card, .glass-method-card,
  .glass-deposit-row, .glass-rep-card, .filters-glass, .glass-bottom-nav,
  .modal-overlay.glass-modal .modal-box, .rep-menu-panel{
    background: var(--elevated, #111322) !important;
  }
  html[data-theme="light"] .glass-card,
  html[data-theme="light"] .glass-stat-card,
  html[data-theme="light"] .glass-page-head,
  html[data-theme="light"] .glass-info-bar,
  html[data-theme="light"] .glass-form-section,
  html[data-theme="light"] .glass-balance-card,
  html[data-theme="light"] .glass-method-card,
  html[data-theme="light"] .glass-deposit-row,
  html[data-theme="light"] .glass-rep-card,
  html[data-theme="light"] .filters-glass,
  html[data-theme="light"] .glass-bottom-nav,
  html[data-theme="light"] .modal-overlay.glass-modal .modal-box,
  html[data-theme="light"] .rep-menu-panel{
    background: #ffffff !important;
  }
}

/* Browsers that don't support backdrop-filter at all (older Edge,
   Firefox < 103 without flag). Same opaque collapse. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass-card, .glass-stat-card, .glass-page-head, .glass-info-bar,
  .glass-form-section, .glass-balance-card, .glass-method-card,
  .glass-deposit-row, .glass-rep-card, .filters-glass, .glass-bottom-nav,
  .modal-overlay.glass-modal .modal-box, .rep-menu-panel{
    background: var(--elevated, #111322);
  }
}

/* ═════════════════════════════════════════════════════════════════
   §53  STICKY MODAL CTA — CENTRED PILL  (narrows §51's full-bleed)
   ═════════════════════════════════════════════════════════════════
   The button keeps its §30 / §22 design (green/indigo gradient + glow)
   and just gets a narrower footprint so the sticky CTA reads as a
   centred pill rather than a full-bleed bar. Width caps at 280 px on
   phones and 320 px on tablet+. §51's sticky-position + heavy blur
   scaffolding still applies — the centred pill just sits on top of
   the scrolling content.
   ═════════════════════════════════════════════════════════════════ */
.modal-overlay.glass-modal .modal-box .btn-glass.cta-deposit,
.modal-overlay.glass-modal .modal-box .btn-glass.wallet-adjust-submit,
.modal-overlay.glass-modal .modal-box .btn-glass.cta-buy{
  width: auto;
  max-width: 320px;
  margin-left: auto;
  margin-right: auto;
  display: block;
}
@media (max-width: 480px){
  .modal-overlay.glass-modal .modal-box .btn-glass.cta-deposit,
  .modal-overlay.glass-modal .modal-box .btn-glass.wallet-adjust-submit,
  .modal-overlay.glass-modal .modal-box .btn-glass.cta-buy{
    max-width: 280px;
  }
}

/* ─────────────────────────────────────────────────────────────────
   §53b  TOP-UP CTA — FULL-WIDTH (overrides §53's centred pill)
   ─────────────────────────────────────────────────────────────────
   In admin/wallet.php's top-up modal the submit ("ثبت درخواست") sat
   next to full-width controls (select / amount / receipt / note), so
   the §53 centred pill read as undersized + off-balance against them.
   Restore the §30 full-bleed footprint so the CTA matches the form
   controls above it on phone + desktop alike. Scoped to #modal-topup
   (the only .cta-deposit usage) — the buy/adjust modal pills keep §53.
   The §51 sticky-bottom + frosted-bar scaffolding still applies, so
   this reads as a full-width sticky submit bar. The ID selector
   outranks §53's class chains on every breakpoint — no media query
   needed. */
#modal-topup .btn-glass.cta-deposit{
  display: flex;            /* re-enable .btn-glass's centring of the label */
  width: 100%;
  max-width: none;
  margin-left: 0;
  margin-right: 0;
}

/* ═════════════════════════════════════════════════════════════════
   §54  AMBIENT COLOUR ACCENTS ON GLASS SURFACES
   ═════════════════════════════════════════════════════════════════
   A barely-saturated radial wash on the main glass containers so the
   surfaces read as cooler/warmer rather than flat-white-on-flat-dark.
   The wash is layered as a background-image, leaving the existing
   solid background-color in place — so no surface loses contrast.
   Dark theme leans into the indigo/violet brand colour at low alpha;
   light theme uses indigo + a touch of cyan so the surfaces don't go
   muddy on the cream-coloured app background.
   ═════════════════════════════════════════════════════════════════ */
/* Phase 5 stage 2: REMOVED the decorative purple/indigo/cyan radial
   background-image tints that every card + the modal carried — this was the
   "white cards carry purple/teal tints" washed-out complaint. Cards are now
   flat solid surfaces (--glass-card-bg / --card / --popover); structure comes
   from borders, color only from primary/status/data. */
/* Reduced-transparency override — flatten back to the elevated solid. */
@media (prefers-reduced-transparency: reduce){
  .glass-card, .glass-rep-card, .glass-form-section, .glass-method-card,
  .glass-info-bar, .glass-page-head, .filters-glass, .glass-balance-card,
  .glass-deposit-row, .modal-overlay.glass-modal .modal-box{
    background-image: none !important;
  }
}

/* ═════════════════════════════════════════════════════════════════
   §55  INFO-BANNER NARROW — modal-scoped
   ═════════════════════════════════════════════════════════════════
   The .glass-alert.info banner inside a modal was stretching the full
   modal width, which made the modal feel visually wider than it should.
   Cap its width + centre so the banner reads as a contained pill
   sitting ABOVE the form rather than as a full-bleed strip. Scoped to
   .modal-box so the same alert on regular pages is unchanged.
   ═════════════════════════════════════════════════════════════════ */
.modal-overlay.glass-modal .modal-box > .glass-alert.info,
.modal-overlay.glass-modal .modal-box form > .glass-alert.info{
  max-width: 360px;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

/* End of fixes-merged-02 additions. */

/* ═══════════════════════════════════════════════════════════════════
   §56  AUTH REDESIGN  (login.php — body.auth-redesign)
   ───────────────────────────────────────────────────────────────────
   Scoped under .auth-redesign so it cannot affect any other surface.
   Layout:
     <main.auth-shell>
       <header.auth-hero>  ← sparkle + brand + subtitle (above card)
       <section.glass-auth-card.auth-card>
         <h2.auth-card-title>
         [<glass-alert>?]
         <form.auth-form>
           <.auth-field>
             <label>
             <.auth-field-row>
               <.auth-icon-chip>
               <.auth-input-wrap[.has-eye]>
                 <input.auth-input>
                 [<button.auth-eye-btn>]
         <.auth-cta>
         <.auth-divider>
         <a.auth-secondary-cta>
         <p.auth-hint>
   ═══════════════════════════════════════════════════════════════════ */

body.auth-page.auth-redesign{
  display: block;
  padding: 0;
  min-height: 100vh;
  /* Slightly deeper background so the radial blobs read with more contrast */
  background: var(--auth-bg);
}
body.auth-page.auth-redesign::before{
  /* Replace the default 3-blob field with a calmer two-blob composition.
     The blobs are positioned so the hero gets a faint top-left lift and
     the card sits in front of a soft accent halo. */
  background:
    radial-gradient(46% 38% at 18% 22%,  var(--auth-blob1),  transparent 65%),
    radial-gradient(42% 34% at 82% 78%,  var(--auth-blob2),  transparent 65%),
    radial-gradient(58% 40% at 50% 100%, var(--auth-blob3),  transparent 70%);
  filter: blur(8px);
}

.auth-redesign .auth-shell{
  position: relative;
  z-index: 1;
  min-height: 100vh;
  width: 100%;
  max-width: 480px;
  margin-inline: auto;
  padding: clamp(var(--sp-6), 5vh, 56px) clamp(var(--sp-4), 4vw, 28px);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: clamp(var(--sp-6), 4vh, 40px);
}

/* ─── Floating theme toggle (auth-only positioning) ─────────────── */
.auth-redesign .auth-theme-toggle{
  position: fixed;
  top: 16px;
  inset-inline-end: 16px;
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-2);
  background: var(--toggle-btn-bg, rgba(255,255,255,0.08));
  border: 1px solid var(--auth-card-border, rgba(255,255,255,0.10));
  cursor: pointer;
  font-size: 16px;
  color: var(--text);
  transition: transform .18s var(--ease), border-color .18s var(--ease);
  z-index: 5;
}
.auth-redesign .auth-theme-toggle:hover{
  transform: translateY(-1px);
  border-color: rgba(255,255,255,0.20);
}
/* UI-0339: on very narrow screens (≤380px) the fixed toggle crowded the
   centered hero. Pull it tighter into the corner and shrink it so it stays
   clear of the hero icon/title. */
@media (max-width: 380px){
  .auth-redesign .auth-theme-toggle{
    top: 8px;
    inset-inline-end: 8px;
    width: 34px; height: 34px;
    font-size: 14px;
  }
}

/* ─── Hero (outside the card) ───────────────────────────────────── */
.auth-redesign .auth-hero{
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding-top: 8px;
}
.auth-redesign .auth-hero-icon{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px; height: 56px;
  color: var(--accent-2);
  filter:
    drop-shadow(0 0 14px rgba(var(--accent-rgb), 0.55))
    drop-shadow(0 0 28px rgba(var(--accent-rgb), 0.25));
  margin-bottom: 4px;
}
.auth-redesign .auth-hero-title{
  font-size: clamp(2rem, 6.5vw, 2.6rem);
  font-weight: 800;
  letter-spacing: 0.5px;
  line-height: 1.1;
  color: var(--text);
  margin: 0;
}
.auth-redesign .auth-hero-sub{
  font-size: var(--fs-2);
  color: var(--text-m);
  opacity: 0.85;
  margin: 0;
}

/* ─── Card overrides ────────────────────────────────────────────── */
.auth-redesign .auth-card{
  max-width: 100%;
  padding: clamp(22px, 4vw, 30px) clamp(var(--sp-5), 4vw, 28px);
  border-radius: var(--r-5);
  /* Softer, lighter glass than the default to match the target screenshot */
  background:
    linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02)),
    var(--auth-card, rgba(20,22,32,0.55));
  border: 1px solid rgba(255,255,255,0.10);
  box-shadow:
    0 24px 64px rgba(0,0,0,0.55),
    inset 0 1px 0 rgba(255,255,255,0.08);
}
.auth-redesign .auth-card::after{
  /* Slightly larger, slightly cooler halo than default */
  inset: -56px;
  background:
    radial-gradient(60% 50% at 20% 20%,  rgba(var(--accent-rgb), 0.22), transparent 60%),
    radial-gradient(50% 50% at 85% 85%,  rgba(var(--accent-rgb), 0.12),  transparent 65%);
  filter: blur(50px);
}

.auth-redesign .auth-card-title{
  text-align: center;
  font-size: var(--fs-5);
  font-weight: 800;
  letter-spacing: 0.2px;
  color: var(--text);
  margin: 6px 0 20px;
}

/* ─── Form rows ─────────────────────────────────────────────────── */
.auth-redesign .auth-form{ display: flex; flex-direction: column; gap: var(--sp-4); }

.auth-redesign .auth-field{ display: flex; flex-direction: column; gap: var(--sp-2); }
.auth-redesign .auth-field > label{
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text-m);
  /* Labels right-aligned in RTL — matches target */
  text-align: start;
  padding-inline-start: 4px;
}
.auth-redesign .auth-field > label .faint{ color: var(--text-d); font-weight: 400; }

.auth-redesign .auth-field-row{
  display: flex;
  align-items: stretch;
  gap: 10px;
  min-height: 52px;
}

/* Icon "chip" — small glass square that prefixes each input (visually on
   the right in RTL because it's the first flex child). */
.auth-redesign .auth-icon-chip{
  flex: 0 0 52px;
  width: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: 14px;
  color: var(--text-m);
  transition: color .18s var(--ease), border-color .18s var(--ease), background .18s var(--ease);
}
.auth-redesign .auth-field-row:focus-within .auth-icon-chip{
  color: var(--accent-2);
  border-color: rgba(var(--accent-rgb), 0.45);
  background: rgba(var(--accent-rgb), 0.10);
}

.auth-redesign .auth-input-wrap{
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
}
.auth-redesign .auth-input{
  width: 100%;
  min-height: 52px;
  padding: 0 var(--sp-4);
  border-radius: 14px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.10);
  color: var(--text);
  font: inherit;
  font-size: 0.98rem;
  outline: none;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease), background .18s var(--ease);
}
.auth-redesign .auth-input::placeholder{
  color: var(--text-d);
  /* Persian placeholder shouldn't fight the dir="ltr" of the actual value */
  direction: rtl; text-align: start;
  opacity: 0.85;
}
/* H1.15: inside a .has-eye wrap the placeholder must flow LTR so it
   starts at the input's leading edge and never overlaps the eye toggle
   sitting on the right (inline-start in an RTL page). The password
   placeholder is a row of dots, so direction has no aesthetic impact;
   it only changes which edge the row begins from. */
.auth-redesign .has-eye .auth-input::placeholder{
  direction: ltr;
  text-align: start;
}
/* UI-0045: an email placeholder (example@email.com) carries neutral chars
   (@ .) that the RTL default reorders into gibberish; flow it LTR so it
   reads correctly. The field is already dir="ltr". */
.auth-redesign input[type="email"]::placeholder{
  direction: ltr;
  text-align: start;
}
.auth-redesign .auth-input:focus{
  border-color: rgba(var(--accent-rgb), 0.55);
  background: rgba(255,255,255,0.06);
  box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.15);
}
/* Eye-toggle slot: extra padding so the value never collides with the
   absolutely-positioned eye button.
   UI-0054: 44px touch target, so the slot is 52px.

   FIXED: this reserved the padding on the input's LEADING edge. The comment
   above it claimed "in RTL the leading edge is the left" — but the input is
   dir="ltr", so `padding-inline-start` resolved against the INPUT's own LTR
   direction = physical LEFT, while the button resolved `inset-inline-start`
   against the RTL wrap = physical RIGHT. Two consequences, both reported:
     1. the value collided with the eye button on the right, which had no
        padding reserved at all;
     2. the 52px indent sat on the leading edge, so the password caret started
        36px further in than every other field on the form — the "cursor is
        not consistent across fields" complaint.
   Reserving on the TRAILING edge fixes both: the slot is now on the same
   physical side as the button, and the leading padding falls back to the
   shared 16px so all fields start their text at the same x. */
.auth-redesign .has-eye .auth-input{ padding-inline-end: 52px; }

/* Pinned to ltr for the same reason as .pw-wrap: the input inside is dir="ltr",
   so every logical property in this subtree now resolves the same way the
   input's own padding does. Without the pin, `inset-inline-start` here meant
   physical RIGHT (resolved against the RTL page) while
   `padding-inline-start` on the input meant physical LEFT — the opposite side.
   The input carries its own dir="ltr", so this does not change how any
   placeholder or value is aligned; it only fixes where the button lands. */
.auth-redesign .auth-input-wrap.has-eye{ direction: ltr; }
.auth-redesign .auth-eye-btn{
  position: absolute;
  inset-block: 0;
  inset-inline-end: 6px;
  width: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-m);
  cursor: pointer;
  border-radius: 10px;
  transition: color .15s var(--ease), background .15s var(--ease);
}
.auth-redesign .auth-eye-btn:hover{
  color: var(--text);
  background: rgba(255,255,255,0.06);
}
.auth-redesign .auth-eye-btn:focus-visible{
  outline: 2px solid rgba(var(--accent-rgb), 0.45);
  outline-offset: 1px;
}

/* ─── Primary CTA (login / register submit) ─────────────────────────
   Dual-background gradient ring: fill painted to `padding-box`, then a
   second gradient painted to `border-box` shows through a transparent
   border. Result: a clearly visible purple→cyan rim that matches the
   target without relying on a faint 1-px solid. */
.auth-redesign .btn-glass.auth-cta{
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 54px;
  margin-top: 8px;
  padding: var(--sp-3-5) var(--sp-5);
  border-radius: var(--r-3);
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: #ffffff;
  cursor: pointer;
  border: 1.5px solid transparent;
  background:
    linear-gradient(180deg,
      rgba(var(--accent-rgb),0.30),
      rgba(var(--accent-rgb),0.14)) padding-box,
    linear-gradient(135deg,
      rgba(var(--accent-rgb),0.95) 0%,
      rgba(var(--accent-rgb),0.65) 100%) border-box;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.20),
    0 14px 36px rgba(var(--accent-rgb),0.30);
  transition: transform .18s var(--ease),
              box-shadow .18s var(--ease),
              filter .18s var(--ease);
}
.auth-redesign .btn-glass.auth-cta::before{
  content: "";
  position: absolute;
  inset: -8px;
  border-radius: 22px;
  background: radial-gradient(60% 80% at 50% 50%,
    rgba(var(--accent-rgb),0.55), transparent 70%);
  filter: blur(26px);
  opacity: 0.55;
  z-index: -1;
  pointer-events: none;
  transition: opacity .18s var(--ease);
}
.auth-redesign .btn-glass.auth-cta:hover{
  transform: translateY(-1px);
  filter: brightness(1.06);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.24),
    0 18px 44px rgba(var(--accent-rgb),0.36);
}
.auth-redesign .btn-glass.auth-cta:hover::before{ opacity: 0.9; }
.auth-redesign .btn-glass.auth-cta:active{ transform: translateY(0); }
.auth-redesign .btn-glass.auth-cta:focus-visible{
  outline: 2px solid rgba(var(--accent-rgb),0.55);
  outline-offset: 3px;
}
.auth-redesign .btn-glass.auth-cta svg{
  /* Arrow naturally points right; in RTL we want it to read as "forward"
     toward the leading edge, so flip horizontally. */
  transform: scaleX(-1);
  opacity: 0.95;
}

/* ─── "یا" divider ──────────────────────────────────────────────── */
.auth-redesign .auth-divider{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 22px 0 16px;
  color: var(--text-d);
  font-size: 0.85rem;
}
.auth-redesign .auth-divider::before,
.auth-redesign .auth-divider::after{
  content: "";
  flex: 1;
  height: 1px;
  /* A-A: `to inline-start` is invalid CSS — the <side-or-corner> grammar
     accepts only physical keywords. Use `to left` to match the existing
     `.glass-divider` pattern at lines 497/501. */
  background: linear-gradient(to left, transparent, rgba(255,255,255,0.14), transparent);
}
.auth-redesign .auth-divider > span{ padding: 0 var(--sp-1); }

/* ─── Secondary CTA (sign-up link box) ──────────────────────────── */
.auth-redesign .auth-secondary-cta{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 52px;
  padding: var(--sp-3) var(--sp-4-5);
  border-radius: 14px;
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.10);
  color: var(--text-m);
  text-decoration: none;
  font-size: var(--fs-2);
  font-weight: 500;
  transition: background .18s var(--ease), border-color .18s var(--ease), color .18s var(--ease), transform .18s var(--ease);
}
.auth-redesign .auth-secondary-cta:hover{
  background: rgba(255,255,255,0.06);
  border-color: rgba(var(--accent-rgb), 0.45);
  color: var(--text);
  transform: translateY(-1px);
}
.auth-redesign .auth-secondary-cta strong{
  color: var(--accent-3);
  font-weight: 700;
  margin-inline-start: 4px;
}
.auth-redesign .auth-secondary-cta svg{
  color: var(--text-d);
  flex-shrink: 0;
}

/* ─── Helper hint ───────────────────────────────────────────────── */
.auth-redesign .auth-hint{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: 14px;
  font-size: 0.8rem;
  color: var(--text-d);
  text-align: center;
}
.auth-redesign .auth-hint svg{ flex-shrink: 0; opacity: 0.8; }

/* ─── Light-theme adjustments ───────────────────────────────────── */
html[data-theme="light"] body.auth-page.auth-redesign{
  background: var(--auth-bg);
}
html[data-theme="light"] .auth-redesign .auth-card{
  background:
    linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.78)),
    var(--auth-card);
  border-color: var(--auth-card-border);
  box-shadow:
    0 18px 56px rgba(40,40,60,0.10),
    inset 0 1px 0 rgba(255,255,255,0.40);
}
html[data-theme="light"] .auth-redesign .auth-icon-chip,
html[data-theme="light"] .auth-redesign .auth-input,
html[data-theme="light"] .auth-redesign .auth-secondary-cta{
  background: #ffffff;
  border-color: var(--auth-input-border);
  color: var(--text);
}
html[data-theme="light"] .auth-redesign .auth-icon-chip{ color: var(--text-m); }
html[data-theme="light"] .auth-redesign .auth-input:focus{
  background: #ffffff;
  border-color: rgba(var(--accent-rgb), 0.65);
}
html[data-theme="light"] .auth-redesign .auth-divider::before,
html[data-theme="light"] .auth-redesign .auth-divider::after{
  /* A-A: see note above. */
  background: linear-gradient(to left, transparent, rgba(0,0,0,0.12), transparent);
}
html[data-theme="light"] .auth-redesign .btn-glass.auth-cta{
  color: #ffffff;
  /* Solid coloured fill on light theme — the gradient ring would
     disappear against a white card. The fill itself carries the brand
     gradient instead. */
  border: 1.5px solid transparent;
  background:
    linear-gradient(135deg,
      rgba(var(--accent-rgb), 0.95),
      rgba(var(--accent-rgb), 0.72) 100%) padding-box,
    linear-gradient(135deg,
      rgba(var(--accent-rgb), 1),
      rgba(var(--accent-rgb), 0.7) 100%) border-box;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.28),
    0 10px 28px rgba(var(--accent-rgb), 0.32);
}
html[data-theme="light"] .auth-redesign .btn-glass.auth-cta::before{
  background: radial-gradient(60% 80% at 50% 50%,
    rgba(var(--accent-rgb), 0.32), transparent 70%);
  opacity: 0.5;
}

/* ─── Small screens ─────────────────────────────────────────────── */
@media (max-width: 380px){
  .auth-redesign .auth-icon-chip{ flex-basis: 46px; width: 46px; }
  .auth-redesign .auth-field-row{ gap: var(--sp-2); min-height: 48px; }
  .auth-redesign .auth-input{ min-height: 48px; padding: 0 var(--sp-3); font-size: 0.94rem; }
  /* Must clear the eye button, which is NOT narrowed at this breakpoint: it
     stays a 44px touch target at a 6px inset = 50px of occupied space. The old
     40px predates UI-0054 widening the button, so the value overlapped it by
     10px here even after the desktop slot was corrected. This rule only exists
     because the `.auth-input{ padding: 0 var(--sp-3) }` shorthand above resets
     both sides, so the trailing slot has to be re-applied. */
  .auth-redesign .has-eye .auth-input{ padding-inline-end: 52px; }
  .auth-redesign .btn-glass.auth-cta{ min-height: 50px; font-size: 0.98rem; }
  .auth-redesign .auth-hero-title{ font-size: 1.9rem; }
}

/* Respect reduced-motion */
@media (prefers-reduced-motion: reduce){
  .auth-redesign .btn-glass.auth-cta,
  .auth-redesign .auth-secondary-cta,
  .auth-redesign .auth-icon-chip,
  .auth-redesign .auth-input{ transition: none; }
  .auth-redesign .btn-glass.auth-cta:hover,
  .auth-redesign .auth-secondary-cta:hover{ transform: none; }
}
/* End of §56 — Auth redesign. */

/* ───────────────────────── 57. Toast system ──────────────────────────────
   Container is created on first Toast.show() (assets/app.js). Sits above
   modal overlays (modal = 1000, theme-fab = 1100) so notifications stay
   visible while a modal is open. RTL: anchors to inline-end so the toast
   slides in from the same edge in both directions. */
#toast-container{
  position: fixed;
  inset-block-start: max(16px, env(safe-area-inset-top, 16px));
  inset-inline-end:  max(16px, env(safe-area-inset-right, 16px));
  z-index: 1200;
  display: flex;
  flex-direction: column;
  gap: 10px;
  pointer-events: none;
  max-width: min(360px, calc(100vw - 32px));
}
.toast{
  pointer-events: auto;
  /* UI-0287: flex row so the message and the dismiss button sit side by side. */
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-2);
  background: rgba(20,22,32,0.85);
  border: 1px solid rgba(255,255,255,0.10);
  box-shadow: 0 12px 32px rgba(0,0,0,0.45);
  color: var(--text);
  font-size: var(--fs-2);
  line-height: 1.5;
  animation: pg-toast-in 0.22s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  will-change: transform, opacity;
}
/* UI-0287: dismiss button — lets error toasts (which now linger longer) be
   closed immediately instead of waiting out the timer. */
.toast-msg{ flex: 1 1 auto; min-width: 0; }
.toast-close{
  flex: 0 0 auto;
  background: transparent;
  border: none;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
  font-size: var(--fs-3);
  line-height: 1;
  padding: 2px var(--sp-1);
  margin: -2px -4px -2px 0;
  border-radius: 6px;
}
.toast-close:hover{ opacity: 1; }
.toast-close:focus-visible{ outline: 2px solid rgba(255,255,255,0.4); outline-offset: 1px; opacity: 1; }
/* Same white-on-white focus loss: the light toast is a near-white panel. */
html[data-theme="light"] .toast-close:focus-visible{ outline-color: rgba(var(--accent-rgb), 0.65); }
.toast.info   { border-color: rgba(var(--accent-rgb),  0.45); box-shadow: 0 12px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(var(--accent-rgb),  0.25); }
.toast.success{ border-color: rgba(var(--success-rgb), 0.45); box-shadow: 0 12px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(var(--success-rgb), 0.25); }
.toast.warn   { border-color: rgba(var(--warn-rgb),    0.45); box-shadow: 0 12px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(var(--warn-rgb),    0.25); }
.toast.error  { border-color: rgba(var(--danger-rgb),  0.45); box-shadow: 0 12px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(var(--danger-rgb),  0.25); }
.toast.hide{
  animation: pg-toast-out 0.28s cubic-bezier(0.4, 0, 0.6, 1) both;
}
@keyframes pg-toast-in{
  from{ opacity: 0; transform: translateX(12px) translateY(-4px); }
  to  { opacity: 1; transform: translateX(0)   translateY(0);    }
}
@keyframes pg-toast-out{
  from{ opacity: 1; transform: translateX(0)   translateY(0);    }
  to  { opacity: 0; transform: translateX(12px) translateY(-4px); }
}
html[data-theme="light"] .toast{
  background: rgba(255,255,255,0.92);
  border-color: rgba(0,0,0,0.08);
  color: var(--text);
  /* 0.45 black at 32px blur is tuned for a dark canvas; on the light page
     it reads as a dirty smudge under the toast. */
  box-shadow: 0 12px 32px rgba(0,0,0,0.16);
}
/* Severity borders MUST be restated: the rule above is (0,2,1) and outranks
   .toast.info/.success/.warn/.error (0,2,0), so in light theme all four
   severities rendered with the same neutral grey edge and the colour coding
   — the toast's only at-a-glance severity signal — was lost. The 1px
   box-shadow ring did survive, but it sits outside a grey border and is far
   too weak to carry the distinction on its own. */
html[data-theme="light"] .toast.info{
  border-color: rgba(var(--accent-rgb), 0.55);
  box-shadow: 0 12px 32px rgba(0,0,0,0.16), 0 0 0 1px rgba(var(--accent-rgb), 0.20);
}
html[data-theme="light"] .toast.success{
  border-color: rgba(var(--success-rgb), 0.55);
  box-shadow: 0 12px 32px rgba(0,0,0,0.16), 0 0 0 1px rgba(var(--success-rgb), 0.20);
}
html[data-theme="light"] .toast.warn{
  border-color: rgba(var(--warn-rgb), 0.55);
  box-shadow: 0 12px 32px rgba(0,0,0,0.16), 0 0 0 1px rgba(var(--warn-rgb), 0.20);
}
html[data-theme="light"] .toast.error{
  border-color: rgba(var(--danger-rgb), 0.55);
  box-shadow: 0 12px 32px rgba(0,0,0,0.16), 0 0 0 1px rgba(var(--danger-rgb), 0.20);
}
@media (prefers-reduced-motion: reduce){
  .toast, .toast.hide{ animation-duration: 0.001s; }
}
/* End of §57 — Toast system. */

/* ═══════════════════════════════════════════════════════════════════════
   §58  BODY SCROLL LOCK + INERT BACKGROUND (H1.4, H1.9)
   ═══════════════════════════════════════════════════════════════════════
   When any glass-modal is open, app.js toggles body.modal-open. We freeze
   page scroll behind the modal on Android Chrome + iOS Safari, and also
   knock the theme-FAB out of the way so it cannot float above the overlay
   (H1.2). The actual focus trap lives in app.js. */
body.modal-open{
  /* UI-0038: keep the scroll-lock (overflow:hidden + overscroll-behavior)
     but DROP `touch-action: none` — that disabled pinch-zoom for the whole
     body, including modal content, so operators couldn't zoom a deposit
     receipt to verify it on iOS Safari. overflow:hidden already prevents the
     page behind from scrolling. */
  overflow: hidden;
  overscroll-behavior: contain;
}
/* Background siblings are flagged with [inert] from app.js. Belt-and-
   suspenders: also hide focus rings from anything inside an inert subtree
   so the visible focus indicator never lives behind the overlay. */
[inert], [inert] *{
  user-select: none;
  -webkit-user-select: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   §59  GLASS CONFIRM MODAL (H1.10, H6.3)
   ═══════════════════════════════════════════════════════════════════════
   A single reusable modal that replaces window.confirm() for every
   data-confirm action. app.js owns the open/close lifecycle and wires
   the destructive variant. Markup is injected once into <body>; the
   selectors below style that template. The host element is a regular
   .modal-overlay.glass-modal so §9 + §58 already cover scroll-lock and
   the bottom-sheet treatment on mobile. */
#glass-confirm-modal .modal-box{
  width: min(440px, calc(100vw - 32px));
  padding: 22px var(--sp-6);
  gap: var(--sp-4);
}
@media (max-width: 768px){
  #glass-confirm-modal .modal-box{
    width: 100%;
    padding: var(--sp-5) var(--sp-5) max(var(--sp-5), env(safe-area-inset-bottom, var(--sp-5)));
  }
}
.glass-confirm-icon{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 14px;
  font-size: var(--fs-5);
  background: rgba(var(--warn-rgb), 0.14);
  border: 1px solid rgba(var(--warn-rgb), 0.35);
  margin-bottom: 4px;
  flex-shrink: 0;
}
#glass-confirm-modal.is-danger .glass-confirm-icon{
  background: rgba(var(--danger-rgb), 0.14);
  border-color: rgba(var(--danger-rgb), 0.40);
}
.glass-confirm-title{
  margin: 0;
  font-size: var(--fs-3);
  font-weight: 700;
  line-height: 1.4;
  color: var(--text);
  /* Persian text often runs longer than English — let it wrap. */
  word-break: break-word;
}
.glass-confirm-message{
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.65;
  color: var(--text-m, var(--text));
  opacity: 0.85;
  word-break: break-word;
}
.glass-confirm-actions{
  display: flex;
  gap: 10px;
  margin-top: 6px;
  flex-wrap: wrap;
}
.glass-confirm-actions > .btn-glass{
  flex: 1 1 0;
  min-width: 0;
  min-height: 46px;
}
@media (max-width: 380px){
  .glass-confirm-actions{ flex-direction: column-reverse; }
  .glass-confirm-actions > .btn-glass{ width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   §60  REP MENU — BUTTON CONTROLLER (H1.3, H1.19, H6.4)
   ═══════════════════════════════════════════════════════════════════════
   The mobile rep-card overflow trigger is being migrated from
   <details>/<summary> to a <button aria-haspopup="menu" aria-expanded>.
   These selectors mirror the visual rules used for the details version,
   so legacy unopened cards still render correctly while app.js drives
   the new accessible controller. */
.rep-card-actions .rep-menu-button{
  appearance: none;
  -webkit-appearance: none;
  min-height: 44px;
  width: 44px;
  height: 44px;
  padding: 0;
  min-width: 44px;
  font-size: 1.1rem;
  cursor: pointer;
  /* Phase 5 stage 3a: GHOST icon button — no fill, no border (reduce card noise). */
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-2);
  color: var(--muted-foreground);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.rep-card-actions .rep-menu-button:hover,
.rep-card-actions .rep-menu-button:focus-visible{
  background: var(--hover);
  outline: none;
  border-color: transparent;
  color: var(--foreground);
}
.rep-card-actions .rep-menu-button:focus-visible{
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
.rep-card-actions .rep-menu-button[aria-expanded="true"]{
  background: rgba(var(--accent-rgb), 0.18);
  border-color: rgba(var(--accent-rgb), 0.45);
}

/* Lift the open card above the bottom-nav (z-index 150). The panel itself
   gets z-index 200 so it floats over every mobile chrome surface. */
.rep-menu-panel{ z-index: 200; }
body > .rep-menu-panel{ z-index: 1050; }
.glass-rep-card:has(.rep-menu-button[aria-expanded="true"]){ z-index: 200; }
.glass-rep-card:has(.rep-menu-button[aria-expanded="true"]) .rep-card-actions{ z-index: 3; }

/* Truncated rep-card usernames need a visible affordance — pair `title=`
   on the markup with a slight underline-on-hover so operators know they
   can hover/tap-long-press to read the full string. */
.rep-card-name,
.rep-card-username{
  /* Keep the existing ellipsis rules from §40 of glass.css but allow the
     hover/tap state to expand on demand on small screens. */
  cursor: help;
}
/* H6.4: tap-to-expand. JS toggles .is-expanded on the same elements; this
   undoes the ellipsis and allows the string to wrap so the operator can
   read the full name/username on mobile without leaving the card. */
.rep-card-name.is-expanded,
.rep-card-username.is-expanded{
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* ═══════════════════════════════════════════════════════════════════════
   §61  RECEIPT PREVIEW — wallet upload + deposit review (H5.1, H5.5)
   ═══════════════════════════════════════════════════════════════════════
   Both surfaces share the same preview chip: a thumbnail (img or a PDF
   placeholder), the filename, and a "remove" link. The wallet upload zone
   shows it inside the drop zone; the deposit-review modal shows it above
   the action buttons so operators verify before approving. */
.receipt-preview{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 10px var(--sp-3);
  margin-top: 10px;
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: var(--r-2);
  background: rgba(255,255,255,0.03);
}
html[data-theme="light"] .receipt-preview{
  background: rgba(0,0,0,0.025);
  border-color: rgba(0,0,0,0.08);
}
.receipt-preview .receipt-thumb{
  width: 56px;
  height: 56px;
  border-radius: 10px;
  object-fit: cover;
  background: rgba(0,0,0,0.20);
  flex-shrink: 0;
}
.receipt-preview .receipt-thumb.is-pdf{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  color: var(--text-m);
}
.receipt-preview .receipt-meta{
  flex: 1;
  min-width: 0;
  font-size: 0.85rem;
  line-height: 1.5;
}
.receipt-preview .receipt-meta .receipt-name{
  display: block;
  word-break: break-all;
  font-weight: 600;
}
.receipt-preview .receipt-meta .receipt-size{
  display: block;
  opacity: 0.65;
  font-size: 0.78rem;
}
.receipt-preview .receipt-clear{
  flex-shrink: 0;
  background: transparent;
  border: 1px solid rgba(var(--danger-rgb), 0.40);
  color: rgb(var(--danger-rgb));
  border-radius: 10px;
  padding: 6px var(--sp-3);
  font-size: 0.78rem;
  cursor: pointer;
  font-family: inherit;
}
.receipt-preview .receipt-clear:hover,
.receipt-preview .receipt-clear:focus-visible{
  background: rgba(var(--danger-rgb), 0.10);
  outline: none;
}
.glass-file-upload .file-error{
  display: block;
  margin-top: 6px;
  color: rgb(var(--danger-rgb));
  font-size: 0.8rem;
  line-height: 1.5;
}

/* Deposit-review modal: inline receipt block above the notes field. */
.dep-receipt-block{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.dep-receipt-block .dep-receipt-label{
  font-size: 0.78rem;
  opacity: 0.65;
  font-weight: 600;
}
.dep-receipt-block .dep-receipt-img{
  display: block;
  max-width: 100%;
  max-height: 260px;
  border-radius: var(--r-2);
  border: 1px solid rgba(255,255,255,0.10);
  background: rgba(0,0,0,0.20);
  object-fit: contain;
  cursor: zoom-in;
}
.dep-receipt-block .dep-receipt-pdf{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3-5);
  border: 1px dashed rgba(255,255,255,0.18);
  border-radius: var(--r-2);
  background: rgba(255,255,255,0.03);
  font-size: 0.85rem;
}
.dep-receipt-block .dep-receipt-pdf .dep-receipt-pdf-icon{ font-size: 1.6rem; }
.dep-receipt-block .dep-receipt-pdf a{
  color: rgb(var(--accent-rgb));
  text-decoration: underline;
  margin-inline-start: auto;
}
.dep-receipt-block .dep-receipt-open{
  align-self: flex-start;
  font-size: 0.78rem;
  color: rgb(var(--accent-rgb));
  text-decoration: underline;
}
html[data-theme="light"] .dep-receipt-block .dep-receipt-pdf{
  background: rgba(0,0,0,0.03);
  border-color: rgba(0,0,0,0.18);
}

/* Reject-reason validation — surfaced inline below the textarea. */
.dep-reason-warn{
  display: none;
  margin-top: 6px;
  font-size: 0.78rem;
  color: rgb(var(--danger-rgb));
}
.dep-reason-warn.show{ display: block; }

/* ═══════════════════════════════════════════════════════════════════════
   §62  TABLET BOTTOM SPACING (A-B)
   ═══════════════════════════════════════════════════════════════════════
   Between 768px and 1023px the bottom-nav is hidden (glass.css §41 only
   renders it ≤768px) but app.css §3 still pads the body by --mob-nav-h.
   Collapse the reserved strip to a normal page bottom on tablet. */
@media (min-width: 768px) and (max-width: 1023.98px){
  body{ padding-bottom: var(--sp-7, 32px); }
}

/* ═══════════════════════════════════════════════════════════════════════
   §63  MOBILE/TABLET TABLE COLLAPSE TWEAKS (A-C, H3.3, H3.15)
   ═══════════════════════════════════════════════════════════════════════
   - Defeat the generic .table[data-label] grid in app.css for tables that
     also collapse via the glass selectors here. The class .glass-tx-table
     wins because it carries `display: block` on <td>; app.css's
     `display: contents` on the same <td> would fight that rule. Belt: an
     explicit override below `.glass-tx-table td` ensures grid never wins.
   - Tablet (769–1023px): hide leaderboard's lowest-priority columns so
     the 9-column dashboard table stops overflowing the card.
   - Mobile 360–390: shrink stat-card values that can run to 4 digits + K
     so they don't overflow the card on narrow phones. */

@media (max-width: 767.98px){
  /* If a .glass-tx-table / .leaderboard / .table-wrap table happens to ALSO
     carry the legacy .table class, force the glass rules to win over
     app.css §10's `display: grid` / `display: contents`.
     PHASE5-TBL: `.tbl-wrap > …` twins deleted here too — see §47. */
  .glass-tx-table.table tbody tr,
  .leaderboard.table tbody tr,
  .table-wrap > table.table:not(.no-collapse) tbody tr{
    display: block;
    grid-template-columns: none;
  }
  .glass-tx-table.table tbody td,
  .leaderboard.table tbody td,
  .table-wrap > table.table:not(.no-collapse) tbody td{
    display: flex;
  }
  .glass-tx-table.table tbody td::before,
  .leaderboard.table tbody td::before,
  .table-wrap > table.table:not(.no-collapse) tbody td::before{
    content: attr(data-label);
    font-size: 0.8rem;
    opacity: 0.65;
    font-weight: 600;
  }

  /* Truncate runaway notes so a single card with a 400-char note doesn't
     stretch and starve the rest. Keep break-word so the cell can wrap if
     it must. */
  .glass-tx-table td,
  .leaderboard td,
  .table-wrap > table td{
    overflow-wrap: anywhere;
    word-break: break-word;
    min-width: 0;
  }
  .glass-tx-table td bdi,
  .leaderboard td bdi{ overflow-wrap: anywhere; word-break: break-all; }
}

/* Mobile 360–390: stat-card numbers can overflow if both the K-suffix
   number and label hit the 2-column tile. Lower the font and let long
   words wrap. */
@media (max-width: 390px){
  .dashboard-stats .stat-val{
    font-size: 1.45rem;
    overflow-wrap: anywhere;
    word-break: break-word;
    min-width: 0;
  }
  .dashboard-stats .stat-lbl{
    font-size: 0.74rem;
    overflow-wrap: anywhere;
  }
  .dashboard-stats .glass-stat-card{
    min-width: 0;
    padding: var(--sp-3-5) var(--sp-3);
  }
}

/* Tablet (768–1023px): the leaderboard fits 9 columns ONLY on ≥1024px.
   In between, hide the two lowest-priority columns (cost + wallet) — the
   key info (sales, profit, action) still shows. Sticky thead helps the
   operator scan a long list. */
@media (min-width: 768px) and (max-width: 1023.98px){
  .leaderboard{ font-size: 0.86rem; }
  .leaderboard thead{
    position: sticky;
    top: 0;
    background: rgba(20,22,32,0.85);
    z-index: 1;
  }
  html[data-theme="light"] .leaderboard thead{ background: rgba(255,255,255,0.92); }
  /* Commit 6 (#8/#28): positional nth-child(6)/(8) hiding removed — superseded
     by the `.lb-extra` class rule (§ tablet column reduction above). The old
     indices were brittle and, worse, hid "موجودی کیف پول" (the wallet balance)
     on tablet while keeping less useful columns. The font-size + sticky thead
     in this block are still the right call and are kept. */
}
/* End of mobile/tablet table tweaks. */

/* ═══════════════════════════════════════════════════════════════════════
   §64  DISABLED FORM-CONTROL FAMILY (H1.5)
   ═══════════════════════════════════════════════════════════════════════
   The legacy .form-control and the cta-* buttons already have :disabled
   rules. Every other glass input/select/textarea/amount/stepper was
   silently identical to its enabled state — operators couldn't tell when
   an input was locked. One shared rule covers them all; component-level
   rules below it stay intact. */
.glass-input:disabled,
.glass-input[aria-disabled="true"],
.glass-select:disabled,
.glass-select[aria-disabled="true"],
.glass-textarea:disabled,
.glass-amount-input:disabled,
.glass-amount-input[readonly],
.glass-stepper .stepper-input:disabled,
.glass-input-group input:disabled,
.glass-input-group textarea:disabled,
.glass-input-group select:disabled,
.btn-glass:disabled,
.btn-glass[aria-disabled="true"]{
  opacity: 0.55;
  cursor: not-allowed;
  /* Soften the focus ring potential. */
  box-shadow: none;
}
/* Keep the inputs visibly "locked" — slightly dimmer background, no
   hover lift, no caret blink. Borders stay visible so the field shape
   reads. */
.glass-input:disabled,
.glass-select:disabled,
.glass-textarea:disabled,
.glass-amount-input:disabled,
.glass-stepper .stepper-input:disabled,
.glass-input-group input:disabled,
.glass-input-group textarea:disabled,
.glass-input-group select:disabled{
  background: rgba(255,255,255,0.02);
  color: var(--text-d, rgba(255,255,255,0.55));
  -webkit-text-fill-color: var(--text-d, rgba(255,255,255,0.55));
}
html[data-theme="light"] .glass-input:disabled,
html[data-theme="light"] .glass-select:disabled,
html[data-theme="light"] .glass-textarea:disabled,
html[data-theme="light"] .glass-amount-input:disabled,
html[data-theme="light"] .glass-stepper .stepper-input:disabled,
html[data-theme="light"] .glass-input-group input:disabled,
html[data-theme="light"] .glass-input-group textarea:disabled,
html[data-theme="light"] .glass-input-group select:disabled{
  background: rgba(0,0,0,0.025);
  color: rgba(0,0,0,0.45);
  -webkit-text-fill-color: rgba(0,0,0,0.45);
}
.btn-glass:disabled,
.btn-glass[aria-disabled="true"]{
  transform: none;
}
.btn-glass:disabled::before,
.btn-glass[aria-disabled="true"]::before{ opacity: 0; }

/* ═══════════════════════════════════════════════════════════════════════
   §65  KEYBOARD FOCUS-VISIBLE (H1.6)
   ═══════════════════════════════════════════════════════════════════════
   Several interactive surfaces had no :focus-visible style — keyboard
   users got no indication of where they were. One shared rule with the
   accent ring; per-surface rules above (auth, rep-menu, theme-FAB) win
   when more specific. */
.dur-btn:focus-visible,
.stepper-btn:focus-visible,
.glass-nav-item:focus-visible,
.glass-tab:focus-visible,
.queue-err-details summary:focus-visible,
.rep-action-primary:focus-visible,
.rep-action-secondary:focus-visible,
.glass-file-upload:focus-visible,
.glass-file-upload:has(input:focus-visible),
.url-copy-glass .btn-glass:focus-visible,
.modal-close:focus-visible,
[data-modal-close]:focus-visible,
.dep-receipt-block .dep-receipt-img:focus-visible,
.receipt-preview .receipt-clear:focus-visible,
[data-copy]:focus-visible{
  outline: 2px solid rgba(var(--accent-rgb), 0.65);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.18);
}
/* End of §64–§65 — disabled + focus-visible. */

/* ═══════════════════════════════════════════════════════════════════════
   §66  RENEW PREVIEW SUMMARY (H4.8)
   ═══════════════════════════════════════════════════════════════════════
   Shared by my_subscription_detail.php + subscription_detail.php renew
   modals. The .glass-summary-dl host already lays out dt/dd as a grid;
   here we just give the renew variant a soft frame so the live total
   reads as a "receipt block" inside the modal. */
.renew-preview-dl{
  padding: var(--sp-3) var(--sp-3-5);
  margin: 4px 0 12px;
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-2);
  background: rgba(255,255,255,0.03);
  display: grid;
  grid-template-columns: minmax(45%, max-content) 1fr;
  gap: 6px var(--sp-3-5);
  font-size: 0.88rem;
}
html[data-theme="light"] .renew-preview-dl{
  background: rgba(0,0,0,0.025);
  border-color: rgba(0,0,0,0.08);
}
.renew-preview-dl dt{
  opacity: 0.7;
  font-size: 0.8rem;
  align-self: center;
}
.renew-preview-dl dd{
  margin: 0;
  font-variant-numeric: tabular-nums;
  align-self: center;
}
.renew-preview-dl dt.renew-total-label,
.renew-preview-dl dd.renew-total-value{
  border-top: 1px dashed rgba(255,255,255,0.08);
  padding-top: 8px;
  margin-top: 4px;
  opacity: 1;
  font-weight: 700;
}
html[data-theme="light"] .renew-preview-dl dt.renew-total-label,
html[data-theme="light"] .renew-preview-dl dd.renew-total-value{
  border-top-color: rgba(0,0,0,0.08);
}
.renew-preview-dl dd.renew-total-value{ color: rgb(var(--accent-rgb)); font-size: 1rem; }

/* ═══════════════════════════════════════════════════════════════════════
   §67  SETTINGS PAGE — JUMP NAV + BULK HINT + PW EYE (H6.6–H6.9)
   ═══════════════════════════════════════════════════════════════════════ */
.settings-jump{
  position: sticky;
  top: calc(var(--sp-2, 8px) + 8px);
  z-index: 30;
  display: flex;
  gap: 6px;
  margin-top: 14px;
  padding: 6px;
  overflow-x: auto;
  scrollbar-width: thin;
  border-radius: var(--r-2);
  border: 1px solid rgba(255,255,255,0.08);
  background: rgba(20,22,32,0.85);
}
html[data-theme="light"] .settings-jump{
  background: rgba(255,255,255,0.85);
  border-color: rgba(0,0,0,0.08);
}
.settings-jump-link{
  flex: 0 0 auto;
  padding: var(--sp-2) var(--sp-3);
  border-radius: 10px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.06);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.settings-jump-link:hover,
.settings-jump-link:focus-visible{
  background: rgba(var(--accent-rgb), 0.16);
  border-color: rgba(var(--accent-rgb), 0.40);
  outline: none;
}
html[data-theme="light"] .settings-jump-link{
  background: rgba(0,0,0,0.025);
  border-color: rgba(0,0,0,0.06);
}

.settings-bulk-hint{
  display: block;
  margin-top: 8px;
  padding: var(--sp-2) 10px;
  font-size: 0.78rem;
  line-height: 1.65;
  background: rgba(var(--warn-rgb), 0.08);
  border: 1px solid rgba(var(--warn-rgb), 0.30);
  border-radius: 10px;
  color: var(--text);
  opacity: 1;
}
html[data-theme="light"] .settings-bulk-hint{
  background: rgba(var(--warn-rgb), 0.10);
  border-color: rgba(var(--warn-rgb), 0.35);
}

/* H6.7: eye toggle styling — app.js fills the SVG into the button on
   load and on click. Position inside .pw-wrap.

   DIRECTION PINNING (bug fix): the input inside is dir="ltr" (passwords are
   LTR), so its inline `padding-inline-end: 42px` resolved against the INPUT's
   LTR direction = physical RIGHT. The button, however, resolved
   `inset-inline-end` against .pw-wrap, which inherited the page's RTL =
   physical LEFT. Padding was reserved on one side and the button drawn on the
   other, so the typed password ran straight through the eye icon.
   Pinning the wrap to ltr makes both resolve the same way (both = right),
   which is the same fix .buy-input-wrap already documents. */
.pw-wrap{ position: relative; direction: ltr; }
.pw-wrap .pw-eye-btn{
  position: absolute;
  inset-inline-end: 8px;
  inset-block-start: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  padding: 0;
  background: transparent;
  border: none;
  color: var(--text-m, var(--text));
  cursor: pointer;
  border-radius: var(--r-1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.pw-wrap .pw-eye-btn:hover,
.pw-wrap .pw-eye-btn:focus-visible{
  background: rgba(255,255,255,0.08);
  color: var(--text);
  outline: none;
}
html[data-theme="light"] .pw-wrap .pw-eye-btn:hover,
html[data-theme="light"] .pw-wrap .pw-eye-btn:focus-visible{
  background: rgba(0,0,0,0.05);
}

/* H6.8: tiny unsaved-changes indicator next to each section's save
   button. The settings page is long; without a hint, operators forget
   they edited a field and navigate away. The indicator is rendered by
   the scoped IIFE in settings.php and is only visible while the form
   carries data-dirty="1". The save button gets an extra accent
   highlight in the same state. */
.settings-unsaved-indicator{
  /* UI-0235: keep the pill in the layout (reserved space) and toggle
     visibility/opacity instead of display:none → inline-flex, so the save
     row doesn't jump when the form becomes dirty. */
  display: inline-flex;
  visibility: hidden;
  opacity: 0;
  transition: opacity .15s ease;
  align-items: center;
  gap: 6px;
  margin-inline-start: 10px;
  padding: var(--sp-1) 10px;
  border-radius: var(--r-full);
  font-size: 0.78rem;
  font-weight: 600;
  color: rgb(252,211,77);
  background: rgba(var(--warn-rgb), 0.10);
  border: 1px solid rgba(var(--warn-rgb), 0.35);
}
form[data-dirty="1"] .settings-unsaved-indicator{ visibility: visible; opacity: 1; }
form[data-dirty="1"] .btn-glass.primary{
  box-shadow: 0 0 0 2px rgba(var(--warn-rgb), 0.45),
              0 6px 18px rgba(var(--warn-rgb), 0.15);
}
html[data-theme="light"] .settings-unsaved-indicator{
  color: rgb(146,64,14);
  background: rgba(var(--warn-rgb), 0.12);
}

/* ═══════════════════════════════════════════════════════════════════════
   §68  MOBILE BOTTOM-NAV LOGOUT BUTTON (H2.4)
   ═══════════════════════════════════════════════════════════════════════
   The logout form acts as a 5th grid cell inside .glass-bottom-nav
   .nav-items. The inner button reuses .glass-nav-item visuals but needs
   the button-default reset (background/border/font) and full-cell sizing
   so the icon + label center the same way as the <a> nav items. */
.glass-bottom-nav .glass-nav-form{
  display: flex;
  margin: 0;
  padding: 0;
  height: 100%;
}
.glass-nav-item.glass-nav-item-button{
  width: 100%;
  height: 100%;
  background: transparent;
  border: none;
  font-family: inherit;
}
.glass-nav-item.glass-nav-item-button:focus-visible{
  outline: 2px solid rgba(var(--accent-rgb), 0.55);
  outline-offset: -3px;
}

/* H2.4: align the desktop logout-form so it doesn't break the .nav
   flex/gap rhythm. The form itself sits inline; the button inherits
   its sibling .btn-glass.danger.sm visuals. */
.nav .logout-form{
  display: inline-flex;
  align-items: center;
}

/* ═══════════════════════════════════════════════════════════════════════
   §69  QUEUE — ERROR PREVIEW + EXPANDER (H3.6)
   ═══════════════════════════════════════════════════════════════════════
   Long last_error strings used to render as a wall of micro-text inside
   a 220px column. The PHP now emits an <details> with a short preview;
   expanding shows the full error inside a scrollable <pre>. */
.queue-err-cell{ max-width: 280px; }
.queue-err-cell code{ font-size: 0.78rem; word-break: break-word; }
.queue-err-details summary{
  cursor: pointer;
  list-style: none;
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
}
.queue-err-details summary::-webkit-details-marker{ display: none; }
.queue-err-details summary::marker{ content: ""; }
.queue-err-details[open] summary{ opacity: 0.7; }
.queue-err-details .queue-err-full{
  margin: 6px 0 0;
  padding: var(--sp-2) 10px;
  font-size: 0.78rem;
  line-height: 1.55;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--r-1);
  max-height: 240px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
}
html[data-theme="light"] .queue-err-details .queue-err-full{
  background: rgba(0,0,0,0.04);
  border-color: rgba(0,0,0,0.08);
}
@media (max-width: 768px){
  /* On the card-collapsed mobile layout, allow the error cell to take
     full width and the inline-code preview to wrap freely. */
  .queue-err-cell{ max-width: 100%; min-width: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════
   §70  UTILITY: .is-hidden + <mark> highlight (H1.12, H5.10)
   ═══════════════════════════════════════════════════════════════════════
   The tools-search filter in admin/search.php toggles .is-hidden on
   non-matching cards (assets/app.js IIFE). Without this rule the class
   does nothing visually. */
.is-hidden{ display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════
   §71  REPRESENTATIVES — row helpers (H5.12)
   ═══════════════════════════════════════════════════════════════════════
   Promote a handful of repeated inline-style patterns from
   admin/representatives.php into the design system so the page stops
   leaking physical directional properties (margin-left, etc.). Only
   the safe, identifiable patterns are migrated; the rest stay inline
   to keep the diff small and the visual output unchanged. */
.rep-row-logo{
  width: 28px;
  height: 28px;
  border-radius: 6px;
  object-fit: cover;
  vertical-align: middle;
  /* H5.12: physical margin-left → logical margin-inline-end so the
     gap between the logo and the name sits on the trailing inline
     side in either locale. In Persian RTL the logo precedes the
     name; the gap belongs after it (visually to its left). */
  margin-inline-end: 6px;
}
.rep-row-name{ font-weight: 700; }
.rep-row-username{
  /* Phase 5 stage 2: muted-foreground token instead of opacity-dimming, so the
     @handle stays >=4.5:1 (opacity 0.6 dropped effective contrast under AA). */
  color: var(--muted-foreground);
  font-size: 0.78rem;
  font-family: ui-monospace, monospace;
}
.rep-row-pagination{
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 14px;
  padding-inline: 8px;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.rep-row-pagination .pager-meta{
  opacity: 0.65;
  font-size: 0.85rem;
}
.rep-row-pagination .pager-nav{
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}
/* H6.5: active page chip — mirrors .btn-glass.neutral.sm visually but
   sits in a non-interactive <span> when there is no link to follow. */
.rep-row-pagination .pager-nav .pager-current{
  pointer-events: none;
  background: rgba(var(--accent-rgb), 0.18);
  border-color: rgba(var(--accent-rgb), 0.45);
  color: var(--text);
}
html[data-theme="light"] .rep-row-pagination .pager-nav .pager-current{
  background: rgba(var(--accent-rgb), 0.14);
  border-color: rgba(var(--accent-rgb), 0.40);
}
.rep-row-pagination .pager-nav .pager-ellipsis{
  padding: 0 6px;
  opacity: 0.55;
  align-self: center;
}

/* Search-results match highlighting. <mark> default browser styling is
   yellow background + black text — fine on light theme but unreadable
   on dark. Token-aware variant keeps the existing glass identity. */
.search-results mark,
mark.search-hit{
  background: rgba(var(--warn-rgb), 0.30);
  color: inherit;
  padding: 0 2px;
  border-radius: 4px;
  font-weight: 700;
}
html[data-theme="light"] .search-results mark,
html[data-theme="light"] mark.search-hit{
  background: rgba(var(--warn-rgb), 0.45);
}

/* Row-as-link affordance for the search result rows. The PHP wraps a
   data-href onto each <tr>; a tiny JS handler navigates on click. */
.glass-tx-table tr[data-href]{ cursor: pointer; }
.glass-tx-table tr[data-href]:hover td{ background: rgba(var(--accent-rgb), 0.06); }
html[data-theme="light"] .glass-tx-table tr[data-href]:hover td{
  background: rgba(var(--accent-rgb), 0.08);
}

/* ═══════════════════════════════════════════════════════════════════════
   §67  GLASS CHECKBOX / RADIO
   ═══════════════════════════════════════════════════════════════════════
   Drop-in styled replacements for raw browser checkboxes and radios so they
   match the glass input family. OPT-IN via class (.glass-check) — never a
   bare input[type=...] rule — so the pre-existing visually-hidden custom
   toggles (.auto-username-check) keep their own treatment untouched. All colours
   come from the shared tokens (--input-bg / --border / --border-strong /
   --accent / --accent-rgb), so both themes are handled automatically.
   The native control's name/value/checked/JS behaviour is unchanged — this
   only repaints the box. */
/* Selector is `input.glass-check` (not bare `.glass-check`) on purpose: it
   raises specificity to (0,1,1) so the fixed 19×19 box always beats the
   generic `.glass-input-group input { width:100%; padding:10px 12px }` rule
   (also 0,1,1, but earlier in the file). Without this, a .glass-check placed
   inside a .glass-input-group inherits width:100% + padding and — because
   appearance:none lets width apply — stretches to the full container width,
   breaking the row. `padding:0` neutralises the same inherited padding. */
input.glass-check{
  appearance: none; -webkit-appearance: none;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: 19px; height: 19px;
  min-width: 19px;            /* belt-and-braces: never shrink/stretch in a flex row */
  margin: 0;
  padding: 0;                 /* override .glass-input-group input's 10px 12px */
  display: inline-grid;
  place-content: center;
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  vertical-align: middle;
  transition: background var(--dur-1, 120ms) var(--ease, ease),
              border-color var(--dur-1, 120ms) var(--ease, ease),
              box-shadow var(--dur-1, 120ms) var(--ease, ease);
}
.glass-check[type="radio"]{ border-radius: 50%; }

/* The check/dot is drawn in CSS (no SVG asset). Hidden until :checked via a
   scale transform so the toggle animates with the rest of the UI. */
.glass-check::after{
  content: "";
  transform: scale(0);
  transition: transform var(--dur-1, 120ms) var(--ease, ease);
}
.glass-check[type="checkbox"]::after{
  width: 5px; height: 9px;
  border: solid #fff;            /* tick reads on the accent fill in both themes */
  border-width: 0 2px 2px 0;
  border-radius: 1px;
  margin-block-start: -2px;      /* optical centring after the 45° rotation */
  transform: rotate(45deg) scale(0);
}
.glass-check[type="checkbox"]:checked::after{ transform: rotate(45deg) scale(1); }
.glass-check[type="radio"]::after{
  width: 9px; height: 9px;
  border-radius: 50%;
  background: #fff;
}
.glass-check[type="radio"]:checked::after{ transform: scale(1); }

.glass-check:checked{
  background: var(--accent);
  border-color: var(--accent);
}
.glass-check:not(:checked):hover{ border-color: var(--border-strong); }
.glass-check:focus-visible{
  outline: none;
  border-color: var(--border-strong);
  box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.18);
}
.glass-check:disabled{ opacity: 0.5; cursor: not-allowed; }


/* ═══════════════════════════════════════════════════════════════════════
   TICKET THREAD — chat-bubble message list for admin/ticket_detail.php
   ═══════════════════════════════════════════════════════════════════════ */
.ticket-thread{
  display: flex;
  flex-direction: column;
  gap: var(--sp-3-5);
}
.ticket-msg{
  max-width: 85%;
  padding: 10px var(--sp-3-5);
  border-radius: 14px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
}
.ticket-msg.mine{
  align-self: flex-end;
  background: rgba(var(--accent-rgb), 0.14);
  border-color: rgba(var(--accent-rgb), 0.3);
}
.ticket-msg.theirs{ align-self: flex-start; }
.ticket-msg-meta{
  font-size: 0.76rem;
  opacity: 0.6;
  margin-bottom: 6px;
}
.ticket-msg-body{
  font-size: 0.92rem;
  line-height: 1.6;
  word-break: break-word;
  overflow-wrap: anywhere;
}
.ticket-msg-attachments{
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 10px;
}
html[data-theme="light"] .ticket-msg{
  background: rgba(0,0,0,0.03);
  border-color: rgba(0,0,0,0.06);
}
html[data-theme="light"] .ticket-msg.mine{
  background: rgba(var(--accent-rgb), 0.10);
  border-color: rgba(var(--accent-rgb), 0.25);
}
@media (max-width: 600px){
  .ticket-msg{ max-width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   §47b REPRESENTATIVES — Phase 4 (menu grouping §7, mobile bottom sheets
   §8, consolidated pricing §4/§5, mobile detail affordance). Placed at EOF
   so source order beats the base .rep-menu-panel rules; the mobile
   bottom-sheet override uses !important only where app.js sets inline
   top/left on the portaled panel.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── §7: grouped ⋯ menu (labels + dividers) ──────────────────────────── */
.rep-menu-group{ display: flex; flex-direction: column; gap: 2px; }
.rep-menu-group + .rep-menu-group{
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid rgba(255,255,255,0.09);
}
html[data-theme="light"] .rep-menu-group + .rep-menu-group{ border-top-color: rgba(0,0,0,0.08); }
/* Commit 5 (#84): identity header at the top of the row-actions panel, so a
   charge / impersonate / suspend is always attributable to a named account.
   Sits above the first group and owns the divider, so the group below no
   longer needs its own top border to read as separated. */
.rep-menu-head{
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border, rgba(255,255,255,0.10));
  margin-bottom: var(--sp-1);
  min-width: 0;
}
.rep-menu-head-name{
  font-weight: 700;
  font-size: 0.86rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rep-menu-head-user{
  font-family: ui-monospace, monospace;
  font-size: 0.75rem;
  opacity: 0.6;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Commit 5 (#86): heading for the expanded row-detail panel. */
.rep-row-detail-title{
  margin: 0 0 var(--sp-2);
  font-size: 0.86rem;
  font-weight: 700;
  opacity: 0.75;
}

.rep-menu-group-label{
  font-size: 0.8rem;   /* Commit 1 (#11): 12px floor */
  font-weight: 700;
  opacity: 0.45;
  padding: var(--sp-1) var(--sp-3) 2px;
}

/* ── §8a: mobile ⋯ becomes a bottom sheet ────────────────────────────── */
@media (max-width: 768px){
  /* app.js portals the panel to <body> and sets inline top/left (no
     !important); these !important rules pin it to the bottom, full-width,
     regardless of where the trigger sits. The big box-shadow spread doubles
     as a full-screen scrim — clicking it lands outside the panel, so app.js's
     existing outside-click handler closes the menu. */
  body > .rep-menu-panel{
    position: fixed !important;
    inset: auto 0 0 0 !important;
    top: auto !important;
    left: 0 !important;
    right: 0 !important;
    width: 100% !important;
    min-width: 0 !important;
    max-width: 100% !important;
    max-height: 72vh;
    overflow-y: auto;
    margin: 0 !important;
    border-radius: var(--r-3) var(--r-3) 0 0;
    padding: 10px var(--sp-3) calc(var(--sp-3-5) + env(safe-area-inset-bottom, 0));
    box-shadow: 0 0 0 100vmax rgba(0,0,0,0.5), 0 -12px 40px rgba(0,0,0,0.45);
    animation: rep-bottom-sheet-up 200ms ease-out !important;
  }
  body > .rep-menu-panel .rep-menu-item{ min-height: 48px; }
}
@keyframes rep-bottom-sheet-up{
  from{ transform: translateY(100%); }
  to  { transform: translateY(0); }
}

/* ── §8b: mobile detail affordance on the card + shared detail sheet ──── */
.rep-card-detail-btn{
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  margin-inline-start: 2px;
  border-radius: var(--r-1);
  /* Phase 5 stage 3a: GHOST chevron — no fill/border (reduce card noise). */
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
  padding: 0;
}
.rep-card-detail-btn .icon{ width: 16px; height: 16px; transform: rotate(-90deg); opacity: 0.8; }
/* Phase 5 stage 3a: soft status/level badges on the mobile rep card — a muted
   fill carries the chip, colored text carries the meaning; no heavy colored
   outline. Scoped to the card so other surfaces' badges are unchanged. */
.glass-rep-card .badge-glass,
.glass-rep-card .glass-tier-badge{
  background: var(--muted);
  border-color: transparent;
}
/* Phase 5 stage 3a: ghost in both themes (was a light-theme box override). */
.rep-card-detail-btn:hover,
.rep-card-detail-btn:focus-visible{ background: var(--hover); outline: none; }
.rep-card-detail-btn:focus-visible{ outline: 2px solid var(--ring); outline-offset: 2px; }
/* Whole mobile card reads as tappable (opens the detail sheet). */
@media (max-width: 1023px){ .glass-rep-card{ cursor: pointer; } }
/* The detail affordance only makes sense on mobile (the desktop table has
   its own inline expander); hide it where the table is shown. */
@media (min-width: 1024px){ .rep-card-detail-btn{ display: none; } }

.rep-sheet-backdrop{
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 1200;
}
.rep-sheet-backdrop[hidden]{ display: none; }
.rep-sheet{
  position: fixed;
  inset: auto 0 0 0;
  z-index: 1201;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: var(--r-3) var(--r-3) 0 0;
  box-shadow: 0 -12px 40px rgba(0,0,0,0.45);
  padding: var(--sp-2) var(--sp-4-5) calc(var(--sp-4-5) + env(safe-area-inset-bottom, 0));
  max-height: 82vh;
  overflow-y: auto;
  animation: rep-bottom-sheet-up 220ms ease-out;
}
.rep-sheet[hidden]{ display: none; }
.rep-sheet-grabber{
  width: 40px; height: 4px; border-radius: var(--r-full);
  background: rgba(255,255,255,0.2);
  margin: 4px auto 12px;
}
html[data-theme="light"] .rep-sheet-grabber{ background: rgba(0,0,0,0.18); }
.rep-sheet-head{ display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); margin-bottom: 14px; }
.rep-sheet-name{ font-size: var(--fs-3); font-weight: 700; }
.rep-sheet-user{ font-size: var(--fs-1); opacity: 0.6; margin-top: 2px; }
.rep-sheet-close{
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: transparent; color: inherit; cursor: pointer;
}
.rep-sheet-close .icon{ width: 20px; height: 20px; }
.rep-sheet-grid{
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3-5) var(--sp-5);
  margin: 0 0 16px;
}
.rep-sheet-grid dt{ font-size: 0.72rem; opacity: 0.55; margin-bottom: 3px; }
.rep-sheet-grid dd{ margin: 0; font-size: 0.92rem; font-variant-numeric: tabular-nums; }
.rep-sheet-foot{ display: flex; }
.rep-sheet-primary{ width: 100%; justify-content: center; }
.rep-sheet-primary[hidden]{ display: none; }

/* ── §4/§5: consolidated, collapsible default-pricing card ───────────── */
details.rep-pricing{ margin-top: 14px; padding: 0; }
.rep-pricing-summary{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3-5) var(--sp-4-5);
  cursor: pointer;
  list-style: none;
  border-radius: inherit;
}
.rep-pricing-summary::-webkit-details-marker{ display: none; }
.rep-pricing-summary:hover{ background: rgba(255,255,255,0.03); }
html[data-theme="light"] .rep-pricing-summary:hover{ background: rgba(0,0,0,0.02); }
.rep-pricing-summary-main{ display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.rep-pricing-summary-title{ font-weight: 700; font-size: var(--fs-2); }
.rep-pricing-summary-vals{ font-size: 0.8rem; opacity: 0.62; font-variant-numeric: tabular-nums; }
.rep-pricing-edit{
  flex-shrink: 0;
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-1); font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}
.rep-pricing-edit-icon{ display: inline-flex; }
.rep-pricing-edit-icon .icon{ width: 16px; height: 16px; }
details.rep-pricing[open] .rep-pricing-edit{ opacity: 0.5; }
.rep-pricing-form{ padding: var(--sp-1) var(--sp-4-5) var(--sp-4-5); border-top: 1px solid var(--border); margin-top: 2px; }
.rep-pricing-grid{
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3-5);
  margin: 14px 0;
}
@media (max-width: 640px){ .rep-pricing-grid{ grid-template-columns: 1fr; } }
.rep-pricing-foot{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
}
.rep-pricing-foot .faint{ flex: 1; min-width: 200px; font-size: 0.78rem; }
.rep-pricing-save{ flex-shrink: 0; width: 160px; max-width: 100%; justify-content: center; }
@media (max-width: 640px){ .rep-pricing-save{ width: 100%; } }

/* ═══════════════════════════ §52 USAGE METERS ═══════════════════════════
   Volume/time consumption meters. Two forms share one tier colour system:
     • .dot-meter  — minimal 5-dot vector (subscription list rows).
     • .usage-ring — full radial gauge (subscription detail pages).
   Tier colours track % USED (green→amber→orange→red→deep-red), the filled
   dot COUNT tracks % remaining, and both grey out at 100%/expired/deleted.
   State is carried by classes + a --ring-pct custom property (CSP-safe:
   no computed inline widths). See lib/UsageMeter.php + render_dot_meter()/
   render_usage_ring() in config.php. RULE-6 safe — usage reveals no tier. */

:root{
  --dm-green:   var(--success);
  --dm-amber:   var(--warn);
  --dm-orange:  #ef8a3c;
  --dm-red:     var(--danger);
  --dm-deepred: #e23b3b;
  --dm-empty:   rgba(255,255,255,0.16);
  --ring-track: rgba(255,255,255,0.10);
  --ring-hole:  var(--card, #14161c);
}
html[data-theme="light"]{
  --dm-orange:  #d9730b;
  --dm-deepred: #c81e1e;
  --dm-empty:   rgba(0,0,0,0.14);
  --ring-track: rgba(0,0,0,0.09);
  --ring-hole:  #ffffff;
}

/* ── Dot meter ─────────────────────────────────────────────────────────── */
.dot-meter{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  vertical-align: middle;
  white-space: nowrap;
}
.dot-meter .dm-icon{
  display: inline-flex;
  align-items: center;
  opacity: 0.75;
}
.dot-meter .dm-icon svg{ width: 14px; height: 14px; }
.dot-meter .dm-dots{ display: inline-flex; gap: 3px; }
.dot-meter .dm-dot{
  width: 7px; height: 7px;
  border-radius: var(--r-full);
  background: var(--dm-empty);
  flex: none;
}
.dot-meter .dm-dot.on{ background: var(--dm-color, var(--dm-green)); }
.dot-meter .dm-label{ font-size: 0.8rem; opacity: 0.85; }

.dot-meter.tier-green  { --dm-color: var(--dm-green);  }
.dot-meter.tier-amber  { --dm-color: var(--dm-amber);  }
.dot-meter.tier-orange { --dm-color: var(--dm-orange); }
.dot-meter.tier-red    { --dm-color: var(--dm-red);    }
.dot-meter.tier-deepred{ --dm-color: var(--dm-deepred);}
.dot-meter.tier-gray, .dot-meter.tier-unknown{ --dm-color: var(--dm-empty); opacity: 0.7; }
.dot-meter.tier-unknown .dm-icon{ opacity: 0.5; }

.dot-meter .dm-dot.blink{ animation: dm-blink 1s steps(2, end) infinite; }
@keyframes dm-blink{ 50%{ opacity: 0.2; } }
@media (prefers-reduced-motion: reduce){
  .dot-meter .dm-dot.blink{ animation: none; }
}

/* ── Radial ring (detail pages) ────────────────────────────────────────── */
.usage-rings{
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5, 20px);
}
.usage-ring-item{
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 220px;
  flex: 1 1 220px;
}
.usage-ring{
  --ring-color: var(--dm-green);
  position: relative;
  width: 72px; height: 72px;
  flex: none;
  border-radius: var(--r-full);
  display: grid;
  place-items: center;
  background:
    radial-gradient(closest-side, var(--ring-hole) 68%, transparent 69% 100%),
    conic-gradient(var(--ring-color) calc(var(--ring-pct, 0) * 1%), var(--ring-track) 0);
}
.usage-ring .ring-pct{
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
}
.usage-ring.tier-green  { --ring-color: var(--dm-green);  }
.usage-ring.tier-amber  { --ring-color: var(--dm-amber);  }
.usage-ring.tier-orange { --ring-color: var(--dm-orange); }
.usage-ring.tier-red    { --ring-color: var(--dm-red);    }
.usage-ring.tier-deepred{ --ring-color: var(--dm-deepred);}
.usage-ring.tier-gray, .usage-ring.tier-unknown{ --ring-color: var(--dm-empty); }
.ring-meta{ min-width: 0; }
.ring-title{ font-weight: 600; font-size: 0.92rem; margin-bottom: 4px; }
.ring-detail{ font-size: 0.82rem; }

/* List-row placement: the two meters sit together, wrapping on narrow rows. */
.row-meters{
  display: inline-flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
@media (max-width: 560px){
  .row-meters{ gap: 8px; }
}
