:root {
    --bg: #f5f6f8;
    --panel: #ffffff;
    --border: #dde1e6;
    --text: #1f2430;
    --muted: #6b7280;
    --primary: #2563eb;
    --primary-dark: #1d4ed8;
    --danger: #dc2626;
    --danger-dark: #b91c1c;
    --radius: 6px;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
    background: var(--bg);
    color: var(--text);
    display: flex;
    min-height: 100vh;
}

#nav {
    width: 210px;
    flex-shrink: 0;
    background: var(--panel);
    border-right: 1px solid var(--border);
    padding: 16px 0;
    display: flex;
    flex-direction: column;
    /* Stays put while a long page scrolls, so the nav is always one click away. */
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
}

#nav .brand {
    font-weight: 700;
    font-size: 1.1rem;
    padding: 0 16px 16px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 8px;
}

#nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    color: var(--text);
    text-decoration: none;
    font-size: 0.92rem;
}

/* Shared by the sidebar links and the phone bottom bar (which copies them). */
.nav-icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Section labels in the sidebar - see the grouping comment in nav.html. */
.nav-group {
    padding: 14px 16px 4px;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--muted);
}

#nav a:hover {
    background: var(--bg);
}

#nav a.active {
    background: #eaf0fe;
    color: var(--primary-dark);
    font-weight: 600;
    border-right: 3px solid var(--primary);
}

/* Phone chrome - top bar, bottom bar, and the backdrop behind the "More" sheet - created by
   js/api.js (setupMobileNav), hidden here by default (desktop keeps the sidebar above) and only
   shown under the mobile breakpoint at the bottom of this file. */
.mobile-topbar,
.bottom-nav,
.nav-backdrop,
.btn.filters-toggle {
    display: none;
}

main {
    flex: 1;
    padding: 24px 32px;
    max-width: 1100px;
    /* A flex item's min-width defaults to its content's width, so without this one wide table or
       tab row stretched the whole page past the screen instead of scrolling inside .table-scroll. */
    min-width: 0;
}

.lang-switcher {
    position: fixed;
    top: 14px;
    right: 20px;
    z-index: 50;
}

.lang-switcher-trigger {
    display: flex;
    align-items: center;
    gap: 5px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--panel);
    padding: 6px 10px;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}

.lang-switcher-trigger:hover {
    border-color: var(--primary);
}

.lang-switcher-flag svg,
.lang-switcher-menu svg {
    display: block;
    width: 20px;
    height: 14px;
    border-radius: 2px;
}

.lang-switcher-caret {
    font-size: 0.65rem;
    color: var(--muted);
}

.lang-switcher-menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    margin: 0;
    padding: 4px;
    list-style: none;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.lang-switcher-menu[hidden] {
    display: none;
}

.lang-switcher-menu li {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px;
    border-radius: 4px;
    cursor: pointer;
}

.lang-switcher-menu li:hover {
    background: var(--bg);
}

h1 {
    margin-top: 0;
    font-size: 1.5rem;
}

.card {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 20px;
    margin-bottom: 20px;
}

.tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--border);
    /* Unconditional, not just under the mobile breakpoint - a no-op once tabs already fit,
       so it's safe at any width and also covers a narrow desktop window. */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    flex-wrap: nowrap;
}

.tab-btn {
    padding: 8px 16px;
    border: none;
    background: none;
    cursor: pointer;
    font-size: 0.9rem;
    color: var(--muted);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    white-space: nowrap;
    flex-shrink: 0;
    /* A tab can also be a link to another page (payments.html <-> vouchers.html). */
    text-decoration: none;
    display: inline-block;
}

.tab-btn:hover {
    color: var(--text);
}

.tab-btn.active {
    color: var(--primary-dark);
    border-bottom-color: var(--primary);
    font-weight: 600;
}

.filters {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 16px;
    align-items: flex-end;
}

.field {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.field label {
    font-size: 0.78rem;
    color: var(--muted);
}

input, select, textarea {
    padding: 7px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-size: 0.9rem;
    background: white;
    color: var(--text);
}

/* Wraps every <table> (see admin pages) so it scrolls horizontally instead of overflowing the
   page - unconditional, same reasoning as .tabs above. sticky-first is an opt-in second class
   for the handful of wide (6+ column) tables, pinning the identifying first column in place
   while the rest scrolls. */
.table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.table-scroll.sticky-first th:first-child,
.table-scroll.sticky-first td:first-child {
    position: sticky;
    left: 0;
    background: var(--panel);
    z-index: 1;
}

.table-scroll.sticky-first tr:hover td:first-child {
    background: #fafbfc;
}

table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.88rem;
}

th, td {
    text-align: left;
    padding: 8px 10px;
    border-bottom: 1px solid var(--border);
}

th {
    color: var(--muted);
    font-weight: 600;
    font-size: 0.78rem;
    text-transform: uppercase;
}

th.sortable {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

th.sortable:hover {
    color: var(--text);
}

tr:hover td {
    background: #fafbfc;
}

.btn {
    display: inline-block;
    padding: 7px 14px;
    border-radius: var(--radius);
    border: none;
    background: var(--primary);
    color: white;
    font-size: 0.85rem;
    cursor: pointer;
    text-decoration: none;
}

.btn:hover {
    background: var(--primary-dark);
}

.btn.secondary {
    background: white;
    color: var(--text);
    border: 1px solid var(--border);
}

.btn.secondary:hover {
    background: var(--bg);
}

.btn.danger {
    background: var(--danger);
}

.btn.danger:hover {
    background: var(--danger-dark);
}

.btn.small {
    padding: 4px 10px;
    font-size: 0.78rem;
}

/* Icon-only row actions (edit/delete/remove/etc.) - a ghost button: no fill or border, just an
   inline SVG glyph (monochrome, currentColor, so it themes with .icon-only/.icon-only.danger
   below) with a soft round hover highlight. Accessible via title (tooltip) + aria-label on the
   button itself. Overrides the base .btn's colored/bordered look entirely - icon-only buttons
   never combine with .secondary/.danger/.small, they're a distinct minimal variant. */
.btn.icon-only {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    margin-right: 2px;
    background: transparent;
    border: none;
    border-radius: 999px;
    color: var(--muted);
}

.btn.icon-only:last-child {
    margin-right: 0;
}

.btn.icon-only:hover {
    background: var(--bg);
    color: var(--text);
}

.btn.icon-only.danger:hover {
    background: #fef2f2;
    color: var(--danger-dark);
}

.btn.icon-only svg {
    width: 16px;
    height: 16px;
    display: block;
}

/* Actions is always the last (unlabeled) column in these tables - keep its icon buttons on one
   line instead of wrapping vertically when the column gets squeezed; the table's .table-scroll
   wrapper (see above) scrolls horizontally if the row doesn't fit. */
table td:last-child {
    white-space: nowrap;
}

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 12px;
    margin-bottom: 12px;
}

.form-actions {
    display: flex;
    gap: 10px;
    /* Always its own full-width row when the form is a .form-grid - without this the buttons are
       laid out as just another grid cell, so Save ends up squeezed beside whatever field or panel
       happens to precede it. Ignored when .form-actions isn't a grid item. */
    grid-column: 1 / -1;
}

/* Transient feedback for an action that just happened - see toast() in js/api.js. Fixed to the top
   of the viewport rather than inside a card, because the thing it answers ("did my click do
   anything?") has to be visible wherever the button was, including below the fold. The stack itself
   is click-through; only the individual toasts take pointer events, so it never blocks the page. */
.toast-stack {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 12px;
    pointer-events: none;
}

.toast {
    pointer-events: auto;
    cursor: pointer;
    min-width: 240px;
    max-width: min(90vw, 560px);
    padding: 10px 14px;
    border-radius: 6px;
    border: 1px solid;
    font-size: 0.9rem;
    line-height: 1.4;
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.14);
    animation: toast-in 160ms ease-out;
}

/* Same palette as the inline .error / .success banners below, so the two channels read as one system. */
.toast.success {
    background: #ecfdf5;
    border-color: #a7f3d0;
    color: #065f46;
}

.toast.error {
    background: #fef2f2;
    border-color: #fecaca;
    color: #991b1b;
}

.toast.warning {
    background: #fffbeb;
    border-color: #fde68a;
    color: #92400e;
}

.toast.leaving {
    animation: toast-out 160ms ease-in forwards;
}

@keyframes toast-in {
    from { opacity: 0; transform: translateY(-10px); }
    to { opacity: 1; transform: none; }
}

@keyframes toast-out {
    to { opacity: 0; transform: translateY(-10px); }
}

@media (prefers-reduced-motion: reduce) {
    .toast, .toast.leaving {
        animation: none;
    }
}

.error {
    background: #fef2f2;
    border: 1px solid #fecaca;
    color: var(--danger-dark);
    padding: 10px 14px;
    border-radius: var(--radius);
    margin-bottom: 14px;
    font-size: 0.88rem;
}

.warning {
    background: #fffbeb;
    border: 1px solid #fde68a;
    color: #92400e;
    padding: 8px 12px;
    border-radius: var(--radius);
    margin-bottom: 14px;
    font-size: 0.85rem;
}

/* "Nothing found" - centred and roomy rather than a stray italic line, so an empty list reads as a
   deliberate state. On phones the page's floating "+ New" button is the call to action. */
.empty {
    color: var(--muted);
    text-align: center;
    padding: 28px 12px;
}

.badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.75rem;
    background: #eef2ff;
    color: #4338ca;
}

/* Notification severity and delivery outcome, on notifications.html. Variants of .badge rather
   than a new primitive - same shape, and severity/outcome is exactly the "encode state in form as
   well as text" case a pill already exists for. Palette matches the .error/.success banners and
   the toasts, so the whole panel reads as one system. */
.badge.info {
    background: #eef2ff;
    color: #4338ca;
}

.badge.warning {
    background: #fef3c7;
    color: #92400e;
}

.badge.critical {
    background: #fee2e2;
    color: var(--danger-dark);
}

.badge.sent {
    background: #dcfce7;
    color: #166534;
}

.badge.pending {
    background: #f1f5f9;
    color: var(--muted);
}

.badge.failed {
    background: #fee2e2;
    color: var(--danger-dark);
}

.badge.skipped {
    background: #f1f5f9;
    color: var(--muted);
    text-decoration: line-through;
}

/* Entitlement status on entitlements.html. Only "still usable" needs its own colour - the other
   three reuse .warning and .critical above rather than adding near-duplicates of them. */
.badge.active {
    background: #dcfce7;
    color: #166534;
}

/* One chip per delivery in the log's "Sent to" column; wraps rather than stretching the row. */
.chip-list {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

/* Secondary text that explains rather than labels - a page's one-line intro, a delivery's error
   under its status pill. Muted and a size down, so it reads as support for the thing above it. */
.hint {
    color: var(--muted);
    font-size: 0.85rem;
}

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 14px;
}

.stat-card {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px;
    text-decoration: none;
    color: var(--text);
    display: block;
}

.stat-card:hover {
    border-color: var(--primary);
}

.stat-card .label {
    color: var(--muted);
    font-size: 0.82rem;
}

.stat-card .go {
    color: var(--primary);
    font-size: 0.82rem;
    margin-top: 6px;
}

.stat-card .stat-value {
    font-size: 1.6rem;
    font-weight: 700;
    margin: 2px 0;
}

/* home.html "Needs attention" - one full-width tappable row per thing to do. */
.attention-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.attention-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 44px;
    padding: 10px 12px;
    margin-bottom: 8px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    border-left-width: 4px;
    color: var(--text);
    text-decoration: none;
}

.attention-item:hover {
    background: var(--bg);
}

.attention-item.warn {
    border-left-color: #d97706;
}

.attention-item.danger {
    border-left-color: var(--danger);
}

.attention-item.info {
    border-left-color: var(--primary);
}

.attention-go {
    color: var(--muted);
    font-size: 1.3rem;
}

.quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.back-link {
    display: inline-block;
    margin-bottom: 14px;
    color: var(--primary);
    text-decoration: none;
    font-size: 0.88rem;
}

.guide {
    background: #f8fafc;
    border-color: #dbeafe;
}

.guide-title {
    margin: 0 0 8px;
    font-size: 0.95rem;
}

.guide-steps {
    margin: 0 0 10px;
    padding-left: 20px;
    font-size: 0.85rem;
    line-height: 1.55;
}

.guide-steps li {
    margin-bottom: 10px;
}

.guide-commands {
    list-style: none;
    margin: 0 0 10px;
    padding: 0;
    font-size: 0.85rem;
    line-height: 1.7;
}

.guide-note {
    margin: 0;
    font-size: 0.8rem;
    color: var(--muted);
    font-style: italic;
}

.guide code, .guide-commands code {
    background: #eef2ff;
    color: #4338ca;
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 0.85em;
}

.chat-mockup {
    margin: 6px 0 2px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-width: 360px;
}

.chat-bubble {
    padding: 6px 10px;
    border-radius: 12px;
    font-size: 0.82rem;
    line-height: 1.4;
    max-width: 85%;
}

.chat-bubble.bot {
    align-self: flex-start;
    background: #eef2f7;
    color: var(--text);
    border-bottom-left-radius: 2px;
}

.chat-bubble.you {
    align-self: flex-end;
    background: var(--primary);
    color: white;
    border-bottom-right-radius: 2px;
}

.chat-bubble.you code {
    background: rgba(255, 255, 255, 0.25);
    color: inherit;
}

.btn:disabled {
    opacity: 0.5;
    cursor: default;
}

.btn.secondary:disabled:hover {
    background: white;
}

.pagination {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 12px;
    color: var(--muted);
    font-size: 0.85rem;
}

.spinner {
    display: inline-block;
    width: 15px;
    height: 15px;
    border: 2px solid var(--border);
    border-top-color: var(--primary);
    border-radius: 50%;
    vertical-align: middle;
    animation: spin 0.7s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* A row appended to a table's tbody while a reload is in flight - shown alongside whatever
   rows are already there (last known good data isn't wiped on a refetch), so a filter/sort/
   page change reads as "more coming" rather than the table flashing empty. */
.loading-row td {
    text-align: center;
    color: var(--muted);
    padding: 14px 0;
}

.loading-row .spinner {
    margin-right: 8px;
}

/* Card-level loading state for pages/tabs that derive everything from one big initial fetch
   (e.g. reports.html) rather than a single paginated table - centers a larger spinner where
   the content would otherwise render. */
.loading-panel {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 48px 0;
    color: var(--muted);
}

.loading-panel .spinner {
    width: 20px;
    height: 20px;
    border-width: 3px;
}

/* reports.html's Busiest/Quietest-days mini-tables, shown side by side - collapses to one
   column under the mobile breakpoint below instead of squeezing two tables into half-width each. */
.split-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
}

/* Generic confirm/pick popup (e.g. consumptions.html's bulk "mark as paid") - modeled on the
   mobile nav drawer's .nav-backdrop dim-overlay pattern above, but unconditional (not gated to
   the mobile breakpoint) since a popup can be triggered at any viewport width. .modal reuses
   .card for its panel chrome - just caps its width/height and makes it scrollable. */
.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
}

.modal {
    max-width: 480px;
    width: 100%;
    max-height: 85vh;
    overflow-y: auto;
}

/* Larger variant for a full editing form (e.g. products.html's create/edit product) rather than
   a short confirm/pick dialog - wide enough for a genuine two-column field layout on desktop.
   modal-header/modal-close give it the typical popup chrome (title + an upper-right "x") instead
   of relying on a Cancel button buried in .form-actions to close it. */
.modal.modal-form {
    max-width: 720px;
}

.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
}

.modal-header h3 {
    margin: 0;
}

.modal-close {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    border-radius: 999px;
    color: var(--muted);
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
}

.modal-close:hover {
    background: var(--bg);
    color: var(--text);
}

/* A guided form's field groups - a bold, full-width label ahead of the fields it introduces
   (Basics, Stock, Cost, Pass/membership settings), so a long optional-field form reads as a
   sequence of short questions instead of one flat wall of inputs. */
.form-section-title {
    grid-column: 1 / -1;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
    margin: 4px 0 -4px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
}

.form-section-title:first-child {
    padding-top: 0;
    border-top: none;
    margin-top: 0;
}

/* Explicit two-column layout for a guided form's own fields (products.html) - .form-grid's
   auto-fill(180px) still packs 3-4 narrow columns into a 720px modal, which is the "messy, lots
   of optional fields" complaint this was built to fix. Collapses to one column under the mobile
   breakpoint below, same as .form-grid already does implicitly. */
.form-grid-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-bottom: 12px;
}

.form-grid-2 .field-wide {
    grid-column: 1 / -1;
}

/* ============ Mobile/tablet (<=768px) ============
   Single breakpoint for everything that must actually branch by viewport - the phone chrome
   (top bar, bottom bar, "More" sheet), tables shown as cards, collapsible filters,
   touch-target sizing, and main's padding. .tabs/.table-scroll (above) are unconditional and
   need no breakpoint, since overflow-x: auto is a no-op until content actually overflows. */
@media (max-width: 768px) {
    :root {
        --topbar-h: 48px;
        --bottom-nav-h: 60px;
        --bottom-safe: env(safe-area-inset-bottom, 0px);
    }

    /* Keeps a focused field from scrolling underneath the fixed top/bottom bars (WCAG 2.4.11). */
    html {
        scroll-padding-top: calc(var(--topbar-h) + 8px);
        scroll-padding-bottom: calc(var(--bottom-nav-h) + var(--bottom-safe) + 8px);
    }

    body {
        display: block;
    }

    main {
        padding: calc(var(--topbar-h) + 16px) 16px calc(var(--bottom-nav-h) + var(--bottom-safe) + 88px);
    }

    h1 {
        font-size: 1.3rem;
    }

    .card {
        padding: 14px;
    }

    .split-2 {
        grid-template-columns: 1fr;
    }

    .form-grid-2 {
        grid-template-columns: 1fr;
    }

    /* home.html: the pending total gets the full width, the two "today" figures share a row. */
    .home-stats {
        grid-template-columns: 1fr 1fr;
    }

    .home-stats .stat-card:first-child {
        grid-column: 1 / -1;
    }

    /* 16px stops iOS zooming the page when a field is tapped; 44px is the touch-target floor
       (Apple HIG / WCAG 2.5.5). Desktop keeps its denser sizing. */
    input:not([type=checkbox]):not([type=radio]), select, textarea {
        font-size: 16px;
        min-height: 44px;
    }

    input[type=checkbox] {
        width: 20px;
        height: 20px;
    }

    .btn:not(.icon-only):not(.small) {
        min-height: 44px;
        font-size: 0.95rem;
    }

    .filters {
        gap: 10px;
    }

    .filters > .field {
        flex: 1 1 100%;
    }

    .filters > .field input,
    .filters > .field select {
        width: 100%;
    }

    /* See setupCollapsibleFilters in js/api.js. */
    .btn.filters-toggle {
        display: inline-flex;
        align-items: center;
        gap: 6px;
    }

    .filters.collapsed > .field:not(.filter-primary) {
        display: none;
    }

    .filters-dot {
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background: var(--primary);
    }

    .filters-dot[hidden] {
        display: none;
    }

    /* The page's main "+ New" button floats above the bottom bar, where a thumb reaches it. */
    .btn.fab {
        position: fixed;
        right: 16px;
        bottom: calc(var(--bottom-nav-h) + var(--bottom-safe) + 16px);
        z-index: 70;
        border-radius: 999px;
        padding: 12px 20px;
        box-shadow: 0 4px 14px rgba(37, 99, 235, 0.35);
    }

    /* ---- Tables become cards (rows labelled by labelTableForCards in js/api.js) ---- */

    .table-scroll:not(.no-cards) {
        overflow: visible;
    }

    .table-scroll:not(.no-cards) table,
    .table-scroll:not(.no-cards) tbody,
    .table-scroll:not(.no-cards) tr,
    .table-scroll:not(.no-cards) td {
        display: block;
        width: 100%;
    }

    /* The header row stays, as a strip of sort buttons (and the select-all box where there is
       one) - hiding it would take sorting away on phones. */
    .table-scroll:not(.no-cards) thead {
        display: block;
    }

    .table-scroll:not(.no-cards) thead tr {
        display: flex;
        gap: 6px;
        overflow-x: auto;
        padding-bottom: 8px;
    }

    .table-scroll:not(.no-cards) thead th {
        border: 1px solid var(--border);
        border-radius: 999px;
        padding: 6px 12px;
        white-space: nowrap;
        background: var(--panel);
    }

    .table-scroll:not(.no-cards) thead th:empty {
        display: none;
    }

    /* The select-all checkbox header: just the box, no pill around it. */
    .table-scroll:not(.no-cards) thead th.th-unlabelled {
        border: none;
        background: transparent;
        padding: 6px 4px;
        align-self: center;
    }

    /* Horizontal strips (sort buttons, tabs) scroll by swipe; the bar itself is just noise. */
    .table-scroll:not(.no-cards) thead tr,
    .tabs {
        scrollbar-width: none;
    }

    .table-scroll:not(.no-cards) thead tr::-webkit-scrollbar,
    .tabs::-webkit-scrollbar {
        display: none;
    }

    .table-scroll:not(.no-cards) tbody tr {
        position: relative;
        border: 1px solid var(--border);
        border-radius: 10px;
        padding: 10px 14px;
        margin-bottom: 10px;
        background: var(--panel);
    }

    .table-scroll:not(.no-cards) tr:hover td {
        background: transparent;
    }

    .table-scroll:not(.no-cards) td {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: 12px;
        padding: 3px 0;
        border: none;
        text-align: right;
        white-space: normal;
        position: static;
    }

    .table-scroll:not(.no-cards) td::before {
        content: attr(data-label);
        color: var(--muted);
        font-size: 0.78rem;
        text-align: left;
        flex-shrink: 0;
    }

    .table-scroll:not(.no-cards) td[data-label=""]::before,
    .table-scroll:not(.no-cards) td.card-title::before {
        display: none;
    }

    .table-scroll:not(.no-cards) td.card-title {
        justify-content: flex-start;
        text-align: left;
        font-weight: 600;
        font-size: 1rem;
        padding-right: 36px;
    }

    .table-scroll:not(.no-cards) td.card-select {
        position: absolute;
        top: 10px;
        right: 12px;
        width: auto;
    }

    .table-scroll:not(.no-cards) td.card-actions {
        justify-content: flex-end;
        border-top: 1px solid var(--border);
        margin-top: 6px;
        padding-top: 6px;
    }

    /* Loading spinners and "nothing found" rows are a single spanning cell - no card around them. */
    .table-scroll:not(.no-cards) tbody tr.card-plain {
        border: none;
        background: transparent;
        padding: 0;
    }

    .table-scroll:not(.no-cards) tr.card-plain td {
        justify-content: center;
        text-align: center;
    }

    /* The product-editing popup fills the whole screen on mobile (no page underneath to see, and
       a fixed-size dialog on a small viewport just adds its own inner scrollbar) - everything
       else still opens as a centered, size-capped dialog. modal-backdrop-form is set directly in
       the markup that wraps a .modal-form, rather than via :has(), for older-browser safety. */
    .modal-backdrop.modal-backdrop-form {
        padding: 0;
    }

    .modal.modal-form {
        max-width: 100%;
        width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
    }

    /* ---- Phone chrome (built by setupMobileNav / fillMobileNav in js/api.js) ---- */

    .mobile-topbar {
        display: flex;
        align-items: center;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        z-index: 80;
        height: var(--topbar-h);
        padding: 0 16px;
        background: var(--panel);
        border-bottom: 1px solid var(--border);
        font-weight: 700;
    }

    .mobile-topbar-title {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .bottom-nav {
        display: grid;
        grid-template-columns: repeat(5, 1fr);
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 90;
        height: calc(var(--bottom-nav-h) + var(--bottom-safe));
        padding-bottom: var(--bottom-safe);
        background: var(--panel);
        border-top: 1px solid var(--border);
    }

    .bottom-nav-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        min-width: 0;
        padding: 0 2px;
        border: none;
        background: none;
        color: var(--muted);
        font: inherit;
        font-size: 0.7rem;
        text-decoration: none;
        cursor: pointer;
        touch-action: manipulation;
    }

    .bottom-nav-item span {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .bottom-nav-item .nav-icon {
        width: 22px;
        height: 22px;
    }

    .bottom-nav-item.active {
        color: var(--primary-dark);
        font-weight: 600;
    }

    /* #nav becomes the "More" sheet, sliding up from above the bottom bar. The four links already
       in the bottom bar are hidden here so the sheet only lists what the bar does not. */
    #nav {
        position: fixed;
        left: 0;
        right: 0;
        top: auto;
        bottom: calc(var(--bottom-nav-h) + var(--bottom-safe));
        z-index: 100;
        width: auto;
        height: auto;
        max-height: 75vh;
        padding: 8px 0 12px;
        border-right: none;
        border-radius: 14px 14px 0 0;
        box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.15);
        transform: translateY(calc(100% + var(--bottom-nav-h) + var(--bottom-safe)));
        transition: transform 0.2s ease;
        overflow-y: auto;
        visibility: hidden;
    }

    #nav.nav-open {
        transform: translateY(0);
        visibility: visible;
    }

    #nav a[data-bottom] {
        display: none;
    }

    #nav a {
        min-height: 48px;
        font-size: 1rem;
    }

    #nav .brand {
        padding-top: 4px;
    }

    #nav .lang-switcher {
        position: static;
        margin: 12px 16px 0;
    }

    #nav #nav-logout {
        min-height: 44px;
    }

    .nav-backdrop {
        display: none;
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.4);
        z-index: 95;
    }

    .nav-backdrop.nav-open {
        display: block;
    }

    /* 30px is fine for a mouse on desktop; bumped to the ~44px touch-target floor (Apple HIG /
       WCAG 2.5.5 AAA) here only - no reason to bloat the already-shipped desktop density. */
    .btn.icon-only {
        width: 44px;
        height: 44px;
        margin-right: 4px;
    }

    .btn.icon-only svg {
        width: 20px;
        height: 20px;
    }

    .toast-stack {
        top: var(--topbar-h);
    }
}

@media (max-width: 768px) and (prefers-reduced-motion: reduce) {
    #nav {
        transition: none;
    }
}

/* ---------------------------------------------------------------- Plans & billing
   The plan banner every page shows above its content (js/api.js renderPlanBanner), the usage bars
   and the plan cards on billing.html. Same palette as the .error/.success banners and toasts. */
.plan-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 10px 14px;
    margin-bottom: 16px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    border-left-width: 4px;
    background: var(--panel);
    color: var(--text);
    font-size: 0.9rem;
}

.plan-banner.info {
    border-left-color: var(--primary);
}

.plan-banner.warn {
    border-left-color: #d97706;
    background: #fffbeb;
}

.plan-banner.danger {
    border-left-color: var(--danger);
    background: #fef2f2;
}

.plan-banner a {
    font-weight: 600;
    white-space: nowrap;
}

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

.usage-row .usage-label {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    font-size: 0.9rem;
    margin-bottom: 4px;
}

.usage-bar {
    height: 8px;
    border-radius: 999px;
    background: #e5e7eb;
    overflow: hidden;
}

.usage-bar > span {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--primary);
}

.usage-bar.warn > span {
    background: #d97706;
}

.usage-bar.over > span {
    background: var(--danger);
}

.plan-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 12px;
}

.plan-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--panel);
}

.plan-card.current {
    border-color: var(--primary);
    box-shadow: 0 0 0 1px var(--primary);
}

.plan-card h3 {
    margin: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
}

.plan-card .plan-price {
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--text);
}

.plan-card ul {
    margin: 6px 0 0;
    padding-left: 18px;
    font-size: 0.88rem;
}

.plan-card li.off {
    color: var(--muted);
    text-decoration: line-through;
}

.plan-card .plan-actions {
    margin-top: auto;
    padding-top: 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.interval-toggle {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: 999px;
    overflow: hidden;
    margin-bottom: 12px;
}

.interval-toggle button {
    border: 0;
    background: transparent;
    padding: 6px 14px;
    min-height: 36px;
    cursor: pointer;
    color: var(--text);
}

.interval-toggle button.active {
    background: var(--primary);
    color: #fff;
}

/* A support ticket's conversation (support.html, superadmin-support.html): the tenant's messages
   on the left, support's on the right in the primary tint, so who said what reads at a glance. */
.ticket-message {
    max-width: 85%;
    margin: 0 auto 10px 0;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--panel);
}

.ticket-message.support {
    margin: 0 0 10px auto;
    background: #eef2ff;
    border-color: #c7d2fe;
}

.ticket-message-meta {
    font-size: 0.78rem;
    color: var(--muted);
    margin-bottom: 4px;
}

.ticket-message-body {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    color: var(--text);
}
