/* Byar Logistics ERP — Admin Panel Styles */
/* Light mode is DEFAULT, dark mode is optional */

:root {
  --bg:           #f8fafc;
  --bg2:          #ffffff;
  --bg3:          #f1f5f9;
  --text:         #0f172a;
  --text2:        #475569;
  /* Lifted from #94a3b8, which measured 2.3:1 on white — under even the 3:1
     large-text line, on the token that carries timestamps, counts and helper
     text across the whole platform. Still clearly the quiet step; now a
     readable one. */
  --text3:        #6b7a8f;
  --border:       #e2e8f0;
  --accent:       #3b82f6;
  --accent-light: #2563eb;
  --success:      #16a34a;
  --warning:      #d97706;
  --danger:       #dc2626;
  /* ── CONTROL INK — the one rule for every FILLED control ─────────────
     A filled button paints its label in the -fill-ink of the SAME pair, never
     a hardcoded white and never an ink derived from some other colour. Each
     pair clears 4.5:1. The Design Engine re-derives all four from the colours
     that actually win (ThemeResolver::withControlInks); these are the measured
     defaults for when it is off. The fill is the brand colour, or the nearest
     deeper shade of it that white reads on.                                 */
  --accent-fill:       #3472D8;  --accent-fill-ink:  #FFFFFF;   /* 4.62:1 */
  --success-fill:      #12823B;  --success-fill-ink: #FFFFFF;   /* 4.9:1  */
  --danger-fill:       #DC2626;  --danger-fill-ink:  #FFFFFF;   /* 4.83:1 */
  --warning-fill:      #AE5F05;  --warning-fill-ink: #FFFFFF;   /* 4.6:1  */
  --sidebar-width: 240px;
  --topbar-height: 56px;
  --radius:       8px;
  --radius-lg:    12px;
  /* ── ELEVATION ────────────────────────────────────────────────────────
     Four steps and they mean four things, so depth says what a surface IS
     rather than decorating it:

       sm   a resting card on the page
       md   something raised on purpose — a hovered card, a popover
       lg   something over the page — a drawer, a sheet
       xl   something over everything — a modal

     Two existed and the rest of the platform reached for hand-written
     rgba() values, which is why some cards floated and others were outlined
     flat beside them. Light-mode shadows are cool-tinted (slate, not black),
     because a neutral black shadow over a blue-grey page reads as dirt. */
  --shadow-sm:    0 1px 2px rgba(15, 23, 42, 0.05), 0 1px 3px rgba(15, 23, 42, 0.06);
  --shadow:       0 1px 3px rgba(15, 23, 42, 0.08);
  --shadow-md:    0 4px 12px rgba(15, 23, 42, 0.10), 0 2px 4px rgba(15, 23, 42, 0.05);
  --shadow-lg:    0 12px 28px rgba(15, 23, 42, 0.14), 0 4px 8px rgba(15, 23, 42, 0.06);
  --shadow-xl:    0 24px 64px rgba(15, 23, 42, 0.22);

  /* ── SURFACES ─────────────────────────────────────────────────────────
     bg   the page itself
     bg2  a surface resting on it (a card, a panel)
     bg3  a surface set INTO one (an inset row, a hover, a well)
     A fourth is named so a card inside a card is not the same colour as the
     card it sits in — the "everything is grey" flatness. */
  --surface-sunken: #eef2f7;

  /* ── ACCENT AS INK vs ACCENT AS FILL ──────────────────────────────────
     The brand accent is chosen to look right as a FILL — a button, a bar,
     a border. The same colour used as TEXT has to clear a contrast bar the
     fill never has to, and in dark mode it does not: the platform's indigo
     measured 2.3:1 against the dark card behind it, so every disclosure
     summary and inline link on every dark screen sat under the readable
     line. In light mode this and the fill are the same colour; dark mode
     lifts it toward white while keeping the hue, so it is still the brand.

     NOT CALLED --accent-ink. That name is taken and means the OPPOSITE:
     TokenRegistry publishes it as the ink to put ON an accent fill, which is
     white. Borrowing it turned every disclosure summary white on white for
     as long as it took to measure — a good reminder that a shared token
     namespace is shared. */
  --accent-on-surface: var(--accent);

  /* ── SEMANTIC COLOUR AS INK ───────────────────────────────────────────
     Same rule as the accent, same reason. Success, warning and danger are
     picked to work as FILLS — a bar, a dot, a border — and a fill has no
     contrast bar to clear. Used as TEXT they do: measured on the platform's
     own surfaces, the badge labels sat at 2.3–2.9:1 in light mode and again
     in dark, under even the 3:1 large-text line.

     These are the same hues, stepped to be readable AS TEXT. Fills keep the
     original tokens, so nothing that is a bar or a dot changes colour. */
  --success-on-surface: #0f7a37;
  --warning-on-surface: #9a5b06;
  --danger-on-surface:  #b31d1d;
  /* SEATING CATEGORIES. A dining room's table types are an identity scale, not
     a status one, so they get their own named tokens rather than borrowing
     success/warning/danger — a booth is not a warning. Six is more than the
     CVD-safe series set carries, so each dot also shows its category as text.
     Defined here because colour belongs to the theme layer; the component
     stylesheet only references them. */

  /* THE RECRUITMENT CONSOLE AND THE JOURNEY STEPS.
     Same rule as the seating scale above: colour belongs to the theme layer,
     and design-system.css only references it. Nineteen literal colours had
     grown in the component stylesheet, which forks the theme — a tenant who
     changes their accent, or a reader in dark mode, got a console that did not
     follow. Named for what they MEAN, so dark mode can answer differently
     without every component knowing about it. */
  /* THE FOUR STEP STATES, NAMED FOR WHAT THEY MEAN.
     These were inverted. `--rv-mark` was #B91C1C — a hard red — and it painted
     the DONE step, while `--step-waiting` was #86EFAC, a green, painting the
     step that had not happened yet. A reader looking at their own recruitment
     saw finished work in the colour of an error and unstarted work in the
     colour of success, which is the exact opposite of what colour is for
     (CLAUDE.md §10: green = healthy, amber = attention, red = critical).
     The owner named the palette he wants, and this is it. */
  --step-done:      #16A34A;   /* finished, and it reads as finished */
  --step-now:       var(--accent, #4F46E5);   /* the brand accent: you are here */
  /* WAITING IS NOT FAILURE. A soft, desaturated rose — present enough to read
     as "not yet", nowhere near the saturation of the destructive red below,
     so a pending step can never be mistaken for a broken one. */
  --step-waiting-ink: #9F7080;
  --step-waiting-bg:  rgba(190, 120, 140, .10);
  --step-blocked:   #DC2626;   /* actually wrong, and it says so */
  --rv-mark:        #B91C1C;
  --rv-screen-bg:   #0b1020;
  --rv-screen-edge: #1e293b;
  --rv-screen-ink:  #60a5fa;
  --rv-screen-jd:   #4ade80;
  --warning-ink:    #7c2d12;
  --warning-ink-2:  #92400e;
  /* Kept: the dashed rail between steps reads as "the track continues", and
     it is drawn in the waiting ink now rather than in a success green. */
  --step-waiting:   var(--step-waiting-ink);
  --seat-regular: #64748b;
  --seat-family:  #2563eb;
  --seat-bar:     #9333ea;
  --seat-booth:   #0891b2;
  --seat-outdoor: #16a34a;
  --seat-private: #b45309;
  /* Text laid over a brand colour or gradient — always light, in every theme. */
  --on-brand:     #ffffff;
}

[data-theme="dark"] {
  --seat-regular: #94a3b8;
  --seat-family:  #60a5fa;
  --seat-bar:     #c084fc;
  --seat-booth:   #22d3ee;
  --seat-outdoor: #4ade80;
  --seat-private: #fbbf24;
  --bg:           #0f172a;
  --bg2:          #1e293b;
  --bg3:          #334155;
  --text:         #f1f5f9;
  --text2:        #94a3b8;
  /* Lifted from #64748b. Dark mode is not an inversion: the same step that
     reads as "quiet" on white reads as "gone" on slate. */
  --text3:        #8896aa;
  --border:       #334155;
  --accent-on-surface: color-mix(in srgb, var(--accent) 45%, #ffffff);
  /* THE STEP STATES, ANSWERED AGAIN FOR SLATE. Dark mode is not an inversion:
     a #16A34A green that reads as "finished" on white is muddy on #0f172a, and
     a rose at 10% opacity disappears entirely. Each one steps toward light. */
  --step-done:        #4ADE80;
  --step-waiting-ink: #C89AA8;
  --step-waiting-bg:  rgba(200, 154, 168, .12);
  --step-blocked:     #F87171;

  /* Dark mode steps the other way: toward light, not toward dark. */
  --success-on-surface: #5ddaa0;
  --warning-on-surface: #f0b657;
  --danger-on-surface:  #f98a8a;
  /* DARK MODE IS NOT AN INVERSION. A shadow on a dark surface barely reads,
     so depth there comes mostly from the surface stepping LIGHTER — the
     shadows stay, deeper and softer, to keep edges from dissolving. */
  --shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.30);
  --shadow:       0 1px 3px rgba(0, 0, 0, 0.35);
  --shadow-md:    0 4px 12px rgba(0, 0, 0, 0.45);
  --shadow-lg:    0 12px 28px rgba(0, 0, 0, 0.55);
  --shadow-xl:    0 24px 64px rgba(0, 0, 0, 0.65);
  --surface-sunken: #172033;
}

[data-theme="blue"] {
  --bg:           #eff6ff;
  --bg2:          #ffffff;
  --bg3:          #dbeafe;
  --text:         #1e3a5f;
  --text2:        #3b5998;
  --text3:        #6b9fd4;
  --border:       #bfdbfe;
  --accent:       #1d4ed8;
  --accent-light: #1e40af;
  --accent-fill:  #1d4ed8;  --accent-fill-ink: #FFFFFF;
  /* The console screen is dark by design in both modes — it is a screen.
     What changes is the ink around it and the amber text, which must stay
     readable on a light-blue ground. */
  --warning-ink:    #92400e;
  --warning-ink-2:  #7c2d12;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* Enterprise/academic style: links are never underlined; emphasis via colour only */
a { color: var(--accent-on-surface); text-decoration: none; }
a:hover { text-decoration: none; }
/* Fading on hover is for TEXT links. A control drawn as a link (a.btn, an
   .ac-btn, anything with role=button) keeps its full label on hover — .85
   opacity took a 3.7:1 primary to 2.7:1 under the pointer. */
a:hover:not([class*="btn"]):not([class*="button"]):not([role="button"]):not([class*="cta"]) { opacity: .85; }

body.admin-body {
  font-family: var(--app-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  display: flex;
  transition: background 0.2s, color 0.2s;
}

/* ---- Sidebar ---- */
.sidebar {
  width: var(--sidebar-width);
  background: var(--bg2);
  border-inline-end: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  height: 100vh;
  position: fixed;
  top: 0; left: 0;
  overflow: hidden;             /* scrolling happens inside .sidebar-nav */
  /* Demo-mode banner uses z-index 5, so the sidebar drawer MUST sit far above
     to avoid the banner painting on top of menu items when the drawer slides
     open on mobile (the bug visible in IMG_1826). */
  z-index: 1200;
  transition: transform 0.25s ease, background 0.2s;
  box-shadow: var(--shadow-md);
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.logo-icon { font-size: 1.25rem; }
.logo-text { font-size: 1rem; font-weight: 700; color: var(--accent-on-surface); flex: 1; }
.sidebar-logo-img { max-height: 36px; max-width: 160px; width: auto; height: auto; object-fit: contain; }
.sidebar-toggle-btn { display: none; background: none; border: none; color: var(--text3); font-size: 1.25rem; cursor: pointer; }

.sidebar-nav { flex: 1; padding: 0.75rem 0; overflow-y: auto; }
.nav-section-label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text3); padding: 0.875rem 1.25rem 0.375rem; font-weight: 600; }

.nav-link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5625rem 1.25rem;
  color: var(--text2);
  text-decoration: none;
  font-size: 0.875rem;
  transition: all 0.15s;
  position: relative;
  border-radius: 0;
}
.nav-link:hover { background: rgba(59,130,246,0.06); color: var(--accent-on-surface); }
.nav-link.active { background: rgba(59,130,246,0.08); color: var(--accent-on-surface); font-weight: 500; }
.nav-link.active::before { content:''; position:absolute; left:0; top:20%; bottom:20%; width:3px; background:var(--accent); border-radius:0 2px 2px 0; }
.nav-icon { font-size: 1rem; width: 20px; text-align: center; flex-shrink: 0; }

/* ── Group header: icon + label + arrow on one row ──
   The group icon is rendered as a separate element (not embedded in
   the translatable label) so it survives translation — fixes the
   "most group icons missing" report. */
.nav-group-header { display: flex; align-items: center; gap: 0.55rem; cursor: pointer; }
/* Fixed-width icon box. NB: with box-sizing:border-box a width PLUS a big
   inline padding collapses the content to 0 and the emoji lands on the
   label's first letter (the desktop "icon overlaps text" bug). So: a real
   width and no inline padding — the row's gap does the spacing. */
.nav-group-icon { font-size: 1.05rem; width: 1.6rem; text-align: center; flex-shrink: 0; padding: 0; }
.nav-group-header .nav-section-label { flex: 1 1 auto; min-width: 0; padding-inline-start: 0 !important; }

/* ── Desktop = Mobile: ONE sidebar layout everywhere ──
   Oropherion 🦅 asked for desktop and mobile to be fully aligned. Rather
   than keep two divergent layouts (which is what caused the desktop icon
   overlap), desktop now renders the SAME icon-above-text card menu the
   phone uses — identical look, no overlap, nothing to keep in sync. */
@media (min-width: 901px) {
    .sidebar .nav-link,
    .sidebar .nav-group-header {
        display: flex !important; flex-direction: column; align-items: center; justify-content: center;
        gap: .3rem; text-align: center; min-height: 68px; padding: .7rem .4rem; position: relative;
    }
    .sidebar .nav-link { font-size: .82rem; line-height: 1.2; }
    .sidebar .nav-icon,
    .sidebar .nav-group-icon {
        display: inline-block !important; font-size: 1.7rem !important;
        width: auto !important; padding: 0 !important; margin: 0 !important; text-align: center; flex-shrink: 0;
    }
    .sidebar .nav-section-label { padding: 0 !important; margin: 0 !important; font-size: .78rem; flex: none !important; }
    .sidebar .nav-arrow { position: absolute; inset-inline-end: 12px; top: 12px; }
    .sidebar .nav-link.active::before,
    .sidebar .nav-link.nav-link--active::before {
        top: auto; bottom: 0; left: 20%; right: 20%; width: auto; height: 3px; border-radius: 2px 2px 0 0;
    }
    .sidebar .nav-badge { position: absolute; top: 8px; inset-inline-end: 12px; }
}

/* ── Item separation — Oropherion 🦅: "add a line of gap to be
   separated from each other." A hairline divider + a touch more
   vertical breathing room between every menu row so the list reads
   cleanly instead of running together. ── */
.nav-group-items .nav-link { border-bottom: 1px solid rgba(148,163,184,0.10); }
.nav-group-items .nav-link:last-child { border-bottom: 0; }
.nav-link { padding-block: 0.7rem; }            /* a little more height per row */
.nav-group { margin-bottom: 0.35rem; }          /* gap between whole groups */

/* ── "What this business has NOT got" — owner only, foot of the menu.
   Deliberately quiet: it is a signpost for one person, not a menu entry, so
   it must never compete with the real navigation above it. Closed by default,
   full 44px summary row, logical properties throughout so RTL is free. ── */
.nav-off { margin: 0.9rem 0.5rem 0.4rem; border-top: 1px solid rgba(148,163,184,0.18); padding-top: 0.5rem; }
.nav-off > summary {
  display: flex; align-items: center; gap: 0.5rem; min-height: 44px;
  padding: 0.4rem 0.75rem; cursor: pointer; list-style: none;
  color: var(--text2); font-size: 0.82rem; opacity: 0.85; border-radius: 9px;
}
.nav-off > summary::-webkit-details-marker { display: none; }
.nav-off > summary:hover { background: rgba(148,163,184,0.10); opacity: 1; }
.nav-off-body { padding: 0.2rem 0.75rem 0.5rem; font-size: 0.8rem; color: var(--text2); }
.nav-off-body p { margin: 0 0 0.45rem; opacity: 0.8; line-height: 1.45; }
.nav-off-body ul { margin: 0 0 0.6rem; padding-inline-start: 1.1rem; }
.nav-off-body li { padding: 0.12rem 0; }
.nav-off-cta {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--primary, #4F46E5); font-weight: 700; text-decoration: none;
}
.nav-off-cta:hover { text-decoration: underline; }

/* On phone / tablet: icon ABOVE the writing, for BOTH group headers
   AND item links — uniformly. Oropherion 🦅 asked for this directly:
   the icon and text were overlapping in the horizontal layout
   ("icon hiding the writing"). Stacking the icon on top, text below,
   centred, makes overlap impossible. Applying it to headers AND
   items (not just items) keeps the menu CONSISTENT — the earlier
   "mixed" look came from stacking only the items. */
@media (max-width: 900px) {
    .nav-link,
    .nav-group-header {
        display: flex !important;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 0.3rem;
        text-align: center;
        min-height: 70px;
        padding: 0.8rem 0.4rem;
        position: relative;
    }
    .nav-link { font-size: 0.82rem; line-height: 1.2; }

    /* Icon sits on its own line, centred, larger, with NO inline
       padding (the old padding-inline-start is what shoved it onto
       the text). */
    .nav-icon,
    .nav-group-icon {
        font-size: 1.7rem !important;
        width: auto !important;
        padding: 0 !important;
        margin: 0 !important;
        text-align: center;
        flex-shrink: 0;
    }
    /* Label on its own line below the icon — kill the desktop side
       padding that was colliding with the icon. */
    .nav-section-label {
        padding: 0 !important;
        margin: 0 !important;
        font-size: 0.78rem;
        flex: none !important;
    }

    /* Group-header collapse arrow → pin to the corner so it doesn't
       sit awkwardly below the stacked icon + title. */
    .nav-arrow { position: absolute; inset-inline-end: 12px; top: 12px; }

    /* Active-item accent → bottom underline (the left edge-stripe
       doesn't read in a centred stack). */
    .nav-link.active::before {
        top: auto; bottom: 0; left: 20%; right: 20%;
        width: auto; height: 3px; border-radius: 2px 2px 0 0;
    }
    .nav-badge { position: absolute; top: 8px; inset-inline-end: 12px; }
}
/* Sidebar nav badge — bold red pill with a soft pulsing halo so a new
   quote / message immediately catches the eye. Used by site-inbox /
   contact-inbox in the sidebar. */
.nav-badge {
    margin-inline-start: auto;
    background: #dc2626; color: #fff;
    border-radius: 999px;
    font-size: 0.75rem;
    padding: .1rem .5rem;
    font-weight: 800;
    box-shadow: 0 0 0 0 rgba(220,38,38,.55);
    animation: navBadgePulse 2s ease-in-out infinite;
}
@keyframes navBadgePulse {
    0%,100% { box-shadow: 0 0 0 0 rgba(220,38,38,.55); }
    50%     { box-shadow: 0 0 0 6px rgba(220,38,38,0); }
}
@media (prefers-reduced-motion: reduce) {
    .nav-badge { animation: none; }
}

.sidebar-footer {
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
  background: var(--bg2);
  position: sticky;
  bottom: 0;
  box-shadow: 0 -8px 16px -8px rgba(0,0,0,.05);
}
.user-avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; color: var(--accent-ink, #fff); flex-shrink: 0; }
.user-meta { flex: 1; min-width: 0; }
.user-name { font-size: 0.8125rem; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-role { font-size: 0.75rem; color: var(--text3); text-transform: capitalize; }
.logout-btn { background: none; border: none; color: var(--text3); font-size: 1.125rem; cursor: pointer; padding: 0.25rem; transition: color 0.15s; }

/* ── topbar controls: measured, and deliberately left alone ────────────────
 * Chromium at 1440px: sign-out is 22x29 and the ⋯ menu 34x29. At 390px both are
 * already 44x44 from the mobile rules, so the platform's actual claim — 44px on
 * TOUCH widths — is met.
 *
 * I tried to grow the desktop hit area with the invisible ::before pad the
 * session chip and help dot use. It broke Arabic and Kurdish: the two buttons
 * sit about 6px apart, so a 44px pad on one reaches across its neighbour, and
 * whichever paints last swallows the other's clicks. In right-to-left the paint
 * order put the pad on top of the menu button and the drawer could not be opened
 * AT ALL — a control that worked in English and was dead in Arabic. Chromium
 * found it; no test would have.
 *
 * So the pad is gone. A desktop pointer minimum is not something this platform
 * has claimed, and inventing one here traded a real RTL regression for a
 * standard nobody asked for. The numbers are recorded for the responsive phase,
 * where widening the topbar's spacing is the fix rather than overlapping pads. */

.logout-btn:hover { color: var(--danger); }

/* ---- Main ---- */
/* min-width: 0 AT EVERY WIDTH, NOT ONLY ON A PHONE.
   A flex item defaults to `min-width: auto`, which means "never narrower than
   my content". So .admin-main refused to shrink below its widest child and the
   240px sidebar margin then pushed the whole page past the viewport: measured
   at 1280px, every admin screen scrolled sideways by 130-185px, including
   /admin and /admin/users, which nobody had changed in months.
   The rule already existed inside the <=768px media query, so phones were
   fine and the desktop the owner actually works on was not. Tables and
   diagrams keep their own overflow-x wrappers, which is where sideways
   scrolling belongs. Found by measuring the rendered page in a browser;
   no test could see it. */
.admin-main { margin-inline-start: var(--sidebar-width); flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 100vh; }

.admin-topbar {
  height: var(--topbar-height);
  background: var(--bg2);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  padding: 0 1.5rem;
  gap: 1rem;
  position: sticky;
  top: 0;
  /* Sit above every sticky page-header (z:50 in admin.css), modernised
     card / data-table header (z:1), and front-front overlay elements
     so the More ⋯ dropdown is never clipped or covered by content
     scrolling underneath. */
  z-index: 100;
  box-shadow: var(--shadow);
  /* A sticky bar that cannot shrink makes the whole document scroll sideways. */
  min-inline-size: 0;
}
.topbar-menu-btn { display: none; background: none; border: none; color: var(--text2); font-size: 1.25rem; cursor: pointer; }
.topbar-breadcrumb { flex: 1; display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; color: var(--text2); }
.breadcrumb-sep { color: var(--text3); }
/* THE TOPBAR'S RIGHT-HAND CLUSTER MUST BE ABLE TO SHRINK AND WRAP.
   Same default as .admin-main above — `min-width: auto` on a flex item means
   "never narrower than my content" — and this cluster holds the session pill,
   the theme and palette buttons, presence and the More menu. Together they
   measured 887px, which pushed every admin page sideways by ~160px at 1280px
   even after the main column was fixed. Found in a browser at a real desktop
   width; no test could see it, and the phone breakpoints hid it from anyone
   testing responsively. min-inline-size keeps the rule reading correctly in
   Arabic and Kurdish. */
.topbar-right { display: flex; align-items: center; gap: 0.75rem; min-inline-size: 0; flex-wrap: wrap; justify-content: flex-end; }
.dialpad-status { font-size: 0.8125rem; color: var(--success); display: flex; align-items: center; gap: 0.375rem; background: rgba(22,163,74,0.08); padding: 0.25rem 0.625rem; border-radius: 100px; }

/* ══════════════════════════════════════════════════════════════════════
   THE SECURE-SESSION CHIP — how long you have, at a glance, in its place.

   A small quiet chip in the topbar, on every screen size. The first cut
   of this floated a large fixed pill over the page on phones — it covered
   the interface it was meant to serve, and the owner rightly called it
   out ("the timer hid the menu bar; I said fix it, not make it that
   big"). A countdown is ambient information: it must be readable in a
   glance and invisible the rest of the time. The keep-alive script is
   what actually protects the operator; the chip only reports.

   Colour is the platform's grammar: green while there is plenty, the
   sun's amber when it is time to act, welded red at the end — the pulse
   alone signals urgency, never a size change.
   ══════════════════════════════════════════════════════════════════════ */
.sess-pill {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .2rem .3rem .2rem .55rem; border-radius: 100px;
  border: 1px solid color-mix(in srgb, var(--sess, #16a34a) 40%, transparent);
  background: color-mix(in srgb, var(--sess, #16a34a) 8%, var(--bg2));
  /* The pill's TEXT is a clock somebody has to read at a glance, so it uses
     the readable step of its own state colour rather than the fill value —
     the same distinction the badges make. The border and the tint keep the
     fill colour, which is what makes the state recognisable. */
  color: var(--sess-ink, var(--success-on-surface, #0f7a37));
  --sess: #16a34a;
}
.sess-pill.warn   { --sess: #d97706; --sess-ink: var(--warning-on-surface, #9a5b06); }
.sess-pill.urgent { --sess: #B91C1C; --sess-ink: var(--danger-on-surface, #b31d1d);
                    animation: sess-beat 1.6s ease-in-out infinite; }
@keyframes sess-beat { 0%,100% { opacity: 1 } 50% { opacity: .68 } }
.sess-pill .sess-clock {
  font-variant-numeric: tabular-nums; font-weight: 800;
  font-size: .84rem; line-height: 1; letter-spacing: .01em;
}
/* A quiet renewal says so beside the clock for a moment — visible on the
   phone layout too, where the button's own text is hidden. */
.sess-pill.auto .sess-clock::after { content: ' ✓'; }
/* The label is spoken, not shown: as visible text it wrapped and inflated the
   chip to 80px tall on a 56px bar. The clock carries it as an aria-label, the
   pill's title explains the whole control, and the element stays in the DOM
   because a REFUSED renewal reveals it — that message must be read, not
   hovered. */
.sess-pill .sess-label { display: none; font-size: 0.75rem; font-weight: 700; }
/* Compact is the DEFAULT; the drain bar and the button's words only appear
   where there is genuinely room. Measured: at 1280px with the sidebar open,
   the full-dress chip (215px) pushed the ⋯ menu off the bar — so full dress
   starts at 1400px, and everywhere else the chip is the same ~97px the old
   dial-pad counter always was. */
.sess-pill .sess-bar { display: none; width: 36px; height: 4px; border-radius: 999px; background: color-mix(in srgb, var(--sess) 22%, transparent); overflow: hidden; }
.sess-pill .sess-keep .sess-keep-txt { display: none; }
@media (min-width: 1600px) {
  .sess-pill .sess-bar { display: block; }
  .sess-pill .sess-keep .sess-keep-txt { display: inline; }
}

/* THE CHIP PAYS ITS OWN RENT. Measured: the topbar fits at every width
   WITHOUT the chip and overflows WITH it — so while the chip exists it
   borrows the bar's most dispensable neighbour, and gives it back the moment
   the secure session ends (`:has` — no JS, no state to forget). The search
   trigger's ⌘K shortcut keeps working while its button steps aside. */
@media (max-width: 1599px) {
  .topbar-right:has(.sess-pill) .pal-trigger { display: none !important; }
}
@media (max-width: 400px) {
  /* The smallest phones: the language select gives up a few letters, and the
     hide-the-bar toggle steps aside while the chip exists (its whole job is
     hiding a bar the chip needs to live in). Itemised at 360px: hiding the
     toggle bought ~50px, which is why the clock and the lock glyph do NOT
     shrink here — the timer stays readable on the smallest screen. */
  .admin-topbar .topbar-lang select { max-width: 62px; }
  /* The hide-the-bar toggle also steps aside here, but its rule lives in
     layouts/admin.blade.php beside the !important display it must beat. */
}
.sess-pill .sess-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--sess); transition: width 1s linear; }
.sess-pill .sess-keep {
  font: inherit; font-size: 0.75rem; font-weight: 800; cursor: pointer;
  color: #fff; background: var(--sess); border: 0;
  border-radius: 100px; padding: .25rem .55rem; min-height: 26px; white-space: nowrap;
  position: relative;
}
.sess-pill .sess-keep:disabled { opacity: .55; cursor: default; }
/* The 44px touch rule, kept without bulk: an invisible pad around the small
   button — the same trick as the ? help dot. */
/* The visible pill is deliberately small; the TAP TARGET is this. It was
   -9px around a 24px button, which is 42px — two short of the rule that the
   rest of the platform keeps. */
.sess-pill .sess-keep::before { content: ''; position: absolute; inset: -10px; }

/* Phones: the chip stays exactly where it is, smaller still. Icon, clock,
   a ＋ — the words go; the auto-renew script is doing the real work. */
@media (max-width: 768px) {
  .sess-pill { gap: .3rem; padding: .15rem .25rem .15rem .5rem; border-width: 1.5px; }
  /* READABLE is the point — "time is important when it is ticking" (the
     owner). The first phone cut went to .7rem, which answered "too big"
     with nearly-invisible; .85rem bold reads at arm's length and the chip
     stays ~31px tall. */
  .sess-pill .sess-clock { font-size: .85rem; }
  .sess-pill .sess-keep { min-width: 26px; padding: .2rem .4rem; }
  /* The globe is decoration; the select is the control. Measured at 390px the
     bar ran past the screen with the chip in it — the globe and this gap were
     part of that margin. */
  .topbar-lang > span:first-child { display: none; }
  .topbar-right { gap: .3rem; }
}
@media (max-width: 480px) {
  /* The narrowest screens cannot hold the theme toggle AND the session chip:
     measured at 390px the ⋯ menu sat ~44px past the edge. The chip only
     exists during a dial-pad window, so the toggle steps aside for exactly
     that long and returns the moment the secure session ends. */
  .topbar-right:has(.sess-pill) .topbar-btn[onclick="adminCycleTheme()"] { display: none; }
}

/* Nearly out. Amber and a slow pulse rather than red and a fast one: this is a
   heads-up while something can still be done about it, not an alarm. */
.dialpad-status.ending-soon { color: var(--warning, #b45309); background: rgba(245,158,11,0.14); animation: dialpad-soon 2s ease-in-out infinite; }
@keyframes dialpad-soon { 0%,100% { opacity: 1 } 50% { opacity: 0.62 } }
/* "Keep me in". 44px tall on touch so it is not a dare — the platform's own
   rule, and this button is pressed under time pressure by definition. */
.dialpad-extend { font: inherit; font-size: 0.75rem; font-weight: 700; color: inherit; background: transparent;
  border: 1px solid currentColor; border-radius: 100px; padding: 0.1rem 0.5rem; cursor: pointer; line-height: 1.4; }
.dialpad-extend:hover { background: rgba(0,0,0,0.06); }
.dialpad-extend:disabled { opacity: 0.5; cursor: default; }
@media (hover: none) { .dialpad-extend { min-height: 44px; padding-inline: 0.75rem; } }
.topbar-btn { background: none; border: none; color: var(--text2); font-size: 1rem; cursor: pointer; text-decoration: none; padding: 0.375rem; border-radius: var(--radius); transition: all 0.15s; }
.topbar-btn:hover { background: var(--bg3); color: var(--text); }

/* Theme switcher */
.theme-switcher { display: flex; gap: 0.25rem; background: var(--bg3); border-radius: 100px; padding: 0.25rem; }
.theme-btn { width: 24px; height: 24px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; transition: all 0.15s; }
.theme-btn:hover { transform: scale(1.15); }
.theme-btn.active { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(59,130,246,0.3); }
.theme-btn-light { background: #f8fafc; border-color: #e2e8f0; }
.theme-btn-dark  { background: #1e293b; }
.theme-btn-blue  { background: #3b82f6; }

/* NOTHING SITS UNDER THE FLOATING FURNITURE.
   The session timer (bottom-left), the demo chip (bottom-left) and the chrome
   pill (bottom-right) are `position:fixed`, so the page scrolls underneath
   them and the LAST rows of every admin screen were being read through a
   translucent pill. Seen on a real phone: "The site answers — Healthy" on
   Platform status sat behind "Session: 0:59:59", and the Domains page's
   closing paragraph behind the same.
   The fix belongs here, once, rather than in each page: reserve the height of
   that furniture at the end of the scroll, plus the phone's home-indicator
   inset. `--floating-gutter` is a single token so a future pill only has to
   agree with one number. */
:root { --floating-gutter: 3.25rem; }

.admin-content {
  flex: 1;
  padding: 1.5rem;
  padding-bottom: calc(1.5rem + var(--floating-gutter) + env(safe-area-inset-bottom, 0px));
  max-width: 1400px;
  width: 100%;
}

/* THE FULL-BLEED TOKEN.
   `.page-header` and `.mod-toolbar` pull themselves outside the content
   padding with a negative margin so they span the whole width. That margin
   was hardcoded at -1rem while the gutter shrinks to .875rem on a phone, so
   on every one of the 99+ legacy pages the sticky header stuck 2px past each
   edge and the page scrolled sideways. The gutter now publishes its own
   value and the bleed reads it, so the two can never disagree again. */
:root { --content-gutter: 1.5rem; }

/* ---- Flash messages ---- */
.flash { padding: 0.875rem 1.25rem; border-radius: var(--radius); margin-bottom: 1.25rem; font-size: 0.9rem; display: flex; align-items: center; gap: 0.625rem; border: 1px solid; }
.flash-success { background: rgba(22,163,74,0.08); border-color: rgba(22,163,74,0.2); color: #16a34a; }
.flash-error   { background: rgba(220,38,38,0.08); border-color: rgba(220,38,38,0.2); color: #dc2626; }
.flash-warning { background: rgba(217,119,6,0.08); border-color: rgba(217,119,6,0.2); color: #d97706; }

/* ---- Page header — modernized to match .mod-toolbar
   so legacy pages (99+ views still using .page-header) inherit
   the same sticky-toolbar look as the modernized pages without
   any per-view edits. ---- */
.page-header {
    position: sticky; top: 0; z-index: 50;
    margin: calc(var(--content-gutter) * -1) calc(var(--content-gutter) * -1) 1.25rem;
    padding: .85rem 1.25rem;
    background: rgba(255,255,255,.92);
    -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--border);
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: 0.75rem;
}
.page-title {
    display: inline-flex; align-items: center; gap: .6rem;
    font-size: 1.05rem; font-weight: 800; color: var(--text);
    letter-spacing: -0.015em;
}
.page-title::before {
    content: ''; width: 8px; height: 8px; border-radius: 50%;
    background: var(--accent, #3b82f6); flex-shrink: 0;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, #3b82f6) 18%, transparent);
}
.page-actions { display: flex; gap: 0.45rem; align-items: center; flex-wrap: wrap; }
/* Soften legacy buttons inside the new toolbar so they read as
   the modern .mod-btn style without changing any view markup. */
.page-actions .btn { border-radius: var(--radius); padding: .55rem 1rem; font-weight: 600; font-size: .88rem; }
.page-actions .btn-primary { box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--accent, #3b82f6) 55%, transparent); }

/* ---- Cards ---- */
.card { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow); }
.card-header { padding: 1rem 1.25rem; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; background: var(--bg2); }
.card-title { font-size: 0.9375rem; font-weight: 600; color: var(--text); }
.card-body { padding: 1.25rem; }

/* ── ACTION CARDS ──────────────────────────────────────────────────────────
   The pattern the owner singled out on /admin/launch as the one that feels
   right on a phone — and it is worth being precise about WHY, because the
   answer is not decoration.

   A phone is ~360px wide. `auto-fill minmax(170px, 1fr)` puts exactly TWO
   cards per row there and more as the screen grows, so the same markup is a
   comfortable two-up on a handset and a dense grid on a desktop without a
   single breakpoint. Each card is a large, centred, single-purpose target with
   its own icon and one short label: nothing to aim at precisely, nothing to
   read twice.

   That is the whole difference from a shrunk desktop page. A table squeezed
   into 360px asks the thumb to hit a 20px link inside a scrolling row; this
   asks it to hit a 100px-tall card.

   It lives here, next to .stats-grid, because it is a MEMBER OF THE SAME
   FAMILY rather than a second design system — same tokens, same radius scale,
   same border colour. RTL is free: every property here is logical or
   direction-neutral, so nothing needs mirroring.
   ── */
.action-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 0.75rem; }
.action-card {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.35rem; min-height: 104px; padding: 1rem; text-align: center;
  border: 2px solid var(--border); border-radius: 12px; background: var(--bg2);
  color: inherit; text-decoration: none; cursor: pointer; transition: border-color 0.15s, background 0.15s, transform 0.15s;
}
.action-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.action-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.action-card-icon  { font-size: 1.8rem; line-height: 1; }
.action-card-label { font-weight: 600; font-size: 0.875rem; }
.action-card-hint  { font-size: 0.75rem; color: var(--text3); }
.action-card.is-selected,
input:checked + .action-card { border-color: var(--accent); background: rgba(46,91,186,.06); }

/* Narrow handsets: keep TWO per row rather than collapsing to one. A single
   column turns a six-item grid into a page of scrolling, which is exactly the
   "squeezed desktop" feeling this pattern exists to avoid. */
@media (max-width: 420px) {
  .action-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0.6rem; }
  .action-card { min-height: 96px; padding: 0.85rem; }
}

/* ---- Stats grid ---- */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
.stat-card { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 1.25rem; box-shadow: var(--shadow); transition: transform 0.15s, box-shadow 0.15s; }
.stat-card:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); }
.stat-label { font-size: 0.75rem; color: var(--text3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 0.5rem; font-weight: 600; }
.stat-value { font-size: 1.875rem; font-weight: 700; color: var(--text); line-height: 1; }
.stat-sub { font-size: 0.8125rem; color: var(--text3); margin-top: 0.375rem; }
.stat-card.accent { border-inline-start: 3px solid var(--accent); }
.stat-card.accent .stat-value { color: var(--accent-on-surface); }
.stat-card.success { border-inline-start: 3px solid var(--success); }
.stat-card.success .stat-value { color: var(--success); }
.stat-card.warning { border-inline-start: 3px solid var(--warning); }
.stat-card.warning .stat-value { color: var(--warning); }
.stat-card.danger  { border-inline-start: 3px solid var(--danger); }
.stat-card.danger .stat-value  { color: var(--danger); }

/* ---- Tables (premium platform-wide) ---- */
.table-wrap { overflow-x: auto; border-radius: 12px; border: 1px solid var(--border); background: var(--bg2); box-shadow: 0 1px 2px rgba(15,23,42,0.04); }
table.data-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 0.875rem; }
table.data-table thead th {
    padding: 0.85rem 1.1rem; text-align: start; font-size: var(--type-floor, 0.75rem);
    text-transform: uppercase; letter-spacing: 0.08em; color: var(--text3);
    background: linear-gradient(180deg, var(--bg3), color-mix(in srgb, var(--bg3) 60%, transparent));
    border-bottom: 1px solid var(--border); white-space: nowrap; font-weight: 700;
    position: sticky; top: 0; z-index: 2;
}
table.data-table thead th:first-child { border-top-left-radius: 12px; }
table.data-table thead th:last-child  { border-top-right-radius: 12px; }
table.data-table tbody td {
    padding: 0.9rem 1.1rem; border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
    color: var(--text2); vertical-align: middle; line-height: 1.4;
}
table.data-table tbody tr { transition: background .15s, transform .15s; }
table.data-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--bg3) 25%, transparent); }
table.data-table tbody tr:hover { background: rgba(59,130,246,0.05); }
table.data-table tbody tr:last-child td { border-bottom: none; }
table.data-table tbody tr:last-child td:first-child { border-bottom-left-radius: 12px; }
table.data-table tbody tr:last-child td:last-child  { border-bottom-right-radius: 12px; }
table.data-table img { vertical-align: middle; }
table.data-table tbody td img { max-height: 44px; max-width: 64px; border-radius: 6px; object-fit: cover; box-shadow: 0 1px 3px rgba(15,23,42,0.12); background: var(--bg3); }
/* Auto-wrap a bare .data-table inside a scroll/border container if it isn't already inside .table-wrap. */
.card > table.data-table, body > table.data-table { border-radius: 12px; }

/* ---- Badges ---- */
.badge { display: inline-flex; align-items: center; padding: 0.25rem 0.625rem; border-radius: 100px; font-size: 0.75rem; font-weight: 600; white-space: nowrap; }
.badge-pending      { background: #fef9c3; color: #854d0e; }
.badge-active       { background: #dcfce7; color: #166534; }
.badge-delivered    { background: #dcfce7; color: #166534; }
.badge-shipped      { background: #dbeafe; color: #1e40af; }
.badge-in_transit   { background: #e0f2fe; color: #075985; }
.badge-out_for_delivery { background: #fce7f3; color: #9d174d; }
.badge-cancelled    { background: #fee2e2; color: #991b1b; }
.badge-on_hold      { background: #fef3c7; color: #92400e; }
.badge-returned     { background: #f3e8ff; color: #6b21a8; }
.badge-draft        { background: #f1f5f9; color: #475569; }
.badge-paid         { background: #dcfce7; color: #166534; }
.badge-overdue      { background: #fee2e2; color: #991b1b; }
.badge-processing   { background: #e0f2fe; color: #075985; }
.badge-ready        { background: #d1fae5; color: #065f46; }
.badge-contacted    { background: #dbeafe; color: #1e40af; }

[data-theme="dark"] .badge-pending   { background: rgba(234,179,8,0.15);   color: #fbbf24; }
[data-theme="dark"] .badge-active    { background: rgba(34,197,94,0.15);   color: #4ade80; }
[data-theme="dark"] .badge-delivered { background: rgba(34,197,94,0.15);   color: #4ade80; }
[data-theme="dark"] .badge-shipped   { background: rgba(59,130,246,0.15);  color: #60a5fa; }
[data-theme="dark"] .badge-cancelled { background: rgba(239,68,68,0.15);   color: #f87171; }
[data-theme="dark"] .badge-draft     { background: rgba(100,116,139,0.15); color: #94a3b8; }
[data-theme="dark"] .badge-paid      { background: rgba(34,197,94,0.15);   color: #4ade80; }
[data-theme="dark"] .badge-overdue   { background: rgba(239,68,68,0.15);   color: #f87171; }

/* ---- Buttons ---- */
.btn { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.5rem 1rem; border-radius: var(--radius); font-size: 0.875rem; font-weight: 500; cursor: pointer; border: 1px solid transparent; transition: all 0.15s; text-decoration: none; white-space: nowrap; }
/* Disabled is a different LOOK, not a faded one. Opacity .5 took every label
   below 3:1; this keeps it readable and still plainly unavailable. */
.btn:disabled, .btn[aria-disabled="true"] { background: var(--bg3); color: var(--text2); border: 1px dashed var(--border); box-shadow: none; filter: none; cursor: not-allowed; }
/* A BUTTON WITH NO VARIANT IS STILL A BUTTON. `.btn` alone sets only layout —
   transparent border, no background — so seventy controls across the estate
   rendered as bare text a reader had no reason to think was clickable, which
   fails the platform's own rule that primary and secondary must be obvious
   without reading. Anything carrying no variant now inherits the secondary
   surface; naming a variant still wins. */
.btn:not(.btn-primary):not(.btn-secondary):not(.btn-success):not(.btn-danger):not(.btn-warning):not(.btn-icon) {
  background: var(--bg2); color: var(--text2); border-color: var(--border);
}
.btn:not(.btn-primary):not(.btn-secondary):not(.btn-success):not(.btn-danger):not(.btn-warning):not(.btn-icon):hover {
  background: var(--bg3); color: var(--text);
}
/* THE INK ON AN ACCENT FILL IS A TOKEN, NOT A LITERAL.
   Owner acceptance, Sep 2026: the Invoices page showed "prominent blue controls
   whose text is not visible". The label was in the markup and the translation
   existed — the colour was hardcoded. TokenRegistry publishes `accent_ink`
   precisely so an operator whose brand accent is light can darken the text on
   it; `color:#fff` overrode that, so the one control the whole screen is for
   rendered as a blank blue rectangle. A tooltip explaining an invisible button
   is not a fix, and every fill below now reads the same token. */
.btn-primary   { background: var(--accent-fill, var(--accent)); color: var(--accent-fill-ink, #fff); border-color: var(--accent-fill, var(--accent)); }
.btn-primary:hover { background: var(--accent-fill, var(--accent)); filter: brightness(.92); box-shadow: 0 2px 8px rgba(59,130,246,0.3); }
.btn-secondary { background: var(--bg2); color: var(--text2); border-color: var(--border); }
.btn-secondary:hover { background: var(--bg3); color: var(--text); }
.btn-success   { background: var(--success-fill, var(--success)); color: var(--success-fill-ink, #fff); border-color: var(--success-fill, var(--success)); }
.btn-success:hover { filter: brightness(.92); }
.btn-danger    { background: transparent; color: var(--danger); border-color: rgba(220,38,38,0.3); }
.btn-danger:hover { background: rgba(220,38,38,0.08); }
.btn-warning   { background: var(--warning-fill, var(--warning)); color: var(--warning-fill-ink, #fff); border-color: var(--warning-fill, var(--warning)); }
.btn-sm { padding: 0.3125rem 0.625rem; font-size: 0.8125rem; }
.btn-icon { padding: 0.375rem; width: 32px; height: 32px; justify-content: center; }

/* ---- Forms ---- */
.form-group { margin-bottom: 1.25rem; }
.form-label { display: block; font-size: 0.8125rem; color: var(--text2); margin-bottom: 0.375rem; font-weight: 500; }
.form-control, .form-select { width: 100%; padding: 0.5625rem 0.875rem; background: var(--bg2); border: 1.5px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 0.9375rem; outline: none; transition: border-color 0.2s, box-shadow 0.2s; }
.form-control:focus, .form-select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(59,130,246,0.1); }
.form-control::placeholder { color: var(--text3); }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.form-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; }
.form-hint { font-size: 0.75rem; color: var(--text3); margin-top: 0.25rem; }
.input-group { display: flex; }
.input-group .form-control { border-radius: var(--radius) 0 0 var(--radius); flex: 1; }
.input-group .btn { border-radius: 0 var(--radius) var(--radius) 0; }

/* ---- Pagination ---- */
.pagination { display: flex; gap: 0.375rem; align-items: center; margin-top: 1rem; }
.page-btn { padding: 0.375rem 0.75rem; border-radius: var(--radius); border: 1px solid var(--border); background: var(--bg2); color: var(--text2); font-size: 0.875rem; cursor: pointer; transition: all 0.15s; text-decoration: none; }
.page-btn:hover, .page-btn.active { background: var(--accent); color: var(--accent-ink, #fff); border-color: var(--accent); }

/* ---- Dial pad overlay ---- */
.dialpad-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 999; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); }
.dialpad-modal { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 2rem; width: 340px; text-align: center; box-shadow: var(--shadow-md); }
.dialpad-modal h3 { color: var(--text); margin-bottom: 0.375rem; }
.dialpad-modal p  { color: var(--text3); font-size: 0.875rem; margin-bottom: 1.25rem; }
.dp-display { background: var(--bg3); border: 1.5px solid var(--border); border-radius: var(--radius); padding: 0.875rem; text-align: center; font-size: 1.75rem; letter-spacing: 0.5em; color: var(--accent-on-surface); margin-bottom: 1rem; font-family: monospace; min-height: 3.5rem; }
.dp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; margin-bottom: 1rem; }
.dp-key { padding: 0.875rem; border-radius: var(--radius); border: 1.5px solid var(--border); background: var(--bg2); color: var(--text); font-size: 1.125rem; font-weight: 500; cursor: pointer; transition: all 0.1s; }
.dp-key:hover { background: var(--bg3); border-color: var(--accent); }
.dp-key:active { transform: scale(0.94); }

/* ---- Responsive ---- */
@media (max-width: 768px) {
  .sidebar { transform: translateX(-100%); z-index: 1200; }
  .sidebar.open { transform: translateX(0); box-shadow: var(--shadow-md); }
  /* OFF-CANVAS MEANS OUT OF THE TAB ORDER TOO. Translated off-screen, the
     drawer's links still took keyboard focus first on every page — a keyboard
     or switch user tabbed through an invisible menu before the content.
     Hidden once the slide-out finishes; visible at once on open. */
  .sidebar:not(.open) { visibility: hidden; transition: transform .22s ease, box-shadow .22s ease, visibility 0s linear .22s; }
  .sidebar.open { visibility: visible; }
  /* Backdrop blocks taps on page content under the open drawer AND visually
     separates the menu from the demo banner peeking through. */
  body.sidebar-backdrop::before {
      content: ''; position: fixed; inset: 0; background: rgba(15, 23, 42, .45);
      z-index: 1100; animation: sidebarFadeIn .15s ease both;
  }
  @keyframes sidebarFadeIn { from { opacity: 0 } to { opacity: 1 } }
  .sidebar-toggle-btn { display: block; }
  .topbar-menu-btn { display: block; }
  .admin-main { margin-inline-start: 0; }
  .form-row, .form-row-3 { grid-template-columns: 1fr; }
  /* HR filter bars: each field full-width and stacked on phones. */
  .filter-field { flex: 1 1 100% !important; min-width: 0 !important; }
  .stats-grid { grid-template-columns: repeat(2, 1fr); }

  /* Mobile header fit: keep the brand/logo inside its bar, never overflow.

     WRAP RATHER THAN CLIP. This bar was flex-wrap:nowrap, so on a 320px phone
     it needed 353px and simply cut 33px off the end — taking real controls
     with it. Wrapping costs one extra row on the narrowest phones and only
     engages when the content genuinely does not fit, so nothing changes at
     360px and above. Hiding the back or chrome buttons instead would have
     traded one unreachable control for another. */
  .admin-topbar { padding-inline: .5rem; gap: .35rem; }
  .topbar-right { gap: .35rem; }
  .topbar-btn { padding: .3rem; font-size: .95rem; }
  /* THE BREADCRUMB IS NOT SHOWN BELOW DESKTOP, BECAUSE IT CANNOT BE READ
     THERE AND IS NOT NEEDED THERE.
     Its three segments — workspace / separator / current page — competed
     with the topbar's right-hand cluster and each was ellipsised until none
     of them said anything. Measured at 768px: the workspace "Zhyri" was cut
     to 8px of 40, and "Control plane / Demo & test accounts" to 54px of 290.
     Giving the current page the whole track was tried first and still left
     it 79px, because the track itself is what the cluster crushes.
     Every admin page carries its own title immediately below the bar —
     measured at 768px: "Platform control plane" 279px, "Demo & test
     accounts" 273px, "Platform estate" 194px — so the bar was repeating,
     illegibly, a name the page already states legibly. This drops the
     repetition rather than shrinking it further. The rule already existed
     at 480px; it starts at the desktop breakpoint instead. */
  .topbar-breadcrumb { display: none; }
  /* The sidebar brand/logo must fit the drawer width on phones. */
  .sidebar-logo { padding: .6rem .75rem; min-height: var(--topbar-height); }
  .sidebar-logo-img { max-height: 30px; max-width: 130px; }
  .logo-name-el { font-size: .95rem; }
  .logo-slogan-wrap { font-size: 0.75rem; }
  /* Language select can shrink on mobile. */
  .admin-topbar select { max-width: 92px; font-size: .8rem; }

  /* ── Mobile topbar fit: hide the noisy / advanced controls so the
       essentials (theme · language · dial-pad · More menu) stay on
       screen and clickable on a phone. The hidden items remain reachable
       from the sidebar and their own pages — nothing is lost. ──        */
  .topbar-right .notif-badge,                              /* 7 colour badges */
  .topbar-right > a[href$="/admin/activity"],              /* "X users online" pill */
  .topbar-right > a[href$="/admin/guard"],                 /* Guard Vault button */
  .topbar-right .pal-trigger                               /* Search palette */
    { display: none !important; }
  /* Note: do NOT put overflow:auto on .topbar-right — it triggers
     overflow-y clipping per CSS spec, which then clips the ⋯ "More"
     dropdown that's absolutely-positioned below the button. The hiding
     rules above are what keeps the topbar tidy on phones. */

  /* Sidebar footer (user / logout) must always be reachable + tappable on mobile.
     Pin it to the bottom of the drawer and give it solid contrast + bigger hit area. */
  .sidebar { padding-bottom: 0; }
  .sidebar-footer {
    position: sticky;
    bottom: 0;
    background: var(--bg2);
    border-top: 1px solid var(--border);
    box-shadow: 0 -4px 12px rgba(0,0,0,.10);
    padding: .9rem 1rem;
    z-index: 2;
  }
  .sidebar-footer .user-avatar { width: 38px; height: 38px; font-size: .85rem; }
  /* The account name is the one label that tells the operator WHICH account
     is acting. Truncating it to "Platform Ow…" on a phone — with the drawer
     at full width and room to spare — hid exactly that. It wraps here
     instead; the footer is not a row that can be widened by it. */
  .sidebar-footer .user-name {
    font-size: .9rem;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    overflow-wrap: break-word;
  }
  .logout-btn { font-size: 1.4rem; padding: .5rem; }
}
@media (max-width: 480px) {
  .stats-grid { grid-template-columns: 1fr; }
  .admin-content { padding: 1rem; --content-gutter: 1rem; }
  .admin-topbar select { max-width: 76px; }
  .sidebar-logo-img { max-height: 28px; max-width: 110px; }
}

/* ═══ LOGO ANIMATIONS ═══════════════════════════════════════════ */
@keyframes logoScrollLeft  { 0%{transform:translateX(110%)} 100%{transform:translateX(-110%)} }
@keyframes logoScrollRight { 0%{transform:translateX(-110%)} 100%{transform:translateX(110%)} }
@keyframes logoGlow        { 0%,100%{text-shadow:0 0 6px var(--logo-accent,#3b82f6),0 0 12px var(--logo-accent,#3b82f6)} 50%{text-shadow:0 0 16px var(--logo-accent,#3b82f6),0 0 32px var(--logo-accent,#3b82f6),0 0 48px var(--logo-accent,#3b82f6)} }
@keyframes logoFade        { 0%,100%{opacity:.15} 30%,70%{opacity:1} }
@keyframes logoBounce      { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-4px)} }
@keyframes logoPulse       { 0%,100%{transform:scale(1)} 50%{transform:scale(1.04)} }
@keyframes logoColorShift  { 0%{color:#3b82f6} 25%{color:#8b5cf6} 50%{color:#10b981} 75%{color:#f97316} 100%{color:#3b82f6} }
@keyframes logoCursor      { 0%,49%{border-right-color:var(--logo-accent,#3b82f6)} 50%,100%{border-right-color:transparent} }

.sidebar-logo-img {
  max-height: 36px;
  max-width: 160px;
  width: auto;
  height: auto;
  object-fit: contain;
}

.logo-name-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.logo-icon-el {
  font-size: 1.375rem;
  flex-shrink: 0;
  line-height: 1;
}

.logo-name-el {
  font-size: var(--logo-name-size, 1rem);
  font-weight: var(--logo-name-weight, 700);
  color: var(--logo-text-color, #f1f5f9);
  font-family: var(--logo-font, inherit);
  text-transform: var(--logo-transform, none);
  letter-spacing: var(--logo-spacing, normal);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color .3s;
}

.logo-slogan-wrap {
  overflow: hidden;
  height: 16px;
  margin-inline-start: calc(1.375rem + 6px);
}

.logo-slogan-el {
  font-size: var(--logo-slogan-size, 0.6875rem);
  color: var(--logo-slogan-color, rgba(148,163,184,.8));
  white-space: nowrap;
  display: inline-block;
  line-height: 1;
}

/* Name styles */
.logo-style-outline .logo-name-el  { -webkit-text-stroke: 1px var(--logo-accent,#3b82f6); color: transparent !important; }
.logo-style-split .logo-name-el .p1 { color: var(--logo-accent,#3b82f6); }
.logo-style-mono .logo-name-el     { font-family: monospace; letter-spacing: 2px; }
.logo-style-upper .logo-name-el    { text-transform: uppercase; letter-spacing: 3px; font-size: .875rem; }
.logo-style-thin .logo-name-el     { font-weight: 300; letter-spacing: 2px; }
.logo-style-condensed .logo-name-el{ letter-spacing: -1px; }

/* Slogan animations */
.logo-anim-scroll-left  .logo-slogan-el { animation: logoScrollLeft 8s linear infinite; }
.logo-anim-scroll-right .logo-slogan-el { animation: logoScrollRight 8s linear infinite; }
.logo-anim-glow         .logo-slogan-el { animation: logoGlow 2.5s ease-in-out infinite; color: var(--logo-accent,#3b82f6) !important; }
.logo-anim-fade         .logo-slogan-el { animation: logoFade 3s ease-in-out infinite; }
.logo-anim-bounce       .logo-slogan-el { animation: logoBounce 1.5s ease-in-out infinite; display:inline-block; }
.logo-anim-pulse        .logo-name-el   { animation: logoPulse 2s ease-in-out infinite; display:inline-block; }
.logo-anim-colorshift   .logo-name-el   { animation: logoColorShift 4s linear infinite; }
.logo-anim-typewriter   .logo-slogan-el { border-right: 2px solid var(--logo-accent,#3b82f6); animation: logoCursor .7s step-end infinite; overflow:hidden; max-width:0; }
.logo-anim-typewriter.tw-done .logo-slogan-el { max-width:none; }

/* Icon animation */
.logo-icon-bounce .logo-icon-el { animation: logoBounce 2s ease-in-out infinite; display:inline-block; }
.logo-icon-spin   .logo-icon-el { animation: logoColorShift 3s linear infinite; }

/* ════════════════════════════════════════════════════════
   ALIEN PREMIUM STYLE — Enterprise UI v2
   ════════════════════════════════════════════════════════ */

/* ── Tier badges ── */
.tier-badge { display:inline-flex;align-items:center;gap:.25rem;padding:.2rem .625rem;border-radius:100px;font-size:0.75rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase }
.tier-bronze  { background:linear-gradient(135deg,#cd7f32,#a0522d);color:#fff;box-shadow:0 2px 8px rgba(205,127,50,.4) }
.tier-silver  { background:linear-gradient(135deg,#c0c0c0,#808080);color:#fff;box-shadow:0 2px 8px rgba(192,192,192,.4) }
.tier-gold    { background:linear-gradient(135deg,#ffd700,#ffa500);color:#fff;box-shadow:0 2px 8px rgba(255,215,0,.5) }
.tier-diamond { background:linear-gradient(135deg,#b9f2ff,#00d4ff,#0099ff);color:#fff;box-shadow:0 2px 8px rgba(0,212,255,.5);animation:diamond-pulse 2s ease-in-out infinite }
@keyframes diamond-pulse { 0%,100%{box-shadow:0 2px 8px rgba(0,212,255,.5)} 50%{box-shadow:0 2px 16px rgba(0,212,255,.9)} }

/* ── Status pills premium ── */
.status-delivered { background:linear-gradient(135deg,#052e16,#14532d);color:#4ade80;border:1px solid rgba(74,222,128,.3);box-shadow:0 0 8px rgba(74,222,128,.2) }
.status-transit   { background:linear-gradient(135deg,#1e3a5f,#1e40af);color:#60a5fa;border:1px solid rgba(96,165,250,.3);box-shadow:0 0 8px rgba(96,165,250,.2) }
.status-pending   { background:linear-gradient(135deg,#3d2800,#78350f);color:#fbbf24;border:1px solid rgba(251,191,36,.3) }
.status-cancelled { background:linear-gradient(135deg,#3b0000,#7f1d1d);color:#f87171;border:1px solid rgba(248,113,113,.3) }
.status-pill-premium { display:inline-flex;align-items:center;gap:.375rem;padding:.3rem .875rem;border-radius:100px;font-size:.75rem;font-weight:700;letter-spacing:.04em }
.status-pill-premium::before { content:'';width:6px;height:6px;border-radius:50%;background:currentColor;animation:status-blink 2s ease-in-out infinite }
@keyframes status-blink { 0%,100%{opacity:1} 50%{opacity:.3} }

/* ── Copy button ── */
.copy-btn { background:none;border:none;cursor:pointer;color:var(--text3);padding:.15rem .375rem;border-radius:4px;font-size:.75rem;transition:all .15s;opacity:0 }
.copy-btn:hover { color: var(--accent-on-surface);background:rgba(59,130,246,.1) }
*:hover > .copy-btn, .copy-wrap:hover .copy-btn { opacity:1 }
.copy-wrap { display:inline-flex;align-items:center;gap:.375rem }

/* ── Typewriter text ── */
.typewriter { overflow:hidden;white-space:nowrap;border-right:2px solid var(--accent);animation:tw-cursor .7s step-end infinite }
@keyframes tw-cursor { 0%,100%{border-color:var(--accent)} 50%{border-color:transparent} }

/* ── Premium number display ── */
.premium-num { font-family:'Courier New',Consolas,monospace;font-variant-numeric:tabular-nums;letter-spacing:2px;font-weight:700 }

/* ── Customer card ── */
.customer-card-premium { background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.25rem;position:relative;overflow:hidden;transition:transform .15s,box-shadow .15s }
.customer-card-premium:hover { transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.12) }
.customer-card-premium::before { content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--accent),var(--accent2,#8b5cf6)) }
.customer-bg-chart { position:absolute;bottom:0;left:0;right:0;height:40%;opacity:.06;pointer-events:none }

/* ── Courier animation ── */
.courier-vehicle { display:inline-block;animation:drive 3s linear infinite }
@keyframes drive { 0%{transform:translateX(-10px)} 50%{transform:translateX(10px)} 100%{transform:translateX(-10px)} }
.road-line { height:2px;background:repeating-linear-gradient(90deg,var(--accent) 0,var(--accent) 10px,transparent 10px,transparent 20px);animation:road-move 1s linear infinite;opacity:.4 }
@keyframes road-move { 0%{background-position:0 0} 100%{background-position:-20px 0} }

/* ── Glow effects ── */
.glow-green { box-shadow:0 0 12px rgba(74,222,128,.3) }
.glow-blue  { box-shadow:0 0 12px rgba(96,165,250,.3) }
.glow-gold  { box-shadow:0 0 12px rgba(255,215,0,.3) }

/* ── Data table premium ── */
.data-table tbody tr { transition:background .15s }
.data-table tbody tr:hover { background:rgba(59,130,246,.04) }
.data-table tbody td { vertical-align:middle }

/* ── Apple-style delivered ── */
.delivered-badge { display:inline-flex;align-items:center;gap:.375rem;background:linear-gradient(135deg,#052e16,#166534);color:#4ade80;border:1px solid rgba(74,222,128,.25);padding:.3rem .875rem;border-radius:100px;font-size:.75rem;font-weight:700;box-shadow:0 2px 8px rgba(22,163,74,.2) }
.delivered-badge svg { width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round }

/* ═══════════════════════════════════════════════════════════════
   RESPONSIVE / MOBILE LAYER  (additive — only applies ≤1024px)
   Makes every admin screen fit any device. Desktop is untouched.
   ═══════════════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
  .admin-content { padding: 1.25rem; --content-gutter: 1.25rem; }
  .form-row-3 { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 768px) {
  /* stop any stray wide element from scrolling the whole page sideways */
  html, body.admin-body { max-width: 100%; overflow-x: hidden; }
  .admin-main, .admin-content { max-width: 100%; min-width: 0; }

  /* collapse any inline multi-column grid to a single column on phones */
  .admin-content [style*="grid-template-columns"] { grid-template-columns: 1fr !important; }
  .dp-grid { grid-template-columns: repeat(3, 1fr) !important; } /* keep keypad 3-wide */

  /* PLATFORM-WIDE: no card content may overflow the screen edge on mobile.
     Long values (phone, email, addresses, Arabic/RTL text) wrap instead of being clipped,
     and flex rows are allowed to shrink (min-width:0) so space-between can't push content off. */
  .admin-content .card, .admin-content .card-body, .admin-content .card-header { max-width: 100%; min-width: 0; }
  .admin-content .card-body { overflow-wrap: anywhere; word-break: break-word; }
  .admin-content .card-body > div { min-width: 0; max-width: 100%; }
  .admin-content .card-body a { overflow-wrap: anywhere; word-break: break-word; }

  /* Card headers (and any action-button group inside them) wrap instead of overflowing. */
  .admin-content .card-header { flex-wrap: wrap; gap: .5rem; }
  .admin-content .card-header > div { flex-wrap: wrap; min-width: 0; }
  .admin-content .card-header .btn { flex: 0 1 auto; }

  /* wide tables shrink a touch; their own overflow wrappers still scroll */
  table.data-table { font-size: 0.8125rem; }
  table.data-table th, table.data-table td { padding: 0.6rem 0.7rem; }

  /* finger-friendly controls; 16px inputs stop iOS auto-zoom on focus */
  .btn { padding: 0.6rem 1rem; }
  .form-control, .form-select { font-size: 16px; }

  .page-title { font-size: 1rem; }
  /* Same token as the base rule — this override re-introduced the hardcoded
     -1rem at exactly the width where the gutter is .875rem, which is why the
     bleed was 2px wrong on a phone and nowhere else. */
  .page-header { padding: .75rem 1rem; margin: calc(var(--content-gutter) * -1) calc(var(--content-gutter) * -1) 1rem; }
  .stat-value { font-size: 1.5rem; }
}

@media (max-width: 480px) {
  .admin-content { padding: 0.875rem; --content-gutter: 0.875rem; }
  .card-body { padding: 1rem; }
  .card-header { padding: 0.9rem 1rem; }
  .stats-grid { grid-template-columns: 1fr; }
  .page-header { gap: 0.5rem; }
  .page-actions { width: 100%; flex-wrap: wrap; }
  .page-actions .btn { flex: 1; justify-content: center; }
}

/* ════════════════════════════════════════════════════════════════════
   ADMIN MODERNIZATION PASS 1 — shared CSS upgrade
   Lifts every legacy class (.card, .btn, .form-control, .data-table,
   .flash, sidebar, topbar) into the modern Site-Editor aesthetic
   without per-view edits. 132 legacy admin pages benefit at once.
   ════════════════════════════════════════════════════════════════════ */

/* ── Cards: rounder corners + soft shadow + subtle hover lift ── */
.card {
    border-radius: 14px;
    border-color: color-mix(in srgb, var(--border) 70%, transparent);
    box-shadow: 0 1px 2px rgba(15,23,42,.04), 0 8px 24px -12px rgba(15,23,42,.08);
    transition: box-shadow .2s, transform .15s, border-color .2s;
}
.card:hover { box-shadow: 0 2px 4px rgba(15,23,42,.05), 0 14px 32px -12px rgba(15,23,42,.12); }
.card-header {
    padding: .9rem 1.1rem;
    border-bottom-color: color-mix(in srgb, var(--border) 65%, transparent);
    background: linear-gradient(180deg, var(--bg2), color-mix(in srgb, var(--bg2) 92%, var(--bg)));
    gap: .75rem;
}
.card-title {
    font-weight: 700; letter-spacing: -.01em;
    display: inline-flex; align-items: center; gap: .55rem;
}
.card-title::before {
    content: ''; width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
    flex: 0 0 auto;
}
.card-body { padding: 1.1rem 1.25rem; }

/* ── Buttons: refined radius, accent shadow on primary, snappier hover ── */
.btn {
    border-radius: 10px; font-weight: 600;
    padding: .55rem 1rem; font-size: .88rem; letter-spacing: .005em;
    transition: transform .12s, box-shadow .2s, background .15s, filter .15s, border-color .15s;
}
.btn:hover:not(:disabled) { transform: translateY(-1px); }
.btn-primary {
    box-shadow: 0 4px 12px -2px color-mix(in srgb, var(--accent) 45%, transparent);
}
/* Hover DARKENS a filled control. Brightening it lowered the contrast of its
   white label under the pointer, so hover could never be the readable state. */
.btn-primary:hover:not(:disabled) {
    background: var(--accent-fill, var(--accent));
    filter: brightness(.92);
    box-shadow: 0 6px 18px -2px color-mix(in srgb, var(--accent) 55%, transparent);
}
.btn-secondary { background: var(--bg2); }
.btn-secondary:hover:not(:disabled) {
    background: color-mix(in srgb, var(--accent) 6%, var(--bg2));
    border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
    color: var(--text);
}
.btn-danger:hover:not(:disabled) {
    background: rgba(220,38,38,.1);
    border-color: rgba(220,38,38,.5);
}
.btn-success { box-shadow: 0 4px 12px -2px rgba(22,163,74,.35); }
.btn-success:hover:not(:disabled) { filter: brightness(1.06); box-shadow: 0 6px 18px -2px rgba(22,163,74,.45); }
.btn-warning { box-shadow: 0 4px 12px -2px rgba(217,119,6,.35); }

/* ── Form controls: refined focus ring, bigger touch targets ── */
.form-control, .form-select, textarea.form-control, input[type=text].form-control, input[type=email].form-control, input[type=number].form-control, input[type=tel].form-control, input[type=url].form-control, input[type=password].form-control, input[type=date].form-control, input[type=search].form-control {
    border-radius: 10px;
    padding: .6rem .85rem;
    border-color: color-mix(in srgb, var(--border) 80%, transparent);
    transition: border-color .15s, box-shadow .15s;
}
.form-control:focus, .form-select:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 14%, transparent);
}
.form-label {
    font-weight: 600;
    color: var(--text2);
    font-size: .82rem;
    letter-spacing: .005em;
}
.form-group { margin-bottom: 1.1rem; }

/* ── Flash messages: pill style with leading icon dot ── */
.flash {
    border-radius: 12px;
    padding: .85rem 1.1rem;
    box-shadow: 0 1px 2px rgba(15,23,42,.04);
    border-width: 1px;
    position: relative;
    padding-inline-start: 2.4rem;
}
.flash::before {
    content: ''; position: absolute;
    inset-inline-start: .85rem; top: 50%; transform: translateY(-50%);
    width: 10px; height: 10px; border-radius: 50%;
    box-shadow: 0 0 0 4px currentColor;
    opacity: .15;
}
.flash-success::before { background: #16a34a; color: #16a34a; }
.flash-error::before   { background: #dc2626; color: #dc2626; }
.flash-warning::before { background: #d97706; color: #d97706; }

/* ── Data tables: cleaner rows, sticky header, row hover, responsive cards ── */
.table-wrap {
    border-radius: 14px;
    border-color: color-mix(in srgb, var(--border) 75%, transparent);
    box-shadow: 0 1px 2px rgba(15,23,42,.04), 0 8px 24px -12px rgba(15,23,42,.06);
}
.data-table, table.data-table {
    border-collapse: separate;
    border-spacing: 0;
    width: 100%;
}
.data-table thead th {
    background: linear-gradient(180deg, var(--bg2), color-mix(in srgb, var(--bg2) 92%, var(--bg)));
    font-size: .78rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em;
    color: var(--text2);
    padding: .75rem 1rem;
    border-bottom: 1px solid var(--border);
    position: sticky; top: 0; z-index: 1;
}
.data-table tbody td {
    padding: .85rem 1rem;
    border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
    font-size: .9rem;
}
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr { transition: background .15s; }
.data-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 4%, transparent); }
.data-table .row-actions { display: inline-flex; gap: .35rem; flex-wrap: wrap; }
.data-table .row-actions .btn-sm { padding: .3rem .65rem; }

/* Responsive table → stacked cards on phone. `data-label` comes from the
   author or from byar-a11y.js, which tags every cell from its column header at
   boot — so a table needs no per-page work to transform.
   `.data-table` is included on purpose: opting in by remembering a class is
   how the owner ended up swiping a visitor list on an iPhone. */
@media (max-width: 760px) {
    /* AN INLINE min-width DEFEATS THIS WHOLE TRANSFORM, and several pages
       carry one. The rules below make a table `display: block` and stack its
       rows into cards; a `style="min-width:760px"` on the table then forces
       that block to 760px anyway, so the page reports an overflow and the
       operator drags it sideways on a phone. Measured Sep 2026: the invoices
       list did exactly that at 375, 390 and 430.
       `!important` is used here and is justified: it is undoing an INLINE
       declaration that contradicts the design system, and there is no other
       way to reach one. It applies only under 760px, so the desktop width the
       author wanted is untouched. */
    table.responsive, table.data-table, table.table { min-width: 0 !important; }

    /* `.table` IS THE DESIGN SYSTEM'S OWN TABLE CLASS — every screen built on
       x-ui.panel uses it — and it was the one table class this transform did
       not know, so those screens got no stacking and no scroller. Naming it
       here is what makes a new panel screen responsive by construction rather
       than by its author remembering a second class. */
    table.responsive, table.data-table, table.table { display: block; }
    table.responsive thead, table.data-table thead, table.table thead { display: none; }
    table.table tbody, table.table tr { display: block; }
    table.table tr {
        background: var(--bg2);
        border: 1px solid var(--border);
        border-radius: 12px;
        padding: .85rem 1rem;
        margin-bottom: .85rem;
    }
    table.table td {
        display: flex; justify-content: space-between; gap: 1rem;
        padding: .35rem 0; border: 0; font-size: .88rem;
        min-width: 0; overflow-wrap: anywhere; text-align: start;
    }
    table.table td::before {
        content: attr(data-label);
        font-weight: 700; color: var(--text2); font-size: .8rem;
        flex: 0 0 40%; text-align: start; min-width: 0; overflow-wrap: anywhere;
    }
    /* A cell with no label prints no empty gutter — an actions column is the
       common case and a blank 40% column beside two buttons is just a hole. */
    table.table td:not([data-label])::before { content: none; }
    table.responsive tbody, table.responsive tr,
    table.data-table tbody, table.data-table tr { display: block; }
    table.data-table tr {
        background: var(--bg2);
        border: 1px solid var(--border);
        border-radius: 12px;
        padding: .85rem 1rem;
        margin-bottom: .85rem;
    }
    table.data-table td {
        display: flex; justify-content: space-between; gap: 1rem;
        padding: .35rem 0; border: 0; font-size: .88rem;
    }
    table.data-table td::before {
        content: attr(data-label);
        font-weight: 700; color: var(--text2); font-size: .8rem;
        flex: 0 0 40%; text-align: start;
    }
    /* A CELL WITH NO LABEL TAKES NO SPACE. `table.table` has always carried
       this guard and `.data-table` did not, so an untagged cell reserved 40%
       of the row for an empty marker and pushed its own value into a narrow
       column — the value squeezed against the edge that the owner read as
       words breaking vertically. byar-a11y.js now tags these from the header,
       so this is the floor under that rather than the usual case. */
    table.data-table td:not([data-label])::before,
    table.responsive td:not([data-label])::before { content: none; }
    /* A VALUE MUST BE ABLE TO WRAP. A long email or visitor id is the most
       common thing in these cells and `min-width: 0` is what lets it break
       inside its own half instead of widening the row. */
    table.responsive td, table.data-table td { min-width: 0; overflow-wrap: anywhere; }
    table.responsive td::before, table.data-table td::before { min-width: 0; overflow-wrap: anywhere; }
    table.responsive { display: block; }
    table.responsive thead { display: none; }
    table.responsive tbody, table.responsive tr { display: block; }
    table.responsive tr {
        background: var(--bg2);
        border: 1px solid var(--border);
        border-radius: 12px;
        padding: .85rem 1rem;
        margin-bottom: .85rem;
        box-shadow: 0 1px 2px rgba(15,23,42,.04);
    }
    table.responsive td {
        display: flex; justify-content: space-between; gap: 1rem;
        padding: .35rem 0; border: 0;
        font-size: .88rem;
    }
    table.responsive td::before {
        content: attr(data-label);
        font-weight: 700; color: var(--text2); font-size: .8rem;
        flex: 0 0 40%; text-align: start;
    }
    /* A CELL THAT HOLDS MORE THAN ONE THING STACKS. A flex row turns every
       child into its own column, so a name + description, or an input + its
       hint, were each squeezed into a sliver beside the 40% label and broke
       one letter per line (owner, premium add-ons on an iPhone, Oct 2026).
       Such a cell now puts its label ABOVE and gives the content the full
       width; a cell with a single plain value keeps the label/value row. */
    table.responsive td:has(> div, > p, > ul, > input, > select, > textarea, > form, > .u-meta-sm),
    table.data-table td:has(> div, > p, > ul, > input, > select, > textarea, > form, > .u-meta-sm) {
        display: block;
    }
    table.responsive td:has(> div, > p, > ul, > input, > select, > textarea, > form, > .u-meta-sm)::before,
    table.data-table td:has(> div, > p, > ul, > input, > select, > textarea, > form, > .u-meta-sm)::before {
        display: block; margin-bottom: .3rem;
    }
}

/* ── Sidebar: subtle polish on active link + brand row ── */
.nav-link {
    border-radius: 8px;
    margin: 1px .55rem;
    padding-inline: .85rem;
    transition: background .15s, color .15s, transform .12s;
}
.nav-link:hover { transform: translateX(2px); }
.nav-link.active {
    background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent);
    font-weight: 600;
}
.nav-link.active::before {
    inset-block: 22%; width: 3px; border-radius: 0 3px 3px 0;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 15%, transparent);
}
.nav-section-label {
    text-transform: uppercase; letter-spacing: .1em;
    font-size: 0.75rem; opacity: .7;
}

/* ── Topbar: soft border + breadcrumb refinement ── */
.admin-topbar {
    border-bottom-color: color-mix(in srgb, var(--border) 65%, transparent);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
.topbar-breadcrumb {
    font-weight: 600;
    color: var(--text2);
}
.topbar-btn:hover { transform: translateY(-1px); }

/* ── Stat cards: refined hover ── */
.stat-card {
    border-radius: 14px;
    transition: transform .15s, box-shadow .2s, border-color .2s;
}
.stat-card:hover {
    border-color: color-mix(in srgb, var(--accent) 25%, var(--border));
    box-shadow: 0 8px 24px -12px color-mix(in srgb, var(--accent) 25%, rgba(15,23,42,.15));
}

/* ── Page-actions inside legacy headers: align with .mod-btn-primary look ── */
.page-actions .btn-primary {
    background: var(--accent-fill, var(--accent));
    border-color: var(--accent-fill, var(--accent));
    color: var(--accent-fill-ink, #fff);
}
.page-actions .btn-primary:hover:not(:disabled) {
    background: var(--accent-fill, var(--accent)); filter: brightness(.92);
}

/* ── Help boxes / hints — when a legacy page uses <div class="form-hint"> ── */
.form-hint, .help-text {
    font-size: .82rem; color: var(--text3); line-height: 1.5;
    margin-top: .35rem;
}

/* ── Tabs (when legacy pages use .tabs / .tab-link) ── */
.tabs { display: flex; gap: .25rem; border-bottom: 1px solid var(--border); margin-bottom: 1.25rem; }
.tab-link {
    padding: .65rem 1rem;
    font-size: .88rem; font-weight: 600;
    color: var(--text2); text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color .15s, border-color .15s;
}
.tab-link:hover { color: var(--text); }
.tab-link.active { color: var(--accent-on-surface); border-bottom-color: var(--accent); }

/* ── Empty-state blocks ── */
.empty-state {
    text-align: center; padding: 3rem 1.5rem;
    color: var(--text3); font-size: .92rem;
}
.empty-state .ic { font-size: 2.5rem; margin-bottom: .75rem; opacity: .5; }
.empty-state h3 { color: var(--text); font-size: 1.1rem; margin-bottom: .4rem; font-weight: 700; }

/* ── Mobile: better card-body padding + larger touch targets everywhere ── */
@media (max-width: 760px) {
    .card-body { padding: 1rem; }
    .card-header { padding: .85rem 1rem; }
    .btn:not(.btn-sm):not(.btn-icon) { padding: .65rem 1rem; min-height: 42px; }
    .btn-sm { min-height: 36px; }
    .form-control, .form-select { font-size: 16px; padding: .65rem .85rem; }
    .stat-card { padding: 1rem; }
    .data-table th, .data-table td { padding: .6rem .7rem; font-size: .85rem; }
}

/* ──────────────────────────────────────────────────────────────────
   📱 TOUCH-FRIENDLY LAYER — Oropherion 🦅: "better view to read,
   easy to use in mobile, bigger to read and understand"
   ──────────────────────────────────────────────────────────────────
   Tablet + mobile only. The super-admin tenants dashboard
   (.tenants-board) is EXCLUDED — operator dashboards stay rich.
   Applied at <= 900 px so iPads + phones get the treatment;
   desktop is untouched. Apple HIG: 44 × 44 px is the minimum
   reliable tap target. */

@media (max-width: 900px) {
    /* Base font bump — improves every text element ~10%. SAFE: only
       changes font sizing, never layout flow. */
    body, .main-content, .card-body {
        font-size: 1.02rem;
        line-height: 1.6;
    }

    /* Page headers + section headings bigger so the operator scans
       fast without leaning into the screen. */
    .page-header h1 { font-size: 1.55rem; }
    .card-title    { font-size: 1.05rem; }

    /* Bigger tap targets. SAFE: only min-height + padding, no
       display / position / float changes. tenants-board excluded so
       the super-admin dashboard keeps its compact buttons. */
    .btn:not(.btn-sm):not(.btn-icon):not(.tenants-board *) { min-height: 46px; }
    .btn-icon { min-width: 44px; min-height: 44px; }

    /* Inputs at 16px so iOS doesn't auto-zoom on focus. SAFE: typography
       + min-height only. */
    input:not([type=checkbox]):not([type=radio]):not([type=color]),
    select, textarea, .form-control, .form-select {
        font-size: 16px;
        min-height: 46px;
    }

    /* Bigger KPI / stat numbers — readability of revenue / counts. */
    .stat-card .num, .stat-num, .kpi-value { font-size: 1.8rem; }
}

/* ──────────────────────────────────────────────────────────────────
   NOTE on tables: the platform ALREADY has a table-to-card system
   for narrow screens (.data-table.responsive + data-label, defined
   above near line ~750). We deliberately do NOT add a second one
   here — two transforms fighting each other was what produced the
   "vertical writing / half-written" breakage. Tables that should
   stack on mobile get the `.responsive` class on the <table>.

   We also deliberately do NOT override inline grid-template-columns
   globally — that nuked carefully-built flex/grid layouts into a
   single squished column. Pages that need to stack on mobile use
   their own scoped media queries.
   ────────────────────────────────────────────────────────────────── */

/* SAFE wide-table handling: any bare <table> inside a .card that
   isn't already in a scroll wrapper gets horizontal scroll on
   narrow screens instead of squishing the page. This makes the
   refunds / disputes plain-tables (and any other inline-styled
   table) readable on mobile without the destructive block-transform.
   The table keeps its real columns; the user swipes sideways. */
/* REPLACED. This block used to make every table in a card a sideways
   scroller with `white-space: nowrap`, which caused three of the owner's
   observed defects at once: long visitor ids and emails could not wrap, the
   columns squeezed to unreadable widths, and reading a row meant dragging it.
   A table on a phone is a list of cards, not a spreadsheet — the stacked
   transformation below is now the DEFAULT for admin data tables rather than
   something a page had to opt into by remembering a class. */


/* ══════════════════════════════════════════════════════════════════
   PLATFORM-WIDE "fit any device" guard — Oropherion 🦅: "fix
   everything to fit any device, no sideways scrolling."
   Belt-and-braces, applies at every width:
   ────────────────────────────────────────────────────────────────── */
/* 1. NO `overflow-x: hidden` HERE, DELIBERATELY, AND NEVER AGAIN.
      This rule used to read `html, body.admin-body { overflow-x: hidden }`
      and it is why the owner's iPhone showed content "disappear beyond the
      phone frame": hiding the overflow does not remove it, it CLIPS it, so a
      value or a button past the edge became invisible AND unreachable in one
      move. His instruction is explicit — "Do not solve this with
      overflow-x:hidden if it merely hides inaccessible controls or data. Fix
      the components themselves."
      So the page is allowed to report the truth, and the components below are
      the fix. MobileViewportTest fails the build if the hider returns. */
html, body.admin-body { max-width: 100%; }

/* 2. Flex / grid children default to min-width:auto, which lets a wide
      table / <pre> / code block expand its cell and push the layout
      wider than the screen. min-width:0 makes the child's OWN
      overflow-x:auto engage so wide content scrolls inside its box. */
.admin-content .card,
.admin-content .card-body,
.admin-content .cs-grid2 > *,
.admin-content .cs-grid3 > *,
.admin-content [style*="grid-template-columns"] > *,
.admin-content [style*="display:flex"] > *,
.admin-content [style*="display: flex"] > * { min-width: 0; }

/* 3. Wide content elements get their own horizontal scroll + are
      capped at the container width, so they never overflow the page.
      Code / pre / diagram blocks scroll; tables scroll; images shrink. */
.admin-content pre,
.admin-content code,
.admin-content table,
.admin-content .cs-diag,
.admin-content .cs-cmd { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.admin-content img { max-width: 100%; height: auto; }

/* 4. On phones, monospace blocks get tighter PADDING so more fits before an
      internal scroll — but not smaller TYPE. Shrinking to .62rem put the
      architecture diagram at 9.9px, below the platform's own 12px floor and
      genuinely unreadable in one hand; the block already scrolls sideways, so
      making the letters smaller bought nothing a swipe did not. */
@media (max-width: 560px) {
    .admin-content .cs-diag { font-size: var(--type-floor, .75rem); padding: .7rem .8rem; }
    .admin-content .cs-cmd  { font-size: max(.72rem, var(--type-floor, .75rem)); }
}

/* 5. Belt-and-braces: overflow-x on a <table> ELEMENT is ignored by
      browsers (a table box isn't a scroll container), and iOS Safari
      ignores overflow-x:hidden on <body>. So a wide non-stacking table
      could still drag the whole PAGE sideways on a phone. Make the
      card the scroll container instead — it's a real block box, so the
      table scrolls INSIDE its card and the page never moves. Tables that
      stack (.responsive) don't overflow, so this is a no-op for them. */
/* REPLACED. Making every card body a horizontal scroll container is what
   the owner's thumb found: the CARD slid sideways under his finger. A card is
   the page's own furniture and must never scroll. Only a genuinely wide,
   non-stackable thing scrolls, and only inside its own declared
   .ui-table-wrap. */
@media (max-width: 760px) {
    .admin-content table.responsive { overflow-x: visible; }
}

/* ══════════════════════════════════════════════════════════════════════
   THE SUN ZIP — the platform's one moving line.

   Lifted straight from components/byar-loader (the "Loading Sun"): a
   21-ray Kurdistan sun travelling a zip, with the WELDED RED road behind
   it for what is finished and BRIGHT-GREEN OPEN TEETH ahead for what is
   not. Same palette, same direction, same speed.

   It lives here, once, because the owner wants that motion in several
   places — the collapsed chrome edge, the dial pad, the courier board —
   and three hand-rolled copies would drift into three different reds.
   Anything that wants the line adds .sun-zip; nothing redefines it.

   Colours are the loader's own, not approximations:
     welded road  #B91C1C   open teeth  #86EFAC
     sun          #F59E0B core → #FDE68A → #FFFDF0
   ══════════════════════════════════════════════════════════════════════ */
.sun-zip {
  position: relative;
  height: 3px;
  /* NO overflow:hidden — that line clipped the class's own sun to a 3px
     sliver everywhere it was used, which is why the owner watched a bare
     line travel the dial pad and asked where the sun was. The gradient
     never overflows a box, so hiding overflow protected nothing; it only
     beheaded the sun. The travel keyframes below keep the sun INSIDE the
     element horizontally, so nothing pokes past a container's edge and no
     page gains a horizontal scrollbar. */
  background: linear-gradient(90deg, #B91C1C 0%, #B91C1C 50%, #86EFAC 50%, #86EFAC 100%);
  background-size: 200% 100%;
  animation: sun-zip-road 3.2s linear infinite;
}
.sun-zip::after {
  content: '';
  position: absolute;
  top: -4px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: radial-gradient(circle, #FFFDF0 0%, #FDE68A 45%, #F59E0B 100%);
  box-shadow: 0 0 10px rgba(245, 158, 11, .9);
  animation: sun-zip-sun 3.2s linear infinite;
}
@keyframes sun-zip-road { from { background-position: 100% 0 } to { background-position: 0 0 } }
@keyframes sun-zip-sun  { from { inset-inline-start: 0 } to { inset-inline-start: calc(100% - 11px) } }

/* A stationary sun still reads as the platform's mark; a moving one does not
   belong on a screen where somebody asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .sun-zip, .sun-zip::after { animation: none }
  .sun-zip::after { inset-inline-start: 50% }
}

/* WHEN THE CHROME IS MINIMISED, THE EDGE BECOMES THE LINE.
   The hover strip that brings the top bar back was invisible — you had to
   already know it was there. Lit with the sun zip it does two jobs at once:
   it is the prettiest thing on the screen, and it is a signpost saying
   "the platform is still here, reach for me". */
body.chrome-hidden .chrome-hover-edge {
  height: 3px;
  background: linear-gradient(90deg, #B91C1C 0%, #B91C1C 50%, #86EFAC 50%, #86EFAC 100%);
  background-size: 200% 100%;
  animation: sun-zip-road 3.2s linear infinite;
  overflow: visible;
  opacity: .92;
}
body.chrome-hidden .chrome-hover-edge::after {
  content: '';
  position: absolute;
  top: -3px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: radial-gradient(circle, #FFFDF0 0%, #FDE68A 45%, #F59E0B 100%);
  box-shadow: 0 0 12px rgba(245, 158, 11, .95);
  animation: sun-zip-sun 3.2s linear infinite;
  pointer-events: none;
}
body.chrome-hidden .chrome-hover-edge:hover { height: 6px; opacity: 1 }
@media (prefers-reduced-motion: reduce) {
  body.chrome-hidden .chrome-hover-edge,
  body.chrome-hidden .chrome-hover-edge::after { animation: none }
}

/* ══════════════════════════════════════════════════════════════════════
   COURIER ROAD — the sun runs it, the lorry chases it, the zip opens.

   It is a JOURNEY, on a loop:

     the SUN leads, running from the right edge to the delivered rate;
     the ZIP OPENS behind it — the red weld grows in its wake, which is the
       loader's own behaviour applied to a real number;
     the LORRY follows, a beat behind the sun, never overtaking it.

   Then all three hold at the rate for the rest of the cycle, so the resting
   position still READS as the courier's real delivery percentage — the point
   of the card — before the next run begins.

   ── IT TRAVELS RIGHT → LEFT, AND THAT IS NOT A STYLE CHOICE ──

   🚚 is drawn with its cab on the LEFT. Move it rightwards and you are
   watching a lorry REVERSE down a road for four seconds — which is exactly
   what the owner saw and said: "lorry go reverse than forward". A vehicle
   has a front, and the animation has to respect it. So the whole journey
   runs right → left, where the emoji is facing, and it needs no mirroring
   in either LTR or RTL: PHYSICAL `right` is animated here, not logical
   `inset-inline-start`, so the lorry drives forward for every reader.

   That also puts the sun where the owner wants it — ahead of the lorry along
   the direction of travel, opening the zip the lorry then drives on. Leading
   the way is the sun's whole job; a lorry crossing unopened teeth would make
   nonsense of the metaphor.

   Speed: 9s, not the 4.2s that shipped first ("the speed is too high"). A
   loader may sprint. A lorry crossing a courier's whole week should not.

   ── THE JOURNEY STARTS OFF-ROAD, AND THAT IS THE 0% FIX ──

   The run used to start at right: 0 and travel to right: var(--rate) — so a
   courier at 0% delivered had a ZERO-LENGTH journey and the lorry simply
   stood still. Nobody saw it on the platform, where couriers carry real
   delivery rates; every fresh tenant saw it immediately, because their
   couriers start at 0% — which is exactly what the owner reported: "in
   tenants, lorry not moving". Starting at right: -14% (under the card edge —
   .card clips overflow, so the lorry drives IN from beneath it) gives every
   rate a real journey while leaving the resting position exactly the true
   rate. The weld deliberately still grows from 0 to the rate and no further:
   the red is data, and at 0% the honest weld is no weld.

   Red behind = delivered. Green teeth ahead = still out there. Same three
   values as .sun-zip and components/byar-loader.
   ══════════════════════════════════════════════════════════════════════ */
.courier-road {
  position: relative;
  height: 3px;
  border-radius: 100px;
  margin: .6rem 0 .55rem;
  background-image: repeating-linear-gradient(90deg, #86EFAC 0 7px, transparent 7px 13px);
  background-color: color-mix(in srgb, #86EFAC 16%, transparent);
}

/* The weld opening behind the sun — anchored to the RIGHT edge, growing
   leftwards in the sun's wake. Ends at --rate and stays there. */
.courier-road-done {
  position: absolute; inset-block: 0; right: 0;
  width: 0;
  background: #B91C1C;
  border-radius: 100px;
  animation: courier-weld 9s cubic-bezier(.42, 0, .35, 1) infinite;
}
@keyframes courier-weld {
  0%        { width: 0 }
  58%, 100% { width: var(--rate, 0%) }
}

.courier-road-sun {
  position: absolute; top: -3px;
  right: 0;
  margin-right: -4px;
  width: 9px; height: 9px; border-radius: 50%;
  background: radial-gradient(circle, #FFFDF0 0%, #FDE68A 45%, #F59E0B 100%);
  box-shadow: 0 0 10px rgba(245, 158, 11, .9);
  animation: courier-travel 9s cubic-bezier(.42, 0, .35, 1) infinite,
             courier-sun-pulse 3.2s ease-in-out infinite;
}

/* A BEAT BEHIND, BY A POSITIVE DELAY — not by a shorter distance. Delaying
   the same journey is what makes it read as following rather than as a second
   thing that happens to be moving; the lorry is always where the sun was.
   The delay must be POSITIVE: a negative one starts the animation part-way
   through, which puts the lorry AHEAD of the sun — the opposite of following,
   and the reason it looked wrong on the first pass. */
.courier-road-lorry {
  position: absolute; top: -12px;
  right: 0;
  margin-right: -11px;
  font-size: .95rem; line-height: 1;
  animation: courier-travel 9s cubic-bezier(.42, 0, .35, 1) .85s infinite;
}
@keyframes courier-travel {
  0%        { right: -14% }
  58%, 100% { right: var(--rate, 0%) }
}
@keyframes courier-sun-pulse { 0%,100% { transform: scale(1) } 50% { transform: scale(1.3) } }

/* Reduced motion: no journey, but the number is still drawn — the card's job
   is to report a delivery rate, and that survives the animation being off. */
@media (prefers-reduced-motion: reduce) {
  .courier-road-done, .courier-road-sun, .courier-road-lorry { animation: none }
  .courier-road-done  { width: var(--rate, 0%) }
  .courier-road-sun   { right: var(--rate, 0%) }
  .courier-road-lorry { right: var(--rate, 0%) }
}

/* ══════════════════════════════════════════════════════════════════════
   CV CARD — one candidate, framed and glowing in the colour of their
   verdict. Shared by the engagement pile and the shortlist so the two
   screens read as one product: set --frame to the medal colour (violet
   diamond, amber gold, slate silver, bronze) and the border, the tint and
   the coloured shadow all follow. No --frame = a quiet neutral card for a
   candidate not yet judged; .unreadable = dashed red, a file that could
   not be read and says so. The glow is the ReVeRa hero's grammar — the
   sun's shadow, per card, in the card's own colour.
   ══════════════════════════════════════════════════════════════════════ */
.rv-cv-card {
  border: 2px solid var(--frame, var(--border));
  border-radius: 13px;
  padding: .85rem 1rem;
  background: var(--bg2);
  box-shadow: var(--shadow);
}
.rv-cv-card[style*="--frame"] {
  background: linear-gradient(180deg, color-mix(in srgb, var(--frame) 8%, var(--bg2)), var(--bg2));
  box-shadow: 0 4px 18px color-mix(in srgb, var(--frame) 32%, transparent);
}
.rv-cv-card.unreadable {
  border-style: dashed;
  border-color: rgba(220, 38, 38, .55);
}

/* The card's furniture. Lives here, not in a page's @push, because the card
   is a COMPONENT rendered on four different screens and a style pushed by one
   page is missing on the other three — which is how "the same card" quietly
   becomes four cards. */
.rv-cv-card .rv-rank {
  font-size: 1.35rem;
  font-weight: 900;
  min-width: 2.1rem;
  line-height: 1.1;
  flex: none;
}
.rv-cv-card .rv-face {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  border: 2px solid var(--frame, var(--border));
  background: var(--bg3);
}
.rv-cv-card .rv-face-ph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 1.05rem;
  color: #fff;
}
/* Fills to the score, in the medal's own colour — the fastest read on a
   phone, and the same shape as the platform's other progress rails. */
.rv-cv-card .rv-bar {
  height: 7px;
  border-radius: 999px;
  background: var(--bg3);
  overflow: hidden;
  margin-top: .5rem;
}
.rv-cv-card .rv-bar span {
  display: block;
  height: 100%;
  border-radius: 999px;
  transition: width .35s ease;
}

/* The verdict band, global for the same reason. */
.band {
  border-radius: 999px;
  padding: .15rem .7rem;
  font-size: 0.75rem;
  font-weight: 800;
  display: inline-block;
  white-space: nowrap;
}
.band-strong  { background: rgba(22, 163, 74, .12); color: #15803d; }
.band-good    { background: rgba(37, 99, 235, .12); color: #1d4ed8; }
.band-weak    { background: rgba(217, 119, 6, .12); color: #b45309; }
.band-unknown,
.band-unscored { background: var(--bg3); color: var(--text3); }

/* One grid for every pile of candidates, so the cards line up the same way
   on every screen and reflow to one column on a phone. */
.rv-cv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: .9rem;
}
/* MEASURED, NOT GUESSED: at 390px a card came out 495px wide inside a 362px
   grid — 133px of it off the side of the phone, taking the action buttons with
   it. A grid item's automatic minimum size is its MIN-CONTENT, so one nowrap
   name or a long email in the card was enough to push the whole card past the
   track. min-width:0 lets the item shrink to its column and hands the problem
   back to the ellipsis and the wrapping that are already there. */
.rv-cv-grid > * { min-width: 0; }
.rv-cv-card { min-width: 0; }
/* Every card, not only the candidate ones: a grid item's automatic minimum
   size is its min-content, so one long name inside any .card can push it past
   its column. Measured on /admin/tenants, where it took the tenant cards'
   buttons off the screen with it. */
.card { min-width: 0; }
.rv-cv-card > * { min-width: 0; }
@media (max-width: 480px) {
  .rv-cv-grid { grid-template-columns: 1fr; }
}

/* A JOB wears the same card as a candidate — same frame, same glow, same
   radius. Only the meaning of the colour differs: on a person it is the
   medal, on a posting it is the state (open, draft, closed, filled). */
/* NOT scoped to .rv-job-card. It was, and the group-companies cards (which
   are .rv-cv-card) got NO sizing at all — a logo then rendered at its
   natural size and its alt text spilled outside the card, which is exactly
   what "logo broken, text outside of frame" looked like. A thumbnail class
   must size its box wherever it is used. */
.rv-jobimg {
  width: 46px;
  height: 46px;
  border-radius: 12px;
  object-fit: cover;
  flex: none;
  border: 2px solid var(--frame, var(--border));
  background: var(--bg3);
  /* A broken/missing file must still occupy exactly this box, never grow to
     fit its alt text. */
  overflow: hidden;
  font-size: 0;
}
.rv-jobimg-ph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.35rem;
}

/* ══════════════════════════════════════════════════════════════════════
   ACTION ROWS — "all buttons showing perfectly on mobile", as a rule
   rather than as a per-page fix.

   Buttons under a card are the place a phone breaks first: .btn is
   white-space:nowrap, so a long label pushes the row wider than the
   screen and the last button is simply not there. Under 560px the
   buttons here share the row two-up, wrap instead of overflowing, and
   every one of them is at least 44px tall — the platform's own touch
   target (CLAUDE.md §10, "Responsive UI — non-negotiable").
   ══════════════════════════════════════════════════════════════════════ */
.rv-actions {
  display: flex;
  gap: .45rem;
  flex-wrap: wrap;
  align-items: center;
  margin-top: .7rem;
}
.rv-actions > form { margin: 0; }
.rv-actions .btn,
.rv-actions button,
.rv-actions summary { min-height: 44px; }
@media (max-width: 560px) {
  .rv-actions > .btn,
  .rv-actions > form,
  .rv-actions > details { flex: 1 1 46%; min-width: 0; }
  .rv-actions > form > .btn,
  .rv-actions > form > button,
  .rv-actions > details > summary { width: 100%; }
  .rv-actions .btn,
  .rv-actions button,
  .rv-actions summary {
    justify-content: center;
    white-space: normal;      /* wrap the label rather than the viewport */
    text-align: center;
    line-height: 1.15;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   TOUCH TARGETS, PLATFORM-WIDE. Applied on narrow screens and on any
   device with no hover (i.e. a finger), so all 99+ legacy pages inherit
   it without a per-view edit — the same trick .page-header itself used.
   ══════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════
   THE TOPBAR AT TABLET AND SMALL-LAPTOP WIDTHS.

   Measured at 1024px with the sidebar showing: the bar's contents ran to
   about 1200px inside a 774px box, so the language picker, the ⋯ menu and
   the "view public site" button were off the right edge — present in the
   markup, unreachable with a finger or a mouse. The noisy extras were
   already hidden below 768px; the bar needs them gone sooner than that,
   because the sidebar eats 250px of every width above it.

   Nothing is lost: each hidden control has its own page and its own
   sidebar entry.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width: 1100px) {
  .topbar-right .pal-trigger,                              /* search palette */
  .topbar-right > a[href$="/admin/activity"],              /* "x users online" */
  .topbar-right > a[href$="/admin/guard"],                 /* Guard Vault */
  .topbar-right .notif-badge                               /* colour badges */
    { display: none !important; }

  /* The secure-session chip keeps its clock and its button; the words go. */
  .sess-pill .sess-label { display: none; }
}

@media (hover: none), (max-width: 768px) {
  .btn { min-height: 44px; }
  .btn-sm { min-height: 44px; padding-block: .4rem; }
  .btn-icon { width: auto; height: auto; min-width: 44px; min-height: 44px; }

  /* A DROP-DOWN IS A CONTROL TOO. Measured at 390px: the criteria "type"
     select was 19px tall, the currency and per-CV-rate selects 19px — a third
     of a finger. These are the controls the owner picks values from, so they
     get the same 44px as a button. */
  select, .form-select, .form-control { min-height: 44px; }

  /* Tick boxes: an 18px box is a miss waiting to happen. The box grows and its
     label carries the 44px hit area, so the tap lands even when the finger is
     nowhere near the square.

     !important is deliberate and narrow. Tick-box sizes are written inline in
     roughly twenty views (16px, 18px, 19px, 20px), and an inline style beats
     any stylesheet — so without this the fix would mean editing every one of
     them and hoping the next view remembers. Same retrofit-globally approach
     .page-header and .filter-field already use. */
  input[type="checkbox"], input[type="radio"] { width: 22px !important; height: 22px !important; }
  label:has(> input[type="checkbox"]),
  label:has(> input[type="radio"]),
  .sl-rowcheck, .sl-selall { min-height: 44px; min-width: 44px; display: inline-flex; align-items: center; justify-content: center; }

  /* The admin chrome's own buttons — theme, language, dial-pad, the ⋯ menu,
     the drawer toggle and logout. Measured between 21px and 29px tall. */
  .topbar-btn, .topbar-menu-btn, .sidebar-toggle-btn, .logout-btn, .pal-trigger {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }

  /* The recruitment screens' own small controls: the pipeline stage buttons,
     the breakdown/history toggles. Small on a desktop is fine; small under a
     thumb is a mis-tap on somebody's hiring decision. */
  .sl-stagebtn, .cp-stagebtn, .sl-toggle, .jd-star { min-height: 44px; }

  /* A LONG LABEL WRAPS; IT DOES NOT PUSH THE PAGE.
     Measured on /admin/tenants at 390px: "➡️ Enter mybiz Distribution &
     Logistics Limited workspace" ran to 891px — .btn is white-space:nowrap, so
     the button forced its card wider than the phone and the card's other
     buttons went with it. On a phone a two-line button is right and a
     half-visible one never is. */
  .btn { white-space: normal; }

  /* A <select> sizes itself to its widest OPTION, which is how the tenant
     suspension-clause picker measured 750px wide on a 390px screen. */
  select, .form-select { max-width: 100%; }

  /* The ? help bubble is 18px by design — small beside a title, and it must
     stay small. What it can have is a real hit area: an invisible 44px pad
     around it, so a thumb lands on it without the dot growing. */
  .byar-help-trigger { position: relative; }
  .byar-help-trigger::after { content: ''; position: absolute; inset: -13px; }
  .sl-stagebar, .cp-stagebar { gap: .45rem; }
  .sl-stagebar form, .cp-stagebar form { flex: 1 1 46%; display: flex; }
  .sl-stagebar .sl-stagebtn, .cp-stagebar .cp-stagebtn { width: 100%; justify-content: center; }
}

/* The page header's action cluster. Most views write it as a bare
   <div style="display:flex"> rather than .page-actions, so this targets the
   header's own children: on a phone the actions take the full width, wrap,
   and share the row instead of running off the edge of it. */
@media (max-width: 560px) {
  .page-header { align-items: stretch; }
  .page-header > div:not(:first-child) {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
  }
  .page-header > div:not(:first-child) > .btn,
  .page-header > div:not(:first-child) > form,
  .page-header > div:not(:first-child) > a {
    flex: 1 1 46%;
    min-width: 0;
  }
  .page-header > div:not(:first-child) > form > .btn,
  .page-header > div:not(:first-child) > form > button { width: 100%; }
  .page-header > div:not(:first-child) .btn {
    justify-content: center;
    white-space: normal;
    text-align: center;
    line-height: 1.15;
    min-height: 44px;
  }

  /* ── THE HAND-ROLLED HEADER ROW ─────────────────────────────────────
     Eleven admin screens — the tenant pages among them — write their
     header as a bare <div style="display:flex"> with the buttons pushed
     right by margin-left:auto, instead of using .page-header. That row
     does not wrap, so on a phone the buttons are squeezed into each
     other and the last one is unreachable: exactly the "all buttons on
     mobile broken and hidden each other" the owner reported on
     /admin/tenants.

     Rather than hunt every one of those views and hope the next author
     remembers, the row is repaired where it stands: the auto margin is
     dropped (it is what causes the crushing), the row wraps, and the
     buttons share it two-up at 44px. Attribute + :has() selectors are
     what make a fix possible on markup that carries its layout inline.
     ─────────────────────────────────────────────────────────────────── */
  /* Matched on the FAULT ITSELF — a button pushed right with margin-left:auto
     inside an inline flex row — and on nothing else.
     A broader selector ("any inline flex row with a .btn child") was tried
     first and MEASURED WRONG: the tenant cards are column flexes with a .btn
     inside, and flex-wrap:wrap on a column flex wraps its contents into a
     SECOND COLUMN. Their "Enter workspace" buttons moved to x=361 on a 390px
     screen and the card's own overflow:hidden clipped them away — the fix
     reproducing the very bug it was written for, invisibly. Hence the narrow
     selector, and hence measuring instead of reasoning. */
  div[style*="display:flex"]:has(> .btn[style*="margin-left:auto"]) { flex-wrap: wrap; }
  div[style*="display:flex"]:has(> .btn[style*="margin-left:auto"]) > .btn,
  div[style*="display:flex"]:has(> .btn[style*="margin-left:auto"]) > form > .btn {
    margin-left: 0 !important;
    margin-inline-start: 0 !important;
    flex: 1 1 46%;
    min-width: 0;
    min-height: 44px;
    justify-content: center;
    white-space: normal;
    text-align: center;
    line-height: 1.15;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE MOBILE ADMIN HEADER.

   WHAT WENT WRONG, because the fix has to answer it. The bar is
   `height: var(--topbar-height)` — a FIXED 56px — and `position: sticky;
   z-index: 100`. A previous pass added `flex-wrap: wrap` to stop controls
   being clipped off the right edge. Wrapping worked; the box did not grow.
   So the second row spilled out of a 56px container that paints ABOVE the
   page, and landed on top of "Viewing: platform data only" and the business
   selector. The owner photographed it.

   `scrollWidth === clientWidth` was true the whole time. The overflow was
   VERTICAL and the damage was STACKING, and a horizontal assertion cannot
   see either. Geometry that fits is not a design that works.

   TWO THINGS, THEREFORE, NOT ONE:

   1. THE BAR CAN NEVER OVERLAP AGAIN, whatever it contains. height becomes
      min-height, so if anything ever wraps the header GROWS and pushes the
      page down instead of painting over it. That is the structural
      guarantee, independent of how well the controls below are sized.

   2. THE CONTROLS BECOME MOBILE CONTROLS. Not desktop pills squeezed until
      they technically fit — icons with popovers, which is what a phone
      header is. Desktop keeps its labels; only ≤560px changes.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 560px) {

  /* (1) The overlap can no longer happen. */
  .admin-topbar {
    height: auto;
    min-height: var(--topbar-height);
    flex-wrap: wrap;
    align-items: center;
    row-gap: .25rem;
  }
  /* A sticky bar whose children cannot shrink makes the whole document
     scroll sideways — that is why this is here. But it is a DIRECT-CHILD
     reset, so it also crushed the platform's own 44px minimum on the
     controls that live directly in the bar: the ☰ mobile menu button
     measured 18px wide at 390px, and it is the primary navigation control
     on a phone. Layout boxes may still shrink to nothing; a thing you tap
     may not. */
  .admin-topbar > *:not(button):not(a) { min-width: 0; }
  .admin-topbar > button,
  .admin-topbar > a { min-width: 44px; }

  /* The chrome toggle is a desktop affordance and the first thing to go;
     ☰ and ← are navigation and both stay. */
  .admin-topbar .topbar-chrome-toggle { display: none !important; }

  /* The breadcrumb yields all remaining width and never forces a wrap. */
  .admin-topbar .topbar-breadcrumb { flex: 1 1 0; min-width: 0; overflow: hidden; }

  /* The control cluster keeps its own end of the bar, in both directions. */
  .admin-topbar .topbar-right {
    margin-inline-start: auto;
    flex: 0 0 auto;
    gap: .2rem;
    align-items: center;
  }

  /* ── LANGUAGE: the icon it already has, not the word "English" ──────────
     The existing markup renders 🌐 — or, in Kurdish, a Kurdistan flag that
     somebody chose deliberately. A first pass here drew the globe with a CSS
     ::before, which would have painted over that flag and, worse, put the
     only visual cue in a pseudo-element where assistive technology cannot
     see it. So the real icon stays and the SELECT is laid invisibly on top:
     one tap, still the native picker, still focusable, still no JavaScript. */
  .admin-topbar .topbar-lang {
    position: relative;
    width: 44px; height: 44px;
    padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    overflow: visible;
  }
  /* The icon comes BACK on a phone, reversing an older rule.
     `.topbar-lang > span:first-child { display: none }` was correct for the
     design it belonged to: the select still showed the word "English", so the
     globe was pure decoration eating width. Here the select is invisible and
     the icon IS the control's whole appearance — hidden, the box renders
     empty but for a caret, which is exactly what a screenshot showed. */
  .admin-topbar .topbar-lang > :not(select) {
    display: inline-flex !important;
    align-items: center;
    pointer-events: none;
    line-height: 1;
  }
  .admin-topbar .topbar-lang select {
    position: absolute; inset: 0;
    width: 100%; max-width: none; height: 100%;
    min-height: 44px;
    opacity: 0;
    cursor: pointer;
  }
  /* The affordance. Without it an icon is just a picture — the caret is what
     says "this opens something", and it is the discoverability the owner
     asked for rather than a surprising invisible tap target. */
  .admin-topbar .topbar-lang::after,
  .admin-topbar [data-appearance-control] > button[aria-checked="true"]::after {
    content: "\25BE";
    position: absolute;
    inset-block-end: 2px; inset-inline-end: 3px;
    font-size: 0.75rem; line-height: 1;
    color: var(--text3);
    opacity: .8;
    pointer-events: none;
  }
  .admin-topbar [data-appearance-control] > button[aria-checked="true"] { position: relative; }
  .admin-topbar [data-appearance-control]:focus-within > button[aria-checked="true"]::after { content: none; }

  /* ── STEP-UP: the clock stays readable, the rest goes ────────────────────
     The owner's rule stands — "time is important when it is ticking" — so
     the countdown keeps its weight and is NOT shortened. What goes is the
     padding and the ＋ label. Every element stays in the DOM: the timer JS
     reads #sess-clock, #sess-label, #sess-keep and #sess-fill, and this
     must not touch behaviour. */
  .admin-topbar .sess-pill {
    gap: .2rem;
    padding: .1rem .2rem .1rem .35rem;
    flex: 0 0 auto;
  }
  .admin-topbar .sess-pill .sess-keep { min-width: 32px; padding-inline: .25rem; }

  /* ── ONE VISUAL FAMILY ──────────────────────────────────────────────────
     Looked at rather than measured: the theme icon sat on a filled grey
     square while the globe sat in a bordered box, side by side, two
     different-looking controls doing the same kind of job. Same size, same
     border, same radius, same quiet weight — they read as siblings now. */
  .admin-topbar .topbar-lang,
  .admin-topbar [data-appearance-control] > button[aria-checked="true"] {
    border: 1px solid var(--border);
    border-radius: 10px;
    background: transparent;
  }

  /* ── THE STEP-UP CHIP STOPS SHOUTING ────────────────────────────────────
     The countdown stays bold and readable — "time is important when it is
     ticking" is the owner's rule and it still holds. What shrinks is the
     furniture around it: the ＋ was a 26px saturated disc doing the visual
     work of a primary action for a secondary one. Measured 110px → ~86px,
     which stops it dominating a 320px bar without shortening the clock. */
  .admin-topbar .sess-pill { padding: .1rem .18rem .1rem .3rem; gap: .15rem; }
  .admin-topbar .sess-pill .sess-keep {
    min-width: 24px; min-height: 24px;
    padding: .1rem .3rem;
    font-size: 0.75rem;
    background: color-mix(in srgb, var(--sess, #16a34a) 88%, transparent);
  }
  .admin-topbar .sess-pill .sess-clock { font-size: .8rem; }

  /* ── MORE: its own predictable place at the end of the row ───────────── */
  .admin-topbar .topbar-more { flex: 0 0 auto; margin-inline-start: .1rem; }
  .admin-topbar .topbar-more > .topbar-btn {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* Anchored to the bar's end edge so it opens inward, never off-screen,
     and mirrors correctly in Arabic and Kurdish. */
  .admin-topbar .topbar-more-menu {
    inset-inline-end: 0; inset-inline-start: auto;
    max-width: calc(100vw - 1rem);
  }
}

/* ── ADMIN TOPBAR: THE CHROME'S OWN CLASSES ───────────────────────────────
   Every one of these was an inline style attribute in layouts/admin.blade.php
   — which means it was repeated on EVERY PAGE of the platform, 25 of them per
   render, and could not be corrected anywhere but in the markup. They are the
   shared chrome, so they belong in the shared stylesheet. */
.notif-badge { position: relative; display: flex; align-items: center; justify-content: center; }
.notif-count {
  position: absolute; top: -2px; inset-inline-end: -4px;
  color: #fff; font-size: 0.75rem; font-weight: 700; min-width: 15px; height: 15px;
  border-radius: 999px; display: flex; align-items: center; justify-content: center;
  padding: 0 3px; line-height: 1;
  /* The tone is the badge's OWN, set per badge — a count that means "waiting"
     and a count that means "failing" are not the same news. */
  background: var(--badge-tone, var(--danger));
}
.topbar-back {
  text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 44px;
}
.topbar-back .glyph { font-size: 1.15rem; line-height: 1; }
.topbar-workspace { color: var(--text3); }
.topbar-online { display: flex; align-items: center; gap: .375rem; font-size: .8125rem; color: var(--success); }
.topbar-online .dot { width: 8px; height: 8px; background: var(--success); border-radius: 50%; display: inline-block; }
/* The operator's own key. Gold because it is the one control that is not a
   tenant's — it opens the platform's own door. */
.topbar-key { background: linear-gradient(135deg, #FFC83D, #FF8F00); color: #111; font-weight: 700;
              border-radius: 8px; padding: .25rem .55rem; }
.topbar-glyph { font-size: .95rem; }
.topbar-glyph-lg { font-size: 1.15rem; line-height: 1; }
.tm-item-btn { width: 100%; border: 0; background: none; cursor: pointer; text-align: start; }
.tm-item-btn.is-on { color: var(--accent-on-surface); }
.sess-fill-full { width: 100%; }

/* The support-mode and blend-mode banners. Both are STATE, shown on every page
   while it holds, so both were inline on every page while it held. */
.adm-band { display: flex; align-items: center; gap: .6rem; padding: .6rem 1rem; border-radius: var(--radius);
            margin-bottom: 1rem; font-size: .85rem; flex-wrap: wrap; color: #fff; }
.adm-band strong, .adm-band b { font-weight: 800; }
.adm-band-support { gap: .85rem; background: linear-gradient(135deg, #B8860B, #FB8500); }
/* The band identifies the business, its kind and the way out. It carries the
   custom domain and the actor too, and at 390px all of that stacked to 307px
   — a third of the screen spent before the page began. On a phone the
   secondary line goes behind the same disclosure everything else does: the
   two things that must survive are WHICH business and HOW OUT. */
.adm-band-support .adm-band-small { color: rgba(255,255,255,.9); }
.adm-band-support .ui-badge {
  background: rgba(255,255,255,.22);
  border-color: rgba(255,255,255,.45);
  color: #fff;
}
@media (max-width: 560px) {
  .adm-band-support { gap: .5rem .6rem; padding: .55rem .7rem; }
  .adm-band-support .adm-band-small,
  .adm-band-support .adm-band-dim { display: none; }
  /* The two ways out sit side by side and keep their touch targets. */
  .adm-band-support .adm-band-act { flex: 1 1 auto; }
  .adm-band-support .adm-band-act .btn { inline-size: 100%; min-block-size: 44px; }
}
.adm-band-blend { background: linear-gradient(90deg, #b45309, #d97706); box-shadow: 0 2px 10px rgba(180,83,9,.35); }
/* THE QUIET STATE IS A CHIP, NOT A CARD.
   On a 390px phone this wrapped — label on one line, a 44px button on the
   next — and ate roughly 190px above the page title on EVERY admin screen,
   before the reader had seen what page they were on. It is the DEFAULT state
   and says nothing surprising, so it must cost almost nothing: one row, the
   action beside the label, the touch target kept. The loud blended banner
   below it is deliberately untouched — that one changes what every number on
   the page means and has earned its noise. */
.adm-band-quiet { background: var(--bg2); border: 1px solid var(--border); color: var(--text2);
                  padding: .3rem .4rem .3rem .85rem; font-size: .8rem;
                  flex-wrap: nowrap; gap: .5rem; margin-bottom: .75rem; }
.adm-band-quiet > span { min-width: 0; }
.adm-band-quiet .adm-band-act {
  flex: 0 0 auto; font-size: .75rem; padding-inline: .7rem;
  min-height: 36px; display: inline-flex; align-items: center;
}
.adm-band-note { opacity: .95; }
.adm-band-dim { opacity: .75; }
.adm-band-small { opacity: .85; font-size: .78rem; }
.adm-band-act { margin-inline-start: auto; }
.adm-band-act.is-light { background: #fff; color: #b45309; font-weight: 700; min-height: 44px;
                         display: inline-flex; align-items: center; }
.adm-band-exit { background: var(--on-brand); color: #B8860B; font-weight: 700; }

/* ── THE LOG-OUT DIALOG ───────────────────────────────────────────────────
   Built in JavaScript on every page, which is why its whole appearance AND
   its four sentences were written inline — and why those sentences shipped in
   English only, on a platform whose first law is three languages on every
   screen. The look lives here; the words come from the adm.php file in each
   of lang/en, lang/ar and lang/ku.
   NEVER WRITE A PATH GLOB IN A CSS COMMENT. This comment used to name those
   files with a star between two slashes, and the slash after the star CLOSED
   the comment early: the rest of the line became the start of a selector, the
   browser threw the whole .lo-dlg rule away, and the dialog rendered as plain
   text at the foot of the page, under the open phone drawer — so tapping the
   power button did nothing anyone could see, and nobody could sign out.
   tests/Feature/Auth/LogoutDoorsTest guards every stylesheet against it. */
.lo-dlg { position: fixed; inset: 0; z-index: 99999; background: rgba(15,23,42,.55);
          display: flex; align-items: center; justify-content: center; padding: 1rem; font: inherit; }
.lo-card { background: var(--bg2); color: var(--text); border: 1px solid var(--border);
           border-radius: var(--radius-lg); max-width: 440px; width: 100%; padding: 1.25rem 1.4rem;
           box-shadow: 0 30px 80px -20px rgba(0,0,0,.4); }
.lo-card h3 { margin: 0 0 .35rem; font-size: 1.05rem; font-weight: 700; }
.lo-card p { margin: 0 0 1rem; color: var(--text2); font-size: .92rem; line-height: 1.5; }
.lo-acts { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: flex-end; }
.lo-acts button { padding: .55rem 1rem; border: 1px solid var(--border); background: transparent;
                  border-radius: 9px; cursor: pointer; font: inherit; color: inherit; min-height: 44px; }
.lo-acts button.is-quiet { color: var(--text2); }
/* This read an unpublished token name, so it always fell through to white
   whatever the brand accent was — an invisible failure, because a fallback
   looks deliberate. TokenRegistry publishes the ink for an accent fill. */
.lo-acts button.is-primary { border: 0; background: var(--accent); color: var(--accent-ink, #fff); font-weight: 600; }
@media (max-width: 480px) { .lo-acts { flex-direction: column-reverse; } .lo-acts button { width: 100%; } }

/* The session timer's dot. Green means the session is alive; the timer turns
   it amber and red as the clock runs down, from JS, by class. */
.timer-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--success); display: inline-block; }

/* Sidebar logo + the account row at its foot. */
.sb-logo-wrap { flex: 1; display: flex; align-items: center; min-width: 0; overflow: hidden; }
.sb-logo-img { background: transparent; }
.sb-logo-animated { display: none; flex-direction: column; gap: 2px; min-width: 0; width: 100%; }
.sb-account { text-decoration: none; display: flex; align-items: center; gap: .75rem; flex: 1; min-width: 0; min-height: 44px; }
.sb-avatar-img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
.u-none { display: none; }

/* ── A CARD ON A PHONE IS A SUMMARY, NOT THE WHOLE ROW ────────────────────
   byar-cards.js marks the secondary cells and appends the toggle; these two
   rules are what actually fold them. Both are scoped to the phone breakpoint,
   so a desktop table is never touched. */
@media (max-width: 760px) {
    table.data-table tr .tr-secondary,
    table.responsive tr .tr-secondary { display: none; }
    table.data-table tr.is-open .tr-secondary,
    table.responsive tr.is-open .tr-secondary { display: flex; }

    .tr-more-cell { display: block; padding: .5rem 0 0; border: 0; }
    .tr-more-cell::before { content: none; }
    .tr-more {
        min-height: 44px; width: 100%; border: 1px solid var(--border);
        background: transparent; color: var(--text2); border-radius: 10px;
        font: inherit; font-size: .82rem; font-weight: 600; cursor: pointer;
    }
    .tr-more:hover { border-color: var(--accent); color: var(--accent-on-surface); }
}
/* Above the breakpoint the fold is irrelevant and the control must not exist
   visually even in the moment before the script unfolds the row. */
@media (min-width: 761px) { .tr-more-cell { display: none; } }
/* An inactive rule is QUIETER, not faded: opacity took its Enable and Copy
   buttons to 2.6:1. The row ground changes; every control keeps its label. */
tr.tr-inactive > td { background: var(--bg3); color: var(--text2); }
@media (max-width: 760px) {
    /* The identity line and the button row carry no label: a card's first line
       is self-evidently what the card is about, and a row of buttons labelled
       "Actions" is a word nobody needed. */
    table.data-table tr .tr-bare::before,
    table.responsive tr .tr-bare::before { content: none; }
    table.data-table tr .tr-bare,
    table.responsive tr .tr-bare { display: block; }
}
/* A textarea that needs more room than one line, without leaving the system. */
.form-control.u-tall { min-height: 60px; }

/* ─── THE SESSION TIMER ────────────────────────────────────────────────────
   It used to be an inline style built in JavaScript, pinned bottom-left over
   the page. On a phone that put a translucent pill across whatever happened to
   be at that height: on Platform status it sat over a check's "Healthy" badge,
   on Domains over the closing paragraph. A countdown nobody is waiting on does
   not earn a permanent hole in the content.

   So: visible on desktop, where the corner is genuinely empty, and on a phone
   only from the point it matters — `is-warning`, the last five minutes, when
   it stops being decoration and becomes something the reader needs. The
   expired state always shows. */
.session-timer {
  position: fixed;
  bottom: calc(.75rem + env(safe-area-inset-bottom, 0px));
  inset-inline-start: .75rem;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: .3rem .7rem;
  font-size: 0.75rem;
  color: var(--text3);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .12);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: .4rem;
  transition: opacity .3s ease, border-color .3s ease, background .3s ease;
  opacity: .85;
  pointer-events: none;
  user-select: none;
}

@media (max-width: 699px) {
  .session-timer { display: none; }
  .session-timer.is-warning,
  .session-timer.is-expired { display: flex; }
}

.session-timer.is-warning {
  border-color: var(--warning);
  background: color-mix(in srgb, var(--warning) 8%, var(--bg2));
  color: var(--text);
  opacity: 1;
}
.session-timer.is-warning .timer-dot { background: var(--warning); animation: timer-pulse 1s infinite; }

.session-timer.is-expired { border-color: var(--danger); color: var(--text); opacity: 1; }
.session-timer.is-expired .timer-dot { background: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  .session-timer .timer-dot { animation: none !important; }
}

/* ─── THE TOUCH FLOOR ──────────────────────────────────────────────────────
   Buttons written with an inline style — a colour preset on the announcement
   form, the Copy button on a code block — came out 20 and 21 pixels tall. A
   control that small is a control a thumb misses, and CLAUDE.md's floor is
   44px. The floor is applied to buttons inside the page content on phones,
   where the thumb is; a desktop mouse does not need it and dense desktop
   toolbars are left alone.

   Deliberately NOT applied inside surfaces whose whole design is a dense grid
   of keys — the dial-pad, the till keypad, the chair board, the colour
   swatches — because there the target is the CELL, which is already large,
   and stretching every key would break the grid it belongs to. */
@media (max-width: 560px) {
  .admin-content button,
  .admin-content a.btn,
  .admin-content input[type="submit"] {
    min-block-size: 44px;
  }

  .admin-content .dp-grid button,
  .admin-content .pos-keys button,
  .admin-content .swatches button,
  .admin-content .seg button,
  .admin-content .chair-board button,
  .admin-content [data-dense] button {
    min-block-size: revert;
  }
}

/* BRAND STUDIO — the heritage packs are available, never first.
   A disclosure rather than a second row of equal buttons: the platform has
   one current identity, and the retired twin brands must not read as a
   choice of equals beside it. */
.bs-heritage{margin:.6rem 0 0;border:1px solid var(--border);border-radius:12px;padding:.55rem .8rem;background:var(--surface2)}
.bs-heritage>summary{cursor:pointer;font-size:.82rem;font-weight:600;color:var(--text2);min-height:44px;display:flex;align-items:center}
.bs-heritage>p{font-size:.78rem;color:var(--text3);margin:.2rem 0 .55rem;max-width:56ch}

/* The one line under a platform settings panel's scope banner: where the
   thing you are editing shows up, and where the website itself is edited. */
.pb-where{font-size:.82rem;color:var(--text3);margin:-.75rem 0 1.25rem;max-width:72ch;line-height:1.5}

/* BRAND STUDIO — the finger floor applies here too.
   The platform's touch rule exempts dense palettes (.swatches, .seg) so a
   PIN pad and a colour strip are not forced apart; this studio's own CSS then
   left its brand switch and its colour swatches at 36px on a phone, which is
   under the floor for controls a person actually aims at. 44px, and the row
   wraps rather than overflowing. */
@media (max-width: 767px) {
  /* An explicit `height` on the studio's own rule beats a `min-height`, so
     the brand switch is given the height outright. */
  #bs-app .seg button { height: 44px; min-width: 44px; }
  #bs-app .swatches .sw { min-height: 44px; min-width: 44px; }
  #bs-app .swatches { flex-wrap: wrap; row-gap: .4rem; }
}

/* ══════════════════════════════════════════════════════════════════════
   THE TOP BAR IS ONE LINE, AT EVERY DESKTOP WIDTH.

   Measured at 1440px (Oct 2026): the control cluster was 915px wide and
   allowed to wrap, so inside a 56px bar it became two rows — the badge row
   rose 24px above the top of the window (cut off) and the language picker
   fell onto the page below. The widest member was the appearance switch at
   270px, three icons each followed by its word. In the bar the icon is the
   control; the word stays as the accessible name (visually hidden, never
   display:none) and as the tooltip the button already carries.
   ══════════════════════════════════════════════════════════════════════ */
@media (min-width: 561px) {
  .admin-topbar .topbar-right { flex-wrap: nowrap; }
  /* The breadcrumb yields its width, and gives up letters before it wraps. */
  .admin-topbar .topbar-breadcrumb { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  .admin-topbar .topbar-breadcrumb * { white-space: nowrap; }
  .admin-topbar .appearance-control button { min-height: 36px; min-width: 36px; padding-inline: .45rem; }
  .admin-topbar .appearance-control button[aria-checked="true"]::before { content: none; }
  .admin-topbar .appearance-control .appearance-label {
    position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
}
/* Below 1366px the seven colour shortcut badges step aside — each has its own
   sidebar entry and page — so the breadcrumb keeps its words (at 1280px in
   Arabic it had shrunk to a single word). */
@media (min-width: 1101px) and (max-width: 1365px) {
  .admin-topbar .topbar-right .notif-badge { display: none !important; }
}
