/* ==========================================================================
   UDYOGPORTAL PLATFORM COMPONENT LIBRARY
   Design Tokens & CSS definitions for standard framework components.
   Built strictly using active THEME 12 variables (light theme locked).
   ========================================================================== */

/* ── `hidden` means hidden ────────────────────────────────────────────────────
   `hidden` is an HTML attribute with a UA rule behind it — `[hidden] { display:
   none }` — and any component in this file that sets its own `display` beats it
   on specificity. So `el.hidden = true` on an `.md-btn` did nothing at all.

   Found on leads/add.php: the wizard's Back button sat visible on step 1, and
   Continue stayed live after the lookup had said "this number is already a
   lead" — the one moment the form must not continue. crm/add.php has the same
   two buttons and had the same bug; one rule fixes both and everything after.

   `!important` is deliberate and is the exception, not a habit: the whole point
   is to outrank every component's own `display`, and there is no case where an
   element carrying `hidden` should be painted. */
[hidden] { display: none !important; }

/* ─── 1. Modals & Dialogs ────────────────────────────────────────────────── */
.platform-modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    /* The one scrim (§7 — "every modal in the app uses the same scrim"). This
       was a 6% wash: at that weight the page behind stayed fully legible and the
       dialog read as floating text rather than as a decision that has to be
       answered. The blur stays; it was never the problem. */
    background-color: var(--md-scrim);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-20);
    z-index: 99999;
    opacity: 0;
    pointer-events: none;
    /* visibility, not just opacity. An overlay hidden by opacity alone still
       renders, so its buttons keep their place in the tab order and in the
       accessibility tree: on a page carrying two of these, the first Tab landed
       on the "Close" button of a dialog nobody had opened. `.auth-modal-overlay`
       in dialog.css always did this correctly — this is the same rule.
       Transitioned so the fade out still plays before it is taken away. */
    visibility: hidden;
    transition: opacity var(--motion-normal), visibility var(--motion-normal);
}

.platform-modal-overlay.active {
    opacity: 1;
    pointer-events: auto;
    visibility: visible;
}

.platform-modal {
    background-color: var(--white);
    border-radius: var(--radius-xl);
    width: 100%;
    max-width: var(--viewport-width);
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    box-shadow: var(--elevation-4);
    transform: scale(0.95);
    opacity: 0;
    transition: transform var(--motion-normal), opacity var(--motion-normal);
    overflow: hidden;
    border: 1px solid var(--border);
}

.platform-modal-overlay.active .platform-modal {
    transform: scale(1);
    opacity: 1;
}

/* Modal Structure: Pinned Header and Footer, Scrollable Body */
.platform-modal-header {
    padding: var(--space-20) var(--space-24) var(--space-16);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: var(--space-12);
    background-color: var(--white);
    flex-shrink: 0;
}

.platform-modal-icon {
    width: 40px;
    height: 40px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.platform-modal-title {
    font-family: var(--font-primary);
    font-size: var(--title);
    font-weight: var(--md-weight-semibold);
    color: var(--text-primary);
    margin: 0;
}

.platform-modal-body {
    padding: var(--space-20) var(--space-24);
    overflow-y: auto;
    flex-grow: 1;
    -webkit-overflow-scrolling: touch;
    font-family: var(--font-primary);
    font-size: var(--body-md);
    color: var(--text-secondary);
    line-height: 1.6;
}

.platform-modal-footer {
    padding: var(--space-16) var(--space-24) var(--space-20);
    border-top: 1px solid var(--border);
    display: flex;
    gap: var(--space-10);
    background-color: var(--background);
    flex-shrink: 0;
}

/* ─── 2. Buttons ────────────────────────────────────────────────────────── */
.platform-btn-primary,
.platform-btn-secondary,
.platform-btn-destructive {
    font-family: var(--font-primary);
    font-size: var(--body-md);
    font-weight: var(--md-weight-semibold);
    height: 48px;
    padding: 0 var(--space-24);
    border-radius: var(--radius-full);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    cursor: pointer;
    border: none;
    outline: none;
    transition: all var(--motion-fast);
    flex: 1;
    text-decoration: none;
    box-sizing: border-box;
    text-align: center;
}

.platform-btn-primary {
    background-color: var(--primary);
    color: var(--white);
    box-shadow: var(--elevation-1);
}

.platform-btn-primary:active {
    background-color: var(--primary-dark);
    transform: scale(0.98);
}

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

.platform-btn-secondary:active {
    background-color: var(--background);
    transform: scale(0.98);
}

.platform-btn-destructive {
    background-color: var(--error);
    color: var(--white);
    box-shadow: var(--elevation-1);
}

.platform-btn-destructive:active {
    background-color: color-mix(in srgb, var(--md-primary) 82%, black);
    transform: scale(0.98);
}

/* ─── 3. Platform Alert Types ────────────────────────────────────────────── */
.platform-alert-modal .platform-modal-icon--info {
    background-color: var(--tertiary-light);
    color: var(--tertiary);
}

.platform-alert-modal .platform-modal-icon--success {
    background-color: var(--success-light);
    color: var(--success);
}

.platform-alert-modal .platform-modal-icon--error {
    background-color: var(--error-light);
    color: var(--error);
}

.platform-alert-modal .platform-modal-icon--warning {
    background-color: var(--warning-light);
    color: var(--warning);
}

/* ─── 4. Tables ──────────────────────────────────────────────────────────── */
.platform-table-container {
    width: 100%;
    overflow-x: auto;
    border-radius: var(--radius-lg);
    border: 1px solid var(--border);
    background-color: var(--white);
    box-shadow: var(--elevation-1);
    margin-bottom: var(--space-20);
}

.platform-table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-primary);
    font-size: var(--body-md);
    text-align: left;
}

.platform-table th {
    background-color: var(--background);
    color: var(--text-secondary);
    font-weight: var(--md-weight-semibold);
    padding: var(--space-12) var(--space-16);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}

.platform-table td {
    padding: var(--space-16);
    border-bottom: 1px solid var(--border);
    color: var(--text-primary);
    vertical-align: middle;
}

.platform-table tr:last-child td {
    border-bottom: none;
}

.platform-table tr:active {
    background-color: var(--navy-light);
}

/* ─── 5. Form Elements ───────────────────────────────────────────────────── */
.platform-form-group {
    margin-bottom: var(--space-20);
    display: flex;
    flex-direction: column;
    gap: var(--space-8);
}

.platform-form-label {
    font-family: var(--font-primary);
    font-size: var(--body-md);
    font-weight: var(--md-weight-semibold);
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: 6px;   /* was var(--space-6), which is not defined anywhere */
}

.platform-form-input {
    font-family: var(--font-primary);
    font-size: var(--body-lg);
    height: 48px;
    padding: 0 var(--space-16);
    border-radius: var(--radius-md);
    border: 1.5px solid var(--border);
    background-color: var(--white);
    color: var(--text-primary);
    outline: none;
    transition: all var(--motion-fast);
    width: 100%;
    box-sizing: border-box;
}

.platform-form-input:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-light);
}

.platform-form-error {
    font-family: var(--font-primary);
    font-size: var(--caption);
    color: var(--error);
    display: none;
    align-items: center;
    gap: var(--space-4);
    margin-top: var(--space-4);
}

.platform-form-group.has-error .platform-form-input {
    border-color: var(--error);
}

.platform-form-group.has-error .platform-form-input:focus {
    box-shadow: 0 0 0 3px var(--error-light);
}

.platform-form-group.has-error .platform-form-error {
    display: flex;
}

/* ─── 6. States (Empty, Loading, Error) ──────────────────────────────────── */

/* Empty State */
.platform-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--space-48) var(--space-24);
    font-family: var(--font-primary);
}

.platform-empty-icon {
    font-size: 48px;
    color: var(--text-muted);
    margin-bottom: var(--space-16);
}

.platform-empty-title {
    font-size: var(--title);
    font-weight: var(--md-weight-semibold);
    color: var(--text-primary);
    margin: 0 0 var(--space-8) 0;
}

/* Loading Skeleton */
.platform-loading-skeleton {
    width: 100%;
    height: 16px;
    background: linear-gradient(90deg, var(--md-surface-container-high) 25%, var(--md-surface-container-highest) 50%, var(--md-surface-container-high) 75%);
    background-size: 200% 100%;
    animation: loading-skeleton-animation 1.5s infinite;
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-8);
}

@keyframes loading-skeleton-animation {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

.platform-file-upload:hover,
.platform-file-upload.dragover {
    border-color: var(--primary);
    background-color: var(--primary-light);
}

/* ── Date Picker (Day · Month · Year dropdowns) ─────────────────────────
   ── The columns are MEASURED, not guessed ──────────────────────────────
   A `<select>` clips silently: it reports no overflow and the longest option
   simply loses its tail. At `1fr 1.55fr 1.1fr` two of the three columns did,
   in the narrowest place this component is used — a dialog on a 360px phone,
   where the control gets 284px and the columns 268px of it:

       col    had   needed                          verdict
       day    31.4   36  "तारीख" (mr)               clipped to "तारीर"
       month  71.8   73  "September" (en)           clipped to "Septembe"
       year   38.8   31  "2028"                     fine

   Both were within a few pixels, which is why they read as a font problem
   rather than a layout one. The ratios below give every column its measured
   need plus slack in all three languages, and the field's own padding gives
   back four more pixels a side (the chevron is 16px at `right 9px`, so 28px
   of right padding still clears it).

   If you change these, MEASURE — the longest option is a different string in
   every language, and Devanagari and Latin do not scale together. */
.date-picker {
    display: grid;
    grid-template-columns: 1.13fr 1.73fr 1.09fr;
    gap: var(--space-8, 8px);
    width: 100%;
    /* The ratios above fit at 15px from about 247px of control width upward.
       This component is dropped into containers far narrower than that —
       `crm/followups.php` gives it 228px on a 360px phone — and a VIEWPORT
       media query cannot see that: the screen is 360 either way. So the
       breakpoint below asks the control about itself. */
    container-type: inline-size;
}


/* Time picker (Hour · Minute · AM/PM) reuses .date-picker-field select styling */
.time-picker {
    display: grid;
    grid-template-columns: 1fr 1fr 0.9fr;
    gap: var(--space-8, 8px);
    width: 100%;
}
@media (max-width: 340px) { .time-picker { gap: 6px; } }

.date-picker-field {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    width: 100%;
    min-width: 0;
    height: 48px;
    padding: 0 28px 0 10px;
    border: 1px solid var(--muted);
    border-radius: var(--radius-md, 14px);
    background-color: var(--white);
    color: var(--navy, var(--md-inverse-surface));
    font-family: var(--font-primary);
    font-size: 15px;
    font-weight: var(--md-weight-semibold);
    line-height: 1.2;
    cursor: pointer;
    /* Custom chevron */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238888AA' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}


.date-picker-field:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--md-outline) 18%, transparent);
}

/* Empty (placeholder) state reads as muted; a chosen value reads solid */
.date-picker-field:invalid,
.date-picker-field option[value=""] {
    color: var(--text-muted);
}

.date-picker-field:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

@media (max-width: 340px) {
    .date-picker { gap: 6px; }
    .date-picker-field { padding: 0 26px 0 8px; font-size: 14px; }
}

/* Two-up form grids / filter rows that hold a Day·Month·Year picker get too
   cramped on narrow screens (6 dropdowns in a row) — stack them to one column. */
@media (max-width: 560px) {
    .mw-fields-grid-2:has(.date-picker),
    .crm-filter-grid:has(.date-picker),
    .rpt-filter-row:has(.date-picker),
    .filter-row:has(.date-picker),
    .date-range-row:has(.date-picker) {
        grid-template-columns: 1fr !important;
        display: grid !important;
    }
}

/* ══════════════════════════════════════════════════════════════════════════
   SHARED HELP MODAL — one consistent pattern for every module's "?" help
   dialog. Gradient brand header (icon + title + subtitle + close), plain-text
   body, gradient "Got it" footer. Content stays translation-driven per page.
   Usage: reuse .auth-modal-overlay + .auth-modal, then these classes inside.
   ══════════════════════════════════════════════════════════════════════════ */
.help-modal { padding: 0 !important; overflow: hidden; }

.help-modal-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 18px;
    background: var(--grad-hero);
    color: #FFFFFF;
    flex-shrink: 0;   /* header never compresses; body scrolls under it */
}
.help-modal-head-left { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }
.help-modal-icon {
    width: 38px;
    height: 38px;
    border-radius: 11px;
    flex-shrink: 0;
    background: rgba(255, 255, 255, 0.22);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.32);
    display: flex;
    align-items: center;
    justify-content: center;
}
.help-modal-icon .material-symbols-rounded { font-size: 20px; color: #FFFFFF; }
.help-modal-title {
    margin: 0;
    font-family: var(--font-primary);
    font-size: 16px;
    font-weight: var(--md-weight-semibold);
    line-height: 1.35;
    color: #FFFFFF;
    letter-spacing: normal;
}
.help-modal-sub {
    font-family: var(--font-primary);
    font-size: 11.5px;
    font-weight: var(--md-weight-medium);
    line-height: 1.4;
    color: rgba(255, 255, 255, 0.9);
    margin-top: 1px;
}
/* 48px to touch, 32px to see (Play accessibility check, 2026-11-05): an 8px
   transparent border the background does not paint under, and the same 8px
   given back as negative margin, so the circle and the header do not move. */
.help-modal-close {
    flex-shrink: 0;
    margin: -8px -8px -8px auto;
    width: 48px;
    height: 48px;
    border: 8px solid transparent;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.18);
    background-clip: padding-box;
    color: #FFFFFF;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.15s ease;
}
.help-modal-close:active { background: rgba(255, 255, 255, 0.3); transform: scale(0.92); }
.help-modal-close .material-symbols-rounded { font-size: 20px; }

.help-modal-body {
    margin: 10px;              /* 10px gap between the scroll area and the modal edges/header/footer */
    padding: 4px 10px 6px;
    /* flex:1 + min-height:0 lets this shrink inside the flex-column modal so its
       own overflow scrolls (instead of pushing the modal past its max-height). */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    display: flex;
    flex-direction: column;
    gap: 18px;
}
.help-para-title {
    font-family: var(--font-primary);
    font-size: 13px;
    font-weight: var(--md-weight-semibold);
    line-height: 1.4;
    color: var(--md-inverse-surface);
    margin: 0 0 3px 0;
    letter-spacing: normal;
}
.help-para-text {
    font-family: var(--font-primary);
    font-size: 12.5px;
    font-weight: var(--md-weight-medium);
    line-height: 1.6;
    color: var(--md-on-surface-variant);
    margin: 0;
}

.help-modal-footer {
    padding: 12px 18px 14px;
    border-top: 1px solid var(--border);
    flex-shrink: 0;
    background: var(--white);
    display: flex;
    flex-direction: column;
    gap: 8px;
}
/* Single gradient "Got it" (kept for pages that don't offer Ask-Myra) */
.help-modal-cta {
    height: 48px;
    width: 100%;
    border: none;
    border-radius: 9999px;
    background: var(--grad-hero);
    color: #FFFFFF;
    font-family: var(--font-primary);
    font-size: 15px;
    font-weight: var(--md-weight-semibold);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}
.help-modal-cta .material-symbols-rounded { font-size: 18px; }

/* Primary "Ask Myra about this" — emerald (Myra's palette), robot icon */
.help-modal-myra {
    height: 48px;
    width: 100%;
    border: none;
    border-radius: 9999px;
    background: linear-gradient(135deg, var(--md-tertiary) 0%, color-mix(in srgb, var(--md-tertiary) 62%, var(--md-secondary)) 100%);
    color: #FFFFFF;
    font-family: var(--font-primary);
    font-size: 15px;
    font-weight: var(--md-weight-semibold);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    box-shadow: 0 10px 22px -8px color-mix(in srgb, var(--md-tertiary) 5%, transparent);
    transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.help-modal-myra:active { transform: scale(0.975); }
.help-modal-myra .material-symbols-rounded { font-size: 20px; }
/* Secondary ghost "Got it" */
.help-modal-got {
    height: 42px;
    width: 100%;
    border: none;
    border-radius: 9999px;
    background: transparent;
    color: var(--md-on-surface-variant);
    font-family: var(--font-primary);
    font-size: 14px;
    font-weight: var(--md-weight-semibold);
    cursor: pointer;
}
.help-modal-got:active { background: color-mix(in srgb, var(--md-on-surface) 04%, transparent); }

/* ──────────────────────────────────────────────────────────────────────────
   Feedback / alert dialog must always be the top-most layer.

   .auth-modal-overlay is z-index 99998, but ~126 page overlays across the app
   raise themselves to 999999 (help modals, delete confirms, save success/error).
   A feedback modal opened from inside one of those would render BEHIND it and
   look like nothing happened — which is exactly what "Ask Myra" did from the
   help modal. An alert is transient and always the newest thing on screen, so
   it belongs above every other overlay.
   ────────────────────────────────────────────────────────────────────────── */
#auth-feedback-overlay,   /* showFeedbackModal()  — .auth-modal-overlay, 99998 */
#platform-alert-overlay { /* Platform.alert.show() — .platform-modal-overlay, 99999 */
    z-index: 1000000;
}


/* ==========================================================================
   MATERIAL 3 — STATE LAYER
   ==========================================================================
   The mechanic behind every M3 interaction. An interactive surface gets a
   translucent overlay of its OWN foreground colour: 8% on hover, 12% on focus
   and press. Because the layer is a pseudo-element painted with currentColor,
   one rule works on a white card, a rose button and a coloured icon tile
   alike — no per-component press styles.

   Usage: add `md-state` to any tappable element.
     <a class="crm-row md-state">   <button class="btn md-state">

   Two requirements the class handles for you: `position:relative` so the layer
   anchors, and `isolation:isolate` so it cannot escape its own stacking
   context. Children are lifted to z-index 1 so they stay above the layer.

   Pair with the ripple in platform-core.js, which is delegated — no per-element
   wiring, and it skips itself when prefers-reduced-motion is set.
   ========================================================================== */
.md-state {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    -webkit-tap-highlight-color: transparent;
}

.md-state::after {
    content: "";
    position: absolute;
    inset: 0;
    background: currentColor;
    opacity: 0;
    pointer-events: none;
    z-index: 0;
    transition: opacity var(--md-dur-short, 150ms) var(--md-ease, ease);
}

/* Hover only where hovering is real — on touch it would stick after a tap. */
@media (hover: hover) {
    .md-state:hover::after { opacity: var(--md-state-hover, .08); }
}
.md-state:focus-visible::after { opacity: var(--md-state-focus, .12); }
.md-state:active::after        { opacity: var(--md-state-press, .12); }

/* Anything the element already contains must sit above the layer. */
.md-state > * { position: relative; z-index: 1; }

/* Disabled surfaces take no state. */
.md-state[disabled]::after,
.md-state[aria-disabled="true"]::after { opacity: 0 !important; }

/* ── ripple ─────────────────────────────────────────────────────────────── */
.md-ripple {
    position: absolute;
    border-radius: 50%;
    transform: scale(0);
    background: currentColor;
    opacity: .20;
    pointer-events: none;
    z-index: 0;
    animation: md-ripple var(--md-dur-long, 400ms) cubic-bezier(0, 0, 0, 1) forwards;
}
/* The ripple must NEVER enter the layout, whatever a module sheet says.

   platform-core.js appends it as a direct child of the tapped `.md-state`,
   sized to the element's longer side — about 330px on a full-width row. A
   module rule of the shape `.x > * { position: relative; z-index: 1 }` has the
   same specificity as `.md-ripple` and loads after this file, so it wins, and
   the ripple becomes an in-flow 330px square: the row it lives in grows to
   that height for the 420ms the node exists, then snaps back as the page
   navigates. "The card expands and then goes."

   Found 2026-09-13 on the upgrade hub (94px → 362px measured mid-tap).
   Three module sheets carry that exact rule on md-state surfaces —
   `.upw__go`, `.bkd-share`, `.cvh-frow` — and each is
   legitimate on its own terms (lifting content above a shine or a wash).

   Two classes out-rank any single-class `> *` rule regardless of load order,
   so this fixes all three here and the next one before it is written, with no
   `!important`. */
.md-state > .md-ripple {
    position: absolute;
    z-index: 0;
}
@keyframes md-ripple {
    to { transform: scale(2.6); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .md-ripple { display: none; }
}

/* ==========================================================================
   MATERIAL 3 — COMPONENT LIBRARY
   ==========================================================================
   The shared components every module builds from. Spec and usage:
   documentation/ai-docs/MATERIAL_DESIGN.md.

   Written once here so a module page contains layout and content, not a
   private copy of the design system. If a page needs a variant, add it here
   with a modifier — do not fork the component into the page.
   ========================================================================== */

/* ── Section header ─────────────────────────────────────────────────────── */
.md-sec {
    /* Usually a real heading element, so the browser's own h2/h3 margin has to
       go or the spacing below is whatever the user agent felt like adding. */
    margin: 0;
    padding: 16px 4px 10px;
    font-size: 14px;
    line-height: 20px;
    font-weight: var(--md-weight-medium);
    letter-spacing: .1px;
    color: var(--md-on-surface-variant);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
/* A link on some screens, a `<button>` on others — a heading's action is
   sometimes a navigation and sometimes a dialog. Without the reset the button
   arrives wearing the user agent's border and grey fill, which reads as an
   input sitting in a heading. Caught on crm/group_money.php 2026-08-29. */
.md-sec__action {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    /* The hit box reaches the 48px accessibility floor while the ink stays
       where the heading puts it: padding grows the target, the equal negative
       margin takes the growth back out of the layout, so the margin box is
       still the 22px the heading laid out and nothing moves.

       Measured at 22px before any of this — the smallest target in the app, on
       a control that sits at the top of every list. It was then 44, which is
       the chip/pill number; touch_measure.js caught it at 84x44 on the
       marketing home (2026-09-02), and D5's floor is 48 under everything. */
    padding: 13px 6px;
    margin: -13px -6px;
    border: 0;
    background: none;
    font-family: inherit;
    font-size: 14px;
    font-weight: var(--md-weight-medium);
    line-height: 1.6;
    color: var(--md-primary);
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.md-sec__action .material-symbols-rounded { font-size: 18px; }

/* ── List — one grouped card, rows divided by a hairline ────────────────── */
.md-list {
    background: var(--md-surface-container-lowest);
    border-radius: var(--md-shape-lg);
    box-shadow: var(--md-elev-1);
    overflow: hidden;
}
.md-list--flat { box-shadow: none; border: 1px solid var(--md-outline-variant); }

.md-row {
    display: flex;
    align-items: center;
    gap: 12px;
    /* 64px. Raised to 80 on 2026-08-17 to satisfy the D5 touch gate, and put
       back the same day: at 80 the destination lists read as too tall — the
       product owner's call, and the reason D5 now records 64 rather than the
       code pretending to meet a number nothing in the app has ever met
       (REDESIGN_TRACKER §7c). Still well clear of the 48px touch floor: the
       row's own height is what the thumb gets. */
    min-height: 64px;
    padding: 9px 14px;
    text-decoration: none;
    color: var(--md-on-surface);
    cursor: pointer;
}
/* `.md-row` as a <button>. The row above is written for an `<a>`; a row that
   runs JS rather than navigating has to be a button, and then the UA defaults
   take over — shrink-to-fit width, centred text, and the page's font replaced
   by the button font. crm/groups.css carried this privately with a note saying
   it belonged here "until a second page needs them". crm/custom_fields.php was
   the second page, and until this moved, its action sheet rendered as three
   staggered, centred boxes. */
button.md-row {
    width: 100%;
    text-align: left;
    font-family: var(--font-primary);
    border: 0;
    background: transparent;
}

/* One-line variant, for dense lists with no supporting text. */
.md-row--dense { min-height: 56px; }
/* Divider between rows only — never above the first or below the last. */
.md-row + .md-row { box-shadow: inset 0 1px 0 var(--md-outline-variant); }

/* Leading media. Module tint classes (ri--bill etc.) are applied here and are
   the ONLY place a non-role colour belongs. */
.md-row__lead {
    width: 44px;
    height: 44px;
    flex: 0 0 44px;
    border-radius: var(--md-shape-md);
    display: grid;
    place-items: center;
    background: var(--md-surface-container);
    color: var(--md-on-surface-variant);
    transition: transform var(--md-dur-short) var(--md-ease-emphasized);
}
.md-row:active .md-row__lead { transform: scale(.94); }
.md-row__lead img { width: 32px; height: 32px; object-fit: contain; display: block; }
.md-row__lead .material-symbols-rounded { font-size: 23px; }

/* Body. Both lines are single-line with ellipsis: a list row must never
   reflow, or the whole list jumps as content loads. */
.md-row__body { flex: 1; min-width: 0; }
.md-row__body b,
.md-row__title {
    display: block;
    font-size: 16px;
    /* 24px, not 22. This box clips its overflow, and §8 of MATERIAL_DESIGN is
       explicit about what that costs in Devanagari: matras sit above the
       shirorekha, so a Latin-tight leading slices the top off the word. At 22
       "सर्व कस्टमर" and "लीड्स मॅनेजमेंट" were losing their उ and ॅ marks to
       the clip; 1.5 gives every script its ascender back and costs a row
       nothing — `.md-row` is 64px tall and a one-line label does not reach it. */
    line-height: 24px;
    /* 600 — the row label is the thing being chosen, and at 500 against a
       supporting line only one step lighter it read as a caption. 600 is the
       app's ceiling (§3); emphasis past this has to come from size or colour. */
    font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.md-row__body span,
.md-row__sub {
    display: block;
    /* 10px — deliberately below the 12px type-scale floor (§3), by product
       decision: the label carries the row and this line is a whisper under it.
       Line-height is 1.6 — the figure §8 names for small text that hides its
       overflow — rather than shrinking with the size, or the matras on मॅनेज
       clip at this height.

       Set in px on purpose: at 10px a relative unit compounds with any
       ancestor scaling and lands somewhere unreadable. */
    font-size: 10px;
    line-height: 1.6;
    font-weight: var(--md-weight-regular);
    /* Faint: the variant ink lightened toward the surface it sits on, rather
       than made transparent — a transparent grey over a tinted card picks up
       the tint. Mixing with --md-surface keeps it neutral and lets it invert
       in dark, where the mix lightens instead. */
    /* 74%, not 72%. At 72 this line measured 4.35:1 on the list card — under
       the 4.5:1 floor for text this size, on a screen read in daylight. 74 is
       4.60:1 and is two shades away; the line still reads as the whisper it was
       asked to be, and now clears the floor. Re-measure if the mix changes.
       77 since 2026-11-05: on a white card (the Myra row on a module home,
       a bill's "Address, timings and more") 74 read 4.48:1 in Google Play's
       pre-launch report; 77 is 4.95 on white and 4.72 on --md-surface. */
    color: color-mix(in srgb, var(--md-on-surface-variant) 77%, var(--md-surface));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Trailing: amount, badge, status pill or chevron. Never a bare dot. */
.md-row__tail {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
    color: var(--md-on-surface-variant);
}
.md-row__amt {
    font-size: 17px;
    font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.md-row.is-off { opacity: .55; }

/* ── Data row — a fact, not a door ─────────────────────────────────────────
   Same grouped card, same leading tile, but the row states something instead
   of going somewhere: no chevron, no `md-state`, no pointer. The body reads
   label-over-value, which is the opposite order to the door row's
   headline-over-supporting — and the reason this is a MODIFIER rather than a
   second component is that `.md-row__sub` at 10px is a whisper UNDER a
   headline, and a label ABOVE a value has to be readable on its own.

   Written here because the shape had already been forked once: crm/profile.css
   carries `.dt-row/.dt-lbl/.dt-val` privately for exactly this, and the profile
   tab was about to be the second copy.

     <div class="md-row md-row--data">
       <span class="md-row__lead md-tint-green">…glyph…</span>
       <span class="md-row__body">
         <span class="md-row__sub">नोंदणीकृत मोबाईल</span>
         <b class="md-row__title">98XXXXXX21</b>
       </span>
     </div>
   ────────────────────────────────────────────────────────────────────────── */
.md-row--data { cursor: default; }
.md-row--data .md-row__body { display: flex; flex-direction: column; gap: 1px; }
.md-row--data .md-row__sub {
    order: -1;
    font-size: 12px;
    line-height: 1.5;
    font-weight: var(--md-weight-medium);
    /* The full variant ink, not the whispered mix a supporting line takes: a
       label carries meaning here rather than trailing a headline. */
    color: var(--md-on-surface-variant);
}
/* Values wrap rather than ellipsise — a truncated address is useless, and this
   row has no second line to fall back on. */
.md-row--data .md-row__title,
.md-row--data .md-row__body b {
    white-space: normal;
    line-height: 1.4;
    overflow-wrap: anywhere;
}


/* ── Badge / status pill ────────────────────────────────────────────────── */
.md-badge {
    min-width: 24px;
    height: 24px;
    padding: 0 7px;
    border-radius: var(--md-shape-full);
    background: var(--md-error);
    color: var(--md-on-error);
    font-size: 12.5px;
    font-weight: var(--md-weight-semibold);
    display: grid;
    place-items: center;
}
.md-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 26px;
    padding: 0 12px;
    border-radius: var(--md-shape-full);
    font-size: 13px;
    font-weight: var(--md-weight-semibold);
    white-space: nowrap;
    background: var(--md-surface-container-high);
    color: var(--md-on-surface-variant);
}
.md-pill--ok   { background: var(--md-pos-container);   color: var(--md-on-pos-container); }
.md-pill--warn { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }
.md-pill--due  { background: var(--md-neg-container);   color: var(--md-on-neg-container); }
.md-pill .material-symbols-rounded { font-size: 17px; }

/* ── Cards ──────────────────────────────────────────────────────────────── */
.md-card {
    background: var(--md-surface-container-low);
    border-radius: var(--md-shape-lg);
    box-shadow: var(--md-elev-1);
}
.md-card--filled   { background: var(--md-surface-container-highest); box-shadow: none; }
.md-card--outlined { background: var(--md-surface); box-shadow: none;
                     border: 1px solid var(--md-outline-variant); }

/* ── Buttons ────────────────────────────────────────────────────────────── */
.md-btn {
    height: 52px;
    padding: 0 28px;
    border: 0;
    border-radius: var(--md-shape-full);
    font-family: var(--font-primary);
    font-size: 16px;
    font-weight: var(--md-weight-semibold);
    letter-spacing: .1px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    text-decoration: none;
    white-space: nowrap;
}
.md-btn .material-symbols-rounded { font-size: 22px; }
.md-btn--filled   { background: var(--md-primary); color: var(--md-on-primary); }
.md-btn--tonal    { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.md-btn--outlined { background: transparent; color: var(--md-primary);
                    border: 1px solid var(--md-outline); }
.md-btn--text     { background: transparent; color: var(--md-primary); padding: 0 12px; }

/* ── The small button ─────────────────────────────────────────────────────
   A library modifier that spent its life inside `crm/groups.css`, so every
   OTHER page that wrote `.md-btn--sm` got a full-height button and no error.
   It came home when the groups feature was split into pages (2026-08-29) and
   that sheet stopped being loaded everywhere the class was used. */
/* 44px, not the 36 it arrived with. It is the SMALL button, not the small
   TARGET — the type and the padding are what make it read as secondary, and a
   36px control fails the touch gate wherever it is used (the attendance
   screen's two bulk marks are the case that caught it). */
.md-btn--sm { height: 44px; padding: 0 14px; font-size: 13px; flex: 0 0 auto; }
.md-btn--sm .material-symbols-rounded { font-size: 18px; }
.md-btn[disabled] { opacity: .38; cursor: not-allowed; }
/* A button whose FEATURE is off, not one that is disabled: it is still
   tappable and still answers — with the maintenance modal — so it keeps
   the pointer and only dims, at the same .55 every other `.is-off` entry
   point on the app home uses. The class was already being written onto
   these buttons (CRM's hero CTA) with nothing to render it. */
.md-btn.is-off { opacity: .55; }

/* ── Extended FAB ───────────────────────────────────────────────────────── */
.md-fab {
    position: fixed;
    right: 16px;
    bottom: calc(20px + env(safe-area-inset-bottom));
    z-index: 90;
    height: 64px;
    padding: 0 26px;
    border: 0;
    /* A pill, and the action colour — the same two decisions `.md-cta` made on
       the module home. An extended FAB IS that screen's one filled action, so
       a 12px blue slab on the list and a crimson pill on the home were two
       answers to one question. `--md-cta` and its `on-` partner, so no literal
       white and nothing that fails to re-theme.

       The ramp is `.md-cta`'s: 90/10, because a fifth of the ink mixed into
       crimson is pink. */
    border-radius: var(--md-shape-full);
    background: linear-gradient(180deg,
                    color-mix(in srgb, var(--md-on-cta) 11%, transparent) 0%,
                    transparent 62%),
                linear-gradient(135deg,
                    var(--md-cta) 0%,
                    color-mix(in srgb, var(--md-cta) 90%, var(--md-on-cta)) 100%);
    color: var(--md-on-cta);
    box-shadow: var(--md-elev-3),
                inset 0 1px 0 color-mix(in srgb, var(--md-on-cta) 28%, transparent);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 12px;
    font-family: var(--font-primary);
    font-size: 17px;
    font-weight: var(--md-weight-semibold);
    text-decoration: none;
    transition: transform var(--md-dur-medium) var(--md-ease-emphasized);
}
.md-fab:active { transform: scale(.94); }

/* The FAB in the brand gradient rather than the crimson `--md-cta` above.
   A module whose Add buttons already wear `--grad-hero` should not switch to
   crimson only when the same action becomes a FAB — the colour is the thing
   an owner recognises, not the shape it arrives in. Carried privately by
   miniwebsite/catalogue.css until categories needed it too; the second
   consumer is where it stops being a page detail.

   White is a literal here for `--grad-hero`'s usual reason: the gradient is
   deliberately the same blue in both schemes, so a role token would invert to
   dark ink on blue after sunset. */
.md-fab--brand {
    background: var(--grad-hero);
    color: var(--md-on-brand);
    /* A pill cannot grow past the screen it floats on — the guard matters for
       a label longer than the English one. */
    max-width: calc(100vw - 32px);
}
.md-fab__lbl { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Stacked tabs — the icon over the label ─────────────────────────────────
   For a record with FOUR faces on a phone. The library's own row tabs put the
   icon beside the label, which at four tabs on a 360px screen leaves about
   88px each: "Customer" plus a 20px glyph plus its gap is 93, so every tab
   runs to its edge and the strip reads as squeezed. MD3's answer is the
   stacked tab — icon above the text — and it is what this variant is.

   The active indicator is the PILL behind the glyph rather than an underline,
   the same shape the navigation bar uses: it holds real colour where a 3px
   rule can only hint at it, and it leaves the label free to say the word
   without competing. The strip keeps its bottom rule, which is what still
   says "these are tabs" and not "this is a nav bar".

   A page tints the pill per tab; unstyled, it takes the brand. */
.md-tabs--stack button,
.md-tabs--stack a {
    flex-direction: column;
    gap: 3px;
    /* Auto height, not a fixed 64px: the label is allowed to be as tall as it
       needs and every tab still matches its neighbours, because the strip is a
       flex row and stretches them together. A fixed box was the bug — at four
       Devanagari labels it cut the text off mid-glyph. */
    height: auto;
    min-height: 68px;
    padding: 8px 3px 10px;
    font-size: 12.5px;
    /* 1.45, not 1.25: Devanagari carries matras above the line and below it,
       and a line box tighter than the glyph clips them — which is what a
       phone showed as "the tab labels are unreadable". */
    line-height: 1.45;
    justify-content: flex-start;
}
.md-tabs--stack .md-tabs__ic {
    position: relative;
    width: 46px; height: 26px;
    border-radius: var(--md-shape-full);
    display: grid; place-items: center;
    transition: background var(--md-dur-short) var(--md-ease);
}
.md-tabs--stack .material-symbols-rounded { font-size: 20px; }
/* The label wraps rather than ellipsising: a stacked tab is narrow by design
   and "Timeline" in Devanagari is longer than the box. It wraps as far as it
   needs to and NOTHING is clipped — the previous two-line clamp was an
   `overflow: hidden` box sized in Latin line boxes, so on a phone in Marathi
   or Hindi it sliced the tops off the words. A tab that cannot be read is not
   a tab. */
.md-tabs--stack .md-tabs__lbl {
    display: block;
    overflow: visible;
    overflow-wrap: break-word;
    text-align: center;
    width: 100%;
}
.md-tabs--stack button[aria-selected="true"],
.md-tabs--stack a[aria-selected="true"] { font-weight: var(--md-weight-semibold); }
.md-tabs--stack button[aria-selected="true"] .md-tabs__ic,
.md-tabs--stack a[aria-selected="true"] .md-tabs__ic {
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
}
/* The pill IS the indicator here, so the underline the row tabs draw would be
   a second one saying the same thing. */
.md-tabs--stack button[aria-selected="true"]::after,
.md-tabs--stack a[aria-selected="true"]::after { content: none; }
/* A count rides the pill's corner, as a badge does on a nav bar. */
.md-tabs--stack .md-tabs__n {
    position: absolute;
    top: -4px; right: 2px;
}

/* NOTE — `.md-tabs--seg` used to be declared HERE, ~550 lines above the base
   `.md-tabs` block it modifies. Every one of its rules collides with the base
   at equal specificity, so source order decided them and the BASE won all of
   them: the variant's 42px height, its 12.5px label, its `border-bottom: 0`
   and its `content: none` on the indicator were all silently discarded, and a
   segmented strip rendered as a 56px underlined one inside a pill-shaped
   trough. Moved below the base — see "Segmented tabs" further down. */

/* A count riding a tab — hidden when empty, so a tab with nothing in it does
   not carry a zero. */
.md-tabs__n {
    font-size: 10.5px; font-weight: var(--md-weight-semibold);
    min-width: 16px; height: 16px; padding: 0 4px;
    border-radius: var(--md-shape-full);
    background: var(--md-primary-container); color: var(--md-on-primary-container);
    display: inline-flex; align-items: center; justify-content: center;
}
.md-tabs__n:empty { display: none; }

/* ── The brand band ─────────────────────────────────────────────────────────
   A sticky block that holds the app bar AND whatever belongs with it — a
   search, usually — on one sheet of brand colour.

   The wrapper exists because it is the ONE element that paints the gradient.
   Two siblings each drawing `--grad-hero` would each run their own 135° ramp
   over their own box, and the join between them shows as a visible band; one
   painted parent has no join to hide, at any bar height, on any device, with
   nothing to measure and nothing to keep in step.

   The bar keeps `.md-appbar--hero` for its INK (white title, white icons)
   and hands over only its background. Its own `position: sticky` goes too —
   the wrapper is the sticky thing now, and a sticky child of a sticky parent
   is one more thing that can drift.

   Carried privately by miniwebsite/catalogue.css until categories wanted the
   same band; the second consumer is where it stops being a page detail. */
.md-topbar {
    position: sticky; top: 0; z-index: 100;
    background: var(--grad-hero);
}
.md-topbar .md-appbar {
    position: static;
    background: none;
    box-shadow: none;
}
.md-topbar__body { padding: 0 14px 14px; }

/* A field ON the gradient. The library search is a filled grey pill for a
   grey page; here it takes a white wash with a white hairline, and its glyph,
   its text and its clear button take white too — the same literal-white
   argument `.md-appbar--onhero` makes, for the same reason: `--grad-hero` is
   deliberately the same blue in both schemes, so a role token in here would
   go pale-on-pale after sunset. */
.md-topbar .md-search__input {
    height: 48px;
    background: rgba(255, 255, 255, .16);
    border-color: rgba(255, 255, 255, .28);
    color: #FFFFFF;
    font-size: 15px;
}
/* The base `:focus` draws its ring in `--md-primary`, which on this band is
   blue on blue and invisible — so the first version simply removed it, and
   left the field with NO focus indicator at all: a .28→.55 shift in a border's
   alpha is not one, and `tests/a11y_render.js` reports the field as unfocusable
   on every page that carries the band (stock, catalogue, faqs, gallery and
   customerview/my_favorites). The ring is white instead, which is the one
   colour guaranteed to read on `--grad-hero` in both schemes. */
.md-topbar .md-search__input:focus {
    background: rgba(255, 255, 255, .24);
    border-color: #FFFFFF;
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .92);
}
.md-topbar .md-search__icon,
.md-topbar .md-search__clear { color: rgba(255, 255, 255, .92); }

/* ── Filter row, with the sort pinned to it ─────────────────────────────────
   Two different questions get two different controls: the chipset asks WHAT
   to show and scrolls, the sort chip asks IN WHAT ORDER and does not — a
   control that can scroll out of sight is one an owner has to go looking
   for. The sort chip wears the current order as its label, so the answer is
   readable without opening anything. */
.md-filterbar { display: flex; align-items: center; gap: 8px; }
.md-filterbar .md-chipset { flex: 1; min-width: 0; }
/* Tonal, so it reads as a control that OPENS something rather than as one
   more filter that can be on or off. */
.md-sortchip {
    flex: 0 0 auto;
    background: var(--md-secondary-container);
    color: var(--md-on-secondary-container);
    border-color: transparent;
    font-weight: var(--md-weight-semibold);
    gap: 6px;
    max-width: 46%;
}
.md-sortchip > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* One order per row, exactly one ticked. The tick keeps its space on every
   row so the list does not shift sideways as the choice moves. */
.md-sortopt__tick { color: var(--md-primary); visibility: hidden; }
.md-sortopt.is-on .md-sortopt__tick { visibility: visible; }
.md-sortopt.is-on .md-row__body b { color: var(--md-primary); }
/* A sort option's label is a SENTENCE — "Date Added (Latest first)" — where
   the library row is tuned for a NAME and so clips to one line with an
   ellipsis. That cuts every one of these in Devanagari, where the same
   phrase runs longer: "ऍड केल्याची तारीख (नवीन आधी)". Two lines, then clip. */
.md-sortopt .md-row__body b {
    white-space: normal;
    font-size: 15px;
    line-height: 1.35;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ── Chips ──────────────────────────────────────────────────────────────── */
.md-chipset {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 2px;
}
.md-chipset::-webkit-scrollbar { display: none; }
.md-chip {
    flex: 0 0 auto;
    height: 44px;
    padding: 0 18px;
    border-radius: var(--md-shape-sm);
    border: 1px solid var(--md-outline);
    background: transparent;
    color: var(--md-on-surface-variant);
    font-family: var(--font-primary);
    font-size: 15px;
    font-weight: var(--md-weight-medium);
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    white-space: nowrap;
}
.md-chip .material-symbols-rounded { font-size: 20px; }
.md-chip.is-active,
.md-chip[aria-pressed="true"] {
    background: var(--md-secondary-container);
    color: var(--md-on-secondary-container);
    border-color: transparent;
}

/* ── Empty state ────────────────────────────────────────────────────────── */
.md-empty {
    padding: 40px 24px;
    text-align: center;
    color: var(--md-on-surface-variant);
}
.md-empty__ic {
    width: 64px; height: 64px; margin: 0 auto 16px;
    border-radius: var(--md-shape-full);
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
    display: grid; place-items: center;
}
.md-empty__ic .material-symbols-rounded { font-size: 32px; }
.md-empty__title { font-size: 16px; font-weight: var(--md-weight-medium); color: var(--md-on-surface); margin-bottom: 6px; }
.md-empty__msg   { font-size: 14px; line-height: 20px; }
/* The way OUT of being empty.

   Optional, and its own element rather than a bare button after the message:
   most empty states are simply an answer ("you have none yet") and want no
   action at all, but the ones that FOLLOW a question the app could not answer
   — a search with no match — leave the user holding a name and nowhere to put
   it. There, the offer to create the thing they searched for is the whole
   point of the state. Capped, so it does not stretch into a banner on a
   tablet. */
.md-empty__act { margin-top: 20px; display: flex; justify-content: center; }
/* Capped, so it does not stretch into a banner on a tablet — and allowed to
   grow TALLER rather than clip: `.md-btn` is a fixed 52px of nowrap, which is
   right for a bar the layout controls and wrong here, where the label is a
   whole Marathi sentence inside whatever width the empty state has left. */
.md-empty__act .md-btn {
    width: 100%; max-width: 320px;
    height: auto; min-height: 52px;
    padding: 10px 18px;
}
.md-empty__act .md-btn__label { white-space: normal; line-height: 1.35; }

/* ── Retry panel ────────────────────────────────────────────────────────────
   What a screen shows when its data did not arrive and the retries in
   Platform.api are spent (see platform-core.js §4b, and API_RESILIENCY.md).

   Deliberately NOT .md-empty: an empty list is an answer — you have no
   customers — and a failed load is the absence of an answer. Painting them the
   same taught users to read "no internet" as "nothing here". So the circle is
   error-toned rather than brand-toned, and this one carries a button, because
   after a failure there is exactly one thing a person wants to do. */
.md-retry {
    padding: 36px 24px;
    text-align: center;
    color: var(--md-on-surface-variant);
}
.md-retry__ico {
    width: 64px; height: 64px; margin: 0 auto 16px;
    border-radius: var(--md-shape-full);
    background: var(--md-error-container);
    color: var(--md-on-error-container);
    display: grid; place-items: center;
}
.md-retry__ico .material-symbols-rounded { font-size: 32px; }
.md-retry__title {
    display: block; margin-bottom: 6px;
    font-size: 16px; font-weight: var(--md-weight-semibold); color: var(--md-on-surface);
}
.md-retry__msg {
    display: block; max-width: 34ch; margin: 0 auto 18px;
    font-size: 14px; line-height: 20px;
}
.md-retry__btn { display: inline-flex; }

/* The compact form (platform-core.js §4c): one row, for a screen whose LIST is
   fine and whose figures are not. Sits where the numbers would have been. */
.md-retry-inline {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px;
    border-radius: var(--md-shape-md);
    background: var(--md-error-container);
    color: var(--md-on-error-container);
}
.md-retry-inline__ic { font-size: 20px; flex-shrink: 0; }
.md-retry-inline__msg {
    flex: 1; min-width: 0;
    font-size: 13px; line-height: 1.45; font-weight: var(--md-weight-medium);
}
.md-retry-inline__btn { flex-shrink: 0; height: 40px; padding: 0 12px; font-size: 13px; }

/* ── Waiting: ONE shimmer, no spinners (documentation/ai-docs/LOADING.md) ───
   The app has exactly one way to say "wait": the grey shape of what is coming,
   with a light passing over it. No spinner, no overlay, no "Loading…" text.

   `.md-skl` is the shape. It draws in the final layout's own box, so nothing
   jumps when the real content lands (motion M5 fades that in).

   Two details make it feel instant rather than slow:
   • It waits 150ms before it shows. An answer that comes back faster never
     flashes grey at all; the page just fills.
   • Every block sweeps at the same pace and angle, so a screen of them reads
     as one light moving down the page, not thirty flickers.

   `.md-skl` on an inline figure (a count, an amount) needs a width: give it
   `.md-skl--fig` or a width from the page sheet. The old per-page class names
   (`.skl`, `-skl`, `-shimmer`) are all this rule now; LOADING.md lists them. */
:root {
    --md-skl-base: var(--md-surface-container);
    --md-skl-hi:   var(--md-surface-container-high);
    --md-skl-bg: linear-gradient(100deg,
        var(--md-skl-base) 30%,
        var(--md-skl-hi) 50%,
        var(--md-skl-base) 70%) 0 0 / 300% 100%;
    --md-skl-anim: md-skl 1.5s cubic-bezier(.4, 0, .2, 1) infinite, md-skl-in 250ms ease 150ms backwards;
}
.md-skl {
    background: var(--md-skl-bg);
    animation: var(--md-skl-anim);
    border-radius: var(--md-shape-xs);
    color: transparent !important;
    -webkit-text-fill-color: transparent;
    pointer-events: none;
    user-select: none;
}
/* Zero-weight tag match, so every modifier below still sets its own display. */
:where(span, b, i, em, strong).md-skl { display: inline-block; vertical-align: middle; }
.md-skl--line  { display: block; height: 14px; width: 100%; border-radius: var(--md-shape-full); }
.md-skl--line + .md-skl--line { margin-top: 10px; width: 62%; }
.md-skl--sub   { display: block; height: 11px; width: 40%; border-radius: var(--md-shape-full); margin-top: 8px; }
.md-skl--fig   { min-width: 56px; min-height: 1em; border-radius: var(--md-shape-sm); }
.md-skl--circle { border-radius: var(--md-shape-full); }
@keyframes md-skl {
    0%   { background-position: 100% 0; }
    100% { background-position: 0 0; }
}
@keyframes md-skl-in { from { opacity: 0; } }

/* A list of rows that is still coming: the same box as `.md-row`, a lead
   square and two lines. Platform.skel.rows(n) writes it. */
.md-skl-row {
    display: flex; align-items: center; gap: 12px;
    min-height: 64px; padding: 9px 14px;
}
.md-skl-row + .md-skl-row { box-shadow: inset 0 1px 0 var(--md-outline-variant); }
.md-skl-row__lead { width: 44px; height: 44px; flex: 0 0 44px; border-radius: var(--md-shape-md); }
.md-skl-row__body { flex: 1; min-width: 0; }
.md-skl-row__body .md-skl--line { width: 62%; }
.md-skl-row__body .md-skl--sub  { width: 38%; }
.md-skl-row:nth-child(3n+2) .md-skl-row__body .md-skl--line { width: 48%; }
.md-skl-row:nth-child(3n) .md-skl-row__body .md-skl--line   { width: 70%; }
.md-skl-row__tail { width: 52px; height: 14px; border-radius: var(--md-shape-full); flex: 0 0 auto; }
.md-skl-card  { display: block; height: 96px; border-radius: var(--md-shape-lg); }
.md-skl-card + .md-skl-card { margin-top: 12px; }
.md-skl-grid  { display: grid; grid-template-columns: repeat(var(--md-skl-cols, 3), 1fr); gap: 8px; }
.md-skl-grid > .md-skl { aspect-ratio: 1; border-radius: var(--md-shape-md); }

/* Something the person asked for is on its way — a Save, a lookup, an upload.
   The control keeps its label and its place, stops taking taps, and the same
   light passes over it. Platform.busy(el, true|false) sets it. */
.is-busy, .md-btn.loading, [aria-busy="true"].md-btn, [aria-busy="true"].md-row {
    pointer-events: none;
    cursor: progress;
}
/* Zero specificity: a page that positions its control absolutely keeps it. */
:where(.is-busy, .md-btn.loading) { position: relative; overflow: hidden; isolation: isolate; }
.is-busy::before, .md-btn.loading::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, .38) 50%, transparent 80%);
    background: linear-gradient(100deg, transparent 20%, color-mix(in srgb, currentColor 20%, transparent) 50%, transparent 80%);
    transform: translateX(-100%);
    animation: md-sweep 1.5s cubic-bezier(.4, 0, .2, 1) infinite;
}
@keyframes md-sweep { to { transform: translateX(100%); } }

/* Spinner classes that predate this rule: whatever is left of them shimmers
   in place and never turns. New code writes `.md-skl` shapes instead. */
.md-circular, .spinner, .pt-spinner, .md-btn__loader {
    display: inline-block;
    width: 24px; height: 24px;
    border: 0 !important;
    border-radius: var(--md-shape-full);
    background: var(--md-skl-bg);
    animation: var(--md-skl-anim) !important;
}
.md-btn__loader, .md-btn.loading .md-btn__loader { display: none !important; }
/* Indeterminate progress is the same light on the track, not a moving bar. */
.md-linear--indet {
    background: var(--md-skl-bg);
    animation: md-skl 1.5s cubic-bezier(.4, 0, .2, 1) infinite;
}
.md-linear--indet i { display: none; }

@media (prefers-reduced-motion: reduce) {
    /* Page sheets that paint their own shapes with the two vars comply too. */
    :root { --md-skl-bg: var(--md-skl-hi); --md-skl-anim: none; }
    .md-skl, .md-linear--indet, .md-circular, .spinner, .pt-spinner { animation: none !important; }
    .md-skl { background: var(--md-skl-hi); }
    .is-busy::before, .md-btn.loading::before { animation: none; transform: none; opacity: .5; }
}

/* ==========================================================================
   PART 3 — the rest of the set
   Promoted from m3-components.html / m3-standards.html so a module page
   contains layout and content, not a private copy of a switch.

   Sizes follow APP_STANDARDS §5: nothing tappable is under 48px.
   Rules that look fussy are in APP_STANDARDS §0 — assume the user may not read.
   ========================================================================== */

/* ── Selection controls ─────────────────────────────────────────────────────
   The native input stays in the DOM for forms and screen readers; it is
   visually hidden and the sibling span is what is painted. */
.md-vh { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }

.md-ctrl {
    display: flex;
    align-items: center;
    gap: 14px;
    min-height: 56px;
    padding: 13px 6px;
    border-radius: var(--md-shape-sm);
    font-size: 16px;
    cursor: pointer;
}
/* The control's label block. It belongs to `.md-ctrl` and lived in
   crm/groups.css instead — a page sheet defining part of a shared component,
   which meant every OTHER page using `.md-ctrl` got a row whose title and hint
   ran together on one line. crm/custom_fields.php found it that way. */
.md-ctrl__tx {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; gap: 2px;
}
/* `.md-ctrl` as a <button>, for the same reason `.md-row` needed it: a control
   row that OPENS something (a picker, a sheet) rather than toggling a checkbox
   has to be a button, and then the UA defaults take over — shrink-to-fit width,
   centred text, and the page's font replaced by the button font. First needed by
   the miniwebsite home's "default website language" row. */
button.md-ctrl {
    width: 100%;
    text-align: left;
    font-family: var(--font-primary);
    border: 0;
    background: transparent;
}
.md-ctrl__tx b    { font-size: 15px; line-height: 21px; font-weight: var(--md-weight-semibold); color: var(--md-on-surface); }
.md-ctrl__tx span { font-size: 13px; line-height: 18px; font-weight: var(--md-weight-medium); color: var(--md-on-surface-variant); }

/* The asterisk on a required field's label. Same story — page-local in
   groups.css, needed by every form. */
.md-req { color: var(--md-error); font-weight: var(--md-weight-semibold); }

/* Destructive, and bounded — findable without being the loudest thing in a
   dialog whose point is usually the button next to it.
   ────────────────────────────────────────────────────────────────────────────
   NOT `--md-error-container` as a fill. That role is a soft pink in light and a
   SOLID saturated red in dark, so the same rule gave a quiet button on one
   theme and a stop sign on the other — crm/profile.css had already written that
   note twice, for `.dc-acts .icon-btn.danger` and `.av-foot .icon-btn.danger`,
   before this modifier existed to make the same mistake a third time. An
   error-tinted outline and error ink read as destructive on both. */
.md-btn--danger {
    background: color-mix(in srgb, var(--md-error) 8%, transparent);
    color: var(--md-error);
    box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--md-error) 38%, transparent);
}

/* A hint that is not attached to a control — a sentence introducing a section
   rather than explaining a field. */
.md-hint--block { display: block; margin: 0 0 10px; }
.md-box {
    width: 26px; height: 26px; flex: 0 0 26px;
    border-radius: 6px;
    border: 2.5px solid var(--md-outline);
    display: grid; place-items: center;
    transition: background var(--md-dur-short) var(--md-ease),
                border-color var(--md-dur-short) var(--md-ease);
}
.md-box .material-symbols-rounded {
    font-size: 20px; color: var(--md-on-primary);
    opacity: 0; transform: scale(.6);
    transition: opacity var(--md-dur-short) var(--md-ease),
                transform var(--md-dur-short) var(--md-ease-emphasized);
}
input:checked + .md-box,
input:indeterminate + .md-box { background: var(--md-primary); border-color: var(--md-primary); }
input:checked + .md-box .material-symbols-rounded { opacity: 1; transform: scale(1); }

.md-dot {
    width: 26px; height: 26px; flex: 0 0 26px;
    border-radius: var(--md-shape-full);
    border: 2.5px solid var(--md-outline);
    display: grid; place-items: center;
}
.md-dot::after {
    content: ""; width: 13px; height: 13px;
    border-radius: var(--md-shape-full);
    background: var(--md-primary);
    transform: scale(0);
    transition: transform var(--md-dur-short) var(--md-ease-emphasized);
}
input:checked + .md-dot { border-color: var(--md-primary); }
input:checked + .md-dot::after { transform: scale(1); }

.md-switch {
    width: 60px; height: 36px; flex: 0 0 60px;
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-highest);
    box-shadow: inset 0 0 0 2px var(--md-outline);
    position: relative;
    transition: background var(--md-dur-medium) var(--md-ease),
                box-shadow var(--md-dur-medium) var(--md-ease);
}
.md-switch::after {
    content: ""; position: absolute; top: 7px; left: 7px;
    width: 18px; height: 18px;
    border-radius: var(--md-shape-full);
    background: var(--md-outline);
    transition: all var(--md-dur-medium) var(--md-ease-emphasized);
}
input:checked + .md-switch { background: var(--md-primary); box-shadow: none; }
input:checked + .md-switch::after {
    left: 29px; top: 5px; width: 26px; height: 26px;
    background: var(--md-on-primary);
}

/* ── Status — colour + icon + word, always all three (APP_STANDARDS §2.3) ──
   A user who reads none of the words still has two channels. Never ship a
   status that is colour alone. */
.md-status {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 14px 7px 10px;
    border-radius: var(--md-shape-full);
    font-size: 14px; font-weight: var(--md-weight-semibold); white-space: nowrap;
}
.md-status .material-symbols-rounded { font-size: 20px; }
.md-status--paid  { background: var(--md-pos-container);       color: var(--md-on-pos-container); }
.md-status--pend  { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }
.md-status--due   { background: var(--md-neg-container);       color: var(--md-on-neg-container); }
.md-status--draft { background: var(--md-surface-container-highest); color: var(--md-on-surface-variant); }

/* The same states as a leading stripe, so a list can be scanned without
   reading anything. Requires position:relative on the row. */
.md-stripe {
    position: absolute; left: 0; top: 10px; bottom: 10px;
    width: 5px; border-radius: 0 4px 4px 0;
}
.md-stripe--paid { background: var(--md-pos); }
.md-stripe--pend { background: var(--md-secondary); }
.md-stripe--due  { background: var(--md-neg); }
.md-stripe--draft{ background: var(--md-outline); }

/* ── Money ─────────────────────────────────────────────────────────────────
   Digits are the first thing a low-literacy user learns and the last they
   lose, so the number gets the size. Direction is an arrow, a sign and a
   colour — never the words "credit" and "debit". */
.md-money {
    font-variant-numeric: tabular-nums;
    font-weight: var(--md-weight-semibold);
    letter-spacing: -.02em;
    white-space: nowrap;
}
.md-money--hero { font-size: 40px; line-height: 1.1; }
.md-money--lg   { font-size: 26px; }
.md-money--in   { color: var(--md-pos); }
.md-money--out  { color: var(--md-neg); }

/* ── Voice input ───────────────────────────────────────────────────────────
   Primary path, not a fallback: typing Devanagari on a phone keyboard is the
   hardest thing we ask. Every free-text field carries one. */
.md-voice {
    display: flex; align-items: center; gap: 12px;
    width: 100%; min-height: 64px;
    padding: 0 8px 0 18px;
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-high);
    border: 2px dashed var(--md-outline);
    color: var(--md-on-surface-variant);
    font-family: var(--font-primary); font-size: 16px; text-align: left;
    cursor: pointer;
}
.md-voice__mic {
    width: 52px; height: 52px; flex: 0 0 52px; margin-left: auto;
    border-radius: var(--md-shape-full);
    background: var(--md-primary); color: var(--md-on-primary);
    display: grid; place-items: center;
}
.md-voice__mic .material-symbols-rounded { font-size: 26px; }
.md-voice.is-live {
    border-style: solid; border-color: var(--md-primary);
    background: var(--md-primary-container); color: var(--md-on-primary-container);
}
.md-voice.is-live .md-voice__mic { animation: md-mic 1.4s ease-out infinite; }
@keyframes md-mic {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--md-primary) 55%, transparent); }
    70%  { box-shadow: 0 0 0 16px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}
/* Mic inside a normal text field. */
.md-field-mic {
    position: absolute; right: 9px;
    width: 44px; height: 44px;
    border: 0; border-radius: var(--md-shape-full);
    background: var(--md-primary-container); color: var(--md-on-primary-container);
    display: grid; place-items: center; cursor: pointer;
}
.md-field-mic .material-symbols-rounded { font-size: 24px; }

/* ── Listen — text-to-speech on anything the user must act on ──────────── */
.md-listen {
    display: inline-flex; align-items: center; gap: 8px;
    height: 44px; padding: 0 16px 0 12px;
    border-radius: var(--md-shape-full);
    border: 1.5px solid var(--md-outline);
    background: transparent; color: var(--md-on-surface-variant);
    font-family: var(--font-primary); font-size: 14px; font-weight: var(--md-weight-semibold);
    cursor: pointer;
}
.md-listen .material-symbols-rounded { font-size: 22px; color: var(--md-primary); }

/* ── Quantity stepper — count, never type. Use under about twenty. ─────── */
.md-qty {
    display: inline-flex; align-items: center;
    padding: 5px; border-radius: var(--md-shape-full);
    background: var(--md-surface-container-high);
}
.md-qty button {
    width: 52px; height: 52px;
    border: 0; border-radius: var(--md-shape-full);
    background: var(--md-surface-container-lowest); color: var(--md-primary);
    box-shadow: var(--md-elev-1);
    display: grid; place-items: center; cursor: pointer;
}
.md-qty button .material-symbols-rounded { font-size: 28px; }
.md-qty__v {
    min-width: 76px; text-align: center;
    font-size: 26px; font-weight: var(--md-weight-semibold); font-variant-numeric: tabular-nums;
}

/* ── Number pad — a calculator, not a keyboard ─────────────────────────────
   Never type="number" for money: it offers a decimal point, a minus sign and
   an exponent key, none of which belong here. */
.md-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.md-pad button {
    height: 64px; border: 0;
    border-radius: var(--md-shape-md);
    background: var(--md-surface-container-high); color: var(--md-on-surface);
    font-family: var(--font-primary);
    font-size: 26px; font-weight: var(--md-weight-semibold); font-variant-numeric: tabular-nums;
    cursor: pointer;
}
.md-pad button.is-act {
    background: var(--md-primary-container); color: var(--md-on-primary-container);
}
.md-pad__out {
    background: var(--md-surface-container-highest);
    border-radius: var(--md-shape-md);
    padding: 16px 18px; margin-bottom: 14px; text-align: right;
    font-size: 34px; font-weight: var(--md-weight-semibold);
    font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}

/* ── Photo capture — a picture beats any form we could design ───────────── */
.md-photo {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 10px; width: 100%; min-height: 132px;
    border-radius: var(--md-shape-lg);
    border: 2px dashed var(--md-outline);
    background: var(--md-surface-container);
    color: var(--md-on-surface-variant);
    font-family: var(--font-primary); font-size: 15px; font-weight: var(--md-weight-semibold);
    cursor: pointer;
}
.md-photo__ic {
    width: 60px; height: 60px;
    border-radius: var(--md-shape-full);
    background: var(--md-primary-container); color: var(--md-on-primary-container);
    display: grid; place-items: center;
}
.md-photo__ic .material-symbols-rounded { font-size: 30px; }

/* ── Tabs ──────────────────────────────────────────────────────────────── */
.md-tabs { display: flex; border-bottom: 1px solid var(--md-outline-variant); }
/* `display: flex` beats the user-agent's `[hidden] { display: none }`, so a
   strip a page hides still paints its full height and its bottom rule — 50px
   of empty bordered band with nothing in it. `crm/customer_activity.php` hides
   its strip whenever the Bookings module is on, and shipped that gap between
   the customer's card and the first control for as long as the flag has
   existed. Any consumer that hides a tab strip had the same bug. */
.md-tabs[hidden] { display: none; }
/* `a` as well as `button`: when a tab strip navigates between PAGES rather
   than switching panels inside one, each tab is a link — the browser's back
   button then works, a tab can be opened in a new one, and there is no
   selected-state to keep in sync. `aria-selected` still drives the indicator,
   so both spellings paint identically. */
.md-tabs button,
.md-tabs a {
    flex: 1; height: 56px;
    border: 0; background: transparent; cursor: pointer;
    font-family: var(--font-primary); font-size: 14px; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    position: relative;
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.md-tabs button .material-symbols-rounded,
.md-tabs a .material-symbols-rounded { font-size: 20px; }
.md-tabs a { text-decoration: none; }
.md-tabs button[aria-selected="true"],
.md-tabs a[aria-selected="true"] { color: var(--md-primary); }
.md-tabs button[aria-selected="true"]::after,
.md-tabs a[aria-selected="true"]::after {
    content: ""; position: absolute; left: 20%; right: 20%; bottom: 0;
    height: 3px; border-radius: 3px 3px 0 0; background: var(--md-primary);
}

/* ── Segmented tabs ─────────────────────────────────────────────────────────
   The pill-in-a-trough variant, for a RECORD with several faces — a booking,
   an order — as opposed to the underlined `.md-tabs` that head a list. The
   selected tab is a raised pill rather than a line, which reads as "you are
   inside this one" rather than "this section is next".

   Written for bookings/booking_details.css; order-view.php is the second
   record page to want it, which is where it stops being a page detail. Four
   tabs at 12.5px is the ceiling on a 360px screen — past that, drop the
   icons before you shrink the words.

   DECLARED AFTER THE BASE, and it has to stay that way: every rule below
   collides with `.md-tabs` at equal specificity, so the later block is the one
   that wins. It sat above the base until 2026-09-04, which meant the variant
   did nothing at all — a segmented strip rendered 56px tall, underlined, with
   the base's 14px label. Nothing had adopted it yet, so nothing looked broken
   until miniwebsite/theme_customize.php became the first consumer. */
.md-tabs--seg {
    border-bottom: 0;
    background: var(--md-surface-container-high);
    border-radius: var(--md-shape-full);
    padding: 4px;
    gap: 2px;
}
.md-tabs--seg button {
    display: flex; align-items: center; justify-content: center; gap: 5px;
    height: 42px;
    min-width: 0;
    border-radius: var(--md-shape-full);
    font-family: var(--font-primary);
    font-size: 12.5px; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    position: relative;
}
.md-tabs--seg button .material-symbols-rounded { font-size: 18px; }
.md-tabs--seg button[aria-selected="true"] {
    background: var(--md-surface-container-lowest, var(--md-surface));
    color: var(--md-primary);
    font-weight: var(--md-weight-semibold);
    box-shadow: var(--md-elev-1);
}
/* The underlined variant draws its indicator with ::after; a pill has none. */
.md-tabs--seg button[aria-selected="true"]::after { content: none; }
.md-tabs--secondary button[aria-selected="true"],
.md-tabs--secondary a[aria-selected="true"] { color: var(--md-on-surface); }
.md-tabs--secondary button[aria-selected="true"]::after,
.md-tabs--secondary a[aria-selected="true"]::after { left: 0; right: 0; }

/* ── Segmented button ──────────────────────────────────────────────────── */
.md-seg {
    display: inline-flex;
    border-radius: var(--md-shape-full);
    overflow: hidden;
    box-shadow: inset 0 0 0 1px var(--md-outline);
}
.md-seg button {
    height: 52px; padding: 0 20px;
    border: 0; border-right: 1px solid var(--md-outline);
    background: transparent; cursor: pointer;
    font-family: var(--font-primary); font-size: 14px; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    display: inline-flex; align-items: center; gap: 7px;
}
.md-seg button:last-child { border-right: 0; }
.md-seg button[aria-pressed="true"] {
    background: var(--md-secondary-container); color: var(--md-on-secondary-container);
}
/* A segmented control used as a form field: it takes the row and its choices
   share it equally (sign-up's Male / Female / Other, under the date of birth).
   Plain .md-seg is inline and hugs its words, which is right in a toolbar and
   wrong under a label in a one-column form. Shared, not in a page sheet: any
   form asking a short single choice wants the same field. Same 52px choices
   and the same selected colours as .md-seg; `.has-error` is the outline of a
   field that was left empty. */
.md-seg--fill { display: flex; width: 100%; }
.md-seg--fill button {
    flex: 1 1 0; min-width: 0;
    justify-content: center;
    padding: 0 8px;
    font-size: 15px;
}
.md-seg--fill button[aria-pressed="true"] { font-weight: var(--md-weight-semibold); }
.md-seg--fill button:focus-visible { outline: 2px solid var(--md-primary); outline-offset: -2px; }
.md-seg--fill.has-error { box-shadow: inset 0 0 0 1px var(--md-error); }

/* ── Navigation bar (§8.5) ─────────────────────────────────────────────── */
.md-nav {
    display: flex;
    background: var(--md-surface-container-low);
    border-top: 1px solid var(--md-outline-variant);
    padding: 10px 4px calc(14px + env(safe-area-inset-bottom));
}
/* `button` as well as `a`: a bar that switches views in place is not
   navigating anywhere, and a link with no href is not a link. Both shapes get
   the identical treatment so the markup can tell the truth about what it does. */
.md-nav a,
.md-nav button {
    flex: 1;
    display: flex; flex-direction: column; align-items: center; gap: 4px;
    font-size: 12.5px; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface-variant); text-decoration: none;
    border: 0; background: none; padding: 0;
    font-family: var(--font-primary); cursor: pointer;
}
.md-nav a .material-symbols-rounded,
.md-nav button .material-symbols-rounded {
    font-size: 26px; padding: 5px 22px;
    border-radius: var(--md-shape-full);
    transition: background var(--md-dur-medium) var(--md-ease-emphasized);
}
/* Active destination. Three channels at once — a filled glyph, a tinted pill
   and a weighted label — because this bar is the app's primary orientation cue
   and colour alone is one channel. `--md-nav-bg` / `--md-nav-ink` let a bar
   give each destination its OWN hue: only one is ever lit, so it reads as
   "where I am" rather than as five things competing, and a user who does not
   read the labels navigates by which colour is glowing. */
.md-nav a.is-active,
.md-nav button.is-active {
    color: var(--md-nav-ink, var(--md-on-primary-container));
    font-weight: var(--md-weight-semibold);
}
.md-nav a.is-active .material-symbols-rounded,
.md-nav button.is-active .material-symbols-rounded {
    background: var(--md-nav-bg, var(--md-primary-container));
    color: var(--md-nav-ink, var(--md-on-primary-container));
    font-variation-settings: 'FILL' 1;
}
.md-nav--blue  { --md-nav-bg: var(--md-primary-container);   --md-nav-ink: var(--md-on-primary-container); }
.md-nav--teal  { --md-nav-bg: var(--md-tertiary-container);  --md-nav-ink: var(--md-on-tertiary-container); }
.md-nav--green { --md-nav-bg: var(--md-pos-container);       --md-nav-ink: var(--md-on-pos-container); }
.md-nav--amber { --md-nav-bg: var(--md-warn-container);      --md-nav-ink: var(--md-on-warn-container); }
.md-nav--slate { --md-nav-bg: var(--md-secondary-container); --md-nav-ink: var(--md-on-secondary-container); }

/* Elevated centre action — the one thing in the bar that creates rather than
   navigates, so it is not a tab and does not take the active state. It sits in
   the flow (not absolutely positioned) and lifts out of the bar, which keeps
   the four tabs evenly spaced whether or not it is rendered. */
/* Written as `.md-nav button…` deliberately: the tab reset above it is
   `.md-nav button`, which outranks a lone class and would otherwise strip the
   gradient straight back off. */
.md-nav button.md-nav__center,
.md-nav__center {
    flex: 0 0 auto;
    align-self: center;
    width: 58px; height: 58px;
    margin: -22px 6px 0;
    border: 0; border-radius: var(--md-shape-lg);
    background: var(--grad-hero);
    color: #FFFFFF;
    display: grid; place-items: center;
    cursor: pointer;
    box-shadow: var(--md-elev-3);
    transition: transform var(--md-dur-short) var(--md-ease-emphasized);
}
.md-nav__center .material-symbols-rounded { font-size: 30px; }
.md-nav__center:active { transform: scale(.92); }

/* ── Menu ──────────────────────────────────────────────────────────────── */
.md-menu {
    min-width: 210px;
    background: var(--md-surface-container-low);
    border-radius: var(--md-shape-md);
    /* elev-2 is an inset ring under the flat theme — an explicit border here
       would double the line. */
    box-shadow: var(--md-elev-2);
    padding: 8px 0;
}
.md-menu a {
    display: flex; align-items: center; gap: 14px;
    height: 56px; padding: 0 16px;
    font-size: 16px; text-decoration: none; color: var(--md-on-surface);
}
.md-menu a .material-symbols-rounded { font-size: 20px; color: var(--md-on-surface-variant); }
.md-menu a.is-danger,
.md-menu a.is-danger .material-symbols-rounded { color: var(--md-error); }

/* ── Progress ──────────────────────────────────────────────────────────── */
.md-linear {
    height: 4px; border-radius: 2px;
    background: var(--md-surface-container-highest);
    overflow: hidden;
}
.md-linear i { display: block; height: 100%; border-radius: 2px; background: var(--md-primary); }
/* `.md-linear--indet` and `.md-circular` are in "Waiting" above: a shimmer. */
@media (prefers-reduced-motion: reduce) {
    .md-voice.is-live .md-voice__mic { animation: none; }
}

/* ── Wizard stepper — ticks, not step numbers ──────────────────────────────
   "Step 2 of 4" requires reading. A filled tick does not. */
.md-steps { display: flex; align-items: center; }
.md-steps__st {
    display: flex; flex-direction: column; align-items: center; gap: 7px;
    flex: 0 0 64px;
}
.md-steps__b {
    width: 44px; height: 44px;
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-highest); color: var(--md-on-surface-variant);
    display: grid; place-items: center;
    font-size: 17px; font-weight: var(--md-weight-semibold);
}
.md-steps__b .material-symbols-rounded { font-size: 24px; }
.md-steps__st.is-done .md-steps__b { background: var(--md-pos); color: var(--md-on-pos); }
.md-steps__st.is-now  .md-steps__b {
    background: var(--md-primary); color: var(--md-on-primary);
    box-shadow: 0 0 0 5px var(--md-primary-container);
}
.md-steps__st small { font-size: 12px; font-weight: var(--md-weight-semibold); color: var(--md-on-surface-variant); }
.md-steps__ln {
    flex: 1; height: 4px; border-radius: 2px; margin: 0 -6px 26px;
    background: var(--md-surface-container-highest);
}
.md-steps__ln.is-done { background: var(--md-pos); }

/* Slim bar variant, for a wizard header where labels do not fit. */
.md-stepbar { display: flex; gap: 6px; }
.md-stepbar i {
    flex: 1; height: 4px; border-radius: 2px;
    background: var(--md-surface-container-highest);
}
.md-stepbar i.is-done { background: var(--md-primary); }

/* ── OTP / PIN grid ────────────────────────────────────────────────────────
   The CTA is revealed only when every box is filled (data-reveal-cta, handled
   centrally in enableAutoAdvance). Never auto-submit: a mistyped digit must
   stay correctable. */
.md-otp { display: flex; gap: 10px; justify-content: center; }
.md-otp input {
    width: 48px; height: 56px;
    text-align: center;
    font-family: var(--font-primary);
    font-size: 22px; font-weight: var(--md-weight-medium);
    border-radius: var(--md-shape-sm);
    border: 1px solid var(--md-outline);
    background: var(--md-surface); color: var(--md-on-surface);
    outline: none;
}
.md-otp input:focus {
    border-color: var(--md-primary);
    box-shadow: inset 0 0 0 1px var(--md-primary);
}
/* The three states the grid never had (account flow, addition 1). Shared
   because every screen that asks for a code or a PIN needs the same answer to
   "that was wrong", "that was right" and "you cannot type here now", and no
   page sheet styled any of them. The class sits on the GRID, not on each box:
   one wrong digit makes the whole code wrong. The ring is an outline drawn
   over the border, not a border colour alone: the auth sheet repaints these
   boxes' border with `!important` (auth.css, `.auth-pin-wrap .md-otp input`),
   and an outline is the one line it does not own. Colour is never the only
   signal: the page prints its error line under the grid as well. */
.md-otp.has-error input {
    border-color: var(--md-error);
    outline: 2px solid var(--md-error);
    outline-offset: -2px;
}
.md-otp.is-ok input {
    border-color: var(--md-pos);
    outline: 2px solid var(--md-pos);
    outline-offset: -2px;
}
.md-otp input:disabled { opacity: .5; }

/* ── Tooltip ───────────────────────────────────────────────────────────── */
.md-tip { position: relative; display: inline-block; }
.md-tip__bub {
    position: absolute; bottom: calc(100% + 8px); left: 50%;
    transform: translateX(-50%) scale(.9);
    background: var(--md-inverse-surface); color: var(--md-inverse-on-surface);
    font-size: 12px; padding: 6px 10px;
    border-radius: var(--md-shape-xs);
    white-space: nowrap; opacity: 0; pointer-events: none;
    transition: opacity var(--md-dur-short) var(--md-ease),
                transform var(--md-dur-short) var(--md-ease);
}
.md-tip:hover .md-tip__bub { opacity: 1; transform: translateX(-50%) scale(1); }

/* ── Payment method — colour and icon before any word is read ──────────── */
.md-pays { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 10px; }
.md-pay {
    border: 0; border-radius: var(--md-shape-md);
    padding: 16px 8px; cursor: pointer;
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    font-family: var(--font-primary); font-size: 14px; font-weight: var(--md-weight-semibold);
    background: var(--md-surface-container-high); color: var(--md-on-surface);
    box-shadow: inset 0 0 0 3px transparent;
    transition: box-shadow var(--md-dur-short) var(--md-ease);
}
.md-pay .material-symbols-rounded { font-size: 32px; }
.md-pay--cash   { background: var(--md-pos-container);       color: var(--md-on-pos-container); }
.md-pay--upi    { background: var(--md-tertiary-container);  color: var(--md-on-tertiary-container); }
.md-pay--udhaar { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }
.md-pay[aria-pressed="true"] { box-shadow: inset 0 0 0 3px currentColor; }

/* ── Tabular numerals ──────────────────────────────────────────────────────
   Required on money, counts and any figure that updates (APP_STANDARDS §3):
   proportional digits change width as the value changes, so a total visibly
   jitters while it recalculates. */
.num { font-variant-numeric: tabular-nums; }

/* ── Text field ────────────────────────────────────────────────────────────
   Label ABOVE, helper BELOW, no placeholder (APP_STANDARDS rule 8): a
   placeholder disappears on focus, which is exactly when a hesitant user needs
   it. 58px clears the 48px floor with room for a 17px value.

   The error contract is the app's existing one and must not change — JS toggles
   `has-error` on the control and `active` on the message. */
.md-field { display: block; }
.md-field + .md-field { margin-top: 18px; }
.md-field__label {
    display: flex; align-items: center; gap: 8px;
    font-size: 14px; line-height: 20px; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    margin-bottom: 8px;
}
.md-field__label .material-symbols-rounded { font-size: 20px; color: var(--md-primary); }
/* A pill in a label ("From your account") sits at the far end of the line
   (account flow, addition 2). Shared because the label is. `:where()` keeps
   this at one class of weight on purpose: the "Optional" pills that already
   sit in a label (`.pw-optpill`, `.mwc-optpill`) set their own left margin in
   their page sheet, and they must keep it. */
:where(.md-field__label) .md-pill { margin-left: auto; }
.md-field__ctl { position: relative; display: flex; align-items: center; }
.md-field input,
.md-field select,
.md-field textarea {
    width: 100%;
    height: 58px;
    padding: 0 16px;
    font-family: var(--font-primary);
    font-size: 17px;
    font-weight: var(--md-weight-medium);
    border-radius: var(--md-shape-sm);
    border: 1px solid var(--md-outline);
    /* `background-color`, not the `background` SHORTHAND: the shorthand resets
       every background property, including the `background-image` a nested
       control draws its own chevron with. That is what left the date and time
       pickers inside a field as three arrow-less boxes. A field only ever wants
       to set the colour here. */
    background-color: var(--md-surface);
    color: var(--md-on-surface);
    outline: none;
    transition: border-color var(--md-dur-short) var(--md-ease),
                box-shadow var(--md-dur-short) var(--md-ease);
}
.md-field textarea { height: auto; min-height: 96px; padding: 15px 16px; resize: vertical; }
.md-field input:focus,
.md-field select:focus,
.md-field textarea:focus {
    border-color: var(--md-primary);
    box-shadow: inset 0 0 0 1px var(--md-primary);
}
.md-field input[disabled] { opacity: .5; }

/* ── A picker inside a field ─────────────────────────────────────────────────
   `render_date_picker` and `render_time_picker` are COMPOSITE controls — three
   selects sharing one hidden value — not bare selects. But `.md-field select`
   (0,1,1) outranks `.date-picker-field` (0,1,0), so dropping a picker into a
   field silently replaced its geometry with a 58px text-input shape AND, because
   `background` is a shorthand, erased the chevron the picker draws with
   `background-image`. Three unlabelled 58px boxes with no arrow.

   Restated here so the picker keeps its own shape wherever it is used, in role
   tokens rather than the legacy `--white` / `--navy` the component itself still
   carries. Found on the lead profile's note dialog; every page that nests a
   picker in a field was getting it. */
.md-field .date-picker-field {
    height: 48px;
    /* Must MATCH `.date-picker-field` above, not restate the old value: this
       selector outranks it (0,2,0 vs 0,1,0), so a padding left behind here
       silently takes back the room the column ratios were measured against. */
    padding: 0 28px 0 10px;
    font-size: 15px;
    font-weight: var(--md-weight-semibold);
    border-radius: var(--radius-md, 14px);
    background-color: var(--md-surface);
    color: var(--md-on-surface);
}
.md-field .date-picker-field:focus {
    border-color: var(--md-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--md-primary) 18%, transparent);
}
/* The pickers own their layout; a field must not stretch them into its own. */
.md-field .date-picker,
.md-field .time-picker { width: 100%; }

/* ── The picker in a container too narrow for its own ratios ─────────────────
   `.date-picker` is a `container-type: inline-size` container (see its rule
   above). The ratios there fit at 15px from about 282px of control width up;
   this component is dropped into containers well below that —
   `crm/followups.php` gives it 228px on a 360px phone — and a VIEWPORT media
   query cannot see that, because the screen is 360 either way.

   It has to sit HERE, after every rule that sets the field's type: `@container`
   adds no specificity, so it wins only by coming later. Moved once already for
   exactly that reason.

   `container-type` is Chrome 105+; this file already depends on `color-mix()`
   at 111+, so nothing new is asked of the browser. */
@container (max-width: 280px) {
    .date-picker-field,
    .md-field .date-picker-field {
        font-size: 13px;
        padding: 0 22px 0 7px;
    }
    .date-picker-field { background-position: right 6px center; }
}
.md-field__hint {
    display: block;
    font-size: 13px; line-height: 18px;
    color: var(--md-on-surface-variant);
    margin-top: 8px;
}
/* The helper line that stands in for a placeholder. Inputs in this app carry
   no placeholder text (APP_STANDARDS): the visible label says what the field
   is, and this line gives the example the placeholder used to. That makes it a
   component, not a page detail — it was found as an identical style="" on 7
   pages under three different class names (.mw-hint, .form-hint, .bcat-hint)
   plus four near-identical .mw-hint rules in four page sheets, each with its
   own line-height. One rule, and the legacy names alias onto it. */
.md-hint,
.mw-hint,
.form-hint,
.bcat-hint {
    display: block;
    font-size: 11.5px; line-height: 1.45;
    color: var(--md-on-surface-variant);
    margin-top: 6px;
}
.md-field input.has-error,
.md-field select.has-error { border-color: var(--md-error); box-shadow: inset 0 0 0 1px var(--md-error); }
/* Colour is never the only signal — the icon and the message carry it too. */
.md-field__err {
    display: none;
    align-items: center; gap: 7px;
    font-size: 13.5px; line-height: 18px; font-weight: var(--md-weight-medium);
    color: var(--md-error);
    margin-top: 8px;
}
.md-field__err.active { display: flex; }
.md-field__err .material-symbols-rounded { font-size: 18px; flex: 0 0 18px; }

/* ── Button: brand ramp and loading state ──────────────────────────────────
   Flat governs DEPTH, not colour: the house rule has always allowed the
   gradient on the PRIMARY action button. One per screen — if two carry it,
   neither reads as the primary one. */
.md-btn--brand { background: var(--grad-hero); color: #FFFFFF; }
.md-btn--cta   { background: var(--grad-cta);  color: #FFFFFF; }

/* JS toggles `.loading`. The label stays where it is and the shimmer passes
   over the button ("Waiting" above) — no spinner swaps in for the words. */

/* ── Divider carrying a label ("or") ─────────────────────────────────────── */
.md-orline {
    display: flex; align-items: center; gap: 14px;
    color: var(--md-on-surface-variant);
    font-size: 13.5px; font-weight: var(--md-weight-semibold);
}
.md-orline::before, .md-orline::after {
    content: ""; flex: 1; height: 1px; background: var(--md-outline-variant);
}

/* ══════════════════════════════════════════════════════════════════════════
   PART 4 — APP CHROME  (MD3 migration, Phase 1 · Tier A)
   ══════════════════════════════════════════════════════════════════════════
   The ten components every module page is built from above the content: the
   bar at the top, the bar at the bottom, and the eight things between them.

   Eight of these are PROMOTIONS, not inventions. The `.bl-*` CSS behind the
   app bar, search, stats and hero has been rendering on 57 pages out of
   crm/crm-shared.css for months; `.wz-*` has been rendering on 26 and is
   defined in no stylesheet at all. What changes here is the name and the
   colour source — every value below resolves to a theme role, so the same
   markup is correct in dark mode, which none of the originals were.

   Two rules apply to all ten:

     · Nothing tappable is under 48px (APP_STANDARDS §5). Where a design wants
       a smaller visual, the VISUAL shrinks and the box does not.
     · Colour is never the only signal (APP_STANDARDS §2.3). Every state-bearing
       component below takes an icon slot, and it is not optional.

   Legacy names (`.bl-*`, `.mw-*`, `.wz-*`) are deliberately left alone here.
   Phase 1 adds vocabulary and touches zero pages; aliasing and deleting the
   old names is Phase 2. See MD3_MIGRATION_PLAN.md §3–4.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · Icon button ───────────────────────────────────────────────────────
   The back arrow, the "?", the close, the overflow — every naked glyph that
   does something. Four emphasis levels, one box size.

   The box is 48px even though the painted circle in most of our screens reads
   as 40: a 40px target fails on a phone held one-handed, and that is how this
   app is used. Pair with `md-state` for the press feedback.

     <button class="md-icon-btn md-state" aria-label="…">
       <span class="material-symbols-rounded" aria-hidden="true">arrow_back</span>
     </button>
   ────────────────────────────────────────────────────────────────────────── */
.md-icon-btn {
    width: 48px; height: 48px; flex: 0 0 48px;
    border: 0; border-radius: var(--md-shape-full);
    background: transparent;
    color: var(--md-on-surface-variant);
    display: grid; place-items: center;
    cursor: pointer; text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    transition: background var(--md-dur-short) var(--md-ease);
}
.md-icon-btn .material-symbols-rounded { font-size: 24px; }
/* --standard is the default above; the modifier exists so a page can say so. */
.md-icon-btn--standard { background: transparent; color: var(--md-on-surface-variant); }
.md-icon-btn--filled   { background: var(--md-primary);           color: var(--md-on-primary); }
.md-icon-btn--tonal    { background: var(--md-primary-container);  color: var(--md-on-primary-container); }
.md-icon-btn--outlined { background: transparent; color: var(--md-on-surface-variant);
                         box-shadow: inset 0 0 0 1px var(--md-outline); }
.md-icon-btn[disabled],
.md-icon-btn[aria-disabled="true"] { opacity: .38; cursor: not-allowed; }
/* Sitting on the brand gradient, which does NOT re-theme in dark — so this
   takes white and a white scrim, never a role token that would invert to a
   pale colour on the same pale-blue ground. Promoted from auth.css, where it
   was written for the login hero and could not be reached from anywhere else. */
.md-icon-btn--onhero {
    background: rgba(255, 255, 255, .16);
    color: #FFFFFF;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .24);
}
@media (hover: hover) {
    .md-icon-btn--onhero:hover { background: rgba(255, 255, 255, .26); }
}

/* ── 2 · App bar ───────────────────────────────────────────────────────────
   On 98 pages — the single most-repeated piece of markup in the app.

   Structure is lead · head(title + sub) · tail. `__head` is not in the original
   component sketch but a flex row cannot stack two lines without it, and the
   supporting line is what makes an app bar say WHERE you are rather than only
   what the page is called.

   The title must be the page's real <h1>. 126 pages fail the accessibility
   audit today and "no h1 on the page" is the most common reason; the app bar
   is where the heading belongs, so putting it here fixes it once.

     <header class="md-appbar">
       <a class="md-icon-btn md-state" href="…"><span class="material-symbols-rounded" aria-hidden="true">arrow_back</span></a>
       <span class="md-appbar__head">
         <h1 class="md-appbar__title">बिलिंग</h1>
         <span class="md-appbar__sub">१२ इनव्हॉइस</span>
       </span>
       <span class="md-appbar__tail"> … </span>
     </header>
   ────────────────────────────────────────────────────────────────────────── */
.md-appbar {
    position: sticky; top: 0; z-index: 100;
    display: flex; align-items: center; gap: 4px;
    min-height: 64px;
    padding: 0 6px;
    padding-top: env(safe-area-inset-top);
    background: var(--md-surface);
    color: var(--md-on-surface);
    /* The hairline belongs to the bar, not to the page below it: a page that
       draws its own would double the line the moment both exist. */
    box-shadow: inset 0 -1px 0 var(--md-outline-variant);
}
/* Leading slot — a tinted module badge, sitting next to the back button. */
.md-appbar__lead {
    width: 36px; height: 36px; flex: 0 0 36px;
    border-radius: var(--md-shape-sm);
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
    display: grid; place-items: center;
    margin-left: 2px;
}
.md-appbar__lead .material-symbols-rounded { font-size: 20px; }
.md-appbar__lead img { width: 26px; height: 26px; object-fit: contain; display: block; }

/* Logo variant — the 3D module PNG (§8.2) rather than a glyph. It needs more
   room than the glyph badge (a rendered 3D object at 26px is mush) and a
   quieter ground: the artwork carries its own colour, so primary-container
   behind it makes two competing tints where the list rows' neutral tile makes
   one. Proportioned like `.md-row__lead` (52px tile / 36px art) scaled to the
   bar's 64px height, so the mark reads as the same object it did in the row
   the user tapped to get here. */
.md-appbar__lead--logo {
    width: 40px; height: 40px; flex-basis: 40px;
    border-radius: var(--md-shape-md);
    background: var(--md-surface-container);
    box-shadow: inset 0 0 0 1px var(--md-outline-variant);
}
.md-appbar__lead--logo img { width: 30px; height: 30px; }

.md-appbar__head {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; justify-content: center;
    padding: 0 4px;
}
.md-appbar__title {
    margin: 0;
    font-family: var(--font-primary);
    /* 1.5, not the Latin-tight 24px this shipped with: the ellipsis needs
       `overflow:hidden`, which clips vertically too, and at 18/24 a Devanagari
       title loses its matras above and its conjunct stacks below — "टेक्नॉलॉजीज"
       measured 27px tall in a 24px box. Same reasoning as .md-tile__badge. */
    font-size: 18px; line-height: 1.5; font-weight: var(--md-weight-semibold);
    letter-spacing: normal;
    color: var(--md-on-surface);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-appbar__sub {
    display: block;
    /* 1.5 for the same clipped-Devanagari reason as the title above. */
    font-size: 12.5px; line-height: 1.5; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-appbar__tail {
    display: flex; align-items: center; gap: 2px;
    margin-left: auto; flex-shrink: 0;
}

/* Centred title, for a step in a flow where there is nothing to say about
   where you are. The leading and trailing slots must balance or it will not
   look centred. */
.md-appbar--center .md-appbar__head { text-align: center; }

/* Large: the title drops to a second row at display size. Landing screens
   only — it costs 48px of the 30% hero budget (§9). */
.md-appbar--large {
    flex-wrap: wrap;
    align-items: flex-start;
    min-height: 112px;
    padding-bottom: 12px;
}
.md-appbar--large .md-appbar__head {
    flex: 0 0 100%;
    order: 3;
    padding: 6px 10px 0;
}
.md-appbar--large .md-appbar__title { font-size: 26px; line-height: 32px; white-space: normal; }

/* JS toggles this once the content scrolls, so the bar separates from the page
   without carrying a shadow while everything is still at rest. */
.md-appbar--scrolled {
    background: var(--md-surface-container);
    box-shadow: inset 0 -1px 0 var(--md-outline-variant),
                0 2px 8px -4px color-mix(in srgb, var(--md-on-surface) 30%, transparent);
}

/* On the brand gradient — for a module HOME, where the bar and the hero below
   it are one block of colour rather than a bar with a card under it. Drops the
   hairline: there is nothing to separate when the two share a background.

   Everything inside takes white, for the reason in the hero note further down:
   `--grad-hero` is deliberately the same blue in both schemes, so a role token
   in here would invert to a pale colour on a pale-blue ground and vanish. */
.md-appbar--onhero {
    background: var(--grad-hero);
    color: #FFFFFF;
    box-shadow: none;
}
.md-appbar--onhero .md-appbar__title { color: #FFFFFF; }
.md-appbar--onhero .md-appbar__sub   { color: rgba(255, 255, 255, .82); }
.md-appbar--onhero .md-appbar__lead  { background: rgba(255, 255, 255, .18); color: #FFFFFF; }
/* Icon buttons — `.md-appbar--hero`'s own rule, restated here because this is
   a different class and inherits nothing from it. First real consumer:
   modules/upgrade/. Plain rides flat on the gradient (a filled circle behind
   a back arrow is a target hint the bar does not need); only `--tonal`
   (help, overflow) keeps its glass wash, so the control that ACTS still reads
   as one. */
.md-appbar--onhero .md-icon-btn {
    color: #FFFFFF;
    background: none;
}
.md-appbar--onhero .md-icon-btn--tonal {
    color: #FFFFFF;
    background: rgba(255, 255, 255, .18);
}

/* Brand home bar (`--brand`) — the treatment the dashboard shipped, promoted
   here so a module HOME wears the same chrome as the app's home rather than a
   look-alike. Three parts: a 5%-primary wash instead of flat surface, the
   line-art watermark bled off the right edge, and the title in brand ink.

   The wash and the watermark are both 5% of `--md-primary`, which is why one
   asset serves both schemes: at that weight the strokes read as texture on a
   near-white and a near-black ground alike. `local` is deliberate on the
   dashboard's scrolling copy; here the bar does not scroll, so the pattern is
   simply anchored to its right edge.
   ────────────────────────────────────────────────────────────────────────── */
.md-appbar--brand {
    background: color-mix(in srgb, var(--md-primary) 5%, var(--md-surface));
    transition: background var(--md-dur-medium) var(--md-ease),
                box-shadow var(--md-dur-medium) var(--md-ease);
}
.md-appbar--brand::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background: var(--md-primary);
    opacity: .05;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='160' viewBox='0 0 300 160' fill='none' stroke='%23FFFFFF' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M24 16h48v6H24z'/%3E%3Cpath d='M28 22h40l6 8H22z'/%3E%3Cpath d='M22 30q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0'/%3E%3Cpath d='M28 22v34M68 22v34'/%3E%3Cpath d='M34 36h28v20h-28z'/%3E%3Cpath d='M34 42h28M34 49h28'/%3E%3Cpath d='M20 56h56'/%3E%3Cpath d='M108 10h34v37l-5.67 3-5.67-3-5.66 3-5.67-3-5.67 3-5.66-3z'/%3E%3Cpath d='M114 18h22M114 25h22M114 32h14M114 39h22'/%3E%3Cpath d='M228 31a5 5 0 1 1 0 .01'/%3E%3Cpath d='M219 50a9 9 0 0 1 18 0'/%3E%3Cpath d='M208 34a6 6 0 1 1 0 .01'/%3E%3Cpath d='M197 54a11 11 0 0 1 22 0'/%3E%3Cpath d='M40 88h56v38H40z'/%3E%3Cpath d='M40 98h56'/%3E%3Cpath d='M47 106h18M47 113h30M47 120h22'/%3E%3Cpath d='M128 100h36l-3 26h-30z'/%3E%3Cpath d='M137 100v-5a9 9 0 0 1 18 0v5'/%3E%3Cpath d='M198 134h50'/%3E%3Cpath d='M204 134v-10h9v10M218 134v-18h9v18M232 134v-26h9v26'/%3E%3Cpath d='M204 106l14-8l10 4l14-10'/%3E%3Cpath d='M234 92h8v8'/%3E%3Ccircle cx='46' cy='93' r='1.6'/%3E%3Ccircle cx='52' cy='93' r='1.6'/%3E%3Ccircle cx='58' cy='93' r='1.6'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='160' viewBox='0 0 300 160' fill='none' stroke='%23FFFFFF' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M24 16h48v6H24z'/%3E%3Cpath d='M28 22h40l6 8H22z'/%3E%3Cpath d='M22 30q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0'/%3E%3Cpath d='M28 22v34M68 22v34'/%3E%3Cpath d='M34 36h28v20h-28z'/%3E%3Cpath d='M34 42h28M34 49h28'/%3E%3Cpath d='M20 56h56'/%3E%3Cpath d='M108 10h34v37l-5.67 3-5.67-3-5.66 3-5.67-3-5.67 3-5.66-3z'/%3E%3Cpath d='M114 18h22M114 25h22M114 32h14M114 39h22'/%3E%3Cpath d='M228 31a5 5 0 1 1 0 .01'/%3E%3Cpath d='M219 50a9 9 0 0 1 18 0'/%3E%3Cpath d='M208 34a6 6 0 1 1 0 .01'/%3E%3Cpath d='M197 54a11 11 0 0 1 22 0'/%3E%3Cpath d='M40 88h56v38H40z'/%3E%3Cpath d='M40 98h56'/%3E%3Cpath d='M47 106h18M47 113h30M47 120h22'/%3E%3Cpath d='M128 100h36l-3 26h-30z'/%3E%3Cpath d='M137 100v-5a9 9 0 0 1 18 0v5'/%3E%3Cpath d='M198 134h50'/%3E%3Cpath d='M204 134v-10h9v10M218 134v-18h9v18M232 134v-26h9v26'/%3E%3Cpath d='M204 106l14-8l10 4l14-10'/%3E%3Cpath d='M234 92h8v8'/%3E%3Ccircle cx='46' cy='93' r='1.6'/%3E%3Ccircle cx='52' cy='93' r='1.6'/%3E%3Ccircle cx='58' cy='93' r='1.6'/%3E%3C/svg%3E");
    -webkit-mask-repeat: repeat;
            mask-repeat: repeat;
    -webkit-mask-size: 205px 109px;
            mask-size: 205px 109px;
    -webkit-mask-position: right -10px top 6px;
            mask-position: right -10px top 6px;
}
/* The watermark sits at z-index 0, so everything the bar carries needs to be
   lifted above it — a stacking fix, not decoration. */
.md-appbar--brand > * { position: relative; z-index: 1; }

/* ── Hero app bar — the solid brand one ───────────────────────────────────
   `--brand` above is the LIGHT treatment: 5% primary over the surface, with
   the watermark. This is the filled one — the same `--grad-hero` the app home
   wears, promoted out of dashboard.css so a module can wear it too.

   The library paints `.md-appbar__title` with a gradient text fill clipped to
   the glyphs (navy → blue, designed for a light bar). On this one that turns
   the title into pale blue on blue, so BOTH the background and the fill have
   to be unset — `color` alone loses to `-webkit-text-fill-color`. The
   dashboard learnt this the hard way; the fix travels with the component now.
   ────────────────────────────────────────────────────────────────────────── */
.md-appbar--hero { background: var(--grad-hero); }
.md-appbar--hero::after { display: none; }          /* no watermark on a filled bar */
.md-appbar--hero .md-appbar__title {
    color: var(--md-on-brand);
    background: none;
    -webkit-text-fill-color: var(--md-on-brand);
}
.md-appbar--hero .md-appbar__sub { color: var(--md-on-brand); opacity: .85; }
/* Plain icon buttons ride flat on the gradient — a filled circle behind a
   back arrow is a target hint the bar does not need, and two circles at the
   two ends made the title look squeezed between buttons. Only `--tonal`
   (help, overflow) keeps its wash, so the one that ACTS still reads as a
   control while the one that goes back is just an arrow. */
.md-appbar--hero .md-icon-btn {
    color: var(--md-on-brand);
    background: none;
}
.md-appbar--hero .md-icon-btn--tonal {
    color: var(--md-on-brand);
    background: rgba(255, 255, 255, .18);
}
.md-appbar--hero .md-appbar__lead img { background: #FFFFFF; border-radius: var(--md-shape-sm); }

/* The title in brand ink: a gradient text fill, on-primary-container → primary.
   Both stops are role tokens with a guaranteed floor on this surface, so the
   wordmark treatment costs no legibility in either scheme. `color` is the
   fallback ink for a renderer without background-clip: text. */
.md-appbar--brand .md-appbar__title {
    color: var(--md-on-primary-container);
    background: linear-gradient(100deg, var(--md-on-primary-container) 5%, var(--md-primary) 92%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* The module mark carries no ring and no tile: the 3D art already has its own
   colour and silhouette, so a painted circle behind it puts two shapes and two
   colours where the user only needs one. Same reasoning as `.md-tile__img`.

   Sized to the glyphs beside it (24px) rather than to the slot it replaces. A
   36px mark next to a 24px arrow and an 18px title made the bar read as three
   different scales, with the loudest weight on the one thing that is only an
   identity cue. */
.md-appbar--brand .md-appbar__lead--logo {
    width: 28px; height: 28px; flex-basis: 28px;
    background: none;
    box-shadow: none;
}
.md-appbar--brand .md-appbar__lead--logo img { width: 24px; height: 24px; }

/* The tonal help circle keeps its 48px touch target (APP_STANDARDS §5) but
   paints only the inner 40px: `background-clip: content-box` fills the content
   box, so the padding is hit area without being ink. At the full 48px the
   filled circle was the heaviest object in the bar — heavier than the title it
   sits beside — which is the wrong emphasis for a help affordance. */
.md-appbar--brand .md-icon-btn--tonal,
.md-appbar--brand .md-icon-btn--filled {
    padding: 4px;
    background-clip: content-box;
}


/* ── 3 · Search field ──────────────────────────────────────────────────────
   NO PLACEHOLDER (APP_STANDARDS rule 8) — a placeholder vanishes on focus,
   which is the moment a hesitant user needs it most. The magnifier plus an
   `aria-label` carries the meaning; if a screen needs words, put a real label
   above the field.

     <div class="md-search">
       <span class="md-search__icon material-symbols-rounded" aria-hidden="true">search</span>
       <input class="md-search__input" type="search" aria-label="…">
       <button class="md-search__clear md-icon-btn md-state" aria-label="…">…</button>
     </div>
   ────────────────────────────────────────────────────────────────────────── */
.md-search { position: relative; display: flex; align-items: center; }
.md-search__icon {
    position: absolute; left: 16px; z-index: 1;
    font-size: 22px;
    color: var(--md-on-surface-variant);
    pointer-events: none;
}
.md-search__input {
    width: 100%; height: 56px;
    padding: 0 56px 0 48px;
    box-sizing: border-box;
    border-radius: var(--md-shape-full);
    border: 1px solid var(--md-outline-variant);
    background: var(--md-surface-container-high);
    color: var(--md-on-surface);
    font-family: var(--font-primary);
    font-size: 16px; font-weight: var(--md-weight-medium);
    outline: none;
    transition: border-color var(--md-dur-short) var(--md-ease),
                box-shadow var(--md-dur-short) var(--md-ease);
    /* Safari paints a native cancel affordance on type=search that ignores
       every one of these rules. */
    -webkit-appearance: none; appearance: none;
}
.md-search__input::-webkit-search-cancel-button { display: none; }
.md-search__input:focus {
    border-color: var(--md-primary);
    box-shadow: inset 0 0 0 1px var(--md-primary);
}
.md-search__clear { position: absolute; right: 4px; }

/* ── 4 · Banner ────────────────────────────────────────────────────────────
   The in-page message: a warning, an explanation, an alert carrying one
   action. NOT feedback — feedback goes through showFeedbackModal (§8.8).

   The icon slot is REQUIRED. The four variants differ by colour, and colour
   alone is not a signal every user can read.
   ────────────────────────────────────────────────────────────────────────── */
.md-banner {
    display: flex; align-items: flex-start; gap: 12px;
    padding: 14px 16px;
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-high);
    color: var(--md-on-surface);
}
.md-banner__icon { flex: 0 0 24px; font-size: 24px; margin-top: 1px; }
.md-banner__body {
    flex: 1; min-width: 0;
    font-size: 14px; line-height: 20px; font-weight: var(--md-weight-medium);
}
.md-banner__body b {
    display: block;
    font-size: 15px; line-height: 21px; font-weight: var(--md-weight-semibold);
    margin-bottom: 2px;
}
.md-banner__action {
    flex-shrink: 0; align-self: center;
    height: 48px; padding: 0 14px;
    border: 0; border-radius: var(--md-shape-full);
    background: transparent;
    color: inherit;
    font-family: var(--font-primary);
    font-size: 14px; font-weight: var(--md-weight-semibold);
    text-decoration: none; cursor: pointer;
    display: inline-flex; align-items: center; gap: 4px;
}
.md-banner--info    { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }
.md-banner--warn    { background: var(--md-warn-container);      color: var(--md-on-warn-container); }
.md-banner--error   { background: var(--md-error-container);     color: var(--md-on-error-container); }
.md-banner--success { background: var(--md-pos-container);       color: var(--md-on-pos-container); }
/* The banner as the top edge of the screen (account flow, addition 4): the
   read-only notice, printed above the app bar on every business page. Shared
   because it shows on every module's pages. It stays under the dialogs. */
.md-banner--top {
    position: sticky; top: 0; z-index: 8900;
    border-radius: 0;
    padding-top: calc(10px + env(safe-area-inset-top, 0px));
    padding-bottom: 10px;
}

/* ── 5 · Stat grid ─────────────────────────────────────────────────────────
   The KPI row under a hero. Four across is the temptation and usually the
   mistake — currency does not fit in a quarter of a 360px screen, which is why
   billing.css already forces its grids back to two.

   So `--2` is the default, `--3` is for counts, and both `--3` and `--4`
   collapse to two columns under 380px whatever they were asked for.
   ────────────────────────────────────────────────────────────────────────── */
.md-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.md-stats--2 { grid-template-columns: repeat(2, 1fr); }
.md-stats--3 { grid-template-columns: repeat(3, 1fr); }
.md-stats--4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 380px) {
    .md-stats--3, .md-stats--4 { grid-template-columns: repeat(2, 1fr); gap: 8px; }
}
.md-stat {
    min-width: 0; overflow: hidden;
    padding: 14px 12px;
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-low);
    box-shadow: var(--md-elev-1);
    display: flex; flex-direction: column; gap: 2px;
    /* Written as a <div>, but a stat that LEADS somewhere has to be an <a> —
       and then the UA underlines the value, the label and the icon square
       alike. Same reason `button.md-row` exists below: the component has to
       survive the element its meaning requires. */
    text-decoration: none;
    color: inherit;
}
.md-stat__ic {
    width: 32px; height: 32px; margin-bottom: 6px;
    border-radius: var(--md-shape-full);
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
    display: grid; place-items: center;
}
.md-stat__ic .material-symbols-rounded { font-size: 18px; }
.md-stat__val {
    max-width: 100%;
    /* 1.3 rather than the 1.15 a Latin-only design would use: this clips its
       overflow, and a value is not always digits ("१२ ग्राहक"). */
    font-size: 22px; line-height: 1.3; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-stat__lbl {
    font-size: 12.5px; line-height: 17px; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    letter-spacing: normal;
}
/* Direction is an arrow AND a colour, never a colour alone. */
.md-stat__delta {
    display: inline-flex; align-items: center; gap: 3px;
    margin-top: 4px;
    font-size: 12.5px; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface-variant);
    font-variant-numeric: tabular-nums;
}
.md-stat__delta .material-symbols-rounded { font-size: 16px; }
.md-stat__delta--up   { color: var(--md-pos); }
.md-stat__delta--down { color: var(--md-neg); }

/* ── 6 · Hero ──────────────────────────────────────────────────────────────
   The gradient block at the top of a module home: one number that matters,
   optionally a two-up row of smaller ones, optionally one call to action.

   Keep it under 30% of the viewport (§9) — 234px at 360×780, preferably 180.
   Everything below the fold costs a scroll before the user has done anything,
   and this is the block that always wants to grow.

   White on the gradient is intentional and exempt from the colour rule: it is
   an absolute, not a palette choice.

   ⚠ The hero does NOT re-theme. `--grad-hero` is defined once in theme.css and
   is not redefined under [data-theme="dark"], so this block paints the same
   dark magenta in both schemes — which means every colour ON it must be
   scheme-invariant too. `--md-primary` is not: it inverts to a pale pink in
   dark, and the white CTA below spent its first render as pale pink text on
   white. Anything sitting on this gradient takes white or `--brand-primary`,
   never a role token that flips.
   ────────────────────────────────────────────────────────────────────────── */
.md-hero {
    position: relative; overflow: hidden;
    padding: 14px;
    border-radius: var(--md-shape-xl);
    /* The dashboard money card's surface, now the hero's: the gradient plus a
       soft top-left sheen so it reads as a lit object rather than a flat fill.
       Promoted rather than copied — the home screen and a module home showing
       the same money should not be two different cards. */
    background:
        radial-gradient(130% 100% at 12% -12%, rgba(255, 255, 255, .22) 0%, transparent 55%),
        var(--grad-hero);
    color: #FFFFFF;
    display: flex; flex-direction: column; gap: 10px;
    box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--brand-primary) 60%, transparent);
}
/* The six business shapes — shopfront, bill, customers, website, parcel, sales
   chart — from the dashboard card. Faded out over the left third with a mask so
   the strokes never sit under the digits, which is the one thing on this card
   that has to be read. Rides ::before because `.md-state` owns ::after. */
.md-hero::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='160' viewBox='0 0 300 160' fill='none' stroke='%23FFFFFF' stroke-opacity='0.16' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M24 16h48v6H24z'/%3E%3Cpath d='M28 22h40l6 8H22z'/%3E%3Cpath d='M22 30q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0'/%3E%3Cpath d='M28 22v34M68 22v34'/%3E%3Cpath d='M34 36h28v20h-28z'/%3E%3Cpath d='M34 42h28M34 49h28'/%3E%3Cpath d='M20 56h56'/%3E%3Cpath d='M108 10h34v37l-5.67 3-5.67-3-5.66 3-5.67-3-5.67 3-5.66-3z'/%3E%3Cpath d='M114 18h22M114 25h22M114 32h14M114 39h22'/%3E%3Cpath d='M228 31a5 5 0 1 1 0 .01'/%3E%3Cpath d='M219 50a9 9 0 0 1 18 0'/%3E%3Cpath d='M208 34a6 6 0 1 1 0 .01'/%3E%3Cpath d='M197 54a11 11 0 0 1 22 0'/%3E%3Cpath d='M40 88h56v38H40z'/%3E%3Cpath d='M40 98h56'/%3E%3Cpath d='M47 106h18M47 113h30M47 120h22'/%3E%3Cpath d='M128 100h36l-3 26h-30z'/%3E%3Cpath d='M137 100v-5a9 9 0 0 1 18 0v5'/%3E%3Cpath d='M198 134h50'/%3E%3Cpath d='M204 134v-10h9v10M218 134v-18h9v18M232 134v-26h9v26'/%3E%3Cpath d='M204 106l14-8l10 4l14-10'/%3E%3Cpath d='M234 92h8v8'/%3E%3Ccircle cx='46' cy='93' r='1.6'/%3E%3Ccircle cx='52' cy='93' r='1.6'/%3E%3Ccircle cx='58' cy='93' r='1.6'/%3E%3C/svg%3E") repeat right top / 225px 120px;
    -webkit-mask-image: linear-gradient(100deg, transparent 8%, #000 62%);
            mask-image: linear-gradient(100deg, transparent 8%, #000 62%);
}
.md-hero__main {
    display: flex; flex-direction: column; align-items: center;
    text-align: center; gap: 1px;
}
.md-hero__lbl {
    font-size: 12px; line-height: 17px; font-weight: var(--md-weight-medium);
    color: rgba(255, 255, 255, .88);
    letter-spacing: normal;
}
.md-hero__val {
    font-family: var(--font-primary);
    font-size: 30px; line-height: 1.15; font-weight: var(--md-weight-semibold);
    color: #FFFFFF;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
}
.md-hero__pill {
    margin-top: 6px;
    display: inline-flex; align-items: center; gap: 5px;
    /* 44px, not the 32 this shipped with. On the billing hero the pill is a
       LINK to the Udhari Book, and a 32px tap target is under the app's floor
       (§5, chip 44) — measured at 32.0 as rendered. The look is unchanged: the
       extra height is padding around the same text. */
    height: 44px; padding: 0 16px;
    border-radius: var(--md-shape-full);
    background: rgba(255, 255, 255, .18);
    color: #FFFFFF;
    font-size: 12px; font-weight: var(--md-weight-semibold);
    text-decoration: none;
}
.md-hero__pill .material-symbols-rounded { font-size: 16px; }
/* `.md-hero__pill` as a <button>, for the same reason `.md-row` and
   `.md-ctrl` needed it: a pill that FILTERS the list below it has to be a
   button, and then the UA defaults take over — a border, the button font,
   and a text colour that is not the white this surface requires. First
   needed by forms/index.php, whose paused-forms pill is a door. */
button.md-hero__pill {
    border: 0;
    font-family: var(--font-primary);
    cursor: pointer;
}
.md-hero__row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.md-hero__mini {
    display: flex; align-items: center; justify-content: center; gap: 6px;
    /* min-height 48: a mini is usually a link to the screen behind the number
       (billing's two open Invoices and Quotations) and measured 37.5px as
       rendered — under the 48px touch minimum. Padding alone could not be
       trusted to reach it, because the height came from the text inside. */
    min-height: 48px;
    padding: 8px 6px; min-width: 0;
    /* A mini is often a link to the screen behind the number — the underline
       the anchor brings with it reads as a spelling error under a rupee
       figure, and `.md-hero__pill` already suppressed it. */
    text-decoration: none; color: inherit;
    border-radius: var(--md-shape-md);
    background: rgba(255, 255, 255, .13);
    border: 1px solid rgba(255, 255, 255, .20);
    text-align: center;
}
.md-hero__mini .md-hero__lbl { font-size: 11.5px; }
.md-hero__mini .md-hero__val { font-size: 17px; }
.md-hero__cta {
    display: flex; align-items: center; justify-content: center; gap: 7px;
    /* 48px — the touch floor exactly, and what this has always been. Raised to
       52 with the D5 experiment on 2026-08-17 and reverted with it. */
    height: 48px;
    border: 0; border-radius: var(--md-shape-full);
    background: var(--md-on-brand);
    /* Brand token, not `--md-primary` — see the scheme-invariance note above. */
    color: var(--brand-primary);
    font-family: var(--font-primary);
    font-size: 15px; font-weight: var(--md-weight-semibold);
    text-decoration: none; cursor: pointer;
    box-shadow: 0 3px 10px -4px color-mix(in srgb, var(--brand-primary) 40%, transparent);
}
.md-hero__cta .material-symbols-rounded { font-size: 20px; font-variation-settings: 'FILL' 1; }

/* Two actions, side by side. A module home has one primary thing to do and
   usually one place to go and look — cramming the second into the list below
   buries it, and giving it equal weight to "make a bill" is wrong. So: the
   filled white CTA takes the room it needs, the ghost sits beside it.
   Single-child heroes are unaffected; `.md-hero__cta` alone still spans. */
.md-hero__acts { display: flex; gap: 8px; }
.md-hero__acts .md-hero__cta { flex: 1 1 auto; min-width: 0; }
.md-hero__acts .md-hero__cta--ghost { flex: 0 0 auto; }

/* ── The hero on its side ──────────────────────────────────────────────────
   One figure and one state, ~90px instead of ~250. For a module home whose job
   is somewhere else on the screen — the miniwebsite home opens on the site's
   address, and the visitor count is the answer to "is any of this working",
   which is one line rather than a card. Same surface, same watermark, same
   scheme-invariant white ink; only the axis changes.

     <div class="md-hero md-hero--slim">
       <div class="md-hero__main"> … __lbl __val … </div>
       <span class="md-hero__pill"> … </span>
     </div>
   ────────────────────────────────────────────────────────────────────────── */
.md-hero--slim {
    flex-direction: row; align-items: center; justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
}
.md-hero--slim .md-hero__main { align-items: flex-start; text-align: left; }
.md-hero--slim .md-hero__val  { font-size: 26px; }
/* The pill is a state here, not a link to a fifth screen, so it keeps the
   44px height for the thumb only when it IS one — see the page that uses it. */
.md-hero--slim .md-hero__pill { margin-top: 0; }

/* Outlined on the gradient, so it reads as the secondary of the two without
   introducing a third colour. White at 16%/38% — absolutes, like every other
   value on this scheme-invariant surface. */
.md-hero__cta--ghost {
    padding: 0 16px;
    background: rgba(255, 255, 255, .16);
    color: #FFFFFF;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .38);
}
.md-hero__cta--ghost .material-symbols-rounded { font-variation-settings: 'FILL' 0; }

/* ── 6b · Shortcut strip ───────────────────────────────────────────────────
   Four round, hard-tinted actions in a card that overlaps the hero above it.

   This is the row that carries the app for someone who does not read. The
   module lists below say what each screen IS; this says what you can DO right
   now, in four coloured circles that never move and never change colour. A
   user learns "blue circle, top left = make a bill" by position and hue and
   stops needing the word under it — which is the same way they already use
   every other app on the phone.

   Four is the maximum. At 360px a fifth circle drops the target under 44px and
   the label under two syllables, and a strip that needs reading to be used has
   given up the only thing it was for.

     <div class="md-quick">
       <a class="md-quick__a md-state">
         <span class="md-quick__ic md-tint-blue">…glyph…</span>
         <span class="md-quick__lbl">नवीन बिल</span>
       </a> ×4
     </div>
   ────────────────────────────────────────────────────────────────────────── */
.md-quick {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 4px;
    padding: 14px 6px 12px;
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-lowest);
    box-shadow: var(--md-elev-2);
}
.md-quick__a {
    display: flex; flex-direction: column; align-items: center; gap: 7px;
    padding: 4px 2px;
    min-height: 84px;
    border-radius: var(--md-shape-md);
    text-decoration: none;
    color: var(--md-on-surface);
    cursor: pointer;
    transition: transform var(--md-dur-short) var(--md-ease-emphasized);
}
.md-quick__a:active { transform: scale(.94); }
.md-quick__ic {
    width: 54px; height: 54px; flex: none;
    border-radius: var(--md-shape-full);
    display: grid; place-items: center;
}
.md-quick__ic .material-symbols-rounded { font-size: 28px; font-variation-settings: 'FILL' 1; }
.md-quick__lbl {
    /* Two lines' worth of box, always — "New Customer" wraps and "New Bill"
       does not, and without a reserved height the four circles sit at four
       different heights. 1.35 and no clipping: Devanagari matras sit above the
       shirorekha, and a label that loses its matra is a different word. */
    min-height: 2.7em;
    font-size: 11.5px; line-height: 1.35; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    text-align: center;
    letter-spacing: normal;
}
.md-quick__a.is-off { opacity: .5; }

/* ── 7 · Tile grid ─────────────────────────────────────────────────────────
   The 3-up squircle grid on the 11 module hubs. A tile is an ENTRY POINT, so
   it carries the module's 3D Fluent PNG from assets/images/menu/ — never a
   flat Material glyph (MODULE_3D_LOGOS.md). `__ic` exists for the handful of
   in-module actions that have no logo.

   A disabled feature stays VISIBLE and dimmed with a badge — show, don't hide
   (FEATURE_FLAGS_IMPLEMENTATION.md). A feature that vanishes reads as a bug.
   ────────────────────────────────────────────────────────────────────────── */
.md-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.md-tiles--2 { grid-template-columns: repeat(2, 1fr); }
.md-tiles--4 { grid-template-columns: repeat(4, 1fr); }
.md-tile {
    position: relative;
    display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
    gap: 7px;
    padding: 13px 5px 11px;
    min-height: 96px;
    box-sizing: border-box;
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-lowest);
    box-shadow: var(--md-elev-1);
    color: var(--md-on-surface);
    text-decoration: none; cursor: pointer;
    transition: transform var(--md-dur-short) var(--md-ease-emphasized);
}
.md-tile:active { transform: scale(.96); }
/* `.md-tile` as a <button>, for the same reason `.md-row` needed it: the tile
   is written for an `<a>`, and a tile that RUNS something rather than
   navigating has to be a button. Without this the UA's 2px bevelled border sits
   on top of the tile's own hairline and the label takes the button font. */
button.md-tile {
    border: 0;
    font-family: var(--font-primary);
    text-align: center;
}

.md-tile__ic {
    width: 48px; height: 48px; flex-shrink: 0;
    border-radius: var(--md-shape-md);
    background: var(--md-surface-container-high);
    color: var(--md-on-surface-variant);
    display: grid; place-items: center;
}
.md-tile__ic .material-symbols-rounded { font-size: 26px; }
/* The PNG carries its own colour and shape — no tinted badge behind it. */
.md-tile__img { width: 48px; height: 48px; object-fit: contain; display: block; }
.md-tile__lbl {
    font-size: 12px; line-height: 1.25; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface);
    text-align: center;
    letter-spacing: normal;
    word-break: break-word;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
}
/* Self-contained, NOT a `.md-pill`. A tile is ~110px wide at 360px, and a pill
   sized for a list row overruns the corner and clips its own text — which in
   Devanagari means losing the matras, not just looking tight. No fixed height
   either: the box grows from the padding, so a taller script still fits.
   Overridable via a `--` modifier if a tile ever needs a state other than the
   "coming soon / under maintenance" cue this is for. */
.md-tile__badge {
    position: absolute; top: 6px; right: 6px; margin: 0;
    /* A tile is `md-state`, and that lifts EVERY child to z-index 1 so the
       state layer paints beneath them. With the badge and the icon both at 1,
       paint order falls back to DOM order — and the icon square, being later
       in the markup, drew straight over the badge. 2 puts the badge above its
       own tile's content regardless of where an author writes it. */
    z-index: 2;
    max-width: calc(100% - 12px);
    padding: 2px 7px;
    border-radius: var(--md-shape-full);
    background: var(--md-warn-container);
    color: var(--md-on-warn-container);
    /* 1.6 because this box hides its overflow (it must, for the ellipsis) and
       Devanagari puts matras above the shirorekha — a Latin-tight 1.2 slices
       the top off लवकरच. */
    font-size: 10px; line-height: 1.6; font-weight: var(--md-weight-semibold);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-tile.is-off { opacity: .55; }

/* ── 7b · Duo card (two up) ───────────────────────────────────────────────
   A destination as a bordered card, two to a row: the module's 3D logo on the
   left, one label beside it. Between `.md-row` (full width, one per line) and
   `.md-tile` (three up, label under a centred icon) — it reads left-to-right
   like a row, but two fit where one used to.

   `--wide` spans both columns, for a menu whose entries do not divide by two
   or an entry that outranks its neighbours; without it an odd count leaves a
   hole in the last line.

   Every value is a theme role, and the border is a real 1px hairline rather
   than the inset ring `.md-list` uses — asked for, and it separates cards that
   sit side by side better than a shadow does.

   Named `.md-duo`, not `.md-menu` — that name is already the dropdown a few
   hundred lines above, and taking it meant `.md-menu a` silently painted a
   surface, a ring and 8px of padding around this grid.

   NOTE: the dashboard's private `.dash-duo` is this same object, built before
   this existed. It should retire into this component rather than the two
   drifting apart.

   NOT CURRENTLY ON ANY PAGE. The CRM home — the build this was written for —
   carried it for a day and went back to `.md-list` rows: four labels in
   half-width boxes each wrap to two lines, the longest breaks mid-word, and a
   2x2 is read as four objects where a column of four is read in one movement.
   Kept, because the trade goes the other way for a menu of six or eight short
   labels, and because `.dash-duo` still has to land somewhere. Read that
   before adopting it for a list of long ones.
   ────────────────────────────────────────────────────────────────────────── */
.md-duo { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.md-duo__a {
    position: relative;
    /* Left aligned, not centred. Centring each card's icon+label pair put the
       four logos at four different x-positions — one per label length — so the
       grid had no shared edge for the eye to run down. */
    display: flex; align-items: center; justify-content: flex-start; gap: 11px;
    min-height: 78px;
    padding: 14px 12px;
    box-sizing: border-box;
    /* ── The card IS the tint ──────────────────────────────────────────────
       Four white cards with a small coloured chip on each: the colour was
       real but it was 40px of a 170px card, so the grid still read as four
       identical white boxes and the tint could not do the job it is here for
       — letting someone who cannot read "फील्ड्स" learn "the coral one" and
       stop reading the label at all (§2).

       So the tint moved out of the chip and became the card, and the chip
       took the white. Same two colours, swapped, and now the recognisable
       thing is the big shape rather than the small one.

       Kept to a WASH — the container role mixed down into the page's own
       white, not laid on at full strength. Four saturated pastel slabs under
       a filled crimson bar is a paint chart; at this strength the grid is
       four legible colours and the one action is still the loudest thing on
       the screen. `--duo` is the whole variant API: one custom property per
       card, set by the four one-liners below. */
    --duo: var(--md-outline-variant);
    --duo-wash: 44%;
    border: 1px solid color-mix(in srgb, var(--duo) 62%, transparent);
    border-radius: var(--md-shape-xl);
    background: color-mix(in srgb, var(--duo) var(--duo-wash), var(--md-surface-container-lowest));
    color: var(--md-on-surface);
    text-decoration: none; cursor: pointer;
    transition: transform var(--md-dur-short) var(--md-ease-emphasized);
}
/* The four tints, as the same container roles `.md-tint-*` reads — so a card
   and a chip of one name can never be two different blues. */
/* Blue carries a heavier wash than the other five. `--md-primary-container` is
   the palest of the six containers, so at the shared 44% it landed almost
   white next to teal and green — four cards where one had visibly opted out of
   the scheme. The number differs so the RESULT does not. */
.md-duo__a--blue  { --duo: var(--md-primary-container); --duo-wash: 62%; }
.md-duo__a--teal  { --duo: var(--md-tertiary-container); }
.md-duo__a--green { --duo: var(--md-pos-container); }
.md-duo__a--amber { --duo: var(--md-warn-container); }
.md-duo__a--slate { --duo: var(--md-secondary-container); }
.md-duo__a--coral { --duo: var(--md-neg-container); }
.md-duo__a:active { transform: scale(.97); }
.md-duo__a--wide { grid-column: 1 / -1; }
/* 40px, not the 44 this briefly grew to. The card is half a phone wide, so
   every pixel the chip takes comes out of the label: at 44 the grid's longest
   English word ("Management") no longer fit its line and `word-break` split it
   across three. The chip is the recognisable object, but the label is the one
   that has to be readable. */
.md-duo__ic {
    width: 40px; height: 40px; flex: none;
    border-radius: var(--md-shape-lg);
    display: grid; place-items: center;
}
/* The chip on a tinted card: white, because the card now carries the colour.
   `.md-tint-*` is still on it and still sets the glyph ink — this overrides
   only the ground, so a Material glyph in a card that has no PNG keeps its
   readable on-container colour. */
.md-duo__a[class*="md-duo__a--"] .md-duo__ic {
    background: var(--md-surface-container-lowest);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--duo) 55%, transparent);
}
.md-duo__ic img { width: 27px; height: 27px; object-fit: contain; display: block; }
.md-duo__ic .material-symbols-rounded { font-size: 22px; }
.md-duo__lbl {
    min-width: 0;
    font-size: 13px;
    /* 1.4 — a half-width box wraps, and Devanagari matras clip below that. */
    line-height: 1.4;
    font-weight: var(--md-weight-semibold);
    text-align: left;
    word-break: break-word;
    /* Exactly two lines, always. The renderer breaks the label after its first
       word, and this reserves the second line even for a label that has only
       one — so every card in the grid is the same height and the logos sit on
       one baseline. Without it a two-word card stood taller than a one-word
       card beside it and the row looked broken. */
    min-height: calc(1.4em * 2);
}
.md-duo__a.is-off { opacity: .55; }
/* No room for a pill beside a wrapped label at half width, so the disabled
   state is a dot in the corner; the modal still says the words on tap. */
.md-duo__dot {
    position: absolute; top: 8px; right: 8px;
    width: 8px; height: 8px;
    border-radius: var(--md-shape-full);
    background: var(--md-warn);
}

/* ── 7c · Module home kit ──────────────────────────────────────────────────
   The three pieces every module landing page needs, so the eleven of them stop
   each inventing their own. CRM is the reference build; billing, hr, tasks,
   expenses, bookings and leads follow.

   The shape is three layers and only ONE of them is a card:
       figures   what you have        — plain, on the page ground
       action    the thing to do      — the single filled button
       menu      where to go          — `.md-duo` cards
   plus a search field above and the assistant below.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Search: a tap target on the page, a full-screen layer to type in.

      Same shape as the app home (`.dh-find` → `.dh-srch` in dashboard.css):
      the bar on the page is a BUTTON, and tapping it opens a layer that owns
      the whole screen. A phone keyboard eats half the viewport, so an inline
      field leaves about four rows for answers; the layer keeps all of them,
      and the results are the point of searching.

      Ported here so the eleven module homes share one search, rather than
      each growing its own the way this screen briefly did. ── */
.md-findbar { display: flex; align-items: center; gap: 10px; width: 100%;
    text-align: left;
    /* An `<a>` as well as a `<button>`: CRM's opens a search layer in the same
       page, the My UdyogPortal tab's goes to a screen of its own. Without this
       the anchor arrives underlined and in link ink from the UA sheet. */
    text-decoration: none; color: inherit;
    padding: 4px 6px 4px 16px;
    background: var(--md-surface-container-lowest);
    /* The neutral hairline, not a brand-mixed one. A tinted frame was a way of
       making the top of the screen feel less empty under a saturated app bar;
       with the bar back to the light `--brand` wash there is nothing to
       compete with, and M3 draws a container's edge in `outline-variant`
       whatever lives inside it. */
    border: 1px solid var(--md-outline-variant);
    border-radius: var(--md-shape-full);
    margin-bottom: 16px;
}
/* On-surface-variant, not primary. M3 gives a search field's leading icon the
   variant ink — it labels the field, it is not an accent — and the page has
   one saturated object now, which is the action. */
.md-findbar__glyph { font-size: 21px; color: var(--md-on-surface-variant); flex: none; }
.md-findbar__lbl {
    flex: 1; min-width: 0;
    height: 44px; display: flex; align-items: center;
    font-family: var(--font-primary);
    font-size: 14.5px; color: var(--md-on-surface-variant);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A plain arrow, not a filled gradient disc. The whole bar is the button —
   the disc was a second, louder target inside a target, and it put a third
   saturated blue at the top of a screen that already had a blue bar above it
   and a blue glyph beside it. */
.md-findbar__go {
    flex: none; width: 40px; height: 40px;
    border: 0; border-radius: var(--md-shape-full);
    background: none; color: var(--md-on-surface-variant);
    display: grid; place-items: center; cursor: pointer;
}
.md-findbar__go .material-symbols-rounded { font-size: 20px; }

/* The search layer. Fixed over everything including the dock — a search that
   leaves the bottom navigation tappable invites the user to leave mid-query. */
.md-srch {
    position: fixed; inset: 0; z-index: 1200;
    display: flex; flex-direction: column;
    background: var(--md-surface);
}
.md-srch[hidden] { display: none; }
.md-srch__bar {
    display: flex; align-items: center; gap: 4px;
    padding: 10px 10px 10px 4px;
    background: var(--md-surface-container-lowest);
}
/* A FILLED container, not a bare input on white: with no placeholder (house
   rule) a bare input renders as an empty white bar with nothing to say a
   caret is waiting there. Focus paints the brand ring. */
.md-srch__field {
    flex: 1; min-width: 0;
    display: flex; align-items: center; gap: 10px;
    padding: 4px 6px 4px 14px;
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-high);
    transition: box-shadow var(--md-dur-short) var(--md-ease),
                background var(--md-dur-short) var(--md-ease);
}
.md-srch__field:focus-within {
    background: var(--md-surface-container);
    box-shadow: inset 0 0 0 2px var(--md-primary);
}
.md-srch__glyph { font-size: 21px; color: var(--md-primary); flex: none; }
.md-srch__input {
    flex: 1; min-width: 0;
    border: 0; outline: 0; background: none;
    font-family: inherit;
    /* 16px keeps iOS from zooming the page when the field takes focus. */
    font-size: 16px;
    color: var(--md-on-surface);
    padding: 10px 0;
    caret-color: var(--md-primary);
}
.md-srch__input::-webkit-search-cancel-button { display: none; }
.md-srch__x {
    width: 36px; height: 36px; flex: none;
    border: 0; border-radius: var(--md-shape-full);
    background: none; color: var(--md-on-surface-variant);
    display: grid; place-items: center; cursor: pointer;
}
.md-srch__x .material-symbols-rounded { font-size: 20px; }
.md-srch__x[hidden] { display: none; }
/* The label the input does not carry as a placeholder: it stays put while the
   user reads it, which is the whole argument for that rule. */
.md-srch__hint {
    margin: 0;
    padding: 0 18px 12px;
    font-size: 12.5px;
    color: var(--md-on-surface-variant);
    background: var(--md-surface-container-lowest);
    box-shadow: 0 1px 0 var(--md-outline-variant);
}
.md-srch__body { flex: 1; overflow-y: auto; padding: 6px 14px 24px; }
.md-find__results { padding: 0; }
.md-find__row {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 12px; min-height: 56px;
    border-radius: var(--md-shape-md);
    text-decoration: none; color: var(--md-on-surface);
}
.md-find__ava {
    width: 36px; height: 36px; flex: none;
    border-radius: var(--md-shape-full);
    background: var(--md-primary-container); color: var(--md-on-primary-container);
    display: grid; place-items: center;
    font-size: 14px; font-weight: var(--md-weight-semibold);
}
.md-find__b { flex: 1; min-width: 0; }
/* Both clip their overflow, so both take the §8 leading — a customer's name is
   the last place in the app that can afford a sliced matra. */
.md-find__t { display: block; font-size: 14px; line-height: 1.5; font-weight: var(--md-weight-semibold); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-find__s { display: block; font-size: 12px; line-height: 1.6; color: var(--md-on-surface-variant); font-variant-numeric: tabular-nums; }
.md-find__note {
    padding: 12px; text-align: center;
    font-size: 13px; color: var(--md-on-surface-variant);
}
/* The field must sit above the figures once it has answers, or the results
   card is painted over by the block below it. */
.md-home-in > .md-find { position: relative; z-index: 40; }

/* The screen's one primary action, wearing the same shape and lift as the
   assistant bar below it.

   GREEN, not brand blue: the app bar above is already `--grad-hero`, so a
   blue button under a blue bar read as more of the same surface rather than
   as the thing to press.

   ── The pill ────────────────────────────────────────────────────────────
   A stadium, not a 12px rectangle. `groups.php` has carried a full-width
   `.md-btn--filled` — a pill — as its create action since it was built, and
   the search bar directly above THIS button is a pill too, so the 12px
   version made the module's primary action the one full-width bar on the
   screen with its own radius. One rule now covers the module: a full-width
   bar is a pill, a card is `--md-shape-xl`, nothing is in between.

   ── The colour ──────────────────────────────────────────────────────────
   Every value here resolves to a role. It used to hold four literals — a
   white sheen written as `rgba(255,255,255,…)` three times and a `#FFFFFF`
   ink — which no theme could reach: under a second scheme the sheen would go
   on assuming a dark button underneath it.

   The ramp runs from the role to a lighter mix of ITSELF, deliberately not
   from a `--grad-*`: every gradient in the theme opens near black because
   they are built for hero surfaces, where a dark corner reads as depth. On a
   52px bar that dark corner is most of the button. */
.md-cta {
    width: 100%;
    min-height: 52px;
    border-radius: var(--md-shape-full);
    background: linear-gradient(180deg,
                    color-mix(in srgb, var(--md-on-cta) 11%, transparent) 0%,
                    transparent 62%),
                /* 90/10, not 80/20: a fifth of the ink mixed into crimson is
                   pink, and the bar read as dusty rather than as one colour.
                   Teal survived the wider ramp; red does not. */
                linear-gradient(135deg,
                    var(--md-cta) 0%,
                    color-mix(in srgb, var(--md-cta) 90%, var(--md-on-cta)) 100%);
    color: var(--md-on-cta);
    /* The inset highlight only. A coloured glow under it made the button
       hover over the card it lives in; M3 rests a filled button at elevation
       0 and lets the fill do the work, and this one is the only filled thing
       on the screen — it does not need help being found. */
    box-shadow: inset 0 1px 0 color-mix(in srgb, var(--md-on-cta) 28%, transparent);
}
.md-cta .md-btn__label, .md-cta .material-symbols-rounded { color: var(--md-on-cta); }

/* ── Figures: the module's two numbers, in a bordered block. Cards here would
      be a third rounded-bordered object on a screen that already has the menu
      — see the CRM note: six lookalike boxes give the eye nowhere to land. ── */
/* Figures and the module's one action, in a single panel.

   They were two separate blocks: a bordered figures card, then a full-width
   gradient bar under it. That put TWO saturated full-width bars on the screen
   with the four white cards trapped between them, and the middle one — the
   primary action — was the heaviest thing on the page without being the most
   important. Inside the panel it is framed by white, so it can stay a filled
   button without shouting. */
.md-panel {
    margin: 14px 0 16px;
    padding: 16px 14px 14px;
    /* ── The one surface on the page that is lit ───────────────────────────
       Everything else here is flat by house rule, and that is right for a
       list of destinations. This block is not a destination: it is what the
       user came to see (two numbers) and the one thing to do about it, so it
       is allowed to read as an object with light falling on it rather than as
       a rectangle drawn on the page.

       The light is two theme hues at a strength you would not call colour —
       primary in the top-left corner, tertiary in the bottom-right, both
       under 8% and both mixed INTO the surface rather than laid over it, so
       the card stays a white card and simply stops being uniform. Straight
       role tokens: a repalette turns the light with the theme, and under a
       dark scheme the same mix darkens instead of glowing.

       The border takes a little of the same primary, and the lift is a single
       wide, nearly-transparent shadow — the card floats a millimetre, not a
       centimetre. */
    border: 1px solid color-mix(in srgb, var(--md-primary) 16%, var(--md-outline-variant));
    border-radius: var(--md-shape-xl);
    box-shadow: 0 10px 26px -20px color-mix(in srgb, var(--md-primary) 65%, transparent);
    /* The light itself. It was briefly a `.md-lit` utility, so the list below
       could be lit from the same direction — and the list turned out not to
       want it: a menu is read once and then navigated by memory, and a lit
       ground under four names is a finish rather than a focus. With one
       caller the class was an abstraction over nothing, so the recipe lives
       where it is used. */
    background:
        radial-gradient(120% 90% at 0% 0%,
            color-mix(in srgb, var(--md-primary) 11%, var(--md-surface-container-lowest)) 0%,
            transparent 62%),
        radial-gradient(110% 85% at 100% 100%,
            color-mix(in srgb, var(--md-tertiary) 8%, var(--md-surface-container-lowest)) 0%,
            transparent 58%),
        var(--md-surface-container-lowest);
}
/* Both rules inside the card fade at their ends instead of running into the
   padding. A hairline that stops dead against a rounded corner is the detail
   that makes a card look drawn; one that fades looks lit, which is the whole
   argument of the block above. */
.md-panel__sep {
    height: 1px; margin: 14px -14px 14px;
    background: linear-gradient(90deg,
        transparent 0%,
        var(--md-outline-variant) 18%,
        var(--md-outline-variant) 82%,
        transparent 100%);
}
.md-figs { display: grid; grid-template-columns: 1fr 1fr; }
.md-fig {
    display: flex; flex-direction: column; align-items: center; gap: 1px;
    padding: 2px 6px; min-width: 0;
    text-align: center;
    text-decoration: none; color: var(--md-on-surface);
}
.md-fig + .md-fig {
    background-image: linear-gradient(180deg,
        transparent 0%,
        var(--md-outline-variant) 22%,
        var(--md-outline-variant) 78%,
        transparent 100%);
    background-size: 1px 100%;
    background-repeat: no-repeat;
    background-position: left center;
}
.md-fig__val {
    font-size: 28px;
    /* 1.25, not a Latin-tight 1.1: a figure is not always digits, and
       Devanagari matras sit above the line. */
    line-height: 1.25; font-weight: var(--md-weight-semibold);
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
    /* The tone is the figure's own, set by the modifier below. Ink is the
       fallback, so a figure nobody has classified still renders. */
    color: var(--md-fig-tone, var(--md-on-surface));
}
/* The figure in brand ink — the same clipped gradient fill `.md-appbar--brand`
   gives the page title, on-primary-container into primary, so the two biggest
   pieces of type on the screen are drawn the same way. Both stops are roles
   with a guaranteed floor on this surface, so it costs no legibility.

   `:not(.skl)` because the skeleton IS a background: shimmer clipped to the
   glyphs of a placeholder "0" is not a loading bar, it is a flickering zero.
   countTo() drops `.skl` the moment a real value arrives, and the number takes
   its colour then. */
.md-fig__val:not(.skl) {
    background: linear-gradient(135deg, var(--md-on-primary-container) 8%, var(--md-primary) 96%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
}
/* The ONE tone, and a page adds it from its data rather than from its markup.
   Both figures were painted at build time — brand for the count, warn for the
   overdue — which put an amber number on the screen of a business with
   nothing overdue at all. A figure that is always coloured is decoration; a
   figure that turns amber the moment somebody is waiting is information (§2),
   and the label says it too, so the colour is never carrying it alone. */
.md-fig--warn { --md-fig-tone: var(--md-warn); }
/* "No answer" is not a number. When the call fails the figure prints an em
   dash, and in the brand fill that dash read like a value someone had chosen.
   Variant ink says the same thing the dash does. */
.md-fig__val.is-none, .md-fig__val.is-none:not(.skl) {
    background: none;
    -webkit-text-fill-color: var(--md-on-surface-variant);
    color: var(--md-on-surface-variant);
}

/* Warn is a state, so it wins over the brand fill: a flat amber number, no
   gradient. A figure that is telling you somebody is waiting should not also
   be the prettiest thing on the card. */
.md-fig--warn .md-fig__val:not(.skl) {
    background: none;
    -webkit-text-fill-color: var(--md-warn);
}
.md-fig__lbl {
    display: inline-flex; align-items: center; gap: 1px;
    max-width: 100%;
    /* 1.6 — small text inside a box that ellipsizes, which is the §8 case. */
    font-size: 12.5px; line-height: 1.6; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Small and the same ink as the label — it is punctuation, not a control. At
   the label's own size it would read as a second thing to tap. */
.md-fig__go { font-size: 15px; flex: none; opacity: .75; }
.md-fig.is-off { opacity: .55; }

/* ── Ask the assistant: full width, under everything, tonal so it reads as
      an offer rather than competing with the screen's one filled action. ── */
.md-ask {
    display: flex; align-items: center; gap: 12px;
    width: 100%; min-height: 64px;
    margin-top: 16px;
    padding: 10px 14px 10px 12px;
    /* A calm row with her face (owner, 2026-10-21: "make this across app"),
       the same as Blogs' "Ask Myra to write" (.md-list / .md-row): a white
       surface on a thin rule. The crimson tint and halo went with her old
       colour; she wears emerald now, and an offer should not shout. */
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-lowest);
    box-shadow: 0 0 0 1px var(--md-outline-variant);
    color: var(--md-on-surface);
    text-decoration: none;
    transition: transform var(--md-dur-short) var(--md-ease-emphasized);
}
.md-ask:active { transform: scale(.98); }
.md-ask__ava {
    width: 44px; height: 44px; flex: none;
    border-radius: var(--md-shape-md);
    background: var(--md-surface-container);
    display: grid; place-items: center;
}
.md-ask__img { width: 32px; height: 32px; object-fit: contain; display: block; }
.md-ask__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.md-ask__lbl { font-size: 15px; line-height: 1.4; font-weight: var(--md-weight-semibold); color: var(--md-on-surface); }
/* The line that says what an assistant is FOR. Without it the button is a
   name and a robot, and a first-time user has no idea what tapping does. */
.md-ask__sub {
    font-size: 12.5px; line-height: 1.6;
    color: var(--md-on-surface-variant);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-ask__go { flex: none; font-size: 22px; color: var(--md-on-surface-variant); }
.md-ask.is-off { opacity: .55; }

/* ── Entrance: the blocks, staggered, never the rows inside them. Four steps
      done inside 0.32s — long enough to read as arrival, short enough that a
      cold load is not a slideshow. ── */
@keyframes md-home-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.md-home-in > * { animation: md-home-in .34s cubic-bezier(.2,.7,.3,1) both; }
.md-home-in > *:nth-child(1) { animation-delay: 0ms; }
.md-home-in > *:nth-child(2) { animation-delay: 60ms; }
.md-home-in > *:nth-child(3) { animation-delay: 120ms; }
.md-home-in > *:nth-child(4) { animation-delay: 180ms; }
.md-home-in > *:nth-child(n+5) { animation-delay: 220ms; }
@media (prefers-reduced-motion: reduce) {
    .md-home-in > * { animation: none; }
}

/* ── 8 · Wizard header — RETIRED ──────────────────────────────────────────
   The `.md-wizard` stepper (segments, count, jump-back) left with the wizard
   shell on 2026-09-13 (FORM_STANDARD §13): a form is one screen of section
   cards and chips now, assembled from the form kit in §11. The number stays
   so the section indexes in this file keep their meaning.
   ────────────────────────────────────────────────────────────────────────── */

/* ── 9 · Action bar ────────────────────────────────────────────────────────
   43 pages hand-roll a sticky footer holding Back and Save. One component.

   `--sticky` is fixed to the bottom of the viewport, so the scrolling content
   above it MUST reserve the clearance or the last field hides underneath —
   that is what `.md-actionbar-clear` is for, and forgetting it is the single
   most common way this component ships broken.
   ────────────────────────────────────────────────────────────────────────── */
.md-actionbar {
    display: flex; align-items: center; gap: 10px;
    padding: 12px 16px;
    padding-bottom: calc(12px + env(safe-area-inset-bottom));
    background: var(--md-surface-container-low);
    box-shadow: inset 0 1px 0 var(--md-outline-variant);
}
.md-actionbar--sticky {
    position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 90;
    box-shadow: inset 0 1px 0 var(--md-outline-variant),
                0 -4px 16px -8px color-mix(in srgb, var(--md-on-surface) 40%, transparent);
}
/* Desktop wears the phone frame: #app-viewport is a centred, rounded,
   overflow-hidden mock device, and `fixed` positions against the BROWSER —
   so this bar (and the FAB below) escaped the bezel and spanned the whole
   screen. Above the frame breakpoint they anchor to the frame instead:
   absolute, against #app-viewport's position:relative, clipped by its
   radius. On a real phone (≤450px) the frame IS the viewport and fixed
   stays right. */
@media (min-width: 451px) {
    .md-actionbar--sticky,
    .md-fab { position: absolute; }
}
/* Two actions sharing the width equally — the common Back / Save pair. */
.md-actionbar--split > .md-btn { flex: 1; min-width: 0; }
/* One action, full width. */
.md-actionbar--single > .md-btn { flex: 1; }
/* Pushes what follows to the right, for a bar whose left side is a running total. */
.md-actionbar__spacer { flex: 1; min-width: 0; }
.md-actionbar-clear { padding-bottom: calc(88px + env(safe-area-inset-bottom)); }

/* ── 10 · Sheet ────────────────────────────────────────────────────────────
   ⚠ NOT a bottom sheet. Bottom sheets are BANNED app-wide (§8.7) — this is the
   panel INSIDE a centred `.auth-modal-overlay`, and the name means a sheet of
   content. If you find yourself giving this `bottom: 0`, stop.

   Replaces `.help-modal*` plus the 83 pages carrying an inline copy of it.
   `--help` carries the question/answer typography `.help-para*` provided.

     <div class="auth-modal-overlay"><div class="auth-modal md-sheet md-sheet--help"
          role="dialog" aria-modal="true" aria-labelledby="…">
       <div class="md-sheet__head"> … __ic __title __sub, close button … </div>
       <div class="md-sheet__body">
         <div><p class="md-sheet__q">…</p><p class="md-sheet__a">…</p></div>
       </div>
       <div class="md-sheet__foot"> … </div>
     </div></div>
   ────────────────────────────────────────────────────────────────────────── */
/* ONE surface, MD3's basic dialog — not a head band over a lighter body.
   The band suited a picker whose head names the list under it; on a dialog it
   read as two panels stuck together, which is what kept sending the category
   editor back for another look. The type carries the hierarchy now. The
   `--help` variant still paints its own gradient head, deliberately: that one
   IS a branded panel.

   ── The ground is the app's LIGHTEST surface ────────────────────────────
   `-lowest`, and this line was `-high` until 2026-08-29. Every `.md-sheet` in
   the app is also an `.auth-modal`, and `dialog.css` sets that to `-lowest` —
   so this rule silently won on all 63 pages that combine them, and every
   dialog in the product was a MID-GREY FRAME around lighter contents. A list,
   a field, a recessed panel all sit on `-lowest`/`-low`/`container`, which are
   lighter than `-high`, so a dialog read inside-out: the frame darker than the
   things inside it.

   `-lowest` is the ground a card uses, which is the point — anything placed in
   a dialog then sits ON it rather than under it, and "one surface" is true in
   pixels and not only in this comment. See MATERIAL_DESIGN §8.7; the rule is
   enforced by tests/theme_parity_test.php PART 4. */
.md-sheet {
    display: flex; flex-direction: column;
    max-height: 85vh;
    padding: 0;
    overflow: hidden;
    border-radius: var(--md-shape-xl);
    background: var(--md-surface-container-lowest);
}
.md-sheet__head {
    flex-shrink: 0;
    display: flex; align-items: flex-start; gap: 12px;
    padding: 20px 16px 12px 20px;
    background: transparent;
    color: var(--md-on-surface);
}
/* A title that wraps — "Add New Category" does at 360px — must not drag its
   icon and close button to the middle of the block. */
.md-sheet--brand .md-sheet__head,
.md-sheet--help .md-sheet__head { align-items: center; }
.md-sheet__ic {
    width: 38px; height: 38px; flex-shrink: 0;
    border-radius: var(--md-shape-md);
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
    display: grid; place-items: center;
}
.md-sheet__ic .material-symbols-rounded { font-size: 20px; }
/* The head's text block. It was `style="flex:1;min-width:0"` written inline at
   every call site — the help partial and every page that hand-rolled a sheet —
   which is a layout rule living in markup, and `min-width: 0` is not optional:
   without it the title refuses to shrink and pushes the close button off the
   panel. */
.md-sheet__hd { flex: 1; min-width: 0; }

.md-sheet__title {
    flex: 1; min-width: 0;
    margin: 0;
    font-family: var(--font-primary);
    font-size: 16px; line-height: 1.35; font-weight: var(--md-weight-semibold);
    color: inherit;
    letter-spacing: normal;
}
.md-sheet__sub {
    display: block;
    margin-top: 1px;
    font-size: 12px; line-height: 1.4; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
}

/* `--help` is the branded variant: gradient head, and the Q/A rhythm below.
   The plain sheet is a quiet panel — a confirm, a picker, a summary — and it
   should NOT shout in brand colour just because it is a dialog. Everything the
   gradient changes is scoped here, so the two cannot drift apart. */
/* ── The branded head ───────────────────────────────────────────────────────
   `--brand` is the head in the app's blue; `--help` is that plus the Q&A
   rhythm below it. They were one rule when help was the only sheet that
   wanted it — the category editor wanted the same head without the help
   panel's insides, so the look is its own modifier now and the two cannot
   drift apart. The gradient on a dialog head is the app's established
   pattern (every help sheet ships it), and white on it is a literal for the
   same reason the hero's is: there is no on-gradient role to name. */
.md-sheet--brand .md-sheet__head,
.md-sheet--help .md-sheet__head {
    background: var(--grad-hero); color: #FFFFFF;
    padding: 18px 16px 18px 20px;
}
.md-sheet--brand .md-sheet__ic,
.md-sheet--help .md-sheet__ic {
    background: rgba(255, 255, 255, .22);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .32);
    color: #FFFFFF;
}
.md-sheet--brand .md-sheet__sub,
.md-sheet--help .md-sheet__sub { color: rgba(255, 255, 255, .9); }
/* The close button sits on the gradient, so it takes white, not a role. */
.md-sheet--brand .md-sheet__head .md-icon-btn,
.md-sheet--help .md-sheet__head .md-icon-btn { color: #FFFFFF; background: rgba(255, 255, 255, .18); }

/* flex:1 + min-height:0 is what lets THIS scroll instead of the modal growing
   past its max-height. Remove either and a long help body breaks on a short
   phone — while still looking fine on a desktop. */
.md-sheet__body {
    flex: 1 1 auto; min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 16px 18px;
    display: flex; flex-direction: column; gap: 18px;
    color: var(--md-on-surface-variant);
    font-size: 14px; line-height: 1.6;
}
/* ── A sheet's body SCROLLS; its children do not shrink (customer side) ──────
   `.md-sheet__body` is a flex column that scrolls. A flex child may shrink,
   and one with `overflow: hidden` — every `.md-list` — shrinks to nothing
   rather than overflowing: the list is squeezed to the space left, its rows
   are cut off, and the body has nothing to scroll. On a short phone a dialog
   with a long list (Add a favourite, Arrange, Contact and more, Customise
   menu) lost its last rows with no way to reach them.

   Ten module sheets already carry this line for their own dialog
   (`.ic-modal`, `.cat-modal`, `.emp-modal` …, and billing/invoice_create.css
   states it for its whole page). This states it once for every dialog the
   customer sees: the customer pages (`data-skin`) and the dashboard in
   Customer Mode (`data-mode`). Measured 2026-10-06 across all 86 dialogs of
   the customer pages. */
body[data-skin="customer"] .md-sheet__body > *,
body[data-mode="customer"] .md-sheet__body > * { flex-shrink: 0; }

.md-sheet__foot {
    flex-shrink: 0;
    padding: 12px 20px calc(16px + env(safe-area-inset-bottom));
    background: transparent;
    /* The hairline stays: it is not decoration but the edge of a scrolling
       body, and a sheet's body scrolls (the help panel's Q&A, a long picker). */
    box-shadow: inset 0 1px 0 var(--md-outline-variant);
    display: flex; flex-direction: column; gap: 8px;
}
.md-sheet__foot .md-btn { width: 100%; }
/* Two answers side by side — Cancel and the verb. Promoted from crm/add.css's
   `.cc-dlg__btns` (2026-09-13) when the customer record's dialogs became its
   second and third users. Equal halves, so the row's rhythm does not change
   with the language. A foot with one button, or three, stays stacked. */
.md-sheet__foot--row { flex-direction: row; gap: 10px; }
.md-sheet__foot--row .md-btn { flex: 1 1 0; min-width: 0; width: auto; }

/* ── The confirm dialog — THE one shape for every "are you sure?" ───────────
   Settled 2026-09-14 (MATERIAL_DESIGN §8.7 "Confirm dialogs"), after a sweep
   found 172 confirmations built ten different ways: red and amber buttons,
   Cancel on the left on one page and on the right on the next, "Back" that
   discarded and "Cancel" that stayed.

   One shape now, and it has three parts:
     1. the HEAD — the brand gradient (`.md-sheet--brand`) with the question as
        its title. No × — the left button is the way out.
     2. the BODY — the message, optionally the record's name and one note.
     3. the FOOT — two answers in a row: NEGATIVE on the LEFT (outlined),
        POSITIVE on the RIGHT (filled theme primary). Always. Any further
        answers stack full-width ABOVE that pair.

   Theme roles only. There is no red and no amber here, not even for a delete:
   the verb on the right ("Delete bill") and the question in the head carry the
   meaning, and the theme carries the colour.

   Drawn by `customConfirm()` (commons/footer.php) for a plain question, and by
   `cfm_open()` / `cfm_close()` (commons/components/confirm_dialog.php) for one
   that holds fields of its own (a reason, a date, a note). */
.md-cfm-overlay { z-index: 99999; }
.md-cfm { max-width: 360px; }
.md-cfm .md-sheet__title {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
    overflow: hidden; overflow-wrap: anywhere;
}
.md-cfm .md-sheet__body { gap: 10px; padding: 18px 20px 8px; }
.md-cfm__name {
    margin: 0;
    font-size: 15px; line-height: 1.4; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    overflow-wrap: anywhere;
}
.md-cfm__msg {
    margin: 0;
    font-size: 14.5px; line-height: 1.55; font-weight: var(--md-weight-regular);
    color: var(--md-on-surface-variant);
    overflow-wrap: anywhere;
    white-space: pre-line;
}
/* What ELSE the answer touches. Surface ink on a recessed panel — not warn
   amber: the standard carries no second colour. */
.md-cfm__note {
    margin: 0;
    padding: 10px 12px;
    border-radius: var(--md-shape-md);
    background: var(--md-surface-container-low);
    font-size: 13px; line-height: 1.5; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface);
    overflow-wrap: anywhere;
}
.md-cfm .md-sheet__body .md-field { margin: 0; }
/* The body does not scroll in a plain confirm, so the foot needs no hairline. */
.md-cfm .md-sheet__foot { box-shadow: none; padding-top: 14px; gap: 10px; }
.md-cfm__opts { display: flex; flex-direction: column; gap: 8px; }
.md-cfm__opts .md-btn { width: 100%; height: auto; min-height: 48px; padding: 10px 16px; white-space: normal; line-height: 1.25; }
.md-cfm__row { display: flex; flex-direction: row; gap: 10px; }
/* Equal halves, and a label that WRAPS rather than clips — a Marathi verb is
   often twice the English. */
.md-cfm__row .md-btn {
    flex: 1 1 0; min-width: 0; width: auto;
    height: auto; min-height: 48px; padding: 10px 14px;
    font-size: 15px; line-height: 1.25;
    white-space: normal; text-align: center;
}
/* Left is negative, right is positive — held by `order` as well as by the
   markup, so a page that writes the pair the wrong way round still lands it
   the right way round on screen. */
.md-cfm__no  { order: 1; }
.md-cfm__yes { order: 2; }
.md-cfm__row .md-btn[hidden] { display: none; }

.md-sheet__q {
    margin: 0 0 3px;
    font-family: var(--font-primary);
    font-size: 14px; line-height: 1.4; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    letter-spacing: normal;
}
.md-sheet__a {
    margin: 0;
    font-family: var(--font-primary);
    font-size: 13.5px; line-height: 1.6; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
}

/* ── 11 · Form kit ─────────────────────────────────────────────────────────
   The ONE-SCREEN form (FORM_STANDARD.md; FORMS_MIGRATION_HANDOFF.md §5).
   Lifted 2026-09-13 from billing/invoice_create.css, where the shape was
   settled: a column of section cards that reveal in order, each owning a
   hue; a dashed picker row that turns into a card; compact numbered lines;
   chips that open centred dialogs and read the answer back; a preview in
   the Confirm dialog; and a sticky Save bar that rides above the keyboard.

   Markup is emitted by commons/components/form_kit.php (fk_* helpers) and
   driven by assets/js/platform/form-kit.js (FormKit.*). A page keeps only
   what is its own — a payment segment, a stock guard, a picker — in its
   page sheet. Every value here is a role token, so the kit re-themes and
   inverts with everything else.

     <div class="screen-content md-form" id="<pfx>-scroll">
       <div class="md-form__col">
         <section class="md-sec md-sec--who">  __head(__ic __ttl __meta) __body  </section>
         <section class="md-sec md-sec--what md-reveal" hidden> …
       </div>
     </div>
     <div class="md-actionbar md-actionbar--sticky md-savebar"> __total __extras __btns </div>
   ────────────────────────────────────────────────────────────────────────── */

/* ── The page ──────────────────────────────────────────────────────────────
   The light container is the ground, so a white section card stands off it
   by tone and not only by a hairline. The bottom clearance is the Save bar's
   full three-row height (total · extras · buttons) plus the safe area; the
   keyboard inset (`--kb`) is style.css's job on every scroller. */
/* `#app-viewport` restated: billing.css paints `#app-viewport .screen-content`
   with the ambient wash at (1,1,0), which outranks a class pair. */
.screen-content.md-form,
#app-viewport .screen-content.md-form {
    flex: 1;
    overflow-y: auto;
    padding: 18px 14px calc(190px + env(safe-area-inset-bottom)) !important;
    background: var(--md-surface-container-low);
}
.md-form__col {
    max-width: 560px; width: 100%;
    margin: 0 auto;
    display: flex; flex-direction: column; gap: 12px;
}

/* ── The section card ──────────────────────────────────────────────────────
   A white card that OWNS a hue: the mark is drawn at that hue on a light wash
   of itself, the same identification tint the module homes use. The hue is
   only ever in the head and the edge — the body stays white, because the
   money on it must not be tinted by anything.

     --who   primary    the person / party
     --tax   tertiary   GST and the like
     --what  warn       what is being sold / bought
     --money pos        amounts, payment
     --when  secondary  dates and times                                    */
/* `section.md-sec`, not `.md-sec`: the library already has `.md-sec` as the
   SECTION HEADER (an `<h2>` with an optional `__action`, twenty pages). §5 of
   the handoff fixed the card's name before that was noticed, so the card is
   told apart by its element — fk_section_open() always emits a `<section>` —
   and resets the header's own declarations (padding, type, centring) that
   would otherwise leak into it. An `<h2 class="md-sec">` is untouched. */
section.md-sec {
    --sec: var(--md-primary);
    display: flex; flex-direction: column; gap: 0;
    align-items: stretch; justify-content: flex-start;
    margin: 0; padding: 0;
    font-size: inherit; line-height: inherit; letter-spacing: normal;
    font-weight: var(--md-weight-regular);
    color: var(--md-on-surface);
    background: var(--md-surface-container-lowest);
    border: 1px solid color-mix(in srgb, var(--sec) 22%, var(--md-outline-variant));
    border-radius: var(--md-shape-xl);
    overflow: hidden;
}
/* Element-qualified like the base rule above: a bare `.md-sec--what` (0,1,0)
   lost to `section.md-sec` (0,1,1) and every card came out primary blue. */
section.md-sec--who   { --sec: var(--md-primary); }
section.md-sec--tax   { --sec: var(--md-tertiary); }
section.md-sec--what  { --sec: var(--md-warn); }
section.md-sec--money { --sec: var(--md-pos); }
section.md-sec--when  { --sec: var(--md-secondary); }

.md-sec__head {
    display: flex; align-items: center; gap: 10px;
    min-height: 46px;
    padding: 8px 14px;
    background: color-mix(in srgb, var(--sec) 10%, var(--md-surface-container-lowest));
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--sec) 20%, transparent);
}
.md-sec__ic {
    width: 30px; height: 30px; flex: none;
    border-radius: var(--md-shape-md);
    background: color-mix(in srgb, var(--sec) 18%, var(--md-surface-container-lowest));
    color: var(--sec);
    display: grid; place-items: center;
}
.md-sec__ic .material-symbols-rounded { font-size: 18px; font-variation-settings: 'FILL' 1; }
/* Hind sets Devanagari high in its line box (the matras sit above the
   shirorekha), so a title beside a 30px mark reads a touch above centre. */
.md-sec__ttl {
    margin: 0; flex: 1; min-width: 0;
    padding-top: 2px;
    font-family: var(--font-primary);
    font-size: 14px; line-height: 1.4; font-weight: var(--md-weight-semibold);
    letter-spacing: normal;
    color: var(--md-on-surface);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-sec__meta {
    flex: none;
    font-size: 12px; line-height: 1.6; font-weight: var(--md-weight-semibold);
    padding: 1px 10px;
    border-radius: var(--md-shape-full);
    background: color-mix(in srgb, var(--sec) 16%, var(--md-surface-container-lowest));
    color: var(--md-on-surface);
}
.md-sec__body { padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; }
/* fk_section_open(['scroll' => true]): a long single-choice list scrolls
   inside the card instead of pushing the save bar off the screen. */
.md-sec__body--scroll { max-height: 340px; overflow-y: auto; overscroll-behavior: contain; }
.md-sec__body > .md-field { margin: 0; }
.md-sec__body > .md-field + .md-field { margin-top: 4px; }
/* The inline errors sit under the thing they are about, only when active. */
.md-sec__err, .md-sec .md-field__err { margin-top: 0; }
.md-sec > .md-sec__err { margin: 0 14px 12px; }

/* ── The fold ──────────────────────────────────────────────────────────────
   fk_section_open(['fold' => true]) + FormKit.folds(). A section whose editor
   is tall (a single-choice list, a picker) closes to ONE row: its title, the
   answer under it, a status mark, a chevron. Closed, the card is plain white
   and the hue stays in the mark, so a column of folds reads as a checklist,
   not a stack of banners; open, the head takes the section's wash again.

     data-state  ok    answered          check_circle  --md-pos
                 need  required, empty   warning       --md-warn   + "Needed"
                 err   after a save try  error         --md-error  + the message
                 opt   optional, empty   (no mark)

   `overflow: clip`, not `hidden`: clip does not make the card a scroll
   container, so a search box inside an open fold can stay sticky against the
   page scroller while the list under it scrolls. */
section.md-fold { overflow: clip; transition: border-color var(--md-dur-short) var(--md-ease); }
.md-fold__h { margin: 0; padding: 0; font: inherit; letter-spacing: normal; }
.md-fold__head {
    width: 100%;
    min-height: 64px;
    padding: 10px 12px 10px 14px;
    border: 0;
    text-align: left;
    font-family: var(--font-primary);
    color: inherit;
    cursor: pointer;
    background: var(--md-surface-container-lowest);
    box-shadow: none;
}
section.md-fold.is-open > .md-fold__h > .md-fold__head {
    background: color-mix(in srgb, var(--sec) 10%, var(--md-surface-container-lowest));
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--sec) 20%, transparent);
}
.md-fold__head:focus-visible { outline: 2px solid var(--md-primary); outline-offset: -2px; }
.md-fold__txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.md-fold__txt .md-sec__ttl { flex: none; padding-top: 0; font-size: 13px; color: var(--md-on-surface-variant); font-weight: var(--md-weight-medium); }
.md-fold__val {
    font-size: 15px; line-height: 1.45;
    font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
section.md-fold[data-state="need"] .md-fold__val,
section.md-fold[data-state="opt"] .md-fold__val { font-weight: var(--md-weight-regular); color: var(--md-on-surface-variant); }
section.md-fold[data-state="err"] .md-fold__val { font-weight: var(--md-weight-medium); color: var(--md-error); white-space: normal; }

.md-fold__st { flex: none; display: inline-flex; align-items: center; gap: 4px; }
.md-fold__st-ic { font-size: 22px; font-variation-settings: 'FILL' 1; }
.md-fold__st-ic:empty, .md-fold__st-lbl:empty { display: none; }
.md-fold__st-lbl {
    font-size: 12px; line-height: 1.5; font-weight: var(--md-weight-semibold);
    padding: 1px 8px; border-radius: var(--md-shape-full);
}
section.md-fold[data-state="ok"]   .md-fold__st-ic { color: var(--md-pos); }
section.md-fold[data-state="need"] .md-fold__st-ic { color: var(--md-warn); }
section.md-fold[data-state="need"] .md-fold__st-lbl { background: var(--md-warn-container); color: var(--md-on-warn-container); }
section.md-fold[data-state="err"]  .md-fold__st-ic { color: var(--md-error); }
section.md-fold[data-state="err"]  .md-fold__st-lbl { background: var(--md-error-container); color: var(--md-on-error-container); }
section.md-fold[data-state="err"] { border-color: var(--md-error); }
/* The open fold shows its editor, so the "Needed" pill has said its piece. */
section.md-fold.is-open .md-fold__st-lbl { display: none; }

.md-fold__chev {
    flex: none; font-size: 24px; color: var(--md-on-surface-variant);
    transition: transform var(--md-dur-short) var(--md-ease);
}
section.md-fold.is-open .md-fold__chev { transform: rotate(180deg); }
section.md-fold.is-open > .md-fold__body { animation: md-fold-in var(--md-dur-short) var(--md-ease); }
@keyframes md-fold-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
    section.md-fold.is-open > .md-fold__body { animation: none; }
    .md-fold__chev { transition: none; }
}

/* ── The picker row ────────────────────────────────────────────────────────
   Unanswered: dashed and brand-tinted, the same "add" grammar the chips use,
   so an owner learns one shape for "tap to fill". Answered: the card, with
   Change. Inside a section the picked card loses its own box. */
.md-pickrow {
    width: 100%;
    display: flex; align-items: center; gap: 12px;
    min-height: 64px;
    padding: 12px 14px;
    border-radius: var(--md-shape-xl);
    border: 1.5px dashed color-mix(in srgb, var(--md-primary) 55%, transparent);
    background: color-mix(in srgb, var(--md-primary) 4%, var(--md-surface-container-lowest));
    text-align: left;
    font-family: var(--font-primary);
    cursor: pointer;
}
.md-pickrow--picked {
    cursor: default;
    border: 1px solid var(--md-outline-variant);
    background: var(--md-surface-container-lowest);
}
.md-sec .md-pickrow--picked { border: 0; padding: 0; min-height: 0; background: transparent; }
.md-pickrow__av {
    width: 46px; height: 46px; flex: 0 0 46px;
    border-radius: var(--md-shape-full);
    background: var(--md-primary);
    color: var(--md-on-primary);
    display: grid; place-items: center;
    font-size: 17px; font-weight: var(--md-weight-semibold);
    overflow: hidden;
}
.md-pickrow__av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.md-pickrow__av .material-symbols-rounded { font-size: 24px; }
.md-pickrow__av--ask { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.md-pickrow__av--alt { background: var(--md-tertiary-container); color: var(--md-on-tertiary-container); }
.md-pickrow__copy { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.md-pickrow__copy b {
    font-size: 15.5px; line-height: 1.35; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-pickrow:not(.md-pickrow--picked) .md-pickrow__copy b { color: var(--md-primary); }
.md-pickrow__copy span {
    font-size: 12.5px; line-height: 1.45; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-pickrow__go { color: var(--md-primary); flex: none; }
.md-pickrow__chg {
    flex: none;
    min-height: 44px; padding: 0 16px;
    border: 0; border-radius: var(--md-shape-full);
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
    font-family: var(--font-primary);
    font-size: 13px; font-weight: var(--md-weight-semibold);
    cursor: pointer;
}
/* The quiet second answer under a picker row ("bill without a customer"). */
.md-pickrow__alt {
    display: inline-flex; align-items: center; gap: 6px;
    align-self: center;
    margin: 4px auto 0;
    height: 36px; padding: 0 12px;
    border: 0; border-radius: var(--md-shape-full);
    background: transparent;
    color: var(--md-on-surface-variant);
    font-family: var(--font-primary);
    font-size: 13px; font-weight: var(--md-weight-medium);
    cursor: pointer;
}
.md-pickrow__alt .material-symbols-rounded { font-size: 18px; }
.md-pickrow-stack { display: flex; flex-direction: column; align-items: stretch; }

/* ── The choice card ───────────────────────────────────────────────────────
   One answer to "which of these are you?": a mark, a title, a sentence that
   may run to two or three lines, a chevron. The whole card is one target, a
   `button` or an `a`.

   Shared (account flow, addition 3) because it is the fifth of its kind:
   `.persona-card` (auth.css), `.bka-tile`, `.ups-trade` and `.mh-pick__o` are
   page copies of this shape, and FORM_DESIGN.md §8 says to promote on the
   fourth. `.md-row` cannot do the job: its second line is one line with an
   ellipsis, and here the second line is the reason to choose.

   It takes `--md-primary*`, so the customer skin recolours it by itself. */
.md-choice {
    display: flex; align-items: center; gap: 14px;
    box-sizing: border-box;
    width: 100%;
    min-height: 80px;
    padding: 14px 16px;
    border: 0;
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-lowest);
    box-shadow: var(--md-elev-1);
    color: var(--md-on-surface);
    font-family: var(--font-primary);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.md-choice + .md-choice { margin-top: 12px; }
.md-choice__ic {
    flex: none;
    width: 52px; height: 52px;
    border-radius: var(--md-shape-md);
    display: grid; place-items: center;
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
}
.md-choice__ic .material-symbols-rounded { font-size: 28px; }
.md-choice__tx { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.md-choice__tx b {
    font-size: 17px; line-height: 1.4; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
}
/* It wraps. No clamp, no ellipsis: see the note above. */
.md-choice__tx span {
    font-size: 13px; line-height: 1.45;
    color: var(--md-on-surface-variant);
}
.md-choice__go { flex: none; color: var(--md-on-surface-variant); }
.md-choice[aria-pressed="true"] { box-shadow: inset 0 0 0 2px var(--md-primary); }
.md-choice:focus-visible { outline: 2px solid var(--md-primary); outline-offset: 2px; }
/* A door whose feature is off: still tappable, it answers with the reason. */
.md-choice.is-off { opacity: .55; }

/* ── The switch row ────────────────────────────────────────────────────────
   One line: a mark, a word, a figure, the switch. As the whole of a `--tax`
   section the row wears the section's wash. `--flat` is the version inside a
   dialog: a hairline row, not a nested box. */
.md-switchrow {
    display: flex; align-items: center; gap: 10px;
    min-height: 52px;
    padding: 8px 14px;
    border-radius: var(--md-shape-lg);
    border: 1px solid var(--md-outline-variant);
    background: var(--md-surface-container-lowest);
    cursor: pointer;
}
.md-sec > .md-switchrow {
    border: 0; border-radius: 0;
    background: color-mix(in srgb, var(--sec) 8%, var(--md-surface-container-lowest));
}
.md-switchrow--flat { border: 0; border-radius: 0; padding: 12px 0 0; box-shadow: inset 0 1px 0 var(--md-outline-variant); background: transparent; }
.md-switchrow__ic {
    width: 30px; height: 30px; flex: none;
    border-radius: var(--md-shape-md);
    background: color-mix(in srgb, var(--sec, var(--md-tertiary)) 18%, var(--md-surface-container-lowest));
    color: var(--sec, var(--md-tertiary));
    display: grid; place-items: center;
}
.md-switchrow__ic .material-symbols-rounded { font-size: 17px; font-variation-settings: 'FILL' 1; }
.md-switchrow__copy { flex: 1; min-width: 0; display: flex; flex-direction: row; align-items: baseline; gap: 8px; }
.md-switchrow__copy b { flex: none; font-size: 14px; line-height: 1.35; color: var(--md-on-surface); white-space: nowrap; }
.md-switchrow__copy span {
    font-size: 12.5px; line-height: 1.45; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-switchrow--flat .md-switchrow__copy,
.md-switchrow--stack .md-switchrow__copy { flex-direction: column; align-items: flex-start; gap: 2px; }
.md-switchrow--flat .md-switchrow__copy b,
.md-switchrow--stack .md-switchrow__copy b { font-size: 14.5px; white-space: normal; }
.md-switchrow--flat .md-switchrow__copy span,
.md-switchrow--stack .md-switchrow__copy span { font-weight: var(--md-weight-medium); color: var(--md-on-surface-variant); white-space: normal; overflow: visible; text-overflow: clip; }
/* `--stack`: the boxed row (border and wash kept) whose second line is a whole
   sentence — a Marathi one collapsed to a word per line in the one-line copy. */

/* The M3 switch (markup: .switch-toggle > input + .switch-slider). Was a copy
   in every form's own sheet. */
.switch-toggle { position: relative; display: inline-block; width: 44px; height: 26px; flex-shrink: 0; }
.switch-toggle input { opacity: 0; width: 0; height: 0; position: absolute; }
.switch-slider {
    position: absolute; inset: 0;
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-highest);
    border: 2px solid var(--md-outline);
    transition: background var(--md-dur-short) var(--md-ease), border-color var(--md-dur-short) var(--md-ease);
    cursor: pointer;
}
.switch-slider::before {
    content: "";
    position: absolute; left: 4px; top: 50%;
    width: 14px; height: 14px;
    transform: translateY(-50%);
    border-radius: var(--md-shape-full);
    background: var(--md-outline);
    transition: transform var(--md-dur-short) var(--md-ease), width var(--md-dur-short) var(--md-ease),
                height var(--md-dur-short) var(--md-ease), background var(--md-dur-short) var(--md-ease);
}
.switch-toggle input:checked + .switch-slider { background: var(--md-primary); border-color: var(--md-primary); }
.switch-toggle input:checked + .switch-slider::before { width: 20px; height: 20px; transform: translate(15px, -50%); background: var(--md-on-primary); }
.switch-toggle--lg { width: 52px; height: 32px; }
.switch-toggle--lg .switch-slider::before { width: 16px; height: 16px; left: 5px; }
.switch-toggle--lg input:checked + .switch-slider::before { width: 24px; height: 24px; transform: translate(18px, -50%); }

/* ── The lines ─────────────────────────────────────────────────────────────
   Compact: two short lines a row, ten on a screen. The name and the row's
   amount on top; below, the rate as a dashed pill with a pencil (it LOOKS
   changeable because it is), × qty, a tag (the line's GST), and the −/+
   stepper. Edge to edge inside a section, under a hairline of the hue. */
.md-lines { display: flex; flex-direction: column; margin: 0; }
.md-lines:empty { display: none; }
.md-sec > .md-lines { box-shadow: inset 0 1px 0 color-mix(in srgb, var(--sec) 20%, var(--md-outline-variant)); }
.md-lines__empty {
    margin: 0 14px 12px;
    padding: 12px 14px;
    border-radius: var(--md-shape-lg);
    border: 1.5px dashed var(--md-outline-variant);
    font-size: 13px; line-height: 1.45; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
    text-align: center;
}
.md-lines__empty.is-err { border-color: var(--md-error); }
.md-line { display: flex; flex-direction: column; gap: 4px; padding: 9px 14px; }
.md-line + .md-line { box-shadow: inset 0 1px 0 var(--md-outline-variant); }
.md-line.is-new { animation: md-line-in var(--md-dur-long) var(--md-ease) both; }
@keyframes md-line-in { from { background: var(--md-primary-container); } to { background: transparent; } }
.md-line__head {
    width: 100%;
    display: flex; align-items: baseline; gap: 8px;
    padding: 0;
    border: 0; background: transparent; border-radius: var(--md-shape-sm);
    text-align: left;
    font-family: var(--font-primary);
    cursor: pointer;
}
.md-line__n { flex: none; min-width: 16px; font-size: 12px; font-weight: var(--md-weight-medium); color: var(--md-outline); }
.md-line__name {
    flex: 1; min-width: 0;
    font-size: 14px; line-height: 1.35; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-line__amt { font-size: 14px; font-weight: var(--md-weight-semibold); color: var(--md-on-surface); white-space: nowrap; }
.md-line__foot { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; padding-left: 24px; }
.md-line__rate {
    flex: none;
    display: inline-flex; align-items: center; gap: 2px;
    height: 26px; padding: 0 6px 0 8px;
    border: 1px dashed var(--md-outline);
    border-radius: var(--md-shape-full);
    background: transparent;
    color: var(--md-primary);
    font-family: var(--font-primary);
    font-size: 12.5px; font-weight: var(--md-weight-semibold);
    cursor: pointer;
}
.md-line__rate .material-symbols-rounded { font-size: 13px; }
.md-line__x { flex: none; font-size: 12px; font-weight: var(--md-weight-medium); color: var(--md-on-surface-variant); }
.md-line__tag {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 10.5px; font-weight: var(--md-weight-medium);
    padding: 1px 7px; border-radius: var(--md-shape-full);
    background: var(--md-tertiary-container); color: var(--md-on-tertiary-container);
}
.md-line__over {
    display: inline-flex; align-items: center; gap: 3px; margin-left: 4px;
    font-size: 10.5px; font-weight: var(--md-weight-medium); color: var(--md-warn);
    white-space: nowrap;
}
.md-line__over .material-symbols-rounded { font-size: 13px; }
/* The −/+ stepper: a joined pill, brand ink on the halves. */
.md-stepper {
    flex: none; margin-left: auto;
    display: inline-flex; align-items: center;
    height: 28px;
    border: 1px solid var(--md-outline-variant);
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-low);
    overflow: hidden;
}
.md-stepper__btn {
    width: 30px; height: 26px;
    border: 0; background: transparent;
    color: var(--md-primary);
    font-family: var(--font-primary);
    font-size: 16px; font-weight: var(--md-weight-semibold);
    display: grid; place-items: center;
    cursor: pointer;
}
.md-stepper__btn:active { background: color-mix(in srgb, var(--md-primary) 12%, transparent); }
.md-stepper__val {
    min-width: 24px; text-align: center;
    font-size: 12.5px; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    font-variant-numeric: tabular-nums;
}

/* ── More on this … — chips that open a dialog ─────────────────────────────
   A chip is dashed and carries its own glyph until its dialog holds an
   answer; then it fills, the glyph becomes a tick, and the answer is read
   back after the label ("Discount · 10% · −₹124"). */
.md-more { display: flex; flex-direction: column; gap: 10px; margin-top: 2px; }
.md-more__ttl {
    display: flex; align-items: center; gap: 6px;
    font-size: 13.5px; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
}
.md-opt {
    font-size: 11px; font-weight: var(--md-weight-medium);
    padding: 1px 8px; border-radius: var(--md-shape-full);
    background: var(--md-surface-container); color: var(--md-on-surface-variant);
}
.md-chiprow { display: flex; flex-wrap: wrap; gap: 8px; }
.md-mchip {
    display: inline-flex; align-items: center; gap: 6px;
    min-height: 44px; max-width: 100%;   /* the app-wide touch floor; it was 40 */
    padding: 0 14px 0 10px;
    border: 1px dashed var(--md-outline);
    border-radius: var(--md-shape-full);
    background: transparent;
    color: var(--md-primary);
    font-family: var(--font-primary);
    font-size: 13.5px; font-weight: var(--md-weight-medium);
    cursor: pointer;
    transition: background var(--md-dur-short) var(--md-ease), border-color var(--md-dur-short) var(--md-ease);
}
.md-mchip__ic { font-size: 18px; }
.md-mchip__lbl { white-space: nowrap; }
.md-mchip__val { color: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.md-mchip.is-filled {
    border-style: solid; border-color: transparent;
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
}
.md-mchip.is-filled .md-mchip__ic { font-variation-settings: 'FILL' 1; }

/* ── The chip dialogs ──────────────────────────────────────────────────────
   The app's standard centred sheet (`.auth-modal.md-sheet.md-sheet--brand`,
   §8.7), one per chip: a brand head with an icon and a ×, a body, a Done in
   the foot. Never a bottom sheet. */
.md-dlg { max-width: 400px; max-height: calc(100% - 24px); }
.md-dlg__body { padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.md-dlg__body > * { flex-shrink: 0; }
.md-dlg__body .md-field { margin: 0; }
/* Fields that are the body's own children are spaced by its gap. Fields a
   page wraps in its own container (the bill's record-payment form) are not —
   zeroing their margin too left every label sitting on the field above it. */
.md-dlg__body > .md-field + .md-field { margin-top: 0; }
.md-dlg__body :not(.md-dlg__body) > .md-field + .md-field { margin-top: 18px; }
.md-dlg__body .md-field .md-hint { display: block; margin-top: 6px; }
/* A dialog FORM (D7) carries the column inside the body: the body's padding
   is the measure, so the column drops its own and its auto margin — or the
   cards sit off-centre. */
.md-dlg__body > .md-form__col { margin: 0; max-width: 100%; width: 100%; padding: 0; }
.md-dlg__foot .md-btn { width: 100%; }
/* fk_dialog_close(['secondary' => …]): Delete beside Done, equal halves. */
.md-dlg__foot--two .md-btn { width: auto; flex: 1 1 0; min-width: 0; }
/* Two lines before the ellipsis: a Marathi title is often longer than the head. */
.md-dlg .md-sheet__title { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; }
/* Confirm offers only the chips not yet set (FORM_STANDARD §6). */
#fk-cf-chips .md-mchip.is-filled { display: none; }
/* The sub line sits on the brand head, so it takes the head's ink. */
.md-sheet--brand .md-dlg__sub { color: color-mix(in srgb, var(--md-on-brand) 86%, transparent); white-space: normal; }
/* A figure read back inside a dialog — "Total · ₹1,240". */
.md-dlg__line {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 10px 12px;
    border-radius: var(--md-shape-lg);
    background: var(--md-surface-container-low);
    font-size: 13.5px; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
}
.md-dlg__line b { font-size: 16px; color: var(--md-on-surface); }
.md-dlg__line--total b { font-size: 20px; color: var(--md-primary); }
/* Inside a dialog the chip row is the last thing, under a hairline. */
.md-more.md-more--done { display: none; }
.md-dlg .md-more { padding-top: 12px; margin-top: 0; box-shadow: inset 0 1px 0 var(--md-outline-variant); }

/* ── Confirm — the preview ─────────────────────────────────────────────────
   Who, the rows, the total, and one line that says how it ends (paid how,
   valid until). `--ok` paid green, `--warn` a balance, `--info` a date. */
.md-preview {
    display: flex; flex-direction: column; gap: 10px;
    padding: 12px;
    border-radius: var(--md-shape-lg);
    border: 1px solid var(--md-outline-variant);
    background: var(--md-surface-container-lowest);
}
.md-preview__who { display: flex; align-items: center; gap: 10px; }
.md-preview__av {
    width: 40px; height: 40px; flex: none;
    border-radius: var(--md-shape-full);
    background: var(--md-primary); color: var(--md-on-primary);
    display: grid; place-items: center;
    font-size: 15px; font-weight: var(--md-weight-semibold);
    overflow: hidden;
}
.md-preview__av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.md-preview__av .material-symbols-rounded { font-size: 22px; }
.md-preview__cp { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.md-preview__cp b { font-size: 15px; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-preview__cp span { font-size: 12.5px; color: var(--md-on-surface-variant); font-weight: var(--md-weight-medium); }
.md-preview__rows { display: flex; flex-direction: column; gap: 6px; padding-top: 10px; box-shadow: inset 0 1px 0 var(--md-outline-variant); }
.md-preview__rows:empty { display: none; }
.md-preview__rows > div,
.md-preview__total {
    display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
    font-size: 13.5px; color: var(--md-on-surface-variant); font-weight: var(--md-weight-medium);
}
.md-preview__rows > div b { color: var(--md-on-surface); font-size: 14px; }
.md-preview__rows .is-pos b { color: var(--md-pos); }
.md-preview__total { padding-top: 8px; box-shadow: inset 0 1px 0 var(--md-outline-variant); }
.md-preview__total span { color: var(--md-on-surface); font-weight: var(--md-weight-semibold); }
.md-preview__total b { font-size: 22px; color: var(--md-primary); }
.md-preview__line {
    display: flex; align-items: center; gap: 8px;
    padding: 9px 12px; border-radius: var(--md-shape-md);
    background: var(--md-pos-container); color: var(--md-on-pos-container);
    font-size: 13px; font-weight: var(--md-weight-medium);
}
.md-preview__line .material-symbols-rounded { font-size: 18px; flex: none; }
.md-preview__line--warn { background: var(--md-warn-container); color: var(--md-on-warn-container); }
.md-preview__line--info { background: var(--md-primary-container); color: var(--md-on-primary-container); }

/* ── The Save bar ──────────────────────────────────────────────────────────
   On `.md-actionbar--sticky`: the running total (tap it for the breakdown),
   what has been set as small chips, and the buttons that finish the form.
   Rides above the keyboard on `--kb-fixed` (header.php publishes it), so the
   buttons are never under the keys at the moment the owner is ready. */
.md-savebar {
    flex-direction: column; align-items: stretch; gap: 8px;
    padding: 8px 14px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom));
    bottom: var(--kb-fixed, 0px);
    background: var(--md-surface-container-lowest);
}
.md-savebar__total {
    width: 100%;
    min-height: 44px;
    display: flex; align-items: center; gap: 10px;
    padding: 4px 2px;
    border: 0; background: transparent; border-radius: var(--md-shape-sm);
    text-align: left;
    font-family: var(--font-primary);
    cursor: pointer;
}
.md-savebar__lbl {
    flex: 1; min-width: 0;
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    font-size: 13.5px; font-weight: var(--md-weight-medium);
    color: var(--md-on-surface-variant);
}
.md-savebar__lbl .material-symbols-rounded { font-size: 18px; color: var(--md-primary); }
.md-savebar__total--static { cursor: default; }
.md-savebar__lbl small { font-size: 11.5px; color: var(--md-on-surface-variant); }
.md-savebar__lbl small::before { content: "· "; }
.md-savebar__val { font-size: 24px; line-height: 1; font-weight: var(--md-weight-semibold); color: var(--md-on-surface); white-space: nowrap; }
.md-savebar__go { color: var(--md-on-surface-variant); font-size: 22px; flex: none; }
.md-savebar__extras { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 2px 2px; }
.md-extra {
    display: inline-flex; align-items: center; gap: 4px;
    height: 26px; padding: 0 9px 0 6px;
    border-radius: var(--md-shape-full);
    background: var(--md-primary-container); color: var(--md-on-primary-container);
    font-size: 11.5px; font-weight: var(--md-weight-medium);
    white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.md-extra .material-symbols-rounded { font-size: 14px; flex: none; }
.md-savebar__btns { display: flex; gap: 10px; }
.md-savebar__btns .md-btn { flex: 1; height: 54px; min-width: 0; padding: 0 14px; font-size: 16px; }
.md-savebar__btns .md-btn--brand { flex: 1.5; }
.md-savebar__btns .md-btn:only-child { flex: 1; }
.md-savebar__btns .md-btn .material-symbols-rounded { flex: none; }
/* Marathi labels run long: the icon holds its box, the words ellipsize. */
.md-savebar__btns .md-btn > span:not(.material-symbols-rounded) { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.md-savebar__hint { font-size: 12px; line-height: 1.5; color: var(--md-on-surface-variant); text-align: center; }

/* ── Step by step ──────────────────────────────────────────────────────────
   A section is `hidden` until the answer before it is given; FormKit.reveal
   un-hides it and it rises in. */
@keyframes md-reveal-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.md-reveal.is-in { animation: md-reveal-in var(--md-dur-medium) var(--md-ease) both; }
@media (prefers-reduced-motion: reduce) { .md-reveal.is-in { animation: none; } }

/* ── The row just written ──────────────────────────────────────────────────
   A dialog form (D7) has no success page: it closes back to its list and
   FormKit.markRow() lights the row it wrote — a brand wash that holds, then
   fades. Reduced motion gets the still tint for the same span. */
@keyframes md-newrow { 0%, 55% { background-color: color-mix(in srgb, var(--md-primary) 14%, transparent); } 100% { background-color: transparent; } }
.md-newrow { animation: md-newrow 2.4s ease-out both; }
@media (prefers-reduced-motion: reduce) { .md-newrow { animation: none; background-color: color-mix(in srgb, var(--md-primary) 10%, transparent); } }

/* ── 11b · The success page ────────────────────────────────────────────────
   render_success_page(), redrawn 2026-10-09 (the owner's pick:
   design_concepts/celebration_concepts.html, B · Colour hero and sheet).

   The top of the screen is a HERO in the colour of what happened — green
   money in, blue people and the account, teal something for the shop or the
   website, amber money out, gold a milestone — under a gloss, with the
   moment's glyph large and faint behind the tick and one band of light across
   it. The tick is drawn on a white badge and the thing just saved is said
   back in white (the page's own script fills title / sub / number).

   Everything the owner does next is on a white SHEET under the hero, where a
   label can be read: one wide main door first (WhatsApp green when the
   customer is still standing there), then quiet tiles, each with its icon on
   a tile of the moment's colour. Confetti falls once over both
   (assets/js/platform/celebrate.js).

   The five gradients are scheme-invariant (theme.css), so a celebration looks
   the same at night; the sheet is a surface role and inverts with the app.
   `--suc-ink` is the hero's own mid colour, for the tick on its white badge:
   the badge is white in both schemes, so the ink is the same in both. */
.screen-content.md-success,
#app-viewport .screen-content.md-success {
    --suc-g: var(--grad-hero); --suc-ink: var(--brand-ink, #0B57D0);
    --suc-c: var(--md-primary-container); --suc-on-c: var(--md-on-primary-container);
    padding: 0 !important;
    background: var(--md-surface-container-lowest);
    color: var(--md-on-surface);
    position: relative;
    overflow: hidden;
    display: flex; flex-direction: column;
    flex: 1;
}
/* Written at the base rule's own weight (it carries #app-viewport), or the
   base's blue would win over every colour here. */
#app-viewport .screen-content.md-success--green, .screen-content.md-success.md-success--green { --suc-g: var(--grad-green); --suc-ink: #006D44; --suc-c: var(--md-pos-container);      --suc-on-c: var(--md-on-pos-container); }
#app-viewport .screen-content.md-success--teal,  .screen-content.md-success.md-success--teal  { --suc-g: var(--grad-teal);  --suc-ink: #006874; --suc-c: var(--md-tertiary-container); --suc-on-c: var(--md-on-tertiary-container); }
#app-viewport .screen-content.md-success--amber, .screen-content.md-success.md-success--amber { --suc-g: var(--grad-amber); --suc-ink: #895100; --suc-c: var(--md-warn-container);     --suc-on-c: var(--md-on-warn-container); }
/* A milestone (success.js adds the class): the hero turns gold, whatever the
   moment was, and the count stands on the badge in place of the tick. */
#app-viewport .screen-content.md-success.is-milestone,
.screen-content.md-success.md-success.is-milestone {
    --suc-g: linear-gradient(135deg, #7A4B00 0%, #C98A00 50%, #F2C14E 120%); --suc-ink: #7A4B00;
    --suc-c: var(--md-warn-container); --suc-on-c: var(--md-on-warn-container);
}

/* ── The hero ── */
.md-success__hero {
    position: relative; z-index: 1; flex: none;
    overflow: hidden;
    padding-bottom: 46px;                 /* the sheet rides up over this */
    background: var(--suc-g);
    color: var(--md-on-brand);
}
.md-success__hero::before {
    content: '';
    position: absolute; left: 0; right: 0; top: 0; height: 52%;
    background: linear-gradient(180deg, rgba(255, 255, 255, .26), rgba(255, 255, 255, .03));
    border-radius: 0 0 50% 50% / 0 0 18% 18%;
    pointer-events: none;
}
.md-success__ghost {
    position: absolute; right: -18px; top: 40px;
    font-size: 190px; line-height: 1;
    font-variation-settings: 'FILL' 1;
    opacity: .13;
    transform: rotate(12deg);
    pointer-events: none;
}
/* One band of light across the hero as the page lands, then still. */
.md-success__sweep {
    position: absolute; top: 0; bottom: 0; left: -45%; width: 36%;
    background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .4), rgba(255, 255, 255, 0));
    transform: skewX(-20deg);
    pointer-events: none;
    animation: md-success-sweep 1000ms ease-in-out 500ms 1 both;
}
@keyframes md-success-sweep { from { left: -45%; } to { left: 125%; } }
/* The bar sits ON the hero rather than painting its own copy of it. */
.md-success__bar { background: transparent; box-shadow: none; position: relative; z-index: 2; }
.md-success__top {
    position: relative; z-index: 2;
    display: flex; flex-direction: column; align-items: center;
    text-align: center;
    padding: 4px 20px 0;
    gap: 8px;
    max-width: 560px; margin: 0 auto;
}
.md-success__tick {
    width: 88px; height: 88px;
    border-radius: var(--md-shape-full);
    background: #FFFFFF;
    color: var(--suc-ink);
    display: grid; place-items: center;
    box-shadow: 0 16px 30px -16px rgba(0, 0, 0, .6);
    animation: md-success-pop var(--md-dur-long) var(--md-ease-emphasized) both;
}
.md-success__tick .material-symbols-rounded { font-size: 52px; font-variation-settings: 'FILL' 1, 'wght' 600; }
/* A milestone's count, in place of the tick. */
.md-success__tick[data-n] svg, .md-success__tick[data-n] .material-symbols-rounded { display: none; }
.md-success__tick[data-n]::before {
    content: attr(data-n);
    font-size: 28px; line-height: 1; font-weight: var(--md-weight-semibold);
    font-variant-numeric: tabular-nums;
}
@keyframes md-success-pop { from { transform: scale(.4); opacity: 0; } to { transform: none; opacity: 1; } }
.md-success__title {
    font-size: 30px; line-height: 1.25;
    font-weight: var(--md-weight-semibold);
    color: var(--md-on-brand);
    margin: 4px 0 0;
    text-wrap: balance;
    overflow-wrap: anywhere;
    text-shadow: 0 1px 1px rgba(0, 0, 0, .25);
}
.md-success__sub {
    font-size: 14.5px; line-height: 1.5;
    color: color-mix(in srgb, var(--md-on-brand) 92%, transparent);
    margin: 0;
    max-width: 320px;
    text-wrap: pretty;
}
.md-success__number {
    font-size: 12px; line-height: 1.6; font-weight: var(--md-weight-semibold);
    padding: 2px 12px;
    border-radius: var(--md-shape-full);
    background: color-mix(in srgb, var(--md-on-brand) 20%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--md-on-brand) 30%, transparent);
    color: var(--md-on-brand);
}
.md-success__confetti {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    pointer-events: none;
    z-index: 3;
}

/* ── The sheet ── */
.md-success__body {
    position: relative; z-index: 2;
    flex: 1;
    /* The screen itself is `overflow: hidden` so the hero and the confetti
       stay put; the sheet is what scrolls. Without this a long set of doors
       (a note card, Marathi labels, a short phone) was cut off at the bottom
       with no way to reach it. */
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    display: flex; flex-direction: column; align-items: stretch;
    margin-top: -26px;
    padding: 18px 16px calc(20px + env(safe-area-inset-bottom));
    gap: 12px;
    max-width: 560px; width: 100%; margin-inline: auto;
    border-radius: 26px 26px 0 0;
    background: var(--md-surface-container-lowest);
    box-shadow: 0 -10px 24px -18px rgba(0, 0, 0, .5);
}
.md-success__body::-webkit-scrollbar { display: none; }
/* Scroll, never squeeze: a column flex box would shrink the doors first. */
.md-success__body > * { flex-shrink: 0; }

/* A note on the sheet — something the owner should tell the person in front
   of them ("they can see this bill in the app"). News, not a door: a tinted
   card with no chevron. */
.md-success__note {
    width: 100%;
    display: flex; align-items: center; gap: 14px;
    padding: 14px 16px;
    border-radius: var(--md-shape-xl);
    background: color-mix(in srgb, var(--suc-c) 45%, var(--md-surface-container-lowest));
    color: var(--md-on-surface);
    text-align: left;
    animation: md-success-rise var(--md-dur-long) var(--md-ease-emphasized) both;
    animation-delay: 220ms;
}
.md-success__note[hidden] { display: none; }
.md-success__note-mark {
    position: relative;
    width: 52px; height: 52px; flex: none;
    border-radius: var(--md-shape-lg);
    background: var(--suc-g);
    display: grid; place-items: center;
}
.md-success__note-mark img { width: 34px; height: 34px; object-fit: contain; display: block; }
.md-success__note-mark .material-symbols-rounded {
    position: absolute; right: -6px; bottom: -6px;
    width: 24px; height: 24px; border-radius: var(--md-shape-full);
    display: grid; place-items: center;
    font-size: 16px; font-variation-settings: 'FILL' 1;
    background: var(--md-pos); color: var(--md-on-brand);
    box-shadow: 0 0 0 2px var(--md-surface-container-lowest);
}
.md-success__note-copy { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.md-success__note-copy b {
    font-size: 15.5px; line-height: 1.5; font-weight: var(--md-weight-semibold);
    color: var(--md-on-surface);
    text-wrap: pretty;
}
.md-success__note-copy span {
    font-size: 13px; line-height: 1.5;
    color: var(--md-on-surface-variant);
    overflow-wrap: anywhere;
}
@keyframes md-success-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.md-success__doors {
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
    width: 100%;
}
/* A door: a quiet tile, its icon on a tile of the moment's colour. */
.md-success__door {
    position: relative;
    display: flex; align-items: center; gap: 10px;
    min-height: 72px;
    padding: 10px 12px;
    border: 0;
    border-radius: var(--md-shape-xl);
    background: var(--md-surface-container);
    color: var(--md-on-surface);
    text-decoration: none;
    font-family: var(--font-primary);
    font-size: 14px; line-height: 1.5; font-weight: var(--md-weight-semibold);
    text-align: start;
    cursor: pointer;
}
.md-success__door[hidden] { display: none; }
.md-success__door > .material-symbols-rounded:not(.md-success__hero-go) {
    width: 40px; height: 40px; flex: none;
    border-radius: var(--md-shape-lg);
    display: grid; place-items: center;
    font-size: 22px;
    background: var(--suc-c); color: var(--suc-on-c);
}
.md-success__door > span:not(.material-symbols-rounded):not(.md-success__pill) { min-width: 0; overflow-wrap: anywhere; }
/* A lone last tile takes the row, so the grid never ends on a hole. */
.md-success__door:not(.md-success__door--wide):not(.md-success__door--hero):last-child:nth-child(odd) { grid-column: 1 / -1; }
/* The whole width, the brand's own green, the label beside the glyph. */
.md-success__door--wide {
    grid-column: 1 / -1;
    justify-content: center;
    min-height: 54px;
    border-radius: var(--md-shape-full);
    background: var(--brand-whatsapp);
    color: #04311A;   /* dark ink on the brand green: 8:1 (white was 1.98:1) */
    font-size: 16px;
}
.md-success__door.md-success__door--wide > .material-symbols-rounded:not(.md-success__hero-go) {
    width: auto; height: auto; background: none; color: inherit; font-size: 24px;
}
/* The page's ONE next step (2026-09-23): the moment's own colour, full width,
   icon · label over its line · arrow, and a countdown bar along its foot that
   the page's script fills before opening it by itself. */
.md-success__door--hero {
    grid-column: 1 / -1;
    gap: 14px;
    min-height: 76px; padding: 14px 16px 16px;
    overflow: hidden;
    background: var(--suc-g);
    color: var(--md-on-brand);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35);
}
.md-success__hero-ic {
    flex: none; width: 46px; height: 46px;
    display: grid; place-items: center;
    border-radius: var(--md-shape-lg);
    background: rgba(255, 255, 255, .22);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .36);
    color: var(--md-on-brand);
}
.md-success__hero-ic .material-symbols-rounded { font-size: 26px; }
.md-success__hero-copy { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.md-success__hero-copy b { font-size: 16.5px; line-height: 1.5; font-weight: var(--md-weight-semibold); color: var(--md-on-brand); }
.md-success__hero-copy span { font-size: 13px; line-height: 1.5; color: color-mix(in srgb, var(--md-on-brand) 90%, transparent); }
.md-success__hero-go { flex: none; font-size: 24px; color: var(--md-on-brand); }
.md-success__timer {
    position: absolute; left: 0; right: 0; bottom: 0; height: 4px;
    background: rgba(255, 255, 255, .25);
}
.md-success__timer > span { display: block; height: 100%; width: 0; background: var(--md-on-brand); }
/* A quiet line under the doors: "Opening in 5s · Stay here". */
.md-success__after {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    font-size: 13.5px; line-height: 1.5; color: var(--md-on-surface-variant);
}
.md-success__after[hidden] { display: none; }
.md-success__after button {
    /* 48px tall: on some pages this is one of the screen's two ways on. */
    border: 0; background: transparent; min-height: 48px; padding: 6px 12px; cursor: pointer;
    font: inherit; font-weight: var(--md-weight-semibold); color: var(--md-primary);
    text-decoration: underline; text-underline-offset: 3px;
}
/* Behind its flag: visible, dimmed, and it still answers (the maintenance
   modal) — the same .is-off every other flagged entry point wears. */
.md-success__door.is-off { opacity: .55; }
/* Not ready YET, which is a different thing from switched off: the door is
   waiting on something it needs before it can be honest (billing B4 waits for
   the shop's own name before it will compose a message in it). Dimmed, not
   tappable, and it arms itself a moment later. */
.md-success__door.is-waiting { opacity: .6; cursor: progress; }
.md-success__pill {
    position: absolute; top: 6px; right: 8px;
    font-size: 10.5px; line-height: 1.5; font-weight: var(--md-weight-semibold);
    letter-spacing: .3px; text-transform: uppercase;
    padding: 1px 8px;
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-lowest);
    color: var(--md-primary);
}
.md-success__door:focus-visible { outline: 2px solid var(--md-primary); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
    .md-success__tick, .md-success__note { animation: none; }
    .md-success__sweep { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   MOTION — the entrance system from the palette studio
   ══════════════════════════════════════════════════════════════════════════
   One keyframe per shape, staggered by a --d custom property set inline. Rise
   for blocks, pop for anything circular or badge-like, slide for a header row.

   Transform and opacity only: both composite on the GPU, so a staggered screen
   costs nothing on a low-end phone. Anything animating width, height, top or
   left would not.

   All of it is off under prefers-reduced-motion, including the ambient loops.
   ══════════════════════════════════════════════════════════════════════════ */
@keyframes md-rise  { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes md-pop   { from { opacity: 0; transform: scale(.84); }       to { opacity: 1; transform: scale(1); } }
@keyframes md-slide { from { opacity: 0; transform: translateX(-14px); } to { opacity: 1; transform: none; } }

.md-rise  { animation: md-rise  .55s var(--md-ease) backwards;            animation-delay: var(--d, 0s); }
.md-pop   { animation: md-pop   .50s var(--md-ease-emphasized) backwards; animation-delay: var(--d, 0s); }
.md-slide { animation: md-slide .50s var(--md-ease) backwards;            animation-delay: var(--d, 0s); }

/* Ambient loops — a slow drifting light behind a gradient, and the sweep across
   a brand mark. Both are opt-in per surface, never global. */
@keyframes md-drift { 0%,100% { transform: translate(0,0) scale(1); }
                      50%     { transform: translate(22px,-14px) scale(1.18); } }
@keyframes md-sheen { 0%      { transform: translateX(-120%) skewX(-18deg); }
                      60%,100%{ transform: translateX(240%)  skewX(-18deg); } }
.md-drift { animation: md-drift 16s ease-in-out infinite; }
.md-sheen { animation: md-sheen 5.5s var(--md-ease) infinite; }

@media (prefers-reduced-motion: reduce) {
    .md-rise, .md-pop, .md-slide, .md-drift, .md-sheen { animation: none; }
}

/* ── Identity tints ─────────────────────────────────────────────────────────
   Six washes for the icon of a destination — a list row's lead square, a stat
   tile's chip, a shortcut's circle. Colour is the fastest thing a person reads,
   and for a user who cannot read the label it is close to the only thing: a
   shopkeeper learns "the amber wallet is my expenses" in one afternoon and
   never reads that row again.

   Every one is a REAL `container` / `on-container` pair from theme.css, which
   is what makes them safe. A hand-mixed wash needs its own dark value invented
   and usually does not get one; these already carry a guaranteed-legible
   foreground in both schemes, and the pair moves together when the palette
   does.

   Assign a tint per SECTION so no two neighbouring rows share one, and keep a
   module's tint fixed forever — the colour is the recognition, so a module that
   changes colour has changed identity. */
.md-tint-blue  { background: var(--md-primary-container);   color: var(--md-on-primary-container); }
.md-tint-teal  { background: var(--md-tertiary-container);  color: var(--md-on-tertiary-container); }
.md-tint-green { background: var(--md-pos-container);       color: var(--md-on-pos-container); }
.md-tint-amber { background: var(--md-warn-container);      color: var(--md-on-warn-container); }
.md-tint-slate { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }
.md-tint-coral { background: var(--md-neg-container);       color: var(--md-on-neg-container); }

/* ══════════════════════════════════════════════════════════════════════════
   WORKFLOW STEPPER (.ows) — "where does this record stand"
   ══════════════════════════════════════════════════════════════════════════
   A row of dots joined by bars: what is behind you is positive, what you are
   on is solid primary, and a record that died carries its pipeline struck
   through with the state that ended it in red at the end.

   Written for miniwebsite/order-view.php; moved here 2026-09-01 when
   bookings/booking_details.php wanted the same shape. Two consumers is where
   a page detail stops being one — the same reason `.md-topbar` moved.

   Markup, per node:
     <div class="ows[ ows--dead]">
       <div class="ows-step[ done|current|bad|struck]">
         <div class="ows-dot"><span class="material-symbols-rounded">…</span></div>
         <div class="ows-lbl">…</div>
       </div>
       <div class="ows-bar[ done|struck]"></div>   ← between nodes only
     </div>
   ══════════════════════════════════════════════════════════════════════════ */
.ows { display: flex; align-items: flex-start; justify-content: center; }
.ows-step { display: flex; flex-direction: column; align-items: center; gap: 5px; flex: 0 0 auto; width: 64px; }
.ows-step--solo { width: auto; margin: 0 auto; }
.ows-dot {
    width: 32px; height: 32px; border-radius: var(--md-shape-full);
    display: flex; align-items: center; justify-content: center;
    background: var(--md-surface-container-high);
    color: var(--md-on-surface-variant);
    border: 2px solid var(--md-surface-container-high);
    transition: all var(--md-dur-short) var(--md-ease);
}
.ows-dot .material-symbols-rounded { font-size: 17px; }
.ows-lbl { font-size: 10px; font-weight: var(--md-weight-semibold); color: var(--md-on-surface-variant); text-align: center; line-height: 1.2; }
.ows-bar { flex: 1; height: 2px; background: var(--md-surface-container-high); margin-top: 15px; border-radius: 2px; min-width: 8px; }
.ows-step.done .ows-dot { background: color-mix(in srgb, var(--md-pos) 12%, transparent); border-color: color-mix(in srgb, var(--md-pos) 40%, transparent); color: var(--md-pos); }
.ows-step.done .ows-lbl { color: var(--md-pos); }
.ows-bar.done { background: color-mix(in srgb, var(--md-pos) 40%, transparent); }
.ows-step.current .ows-dot { background: var(--md-primary); border-color: var(--md-primary); color: var(--md-on-primary); }
.ows-step.current .ows-lbl { color: var(--md-primary); }
.ows-step.bad .ows-dot { background: color-mix(in srgb, var(--md-error) 12%, transparent); border-color: color-mix(in srgb, var(--md-error) 40%, transparent); color: var(--md-error); }
.ows-step.bad .ows-lbl { color: var(--md-error); font-weight: var(--md-weight-semibold); }

/* ── A void order ──────────────────────────────────────────────────────────
   Rejected or cancelled: the pipeline it was travelling stays on screen and
   is struck through, then the state that ended it stands in red at the end.
   Five nodes where there were four, so each is narrower — at 64px they run
   past the card on a 360px screen.

   The strike is on the LABEL and the dimming on the dot: a line through a
   round icon reads as damage rather than as "this did not happen". */
.ows--dead .ows-step { width: 54px; }
.ows-step.struck .ows-dot { opacity: .4; }
.ows-step.struck .ows-lbl {
    text-decoration: line-through;
    color: var(--md-on-surface-variant);
    opacity: .7;
}
.ows-bar.struck { opacity: .35; }
.ows-bar--dead { background: color-mix(in srgb, var(--md-error) 45%, transparent); }


/* ═══════════════════════════════════════════════════════════════════════════
   §16 THE CANVAS — a module home wearing the owner Home's shape (2026-09-13)

   Settled on crm/index.php and rolled to the other homes the same day. The
   shape: the app bar INSIDE the scroller and sticky, transparent over a
   gradient hero that holds only the search pill and a story-style row of
   recent things; a rounded sheet riding over the hero's bottom edge, opening
   with the page's one action in the brand blue, then the menu rows the home
   always had, then the assistant's offer. The figures a home used to carry
   on a lit panel become a count pill on the row they count.

   These are the dashboard's own rules (dashboard.css §hero, .dh-find,
   .dash-sheet) restated once with the library's names, so nine pages do not
   carry nine copies. The stencil is the app's signature line-art, the same
   asset auth.css and dashboard.css carry.

   Contract:
     <main class="screen-content md-canvas-page" id="<m>-scroll">
       <header class="md-appbar md-appbar--hero md-canvas__bar">   (render_app_bar)
       <div class="md-canvas__hero">
         <button class="md-canvas__find md-state" …>            search pill
         <div class="md-canvas__stories" …>                     Platform.stories()
       </div>
       <div class="md-canvas__sheet">
         <a class="md-canvas__add md-state" …>                  the one action
         <div class="md-list"> … <span class="md-pill md-canvas__cnt"> …
         <a class="md-ask …">
       </div>
     </main>
   platform-core.js §canvas measures the bar into `--md-canvas-bar-h` and
   toggles `is-past-hero` on the bar as the hero scrolls away.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The scroller: no top padding, the bar sits flush; the bottom clearance is
   the sheet's own, which runs to the foot of the screen. `!important` because
   forty pages restyle this element's padding with one of their own. */
.screen-content.md-canvas-page {
    padding: 0 14px 0 !important;
    background: var(--ambient-background);
}

/* The bar, made transparent. `--hero` still paints the white title and the
   flat back arrow; this takes the gradient away while the hero is under it,
   and fades the bar's OWN gradient back in on a ::before layer once the hero
   has scrolled past. Opacity animates where a background-image cannot. */
.md-canvas-page > .md-appbar.md-canvas__bar {
    position: sticky; top: 0;
    isolation: isolate;
    margin: 0 -14px 0;
    background: transparent;
    box-shadow: none;
}
.md-canvas-page > .md-canvas__bar::before {
    content: '';
    position: absolute; inset: 0; z-index: -1;
    pointer-events: none;
    background: var(--grad-hero);
    opacity: 0;
    transition: opacity var(--md-dur-medium, 250ms) var(--md-ease, ease);
}
.md-canvas-page > .md-canvas__bar.is-past-hero::before { opacity: 1; }
/* `md-appbar--scrolled` (platform-core.js) is a grey fill and a hairline for
   a white bar over a white page. Not here — a grey slab over the hero, or a
   shadow under a bar that is not there. The lift returns once the bar is
   painting itself again. */
.md-canvas-page > .md-canvas__bar.md-appbar--scrolled {
    background: transparent;
    box-shadow: none;
}
.md-canvas-page > .md-canvas__bar.is-past-hero.md-appbar--scrolled {
    box-shadow: 0 6px 16px -10px color-mix(in srgb, var(--md-on-surface) 70%, transparent);
}

/* ── The canvas weave, as a token ──────────────────────────────────────
   The faint white line-art the hero wears — a shop, a bill, two people, a
   chart — at 16% stroke over whatever gradient is under it. It is what stops
   the hero reading as a printed slab, and it is the thing another surface
   means when it says it wants to look like the canvas.

   Named here because it had been pasted inline; billing's three doors are the
   first surface outside this file to wear it. The other copies in this
   stylesheet are NOT all this one — two draw a grey 5.5% variant and two add
   three dots — so they stay where they are rather than being folded in under
   a name that would quietly change them. */
:root {
    --canvas-weave: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='160' viewBox='0 0 300 160' fill='none' stroke='%23FFFFFF' stroke-opacity='0.16' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M24 16h48v6H24z'/%3E%3Cpath d='M28 22h40l6 8H22z'/%3E%3Cpath d='M22 30q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0'/%3E%3Cpath d='M28 22v34M68 22v34'/%3E%3Cpath d='M34 36h28v20h-28z'/%3E%3Cpath d='M34 42h28M34 49h28'/%3E%3Cpath d='M20 56h56'/%3E%3Cpath d='M108 10h34v37l-5.67 3-5.67-3-5.66 3-5.67-3-5.67 3-5.66-3z'/%3E%3Cpath d='M114 18h22M114 25h22M114 32h14M114 39h22'/%3E%3Cpath d='M228 31a5 5 0 1 1 0 .01'/%3E%3Cpath d='M219 50a9 9 0 0 1 18 0'/%3E%3Cpath d='M208 34a6 6 0 1 1 0 .01'/%3E%3Cpath d='M197 54a11 11 0 0 1 22 0'/%3E%3Cpath d='M40 88h56v38H40z'/%3E%3Cpath d='M40 98h56'/%3E%3Cpath d='M47 106h18M47 113h30M47 120h22'/%3E%3Cpath d='M128 100h36l-3 26h-30z'/%3E%3Cpath d='M137 100v-5a9 9 0 0 1 18 0v5'/%3E%3Cpath d='M198 134h50'/%3E%3Cpath d='M204 134v-10h9v10M218 134v-18h9v18M232 134v-26h9v26'/%3E%3Cpath d='M204 106l14-8l10 4l14-10'/%3E%3Cpath d='M234 92h8v8'/%3E%3C/svg%3E");
}

/* The hero: one gradient block, bled to the edges and UP behind the bar by
   the bar's measured height, with the same amount returned as top padding.
   The 54px at the foot is 38 under the sheet and 16 of canvas showing. */
.md-canvas-page > .md-canvas__hero {
    position: relative;
    isolation: isolate;
    margin: calc(-1 * var(--md-canvas-bar-h, 64px)) -14px 0;
    padding: calc(var(--md-canvas-bar-h, 64px) + 8px) 14px 54px;
    background: var(--grad-hero);
}
.md-canvas-page > .md-canvas__hero::after {
    content: '';
    position: absolute; inset: 0; z-index: -1;
    pointer-events: none;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='160' viewBox='0 0 300 160' fill='none' stroke='%23FFFFFF' stroke-opacity='0.16' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M24 16h48v6H24z'/%3E%3Cpath d='M28 22h40l6 8H22z'/%3E%3Cpath d='M22 30q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0'/%3E%3Cpath d='M28 22v34M68 22v34'/%3E%3Cpath d='M34 36h28v20h-28z'/%3E%3Cpath d='M34 42h28M34 49h28'/%3E%3Cpath d='M20 56h56'/%3E%3Cpath d='M108 10h34v37l-5.67 3-5.67-3-5.66 3-5.67-3-5.67 3-5.66-3z'/%3E%3Cpath d='M114 18h22M114 25h22M114 32h14M114 39h22'/%3E%3Cpath d='M228 31a5 5 0 1 1 0 .01'/%3E%3Cpath d='M219 50a9 9 0 0 1 18 0'/%3E%3Cpath d='M208 34a6 6 0 1 1 0 .01'/%3E%3Cpath d='M197 54a11 11 0 0 1 22 0'/%3E%3Cpath d='M40 88h56v38H40z'/%3E%3Cpath d='M40 98h56'/%3E%3Cpath d='M47 106h18M47 113h30M47 120h22'/%3E%3Cpath d='M128 100h36l-3 26h-30z'/%3E%3Cpath d='M137 100v-5a9 9 0 0 1 18 0v5'/%3E%3Cpath d='M198 134h50'/%3E%3Cpath d='M204 134v-10h9v10M218 134v-18h9v18M232 134v-26h9v26'/%3E%3Cpath d='M204 106l14-8l10 4l14-10'/%3E%3Cpath d='M234 92h8v8'/%3E%3C/svg%3E") repeat center top;
    background-size: 240px 128px;
}

/* The search pill (Home's .dh-find): a white pill on the gradient, one soft
   brand-tinted glow so it lifts off the canvas, the glyph in the brand colour
   on the lead and a filled brand circle on the tail. A <button>: the field
   lives in the layer it opens. */
.md-canvas__find {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    margin: 2px 0 0;
    padding: 7px 7px 7px 16px;
    border: 0;
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-lowest);
    box-shadow: var(--md-elev-1),
                0 8px 18px -14px color-mix(in srgb, var(--md-primary) 70%, transparent);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    /* A <button> on most homes, an <a> on Leads (it opens the list page's
       own search): the anchor must not arrive underlined and blue. */
    color: inherit;
    text-decoration: none;
    transition: box-shadow var(--md-dur-short) var(--md-ease);
}
.md-canvas__find:active {
    box-shadow: inset 0 0 0 1.5px var(--md-primary),
                0 8px 18px -14px color-mix(in srgb, var(--md-primary) 70%, transparent);
}
.md-canvas__find__glyph { font-size: 22px; color: var(--md-primary); flex: none; }
.md-canvas__find__lbl {
    flex: 1; min-width: 0;
    font-size: 14.5px; font-weight: var(--md-weight-medium); line-height: 21px;
    color: var(--md-on-surface-variant);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.md-canvas__find__go {
    width: 38px; height: 38px;
    flex: none;
    border-radius: var(--md-shape-full);
    display: grid; place-items: center;
    background: var(--grad-hero);
    color: var(--md-on-brand);
}
.md-canvas__find__go .material-symbols-rounded { font-size: 20px; }

/* The stories row: six ringed faces under the pill that open a record in one
   tap. Bled to the edges so the strip scrolls edge to edge, with the
   scroller's 14px returned as padding so the first face lines up with the
   pill. No scrollbar — it is a strip, not a list. */
.md-canvas__stories {
    display: flex;
    gap: 4px;
    margin: 14px -14px 0;
    padding: 0 12px;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
}
.md-canvas__stories::-webkit-scrollbar { display: none; }
.md-canvas__stories[hidden] { display: none; }
.md-story {
    flex: 0 0 66px; width: 66px;
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    padding: 2px 0 4px;
    border-radius: var(--md-shape-lg);
    text-decoration: none;
    color: var(--md-on-brand);
    scroll-snap-align: start;
}
/* The ring: a hairline of white with a gap of canvas showing through before
   the picture — the stories grammar every phone already knows. 50px of
   picture inside a 58px ring. */
.md-story__ring {
    width: 58px; height: 58px; flex: none;
    box-sizing: border-box;
    border: 2px solid var(--md-on-brand);
    padding: 2px;
    border-radius: var(--md-shape-full);
    display: grid; place-items: center;
    box-shadow: 0 6px 14px -8px color-mix(in srgb, var(--md-on-primary-container) 80%, transparent);
}
.md-story__ring img,
.md-story__ini {
    width: 100%; height: 100%;
    border-radius: var(--md-shape-full);
    display: block;
}
.md-story__ring img { object-fit: cover; background: var(--md-surface-container-lowest); }
/* No picture: the initial (or a glyph) on the brand pair. */
.md-story__ini {
    display: grid; place-items: center;
    font-size: 19px; font-weight: var(--md-weight-semibold); line-height: 1;
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
}
.md-story__ini .material-symbols-rounded { font-size: 24px; }
.md-story__name {
    max-width: 66px;
    font-size: 11.5px; font-weight: var(--md-weight-medium); line-height: 1.45;
    text-align: center;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Until the call answers: rings and name bars in the canvas's own white,
   dimmed, so the strip holds its height without pretending to be data. */
.md-story.is-skel .md-story__ring {
    border-color: color-mix(in srgb, var(--md-on-brand) 35%, transparent);
    background: color-mix(in srgb, var(--md-on-brand) 14%, transparent);
    box-shadow: none;
}
.md-story.is-skel .md-story__name {
    width: 40px; height: 10px; margin-top: 3px;
    border-radius: 5px;
    background: color-mix(in srgb, var(--md-on-brand) 25%, transparent);
}

/* The sheet: everything that is not the hero rides over its bottom edge on a
   rounded surface wearing the page's whispered stencil, grown to the foot of
   the screen so the ground never shows under it. --page-room at the bottom — the
   room every page ends with (style.css, 2026-09-25; 75px from 2026-11-03) — as PADDING, so the
   white sheet runs on down instead of stopping and showing the canvas. */
.md-canvas-page > .md-canvas__sheet {
    position: relative;
    z-index: 2;
    flex: 1 0 auto;
    margin: -38px -14px 0;
    border-radius: 24px 24px 0 0;
    padding: 18px 14px var(--page-room, 75px);
    display: flex;
    flex-direction: column;
    gap: 12px;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='160' viewBox='0 0 300 160' fill='none' stroke='%23808080' stroke-opacity='0.055' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M24 16h48v6H24z'/%3E%3Cpath d='M28 22h40l6 8H22z'/%3E%3Cpath d='M22 30q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0q4.33 4 8.67 0'/%3E%3Cpath d='M28 22v34M68 22v34'/%3E%3Cpath d='M34 36h28v20h-28z'/%3E%3Cpath d='M34 42h28M34 49h28'/%3E%3Cpath d='M20 56h56'/%3E%3Cpath d='M108 10h34v37l-5.67 3-5.67-3-5.66 3-5.67-3-5.67 3-5.66-3z'/%3E%3Cpath d='M114 18h22M114 25h22M114 32h14M114 39h22'/%3E%3Cpath d='M228 31a5 5 0 1 1 0 .01'/%3E%3Cpath d='M219 50a9 9 0 0 1 18 0'/%3E%3Cpath d='M208 34a6 6 0 1 1 0 .01'/%3E%3Cpath d='M197 54a11 11 0 0 1 22 0'/%3E%3Cpath d='M40 88h56v38H40z'/%3E%3Cpath d='M40 98h56'/%3E%3Cpath d='M47 106h18M47 113h30M47 120h22'/%3E%3Cpath d='M128 100h36l-3 26h-30z'/%3E%3Cpath d='M137 100v-5a9 9 0 0 1 18 0v5'/%3E%3Cpath d='M198 134h50'/%3E%3Cpath d='M204 134v-10h9v10M218 134v-18h9v18M232 134v-26h9v26'/%3E%3Cpath d='M204 106l14-8l10 4l14-10'/%3E%3Cpath d='M234 92h8v8'/%3E%3Ccircle cx='46' cy='93' r='1.6'/%3E%3Ccircle cx='52' cy='93' r='1.6'/%3E%3Ccircle cx='58' cy='93' r='1.6'/%3E%3C/svg%3E") repeat center top / 250px 133px, var(--md-surface);
    box-shadow: 0 -10px 24px -18px color-mix(in srgb, var(--md-on-primary-container) 45%, transparent);
}

/* The one action, first on the sheet, row-shaped so it sits with the rows
   under it: a glyph in a glass disc on the lead, the label, an arrow on the
   tail. The brand gradient is the flat-UI rule's one allowance — the primary
   action — and it is the only gradient on the sheet. */
.md-canvas__add {
    display: flex; align-items: center; gap: 12px;
    min-height: 58px;
    padding: 6px 16px 6px 7px;
    border-radius: var(--md-shape-full);
    background: var(--grad-hero);
    color: var(--md-on-brand);
    text-decoration: none;
    font-size: 17px; font-weight: var(--md-weight-semibold); line-height: 1.4;
    box-shadow: 0 10px 22px -12px color-mix(in srgb, var(--md-primary) 75%, transparent),
                inset 0 1px 0 color-mix(in srgb, var(--md-on-brand) 28%, transparent);
}
/* `.md-canvas__add` as a <button>, for the same reason `.md-ctrl` needed it:
   the Store's CTA OPENS the type chooser rather than going somewhere, and the
   moment it stops being an <a> the UA defaults take over — shrink-to-fit width,
   centred text and the button font. Eleven homes drew this row one way and the
   twelfth drew it another, for no reason anybody chose. */
button.md-canvas__add {
    width: 100%;
    text-align: left;
    font-family: var(--font-primary);
    border: 0;
}

.md-canvas__add__ic {
    width: 44px; height: 44px; flex: none;
    border-radius: var(--md-shape-full);
    display: grid; place-items: center;
    background: color-mix(in srgb, var(--md-on-brand) 18%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--md-on-brand) 30%, transparent);
}
.md-canvas__add__ic .material-symbols-rounded { font-size: 24px; }
.md-canvas__add__lbl { flex: 1; min-width: 0; }
.md-canvas__add__go { font-size: 22px; flex: none; opacity: .92; }
.md-canvas__add.is-off { opacity: .55; }

/* The count on a row: tonal, in the brand pair, tabular so it does not
   jitter while it counts up. `.skl` shimmers over it until the call answers;
   `is-none` is the em dash on a failed call, in surface ink so it reads as
   "no answer" rather than as a value somebody chose. */
.md-pill.md-canvas__cnt {
    height: 26px; min-width: 36px;
    padding: 0 10px;
    justify-content: center;
    font-size: 13px; font-weight: var(--md-weight-semibold);
    font-variant-numeric: tabular-nums;
    background: var(--md-primary-container);
    color: var(--md-on-primary-container);
}
.md-pill.md-canvas__cnt.is-none {
    background: var(--md-surface-container-high);
    color: var(--md-on-surface-variant);
}
.md-canvas-page .md-list .md-row__tail { gap: 8px; }

/* Entrance: the hero and the sheet, not the bar and not every row. The bar is
   excluded outright — a sticky element with a lingering transform is a sticky
   element that stops sticking. */
@keyframes md-canvas-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.md-canvas-page > .md-canvas__hero,
.md-canvas-page > .md-canvas__sheet { animation: md-canvas-in .36s cubic-bezier(.2,.7,.3,1) backwards; }
.md-canvas-page > .md-canvas__sheet { animation-delay: .06s; }
@media (prefers-reduced-motion: reduce) {
    .md-canvas-page > .md-canvas__hero,
    .md-canvas-page > .md-canvas__sheet { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Counts off (owner, 2026-10-14)
   While the flag `dashboard_tile_counts` is off (shared/session/flags.php),
   commons/header.php puts data-counts="off" on <html> and nothing below is
   drawn: the count pill on an icon, a face or a door. The number on a tab
   and on a filter chip stays (owner, the same day: "bring this back"), and
   so do numbers that are the content of a page (results, cart, totals).

   A NEW badge goes in this list. The flag on removes the attribute, and
   every one of them comes back as it was.
   ══════════════════════════════════════════════════════════════════════════ */
/* on an icon, a face or a door */
html[data-counts="off"] .dash-duo__n,
html[data-counts="off"] .dnav-n,
html[data-counts="off"] .dash-fab__n,
html[data-counts="off"] .mode-chip__n,
html[data-counts="off"] .mode-row__n,
html[data-counts="off"] .pc-chip__n,
html[data-counts="off"] .cvh-duo__n,
html[data-counts="off"] .td-st__n,
html[data-counts="off"] .mh-door__n,
html[data-counts="off"] .mh-ppl__n,
html[data-counts="off"] .tkl-face__n,
html[data-counts="off"] .cvbz-tile__n,
html[data-counts="off"] .myra-jump__badge { display: none !important; }

/* ── The loading note (owner, 2026-11-14) ───────────────────────────────────
   "wherever we have shimmers or loading effects, display Please wait, Loading
   data … somewhere, visually appealing". One small white card under the app
   bar while something is loading (platform-core.js shows and hides it). A
   still hourglass in a brand-tinted circle, "Please wait" in the ink, "Loading
   data…" quieter beside it. It never takes a tap and sits under the dialogs. */
.md-loadnote {
    position: fixed;
    left: 50%;
    top: calc(env(safe-area-inset-top, 0px) + 72px);
    transform: translateX(-50%);
    z-index: 900;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    max-width: calc(100% - 32px);
    padding: 7px 18px 7px 7px;
    border-radius: var(--md-shape-full);
    background: var(--md-surface-container-lowest);
    color: var(--md-on-surface);
    box-shadow: 0 8px 24px -10px color-mix(in srgb, var(--md-primary) 45%, transparent),
                0 0 0 1px color-mix(in srgb, var(--md-primary) 16%, transparent);
    pointer-events: none;
    font-size: 14px;
    line-height: 1.4;
    white-space: nowrap;
}
.md-loadnote[hidden] { display: none; }
.md-loadnote__ic {
    flex: none;
    width: 34px; height: 34px;
    border-radius: 50%;
    display: grid; place-items: center;
    background: var(--md-primary-container);
    color: var(--md-primary);
}
.md-loadnote__ic .material-symbols-rounded { font-size: 20px; font-variation-settings: 'FILL' 1; }
.md-loadnote__tx { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.md-loadnote__tx b { font-weight: var(--md-weight-semibold); }
.md-loadnote__tx span { margin-left: 5px; color: var(--md-on-surface-variant); }
