/* ============================================================
   GANGA WATER – Professional Design System
   ============================================================ */

:root {
    --bg: #f4f6f8;
    --surface: #ffffff;
    --surface-alt: #f8f9fb;
    --surface-muted: #f1f3f5;
    --line: #e5e7eb;
    --line-strong: #d1d5db;
    --text: #1f2937;
    --text-secondary: #6b7280;
    --muted: #6b7280;

    /* The brand colour and everything derived from it. The manager can pick a
       different colour in Account settings; brand_palette_style() in
       helpers.php then overrides this whole group with values worked out from
       that colour, so text laid on it stays readable whatever was picked.
         --primary        fills and borders
         --primary-rgb    the same colour as channels, for translucent rings
         --primary-hover  a filled button under the pointer
         --primary-dark   text on a --primary-light tint
         --primary-light  the tint behind a selected thing
         --primary-ink    the brand colour used as text on white
         --on-primary     text laid on a --primary fill */
    --primary: #0d9488;
    --primary-rgb: 13 148 136;
    --primary-hover: #0f766e;
    --primary-dark: #0f766e;
    --primary-light: #ccfbf1;
    --primary-ink: #0d9488;
    --on-primary: #ffffff;

    --danger: #ef4444;
    --danger-light: #fef2f2;
    --success: #059669;
    --success-light: #ecfdf5;
    --warning: #d97706;
    --warning-light: #fffbeb;
    --info: #2563eb;
    --info-light: #eff6ff;

    /* WhatsApp's own green. Borrowed rather than chosen: the button is found by
       its colour before it is read, and any other green would slow that down. */
    --whatsapp: #128c7e;

    /* Money the customer owes, by what kind of owing it is.
       Deliberately not --danger: that red is a UI alarm, and at the weight a
       currency figure is set it shouted over the teal the rest of the page is
       built on. This is a print-ink red — legible against white and against
       the mint of a selected row, quiet enough to sit in a long list.
       The unbilled figure is a slate grey rather than a second warm colour:
       red beside amber reads as one shade of "money" from arm's length, and
       the whole point of the pair is that the one you have already asked for
       jumps out of a list of thirty names. */
    --money-billed: #b42318;
    --money-billed-bg: #fef3f2;
    --money-billed-line: #fbd5d1;
    --money-unbilled: #667085;

    --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
    --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.08), 0 1px 2px -1px rgb(0 0 0 / 0.08);
    --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.08), 0 2px 4px -2px rgb(0 0 0 / 0.05);
    --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.08), 0 4px 6px -4px rgb(0 0 0 / 0.03);

    --radius-sm: 6px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-xl: 16px;
    --radius-full: 9999px;

    --icon-phone: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linejoin='round'%3E%3Cpath d='M21.45 17v2.6a1.9 1.9 0 0 1-2.1 1.9 18.8 18.8 0 0 1-8.2-2.9 18.5 18.5 0 0 1-5.7-5.7A18.8 18.8 0 0 1 2.55 4.6 1.9 1.9 0 0 1 4.45 2.5h2.6a1.9 1.9 0 0 1 1.9 1.6 12 12 0 0 0 .7 2.6 1.9 1.9 0 0 1-.4 2L7.95 10.1a15.2 15.2 0 0 0 5.7 5.7l1.4-1.3a1.9 1.9 0 0 1 2-.4 12 12 0 0 0 2.6.7 1.9 1.9 0 0 1 1.6 1.9Z'/%3E%3C/svg%3E");

    --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    /* Devanagari names come last in the Hindi stack only so a mixed line —
       "12 कैंपर" — keeps its digits in Inter and its words in Noto. */
    --font-hi: 'Inter', 'Noto Sans Devanagari', -apple-system, BlinkMacSystemFont, 'Nirmala UI', 'Kohinoor Devanagari', sans-serif;
    --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ---- RESET & BASE ---- */

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
[hidden] { display: none !important; }

body {
    margin: 0;
    min-height: 100vh;
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.6;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

a { color: inherit; text-decoration: none; }
button, input, textarea, select { font: inherit; }
h1, h2, h3, h4, h5, h6 { margin: 0; line-height: 1.3; }
p { margin: 0; }
img { max-width: 100%; display: block; }

/* ---- LAYOUT SHELL ---- */

.app-shell {
    width: min(1340px, 100%);
    margin: 0 auto;
    min-height: 100vh;
    padding: 16px;
    /* How much of the viewport this shell's own padding uses up, top and
       bottom together. Published as a variable because the padding changes at
       both breakpoints and the sign-in page sizes itself to what is left: a
       page that assumed 16px here grew past the viewport on desktop, where the
       padding is 24, and brought a scrollbar with it. Kept beside each padding
       declaration so the two cannot drift. */
    --shell-pad-y: 32px;
}

.page,
.workspace-page {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

/* ---- TYPOGRAPHY ---- */

.muted,
.lead { color: var(--text-secondary); }

/* ---- SURFACES & CARDS ---- */

.hero-card,
.panel,
.info-card,
.stat-card,
.list-card,
.surface-card,
.metric-card,
.mini-metric {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xs);
}

.hero-card,
.panel,
.surface-card { padding: 20px; }

.panel {
    width: min(480px, 100%);
    margin: 0 auto;
}

.info-card,
.stat-card,
.list-card,
.metric-card,
.mini-metric,
.detail-card {
    padding: 16px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xs);
}

.hero-card { position: relative; overflow: hidden; }
.hero-card::after { display: none; }

.hero-card h1,
.panel h1 {
    font-size: 20px;
    font-weight: 700;
}

/* ---- GRIDS ---- */

.grid-cards, .stats-grid, .list-stack, .form-stack,
.metric-grid, .content-stack, .dashboard-grid,
.dashboard-grid-wide, .data-list, .quick-actions,
.sidebar-stack, .hero-badge-stack, .detail-stack,
.field-grid, .detail-grid, .customer-admin-layout {
    display: grid;
    gap: 16px;
}

.grid-cards, .stats-grid, .metric-grid,
.dashboard-grid, .dashboard-grid-wide,
.detail-grid, .field-grid, .hero-badge-stack {
    grid-template-columns: 1fr;
}

.metric-card strong,
.mini-metric strong,
.hero-badge strong,
.stat-card strong {
    display: block;
    margin-top: 8px;
    font-size: 22px;
    line-height: 1;
    font-weight: 700;
    color: var(--text);
}

.metric-card span,
.mini-metric span,
.stat-card span {
    font-size: 12px;
    font-weight: 500;
    color: var(--text-secondary);
}

/* ---- BUTTONS ---- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 44px;
    padding: 10px 18px;
    border: none;
    border-radius: var(--radius-md);
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
    transition: all var(--transition);
    white-space: nowrap;
}

.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.btn:active { transform: translateY(0); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; }

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

.btn-secondary {
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--line);
}
.btn-secondary:hover { background: var(--surface-muted); }

/* Deleting something that does not come back. Filled for the button that
   actually does it, outlined for the one that only opens the question — the
   same distinction the rest of the app draws between primary and secondary,
   kept in the danger colour so neither can be mistaken for a save. */
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { background: #dc2626; }

.btn-danger-outline {
    background: var(--surface);
    color: var(--danger);
    border: 1px solid var(--line);
}

.btn-danger-outline:hover {
    background: var(--danger-light);
    border-color: var(--danger);
}

.btn-call { background: var(--primary); color: var(--on-primary); }
.btn-small { min-height: 36px; padding: 8px 14px; font-size: 12px; }
.btn-icon { min-width: 36px; padding: 8px 10px; }

/* A filter or sort button that is switched on. */
.btn-icon[aria-pressed="true"],
.btn-icon[aria-pressed="true"]:hover {
    background: var(--primary-light);
    color: var(--primary-dark);
    border-color: var(--primary-ink);
}

/* The unpaid-bills toggle in the red of the amount it narrows the list to, so
   the list and the button read as one thing. */
.btn-toggle-unpaid[aria-pressed="true"],
.btn-toggle-unpaid[aria-pressed="true"]:hover {
    background: var(--money-billed-bg);
    color: var(--money-billed);
    border-color: var(--money-billed-line);
}

/* ---- FORMS ---- */

label {
    display: grid;
    gap: 6px;
    font-weight: 600;
    font-size: 13px;
    color: var(--text);
}

input, textarea, select {
    width: 100%;
    min-width: 0;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text);
    font-size: 14px;
    outline: none;
    transition: border-color var(--transition), box-shadow var(--transition);
}

/* One height for every control on a form row.

   Left to themselves a text box, a date box and a dropdown all size to their
   own content and land within a pixel or two of each other — which is enough
   to knock the labels above them off a shared line, and a row of labels that
   nearly lines up reads as broken in a way one that plainly does not never
   would. 42px is what the custom dropdown button already stands at, so this is
   the rest of the controls agreeing with it rather than a new number.

   Textareas are left out: their height is the number of lines asked for. */
/* Scoped to `.form-stack`, which is what the modals' forms are, and only those.
   The delivery sheet's own toolbar is a `.sheet-toolbar` of deliberately
   smaller controls sitting beside a pair of 34px stepper buttons; sizing every
   input on the page from here left that row a jumble of three different
   heights. */
/* Radios and checkboxes are left out: stretched to 42px they drew the text-box
   border and focus ring as a tall rectangle around the dot. */
.form-stack input:not([type="radio"]):not([type="checkbox"]), .form-stack select { min-height: 42px; }

/* A date or time box is a couple of pixels taller than a text box at the same
   font size, because the browser draws its own picker widget inside it. That is
   enough to leave "Delivery date" sitting a hair below "Delivery time" beside
   it. Sized here from the same three parts every other control is made of — one
   line box, the vertical padding, the border — rather than a fixed pixel value,
   so it stays in step when the type scale changes (a phone sets these to 16px
   to stop Safari zooming the page on focus). */
.form-stack input[type="date"],
.form-stack input[type="time"],
.form-stack input[type="month"],
.form-stack input[type="datetime-local"] {
    height: calc(1.6em + 22px);
}

/* A field that only states a fact — the order a payment is being recorded
   against. Drawn as a field so the form reads as one column of them, and
   greyed so it is plainly not a box to type in. */
input[readonly] {
    background: var(--surface-muted);
    color: var(--text-secondary);
    cursor: default;
}

select {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 12px;
    padding-right: 36px;
}

/* ---- CUSTOM DROPDOWN ----
   The real <select> is kept for form submission but taken out of sight; JS
   draws the button and list so an open dropdown looks the same everywhere
   instead of being painted by the operating system. */
.select-wrap {
    position: relative;
    display: block;
    width: 100%;
    /* Never let the chosen value set the width of the column holding it. */
    min-width: 0;
}

.field-grid > label { min-width: 0; }

.select-wrap > select {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    opacity: 0;
    overflow: hidden;
    clip-path: inset(50%);
    pointer-events: none;
}

.select-button {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    min-height: 42px;
    /* The chevron gets its own column, so long labels can never run under it. */
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text);
    font-size: 14px;
    text-align: left;
    cursor: pointer;
    transition: border-color var(--transition), box-shadow var(--transition);
}

.select-button:hover { border-color: var(--line-strong); }

.select-wrap.is-open .select-button,
.select-button:focus-visible {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgb(var(--primary-rgb) / 0.1);
    outline: none;
}

.select-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    /* A long value fades out before it reaches the chevron rather than running
       up against it. The label box is always the full free width, so a short
       value never reaches the gradient and stays perfectly crisp. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}

.select-chevron {
    flex: none;
    color: var(--text-secondary);
    transition: transform var(--transition);
}

.select-wrap.is-open .select-chevron { transform: rotate(180deg); }

.select-list {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 60;
    display: none;
    flex-direction: column;
    max-height: 260px;
    overflow-y: auto;
    padding: 4px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
}

.select-wrap.is-open .select-list { display: flex; }

.select-option {
    /* Without this the column flex squashes every row once the list is longer
       than its max-height, instead of scrolling. */
    flex: none;
    padding: 9px 12px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font-size: 14px;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

.select-option:hover, .select-option:focus-visible {
    background: var(--surface-muted);
    outline: none;
}

.select-option.is-selected {
    background: var(--primary-light);
    color: var(--primary-dark);
    font-weight: 600;
}

/* Two-line option: the label leads, the detail underneath tells two similarly
   named entries apart without widening the dropdown. */
.select-option-main,
.select-option-hint {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
}

.select-option-main { font-weight: 600; }

.select-option-hint {
    margin-top: 2px;
    font-size: 12px;
    font-weight: 400;
    line-height: 1.4;
    color: var(--text-secondary);
}

.select-option.is-selected .select-option-hint { color: var(--primary-dark); opacity: 0.75; }

/* Search box pinned above the options it filters. */
.select-search {
    flex: none;
    position: sticky;
    top: 0;
    z-index: 1;
    margin-bottom: 4px;
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
    font-size: 14px;
}

.select-empty {
    flex: none;
    margin: 0;
    padding: 10px 12px;
    font-size: 13px;
    color: var(--text-secondary);
}

/* The last row of a creatable list — "Add X as a new customer". Ruled off and
   tinted so it reads as an action rather than as one more name in the list,
   which is what it would look like sharing the options' plain styling. It is
   pinned to the bottom so a long directory scrolls past it to the same place
   every time. */
.select-create {
    position: sticky;
    /* The list carries 4px of padding, and that padding is inside the scroll
       area — so a row pinned to `bottom: 0` left a 4px strip beneath it that
       the options underneath scrolled through, showing a sliver of a name below
       the divider. Bleeding into the padding on all three sides closes it: the
       row now reaches the real edge of the list and nothing passes under it.
       The extra 4px of padding puts the text back in line with the options. */
    bottom: -4px;
    margin: 4px -4px -4px;
    padding: 9px 16px;
    border-top: 1px solid var(--line);
    border-radius: 0 0 calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px);
    background: var(--surface);
    color: var(--primary-dark);
    font-weight: 600;
}

.select-create:hover, .select-create:focus-visible { background: var(--primary-light); }

/* ---- Quick customer panel ----
   Lives inside the order and subscription modals, hidden until the customer
   dropdown offers to create somebody. Inset and ruled so that once it is open
   it reads as a form within a form — the three fields belong to the new
   customer, not to the order being written around them. */
.quick-customer {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: -2px 0 2px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-muted);
}

.quick-customer .field-grid { gap: 10px; }

.quick-customer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: 14px;
}

/* ---- Routes, from a customer's card ----
   The picker is `.choice-set` — the idiom this dashboard already uses for "pick
   one of these". A round can also be deleted from its own row, so the row grows
   an x at the end; General has none, because it is not a round anybody made. */
.choice-row {
    display: flex;
    align-items: stretch;
    gap: 6px;
}

/* The sortable list keeps the same gap the fieldset gives its own children,
   now that these rows sit inside a wrapper of their own. */
.choice-sortable {
    display: grid;
    gap: 8px;
}

.choice-row .choice { flex: 1; }

/* Quiet until you go for it. It sits beside a control the manager presses all
   the time, and the two must not look like the same kind of button. */
.choice-remove {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    min-width: 30px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    align-self: center;
    height: 30px;
    transition: color var(--transition), border-color var(--transition), background var(--transition);
}

.choice-remove:hover {
    border-color: var(--danger);
    background: color-mix(in srgb, var(--danger) 8%, transparent);
    color: var(--danger);
}

/* `.text-link` is written for anchors, so on a <button> it keeps the browser's
   grey face and border — and in a grid it stretches the full width, which is
   how a text link ends up looking like an empty input. Flattened back to text
   and shrunk to its words. */
.route-add-toggle {
    justify-self: start;
    padding: 0;
    border: none;
    background: none;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

/* Stacked, not inline: the label above the field is how every other form in
   this app is written, and a lone field beside a button read as a search box. */
.route-create {
    display: grid;
    gap: 10px;
    margin-top: 4px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-muted);
}

/* Name and priority on one line: they are one decision — where this round goes
   and what it is called — and splitting them down the panel would read as two
   questions, the second of which is easy to leave alone. The name takes the
   room it needs; the priority is two characters and asks for no more. */
/* Discard sits to the left of the button that commits, the same order as the
   modal's own footer underneath it — one grammar for "go back, or go on", and
   the two are the same height and the same width so the pair reads as one
   control with two answers rather than a main action and an afterthought. */
.route-create-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 10px;
}

.route-create-actions .btn { flex: 0 0 auto; min-width: 148px; }

@media (max-width: 520px) {
    /* On a phone they take the row between them, still matched. */
    .route-create-actions .btn { flex: 1 1 0; min-width: 0; }
}


/* ---- Generating a delivery sheet ----
   The count and the button on one line: the number is the reason the button is
   being pressed, so they are read together. */
.sheet-generate {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-muted);
}

.sheet-generate strong { font-size: 14px; }

@media (max-width: 620px) {
    .sheet-generate {
        flex-direction: column;
        align-items: stretch;
    }

    .sheet-generate .btn,
    .route-create .btn { width: 100%; justify-self: stretch; }
}

/* `.text-link` is written for anchors, so on a <button> it keeps the browser's
   default border and grey face and reads as a second, competing action beside
   "Create and select". Flattened back to text. */
.quick-customer-head .text-link {
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
}

.quick-customer-error {
    margin: 0;
    font-size: 13px;
    color: var(--danger);
}

input:focus, textarea:focus, select:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgb(var(--primary-rgb) / 0.1);
}

.form-inline, .pause-form, .filter-inline, .inventory-form {
    display: grid;
    gap: 12px;
}

.checkbox-row {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 600;
    font-size: 13px;
}

.checkbox-row input {
    width: 18px;
    height: 18px;
    min-width: 18px;
    margin: 0;
    accent-color: var(--primary);
}

/* A list of channels you tick, each a full-width hit target with its own
   hint underneath — a bare checkbox row leaves the label as the only thing
   worth aiming at, which is a poor target on a phone. */
.choice-set {
    display: grid;
    gap: 8px;
    margin: 0;
    padding: 0;
    border: none;
}

.choice-set legend {
    padding: 0 0 8px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
}

.choice {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition);
}

.choice:hover { background: var(--surface-muted); }

.choice input {
    width: 18px;
    height: 18px;
    min-width: 18px;
    margin: 1px 0 0;
    padding: 0;
    border: 0;
    box-shadow: none;
    accent-color: var(--primary);
}

.choice:has(input:checked) {
    border-color: var(--primary);
    background: color-mix(in srgb, var(--primary) 6%, transparent);
}

.choice:focus-within { border-color: var(--primary); }

/* Pay-to options: radio centred on its two lines of text. */
.payto-form .choice { align-items: center; }
.payto-form .choice input { margin: 0; }

.choice-body { display: grid; gap: 2px; }
.choice-label { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13px; }
.choice-hint { font-size: 12px; color: var(--muted); }

/* ---- The place a round takes in the running order ----
   A number in front of the name, because that is where the eye reads an order
   from — 1, 2, 3 down the list, the same shape as the bands on the sheet the
   list is describing. Quiet enough not to compete with the name it is labelling:
   the name is what is being chosen, the number is what it costs. */
.choice-rank {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    border: 1px solid var(--line);
    color: var(--muted);
    font-size: 11px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
}

/* ---- Moving a round up or down the running order ----
   The handle is the only part of the row that drags, so it has to look like a
   thing you take hold of and not like a third button competing with the radio
   and the delete. Quiet until the row is hovered or it is focused. */
.choice-grip {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    min-width: 26px;
    padding: 0;
    border: none;
    background: none;
    color: var(--line-strong);
    cursor: grab;
    touch-action: none;
    align-self: stretch;
    transition: color var(--transition);
}

.choice-row:hover .choice-grip,
.choice-grip:focus-visible { color: var(--text-secondary); }

.choice-grip:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* While a drag is running, nothing in the list should look clickable — the
   pointer is holding a row, not choosing one. */
.choice-sortable.is-sorting { cursor: grabbing; }
.choice-sortable.is-sorting .choice { cursor: grabbing; }
.choice-sortable.is-sorting .choice-grip { cursor: grabbing; }

/* The row being carried: lifted off the list, and light enough that the gap it
   leaves behind reads as where it would land. */
.choice-row.is-dragging {
    opacity: 0.9;
    transform: scale(0.995);
}

.choice-row.is-dragging .choice {
    border-color: var(--primary);
    box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
}

/* ---- Routes dialog: route priority, then the stop order per route ----
   Two sections. Route rows and customer rows are the same sortable rows as
   elsewhere, tighter and one line each, because the stop list runs to forty
   and more. Nothing in them is chosen, so they are not pointers; with a mouse
   a row is picked up whole (see `data-drag-row`).

   Every flex child down the row allows itself to shrink (`min-width: 0`), so a
   long name ends in an ellipsis instead of widening the row past the dialog —
   which on a phone scrolled the whole sheet sideways and cut its left edge. */
.round-modal { overflow-x: hidden; }

.round-modal .route-modal-body { margin-top: 16px; }

/* Two tabs, the segmented look the delivery sheet's own panes use: a grey
   track with the open one lifted out of it. */
.round-tabs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px;
    margin-bottom: 14px;
    padding: 4px;
    border-radius: var(--radius-md);
    background: var(--surface-muted);
}

.round-tab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 10px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
}

.round-tab:hover { color: var(--text); }
.round-tab[aria-selected="true"] { background: var(--surface); color: var(--primary-ink); box-shadow: var(--shadow-xs); }
.round-tab:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

.round-tab-badge,
.round-tab-count {
    min-width: 18px;
    padding: 1px 6px;
    border-radius: var(--radius-full);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.4;
    text-align: center;
}

.round-tab-badge { background: var(--info-light); color: var(--info); }
.round-tab-count { background: var(--surface-muted); color: var(--muted); }
.round-tab[aria-selected="true"] .round-tab-count { background: var(--surface-alt); }

.round-section-hint { margin: 0 0 10px; font-size: 12px; }

/* Clear of the strip the first route band paints above itself. */
.round-section-hint + .round-route { margin-top: 14px; }

.round-modal .choice-row { min-width: 0; }
.round-modal .choice-row > .choice { min-width: 0; }
.round-modal .choice-label { flex-wrap: nowrap; min-width: 0; }
.round-modal .choice-label .pill { flex: none; }
.round-modal .choice-rank { width: auto; min-width: 24px; padding: 0 5px; }
.round-modal .choice-grip { width: 34px; min-width: 34px; }
.round-name { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }

.route-priority-list,
.round-list { gap: 6px; }

.route-priority-row {
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    cursor: default;
}

.route-priority-list[data-drag-row] .route-priority-row { cursor: grab; }
.route-priority-row .choice-hint { flex: none; }

/* Renaming happens in the row: the name and count give way to a field. */
.route-rename { display: flex; flex: 1; align-items: center; gap: 6px; min-width: 0; }
.route-rename input[type="text"] { flex: 1; min-width: 0; height: 32px; padding: 0 10px; font-size: 13px; }
.route-priority-row:has(> .route-rename:not([hidden])) > .choice-label,
.route-priority-row:has(> .route-rename:not([hidden])) > .choice-hint { display: none; }
.route-priority-row:has(> .route-rename:not([hidden])) { cursor: default; padding: 6px 8px; }

/* The pencil, built like the x beside it so the two read as a pair. */
.choice-edit {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    min-width: 30px;
    height: 30px;
    align-self: center;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    transition: color var(--transition), border-color var(--transition), background var(--transition);
}

.choice-edit:hover { border-color: var(--primary); color: var(--primary-ink); }
.choice-edit:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* General: shown for completeness, at the foot of the list it always prints
   at, without a handle or an x — it is where the rest fall back to. */
.route-priority-row.is-fixed {
    margin: 6px 72px 0 40px;
    border-style: dashed;
    background: var(--surface-alt);
}
.route-priority-row.is-fixed:hover { background: var(--surface-alt); }

.round-modal .route-add-toggle { margin-top: 10px; }

/* One route's stops under its heading. The heading is a band that stays at
   the top of the dialog while that route's customers scroll beneath it, then
   gives way to the next route's. */
.round-route + .round-route { margin-top: 16px; }

.round-route-head {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    padding: 9px 12px;
    border: 1px solid color-mix(in srgb, var(--primary) 22%, var(--line));
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--primary) 7%, var(--surface));
    /* Covers the strip above the band, so rows scrolling past do not show
       between it and the top of the dialog. */
    box-shadow: 0 -12px 0 var(--surface);
    color: var(--primary-ink);
}

.round-route-name {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text);
}

.round-route-count {
    flex: none;
    min-width: 22px;
    padding: 1px 7px;
    border-radius: var(--radius-full);
    background: var(--surface);
    font-size: 11px;
    font-weight: 700;
    text-align: center;
    color: var(--muted);
}

.round-route-pending { flex: none; margin-left: auto; }
.round-route-note { margin: 0 0 8px; font-size: 12px; }
/* An empty route's list keeps a drop target the size of a row, so a customer
   can be dragged into it; it disappears once anybody is in the list. */
.round-drop-hint {
    display: none;
    margin: 0;
    padding: 10px 12px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-md);
    font-size: 12px;
    color: var(--muted);
    text-align: center;
}

.round-list:not(:has(.choice-row)) .round-drop-hint { display: block; }

/* Rows from every route are dragged between the lists; the one the pointer is
   over is the one it will land in. */
.round-list.is-sorting { min-height: 40px; }

.round-stop { align-items: center; padding: 8px 12px; cursor: default; }
.round-list[data-drag-row] .round-stop { cursor: grab; }
.round-stop.is-flagged { border-style: dashed; }

/* The customer's route dialog points here for everything but picking. */
.route-manage-note { margin: 0; font-size: 12px; }

/* Customer → Pay-to: the boxes only while "Separate" is chosen. Hidden, not
   removed — the server ignores them for "Default", and switching back and
   forth keeps what was typed. */
.payto-form:not(:has(input[name="payto_mode"][value="own"]:checked)) .payto-fields { display: none; }
.payto-note { margin: 0; font-size: 12px; }

/* The chosen round owns its number the way it owns its border. */
.choice:has(input:checked) .choice-rank {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
}

.compact-top { margin-top: 16px; }

/* ---- TOAST NOTIFICATIONS ---- */

.toast-wrapper {
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 10000;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 280px;
    max-width: 380px;
    padding: 12px 16px;
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    border: 1px solid var(--line);
    font-weight: 500;
    font-size: 13px;
    pointer-events: auto;
    cursor: pointer;
    opacity: 0;
    transform: translateX(100%);
    animation: toastIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

.toast:hover { box-shadow: var(--shadow-lg); }
.toast.hiding { animation: toastOut 0.25s ease forwards; }

@keyframes toastIn {
    to { transform: translateX(0); opacity: 1; }
}
@keyframes toastOut {
    to { transform: translateX(120%); opacity: 0; }
}

.toast-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: var(--radius-full);
    font-weight: 700;
    font-size: 12px;
    flex-shrink: 0;
}

.toast-success { border-left: 3px solid var(--success); }
.toast-success .toast-icon { background: var(--success-light); color: var(--success); }

.toast-error { border-left: 3px solid var(--danger); }
.toast-error .toast-icon { background: var(--danger-light); color: var(--danger); }

/* ---- LIVE NEW-ORDER ALERTS (admin) ----
   Deliberately not a .toast. A toast is a receipt for something the manager
   just did and can fade; this is work arriving that they have not seen yet, so
   it sits on screen until it is closed by hand and is sized to be read from
   across the room. It stacks below the toast strip so a flash message and an
   order alert never land on top of each other. */

.order-alert-stack {
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: min(360px, calc(100vw - 32px));
    max-height: calc(100vh - 32px);
    overflow-y: auto;
    pointer-events: none;
}

/* Once a flash toast is on screen the alerts start below it rather than
   underneath it, and slide back up when it dismisses itself. `:has` and not
   `:not(:empty)`: the wrapper is never truly empty — the markup leaves
   whitespace in it even with no toast to show. */
.toast-wrapper:has(.toast) ~ .app-shell .order-alert-stack {
    top: 76px;
}

.order-alert {
    position: relative;
    padding: 14px 44px 14px 16px;
    border-radius: var(--radius-lg);
    border: 1px solid var(--line);
    border-left: 4px solid var(--primary);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    pointer-events: auto;
    animation: orderAlertIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes orderAlertIn {
    from { transform: translateX(110%); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .order-alert { animation: none; }
}

/* A cancellation is the same card in a colour that says "something was taken
   off the sheet" rather than "something arrived". */
.order-alert.is-cancelled { border-left-color: var(--danger); }
.order-alert.is-cancelled .order-alert-title { color: var(--money-billed); }
.order-alert.is-cancelled .order-alert-link { color: var(--money-billed); }

/* A customer taking one day off a standing order. Amber rather than the
   cancellation red: nothing has been lost, one stop has moved off one round,
   and it must not arrive looking like an order walking out of the door. */
.order-alert.is-skipped { border-left-color: var(--warning); }
.order-alert.is-skipped .order-alert-title { color: var(--warning); }
.order-alert.is-skipped .order-alert-link { color: var(--warning); }

.order-alert-title {
    display: block;
    font-size: 14px;
    font-weight: 700;
    color: var(--primary-dark);
    margin-bottom: 4px;
}

.order-alert-line {
    font-size: 13px;
    color: var(--text);
    line-height: 1.45;
}

.order-alert-line + .order-alert-line { color: var(--text-secondary); }

.order-alert-link {
    display: inline-block;
    margin-top: 8px;
    font-size: 13px;
    font-weight: 600;
    color: var(--primary-ink);
    text-decoration: none;
}

.order-alert-link:hover { text-decoration: underline; }

.order-alert-close {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 30px;
    height: 30px;
    font-size: 12px;
}

/* ---- PILLS & TAGS ---- */

.tag, .pill, .status-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    padding: 4px 10px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    color: var(--text-secondary);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
}

.pill-warning, .status-pill-warning { background: var(--warning-light); color: var(--warning); }
.pill-success, .status-pill-success { background: var(--success-light); color: var(--success); }
.pill-danger, .status-pill-danger { background: var(--danger-light); color: var(--danger); }
.pill-info, .status-pill-info { background: var(--info-light); color: var(--info); }
/* Cancelled and other "no longer live" states: readable, but visibly inactive. */
.pill-muted, .status-pill-muted { background: var(--surface-muted); color: var(--text-secondary); opacity: 0.75; }

/* ---- DATA ROWS ---- */

.data-row, .subscription-row, .quick-row, .list-link-card, .inventory-row {
    display: grid;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    transition: border-color var(--transition);
}

.data-row:hover, .list-link-card:hover { border-color: var(--line-strong); }

.data-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}

.card-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
}

.card-head h3 {
    font-size: 19px;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin: 0 0 4px;
}

/* A single-line title sitting opposite its action button — the two line up on
   the same axis. Modal heads keep the default top alignment, because their
   title carries a second line and the close button belongs in the corner.
   Declared after `.card-head h3` on purpose: same specificity, so the later
   rule is the one that drops the title's bottom margin. */
.card-head-inline { align-items: center; }
.card-head-inline h3 { margin-bottom: 0; }

.surface-card h4,
.detail-card h4,
.list-link-card h4,
.data-row h4,
.subscription-row h4 {
    margin: 0 0 4px;
    font-size: 14px;
    font-weight: 600;
}

/* ---- MONTH PICKER ----
   Month and year in one control, sitting opposite a card title. A native
   <input type="month"> was the other option and was rejected: it paints its own
   calendar per browser, and the year matters as much as the month here — the
   grid below lets you step a year and land on August in two clicks.

   The whole thing filters rows already on the page, so it never navigates. */

.month-picker { position: relative; }

.month-picker-button {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 36px;
    padding: 7px 12px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
}

.month-picker-button:hover { border-color: var(--primary); color: var(--primary-dark); }
.month-picker-button svg:first-child { color: var(--primary-ink); flex-shrink: 0; }
.month-picker-value { white-space: nowrap; }

.month-picker-chevron {
    color: var(--text-secondary);
    transition: transform var(--transition);
}

.month-picker.is-open .month-picker-button {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-light);
}

.month-picker.is-open .month-picker-chevron { transform: rotate(180deg); }

.month-picker-pop {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 40;
    width: 260px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    animation: month-picker-in 0.14s ease-out;
}

@keyframes month-picker-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: none; }
}

.month-picker-year {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 10px;
}

.month-picker-year-label {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.01em;
}

.month-picker-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--transition);
}

.month-picker-step:hover:not(:disabled) {
    border-color: var(--primary);
    color: var(--primary-ink);
    background: var(--primary-light);
}

.month-picker-step:disabled { opacity: 0.35; cursor: default; }

.month-picker-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
}

.month-picker-month {
    padding: 8px 0;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--text);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
}

.month-picker-month:hover:not(:disabled) { background: var(--primary-light); color: var(--primary-dark); }

.month-picker-month.is-selected {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
}

/* A month with no deliveries is shown rather than removed: the gap in the year
   is itself information, and a grid that changes shape as you step years is
   harder to aim at. */
.month-picker-month:disabled {
    background: transparent;
    color: var(--line-strong);
    cursor: default;
}

/* ---- DATA TABLES ---- */

.table-scroll {
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.data-table {
    width: 100%;
    min-width: 480px;
    border-collapse: collapse;
    font-size: 13px;
}

.data-table thead th {
    padding: 0 12px 10px;
    text-align: left;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary);
    white-space: nowrap;
    border-bottom: 1px solid var(--line);
}

.data-table tbody td {
    padding: 12px;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}

.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover { background: var(--surface-alt); }

.data-table tbody td:first-child { white-space: nowrap; }
.data-table .strong { font-weight: 700; }
.data-table td.muted { color: var(--text-secondary); font-size: 12px; }

/* Column sizing.
   A table left to itself shares its width equally between columns, which pushes
   six short values far apart and reads as scattered. Instead every column
   shrinks to its content — `width: 1%` on a table that must still fill its box
   is the standard way to say "as narrow as you can" — and exactly one column
   per table carries `.col-grow` to absorb the slack. The result is a tight
   block of aligned figures with the free space in one deliberate place. */
.data-table th.col-grow { width: 100%; }

.data-table .numeric,
.data-table .col-tight,
.data-table .col-actions {
    width: 1%;
    white-space: nowrap;
}

/* Figures line up digit over digit, so a column of money can be read down. */
.data-table .numeric {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.data-table .col-actions { text-align: right; }

/* A name and the marks that qualify it, on one line: the address label with its
   Default and In-use badges. Never wraps — the badges say what the row is, and
   stacked under the name they read as three separate values. The cell is a
   `.col-tight` column, so not wrapping is also what tells the table how wide to
   make it. */
.data-table .cell-label {
    display: inline-flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 6px;
}

/* A second line inside a cell — the delivery slot under its date. Kept out of
   the row's own flow so the column stays as narrow as its widest date. */
.data-table .cell-subline {
    display: block;
    font-size: 12px;
    color: var(--text-secondary);
}


/* The deposit history is three narrow columns inside a modal, not a dashboard
   table: the shared 480px floor would make it scroll sideways in a box that has
   room for it. */
.deposit-history-table { min-width: 0; }

/* ---- CUSTOMER LEDGER ---- */

.mini-metric.is-danger {
    background: var(--danger-light);
    border-color: #fecaca;
}

/* The same ink as the figure beside the customer's name in the directory —
   the tile is where a manager learns what that colour means. */
.mini-metric.is-danger strong { color: var(--money-billed); }

.mini-metric-billed strong { color: var(--money-billed); }
.mini-metric-unbilled strong { color: var(--money-unbilled); }

/* ---- AUTH ----

   The sign-in, sign-up and landing screens. One card, centred, holding
   everything — the plant's name included. On a phone this is usually the only
   thing on the screen, and it is meant to fit there without scrolling: a form
   the customer has to scroll to finish is a form they abandon.

   Deliberately wordless. Every line of explanation that used to sit here
   ("enter your phone number and we will text you a code") described what the
   button underneath it already said. The placeholders went the same way — a
   grey "98765 43210" sitting in an empty box reads as a number already
   entered, and a customer was left to tell the difference by its shade. */

.auth-page {
    /* dvh, not vh: on a phone browser vh counts the space behind the address
       bar, which is exactly how a page that fits ends up with a scrollbar. */
    min-height: calc(100dvh - var(--shell-pad-y, 32px));
    display: grid;
    place-items: center;
}

/* A soft wash of the brand colour behind the card. Fixed rather than painted
   on .auth-page, which sits inside the shell's padding and would leave the
   tint stopping short of the edges. */
.auth-page::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -1;
    background:
        radial-gradient(900px 520px at 50% -12%, rgb(var(--primary-rgb) / 0.10), transparent 62%),
        var(--bg);
}

.auth-wrap {
    width: min(392px, 100%);
    display: grid;
    gap: 14px;
    padding: 20px 0;
}

.auth-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-md);
    padding: 26px 24px;
}

/* The plant's name, above the card's own title. Small and lettered wide: it
   says whose sign-in page this is without competing with the heading that says
   what the visitor is about to do. */
.auth-brand {
    text-align: center;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--primary-ink);
}

.auth-title {
    margin-top: 4px;
    font-size: 19px;
    font-weight: 700;
    letter-spacing: -0.02em;
    text-align: center;
    color: var(--text);
}

/* One line under the heading of the panel that asks a new customer for the
   rest of their details, saying how little is left. */
.auth-lead {
    margin-top: 6px;
    text-align: center;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-secondary);
}

.auth-form {
    display: grid;
    gap: 14px;
    margin-top: 18px;
}

.auth-form label {
    gap: 6px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
}

.auth-form input,
.auth-form textarea {
    padding: 11px 13px;
    /* 16px is the threshold below which iOS Safari zooms the page in the moment
       a field is tapped, leaving the customer looking at a magnified form they
       then have to pinch back out of. Nothing typed into here is smaller. */
    font-size: 16px;
}

.auth-form textarea { line-height: 1.5; }

/* The country code is a fact about the number, not something to be typed, so
   it sits inside the field rather than being one more thing to get wrong. The
   group carries the border and the focus ring; the input inside carries
   neither, so the two read as a single control. */
.auth-input-group {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    overflow: hidden;
    transition: border-color var(--transition), box-shadow var(--transition);
}

.auth-input-group:focus-within {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgb(var(--primary-rgb) / 0.1);
}

.auth-prefix {
    display: flex;
    align-items: center;
    padding: 0 12px;
    border-right: 1px solid var(--line);
    background: var(--surface-alt);
    color: var(--text-secondary);
    font-size: 15px;
    font-weight: 600;
}

.auth-input-group input {
    flex: 1;
    min-width: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
}

.auth-input-group input:focus {
    border: 0;
    box-shadow: none;
}

/* ---- One box per digit ----
   A six-character code read off an SMS banner is checked digit by digit, and a
   row of boxes is how a person keeps their place — a single field leaves them
   counting characters in a string. It also makes one mistyped digit a single
   tap to fix. Sized off the row rather than in fixed pixels, so the same markup
   fits a narrow phone and a desktop card. */
.otp-boxes {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 8px;
}

.otp-box {
    width: 100%;
    height: 52px;
    padding: 0;
    text-align: center;
    font-size: 20px;
    font-weight: 600;
    color: var(--text);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-md);
    background: var(--surface);
}

.otp-box:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgb(var(--primary-rgb) / 0.12);
}

.auth-submit {
    width: 100%;
    min-height: 46px;
    font-size: 14px;
}

/* ---- Steps ----
   Two bars rather than "Step 1 of 2": the shape of the row says how far along
   the form is and how much is left, which is the whole of what the words were
   for. Hidden until the script that drives them is running, because without it
   both steps are on screen at once and a progress row would be a lie. */
.auth-steps {
    display: grid;
    grid-auto-flow: column;
    gap: 6px;
    margin-bottom: 2px;
}

.auth-step-dot {
    height: 3px;
    border-radius: var(--radius-full);
    background: var(--line);
    transition: background var(--transition);
}

.auth-step-dot.is-done { background: var(--primary); }

/* Back and Create sit on one line, and the way forward is the wide one. */
.auth-step-actions {
    display: flex;
    gap: 10px;
}

.auth-step-actions .btn {
    min-height: 46px;
    flex: 1;
}

.auth-step-actions .btn-secondary {
    flex: 0 0 auto;
    padding-inline: 20px;
}

/* Each step stacks like a form of its own — of the OTP form and of the sign-up
   form alike. The wrapper is what is shown and hidden, so it has to carry the
   spacing rather than inherit the parent grid's, which would collapse it to a
   single row. */
[data-otp-step],
[data-step] {
    display: grid;
    gap: 14px;
}

/* Where the code went, and the way back to the number field. A statement of
   fact rather than an instruction, which is why it survived the cull. */
.auth-sent {
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
}

.auth-sent strong {
    color: var(--text);
    font-weight: 600;
    white-space: nowrap;
}

/* Holds its height whether or not there is anything in it, so the card does
   not jump when the countdown gives way to the resend link. */
.auth-resend {
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
    min-height: 20px;
}

.auth-error {
    margin: 0;
    padding: 10px 12px;
    border: 1px solid var(--money-billed-line);
    border-radius: var(--radius-md);
    background: var(--danger-light);
    color: var(--money-billed);
    font-size: 13px;
    font-weight: 500;
}

.auth-verified {
    margin-top: 16px;
    padding: 10px 12px;
    border: 1px solid var(--primary-light);
    border-radius: var(--radius-md);
    background: var(--success-light);
    color: var(--success);
    font-size: 13px;
    font-weight: 600;
    text-align: center;
}

/* The other way into the same account — an OTP instead of a password, or the
   reverse. Separated by a rule rather than by white space alone: it is a
   different route, not the next step of this one. */
.auth-switch {
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--line);
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
}

/* A different destination altogether — signing up rather than in. */
.auth-alt {
    margin-top: 14px;
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
}

/* The same pointer, on a page where it belongs under the card rather than
   inside it — unboxed, the way the small print of a form is. */
.auth-foot {
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
}

.auth-foot a { color: var(--primary-ink); font-weight: 600; }

.auth-switch-link,
.link-button {
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    font-size: inherit;
    color: var(--primary-ink);
    font-weight: 600;
}

.auth-switch-link:hover,
.link-button:hover { color: var(--primary-dark); }

.link-button:disabled { color: var(--text-secondary); cursor: not-allowed; }

/* Two short fields that belong together — the map coordinates on the sign-up
   form — kept on one line so the form is not a longer column than it is. */
.auth-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

/* ---- Attach precise location ----
   Shared by the sign-up form and the quick-order page, which is why it lives
   here rather than in either page's own stylesheet. See
   partials/location-picker.php and bindLocationPicker() in app.js. */
.location-picker {
    display: grid;
    gap: 6px;
}

.location-button {
    width: 100%;
    padding: 10px 12px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-md);
    background: var(--surface-alt);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition), color var(--transition);
}

.location-button:hover { border-color: var(--primary); }

/* Solid once there is something behind it: a dashed box is an invitation, and
   the invitation has been taken. */
.location-button.is-on {
    border-style: solid;
    border-color: var(--primary);
    background: var(--success-light);
    color: var(--success);
}

.location-state {
    margin: 0;
    font-size: 12px;
    line-height: 1.6;
    color: var(--text-secondary);
}

/* Check on map, and Remove. A link and a button side by side, which is what
   they are — one goes somewhere, the other undoes something — and they read the
   same because to the customer they are two equal things they may want to do
   about the pin they just dropped. */
.location-link {
    border: 0;
    padding: 0;
    background: none;
    font: inherit;
    font-weight: 600;
    color: var(--primary-ink);
    text-decoration: underline;
    cursor: pointer;
}

/* A fix good only to the nearest neighbourhood — a phone indoors, usually. The
   delivery sheet prefers a pin over the written address, so a bad one is worse
   than none and the customer is the only person who can tell. */
.location-warn {
    color: var(--warning);
    font-weight: 500;
}

.auth-optional {
    font-weight: 500;
    color: var(--text-secondary);
    opacity: 0.85;
}

/* The password link and who it is for, on one line. `.auth-switch` centres its
   text, so the qualifier follows the link as part of the same sentence rather
   than turning into a second row of small print. */
.auth-switch .auth-optional { margin-left: 4px; }

.auth-form input[type="file"] {
    padding: 8px 12px;
    font-size: 14px;
    color: var(--text-secondary);
}

/* The browser's own "Choose file" button, brought into the same family as the
   rest of the form. Everything else about a file input is drawn by the
   operating system and cannot be reached. */
.auth-form input[type="file"]::file-selector-button {
    margin-right: 10px;
    padding: 6px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-muted);
    color: var(--text);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background var(--transition);
}

.auth-form input[type="file"]::file-selector-button:hover {
    background: var(--line);
}

@media (max-width: 480px) {
    .auth-wrap { padding: 12px 0; gap: 12px; }
    .auth-card { padding: 22px 16px; border-radius: var(--radius-lg); }
    .auth-title { font-size: 18px; }
    .auth-form { gap: 13px; margin-top: 16px; }
    .auth-form input,
    .auth-form textarea { padding: 10px 12px; }
    .otp-boxes { gap: 6px; }
    .otp-box { height: 48px; font-size: 18px; }
    .auth-submit,
    .auth-step-actions .btn { min-height: 44px; }
    .auth-pair { gap: 10px; }
}

.centered, .centered-panel { text-align: center; }


/* ---- MISC UTILITY ---- */

.meta-line, .text-link { color: var(--primary-ink); font-weight: 600; }

.section-divider {
    height: 1px;
    margin: 4px 0;
    background: var(--line);
}

.scroll-list {
    max-height: 460px;
    overflow: auto;
}

.list-link-card.is-active {
    border-color: var(--primary);
    background: var(--primary-light);
}

.customer-admin-layout { align-items: start; }
.customer-card-metrics {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* Directory card: name and amount on one baseline, phone and containers on the
   next. Kept to the two lines the card already had — the figures sit in space
   that was empty, so a long list gains information without gaining height.
   Both are drawn only when non-zero, so a settled customer stays quiet and the
   ones carrying something are the only colour on the list. */
/* The customer's four figures: two by two on a phone, one row from the tablet
   breakpoint up.
   A phone has no width for four tiles side by side — they would shrink until
   the amounts wrapped — and stacking all four costs most of the screen before
   the bills below are reached. Two by two fits the figures at full size and
   keeps them above the fold.
   `auto-flow: column` on the wide layout rather than a fixed four-column
   template, so the row stays a single line whatever it is handed. */
.metric-row {
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(2, minmax(0, 1fr));
}


@media (min-width: 768px) {
    .metric-row {
        gap: 16px;
        grid-auto-flow: column;
        grid-auto-columns: minmax(0, 1fr);
        grid-template-columns: none;
    }
}

/* The per-product breakdown that goes with a count. Small and secondary: the
   number is the figure, this is what makes it up. */
.metric-note {
    display: block;
    margin-top: 6px;
    font-size: 11px;
    font-style: normal;
    font-weight: 600;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* One tile in a row carrying a breakdown used to make every tile in that row
   taller, because a grid row is as tall as its tallest cell — three tiles grew
   a band of empty space to keep company with a fourth tile's second line.

   So the breakdown moves up beside the figure instead of under it. The tile
   stays two rows tall like its neighbours, the note takes the space the short
   number leaves on that line, and it truncates rather than wraps if there is
   not enough of it. */
.mini-metric-aside {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "label label"
        "value note";
    align-items: baseline;
    column-gap: 6px;
}

.mini-metric-aside > span { grid-area: label; }
.mini-metric-aside > strong { grid-area: value; }

/* Drawn as a chip, not as loose text. Beside a 22px numeral a bare grey line
   read as something that had fallen out of place; the chip gives it an edge of
   its own, and it is the same chip this breakdown already wears on the
   customer's row in the directory, so the two places agree.
   A half-width tile on a phone is still too narrow for the longest breakdown,
   so it truncates there and the title carries the rest. */
.mini-metric-aside > .metric-note {
    grid-area: note;
    justify-self: start;
    max-width: 100%;
    margin-top: 0;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    font-size: 10.5px;
    letter-spacing: -0.01em;
}

/* Two-class selector on purpose: from the tablet breakpoint up, `.list-link-card`
   becomes a two-column grid so a card can put pills beside its text. This card
   carries its own two rows instead, and has to stay a single column at every
   width — the extra class outranks that rule without reopening the media query. */
.list-link-card.customer-link-card {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
}

/* The icon actions in the customers panel head. Kept to a row of their own
   so the head's flex layout does not put a gap between them the size of the one
   separating them from the heading. */
.panel-head-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

.customer-card-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    min-width: 0;
}

.customer-card-line h4,
.customer-card-line p {
    margin: 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Two figures, not one total: what has been billed and is still unpaid, and
   what has been delivered since and is not on a bill yet.
   They are told apart by more than a hue, because a hue alone did not survive
   a scan down a long list. Both sit in a chip so the line keeps one shape, and
   the chip is what carries the difference: red-tinted for money already asked
   for, plain neutral for money still accruing. The red is the only warm thing
   on the card, so the customers who owe on a raised bill can be picked out
   without reading a single figure. */
.customer-card-due {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-shrink: 0;
    font-size: 13px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.money-billed,
.money-unbilled {
    padding: 1px 7px;
    border-radius: var(--radius-full);
    border: 1px solid transparent;
}

.money-billed {
    background: var(--money-billed-bg);
    border-color: var(--money-billed-line);
    color: var(--money-billed);
}

.money-unbilled {
    background: var(--surface-muted);
    border-color: var(--line);
    color: var(--money-unbilled);
    font-weight: 600;
}

/* The selected row is mint, and a grey chip on it turns muddy. Same treatment
   the held-containers chip beside it already gets. */
.list-link-card.is-active .money-unbilled {
    background: rgb(255 255 255 / 0.7);
    border-color: rgb(255 255 255 / 0.9);
}

.customer-card-held {
    flex-shrink: 0;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--surface-muted);
    border-radius: var(--radius-full);
    padding: 1px 8px;
}

.list-link-card.is-active .customer-card-held {
    background: rgb(255 255 255 / 0.7);
}

/* A customer who cannot sign in. Inline with the phone number rather than a
   chip on the right, where the held-containers count already sits — two chips
   on that line pushed the phone number out of a narrow list pane. */
.customer-card-blocked {
    font-weight: 600;
    color: var(--danger);
}

.customer-no-phone {
    /* A missing number is a to-do, not a fault: amber-ish and quiet, not the
       danger red a blocked account gets. */
    color: var(--warning, var(--text-secondary));
    font-weight: 500;
}

.list-link-card.is-active .customer-card-blocked {
    /* The active card is a filled panel, so the danger red on it is too dark
       to read. Inherit instead: the word alone is enough there. */
    color: inherit;
    opacity: 0.85;
}

/* The location photo, in its own modal. Contained rather than cropped: it is a
   picture of a doorway taken on somebody's phone, in whatever aspect ratio that
   phone was held at, and cropping it to a tidy box is exactly what would cut
   the gate or the house number out of it. Capped by viewport height so a tall
   portrait shot cannot push the buttons under the fold. */
.location-photo {
    display: block;
    width: 100%;
    max-height: min(60vh, 520px);
    object-fit: contain;
    border-radius: var(--radius-md);
    background: var(--surface-muted);
}

/* Table row controls. The cell keeps its buttons on one line so a three-action
   row cannot wrap into two. */
.row-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    white-space: nowrap;
}

.row-actions form { display: contents; }

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

/* Stands in for the delete button when the action is blocked — same footprint,
   so the column does not jump between rows. */
.row-action-locked {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--surface-muted);
    border-radius: var(--radius-full);
    padding: 3px 9px;
    cursor: help;
}

/* A payment carrying a bill number is showing the reference the customer holds;
   a generated one is bookkeeping, and recedes. */
.ref-bill {
    font-weight: 600;
    color: var(--text);
}

.ref-general {
    font-size: 12px;
    color: var(--text-secondary);
}
.compact-copy { margin-top: 6px; }
.detail-card { min-width: 0; }
.surface-card, .panel, .hero-card { min-width: 0; }
.empty-state-card h3 { margin: 0; }

/* Bottom-aligned so the save button sits on the same baseline as the number
   inputs, which carry a label above them. */
.daily-entry-form {
    display: flex;
    align-items: flex-end;
    gap: 8px;
    flex-wrap: nowrap;
}

/* Same box metrics as the number inputs beside it (14/22.4 text, 8px padding,
   1px border), so the two line up top and bottom rather than only at the base. */
.daily-entry-form .btn-icon {
    flex: none;
    padding: 8px 10px;
    border: 1px solid transparent;
    font-size: 14px;
    line-height: 22.4px;
}

.daily-entry-field {
    display: grid;
    gap: 4px;
    font-size: 11px;
    font-weight: 700;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.daily-entry-form input {
    width: 84px;
    min-width: 84px;
    padding: 8px 10px;
}

/* ---- Quantity stepper ----
   The field keeps its size; the arrows move inside it and take its full
   height, top half up and bottom half down, instead of the browser's 8px
   hover-only spinner. */
.qty-stepper { position: relative; display: block; }

.qty-stepper input { -moz-appearance: textfield; appearance: textfield; }
.qty-stepper input::-webkit-inner-spin-button,
.qty-stepper input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

.daily-entry-form .qty-stepper input { padding-right: 32px; }

.qty-steps {
    position: absolute;
    top: 1px;
    right: 1px;
    bottom: 1px;
    width: 26px;
    display: flex;
    flex-direction: column;
    border-left: 1px solid var(--line);
    border-radius: 0 calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0;
    overflow: hidden;
}

.qty-steps button {
    flex: 1 1 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: var(--surface-muted);
    color: var(--text-secondary);
    cursor: pointer;
    touch-action: manipulation;
}

.qty-steps button + button { border-top: 1px solid var(--line); }
.qty-steps button:hover { background: var(--primary-light); color: var(--primary-ink); }
.qty-steps button:active { background: var(--primary); color: var(--on-primary); }

/* Date with a day-at-a-time step on either side. The arrows are the fast path;
   the picker stays for jumping somewhere further off. */
.date-stepper {
    display: flex;
    align-items: center;
    gap: 6px;
}

.date-stepper input[type="date"] {
    flex: 1;
    min-width: 0;
}

/* The day's name, painted over the native date box.

   The input keeps everything that makes it worth having — the calendar, the
   phone's date wheel, the keyboard, the form value — and on the three days that
   have a name its digits are simply made invisible and the word laid on top.
   Nothing is replaced, so there is no fallback to get wrong: if the overlay
   never applies, the date reads exactly as it always did.

   The transparent text is scoped to `.has-relative` so it can only ever hide a
   date the word is standing in for. `-webkit-text-fill-color` is the one that
   actually does it in WebKit and Blink, where `color` alone leaves the digits
   visible; the calendar indicator is a separate element and keeps its own
   colour, so the little icon on the right stays exactly where it was. */
.date-field {
    position: relative;
    display: flex;
    flex: 1;
    min-width: 0;
}

.date-field.has-relative input[type="date"] {
    color: transparent;
    -webkit-text-fill-color: transparent;
}

.date-field.has-relative input[type="date"]::-webkit-datetime-edit {
    color: transparent;
    -webkit-text-fill-color: transparent;
}

.date-relative {
    position: absolute;
    left: 13px;
    top: 50%;
    transform: translateY(-50%);
    /* Every tap goes to the input underneath, including on the word itself. */
    pointer-events: none;
    font-size: 14px;
    font-weight: 600;
    /* The field label above is small-caps; this is a value, and inheriting that
       treatment through the wrapper turned "Today" into "TODAY" beside a date
       that would have been set in ordinary figures. */
    text-transform: none;
    letter-spacing: normal;
    color: var(--text);
    white-space: nowrap;
}

.date-field:not(.has-relative) .date-relative { display: none; }

.step-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 34px;
    height: 34px;
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text-secondary);
    text-decoration: none;
    cursor: pointer;
    transition: all var(--transition);
}

.step-btn:hover {
    border-color: var(--primary);
    color: var(--primary-ink);
    background: var(--primary-light);
}

.step-btn:active { transform: scale(0.94); }

.step-btn:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}

/* One filter bar shared by the delivery sheet, orders and subscriptions. */
.sheet-toolbar {
    display: flex;
    align-items: flex-end;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px 0 4px;
}

.sheet-toolbar input {
    padding: 8px 10px;
}

/* Only the left padding is narrowed here. The shorthand used to reset the
   right padding too, which let the chevron sit on top of the label. */
.sheet-toolbar .select-button { min-height: 38px; padding-top: 8px; padding-bottom: 8px; }
.sheet-toolbar .select-wrap { min-width: 150px; }

/* Heading, primary action and filter as one block at the top of a list card —
   the same trio every other tab keeps inside its card. The rule underneath it
   is the .section-divider those tabs draw, bled out to the card's edges. */
.list-panel-head {
    margin: 0 -20px;
    padding: 0 20px 12px;
    border-bottom: 1px solid var(--line);
}

.list-panel-head .sheet-toolbar { padding-bottom: 0; }

/* The search field earns more room than the numeric fields around it. */
.sheet-toolbar-search { flex: 1 1 220px; }
.sheet-toolbar-search input { width: 100%; min-width: 160px; }

.search-input-wrap {
    position: relative;
    display: block;
    width: 100%;
}

/* Room for the clear button so long text never slides under it. */
/* Block, so the wrap is exactly as tall as the box — an inline input leaves a
   few pixels of line-height under it, and the full-height button would sit in
   a box taller than the field it belongs to. */
.search-input-wrap input[type="search"] { display: block; padding-right: 34px; }

/* Chrome's own ✕ only appears on hover; ours replaces it, so hide theirs
   rather than showing two. Scoped to .has-clear, which only JS adds — without
   JS the native button stays as the fallback. */
.has-clear input[type="search"]::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
    display: none;
}

/* Built like the quantity stepper's arrows: the field keeps its size and the
   button lives inside it, taking the full height between the borders, so every
   control tucked into a field reads as the same kind of thing. A 26px-tall
   circle floating in a 40px box was a smaller target and looked like it had
   been dropped in. */
.search-clear {
    position: absolute;
    top: 1px;
    right: 1px;
    bottom: 1px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    /* Balances the border-left. Centring happens inside the content box, which
       that border has already pushed a pixel to the right — so without this the
       mark sits a pixel off-centre against the cell you actually see. */
    padding: 0 1px 0 0;
    border: 0;
    border-left: 1px solid var(--line);
    border-radius: 0 calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0;
    background: var(--surface-muted);
    color: var(--text-secondary);
    line-height: 1;
    cursor: pointer;
    touch-action: manipulation;
    transition: background var(--transition), color var(--transition);
}

.search-clear:hover { background: var(--primary-light); color: var(--primary-ink); }
.search-clear:active { background: var(--primary); color: var(--on-primary); }
.search-clear:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

.daily-entry-check input {
    width: auto;
    min-width: 0;
    justify-self: start;
    height: 20px;
}

/* ---- DELIVERY SHEET ROW ----
   Name first with its status beside it, then what to hand over. The address
   moves into a tap-away modal: a delivery boy needs it once, at the door, not
   on every line of a fifty-stop list. */
.sheet-row-main {
    display: grid;
    gap: 4px;
    min-width: 0;
}

.sheet-row-head {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.sheet-row-head h4 { margin: 0; font-size: 15px; }

/* A customer's name that opens their profile. Reads as the name it is until
   the pointer reaches it, so a sheet of thirty links does not look like a page
   of hyperlinks. */
.customer-link { color: inherit; text-decoration: none; }
.customer-link:hover,
.customer-link:focus-visible {
    color: var(--primary-ink);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* A row's own number, sitting beside the name it belongs to. Tabular figures
   so a column of them lines up, and muted so it never competes with the name
   or with the pills that say what the row is doing. */
.row-ref {
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* On the delivery sheet the reference gets a line of its own above the row's
   name, so the head keeps the name and its pills together on one line. */
.sheet-row-ref { margin: 0 0 2px; }

.sheet-row-product { font-size: 14px; color: var(--text); }
.sheet-row-product strong { font-weight: 700; }

/* When an order was placed, under what it is for. Smaller than the product
   line and never bold: it is provenance, read once when somebody queries the
   order, not something the eye should stop on while scanning the list. */
.sheet-row-placed {
    margin: 2px 0 0;
    font-size: 12px;
}

/* What comes back from a door — containers, money — on a line under what goes
   to it. Its own row rather than trailing the product, so a long product name
   cannot push the chips onto a second line on one row and not the next; a
   column of rows then starts its chips at the same place every time.

   Shared by the delivery sheet and the pickups card so both read alike. */
/* ---- Chip with a tip ----
   An (i) inside the chip, for the one fact behind the count — when those
   containers went out. The bubble shows on hover, on keyboard focus, and on a
   tap (app.js toggles .is-open, since a tapped button is not reliably focused
   on iOS). */
.pill-has-tip { display: inline-flex; align-items: center; gap: 5px; padding-right: 5px; }

.pill-tip {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    margin: -2px 0;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: inherit;
    cursor: help;
}

.pill-tip:hover, .pill-tip.is-open { background: rgb(0 0 0 / 0.07); }
.pill-tip:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }

.pill-tip::after {
    content: attr(data-tip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    z-index: 30;
    padding: 6px 10px;
    border-radius: 6px;
    background: #1f2937;
    color: #fff;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.3;
    letter-spacing: 0;
    text-transform: none;
    white-space: nowrap;
    box-shadow: 0 6px 16px -6px rgb(16 24 40 / 0.35);
    transform: translate(-50%, 4px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s;
}

.pill-tip::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 3px);
    left: 50%;
    z-index: 30;
    border: 5px solid transparent;
    border-bottom: 0;
    border-top-color: #1f2937;
    transform: translate(-50%, 4px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s;
}

.pill-tip:hover::after, .pill-tip:hover::before,
.pill-tip:focus-visible::after, .pill-tip:focus-visible::before,
.pill-tip.is-open::after, .pill-tip.is-open::before {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, 0);
}

.sheet-row-chips {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin: 4px 0 0;
}

.sheet-row-collect {
    font-size: 13px;
    font-weight: 700;
    color: var(--warning);
}

/* Nothing to collect reads green, the same as a settled order on the Orders
   screen — the delivery boy is scanning for the rows that owe money. */

/* Money reads as plain emphatic text rather than a chip — same treatment the
   delivery sheet gives "One-time — collect Rs 160.00". Settled amounts go green
   so a paid order is distinguishable at a glance from one still owing. */
.sheet-row-money {
    font-size: 13px;
    font-weight: 700;
    color: var(--warning);
}

.sheet-row-money.is-settled { color: var(--success); }

.info-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text-secondary);
    cursor: pointer;
    transition: all var(--transition);
}

/* An amount that the bill decided, not the manager. It has to look unavailable
   without looking broken — a plain readonly input is indistinguishable from an
   editable one and reads as a field that has stopped responding. */
input.is-locked {
    background: var(--surface-muted);
    color: var(--text);
    cursor: default;
}

input.is-locked:focus { border-color: var(--line); box-shadow: none; }

.info-btn:hover { border-color: var(--primary); color: var(--primary-ink); }
.info-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* The "not delivered" twin of the info button — same circle, danger accent. */
.info-btn-danger:hover { border-color: var(--danger); color: var(--danger); }
.info-btn-danger:focus-visible { outline-color: var(--danger); }

/* Beside "Print sheet" it is a toolbar button, not an inline hint: square, and
   exactly that button's height and corners, so the pair reads as one row. The
   id outranks the phone rule that shrinks and strips every info button. */
#sheet-actions .info-btn {
    width: 36px;
    height: 36px;
    border-color: var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
}

.address-full {
    font-size: 15px;
    line-height: 1.5;
    color: var(--text);
}

/* The footer of every modal, without exception: the action on the right, the
   way out beside it on the left.

   Both halves are spelled out rather than leaving the X in the corner as the
   only way to back out. On a phone a modal rises as a sheet from the bottom
   edge, which puts that X at the far end of the screen from the thumb holding
   it — so the dismiss has to be down here with the confirm, at the same size,
   or it is not really offered. Two equal columns because neither button is
   allowed to look like the safe one by being bigger.

   Buttons sit clear of the text above them rather than butting against it. */
.modal-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-top: 20px;
}

.modal-actions .btn { width: 100%; }

/* A footer with only one button in it — nothing to cancel, just an
   acknowledgement — lets that button have the whole row. */
.modal-actions > .btn:only-child { grid-column: 1 / -1; }

/* Save controls and the "not delivered" action stack on narrow screens
   instead of squeezing the number inputs. */
.daily-entry-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* Delivery sheet totals: two boxes per row on phones, all four in a line
   once there is room. */
.sheet-summary-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
}

/* The totals describe the day; the toolbar below changes what is listed. The
   rule between them runs the full width of the card — pulled out over the
   card's own 20px padding — so it reads as a real break rather than an
   underline belonging to the tiles. */
.section-divider {
    height: 1px;
    margin: 18px -20px 0;
    background: var(--line);
}

/* In a dialog the rule separates two lists rather than heading a toolbar, so
   what follows it needs room to read as its own section instead of sitting on
   the line. Scoped to modals: under the sheet totals the toolbar is meant to
   sit tight against it. */
.modal-card .section-divider { margin-bottom: 18px; }

.sheet-summary-grid .mini-metric { padding: 12px 14px; }
.sheet-summary-grid strong { margin-top: 4px; font-size: 20px; }

/* Label and number together on the left, the breakdown button centred against
   the height of the whole tile rather than pinned to the label's line. */
.metric-tile {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.metric-tile-main { min-width: 0; }

/* One product per line, its number right-aligned so a column of figures can be
   read down rather than hunted for. */
.metric-breakdown {
    display: grid;
}

/* ---- The delivery sheet's two lists ----
   A segmented control, not a second row of page tabs: these are two views of
   one card, and the track around them says so. The count rides on the tab so
   the other list can be read without opening it — after a search it is what
   says where the name went. */
.pane-tabs {
    display: flex;
    gap: 4px;
    margin: 12px 0 4px;
    padding: 4px;
    border-radius: var(--radius-md);
    background: var(--surface-muted);
}

.pane-tab {
    display: inline-flex;
    text-decoration: none;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex: 1 1 0;
    min-height: 36px;
    padding: 7px 12px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-weight: 600;
    font-size: 13px;
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
}

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

/* The open tab is lifted out of the track rather than filled with the brand
   colour: it is a view switch, and a solid teal block here would outrank the
   Save buttons on the rows below it. */
.pane-tab.is-active {
    background: var(--surface);
    color: var(--primary-ink);
    box-shadow: var(--shadow-xs);
}

.pane-tab:focus-visible {
    outline: 2px solid rgb(var(--primary-rgb) / 0.55);
    outline-offset: 1px;
}

.pane-tab-count {
    min-width: 20px;
    padding: 1px 6px;
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text-secondary);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.pane-tab.is-active .pane-tab-count {
    background: var(--primary-light);
    color: var(--primary-dark);
}

/* An empty list is still worth showing — "nothing to collect" is an answer —
   but its count should not read as work waiting. */
.pane-tab-count.is-zero { background: transparent; color: var(--text-secondary); opacity: 0.7; }
.pane-tab.is-active .pane-tab-count.is-zero { background: var(--surface-muted); }

/* ---- Month-end bills: the Generate picker ----
   A ticked list of the month's customers inside the modal. Rows rather than
   cards, a hairline between them, the amount right-aligned so the column can
   be read down. The list scrolls on its own, under a header that stays put, so
   "All 37 selected" and the search are in reach at the fiftieth name. */
.bill-pick {
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.bill-pick-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 12px;
    background: var(--surface-alt);
    border-bottom: 1px solid var(--line);
}

.bill-pick-all { flex: 1 1 auto; min-width: 0; }

.bill-pick-search {
    flex: 0 1 200px;
    min-height: 34px;
    padding: 6px 10px;
    font-size: 13px;
}

.bill-pick-list {
    max-height: 320px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The bills dialog fits the window rather than running past it: the card is
   capped at the overlay's height and the tick list is the one part that gives
   way, so the overlay never grows a scrollbar of its own on a laptop screen.
   Nothing here clips — the month dropdown still opens over the list. */
.bills-modal {
    display: flex;
    flex-direction: column;
    max-height: calc(100dvh - 32px);
}

.bills-modal > .card-head,
.bills-modal > .pane-tabs { flex: none; }

.bills-modal [data-bills-panel]:has(> form):not([hidden]),
.bills-modal [data-bills-panel] > form {
    display: flex;
    flex-direction: column;
    flex: 0 1 auto;
    min-height: 0;
}

.bills-modal [data-bills-panel] > form { gap: 16px; }
.bills-modal [data-bills-panel] > form > * { flex: none; }

.bills-modal [data-bills-panel] > form > .bill-pick {
    display: flex;
    flex-direction: column;
    flex: 0 1 auto;
    min-height: 168px;
}

.bills-modal .bill-pick-head { flex: none; }
.bills-modal .bill-pick-list { flex: 0 1 auto; min-height: 0; }

.bill-pick-row {
    display: grid;
    grid-template-columns: 18px minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border-top: 1px solid var(--line);
    cursor: pointer;
    transition: background var(--transition);
}

.bill-pick-row:first-child { border-top: 0; }
.bill-pick-row:hover { background: var(--surface-alt); }

.bill-pick-row input {
    width: 18px;
    height: 18px;
    margin: 0;
    accent-color: var(--primary);
}

/* An unticked customer stays in the list, set back a step, so it is plain at
   a glance who is being left out of the run. */
.bill-pick-row:has(input:not(:checked)) .bill-pick-who,
.bill-pick-row:has(input:not(:checked)) .bill-pick-amount { opacity: 0.55; }

.bill-pick-who { display: grid; gap: 2px; min-width: 0; }

.bill-pick-name {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-weight: 600;
    font-size: 14px;
    color: var(--text);
}

.bill-pick-meta {
    font-size: 12px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bill-pick-amount {
    font-weight: 600;
    font-size: 14px;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

.bill-pick-empty {
    margin: 0;
    padding: 20px 12px;
    text-align: center;
    font-size: 13px;
    color: var(--text-secondary);
}

.bill-pick-skeleton {
    height: 52px;
    border-top: 1px solid var(--line);
    background: linear-gradient(90deg, var(--surface-muted) 0 40%, var(--surface-alt) 50%, var(--surface-muted) 60% 100%) 0 0 / 300% 100%;
    animation: bill-pick-shimmer 1.2s linear infinite;
}

.bill-pick-skeleton:first-child { border-top: 0; }

@keyframes bill-pick-shimmer { to { background-position: -150% 0; } }

@media (prefers-reduced-motion: reduce) {
    .bill-pick-skeleton { animation: none; }
}

.bill-pick-legend {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: -4px 0 0;
    font-size: 12px;
}

.bill-pick-legend .pill { flex: none; }

.bill-run-notify { align-items: flex-start; font-weight: 500; }
.bill-run-notify input { margin-top: 1px; }

.bill-run-notify em {
    display: block;
    margin-top: 2px;
    font-style: normal;
    font-weight: 400;
    font-size: 12px;
    color: var(--text-secondary);
}

/* A short line under a field that changes what the button will do. */
.bill-run-note {
    margin: -4px 0 0;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    background: var(--surface-alt);
    color: var(--text-secondary);
    font-size: 13px;
}

.bill-run-note[hidden] { display: none; }

.bill-run-note-warn {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
    margin: 0;
    background: var(--warning-light);
    color: var(--warning);
}

@media (max-width: 560px) {
    .bill-pick-head { flex-wrap: wrap; }
    .bill-pick-search { flex: 1 1 100%; }
}

/* The "3 in To collect" button under an empty list: a step sideways, not a
   full-width call to action. The lists are grids, which would stretch it. */
.pane-jump { justify-self: start; align-self: start; margin-top: 4px; }

/* Sorting and the status filter only narrow the round, so the collections tab
   does without them rather than showing controls that do nothing. */
[data-sheet-pane="collect"] [data-pane-only="deliveries"] { display: none; }

/* Header buttons that sit together at the right of a card's title. */
.card-head-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: none;
}

/* Routes beside Print sheet. In words on a desktop; on a phone, where three
   buttons and the title do not fit one line, an icon. The count of customers
   still to place rides along as a badge either way. */
.sheet-round-btn { display: inline-flex; align-items: center; gap: 6px; }
.sheet-round-icon { display: none; }

.sheet-round-badge {
    min-width: 18px;
    padding: 1px 6px;
    border-radius: var(--radius-full);
    background: var(--info-light);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.4;
    text-align: center;
    color: var(--info);
}

@media (max-width: 767px) {
    .sheet-round-icon { display: block; }
    .sheet-round-label { display: none; }
}

/* Every customer about to be written off, each with a box to leave them out.
   Scrolls on its own so fifty names do not push the reason and the buttons off
   the bottom of a phone. */
.closeout-list {
    display: grid;
    max-height: min(45vh, 360px);
    overflow-y: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
}

.closeout-item {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 10px;
    row-gap: 2px;
    align-items: center;
    padding: 9px 12px;
    cursor: pointer;
}

.closeout-item + .closeout-item { border-top: 1px solid var(--line); }
.closeout-item input { grid-row: span 2; width: 18px; height: 18px; min-height: 0; margin: 0; padding: 0; accent-color: var(--primary); }
.closeout-item .closeout-name { font-weight: 600; font-size: 14px; }
.closeout-item .muted { font-size: 13px; }

/* Separators sit *between* rows, so the last product and the total rule do not
   stack into a double line. */
.metric-breakdown-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 9px 2px;
    font-size: 14px;
}

.metric-breakdown-row + .metric-breakdown-row { border-top: 1px solid var(--line); }
.metric-breakdown-row strong { font-weight: 700; font-variant-numeric: tabular-nums; }

.metric-breakdown-row + .metric-breakdown-row.is-total {
    margin-top: 4px;
    padding-top: 12px;
    border-top-color: var(--line-strong);
}

.metric-breakdown-row.is-total span { font-weight: 600; }

.group-heading {
    margin: 14px 0 2px;
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-secondary);
}

.group-heading:first-child { margin-top: 0; }

/* A list waiting on a live-search response: dimmed, but still readable and
   still the right height, so the page does not jump under the cursor. */
.is-loading {
    opacity: 0.45;
    transition: opacity 0.12s ease;
    pointer-events: none;
}

/* ---- CONTAINER STOCK ----
   Each card answers one question: where are our containers? The bar shows the
   split at a glance, the legend gives the exact numbers. */
.stock-list {
    display: grid;
    gap: 14px;
}

.stock-card {
    display: grid;
    gap: 12px;
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

.stock-card-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.stock-card-head h4 { margin: 0; }
.stock-card-head p { margin: 2px 0 0; }

.stock-bar {
    display: flex;
    width: 100%;
    height: 10px;
    border-radius: var(--radius-full);
    overflow: hidden;
    background: var(--surface-muted);
}

.stock-bar-seg { display: block; height: 100%; }

.stock-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
    font-size: 13px;
    color: var(--text-secondary);
}

.stock-stat { display: inline-flex; align-items: center; gap: 7px; }
.stock-stat strong { color: var(--text); font-size: 15px; }

.stock-dot {
    width: 9px;
    height: 9px;
    border-radius: var(--radius-full);
    flex: none;
}

/* Held by customers, sitting in the warehouse, and unaccounted for. */
.seg-customers { background: var(--primary); }
.seg-warehouse { background: #94a3b8; }
.seg-missing { background: var(--danger); }

/* What is about to be destroyed, itemised. Deliberately a list rather than a
   sentence: a manager reading "and 42 delivery lines" in a paragraph reads past
   it, and the whole point of this screen is that they do not. */
.danger-list {
    margin: 12px 0 0;
    padding-left: 20px;
    display: grid;
    gap: 4px;
    font-size: 13px;
    color: var(--text-secondary);
}

.danger-list strong { color: var(--text); }

.stock-note {
    margin: 0;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    background: var(--danger-light);
    color: var(--danger);
    font-size: 13px;
    font-weight: 600;
}

/* Two containers side by side once there is room: they are compared, not read
   one after the other. */
@media (min-width: 900px) {
    .stock-list-split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Today's traffic under the split bar: three small figures in a row, set off
   by a hairline so they read as "today", not as more of the split. */
.stock-today {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
    gap: 8px;
    margin: 0;
    padding-top: 12px;
    border-top: 1px solid var(--line);
}

.stock-today dt {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
}

.stock-today dd {
    margin: 2px 0 0;
    font-size: 18px;
    font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

/* Direction colours, used the same way in the cards, the sum and the table:
   out of the warehouse is the brand colour (it went to a customer), back in is
   neutral, a write-off is red, a purchase is green. */
.figure-out { color: var(--primary-ink); }
.stock-today .figure-in { color: var(--text); }

/* Four of today's figures do not fit a phone's width; two by two reads better
   than three and an orphan. */
@media (max-width: 640px) {
    .stock-today:has(> div:nth-child(4)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.figure-add { color: var(--success); font-weight: 600; }
.figure-remove { color: var(--danger); font-weight: 600; }

/* ---- Day-wise stock book ---- */

.stock-book-table { min-width: 760px; }

.stock-book-table tbody td { padding-top: 10px; padding-bottom: 10px; }

/* The date stays put while the figures scroll sideways on a phone. */
.stock-book-table th:first-child,
.stock-book-table td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface);
}

.stock-book-table tbody tr:hover td:first-child { background: var(--surface-alt); }

.stock-book-date {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    color: var(--text);
    font-weight: 600;
    text-decoration: none;
}

.stock-book-date:hover { color: var(--primary-ink); text-decoration: underline; }

.stock-book-weekday {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-secondary);
}

.stock-book-table .pill { margin-left: 6px; padding: 2px 8px; }

/* The column the whole table exists for: shaded, and heavier than the rest. */
.stock-book-table .col-closing {
    font-weight: 700;
    background: rgb(var(--primary-rgb) / 0.06);
}

.stock-book-table .col-closing.is-negative { color: var(--danger); }

.stock-book-table .muted-figure { color: var(--text-secondary); }

/* A day nothing moved: still a row, so the book has no gaps, but quiet. */
.stock-book-table tr.is-quiet td { color: var(--text-secondary); }
.stock-book-table tr.is-quiet .col-closing { font-weight: 500; }

.stock-book-table tr.is-today td { background: var(--surface-alt); }
.stock-book-table tr.is-today .col-closing { background: rgb(var(--primary-rgb) / 0.1); }

.stock-book-table tfoot th,
.stock-book-table tfoot td {
    padding: 12px;
    font-size: 13px;
    font-weight: 700;
    border-top: 2px solid var(--line-strong);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.stock-book-table tfoot th { text-align: left; }

.stock-book-help { font-size: 12px; margin-bottom: 0; }

.inventory-dashboard {
    gap: 20px;
}

.inventory-summary-grid {
    display: grid;
    gap: 16px;
    grid-template-columns: 1fr;
}

.inventory-summary-card {
    background:
        radial-gradient(circle at top right, rgb(var(--primary-rgb) / 0.12), transparent 38%),
        linear-gradient(180deg, #ffffff 0%, color-mix(in srgb, var(--primary) 4%, #ffffff) 100%);
}

.inventory-dashboard-row {
    align-items: start;
}

.inventory-pills {
    justify-content: flex-end;
}

/* ---- MOBILE TOPBAR ---- */

/* ---- CONTACT THE MANAGER ----
   Call and WhatsApp are the same errand reached two ways, so they sit together
   and look alike. Only the glyph and its green mark them apart — WhatsApp's own
   colour is the fastest thing on the page to find, and using anything else for
   it would make a customer hunt for a button they already know by sight. */

.btn-icon-glyph { flex: none; }

.btn-whatsapp {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}

.btn-whatsapp:hover {
    border-color: var(--whatsapp);
    color: var(--whatsapp);
}

.btn-whatsapp .btn-icon-glyph { color: var(--whatsapp); }

/* Both contact buttons are glyphs in a circle, at every width. Spelled out,
   "Call manager" and "WhatsApp" took a third of the customer's navbar between
   them to say what a handset and a speech bubble say faster — and side by side
   as two round marks they read as one pair of ways to reach the same person.
   The labels are collapsed rather than removed, so they still reach a screen
   reader and still fill the tooltip. */
.customer-page .sidebar-stack > .btn {
    width: 36px;
    min-width: 36px;
    min-height: 36px;
    padding: 0;
    /* `.btn` keeps an 8px gap for the space between an icon and its label. With
       the label at zero width that gap survives as empty flex track and pushes
       the glyph off centre. */
    gap: 0;
    font-size: 0;
    border-radius: var(--radius-full);
}

.customer-page .sidebar-stack > .btn::before {
    content: '';
    width: 17px;
    height: 17px;
    background: currentColor;
    -webkit-mask: var(--icon-phone) center / contain no-repeat;
    mask: var(--icon-phone) center / contain no-repeat;
}

/* WhatsApp brings its own mark inside the anchor, so the phone glyph above
   would print a second icon beside it. */
.customer-page .sidebar-stack > .btn-whatsapp::before { content: none; }

/* ---- MODAL ---- */

.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    /* The overlay scrolls, not the card: a card that clipped its own overflow
       also cut off any dropdown opening near its bottom edge. */
    overflow: auto;

    /* Fixed positioning takes the dialog out of the layout; it does not take it
       out of the document, and inheritance follows the document. A modal
       written inside a table cell — which is where a row's own actions belong —
       was inheriting that cell's typography, and `white-space: nowrap` on a
       narrow column turned every sentence in the dialog into one long line
       running out the side of the card. Reset here rather than on each modal,
       because the next one placed in a cell would have found the same trap. */
    white-space: normal;
    text-align: left;
    text-transform: none;
    letter-spacing: normal;
}

.modal-card {
    width: min(560px, 100%);
    /* `auto` on all sides keeps a taller-than-viewport card reachable at the
       top, which plain flex centring plus scrolling does not. */
    margin: auto;
    padding: 24px;
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
}

/* A confirmation is one sentence and two buttons. At the full 560px the
   sentence sat on one short line in a very wide box and read as an empty
   dialog rather than a question. */
.modal-card-sm { width: min(420px, 100%); }

.modal-close,
.order-alert-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-secondary);
    cursor: pointer;
    line-height: 1;
    transition: all var(--transition);
}

.modal-close:hover,
.order-alert-close:hover { background: var(--surface-muted); color: var(--text); }

.modal-close:focus-visible,
.order-alert-close:focus-visible {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgb(var(--primary-rgb) / 0.1);
}

.modal-close {
    width: 36px;
    height: 36px;
    font-size: 13px;
}

/* The glyph is drawn from CSS rather than typed into 30-odd templates, which
   is how half of them ended up carrying a lowercase letter x instead — lighter
   than a real multiplication sign and sitting a shade off centre beside one. */
.modal-close::before,
.order-alert-close::before { content: '\2715'; }

/* ---- WORKSPACE LAYOUT ---- */

.workspace-shell {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.workspace-sidebar {
    display: flex;
    flex-direction: column;
    gap: 20px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 16px 20px;
    box-shadow: var(--shadow-xs);
}

.brand-block {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.brand-block p,
.brand-block h1 { margin: 0; }

.brand-block h1 {
    font-size: 16px;
    font-weight: 700;
}

.workspace-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.sidebar-stack {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.section-tabs { display: none; }

.workspace-nav a,
.section-tabs a,
.mobile-nav a {
    display: inline-flex;
    align-items: center;
    min-height: 38px;
    padding: 8px 14px;
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-weight: 500;
    font-size: 13px;
    white-space: nowrap;
    text-decoration: none;
    transition: all var(--transition);
}

.workspace-nav a:hover, .section-tabs a:hover {
    background: var(--surface-muted);
    color: var(--text);
}

.workspace-nav a.active,
.section-tabs a.active,
.mobile-nav a.active {
    background: var(--primary);
    color: var(--on-primary);
}

.workspace-main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.workspace-hero { background: var(--surface); }

.hero-badge-stack { align-self: stretch; }

.hero-badge {
    min-width: 0;
    padding: 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
}

.hero-badge span {
    display: block;
    color: var(--text-secondary);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.inline-form {
    display: flex;
    gap: 8px;
    align-items: center;
}

/* A dropdown sitting in a row of pills should size to its content, not stretch
   the whole row. */
.inline-form .select-wrap,
.data-pills .select-wrap { width: auto; min-width: 130px; }

.topbar {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
}

.topbar h1 {
    font-size: 20px;
    font-weight: 700;
}

.mobile-nav {
    position: fixed;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    width: min(680px, calc(100% - 24px));
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 4px;
    padding: 8px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-md);
}

/* ---- FILTER INLINE ---- */

.filter-inline {
    display: flex;
    gap: 8px;
    align-items: end;
}

.page-home .grid-cards { grid-template-columns: 1fr; }

/* ============================================================
   RESPONSIVE – DESKTOP (≥768px)
   ============================================================ */

@media (min-width: 768px) {
    .app-shell { padding: 24px; --shell-pad-y: 48px; }

    .grid-cards, .stats-grid { grid-template-columns: repeat(3, 1fr); }

    .landing-auth-grid {
        grid-template-columns: 1fr 1fr;
        align-items: start;
    }

    .page-home .hero-card { padding: 32px; }

    .metric-grid { grid-template-columns: repeat(3, 1fr); }
    .metric-grid-admin { grid-template-columns: repeat(4, 1fr); }
    /* Six tiles: two rows of three until there is room for one row of six. */
    .sheet-summary-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .compact-grid, .metric-grid-report { grid-template-columns: repeat(2, 1fr); }

    .dashboard-grid { grid-template-columns: 1.2fr 1fr; }
    .dashboard-grid-wide { grid-template-columns: 1fr 1fr; }
    .hero-badge-stack { grid-template-columns: repeat(2, minmax(160px, 1fr)); }
    .inventory-summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    /* `minmax(0, …)` rather than a bare `1fr`: a plain `1fr` floors the column
       at its content's min-content width, so one long value (a full address in
       a dropdown) stole the other column's share. */
    .form-inline, .pause-form, .field-grid, .inventory-form {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: end;
    }

    /* A row of fields in a form aligns on its top edge, not its bottom.
       `end` is right for the inline forms above, where a button has to sit
       level with the box beside it — but on a pair of labelled fields it means
       any difference in control height pushes the two labels apart, and
       "Delivery date" sat two pixels below "Delivery time". The labels are what
       the eye lines up on. */
    .field-grid { align-items: start; }

    /* Every field in a form is either the full width or exactly half of it.
       A row left with one field in it hands that field the whole row rather
       than leaving a half-width box beside an empty gap. */
    .field-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; }

    .filter-inline {
        display: flex;
        gap: 8px;
        align-items: end;
    }

    .data-row, .subscription-row, .quick-row,
    .list-link-card, .inventory-row {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
    }

    .inventory-note { grid-column: 1 / -1; }
    .detail-grid { grid-template-columns: 0.95fr 1.05fr; }
    .quick-actions { grid-template-columns: 1fr 1fr; }

    /* Desktop: horizontal navbar */
    .workspace-shell { flex-direction: column; gap: 20px; }
    .customer-shell { flex-direction: column; }

    .workspace-sidebar {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        padding: 12px 20px;
        gap: 16px;
    }

    .brand-block {
        flex-direction: row;
        align-items: center;
        gap: 10px;
        flex-shrink: 0;
    }

    .brand-block .muted { display: none; }
    .brand-block h1 { font-size: 16px; }

    .workspace-nav {
        flex-direction: row;
        align-items: center;
        gap: 2px;
        flex-wrap: wrap;
    }

    .sidebar-stack {
        flex-direction: row;
        align-items: center;
        gap: 8px;
        flex-shrink: 0;
    }

    .sidebar-stack .btn {
        min-height: 36px;
        padding: 8px 14px;
        font-size: 12px;
    }

    /* An icon button carries no text, so the wider padding above just made it
       a rectangle next to the square icon buttons elsewhere on the page. */
    .sidebar-stack .btn-icon {
        padding: 8px 10px;
    }

    /* The workspace is exactly one viewport tall and never scrolls as a whole:
       the navbar stays put and the panes below it scroll inside themselves.
       Scoped to the admin dashboard — the public and customer pages are
       ordinary documents that should keep scrolling normally. Phones are left
       alone too: a locked viewport there fights the browser chrome. */
    html:has(.workspace-page), body:has(.workspace-page) {
        height: 100%;
        overflow: hidden;
    }

    body:has(.workspace-page) .app-shell {
        height: 100%;
        min-height: 0;
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }

    .workspace-page {
        flex: 1;
        min-height: 0;
    }

    /* The modal wrappers are in-flow siblings of the shell holding nothing but
       fixed-position overlays. As flex items they contribute no height but do
       collect the column gap, which would push the shell past the viewport. */
    .workspace-page > div[id$="-modals"] { display: contents; }

    .workspace-shell {
        flex: 1;
        min-height: 0;
    }

    /* The default scroll area: whatever a tab renders scrolls here, under a
       navbar that stays on screen. Tabs that manage their own panes (see
       .fill-height) simply fill this box instead. */
    .workspace-main {
        flex: 1;
        min-height: 0;
        overflow-y: auto;
        /* Room for the card shadows, which a clipped scroll box would cut off. */
        padding: 2px;
        margin: -2px;
    }

    /* A tab that lays out its own scroll panes and must not scroll as a unit. */
    .fill-height {
        display: flex;
        flex-direction: column;
        flex: 1 1 auto;
        min-height: 0;
    }

    .customer-admin-layout { grid-template-columns: 320px minmax(0, 1fr); }

    /* The directory and the customer's detail scroll on their own, so working
       down a long ledger does not carry the list you are picking from off the
       screen — and picking someone further down the list does not scroll the
       detail away. Only on wide screens: stacked on a phone they are one
       column, and two scroll areas inside one page would fight each other. */
    .fill-height > .customer-admin-layout {
        flex: 1 1 auto;
        min-height: 0;
        align-items: stretch;
    }

    .customer-admin-layout > .surface-card,
    .customer-admin-layout > .detail-stack {
        min-height: 0;
        max-height: 100%;
        overflow-y: auto;
    }

    .customer-admin-layout > .detail-stack {
        /* Room for the card shadows, which a clipped scroll box would cut off. */
        padding: 2px;
        margin: -2px;
    }

    /* The directory's heading and search ride at the top of the list they
       belong to, so you can retype a name without scrolling the list back up
       to reach the field. The negative side margins let the block cover the
       card's padding edge to edge, otherwise names slide through the gap
       beside it while scrolling. */
    .customer-admin-layout > .surface-card:has(> .list-panel-head) { padding-top: 0; }

    .customer-admin-layout > .surface-card > .list-panel-head {
        position: sticky;
        top: 0;
        z-index: 2;
        margin: 0 -20px;
        padding: 20px 20px 12px;
        background: var(--surface);
        border-bottom: 1px solid var(--line);
    }

    .section-tabs { display: none; }
    .mobile-nav { display: none; }
}

@media (min-width: 1100px) {
    .compact-grid, .metric-grid-report { grid-template-columns: repeat(3, 1fr); }
}

@media (min-width: 1280px) {
    .sheet-summary-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

/* ============================================================
   RESPONSIVE – MOBILE (<768px)
   ============================================================ */

@media (max-width: 767px) {
    .app-shell { padding: 12px 12px 32px; --shell-pad-y: 44px; }

    .card-head, .topbar { flex-direction: column; align-items: stretch; gap: 12px; }

    /* ...except inside a modal, where stacking drops the close button onto its
       own line below the title instead of keeping it in the corner. */
    .modal-card .card-head {
        flex-direction: row;
        align-items: flex-start;
        justify-content: space-between;
        gap: 12px;
    }

    .section-tabs { display: none; }

    .hero-badge-stack, .filter-inline { width: 100%; }
    .filter-inline { flex-direction: column; }

    .field-grid, .quick-actions { grid-template-columns: 1fr; }
    .daily-entry-form {
        width: 100%;
        justify-content: flex-start;
    }
    .inventory-pills {
        justify-content: flex-start;
    }

    /* The sidebar's phone base. Both dashboards redraw it as a sticky header
       below; what stays here is only what the two of them share. */
    .workspace-sidebar {
        flex-direction: column;
        justify-content: flex-start;
        align-items: stretch;
    }

    .brand-block {
        flex-direction: column;
        align-items: flex-start;
        position: relative;
    }

    .brand-block .muted { display: block; }

    .workspace-nav { flex-direction: column; }
    .sidebar-stack { margin-top: auto; }

    /* Modal adjustments */
    .modal-card { padding: 20px; }
}

/* ---- ACCOUNT MENU ----
   The customer's own corner of the navbar: name, deposit, language and log out.
   Sits where the manager's log-out button sits, and opens the way the month
   picker does — same offset, same shadow, same close-on-outside-click. */

.account-menu { position: relative; }

.account-trigger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px 4px 4px;
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text-secondary);
    cursor: pointer;
    transition: var(--transition);
}

.account-trigger:hover { border-color: var(--primary); color: var(--primary-dark); }

.account-menu.is-open .account-trigger {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgb(var(--primary-rgb) / 0.1);
}

/* The initials are the only thing in the navbar that has to be legible at a
   glance, so they carry the brand colour rather than the muted grey around them. */
.account-initials {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    background: var(--primary-light);
    color: var(--primary-dark);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.account-chevron { flex: none; transition: transform var(--transition); }
.account-menu.is-open .account-chevron { transform: rotate(180deg); }

.account-pop {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 40;
    width: 240px;
    padding: 8px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    animation: month-picker-in 0.14s ease-out;
}

/* Name and number on the left, the gear that edits them on the right. The
   name column can shrink so a long one truncates instead of pushing the gear
   off the edge of the popover. */
.account-identity {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 10px 10px;
}

.account-identity-main {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.account-identity strong,
.account-identity .muted {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.account-identity strong { font-size: 14px; font-weight: 700; }
.account-identity .muted { font-size: 12px; }

.account-divider { height: 1px; margin: 6px 0; background: var(--line); }

.account-label {
    padding: 4px 10px 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
}

.account-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 9px 10px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font-size: 13px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
}

.account-item:hover, .account-item:focus-visible { background: var(--surface-muted); outline: none; }
.account-item strong { font-weight: 700; font-variant-numeric: tabular-nums; }

.account-item-danger { color: var(--text-secondary); }
.account-item-danger:hover, .account-item-danger:focus-visible { background: var(--danger-light); color: var(--danger); }

/* The delivery address in the account menu: a label over the address itself,
   and the action on the right. The address wraps to two lines and no more —
   enough to recognise it, and the dialog behind it has the rest. */
.account-item-address { align-items: flex-start; }

.account-item-stack {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.account-item-stack small {
    display: -webkit-box;
    overflow: hidden;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.4;
    white-space: pre-line;
}

.account-item-action {
    flex-shrink: 0;
    color: var(--primary-dark);
    font-size: 12px;
    font-weight: 700;
}

.account-item-warn { color: var(--warning); font-weight: 600; }

/* The same fact inside the account settings dialog, read-only, with the button
   that opens the address dialog on top of it. */
.profile-address {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-alt);
}

.profile-address-main { display: grid; gap: 4px; min-width: 0; }
.profile-address-main > span { font-size: 13px; font-weight: 600; }
.profile-address-main p { margin: 0; font-size: 13px; line-height: 1.45; white-space: pre-line; overflow-wrap: anywhere; }
.profile-address .btn { flex-shrink: 0; }

/* No address on file: said once, at the top of whichever tab is open. */
.address-banner {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
    padding: 12px 14px;
    border: 1px solid #fde68a;
    border-radius: var(--radius-lg);
    background: var(--warning-light);
    color: var(--text);
}

.address-banner svg { flex-shrink: 0; color: var(--warning); }
.address-banner p { flex: 1; margin: 0; font-size: 14px; font-weight: 500; line-height: 1.45; }
.address-banner .btn { flex-shrink: 0; }

@media (max-width: 560px) {
    .address-banner { flex-wrap: wrap; }
    .address-banner p { flex-basis: calc(100% - 32px); }
    .address-banner .btn { width: 100%; }
}

/* "Deliver to" inside an order or subscription form. See
   partials/address-field.php. Reads as a summary card rather than a form
   control, because most of the time it is one: there is one address and the
   form is telling you where the order is going. */
.address-field {
    display: grid;
    gap: 8px;
}

.address-field-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
}

.address-field-label { display: block; font-size: 13px; font-weight: 600; }

.address-field-toggle {
    padding: 2px 0;
    border: none;
    background: none;
    color: var(--primary-dark);
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
}

.address-field-toggle:hover { text-decoration: underline; }
.address-field-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px; }

.address-field-view {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 11px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-alt);
}

.address-field-pin { flex-shrink: 0; margin-top: 1px; color: var(--primary-ink); }

.address-field-text {
    margin: 0;
    min-width: 0;
    font-size: 14px;
    line-height: 1.45;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.address-field-text.is-placeholder { color: var(--muted); }
.address-field-view:has(.is-placeholder) .address-field-pin { color: var(--line-strong); }

.address-field-edit { display: grid; gap: 6px; }

.address-field-note {
    margin: 0;
    font-size: 12px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--text-secondary);
}

/* Two languages side by side rather than a dropdown inside a dropdown: with
   exactly two choices, a pair of buttons is one tap instead of three. */
.account-languages {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    padding: 0 4px 2px;
}

.account-language {
    width: 100%;
    padding: 8px 6px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
}

.account-language:hover { border-color: var(--primary); color: var(--primary-dark); }

.account-language.is-selected {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
}


/* Parts of both dashboards that exist only for their phone layout, and are
   switched on inside the mobile breakpoints below. Off by default so the
   desktop layout — which was already right — is left exactly as it was:

   - the tab-bar icons, which on a desktop navbar of text pills would only crowd
     the labels and wrap the nav onto a second line;
   - More, and the shortened label beside it: a desktop navbar has room for all
     seven of the manager's tabs and spells every one of them out;
   - the way back from one customer to the directory, which on a desktop is not
     a journey — the list is still there beside them;
   - the line under a card's title, which repeats a column that has a proper
     column of its own once there is room for one. */
.nav-icon { display: none; }
.nav-more { display: none; }
.nav-label-short { display: none; }
.detail-back { display: none; }
.row-title-aside { display: none; }
.row-title-pill { display: none; }
.btn-plus { display: none; }
.unit-suffix { display: none; }

/* The sections behind More. Only ever seen inside that sheet, and only on a
   phone, but styled outside the breakpoint so the modal is never a naked list
   of links at any width. Each row is a full-width target with its own icon —
   the same glyph the tab bar would have shown it under. */
.nav-more-list {
    display: grid;
    gap: 4px;
}

.nav-more-item {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 50px;
    padding: 10px 12px;
    border-radius: var(--radius-md);
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
    text-decoration: none;
    transition: background var(--transition), color var(--transition);
}

.nav-more-item:hover { background: var(--surface-muted); }

.nav-more-item.is-active {
    background: var(--primary-light);
    color: var(--primary-dark);
}

/* ============================================================
   CUSTOMER APP ON A PHONE
   ============================================================
   Almost every customer opens this on a phone and nothing else, so the phone
   layout is the one that has to be right rather than the one that has to cope.

   Every rule below lives inside the mobile breakpoint, so nothing here applies
   at 768px and up and the desktop layout is exactly as it was.

   Most of it hangs off `.customer-page` alone. Two patterns are written for
   `.admin-page` as well, because the manager's dashboard is now worked from a
   phone too and they are the same problem on both screens: a table wider than
   the screen becomes one card per row, and a modal rises from the bottom edge
   as a sheet instead of sitting mid-page with its buttons out of a thumb's
   reach. Those selectors name both pages; everything the two pages do
   differently stays apart — the manager's own layout is in its own section
   below.

   The shape is the one a phone user already knows: a sticky header carrying
   the brand and the account, a scrolling page, and a fixed tab bar at the
   bottom where a thumb already rests. The drawer this replaces cost two taps
   and a hidden gesture to reach a tab; the bar costs one and is always visible.
*/
@media (max-width: 767px) {

    /* ---- Header ---- */

    .customer-page .workspace-shell { gap: 0; }

    /* Full-bleed and sticky: the negative margin cancels `.app-shell`'s padding
       so the bar reaches both edges the way an app's header does. */
    .customer-page .workspace-sidebar {
        position: sticky;
        /* The drawer this replaces was a 280px fixed panel; every one of its
           offsets has to be given back or the header keeps its width. */
        top: 0;
        left: auto;
        bottom: auto;
        width: auto;
        z-index: 50;
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        margin: -12px -12px 16px;
        padding: 10px 14px;
        border: none;
        border-bottom: 1px solid var(--line);
        border-radius: 0;
        box-shadow: var(--shadow-xs);
        transform: none;
        transition: none;
    }

    .customer-page .brand-block {
        flex-direction: row;
        align-items: center;
        position: static;
        flex-shrink: 1;
        min-width: 0;
    }

    .customer-page .brand-block h1 {
        font-size: 17px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* The phone number belonged to the drawer, where there was room for it. It
       is one tap away in the account menu, which is also where it is editable. */
    .customer-page .brand-block .muted { display: none; }

    .customer-page .sidebar-stack {
        flex-direction: row;
        align-items: center;
        gap: 8px;
        margin-top: 0;
        flex-shrink: 0;
    }

    /* The same two circles, at the size a thumb hits reliably. */
    .customer-page .sidebar-stack > .btn {
        width: 44px;
        min-width: 44px;
        min-height: 44px;
    }

    .customer-page .sidebar-stack > .btn::before {
        width: 18px;
        height: 18px;
    }

    .customer-page .sidebar-stack > .btn .btn-icon-glyph {
        width: 20px;
        height: 20px;
    }

    /* ---- Bottom tab bar ---- */

    /* The same <nav>, pinned. Fixed rather than sticky so it never scrolls away
       mid-list, and padded for the home indicator on a notched phone. */
    .customer-page .workspace-nav {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 60;
        flex-direction: row;
        justify-content: space-around;
        gap: 2px;
        padding: 6px 4px;
        padding-bottom: calc(6px + env(safe-area-inset-bottom));
        background: var(--surface);
        border-top: 1px solid var(--line);
        box-shadow: 0 -2px 14px rgb(0 0 0 / 0.06);
    }

    .customer-page .workspace-nav a {
        flex: 1 1 0;
        flex-direction: column;
        gap: 3px;
        min-width: 0;
        min-height: 52px;
        padding: 6px 2px;
        border-radius: var(--radius-md);
        background: transparent;
        color: var(--text-secondary);
        font-size: 10px;
        font-weight: 600;
        line-height: 1.2;
        text-align: center;
    }

    /* Colour alone marks the active tab. A filled pill is right on the desktop
       navbar, where the tabs sit in a row of their own; down here it would be a
       solid block under the thumb on every screen. */
    .customer-page .workspace-nav a.active {
        background: transparent;
        color: var(--primary-ink);
    }

    .customer-page .nav-icon { display: block; }
    .customer-page .workspace-nav a.active .nav-icon { stroke-width: 2.1; }

    .customer-page .nav-label {
        max-width: 100%;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* Clears the bar so the last row of a list is never trapped behind it. */
    .customer-page .workspace-main {
        padding-bottom: calc(76px + env(safe-area-inset-bottom));
    }

    /* ---- Cards ---- */

    .customer-page .surface-card { padding: 16px 14px; }
    .customer-page .section-divider { margin: 14px -14px 0; }
    .customer-page .card-head h3 { font-size: 17px; }

    /* A title with one action beside it stays on one line. Stacking put a
       full-width button under every heading, which pushed the actual content of
       a short list below the fold. */
    .customer-page .card-head-inline {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
    }

    /* ---- Tables become cards ---- */

    /* A phone cannot show seven columns, and the sideways scroll it fell back to
       hid the two that matter most — whether a bill is paid, and the button that
       opens it. Each row becomes a card instead.

       The card is a wrapping flex line, not a stack of label/value rows. A row
       per field is easy to generate and dull to read: it makes an order number
       and a payment status look equally important, and prints a heading beside
       a value that plainly says what it is. So the row is a flex container, each
       table below places its own cells with `order` and `flex`, and a cell only
       keeps its label where the number genuinely needs one — Total, Paid and Due
       do; an address and a date do not. */
    .customer-page .table-scroll,
    .admin-page .table-scroll { overflow-x: visible; }
    .customer-page .data-table,
    .admin-page .data-table { min-width: 0; }
    .customer-page .data-table thead,
    .admin-page .data-table thead { display: none; }

    .customer-page .data-table,
    .customer-page .data-table tbody,
    .admin-page .data-table,
    .admin-page .data-table tbody { display: block; }

    .customer-page .data-table tbody tr,
    .admin-page .data-table tbody tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 6px 10px;
        padding: 14px;
        border: 1px solid var(--line);
        border-radius: var(--radius-lg);
        background: var(--surface);
    }

    .customer-page .data-table tbody tr + tr,
    .admin-page .data-table tbody tr + tr { margin-top: 10px; }
    .customer-page .data-table tbody tr:hover,
    .admin-page .data-table tbody tr:hover { background: var(--surface); }

    .customer-page .data-table tbody td,
    .admin-page .data-table tbody td {
        display: flex;
        align-items: baseline;
        gap: 6px;
        width: auto;
        padding: 0;
        border: none;
        white-space: normal;
        font-size: 13px;
        text-align: left;
    }

    /* The default is a label followed by its value. Cells that do not want one
       carry `.no-label` from the per-table rules below. */
    .customer-page .data-table tbody td::before,
    .admin-page .data-table tbody td::before {
        content: attr(data-label);
        flex: none;
        font-size: 10px;
        font-weight: 700;
        letter-spacing: 0.05em;
        text-transform: uppercase;
        color: var(--text-secondary);
    }

    .customer-page .data-table tbody td.no-label::before,
    .admin-page .data-table tbody td.no-label::before { display: none; }

    /* A figure with its heading above it rather than beside it — three of these
       in a row is how a bill shows Total, Paid and Due without a table. */
    .customer-page .data-table tbody td.stat-cell,
    .admin-page .data-table tbody td.stat-cell {
        flex-direction: column;
        align-items: flex-start;
        gap: 1px;
        flex: 1 1 0;
        min-width: 0;
        font-size: 14px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    .customer-page .data-table tbody td.stat-cell::before,
    .admin-page .data-table tbody td.stat-cell::before { font-size: 9px; }

    /* The card's heading. Gives up its label and takes the line, with the least
       interesting column folded underneath it as a subtitle. */
    .customer-page .data-table tbody td.is-row-title,
    .admin-page .data-table tbody td.is-row-title {
        display: block;
        flex: 1 1 auto;
        min-width: 0;
        font-size: 15px;
        font-weight: 700;
    }

    .customer-page .data-table tbody td.is-row-title::before,
    .admin-page .data-table tbody td.is-row-title::before { display: none; }
    .customer-page .data-table tbody td.is-row-secondary,
    .admin-page .data-table tbody td.is-row-secondary { display: none; }

    .customer-page .row-title-aside,
    .admin-page .row-title-aside {
        display: block;
        margin-top: 1px;
        font-size: 12px;
        font-weight: 500;
        color: var(--text-secondary);
    }

    /* A muted line of its own, full width — an address, or the day a delivery
       is due. Nothing about either needs announcing. */
    .customer-page .data-table tbody td.meta-cell,
    .admin-page .data-table tbody td.meta-cell {
        flex: 1 1 100%;
        font-size: 12px;
        color: var(--text-secondary);
    }

    .customer-page .data-table tbody td .cell-subline,
    .admin-page .data-table tbody td .cell-subline { display: inline; margin-left: 6px; }

    .customer-page .data-table tbody td.col-actions,
    .admin-page .data-table tbody td.col-actions {
        flex: 1 1 100%;
        justify-content: flex-end;
        margin-top: 2px;
    }

    .customer-page .data-table .row-actions,
    .admin-page .data-table .row-actions { justify-content: flex-end; }
    .customer-page .data-table tbody .status-pill,
    .admin-page .data-table tbody .status-pill { white-space: nowrap; }

    /* Extra air above the figures or the buttons, so a card reads as a heading
       and then its detail rather than as one undifferentiated block.
       Space rather than a rule: these cells are separate flex items, so a
       border-top drew one short line per cell instead of one line across the
       card, and three of them under a bill looked like a mistake. */
    .customer-page .data-table tbody td.gap-above,
    .admin-page .data-table tbody td.gap-above { margin-top: 6px; }

    /* ---- Orders ----
       "#81 / 500ml bottle x1 ........ ₹20", the day underneath, then the two
       pills. Five headings became none: an amount, a date and a status pill all
       say what they are. */
    .customer-page .table-orders tbody td:nth-child(4) {
        order: 2;
        flex: none;
        /* Level with the order number, not with the middle of the two-line
           title block beside it. The amount and the number are the two things
           being scanned for, and they should read as one line across the top of
           the card rather than as two figures at slightly different heights. */
        align-self: flex-start;
        font-size: 16px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    .customer-page .table-orders tbody td:nth-child(3) { order: 3; }
    .customer-page .table-orders tbody td:nth-child(5) { order: 4; flex: none; }
    .customer-page .table-orders tbody td:nth-child(6) { order: 5; flex: none; }
    /* Cancel sits at the end of the pill line rather than under it: it is the
       only thing on the card that does anything, and a line of its own gave one
       button as much height as the order itself. `flex: 1 1 auto` overrides the
       full-width default so it takes what the pills leave and pushes hard right;
       it drops to its own line only when there is genuinely no room. */
    .customer-page .table-orders tbody td:nth-child(7) {
        order: 6;
        flex: 1 1 auto;
        align-items: center;
    }

    /* ---- Bills ----
       An explicit grid rather than the wrapping flex line the other cards use:
       flex kept trying to fit the figures beside the title, and the amounts ran
       into each other.

       The state pill sits immediately after the bill number, as a badge on the
       name rather than as a thing in its own column — "BILL-#0022, paid" is one
       statement. The amount takes the top right, where an order card puts the
       same fact. Then, underneath, what is still moving: how much has been
       paid, how much is left, and the way in.

       Paid and Due share their line with View, so the button is not left
       stranded on a row of its own. */
    .customer-page .table-bills tbody tr,
    .admin-page .table-bills tbody tr {
        display: grid;
        grid-template-columns: auto auto 1fr;
        align-items: center;
        gap: 4px 10px;
    }

    /* The title holds both its lines, so it spans the first two rows; the pill
       and the amount sit against its first line rather than against the block.
       Rows are pinned as well as columns — auto-placement walks forward only,
       and naming a column alone dropped the pill onto a line of its own. */
    .customer-page .table-bills tbody td:nth-child(1),
    .admin-page .table-bills tbody td:nth-child(1) { grid-area: 1 / 1 / 3 / 2; }
    .customer-page .table-bills tbody td:nth-child(6),
    .admin-page .table-bills tbody td:nth-child(6) { grid-area: 1 / 2 / 2 / 3; align-self: start; }
    .customer-page .table-bills tbody td:nth-child(3),
    .admin-page .table-bills tbody td:nth-child(3) { grid-area: 1 / 3 / 2 / 4; justify-self: end; align-self: start; }
    .customer-page .table-bills tbody td:nth-child(4),
    .admin-page .table-bills tbody td:nth-child(4) { grid-area: 3 / 1 / 4 / 2; }
    .customer-page .table-bills tbody td:nth-child(5),
    .admin-page .table-bills tbody td:nth-child(5) { grid-area: 3 / 2 / 4 / 3; }
    .customer-page .table-bills tbody td:nth-child(7),
    .admin-page .table-bills tbody td:nth-child(7) { grid-area: 3 / 3 / 4 / 4; justify-self: end; }

    /* Against the product name's line rather than centred in its own cell. */
    .customer-page .table-subscriptions tbody td.rate-cell { align-self: center; }

    /* Both are set against the bill number's own line, which sits a shade lower
       than the top of the cell it shares with the month below it. */
    .customer-page .table-bills tbody td.bill-status,
    .customer-page .table-bills tbody td.total-cell,
    .admin-page .table-bills tbody td.bill-status,
    .admin-page .table-bills tbody td.total-cell { margin-top: 1px; }

    /* The one figure the card is about — at the same weight and size as the
       amount on an order card, which is the same kind of figure. */
    .customer-page .table-bills tbody td.total-cell,
    .admin-page .table-bills tbody td.total-cell {
        font-size: 16px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    /* ---- Subscriptions ----
       A standing order is one sentence: this product, this often, at this time,
       to this address. So the card says it as one — "2 per day · 07:00 AM" on a
       line under the product, with the address below it — instead of setting out
       QUANTITY and TIME as a little form the customer has to read across.

       Laid out as a bill card is, because the customer reads both on the same
       screen: the name and its state together on the first line, the money in
       the top right where every other card in this app puts it, and the details
       underneath. The quantity keeps its words because "2" alone means nothing;
       the time drops its heading, because a time is obviously a time. */
    .customer-page .table-subscriptions tbody tr {
        display: grid;
        grid-template-columns: auto auto 1fr;
        align-items: center;
        gap: 3px 8px;
    }

    /* The state pill, set against the product name it describes. */
    .customer-page .row-title-pill {
        display: inline-flex;
        margin-left: 6px;
        vertical-align: middle;
    }

    /* Line one is what is being delivered and what it costs — name, state, how
       many, at what rate. Line two is when: the time it comes and the day the
       next one is due, one at each end.
       .
       The quantity sits up on the first line rather than at the head of the
       second, where the column it shared with the product name left a hole
       beside it — and where it crowded out the one thing this card is opened
       to check. */
    /* The name and its pill span the detail line's own columns rather than
       taking one of their own: a column wide enough for "Capsule" is far wider
       than the quantity that would sit under it, and that difference is exactly
       the hole this layout used to leave beside "1 Qty".

       So the columns are sized by the second line — quantity, time, next — and
       the title stretches across them. */
    .customer-page .table-subscriptions tbody td:nth-child(1) { grid-area: 1 / 1 / 2 / 3; }
    .customer-page .table-subscriptions tbody td:nth-child(4) { grid-area: 1 / 3 / 2 / 4; justify-self: end; }
    .customer-page .table-subscriptions tbody td:nth-child(3) { grid-area: 2 / 1 / 3 / 2; }
    /* Nothing else shares the detail line now, so the time runs to the edge
       rather than stopping at the rate's column and wrapping inside it. */
    .customer-page .table-subscriptions tbody td:nth-child(5) { grid-area: 2 / 2 / 3 / 4; }
    .customer-page .table-subscriptions tbody td:nth-child(2) { grid-area: 3 / 1 / 4 / 4; }
    /* When it started, then when the next one comes: the standing arrangement
       first and the thing that moves last, so the pill still closes the card. */
    .customer-page .table-subscriptions tbody td:nth-child(6) { grid-area: 4 / 1 / 5 / 4; }
    .customer-page .table-subscriptions tbody td:nth-child(7) { grid-area: 5 / 1 / 6 / 4; }

    /* When the next one arrives, as a pill on its own line under the address.
       It is the one thing on this card that changes by itself — everything
       above it is the standing arrangement, set once and then true — so it is
       set apart rather than queued at the end of a row of facts. */
    .customer-page .data-table tbody td.next-cell {
        justify-self: start;
        align-items: baseline;
        gap: 5px;
        margin-top: 4px;
        padding: 4px 10px;
        border-radius: var(--radius-full);
        background: var(--primary-light);
        font-size: 13px;
        font-weight: 700;
        color: var(--primary-dark);
        white-space: nowrap;
    }

    .customer-page .data-table tbody td.next-cell::before {
        color: var(--primary-dark);
        opacity: 0.7;
    }

    /* "Qty" beside the number, where the full word costs more room than it
       earns. The heading above the desktop column still says Quantity. */
    .customer-page .data-table tbody td.unit-cell[data-label-short]::before {
        content: attr(data-label-short);
    }

    /* The one figure the card is about, at the weight a bill card gives its
       total. It leads its own corner, so it takes no divider. */
    .customer-page .data-table tbody td.rate-cell {
        align-items: baseline;
        gap: 4px;
        font-size: 16px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    .customer-page .rate-cell .unit-suffix {
        display: inline;
        font-size: 11px;
        font-weight: 600;
        color: var(--text-secondary);
    }

    .customer-page .data-table tbody td.time-cell::after {
        content: '';
        order: -1;
        width: 1px;
        height: 11px;
        margin-right: 4px;
        background: var(--line-strong);
    }



    /* A value with its unit after it — "2 per day" — rather than a heading
       above it. `::before` is a flex item like any other, so `order` moves the
       label to the far side of the number it belongs to. */
    .customer-page .data-table tbody td.unit-cell {
        align-items: baseline;
        gap: 4px;
        font-size: 15px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    .customer-page .data-table tbody td.unit-cell::before {
        order: 2;
        font-size: 11px;
        font-weight: 600;
        letter-spacing: 0;
        text-transform: none;
    }

    /* The quietest of the line, and the one that gives up room first: when the
       round arrives matters less than what is coming, what it costs and when
       the next one is due — and all four have to hold one line on a 360px
       phone, which is the narrowest screen this is read on. */
    .customer-page .data-table tbody td.time-cell {
        font-size: 13px;
        font-weight: 600;
        color: var(--text-secondary);
    }

    /* The hour and how often it comes are two facts, not one phrase: each holds
       together and the pair wraps between them when the row runs out of width.
       Without this the time itself broke, and "07:00 / AM" reads as a fault. */
    .customer-page .table-subscriptions tbody td.time-cell .time-value,
    .customer-page .table-subscriptions tbody td.time-cell .cell-subline {
        white-space: nowrap;
    }

    /* A grid item will not shrink past its content unless told to, and an
       unshrinkable cell full of nowrap text runs off the side of the card
       instead of wrapping between the two. Kept as the floor under the short
       label: Hindi is longer than English here and can still need the break. */
    .customer-page .table-subscriptions tbody td.time-cell {
        min-width: 0;
        flex-wrap: wrap;
    }


    /* Both sit on one line under the product, so neither needs the extra air
       that separates a block of figures from the heading above it. */
    .customer-page .table-subscriptions tbody td.gap-above { margin-top: 1px; }

    /* ---- Addresses ---- */
    .customer-page .table-addresses tbody td:nth-child(2) { order: 2; }
    .customer-page .table-addresses tbody td:nth-child(3) { order: 3; }

    /* ---- Deposit history ---- */
    .customer-page .table-deposit tbody td:nth-child(1) { order: 1; flex: 1 1 auto; font-weight: 600; }
    .customer-page .table-deposit tbody td:nth-child(3) {
        order: 2;
        flex: none;
        font-size: 15px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }
    .customer-page .table-deposit tbody td:nth-child(2) { order: 3; }

    /* ---- Delivery sheet ---- */

    .customer-page .sheet-summary-grid { gap: 8px; }
    .customer-page .sheet-summary-grid .mini-metric { padding: 10px 12px; }
    .customer-page .sheet-summary-grid .mini-metric span { font-size: 11px; line-height: 1.3; }
    .customer-page .sheet-summary-grid strong { margin-top: 2px; font-size: 18px; }

    /* Five tiles over three rows, the odd one out taking the full width rather
       than sitting half-empty beside a gap. */
    .customer-page .sheet-summary-grid .mini-metric:last-child { grid-column: 1 / -1; }

    /* Two products side by side, at the same weight as the delivery-sheet tiles
       under them. They were the full-size tile and read as the more important
       row of the two, which is backwards. */
    .customer-page .metric-row {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 8px;
    }

    /* Three tiles over two columns leaves the last one stranded beside a hole,
       so the total takes the whole line under the two figures it adds up. */
    .customer-page .statement-metrics .mini-metric:last-child { grid-column: 1 / -1; }

    .customer-page .metric-row .mini-metric { padding: 10px 12px; }
    .customer-page .metric-row .mini-metric span { font-size: 11px; }
    .customer-page .metric-row .mini-metric strong { margin-top: 2px; font-size: 18px; }

    /* The stepper is the one control on the page, so it sits in the middle of
       the card rather than hard against its left edge. */
    .customer-page .sheet-toolbar { padding: 14px 0 0; justify-content: center; }
    /* `.daily-entry-field` is a grid, so centring its DATE caption and the
       stepper under it is `justify-items`, not `align-items`. */
    .customer-page .sheet-toolbar .daily-entry-field { justify-items: center; width: 100%; }
    .customer-page .date-stepper { justify-content: center; width: 100%; }
    .customer-page .date-stepper input[type="date"] { flex: 0 1 auto; min-width: 0; text-align: center; }
    .customer-page .step-btn { width: 44px; height: 44px; flex: none; }

    .customer-page .inventory-row { padding: 14px; }

    /* ---- The two "add" buttons ----
       A square + beside the heading. Spelled out, "Add address" took a third of
       the line on a 375px screen and pushed the heading into an ellipsis; the
       plus is the one glyph every phone user already reads as "new". The words
       stay as the button's accessible name and its tooltip. */
    .customer-page .btn-add {
        width: 44px;
        min-width: 44px;
        min-height: 44px;
        padding: 0;
        gap: 0;
        flex: none;
    }

    /* The glyph carries the whole button, so it is drawn large inside it rather
       than as a small mark floating in a large square. */
    .customer-page .btn-add .btn-plus {
        display: block;
        width: 20px;
        height: 20px;
        stroke-width: 2.6;
    }

    .customer-page .btn-add .btn-add-label { display: none; }

    /* ---- Payments ----
       Two lines, four facts, no headings: "₹2,440" against the day it was
       taken, and under it how it came in against what it was put towards. An
       amount, a date, "Cash" and a bill number all announce themselves, and
       four small-caps labels on a card this short read as more text than the
       card itself. */
    .customer-page .table-payments tbody tr,
    .admin-page .table-payments tbody tr {
        display: grid;
        grid-template-columns: auto 1fr;
        align-items: baseline;
        gap: 3px 10px;
    }

    /* The figure the card is about, at the weight a bill card gives its total —
       the customer is scanning a column of amounts, not reading rows. */
    .customer-page .table-payments tbody td.total-cell,
    .admin-page .table-payments tbody td.total-cell {
        grid-area: 1 / 1 / 2 / 2;
        font-size: 16px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    .customer-page .table-payments tbody td.payment-date,
    .admin-page .table-payments tbody td.payment-date {
        grid-area: 1 / 2 / 2 / 3;
        justify-self: end;
        font-size: 12.5px;
        color: var(--text-secondary);
    }

    .customer-page .table-payments tbody td.payment-mode,
    .admin-page .table-payments tbody td.payment-mode {
        grid-area: 2 / 1 / 3 / 2;
        font-size: 12.5px;
        color: var(--text-secondary);
    }

    .customer-page .table-payments tbody td.payment-ref,
    .admin-page .table-payments tbody td.payment-ref {
        grid-area: 2 / 2 / 3 / 3;
        justify-self: end;
        min-width: 0;
    }

    /* ---- Delivery and returns ----
       Laid out as a bill card is, because it is read on the same screen: the
       product and its state on the first line with the money at the right, and
       the three quantities as a row of figures underneath. The date and the
       source fold into the subtitle — both are one short phrase, and neither
       deserves a column of its own on a 375px screen. */
    /* Three equal columns, because the second row is three figures of the same
       kind and they should read as a row of equals rather than as one at the
       left and two pushed over to the right. The name and the money share the
       first row across the same three tracks. */
    .customer-page .table-deliveries tbody tr {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        align-items: center;
        gap: 4px 10px;
    }

    .customer-page .table-deliveries tbody td.is-row-title { grid-area: 1 / 1 / 2 / 3; }

    /* The state travels with the product name, so the money takes the end of
       the line on its own. */
    .customer-page .table-deliveries tbody td.total-cell {
        grid-area: 1 / 4 / 2 / 5;
        justify-self: end;
        align-self: start;
        font-size: 16px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    /* Planned, delivered, returned — three figures on one line, each under its
       own heading. This is the row the customer came to check: whether what was
       promised went out, and how many empties went back. */
    .customer-page .table-deliveries tbody td:nth-child(4) { grid-area: 2 / 1 / 3 / 2; }
    .customer-page .table-deliveries tbody td:nth-child(5) { grid-area: 2 / 2 / 3 / 3; }
    .customer-page .table-deliveries tbody td:nth-child(6) { grid-area: 2 / 3 / 3 / 4; }

    /* Set against the product name's own line, which sits slightly below the
       top of the cell it shares with the subtitle under it. */
    .customer-page .table-deliveries tbody td.total-cell { margin-top: 1px; }

    /* The month picker's empty row is one sentence, not a card of fields. */
    .customer-page .table-deliveries tbody tr[data-month-empty] { display: block; }

    /* The picker goes full width on a narrow screen wherever it is the only
       thing in its row — but here it shares the card's heading line, and at
       full width it pushed "Delivery and returns" onto two lines and then took
       a line of its own underneath. Beside the heading it stays its own size. */
    .customer-page .card-head-inline .month-picker { width: auto; }
    .customer-page .card-head-inline .month-picker-button { width: auto; }
    .customer-page .card-head-inline .month-picker-pop { left: auto; right: 0; }

    /* ---- Card polish ---- */

    /* The heading of a list card sits opposite a 44px button, so it gets the
       vertical room to sit against it rather than being crowded. */
    .customer-page .card-head-inline { min-height: 44px; }

    /* A card's own heading and its detail want different weights of gap: the
       subtitle belongs to the title, the figures under it do not. */
    .customer-page .data-table tbody tr,
    .admin-page .data-table tbody tr { padding: 13px 14px; }

    /* Small-caps headings inside a card are supporting text, not labels of
       equal standing with the values — a shade lighter than the body they
       introduce, and tight to it. */
    .customer-page .data-table tbody td.stat-cell::before,
    .admin-page .data-table tbody td.stat-cell::before { letter-spacing: 0.06em; opacity: 0.85; }

    /* An address or a note runs to two lines; a touch more leading keeps it
       from setting as a solid block. */
    .customer-page .data-table tbody td.meta-cell,
    .admin-page .data-table tbody td.meta-cell { line-height: 1.5; }

    /* The delivery rows are the densest thing on the dashboard — three lines
       each, several to a day — so they lose a little height and their status
       pill sits against the product name rather than below it. */
    /* A floor under the list, so the card does not collapse on a day with
       nothing on it. Without it the page becomes shorter than the screen, the
       browser has no scroll left to give back, and the stepper slides up the
       screen however carefully the swap tries to hold it — see the live anchor
       in app.js. Roughly one row plus the empty line. */
    .customer-page #customer-sheet-rows { min-height: 150px; }

    .customer-page .inventory-row { padding: 12px 14px; gap: 4px; }
    .customer-page .sheet-row-main { gap: 3px; }
    .customer-page .sheet-row-head h4 { font-size: 14px; }
    .customer-page .sheet-row-product { font-size: 13px; }
    .customer-page .sheet-row-collect { font-size: 12px; }

    /* ---- Touch targets ---- */

    /* Deliberately under the 44px guideline. Every one of these is a
       well-separated secondary control — Edit, Delete, View — and at 44px they
       dominated cards whose whole job is to be read. Nothing here is a target
       you hit in a hurry. */
    .customer-page .btn-small,
    .admin-page .btn-small { min-height: 36px; padding: 8px 13px; font-size: 12.5px; }
    /* 44px is the smallest thing a thumb hits reliably, so the breakdown button
       gets there even though its drawn circle stays the same size — the extra is
       transparent padding around it. */
    .customer-page .info-btn,
    .admin-page .info-btn {
        width: 34px;
        height: 34px;
        flex: none;
        border-color: transparent;
        background: transparent;
    }
    .customer-page .account-trigger,
    .admin-page .account-trigger { min-height: 44px; }

    /* Safari zooms the whole page when a field smaller than 16px takes focus,
       and never zooms back out. Every input here is 16px for that reason alone. */
    .customer-page input,
    .customer-page select,
    .customer-page textarea,
    .customer-page .select-button,
    .admin-page input,
    .admin-page select,
    .admin-page textarea,
    .admin-page .select-button { font-size: 16px; }

    /* ---- Account menu ---- */

    .customer-page .account-pop,
    .admin-page .account-pop {
        width: min(260px, calc(100vw - 24px));
        right: 0;
    }

    /* ---- Modals become bottom sheets ---- */

    /* A centred box on a phone leaves a form's submit button under the thumb's
       reach and its title above the fold. A sheet rises from the bottom edge,
       so the button lands where the thumb already is. */
    .customer-page .modal-overlay,
    .admin-page .modal-overlay {
        align-items: flex-end;
        padding: 0;
    }

    .customer-page .modal-card,
    .admin-page .modal-card {
        width: 100%;
        max-height: 92vh;
        margin: 0;
        padding: 8px 16px calc(20px + env(safe-area-inset-bottom));
        overflow-y: auto;
        border-radius: var(--radius-xl) var(--radius-xl) 0 0;
        animation: sheet-up 0.24s cubic-bezier(0.16, 1, 0.3, 1);
    }

    /* The grab handle every phone user reads as "this panel came from the
       bottom and goes back there". Drawn rather than marked up, because it
       carries no meaning a screen reader needs. */
    .customer-page .modal-card::before,
    .admin-page .modal-card::before {
        content: '';
        display: block;
        width: 36px;
        height: 4px;
        margin: 4px auto 14px;
        border-radius: var(--radius-full);
        background: var(--line-strong);
    }

    .customer-page .modal-card .modal-actions,
    .admin-page .modal-card .modal-actions { margin-top: 16px; }
    .customer-page .modal-card .btn,
    .admin-page .modal-card .btn { min-height: 46px; }

    @keyframes sheet-up {
        from { transform: translateY(100%); }
        to { transform: none; }
    }
}


/* ============================================================
   MANAGER'S DASHBOARD ON A PHONE
   ============================================================
   The manager plans at a desk, but the round is worked from a phone: the
   delivery sheet is ticked off at the gate, the pickups card is read on a
   doorstep, and the customer who has to be rung is looked up standing in the
   street. That screen was a desktop layout coped with — a hidden drawer, a
   filter bar four rows deep, tables scrolling sideways past the columns that
   matter — so it gets the same treatment the customer's app already has.

   Every rule below hangs off `.admin-page` and lives inside the mobile
   breakpoint. Nothing here applies at 768px and up, so the desktop dashboard
   is exactly as it was; nothing here reaches `.customer-page` either. What the
   two phone layouts genuinely share — tables folding into cards, modals rising
   as sheets, touch sizes — is written once in the section above and names both.
*/
@media (max-width: 767px) {

    /* ---- Header ---- */

    .admin-page .workspace-shell { gap: 0; }

    /* Full-bleed and sticky: the negative margin cancels `.app-shell`'s padding
       so the bar reaches both edges the way an app's header does. Every offset
       of the 280px drawer this replaces has to be given back, or the header
       keeps the drawer's width. */
    .admin-page .workspace-sidebar {
        position: sticky;
        top: 0;
        left: auto;
        bottom: auto;
        width: auto;
        z-index: 50;
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        margin: -12px -12px 16px;
        padding: 10px 14px;
        border: none;
        border-bottom: 1px solid var(--line);
        border-radius: 0;
        box-shadow: var(--shadow-xs);
        transform: none;
        transition: none;
    }

    .admin-page .brand-block {
        flex-direction: row;
        align-items: center;
        position: static;
        flex-shrink: 1;
        min-width: 0;
    }

    .admin-page .brand-block h1 {
        font-size: 17px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* Belonged to the drawer. The number is one tap away in the account menu,
       which is also where it is editable. */
    .admin-page .brand-block .muted { display: none; }

    .admin-page .sidebar-stack {
        flex-direction: row;
        align-items: center;
        gap: 8px;
        margin-top: 0;
        flex-shrink: 0;
    }

    /* ---- Bottom tab bar ---- */

    /* The same <nav>, pinned. Fixed rather than sticky so it never scrolls away
       mid-round, and padded for the home indicator on a notched phone. */
    .admin-page .workspace-nav {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 60;
        flex-direction: row;
        justify-content: space-around;
        gap: 2px;
        padding: 6px 4px;
        padding-bottom: calc(6px + env(safe-area-inset-bottom));
        background: var(--surface);
        border-top: 1px solid var(--line);
        box-shadow: 0 -2px 14px rgb(0 0 0 / 0.06);
    }

    /* The three setup sections. They keep their place in the desktop navbar and
       are reached here through More, which stands in the slot they would have
       had to share five ways. */
    .admin-page .workspace-nav a.nav-overflow { display: none; }

    .admin-page .workspace-nav a,
    .admin-page .nav-more {
        display: flex;
        flex: 1 1 0;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        min-width: 0;
        min-height: 52px;
        padding: 6px 2px;
        border: none;
        border-radius: var(--radius-md);
        background: transparent;
        color: var(--text-secondary);
        font-size: 10px;
        font-weight: 600;
        line-height: 1.2;
        text-align: center;
        cursor: pointer;
    }

    /* Colour alone marks the active tab. A filled pill is right on the desktop
       navbar, where the tabs sit in a row of their own; down here it would be a
       solid block under the thumb on every screen. */
    .admin-page .workspace-nav a.active,
    .admin-page .nav-more.active {
        background: transparent;
        color: var(--primary-ink);
    }

    .admin-page .nav-icon { display: block; }

    .admin-page .workspace-nav a.active .nav-icon,
    .admin-page .nav-more.active .nav-icon { stroke-width: 2.1; }

    .admin-page .nav-label,
    .admin-page .nav-label-short {
        display: block;
        max-width: 100%;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* Where a tab carries a short label it is the only one drawn. The full word
       stays in the markup for the desktop navbar, which has room for it.

       The class is put on the link by the template rather than read off its
       children with `:has`. Everywhere else `:has` fails safe — the customers
       tab simply keeps both panes — but here a browser that did not support it
       would print "Subscriptions" and "Subs" one above the other in a 67px
       tab, and that is not a fallback, it is a fault. */
    .admin-page .workspace-nav a.has-short-label .nav-label { display: none; }

    /* Clears the bar so the last row of a list is never trapped behind it. */
    .admin-page .workspace-main {
        padding-bottom: calc(76px + env(safe-area-inset-bottom));
    }

    /* ---- Cards ---- */

    .admin-page .surface-card { padding: 16px 14px; }
    .admin-page .section-divider { margin: 14px -14px 0; }
    .admin-page .card-head h3 { font-size: 17px; }

    /* A title with its actions beside it stays on one line. Stacking put a
       full-width button under every heading, which pushed the actual content of
       a short list below the fold. */
    .admin-page .card-head-inline {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        min-height: 38px;
    }

    /* A heading with a count beside it — "Containers to collect · 2" — is one
       statement, so the two stay on one line. Stacked, the number was left
       floating on a row of its own with nothing to belong to.

       Matched by structure rather than by a class, so the desktop head keeps
       the top alignment it has: there, the pill is meant to sit against the
       title's first line, not centred on the block. */
    .admin-page .card-head:has(> .pill) {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
    }

    .admin-page .card-head:has(> .pill) h3 { margin-bottom: 0; }

    /* The heading block bleeds to the card's edges, and the card's padding is
       narrower here than the 20px it is on a desktop. */
    .admin-page .list-panel-head {
        margin: 0 -14px;
        padding: 0 14px 12px;
    }

    /* ---- Filter bars ---- */

    /* Four controls stacked full width cost most of a screen before the first
       delivery row was reached. The date and the search each keep a line — a
       stepper with a picker between two buttons, and a field that has to hold a
       name — and the two dropdowns share the one below, because a status and a
       sort order are each two or three words wide.

       Written by element rather than by content: the date is the toolbar's only
       <div>, the search is the label that says so, and whatever labels are left
       are the dropdowns. A toolbar carrying just one of them still fills the
       line, because they grow. */
    .admin-page .sheet-toolbar {
        gap: 10px;
        padding: 12px 0 2px;
    }

    .admin-page .sheet-toolbar > .daily-entry-field { flex: 1 1 100%; }

    .admin-page .sheet-toolbar > label.daily-entry-field:not(.sheet-toolbar-search) {
        flex: 1 1 calc(50% - 5px);
        min-width: 0;
    }

    .admin-page .sheet-toolbar .select-wrap { min-width: 0; }
    .admin-page .sheet-toolbar-search input { min-width: 0; }

    /* The two arrows are the fast path through the week and are hit while
       walking, so they get the full touch size rather than the 34px they take
       beside a desktop cursor. */
    .admin-page .step-btn {
        width: 44px;
        height: 44px;
        flex: none;
    }

    .admin-page .date-stepper input[type="date"] { text-align: center; }

    /* ---- Delivery sheet ---- */

    /* Two totals to a row; an odd one out at the end takes the full width
       rather than sitting half-empty beside a gap. Counted, not assumed — the
       sheet has six tiles now, which pair up evenly. */
    .admin-page .sheet-summary-grid { gap: 8px; }
    .admin-page .sheet-summary-grid .mini-metric { padding: 10px 12px; }
    .admin-page .sheet-summary-grid .mini-metric span { font-size: 11px; line-height: 1.3; }
    .admin-page .sheet-summary-grid strong { margin-top: 2px; font-size: 18px; }
    .admin-page .sheet-summary-grid .mini-metric:last-child:nth-child(odd) { grid-column: 1 / -1; }

    /* The densest thing on the dashboard — three or four lines each, fifty to a
       day — so a row gives up a little height. */
    .admin-page .inventory-row,
    .admin-page .data-row { padding: 12px 14px; gap: 10px; }

    .admin-page .sheet-row-main { gap: 3px; }
    .admin-page .sheet-row-head h4 { font-size: 15px; }

    /* The row's own buttons — details, and "not delivered" — move up next to
       the name, ahead of the pills that say what state the row is in.

       Last in the line they were the first thing to wrap, and an order row
       carrying both a status and a "1/3 delivered" count wraps on a 375px
       screen every time — so every row in the list grew a second line holding
       one loose circle at its left edge. Ahead of the pills the whole head fits
       on one line, and where it still does not, what drops to the next line is
       a pill, which is a thing that reads perfectly well on a line of its own.

       `order` rather than a change to the markup: on a desktop the pills belong
       against the name and the buttons belong after them, and that is the same
       document either way. */
    .admin-page .sheet-row-head > button { order: 1; }
    .admin-page .sheet-row-head > .pill { order: 2; }
    .admin-page .sheet-row-product { font-size: 13px; }
    .admin-page .sheet-row-placed { font-size: 11.5px; }

    /* What is typed at the door. The boxes stay the width of the two-digit
       numbers they hold — a full-width box for "2" reads as a field expecting a
       sentence — and the tick is pushed to the right edge at a fixed size.

       Pinned rather than stretched: a delivery row carries two boxes and a
       pickup row carries one, so a tick that took whatever was left over was a
       different width and in a different place on every row of the list. At the
       edge it is in the same spot fifty times running, which is what makes it
       hittable one-handed, in a hurry, without looking. */
    /* Allowed to wrap, unlike the desktop row, which is set `nowrap` to keep a
       tidy strip beside the text. Two boxes and a tick at a size a thumb can
       hit come to 272px, and the space inside a card on the narrowest phone
       still in use is 240 — so on those the tick drops to a line of its own
       rather than the row running off the side of the screen. */
    .admin-page .daily-entry-form {
        gap: 10px;
        flex-wrap: wrap;
    }

    /* Sized by what they hold, then given whatever the row has left over, up to
       a limit: past about 120px a box holding "2" stops reading as a box for a
       quantity and starts reading as one expecting a sentence. */
    .admin-page .daily-entry-form .daily-entry-field {
        flex: 1 1 84px;
        min-width: 0;
        max-width: 120px;
    }

    .admin-page .daily-entry-form input {
        width: 100%;
        min-width: 0;
        padding: 10px 12px;
    }

    /* A thumb, not a cursor: wider halves on a phone. The search box's clear
       button is the same kind of control, so it grows with them. */
    .admin-page .daily-entry-form .qty-stepper input { padding-right: 44px; }
    .admin-page .qty-steps { width: 36px; }
    .search-input-wrap input[type="search"] { padding-right: 44px; }
    .search-clear { width: 36px; }

    .admin-page .daily-entry-form .btn-icon {
        flex: none;
        min-width: 84px;
        min-height: 46px;
        margin-left: auto;
        font-size: 16px;
    }

    /* Edit, Pause, Terminate — and Cancel, and Mark paid. Sharing the row
       equally keeps them all reachable instead of leaving a 60px "Edit" beside
       a 110px "Terminate". */
    .admin-page .daily-entry-actions { gap: 8px; }

    .admin-page .daily-entry-actions > .btn,
    .admin-page .daily-entry-actions > .inline-form { flex: 1 1 auto; }
    .admin-page .daily-entry-actions > .inline-form .btn { width: 100%; }

    /* ---- Customers: the list, then one customer ---- */

    /* Two panes side by side is a desktop shape. Stacked, the whole directory
       sat above the customer that had just been picked, so the tap appeared to
       do nothing until the page was scrolled past every other name.

       So the phone shows one or the other: the list, or — once a customer is
       open, which is exactly when the detail pane has a card in it — that
       customer, with a way back at the top. `:has` rather than a class on the
       wrapper, because picking a name swaps the two panes in place and never
       re-renders what holds them. */
    .admin-page .customer-admin-layout:has(> .detail-stack > .surface-card):not(:has(> .detail-stack [data-auto-open])) > .surface-card { display: none; }

    /* Except on a first visit, when the customer in that pane is not one
       anybody picked: the server opens the first name so a desktop does not
       start with half a blank screen, and on a phone that meant the Customers
       tab opened on a stranger's card with the directory hidden behind it. The
       marker is written into the detail pane by the view; the moment a name is
       actually tapped the pane is swapped for one without it, and the two rules
       change places. */
    .admin-page .customer-admin-layout:has(> .detail-stack [data-auto-open]) > .detail-stack { display: none; }

    .admin-page .detail-back {
        display: inline-flex;
        align-items: center;
        gap: 4px;
        align-self: flex-start;
        min-height: 40px;
        margin: -4px 0 -4px -8px;
        padding: 8px;
        color: var(--primary-ink);
        font-size: 14px;
        font-weight: 600;
        text-decoration: none;
    }

    /* On the directory card the containers chip gives way first. Both it and
       the line it shares are set not to shrink, so the browser took the width
       out of the one that could clip — and a blocked customer's row read
       "+91987654325 · Blo…", which is the one thing on that card nobody can
       afford to have cut off. The chip loses a product name instead, and the
       count in front of it survives either way. */
    .admin-page .customer-card-held {
        min-width: 0;
        flex-shrink: 1;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    .admin-page .customer-card-line p { flex-shrink: 0; }

    /* The customer's four figures keep their two-by-two block, a size down. */
    .admin-page .metric-row { gap: 8px; }
    .admin-page .metric-row .mini-metric { padding: 10px 12px; }
    .admin-page .metric-row .mini-metric span { font-size: 11px; line-height: 1.3; }
    .admin-page .metric-row .mini-metric strong { margin-top: 2px; font-size: 17px; }

    /* The name, then what can be done about the customer. Six controls: the
       deposit, and five circles — photo, call, notifications, edit, block.

       The deposit takes a line of its own and the circles take the next. Left
       to wrap on their own the six came out four-and-two, so the row ended in
       two circles hanging under a gap with nothing to line up against. Split
       deliberately it is a labelled figure over an even row of icons, and the
       circles are the size a thumb wants rather than whatever the line had
       left. */
    .admin-page .customer-admin-layout .card-head { gap: 10px; }
    .admin-page .customer-admin-layout .card-head .data-pills { gap: 8px; }
    .admin-page .customer-admin-layout .card-head .data-pills > .btn:not(.btn-icon) { flex: 1 1 100%; }

    .admin-page .customer-admin-layout .card-head .data-pills > .btn-icon {
        flex: 1 1 0;
        min-width: 44px;
        min-height: 40px;
    }

    /* The picker shares the card's heading line; at full width it pushed
       "Delivery and Returns" onto two lines and then took one of its own. */
    .admin-page .card-head-inline .month-picker,
    .admin-page .card-head-inline .month-picker-button { width: auto; }
    .admin-page .card-head-inline .month-picker-pop { left: auto; right: 0; }

    /* Title, Print and the month do not fit one 375px line with Print spelled
       out; the printer glyph carries it, and the label stays as its tooltip
       and accessible name. */
    .admin-page .delivery-print-link span { display: none; }
    .admin-page .delivery-print-link { padding-left: 11px; padding-right: 11px; }

    /* ---- Delivery and returns, as cards ----
       The product leads, with the day and the source folded under it and the
       money at the end of that line — the shape every other card in this app
       uses. Then the three quantities as a row of equals, because that is the
       row the card is opened for: what was promised, what went out, and how
       many empties came back. The two states close it. */
    .admin-page .table-admin-deliveries tbody tr {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr 1fr;
        align-items: center;
        gap: 4px 10px;
    }

    .admin-page .table-admin-deliveries tbody td.is-row-title { grid-area: 1 / 1 / 2 / 4; }

    .admin-page .table-admin-deliveries tbody td.total-cell {
        grid-area: 1 / 3 / 2 / 4;
        justify-self: end;
        align-self: start;
        /* Set against the product's own line, which sits a shade below the top
           of the cell it shares with the subtitle under it. */
        margin-top: 1px;
        font-size: 16px;
        font-weight: 700;
        font-variant-numeric: tabular-nums;
    }

    .admin-page .table-admin-deliveries tbody td:nth-child(4) { grid-area: 2 / 1 / 3 / 2; }
    .admin-page .table-admin-deliveries tbody td:nth-child(5) { grid-area: 2 / 2 / 3 / 3; }
    .admin-page .table-admin-deliveries tbody td:nth-child(6) { grid-area: 2 / 3 / 3 / 4; }
    .admin-page .table-admin-deliveries tbody td:nth-child(7) { grid-area: 2 / 4 / 3 / 5; }

    .admin-page .table-admin-deliveries tbody td.delivery-billing { grid-area: 3 / 1 / 4 / 3; margin-top: 4px; }
    .admin-page .table-admin-deliveries tbody td.delivery-status { grid-area: 3 / 3 / 4 / 5; justify-self: end; margin-top: 4px; }

    /* The month picker's empty row is one sentence, not a card of fields. */
    .admin-page .table-admin-deliveries tbody tr[data-month-empty] { display: block; }

    /* ---- Bills, as cards ----
       The seven columns fold exactly as the customer's own bills do — see the
       shared rules above — with one difference: the customer gets a View
       button and the manager gets Print, Edit and Delete. Three buttons on a
       line that also has to hold Paid and Due is not a row, it is a squeeze,
       and it dragged the bill number down to one word a line. So they take a
       line of their own under the figures. */
    .admin-page .table-bills tbody td:nth-child(7) {
        grid-area: 4 / 1 / 5 / 4;
        justify-self: end;
        margin-top: 6px;
    }

    /* ---- Payments, as cards ----
       Laid out as the customer's own statement lays them out — amount against
       date, mode against reference — with the manager's two buttons on a line
       of their own underneath, which is the only thing the two screens differ
       by here. */
    .admin-page .table-payments tbody td.col-actions {
        grid-area: 3 / 1 / 4 / 3;
        justify-self: end;
    }

    .admin-page .table-payments tbody td.col-actions .row-action-locked {
        font-size: 12px;
        color: var(--text-secondary);
    }

    /* ---- Stock ---- */

    .admin-page .stock-list { gap: 10px; }
    .admin-page .stock-card { padding: 14px; gap: 10px; }
    .admin-page .stock-card-head { align-items: center; }
    .admin-page .stock-legend { gap: 6px 16px; }
}


/* ---- HINDI TYPOGRAPHY ----

   Devanagari is not Latin at a different size. Three things had to change for
   a Hindi page to read as cleanly as the English one:

   1. Size. Noto Sans Devanagari's letterforms sit lower and carry marks above
      and below, so at Inter's 14px the body was noticeably harder to read than
      the same page in English. Everything scales up ~8% via a root font-size
      bump on the rem-based sizes plus an explicit body size.
   2. Leading. Matras hang above the shirorekha and below the baseline. At
      line-height 1.6 they were nearly touching the neighbouring lines; 1.85
      gives them the room the script assumes.
   3. Letter-spacing. Tracking pulls apart conjuncts and detaches the
      shirorekha, which is what makes spaced Devanagari look broken rather than
      airy. Every tracked label — the uppercase small-caps ones especially,
      where text-transform does nothing at all in this script — is reset to
      normal spacing.

   These are scoped to html[lang="hi"], which the layout now sets from the
   session language, so the English page is untouched. */

html[lang="hi"] {
    --font: var(--font-hi);
}

html[lang="hi"] body {
    font-size: 15px;
    line-height: 1.85;
}

html[lang="hi"] h1,
html[lang="hi"] h2,
html[lang="hi"] h3,
html[lang="hi"] h4,
html[lang="hi"] h5,
html[lang="hi"] h6 {
    line-height: 1.5;
}

/* Nothing in Hindi should be tracked out or uppercased. */
html[lang="hi"] * {
    letter-spacing: normal;
    text-transform: none;
}

/* The smallest labels — pills, table captions, stat legends — were the worst
   affected, because 10-12px Devanagari with marks is at the edge of legible on
   a phone. Lift the floor to 12px and give them room. */
html[lang="hi"] .badge,
html[lang="hi"] .pill,
html[lang="hi"] .chip,
html[lang="hi"] .muted,
html[lang="hi"] .data-table th,
html[lang="hi"] .data-table td.muted,
html[lang="hi"] .choice-hint,
html[lang="hi"] small {
    font-size: 12px;
    line-height: 1.6;
}

/* Buttons and inputs grow with the body text, and need the extra height so a
   descending matra is not clipped by the control's own padding. */
html[lang="hi"] .btn,
html[lang="hi"] button,
html[lang="hi"] input,
html[lang="hi"] select,
html[lang="hi"] textarea {
    line-height: 1.6;
}

/* Devanagari sits taller than Latin at the same font size, so the small button
   needs the extra height to keep its matras off the border. Not an
   inconsistency with the 36px above it — the same optical height, in a script
   that measures differently. */
html[lang="hi"] .btn-small {
    min-height: 40px;
}

/* ============================================================
   ERROR PAGE (404)
   ============================================================ */

/* Centred in the viewport rather than sitting under the shell's top padding:
   there is no navigation on this page and nothing below the fold, so anything
   other than the middle of the screen reads as a page that failed to load
   rather than one that is telling you something. */
.error-page {
    display: grid;
    place-items: center;
    /* `--shell-pad-y` is already the shell's top and bottom padding added
       together — the sign-in page sizes itself the same way — so it is
       subtracted once. Doubling it left the card sitting visibly above centre. */
    min-height: calc(100dvh - var(--shell-pad-y, 32px));
    padding: 24px 0;
    text-align: center;
}

.error-card {
    width: min(520px, 100%);
    padding: 40px 32px 32px;
    border: 1px solid var(--line);
    border-radius: var(--radius-xl);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
}

.error-art {
    width: 168px;
    margin: 0 auto 20px;
}

.error-art svg { display: block; width: 100%; height: auto; }

.error-drop {
    /* Drawn from the tip down, so the drop reads as falling into place. */
    transform-origin: 100px 104px;
    animation: error-drop-in 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Two rings leaving the drop, a beat apart — the surface it landed on. */
.error-ripple {
    fill: none;
    stroke: var(--primary);
    stroke-width: 2;
    opacity: 0;
    transform-origin: 100px 104px;
    animation: error-ripple 2.8s ease-out infinite;
}

.error-ripple-2 { animation-delay: 1.4s; }

.error-code-mark {
    font-size: 40px;
    font-weight: 800;
    letter-spacing: 1px;
    fill: var(--on-primary);
    fill-opacity: 0.92;
}

.error-eyebrow {
    margin-bottom: 6px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: var(--primary-ink);
}

.error-title {
    margin-bottom: 10px;
    font-size: 26px;
    line-height: 1.2;
}

.error-lead {
    margin: 0 auto;
    max-width: 42ch;
    color: var(--text-secondary);
    line-height: 1.6;
}

.error-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: center;
    margin-top: 24px;
}

.error-actions .btn { min-width: 150px; }

.error-help {
    margin-top: 22px;
    padding-top: 18px;
    border-top: 1px solid var(--line);
    font-size: 13px;
    color: var(--text-secondary);
}

.error-help a { color: var(--primary-ink); font-weight: 600; }

@keyframes error-drop-in {
    from { opacity: 0; transform: translateY(-18px) scale(0.92); }
    to   { opacity: 1; transform: none; }
}

@keyframes error-ripple {
    0%   { opacity: 0.5; transform: scale(0.55); }
    70%  { opacity: 0; transform: scale(1.25); }
    100% { opacity: 0; transform: scale(1.25); }
}

@media (max-width: 480px) {
    .error-card { padding: 32px 20px 24px; }
    .error-art { width: 132px; }
    .error-title { font-size: 22px; }
    .error-actions .btn { width: 100%; }
}

/* The drop and its ripples are decoration; nobody needs them to read the page. */
@media (prefers-reduced-motion: reduce) {
    .error-drop, .error-ripple { animation: none; }
    .error-ripple { opacity: 0.25; }
}

/* ============================================================
   ANALYTICS
   ============================================================
   The month read back as a business. Everything here is either a
   figure or the context that stops a figure being misread, so the
   palette is narrower than the rest of the app on purpose: teal for
   what was kept, slate for what it cost, and the print-ink red only
   for an actual loss. Red is not used for "down" on its own — a
   month where costs fell is not an alarm.
   ============================================================ */

/* ---- Product form: the making-cost field ---- */

/* "optional", "per unit" — the qualifier that belongs to the label but must not
   compete with it. Set back to normal weight inside a 600-weight label so the
   field is still found by its name. */
.label-hint {
    font-weight: 500;
    font-size: 11px;
    color: var(--text-secondary);
}

/* The sentence under an input that says what the number will do. Labels are
   `display: grid`, so this lands under the field on its own row with the
   label's own 6px gap. */
.field-note {
    font-size: 12px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--text-secondary);
}

.field-note strong { font-weight: 700; color: var(--text); }

/* ---- Month picker ---- */

/* Sits in the card head opposite the title, at the width of the longest month
   name rather than stretched across the card — it is one control, not a
   toolbar. */
.analytics-month-form {
    display: flex;
    align-items: flex-end;
    gap: 8px;
}

.analytics-month-form .daily-entry-field { min-width: 180px; }

/* ---- Headline tiles ---- */

/* Two up on a phone, four in a line once there is room. Four across a 375px
   screen would set a rupee figure at a size that has to be squinted at, which
   defeats the point of a headline. */
.analytics-headline {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

@media (min-width: 900px) {
    .analytics-headline { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.analytics-tile { padding: 14px 16px; }

/* Two lines, always: the label, then the figure with its chip beside it.
   The chip sits *next to* the number rather than under it because a grid row is
   as tall as its tallest cell — one tile carrying a second line used to add a
   band of empty space to every tile beside it. Same trap, same fix, as
   `.mini-metric-aside` further up this file.
   `minmax(0, 1fr)` on the chip column is what lets it truncate instead of
   pushing the figure out of the tile. */
.analytics-tile .metric-tile-main {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "label label"
        "value chip";
    align-items: baseline;
    column-gap: 8px;
    min-width: 0;
    flex: 1;
}

.analytics-tile .metric-tile-main > span { grid-area: label; }
.analytics-tile .metric-tile-main > strong { grid-area: value; }

.analytics-tile strong {
    display: block;
    margin-top: 6px;
    font-size: 22px;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.analytics-tile .metric-note {
    grid-area: chip;
    justify-self: start;
    max-width: 100%;
    margin: 0;
    overflow: hidden;
}

/* Centred on the tile, which is only honest now that every tile in a row is
   the same height. While one could grow a third line, centring left one (i)
   beside its figure and the next one beside white space. */
.analytics-tile .info-btn { flex: none; }

/* On the mint hero tile a white button would read as a hole punched in it. */
.analytics-tile-hero .info-btn {
    border-color: transparent;
    background: rgb(255 255 255 / 0.65);
    color: var(--primary-dark);
}

.analytics-tile-hero .info-btn:hover { background: var(--surface); border-color: var(--primary); }

/* Profit is the number the page exists to produce, so it is the one tile that
   is allowed to look different from its neighbours — a tint and a left edge,
   not a bigger font, which would break the line the four figures read along. */
.analytics-tile-hero {
    background: var(--primary-light);
    border-color: transparent;
    box-shadow: inset 3px 0 0 var(--primary);
}

.analytics-tile-hero span { color: var(--primary-dark); }

.figure-profit { color: var(--primary-dark); }
.figure-loss { color: var(--money-billed); }
.figure-cost { color: var(--money-unbilled); }

/* The note under each figure: a change pill and what it is measured against.
   `.metric-note` truncates to one line everywhere else on the dashboard, where
   it sits beside a figure on a shared row. Here it is a line of its own under
   the figure and has to wrap — a note reading "money that arrived this month,
   for" is worse than no note at all. */
.analytics-tile .metric-note {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: 8px 0 0;
    overflow: visible;
    font-size: 11px;
    font-weight: 500;
    line-height: 1.45;
    white-space: normal;
    color: var(--text-secondary);
}

/* ---- Margin chip ---- */

/* The one chip left on the headline row. It carries a derived figure — profit
   over revenue — not a comparison against another period, so it is a single
   neutral shape with no arrow and no red or green. The change pills that used
   to live here were removed along with the month-over-month figures: while a
   month is still running they measured a part-month against a whole one and
   pointed down for three weeks out of four, which is a fact about the calendar
   rather than about the business. */
/* Scoped through `.analytics-tile` on purpose: the tile is a `.mini-metric`,
   and `.mini-metric span` is one class more specific than a bare `.analytics-chip`
   — it was quietly winning the font-size and the colour. */
.analytics-tile .analytics-chip {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    /* `line-height: 1` with this padding keeps the whole chip inside the line
       box the 22px figure beside it already occupies, so the chip adds nothing
       at all to the height of the row. Anything looser and every tile in the
       row grows by a pixel or two to accommodate one chip's descender. */
    padding: 3px 8px;
    border-radius: var(--radius-full);
    background: var(--surface);
    box-shadow: inset 0 0 0 1px var(--line);
    font-size: 11px;
    line-height: 1;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--primary-dark);
}

/* ---- Volume strip ---- */

/* Units and containers. The same tile component as the headline row — they
   carry the same (i) and open the same kind of breakdown — but set quieter:
   these are context for the money above, not a second set of headlines, and at
   the same weight the page reads as seven equal figures with no answer in it.
   Three across once there is room; two up on a phone with the odd one taking
   the full row rather than leaving a hole beside it. */
.analytics-subline {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

@media (min-width: 760px) {
    .analytics-subline { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.analytics-subline > .analytics-tile:last-child:nth-child(odd) { grid-column: 1 / -1; }

@media (min-width: 760px) {
    .analytics-subline > .analytics-tile:last-child:nth-child(odd) { grid-column: auto; }
}

.analytics-subline .analytics-tile { background: var(--surface-alt); }
.analytics-subline .analytics-tile strong { font-size: 18px; }

/* ---- Uncosted-products notice ---- */

/* Amber rather than red. Nothing is broken — a product has simply not been
   costed yet — but the profit figure above is overstated until it is, and that
   is worth interrupting for. */
.analytics-notice {
    padding: 14px 16px;
    border: 1px solid var(--warning);
    border-left-width: 3px;
    border-radius: var(--radius-md);
    background: var(--warning-light);
}

.analytics-notice strong { display: block; font-size: 13px; color: var(--text); }
.analytics-notice p { margin: 5px 0 10px; font-size: 12.5px; line-height: 1.6; color: var(--text-secondary); }
.analytics-notice p strong { display: inline; color: var(--text); }

/* ---- Day-by-day chart ---- */

/* The SVG keeps its drawn width and the wrapper scrolls, rather than the bars
   being squeezed to fit a phone: thirty bars in 340px is a texture, not a
   chart. */
.chart-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
}

/* Scales to the card it is in rather than being pinned to its drawn width: a
   thirteen-day month would otherwise leave two thirds of the card empty. The
   viewBox is drawn a little wider than a desktop card so it scales down
   slightly rather than up, which keeps the axis labels at their intended size.
   The floor is what makes the wrapper scroll on a phone instead of squeezing a
   month into 340px. */
.analytics-chart {
    display: block;
    width: 100%;
    min-width: 520px;
    height: auto;
}

.chart-grid { stroke: var(--line); stroke-width: 1; }
.chart-axis { stroke: var(--line-strong); }
.chart-axis-label,
.chart-day-label {
    font-size: 10px;
    font-family: var(--font);
    fill: var(--text-secondary);
}

.chart-day-label.is-future { opacity: 0.45; }

.chart-bar-revenue { fill: var(--primary); }
.chart-bar-cost { fill: #64748b; }

/* A day that cost more than it earned: the whole bar goes red rather than the
   cost segment overflowing the top of the bar it sits inside. */
.chart-bar.is-loss .chart-bar-revenue,
.chart-bar.is-loss .chart-bar-cost { fill: var(--money-billed); }

.chart-bar { transition: opacity var(--transition); }
.chart-bar:hover { opacity: 0.75; }

.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 18px;
    margin-top: 14px;
    font-size: 11.5px;
    color: var(--text-secondary);
}

.chart-key { display: inline-flex; align-items: center; gap: 6px; }

.chart-swatch {
    width: 10px;
    height: 10px;
    border-radius: 3px;
}

.swatch-kept { background: var(--primary); }
.swatch-cost { background: #64748b; }
.swatch-loss { background: var(--money-billed); }

/* ---- By-product table ---- */

.analytics-table { min-width: 680px; }

/* The qualifier under a cell's main value — the product's type, or the margin
   under its profit. */
.analytics-table .table-sub {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    font-weight: 500;
    color: var(--text-secondary);
}

/* A margin worked out from only part of the month, because the cost was filled
   in partway through it. The figure is real; it just does not describe the
   whole column beside it, and saying so is cheaper than leaving it to be found
   out later. */
.analytics-table .table-sub.is-partial { color: var(--warning); }

.analytics-table tfoot th {
    padding: 12px;
    font-size: 13px;
    font-weight: 700;
    text-align: left;
    border-top: 2px solid var(--line-strong);
    white-space: nowrap;
}

.analytics-table tfoot th.numeric {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* ---- Collections and top customers ---- */

.analytics-split {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
}

@media (min-width: 960px) {
    .analytics-split { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    /* A month with no payments leaves one card here. Half a row with the
       other half empty reads as a card that failed to load, so it takes the
       whole row, and its list runs in two columns to use the width. */
    .analytics-split > :only-child { grid-column: 1 / -1; }

    .analytics-split > :only-child .rank-list {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        column-gap: 32px;
    }

    .analytics-split > :only-child .rank-row + .rank-row { border-top: 0; }
    .analytics-split > :only-child .rank-row { border-bottom: 1px solid var(--line); }
}

/* Same override as the tile notes above: this is a sentence, so it wraps. */
.analytics-gap {
    margin: 14px 0 0;
    padding-top: 12px;
    border-top: 1px dashed var(--line);
    overflow: visible;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.6;
    white-space: normal;
    color: var(--text-secondary);
}

/* ---- Tile label and icon ---- */

.analytics-tile .analytics-tile-label {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
}

.analytics-tile-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: var(--radius-sm);
    background: rgb(var(--primary-rgb) / 0.1);
    color: var(--primary-ink);
}

.analytics-tile-hero .analytics-tile-icon {
    background: var(--primary);
    color: var(--on-primary);
}

.analytics-subline .analytics-tile-icon {
    background: var(--surface);
    box-shadow: inset 0 0 0 1px var(--line);
    color: var(--text-secondary);
}

.analytics-headline .analytics-tile { padding: 16px 18px; }
.analytics-headline .analytics-tile strong { margin-top: 10px; font-size: 24px; letter-spacing: -0.01em; }

/* ---- Where the revenue went ---- */

/* One bar, split by share: making cost, profit, and — hatched amber, so it
   reads as a gap rather than a category — revenue with no cost set. The same
   colours as the day chart below, so the two are read with one key. */
.analytics-splitbar {
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

.splitbar-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 4px 12px;
    margin-bottom: 10px;
}

.splitbar-head strong { font-size: 13px; color: var(--text); }

.splitbar-flag {
    font-size: 12px;
    font-weight: 600;
    color: var(--money-billed);
}

.splitbar-track {
    display: flex;
    gap: 2px;
    height: 12px;
    overflow: hidden;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
}

.splitbar-seg { display: block; height: 100%; flex: none; }
.splitbar-seg:first-child { border-radius: var(--radius-full) 0 0 var(--radius-full); }
.splitbar-seg:last-child { border-radius: 0 var(--radius-full) var(--radius-full) 0; }
.splitbar-seg:only-child { border-radius: var(--radius-full); }

.seg-cost { background: #64748b; }
.seg-cost.is-loss { background: var(--money-billed); }
.seg-kept { background: var(--primary); }
.seg-uncosted,
.swatch-uncosted {
    background: repeating-linear-gradient(135deg, var(--warning) 0 3px, var(--warning-light) 3px 6px);
}

.splitbar-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 20px;
    margin-top: 10px;
    font-size: 12px;
    color: var(--text-secondary);
}

.splitbar-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.splitbar-legend strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* The payment mix reuses the track, shaded from the brand colour. */
.method-track .splitbar-seg { background: rgb(var(--primary-rgb) / var(--shade, 1)); }

.method-label { display: inline-flex; align-items: center; gap: 8px; }

.method-dot {
    flex: none;
    width: 10px;
    height: 10px;
    border-radius: 3px;
    background: rgb(var(--primary-rgb) / var(--shade, 1));
}

/* ---- Chart head, average line, tooltip ---- */

.analytics-chart-head { align-items: flex-start; gap: 16px; }

.chart-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
}

.chart-stats > div {
    min-width: 110px;
    padding: 8px 12px;
    border-radius: var(--radius-md);
    background: var(--surface-alt);
    box-shadow: inset 0 0 0 1px var(--line);
}

.chart-stats dt {
    font-size: 11px;
    font-weight: 500;
    color: var(--text-secondary);
    white-space: nowrap;
}

.chart-stats dd {
    margin: 2px 0 0;
    font-size: 15px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

.chart-average {
    stroke: var(--warning);
    stroke-width: 1.25;
    stroke-dasharray: 5 4;
    pointer-events: none;
}

.chart-average-label {
    font-size: 10px;
    font-weight: 700;
    font-family: var(--font);
    fill: var(--warning);
    paint-order: stroke;
    stroke: var(--surface);
    stroke-width: 3px;
    pointer-events: none;
}

.swatch-average {
    height: 0;
    border-top: 2px dashed var(--warning);
    border-radius: 0;
    width: 14px;
}

.chart-hit { fill: transparent; }
.chart-bar:hover .chart-hit { fill: rgb(var(--primary-rgb) / 0.06); }
.chart-bar:hover { opacity: 1; }
.chart-bar:hover .chart-bar-revenue { fill: var(--primary-hover); }
.chart-bar.is-loss:hover .chart-bar-revenue { fill: var(--money-billed); }

.chart-scroll[data-chart-tips] { position: relative; }

.chart-tip {
    position: absolute;
    z-index: 5;
    min-width: 150px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    font-size: 12px;
    pointer-events: none;
    transform: translate(-50%, calc(-100% - 10px));
}

.chart-tip[hidden] { display: none; }
.chart-tip-date { display: block; margin-bottom: 6px; font-weight: 700; color: var(--text); }

.chart-tip-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    color: var(--text-secondary);
}

.chart-tip-row + .chart-tip-row { margin-top: 3px; }
.chart-tip-row strong { color: var(--text); font-variant-numeric: tabular-nums; }
.chart-tip-row > span { display: inline-flex; align-items: center; gap: 6px; }

/* ---- Share meters, margin pills, ranked customers ---- */

.share-meter {
    display: block;
    height: 4px;
    margin-top: 6px;
    overflow: hidden;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
}

.share-meter > i {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--primary);
}

.analytics-table td.numeric .share-meter { width: 84px; margin-left: auto; }

.margin-pill {
    display: inline-block;
    padding: 2px 7px;
    border-radius: var(--radius-full);
    background: rgb(var(--primary-rgb) / 0.1);
    font-size: 10.5px;
    font-weight: 700;
    color: var(--primary-dark);
}

.margin-pill.is-partial { background: var(--warning-light); color: var(--warning); }
.margin-pill.is-loss { background: var(--money-billed-bg); color: var(--money-billed); }

.rank-list { margin: 0; padding: 0; list-style: none; }

.rank-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 11px 0;
}

.rank-row + .rank-row { border-top: 1px solid var(--line); }

.rank-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 26px;
    height: 26px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    font-size: 12px;
    font-weight: 700;
    color: var(--text-secondary);
}

.rank-row:first-child .rank-badge { background: var(--primary); color: var(--on-primary); }

.rank-body { flex: 1; min-width: 0; }

.rank-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: 13px;
}

.rank-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    color: var(--text);
}

.rank-line strong { font-variant-numeric: tabular-nums; }

.rank-sub {
    display: block;
    margin-top: 4px;
    font-size: 11.5px;
    color: var(--text-secondary);
}

/* ============================================================
   NOTIFICATIONS
   ============================================================
   See app/views/admin/partials/notifications.php. Each message is
   drawn as the customer will read it — a bubble, line by line —
   because this screen is where the manager checks them before
   pressing Release all. */

.notif-head { align-items: flex-start; gap: 16px; }
.notif-head p { max-width: 560px; }

.notif-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    flex: none;
}

.notif-release { display: inline-flex; align-items: center; gap: 8px; }

.notif-release-count {
    min-width: 22px;
    padding: 2px 7px;
    border-radius: var(--radius-full);
    background: rgb(255 255 255 / 0.22);
    font-size: 12px;
    font-weight: 700;
    text-align: center;
    color: var(--on-primary);
}

/* ---- Filter chips ---- */

.notif-filters {
    display: flex;
    gap: 8px;
    margin: 18px 0 4px;
    overflow-x: auto;
    padding-bottom: 2px;
    scrollbar-width: none;
}

.notif-filters::-webkit-scrollbar { display: none; }

.notif-filter {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: none;
    padding: 7px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    text-decoration: none;
    transition: border-color var(--transition), background var(--transition);
}

.notif-filter:hover { border-color: var(--line-strong); color: var(--text); }

.notif-filter > span {
    min-width: 20px;
    padding: 1px 6px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    font-size: 11px;
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.notif-filter.is-active {
    border-color: var(--primary);
    background: rgb(var(--primary-rgb) / 0.08);
    color: var(--primary-ink);
}

.notif-filter.is-active > span { background: var(--primary); color: var(--on-primary); }
.notif-filter.is-failed:not(.is-active) > span { background: var(--money-billed-bg); color: var(--money-billed); }

/* ---- List ---- */

.notif-list { display: flex; flex-direction: column; gap: 10px; }

.notif-day {
    margin: 18px 0 2px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.notif-item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 14px;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
}

/* The ones waiting on the manager carry the brand edge; everything else is
   history and sits back. */
.notif-item.is-held { box-shadow: inset 3px 0 0 var(--primary); }
.notif-item.is-failed { box-shadow: inset 3px 0 0 var(--money-billed); }
.notif-item.is-sent,
.notif-item.is-skipped { background: var(--surface-alt); }

.notif-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-full);
    background: rgb(var(--primary-rgb) / 0.12);
    font-size: 14px;
    font-weight: 700;
    color: var(--primary-ink);
}

.notif-body { min-width: 0; }

.notif-top {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 10px;
}

.notif-name { font-size: 14px; color: var(--text); }

.notif-event {
    padding: 2px 8px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    font-size: 11px;
    font-weight: 600;
    color: var(--text-secondary);
}

/* The message, the way it will arrive. */
.notif-bubble {
    max-width: 440px;
    margin-top: 10px;
    padding: 10px 14px;
    border-radius: 4px var(--radius-lg) var(--radius-lg) var(--radius-lg);
    background: var(--surface-alt);
    box-shadow: inset 0 0 0 1px var(--line);
    font-size: 13px;
    line-height: 1.5;
    color: var(--text);
}

.notif-item.is-sent .notif-bubble,
.notif-item.is-skipped .notif-bubble { background: var(--surface); }

.notif-bubble-title { margin-bottom: 4px; font-weight: 600; }
.notif-bubble p { margin: 0; }
.notif-bubble p + p { margin-top: 4px; }

.notif-pair {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    padding: 3px 0;
}

.notif-pair + .notif-pair { border-top: 1px dashed var(--line); }
.notif-pair > span { color: var(--text-secondary); }
.notif-pair > strong { font-variant-numeric: tabular-nums; white-space: nowrap; }

.notif-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin-top: 8px;
    font-size: 12px;
    color: var(--text-secondary);
}

.notif-error {
    margin: 8px 0 0;
    font-size: 12px;
    color: var(--text-secondary);
}

.notif-error.is-failed { font-weight: 600; color: var(--money-billed); }

.notif-side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: space-between;
    gap: 10px;
}

.notif-status {
    padding: 3px 10px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    font-size: 11px;
    font-weight: 700;
    white-space: nowrap;
    color: var(--text-secondary);
}

.notif-status.is-held { background: rgb(var(--primary-rgb) / 0.12); color: var(--primary-dark); }
.notif-status.is-queued,
.notif-status.is-pending { background: var(--info-light); color: var(--info); }
.notif-status.is-sent { background: var(--success-light); color: var(--success); }
.notif-status.is-failed { background: var(--money-billed-bg); color: var(--money-billed); }

.notif-buttons { display: flex; align-items: center; gap: 6px; }
.notif-buttons form { margin: 0; }

.notif-discard {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    color: var(--text-secondary);
    cursor: pointer;
}

.notif-discard:hover { border-color: var(--money-billed-line); color: var(--money-billed); }

/* ---- Pager ---- */

.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px 16px;
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

.pager-summary {
    font-size: 12.5px;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.pager-links { display: flex; align-items: center; gap: 4px; }

.pager-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 36px;
    padding: 0 10px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    text-decoration: none;
    font-variant-numeric: tabular-nums;
}

.pager-link:hover { border-color: var(--line-strong); color: var(--text); }

.pager-link.is-active {
    border-color: var(--primary);
    background: var(--primary);
    color: var(--on-primary);
}

.pager-gap { padding: 0 4px; color: var(--text-secondary); }

@media (max-width: 767px) {
    .pager { justify-content: center; }
    .admin-page .notif-head { flex-direction: column; align-items: stretch; }
    .notif-actions { width: 100%; }
    .notif-actions form:last-child { flex: 1; }
    .notif-release { width: 100%; justify-content: center; }

    /* Avatar and body on the first row; status and buttons along the bottom. */
    .notif-item {
        grid-template-columns: auto minmax(0, 1fr);
        gap: 12px;
        padding: 12px;
    }

    .notif-side {
        grid-column: 1 / -1;
        flex-direction: row;
        align-items: center;
        padding-top: 10px;
        border-top: 1px solid var(--line);
    }

    .notif-bubble { max-width: none; }
}

/* ---- Phone ---- */

@media (max-width: 767px) {
    /* Stats go under the title, three abreast, the same move the month
       picker makes above. */
    .admin-page .analytics-chart-head {
        flex-direction: column;
        align-items: stretch;
        gap: 12px;
    }

    .chart-stats { width: 100%; flex-wrap: nowrap; }
    .chart-stats > div { flex: 1; min-width: 0; padding: 8px 10px; }
    .chart-stats dt { overflow: hidden; text-overflow: ellipsis; }

    /* Half a phone is too narrow for an icon, a label and an (i) on one line;
       the icon is the one that can go without losing anything. */
    .analytics-tile-icon { display: none; }
    .analytics-headline .analytics-tile { padding: 14px; }
    .analytics-headline .analytics-tile strong { font-size: 19px; }

    /* And too narrow for a figure with a chip beside it — "64.4% margin" was
       cut to "6". The chip takes its own line under the figure here instead;
       both tiles in that row carry one, so the row still lines up. */
    .analytics-tile .metric-tile-main {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas: "label" "value" "chip";
    }

    .analytics-tile .metric-note { margin-top: 6px; }

    /* The dozen pixels "64.4% margin" needs beside the (i) on a 375px phone. */
    .analytics-headline .analytics-tile { padding: 14px 12px; gap: 6px; }
    .analytics-tile .analytics-chip { padding: 3px 6px; }

    /* This table is opted out of the card-per-row treatment the rest of the
       dashboard's tables get on a phone. Those tables are lists of records, and
       a record reads perfectly well as a card. This one is a comparison: the
       whole reason it exists is reading Profit down the column and finding the
       product carrying the month, which cards make impossible. So it keeps its
       header row and scrolls sideways instead. */
    .admin-page .analytics-scroll { overflow-x: auto; }
    .admin-page .analytics-table { min-width: 680px; }
    .admin-page .analytics-table thead { display: table-header-group; }
    .admin-page .analytics-table,
    .admin-page .analytics-table tbody { display: table; }
    .admin-page .analytics-table tbody { display: table-row-group; }

    .admin-page .analytics-table tbody tr {
        display: table-row;
        gap: 0;
        padding: 0;
        border: none;
        border-radius: 0;
        background: transparent;
    }

    .admin-page .analytics-table tbody tr + tr { margin-top: 0; }

    .admin-page .analytics-table tbody td {
        display: table-cell;
        width: auto;
        padding: 12px;
        border-bottom: 1px solid var(--line);
        white-space: nowrap;
        vertical-align: middle;
    }

    .admin-page .analytics-table tbody td.numeric { text-align: right; }
    .admin-page .analytics-table tbody td::before { content: none; }

    /* The day-wise stock book opts out for the same reason: it is read down
       its Closing column and across a day's sum, never as separate records.
       The date column stays pinned while the figures scroll. */
    .admin-page .stock-book-scroll { overflow-x: auto; }
    .admin-page .stock-book-table { display: table; min-width: 760px; }
    .admin-page .stock-book-table thead { display: table-header-group; }
    .admin-page .stock-book-table tbody { display: table-row-group; }

    .admin-page .stock-book-table tbody tr {
        display: table-row;
        gap: 0;
        padding: 0;
        border: none;
        border-radius: 0;
        background: transparent;
    }

    .admin-page .stock-book-table tbody tr + tr { margin-top: 0; }

    .admin-page .stock-book-table tbody td {
        display: table-cell;
        width: auto;
        padding: 10px 12px;
        border-bottom: 1px solid var(--line);
        white-space: nowrap;
        vertical-align: middle;
    }

    .admin-page .stock-book-table tbody td.numeric { text-align: right; }
    .admin-page .stock-book-table tbody td::before { content: none; }

    /* The month picker goes full width under the title rather than sharing the
       line with it — a 180px select beside a heading left both truncated, the
       select showing "September 2…" for the one value it exists to display. */
    .admin-page .analytics-head {
        flex-direction: column;
        align-items: stretch;
        gap: 12px;
    }

    .analytics-month-form { width: 100%; }
    .analytics-month-form .daily-entry-field { flex: 1; min-width: 0; }

    .analytics-tile strong { font-size: 19px; }
}

/* ============================================================
   EMPTY STATES
   ============================================================
   See app/views/partials/empty-state.php for why these exist at
   all. Quiet on purpose: a manager meets them several times a
   day on screens whose real content is a list of rows, and an
   illustration would out-shout the rows on every other load.
   ============================================================ */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 32px 24px;
    text-align: center;
}

/* Inside a modal the block is the whole body of the dialog, which is already
   padded and already small. */
.modal-card .empty-state { padding: 20px 8px 8px; }

.empty-state-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-bottom: 6px;
    border-radius: var(--radius-full);
    background: var(--surface-muted);
    color: var(--text-secondary);
}

.empty-state strong {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
}

/* Capped rather than left to run the width of a wide card: a single sentence
   set across 900px is read as a paragraph and skipped. */
.empty-state p {
    max-width: 42ch;
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-secondary);
}

.empty-state-action { margin-top: 10px; }

@media (max-width: 767px) {
    .empty-state { padding: 26px 16px; }
}

/* ---- Skipping a day from the customer's own sheet ---- */

/* The row's one action, pushed to its own line under the quantities rather than
   sitting beside them: on a phone the sheet row is already a stack, and a
   button tucked into the flow of that stack reads as part of the reading matter
   instead of as something to press. */
.sheet-row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}

/* The state the button above puts the row into, said in words. The pill on the
   row says "Not delivered", which is the plant's word for it and covers a van
   breakdown just as well — this line is what tells the customer it was their
   own doing and is theirs to undo. */
.sheet-row-skipped {
    margin: 8px 0 0;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--warning);
}

/* ============================================================
   SKIPPING DAYS OF A STANDING ORDER
   ============================================================
   One button per subscription row, and a panel that is a list
   of the days not being delivered plus a way to add to it.
   See app/views/partials/skip-days.php.
   ============================================================ */

/* The button is a `.btn btn-secondary btn-small` in the markup, so its height,
   radius, padding and hover are the Edit / Pause / Terminate ones rather than a
   near-miss maintained separately. Only the state those buttons do not have is
   described here. */

/* Amber when days are already called off. Nothing has gone wrong — somebody
   asked for this — but it is a departure from the standing arrangement, and a
   manager scanning a list of twenty rows should be able to see which ones are
   not what they look like without opening any of them. */
.skip-open-btn.is-skipped {
    border-color: var(--warning);
    background: var(--warning-light);
    color: var(--warning);
}

.skip-open-btn.is-skipped:hover { background: var(--warning-light); }

/* Picking a day. The label is a question, and the answer and the button that
   acts on it sit on one line, so the whole thing reads as a sentence rather
   than a form to fill in. */
.skip-add { margin-top: 18px; }

.skip-add-row {
    display: flex;
    align-items: stretch;
    gap: 10px;
    margin-top: 6px;
}

.skip-add-row select { flex: 1; min-width: 0; }

/* Matched to the select beside it rather than left at the small button's 36px,
   which read as a button that had fallen into the field. */
.skip-add-row .btn { flex: none; min-height: 0; }

/* The days already called off. This is the part that answers "what did I
   do" — so it is a list with the undo on each line, not a summary. */
.skip-taken { margin-top: 20px; }

.skip-taken-head {
    margin: 0 0 8px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.skip-taken-day {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    border: 1px solid var(--warning);
    border-radius: var(--radius);
    background: var(--warning-light);
    font-size: 13px;
}

.skip-taken-day + .skip-taken-day { margin-top: 8px; }

.skip-taken-name { font-weight: 700; color: var(--text); }

.skip-undo-btn {
    flex: none;
    border-color: var(--warning);
    color: var(--warning);
}

.skip-undo-btn:hover { border-color: var(--warning); background: var(--surface); }

/* Nothing on offer and nothing called off — a paused order, a finished one.
   Sized as body text, not as a footnote: it is the whole content of the panel
   when it appears, and an empty dialog would read as a fault. */
.skip-empty {
    margin: 18px 0 0;
    padding: 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg);
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-secondary);
}

/* On a phone every table row is a card, laid out as a grid. The button takes a
   full row of that grid so it lands on a line of its own, below the details,
   where a control belongs — and so it is wide enough to hit with a thumb. */
@media (max-width: 767px) {
    .customer-page .data-table tbody td.skip-cell {
        grid-column: 1 / -1;
        margin-top: 4px;
        padding-top: 10px;
        border-top: 1px solid var(--line);
    }

    .customer-page td.skip-cell .skip-open-btn { width: 100%; }

    /* Stacked, because a select and a button sharing a 320px line leaves the
       dates truncated and the button too narrow to hit. */
    .skip-add-row { flex-direction: column; }
    .skip-add-row .btn { min-height: 44px; }
}

/* ---- Deposit: which way the money is going ---- */

/* Two cards, not a dropdown and not a signed number. Taking a deposit and
   giving one back are opposite actions on the same form, and the old single
   Amount box expressed the difference as a minus sign nothing on screen asked
   for. Radios keep it keyboard- and screen-reader-native; the card is only
   their label, made large enough to be a target. */
.deposit-direction {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
}

.deposit-choice {
    position: relative;
    display: block;
    margin: 0;
    cursor: pointer;
}

/* The input stays in the DOM and stays focusable — it is what actually holds
   the value — but the card beside it is what is seen. */
.deposit-choice input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    border: 0;
    opacity: 0;
    overflow: hidden;
    clip-path: inset(50%);
}

.deposit-choice > span {
    display: block;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
    transition: all var(--transition);
}

.deposit-choice strong {
    display: block;
    font-size: 14px;
    font-weight: 700;
    color: var(--text);
}

.deposit-choice small {
    display: block;
    margin-top: 2px;
    font-size: 11.5px;
    font-weight: 500;
    line-height: 1.4;
    color: var(--text-secondary);
}

.deposit-choice input:checked + span {
    border-color: var(--primary);
    background: var(--primary-light);
    box-shadow: inset 0 0 0 1px var(--primary);
}

.deposit-choice input:checked + span strong { color: var(--primary-dark); }
.deposit-choice input:focus-visible + span { outline: 2px solid var(--primary); outline-offset: 2px; }

@media (max-width: 480px) {
    /* Stacked rather than squeezed: at 375px two of these side by side cut
       "Customer paid a deposit" onto three lines. */
    .deposit-direction { grid-template-columns: 1fr; }
}

/* ---- App colour picker (Account settings) ----
   A row of round swatches and one colour box for anything else. The ring on
   the chosen swatch is drawn in the page's text colour rather than the brand
   colour, so it shows on every swatch including the current one. */
.brand-color-field {
    display: grid;
    gap: 10px;
    margin: 0;
    padding: 0;
    border: 0;
    min-width: 0;
}

.brand-color-field legend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 8px;
    align-items: baseline;
    margin-bottom: 8px;
    padding: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
}

.brand-color-field legend .muted { font-weight: 400; font-size: 12px; }

/* A titled group inside a settings form — same weight as the colour
   picker's legend, with its one-line explanation underneath. */
.form-section-head { display: grid; gap: 2px; }
.form-section-title { margin: 0; font-size: 13px; font-weight: 600; color: var(--text); }
.form-section-hint { margin: 0; font-size: 12px; line-height: 1.45; }

.brand-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.brand-swatch {
    position: relative;
    display: inline-flex;
    width: 32px;
    height: 32px;
    cursor: pointer;
}

.brand-swatch > input[type="radio"] {
    min-height: 0;
    position: absolute;
    inset: 0;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.brand-swatch > span,
.brand-swatch > input[type="color"] {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background: var(--swatch);
    box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.12);
    transition: box-shadow var(--transition), transform var(--transition);
}

.brand-swatch:hover > span { transform: scale(1.08); }

.brand-swatch > input[type="radio"]:checked + span {
    box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--text);
}

.brand-swatch > input[type="radio"]:focus-visible + span {
    outline: 2px solid var(--text);
    outline-offset: 4px;
}

/* What the pick will look like, drawn with the same variables the app uses
   so the text colour it gets is the one the app will give it. */
.brand-preview {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-md);
    background: var(--surface-alt);
    font-size: 13px;
}

.brand-preview-button {
    padding: 6px 14px;
    border-radius: var(--radius-sm);
    background: var(--primary);
    color: var(--on-primary);
    font-weight: 600;
}

.brand-preview-pill {
    padding: 3px 10px;
    border-radius: var(--radius-full);
    background: var(--primary-light);
    color: var(--primary-dark);
    font-weight: 600;
}

.brand-preview-link { color: var(--primary-ink); font-weight: 600; }

/* ---- Pay your bills (customer Bills tab) ----
   The QR code beside the UPI and bank details. The code is scanned off the
   screen by another phone, so it keeps a white quiet zone of its own and a
   fixed size. The tap-to-pay button only appears on a touch phone, where it
   can open a UPI app; on a desktop the QR is the way to pay. */
.pay-card-body {
    display: flex;
    align-items: flex-start;
    gap: 20px;
    margin-top: 14px;
}

.pay-card-qr { flex: 0 0 168px; text-align: center; }
.pay-card-qr svg {
    display: block;
    width: 168px;
    height: 168px;
    border: 1px solid var(--line);
    border-radius: 8px;
}
.pay-card-qr p { margin: 6px 0 0; font-size: 12px; }

.pay-card-details { flex: 1 1 auto; min-width: 0; }
.pay-card-details dl { margin: 0; }
.pay-card-details dl > div {
    display: flex;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px solid var(--line);
}
.pay-card-details dt { flex: 0 0 96px; color: var(--muted); font-size: 13px; }
.pay-card-details dd {
    margin: 0;
    font-weight: 600;
    overflow-wrap: anywhere;
    font-variant-numeric: tabular-nums;
}
.pay-card-hint { margin: 10px 0 0; font-size: 12px; }

/* A UPI number is typed into the customer's UPI app by hand, and on a phone
   the app is the same screen as this one — so the number copies with a tap. */
.pay-copy {
    margin-left: 8px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--primary-ink);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}
.pay-copy:hover { text-decoration: underline; }

.pay-card-button { display: none; }

@media (hover: none) and (pointer: coarse) {
    .pay-card-button {
        display: flex;
        justify-content: center;
        width: 100%;
        min-height: 46px;
        margin-top: 14px;
        align-items: center;
    }
}

@media (max-width: 620px) {
    .pay-card-body { flex-direction: column; align-items: stretch; }
    .pay-card-qr { flex-basis: auto; }
    .pay-card-qr svg { margin: 0 auto; }
}

/* ---- CLAUDE ACCESS ---- */

/* One block in Account settings: what it is, on/off, the two buttons, and —
   right after "Turn on" — the key to copy. Redrawn in place by app.js. */
.claude-access { margin-bottom: 18px; }
.claude-access-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.claude-access-row > div:first-child { flex: 1 1 200px; }
.claude-access-row p { margin: 0; }
.claude-access-actions { display: flex; align-items: center; gap: 8px; }

.claude-key-box {
    display: grid;
    gap: 8px;
    margin-top: 12px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-muted);
}
.claude-key-box p { margin: 0; }
.claude-key-box textarea {
    width: 100%;
    resize: none;
    background: var(--surface);
    font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
    overflow-wrap: anywhere;
}
.claude-key-box .btn { justify-self: start; }
