/* ============================================================
   Ava — stylesheet.  Tokens live in tokens.css.
   ------------------------------------------------------------
   The component language is ubos-dms / Manager's, deliberately:
   navy rail on the left, 56px topbar, 12px cards with a soft
   shadow, 40px controls so a button lines up with the field beside
   it, 13px tables. Somebody who uses both products should not have
   to learn two layouts.

   FOUR breakpoints, and only four (docs/RESPONSIVE_SPEC.md):

     mobile   0    – 767px   bottom nav, one column, cards, wizard
     tablet   768  – 1023px  icon-only rail, essential columns
     laptop   1024 – 1439px  full sidebar with labels, normal tables
     monitor  1440px+        wider rail, more columns per row

   Base styles below are the MOBILE styles — no media query. Every
   larger tier is added with (min-width: ...). Writing it the other
   way round means each rule has to be undone at the next tier.

   The one deliberate exception is the table→card transform, which
   uses max-width: a <table> already behaves correctly on desktop by
   default; it is mobile that has to break it apart.

   Gold and sweet pink are ornament. They appear on the login panel,
   on rules and edges, in empty states and on print headers — never
   behind figures somebody is reading. See tokens.css.
   ============================================================ */

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

html {
    font-family: var(--font-sans);
    font-size: var(--text-base);
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--page-bg);
    color: var(--ink);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3 {
    /* The brand face on headings only. Poppins costs real width and
       reads worse than Inter at 13px, which is what most of an ERP
       is. Set on the elements so a heading added next year is right
       without anybody remembering. */
    font-family: var(--font-display);
    color: var(--ink);
    margin: 0 0 0.6rem;
}

h1 {
    font-size: var(--text-title-lg);
    font-weight: 700;
    line-height: 1.25;
}
h2 {
    font-size: var(--text-title-sm);
    font-weight: 600;
    margin-top: 1.6rem;
}
h3 {
    font-size: 15px;
    font-weight: 600;
}

a {
    color: var(--royal-700);
    text-decoration: none;
}
a:hover {
    text-decoration: underline;
}

p {
    margin: 0 0 0.75rem;
}

.muted {
    color: var(--ink-muted);
    font-size: var(--text-sm);
}

/* Visible to a screen reader, invisible on screen. For a button whose
   meaning is carried by an icon — an icon-only control with no
   accessible name is a button nobody using a reader can identify.
   `display: none` would hide it from the reader too, which is the
   whole thing this is avoiding. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.error {
    color: var(--danger-700);
    background: var(--danger-50);
    border: 1px solid #fecaca;
    border-radius: var(--radius);
    padding: 0.6rem 0.85rem;
    margin: 0 0 1rem;
}

/* Focus is never removed, only restyled. A keyboard user who cannot
   see where they are is locked out of the form. */
:focus-visible {
    outline: 2px solid var(--royal-500);
    outline-offset: 2px;
    border-radius: 4px;
}

/* ---- Card ---- */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
    padding: 1.25rem;
    transition: box-shadow 0.15s ease;
}
.card:hover {
    box-shadow: var(--shadow-soft-lg);
}

.card-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    margin-bottom: 1.5rem;
}
.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
    padding: 1rem 1.15rem;
}
.stat strong {
    display: block;
    font-family: var(--font-display);
    font-size: 22px;
    font-weight: 700;
    margin-top: 0.25rem;
}

/* ---- Buttons ----
   One height for buttons and inputs (--control) so a button beside a
   field lines up exactly. */
button,
.btn,
input[type="submit"] {
    font-family: inherit;
    font-size: var(--text-base);
    font-weight: 500;
    min-height: var(--control);
    padding: 0 1rem;
    border-radius: var(--radius);
    border: 1px solid transparent;
    background: var(--royal-600);
    color: var(--ink-inverse);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
button:hover,
.btn:hover {
    background: var(--royal-700);
}
button:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* ---- §09 Button Standard ----
   Six variants, each with one job. Naming them after the job rather
   than the colour is what stops "the green one" meaning Submit on one
   screen and Approve on another. Master Plan §28: same button naming
   everywhere. */
.btn-primary {
    background: var(--royal-600);
    color: var(--ink-inverse);
}
.btn-primary:hover {
    background: var(--royal-700);
}

button.secondary,
.btn-secondary {
    background: var(--surface);
    border-color: var(--border-strong);
    color: var(--gray-700);
}
button.secondary:hover,
.btn-secondary:hover {
    background: var(--gray-50);
}

.btn-success {
    background: var(--success-600);
    color: var(--ink-inverse);
}
.btn-success:hover {
    background: var(--success-700);
}

button.danger,
.btn-danger {
    background: var(--danger-600);
    color: var(--ink-inverse);
}
button.danger:hover,
.btn-danger:hover {
    background: var(--danger-700);
}

.btn-outline {
    background: transparent;
    border-color: var(--royal-600);
    color: var(--royal-700);
}
.btn-outline:hover {
    background: var(--royal-50);
}

.btn-ghost {
    background: transparent;
    border-color: transparent;
    color: var(--gray-600);
}
.btn-ghost:hover {
    background: var(--gray-100);
    color: var(--ink);
}

/* ---- Status badges ----
   Never colour alone: the label says the state, the colour only
   reinforces it. A greyscale printout and a red-green colour-blind
   reader both still get the answer. */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.55rem;
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
    border: 1px solid transparent;
}
.badge-posted {
    background: var(--success-50);
    color: var(--success-700);
    border-color: #a7f3d0;
}
.badge-pending {
    background: var(--warning-50);
    color: var(--warning-700);
    border-color: #fde68a;
}
.badge-draft {
    background: var(--gray-100);
    color: var(--gray-700);
    border-color: var(--border-strong);
}
.badge-rejected {
    background: var(--danger-50);
    color: var(--danger-700);
    border-color: #fecaca;
}

.form-actions .primary {
    width: 100%;
    min-height: 48px;
    font-size: 15px;
}

/* ---- Switches in the Control Panel ---- */
.inline-form {
    display: inline-flex;
    gap: 0.4rem;
    align-items: center;
}
.inline-form + .inline-form {
    margin-left: 0.5rem;
}
.inline-form button {
    min-height: 34px;
    padding: 0 0.75rem;
    font-size: var(--text-table);
    background: var(--surface);
    border-color: var(--border-strong);
    color: var(--gray-600);
}
.inline-form button:hover {
    background: var(--gray-50);
}
.switch-on {
    background: var(--royal-50) !important;
    border-color: var(--royal-300) !important;
    color: var(--royal-700) !important;
    font-weight: 600;
}
.switch-off {
    color: var(--ink-muted) !important;
}

/* ---- Tables ----
   13px, and on mobile every row becomes a card. That only works if
   each <td> carries data-label — see docs/RESPONSIVE_SPEC.md §4. */
table {
    width: 100%;
    border-collapse: collapse;
    background: var(--surface);
    font-size: var(--text-table);
    border-radius: var(--radius-card);
    overflow: hidden;
}
thead th {
    text-align: left;
    font-weight: 600;
    color: var(--gray-600);
    background: var(--gray-50);
    border-bottom: 1px solid var(--border);
    padding: 0.6rem 0.75rem;
    white-space: nowrap;
}
tbody td {
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}
tbody tr:last-child td {
    border-bottom: none;
}
tbody tr:hover {
    background: var(--royal-50);
}

/* ---- Money ----
   The single most important typographic rule in an accounting
   product, and the one this stylesheet was shipping wrong: amounts
   were left-aligned in wide columns, so the digits of a trial balance
   did not line up and the column could not be read down or added up
   by eye.

   Three things together make a figure column readable:
     text-align: right      units under units, tens under tens
     font-variant-numeric   tabular figures, so 1 is as wide as 8
     a narrow fixed column  the number sits next to what it belongs to

   `th.num` matters as much as `td.num` — a right-aligned column under
   a left-aligned heading looks like a mistake. */
.num,
th.num,
td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    white-space: nowrap;
    width: 1%; /* shrink-to-fit: the label column takes the slack */
}

/* A negative figure is red AND parenthesised. Colour alone fails for
   the ~8% of men who cannot reliably distinguish it, and an accountant
   reading a printout in greyscale gets nothing from it either. */
.num--negative {
    color: var(--danger-700);
}

/* The total line has to read as a total without being shouted. A rule
   above it and the figures in 600 is how a printed statement does it. */
tbody tr.total-row td,
tfoot td {
    border-top: 2px solid var(--border-strong);
    border-bottom: none;
    font-weight: 600;
    background: var(--gray-50);
}
tbody tr.total-row:hover td {
    background: var(--gray-50);
}

/* A statement's grand total — one step louder than a subtotal. */
tbody tr.grand-total td {
    border-top: 2px solid var(--royal-600);
    background: var(--royal-50);
    color: var(--royal-900);
}

/* An empty table says so, in the table, instead of collapsing to a
   header with nothing under it. */
.report-verdict.is-ok {
    color: var(--success-700);
    background: var(--success-50);
    border: 1px solid var(--success-600);
    border-radius: var(--radius);
    padding: 0.6rem 0.85rem;
    display: inline-block;
}

.table-empty td {
    text-align: center;
    color: var(--ink-muted);
    padding: 2rem 1rem;
}

/* Mobile: tables become stacked cards. */
@media (max-width: 767px) {
    table,
    table tbody,
    table tr,
    table td {
        display: block;
        width: 100%;
    }
    table thead {
        display: none;
    }
    table {
        background: transparent;
    }
    table tr {
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--radius-card);
        box-shadow: var(--shadow-soft);
        margin-bottom: 0.75rem;
        padding: 0.4rem 0;
    }
    tbody td {
        border-bottom: none;
        padding: 0.35rem 0.85rem;
    }
    tbody tr:hover {
        background: var(--surface);
    }
    table td[data-label]::before {
        content: attr(data-label);
        display: block;
        font-size: 11px;
        font-weight: 500;
        color: var(--ink-muted);
        margin-bottom: 1px;
    }

    /* A figure in card mode reads best on one line with its label —
       "Debit ............ 1,500.00" — rather than stacked, which
       wastes a line each and makes a five-column row eight lines
       tall on a phone. The shrink-to-fit width from the desktop rule
       has to be undone or the cell collapses. */
    tbody td.num {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 1rem;
        width: auto;
    }
    tbody td.num[data-label]::before {
        margin-bottom: 0;
        font-size: 12px;
    }

    tbody tr.total-row td,
    tbody tr.grand-total td {
        border-top: none;
    }
    tbody tr.total-row,
    tbody tr.grand-total {
        border-color: var(--border-strong);
        background: var(--gray-50);
    }
}

/* ============================================================
   Alpona — the golden ornament
   ------------------------------------------------------------
   The rice-paste motif drawn on Bengali thresholds at festivals.
   It is here to make the product feel like somebody cared, which is
   a real thing to want from software people open every morning.

   Placed with mask-image rather than as a background-image, so the
   colour comes from the tokens: one SVG file serves gold on navy and
   gold on white, and changing --gold-line changes the ornament.

   WHERE IT IS ALLOWED, and this is a rule:
     login screen, empty states, print headers, section rules.
   WHERE IT IS NOT:
     behind a table, a form, a report, or any screen somebody works
     in. An operator entering two hundred lines a day needs calm
     ground and clear figures — a pattern behind data makes the eye
     read the pattern. See docs/PRODUCT_PRINCIPLES.md.

   aria-hidden by construction: it is a background, so a screen
   reader never meets it.
   ============================================================ */
.alpona {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-color: var(--gold-line);
    -webkit-mask-image: url("/static/img/alpona-tile.svg");
    mask-image: url("/static/img/alpona-tile.svg");
    -webkit-mask-repeat: repeat;
    mask-repeat: repeat;
    -webkit-mask-size: 240px 240px;
    mask-size: 240px 240px;
}

/* Two scales, layered, so the ornament has depth instead of reading
   as wallpaper: a large open motif across the whole ground and a
   denser one inside the brand panel. The form itself sits on solid
   white above both, so nothing is ever read through a pattern. */
.alpona--field {
    opacity: 0.2;
    -webkit-mask-size: 420px 420px;
    mask-size: 420px 420px;
}
.alpona--panel {
    opacity: 0.3;
    -webkit-mask-size: 190px 190px;
    mask-size: 190px 190px;
}

/* A second, offset copy at a different scale. Two grids at different
   periods stop the eye finding the repeat, which is what separates
   ornament from tiling. */
.alpona--field::after,
.alpona--panel::after {
    content: "";
    position: absolute;
    inset: 0;
    background-color: inherit;
    -webkit-mask-image: url("/static/img/alpona-tile.svg");
    mask-image: url("/static/img/alpona-tile.svg");
    -webkit-mask-repeat: repeat;
    mask-repeat: repeat;
    -webkit-mask-size: 118px 118px;
    mask-size: 118px 118px;
    -webkit-mask-position: 59px 59px;
    mask-position: 59px 59px;
    opacity: 0.55;
}

/* An empty state has nothing to compete with, so the alpona can be
   the thing on screen. Faded from the centre so the message stays
   the brightest object. */
.alpona--empty {
    opacity: 0.5;
    background-color: var(--gold-line-strong);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
    -webkit-mask-image: url("/static/img/alpona-tile.svg"),
        radial-gradient(60% 60% at 50% 50%, #000 0%, transparent 78%);
    mask-image: url("/static/img/alpona-tile.svg"),
        radial-gradient(60% 60% at 50% 50%, #000 0%, transparent 78%);
    -webkit-mask-size: 200px 200px, 100% 100%;
    mask-size: 200px 200px, 100% 100%;
    -webkit-mask-repeat: repeat, no-repeat;
    mask-repeat: repeat, no-repeat;
}

.empty-state {
    position: relative;
    overflow: hidden;
    text-align: center;
    padding: 3rem 1.5rem;
    border-radius: var(--radius-card);
    background: var(--surface);
    border: 1px solid var(--border);
}
/* Same trap as .login-art — see the note there. */
.empty-state > :not(.alpona) {
    position: relative;
    z-index: 1;
}
.empty-state p {
    color: var(--ink-muted);
    margin: 0;
}

/* ============================================================
   Login — the one screen allowed to be beautiful
   ------------------------------------------------------------
   Two panels above 900px: the alpona on navy at the left, the form
   at the right. Below that the ornament collapses to a band above
   the form, because a phone should show the password field without
   scrolling past decoration. The ornament is always behind an empty
   panel, never behind data.
   ============================================================ */
.login-wrap {
    position: relative;
    overflow: hidden;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.25rem;
    background:
        var(--sweet-wash),
        radial-gradient(120% 90% at 50% -10%, var(--navy-800) 0%, var(--navy-950) 62%);
}

.login-panel {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 400px;
    background: var(--surface);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-panel);
    overflow: hidden;
    display: grid;
    grid-template-columns: 1fr;
    /* Rises into place rather than appearing. Short and eased, so it
       reads as the page settling and not as an effect. Anyone who has
       asked their system to stop moving things gets it instantly —
       see the prefers-reduced-motion block at the end of this file. */
    animation: panel-rise 0.42s cubic-bezier(0.22, 0.8, 0.3, 1) both;
}
/* A hair of gold around the whole panel. At 0.22 alpha it is felt
   more than seen, which is the correct strength for a frame. */
.login-panel::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    border: 1px solid rgb(201 162 39 / 0.22);
    pointer-events: none;
}

@keyframes panel-rise {
    from {
        opacity: 0;
        transform: translateY(14px) scale(0.995);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

/* ---- The ornamental half ---- */
.login-art {
    position: relative;
    overflow: hidden;
    /* Three layers, so the navy has depth instead of being one flat
       fill: a warm gold bloom in the lit corner, a rose cast in the
       far one, and the deep navy underneath. */
    background:
        radial-gradient(90% 70% at 12% 2%, rgb(217 180 80 / 0.16) 0%, transparent 62%),
        radial-gradient(80% 70% at 96% 100%, rgb(248 96 155 / 0.14) 0%, transparent 60%),
        linear-gradient(160deg, var(--navy-800) 0%, var(--navy-950) 58%, #070d1e 100%);
    color: #fff;
    padding: 1.5rem 1.5rem 1.35rem;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: 168px;
}
/* A scrim under the wordmark. The alpona tiles, so where a rosette
   lands behind the text is a matter of viewport height — this
   guarantees the same contrast at every size instead of leaving it
   to luck. */
.login-art::before {
    content: "";
    position: absolute;
    inset: 35% 0 0 0;
    background: linear-gradient(to bottom, transparent 0%, rgb(7 13 30 / 0.72) 62%);
    pointer-events: none;
}

/* The lit metal edge — gold into sweet pink. Not flat paint. */
.login-art::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 3px;
    background: var(--brand-gradient);
}
/* The panel is the richest surface on the page, not the quietest —
   the first attempt faded it so hard with a corner light that it
   read as a flat black rectangle beside a beautifully patterned
   ground, which is exactly backwards.

   The gradient now only leans the drawing towards the lit corner; it
   never takes it away. Nothing is read through it — the panel holds
   a logo and two lines of brand text, and the form is on solid white
   next to it. */
.login-art .alpona {
    opacity: 0.62;
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
}
.login-art .alpona,
.login-art .alpona::after {
    -webkit-mask-image: url("/static/img/alpona-tile.svg"),
        radial-gradient(150% 130% at 14% 4%, #000 0%, rgb(0 0 0 / 0.78) 62%, rgb(0 0 0 / 0.55) 100%);
    mask-image: url("/static/img/alpona-tile.svg"),
        radial-gradient(150% 130% at 14% 4%, #000 0%, rgb(0 0 0 / 0.78) 62%, rgb(0 0 0 / 0.55) 100%);
    -webkit-mask-size: 168px 168px, 100% 100%;
    mask-size: 168px 168px, 100% 100%;
    -webkit-mask-repeat: repeat, no-repeat;
    mask-repeat: repeat, no-repeat;
}
.login-art .alpona::after {
    -webkit-mask-size: 104px 104px, 100% 100%;
    mask-size: 104px 104px, 100% 100%;
    opacity: 0.6;
}
/* :not(.alpona) matters. This rule used to be `.login-art > *`, which
   has the same specificity as `.alpona` and comes later in the file,
   so it replaced the ornament's `position: absolute` with `relative`
   — the element then had no box, collapsed to zero height, and the
   panel rendered as a flat navy rectangle beside a beautifully
   patterned background. Exactly backwards, and invisible in the
   markup: the div was there, the CSS was there, the cascade quietly
   ate it. */
.login-art > :not(.alpona) {
    position: relative;
    z-index: 1;
}
/* The mark sits in a gold ring rather than floating as a bare
   square — a ring is a line, which is what gold is for. */
.login-logo {
    width: 46px;
    height: 46px;
    border-radius: 13px;
    margin-bottom: 0.9rem;
    padding: 2px;
    background: var(--gold-gradient);
    box-shadow:
        0 6px 20px rgb(11 19 42 / 0.55),
        0 0 0 1px rgb(231 201 122 / 0.35);
}
.login-art h1 {
    color: #fff;
    font-size: 30px;
    letter-spacing: -0.01em;
    margin: 0 0 0.2rem;
}
.login-art .tagline {
    display: block;
    /* Gold ink: 11.4:1 on this navy, weight 500 — a short label, which
       is the only text job gold is allowed. */
    color: var(--gold-ink);
    font-weight: 500;
    font-size: 12px;
    letter-spacing: 0.01em;
    line-height: 1.45;
    margin: 0;
    max-width: 24ch;
}

/* ---- The working half ---- */
/* A gold hairline where the two halves meet, on the stacked layout.
   Rules and edges are exactly what gold is for. */
.login-art + .login-form {
    border-top: 1px solid rgb(201 162 39 / 0.28);
}

.login-form {
    padding: 1.6rem 1.5rem 1.5rem;
    background: var(--surface);
}
/* The gold flourish above the heading: a short rule, the way an
   invitation card carries one. */
.login-form h2::before {
    content: "";
    display: block;
    width: 38px;
    height: 2px;
    margin-bottom: 0.7rem;
    border-radius: 2px;
    background: var(--gold-gradient);
}
.login-form h2 {
    margin: 0 0 0.15rem;
    font-size: 19px;
}
.login-form .login-hint {
    color: var(--ink-muted);
    font-size: var(--text-sm);
    margin: 0 0 1.25rem;
}
.login-form label {
    margin-top: 0.9rem;
}
.login-form label:first-of-type {
    margin-top: 0;
}
.login-form .form-actions {
    margin-top: 1.4rem;
}
.login-foot {
    margin: 1.1rem 0 0;
    padding-top: 0.9rem;
    border-top: 1px solid var(--border);
    font-size: 11px;
    color: var(--ink-muted);
    text-align: center;
}

/* The split waits for the laptop tier rather than getting a
   breakpoint of its own. A fifth width is a fifth thing to test, and
   a 780px panel inside a 820px tablet is cramped anyway — stacked is
   genuinely better there. docs/RESPONSIVE_SPEC.md §2. */
@media (min-width: 1024px) {
    .login-panel {
        max-width: 820px;
        grid-template-columns: 5fr 6fr;
    }
    .login-art {
        min-height: 420px;
        padding: 2rem;
    }
    /* On the wide layout the brand rule runs down the seam between
       the two panels rather than across the bottom. */
    .login-art::after {
        inset: 0 0 0 auto;
        width: 3px;
        height: auto;
    }
    .login-art h1 {
        font-size: 38px;
    }
    .login-art .tagline {
        font-size: 13px;
    }
    .login-logo {
        width: 56px;
        height: 56px;
    }
    .login-form {
        padding: 2.25rem 2rem;
        align-self: center;
        width: 100%;
    }
}

/* ---- Forms ---- */
label {
    display: block;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--gray-600);
    margin-bottom: 0.25rem;
}
input,
select,
textarea {
    font-family: inherit;
    font-size: var(--text-base);
    width: 100%;
    min-height: var(--control);
    padding: 0.5rem 0.7rem;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
}
input:focus,
select:focus,
textarea:focus {
    outline: none;
    border-color: var(--royal-500);
    box-shadow: var(--ring);
}
.card label {
    margin-top: 0.85rem;
}
.card label:first-of-type {
    margin-top: 0;
}

.form-field {
    margin-bottom: 0.9rem;
}
.form-field label {
    margin-bottom: 0.25rem;
}
.form-actions {
    margin-top: 1.25rem;
}

/* ---- Language switch ---- */
.lang-switch {
    position: fixed;
    top: 0.6rem;
    right: 1rem;
    z-index: 40;
    display: flex;
    gap: 0.4rem;
    align-items: center;
    font-size: var(--text-sm);
    background: var(--surface);
    border: 1px solid var(--border);
    padding: 0.25rem 0.6rem;
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-soft);
}
.lang-switch a {
    color: var(--ink-muted);
}
.lang-switch a.active {
    color: var(--royal-700);
    font-weight: 600;
}

/* ============================================================
   App shell — Master Plan §01 Global Layout

     Top Navigation
     ├─ Breadcrumb
     ├─ Page Toolbar
     ├─ Main Content
     └─ Footer

   Navy rail at the left on a laptop; on a phone the rail becomes the
   bottom bar and the topbar stays, because the company selector and
   the way out have to be reachable on every device.
   ============================================================ */
.app-shell {
    min-height: 100vh;
}
/* Both sidebar columns are opt-in: on a phone the bottom bar is the
   navigation and neither of these exists. */
.sidebar,
.sidebar-rail {
    display: none;
}

/* ---- Menu search + Recent (inside the panel) ---- */
.menu-search {
    padding: 0 0.15rem 0.6rem;
}
.menu-search input {
    min-height: 34px;
    padding: 0.35rem 0.6rem;
    font-size: 12.5px;
    color: #fff;
    background: rgb(255 255 255 / 0.07);
    border: 1px solid rgb(255 255 255 / 0.14);
}
.menu-search input::placeholder {
    color: var(--navy-300);
}
.menu-search input:focus {
    border-color: var(--royal-400);
    box-shadow: none;
    background: rgb(255 255 255 / 0.12);
}

.sidebar-recent {
    margin-bottom: 0.5rem;
}
.sidebar-recent > summary {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.35rem 0.6rem;
    list-style: none;
    cursor: pointer;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--navy-300);
    min-height: 32px;
}
.sidebar-recent > summary::-webkit-details-marker {
    display: none;
}
.recent-list {
    display: flex;
    flex-direction: column;
}
.recent-list a {
    padding: 0.35rem 0.6rem 0.35rem 1.5rem;
    border-radius: var(--radius);
    color: var(--navy-200);
    font-size: 12.5px;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.recent-list a:hover {
    background: rgb(255 255 255 / 0.07);
    color: #fff;
    text-decoration: none;
}
.menu-no-match {
    padding: 0.75rem 0.6rem;
    color: var(--navy-300);
    font-size: 12px;
}
.shell-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    min-height: 100vh;
}
.shell-main {
    flex: 1;
    padding: 1rem 1rem 5.5rem;
    max-width: 100%;
}

/* ---- §02 Top Navigation ---- */
.topbar {
    position: sticky;
    top: 0;
    z-index: 25;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-height: var(--topbar-h);
    padding: 0 0.85rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}
.topbar-brand {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 15px;
    color: var(--ink);
    text-decoration: none;
}
.topbar-brand img {
    width: 26px;
    height: 26px;
    border-radius: 7px;
}
.topbar-spacer {
    flex: 1;
}
/* The background/border reset is not cosmetic. A <button> here picks
   up the global primary-button rule and renders as a solid royal-blue
   rectangle with an icon lost inside it — which is exactly what the
   fullscreen and theme controls were doing. .topbar-item has the
   higher specificity but it has to actually SET the properties;
   winning on colour while saying nothing about background leaves the
   element wearing the other rule's blue. */
.topbar-item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: var(--touch);
    padding: 0 0.6rem;
    border-radius: var(--radius);
    background: transparent;
    border: 1px solid transparent;
    color: var(--gray-600);
    font-size: var(--text-table);
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}
.topbar-item:hover,
button.topbar-item:hover {
    background: var(--gray-100);
    color: var(--ink);
    text-decoration: none;
}
/* The active company and branch. Named on screen at all times
   because posting to the wrong company is a mistake whose fix is a
   journal entry. */
.topbar-org {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.2rem 0.5rem;
    border-radius: var(--radius);
    text-decoration: none;
    min-width: 0;
}
.topbar-org:hover {
    background: var(--gray-100);
    text-decoration: none;
}
.topbar-org .org-name {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 15px;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 12ch;
}
/* The branch is its own control beside the company, not a label
   inside it — a head-office user switches branch several times a day
   and company almost never, and burying the frequent move inside the
   rare one costs a click every time. */
.org-branch {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.15rem 0.6rem;
    min-height: 28px;
    border: 1px solid var(--royal-200);
    border-radius: var(--radius-pill);
    background: var(--royal-50);
    color: var(--royal-700);
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none;
}
.org-branch:hover {
    background: var(--royal-100);
    text-decoration: none;
}
.branch-card {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    align-items: flex-start;
}
.branch-card .stat-value {
    margin-top: 0;
    font-size: 17px;
}
/* One line, name then switch icon.
   
   This used to wrap deliberately, so the company name sat above its
   tagline as a two-line lockup. The tagline has gone from the topbar
   — it was already in the sidebar in gold, three centimetres to the
   left, and printing a slogan twice spent the two most valuable
   places on the screen saying one thing. With it gone the wrap had
   nothing left to stack, so the switch icon dropped onto its own line
   under the name and read as a broken control. */
.topbar-org .icon {
    color: var(--ink-muted);
    flex: 0 0 auto;
}

/* The uploaded logo, wherever it lands. object-fit so a wide lockup
   and a square mark both sit correctly in the same box. */
.rail-brand img,
.topbar-brand img,
.login-logo {
    object-fit: contain;
}
.logo-preview {
    height: 34px;
    max-width: 160px;
    object-fit: contain;
    vertical-align: middle;
    margin-right: 0.6rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 2px 4px;
}

.topbar-search {
    flex: 1;
    min-width: 0;
    max-width: 340px;
    display: none;
}
.topbar-search input {
    min-height: 34px;
    font-size: var(--text-table);
    background: var(--surface-sunken);
}

.topbar-approval {
    position: relative;
}
/* The count is a badge, but the accessible name says the number in
   words next to it — a superscript digit is not something a screen
   reader announces usefully on its own. */
.badge-count {
    position: absolute;
    top: 2px;
    right: 0;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: var(--radius-pill);
    background: var(--danger-600);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
}

/* Inline SVG icons: stroke follows the text colour, so one file works
   in both themes and inside a hovered or active item. 18px sits right
   beside 13px text without dominating it. */
.icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

.topbar-lang {
    gap: 0.3rem;
}
.topbar-lang a {
    color: var(--ink-muted);
    padding: 0 0.15rem;
}
.topbar-lang a.is-current {
    color: var(--royal-700);
    font-weight: 600;
}
.topbar-user {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.topbar-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--royal-600);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 600;
    flex-shrink: 0;
}
.topbar-user .who {
    display: none;
    line-height: 1.2;
}
.topbar-user .who strong {
    display: block;
    font-size: var(--text-table);
    font-weight: 600;
}
.topbar-user .who span {
    font-size: 11px;
    color: var(--ink-muted);
}

/* ---- §05 Page Header: breadcrumb + title + actions ---- */
.breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    font-size: var(--text-sm);
    color: var(--ink-muted);
    margin-bottom: 0.5rem;
}
.breadcrumb a {
    color: var(--ink-muted);
}
.breadcrumb a:hover {
    color: var(--royal-700);
}
.breadcrumb .sep {
    color: var(--gray-300);
}
.breadcrumb [aria-current="page"] {
    color: var(--gray-700);
    font-weight: 500;
}

.page-header {
    margin-bottom: 1.25rem;
}
.page-header-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.75rem;
}
.page-header-titles {
    min-width: 0;
}
.page-header-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
.page-header h1 {
    margin: 0;
    font-size: 22px;
}
.page-header .subtitle {
    margin: 0.25rem 0 0;
    color: var(--ink-muted);
    font-size: var(--text-sm);
}

/* ---- §06 Page Toolbar ---- */
.page-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.85rem;
}
.page-toolbar .spacer {
    flex: 1;
}
.toolbar-search input {
    width: 16rem;
    max-width: 100%;
    min-height: 34px;
    font-size: var(--text-table);
}

/* ---- §06 Pagination ---- */
.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.55rem 0.75rem;
    border: 1px solid var(--border);
    border-top: none;
    border-radius: 0 0 var(--radius-card) var(--radius-card);
    background: var(--surface);
    font-size: var(--text-sm);
    color: var(--ink-muted);
}
.pagination-controls {
    display: flex;
    gap: 0.5rem;
}
.pagination .btn-secondary {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding: 0 0.7rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--gray-700);
    font-size: var(--text-table);
    text-decoration: none;
}
/* A disabled edge is a <span>, so it cannot be focused or followed —
   a greyed-out <a> still takes a Tab stop and still navigates. */
.pagination .is-disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* ---- Tabs ---- */
.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1.25rem;
}
.tab {
    padding: 0.55rem 1rem;
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    color: var(--ink-muted);
    font-size: var(--text-table);
    font-weight: 500;
    text-decoration: none;
    min-height: var(--touch);
    display: inline-flex;
    align-items: center;
}
.tab:hover {
    color: var(--gray-700);
    text-decoration: none;
}
.tab.is-active {
    border-bottom-color: var(--royal-600);
    color: var(--royal-700);
    font-weight: 600;
}

/* ---- §28 Modal ----
   <dialog> gives Escape, focus trapping and the top layer for free.
   Hand-rolled overlays get the first two wrong roughly always. */
.modal {
    width: min(32rem, calc(100vw - 2rem));
    max-height: 90vh;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-soft-lg);
}
.modal::backdrop {
    background: rgb(11 19 42 / 0.45);
}
.modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1.25rem;
    border-bottom: 1px solid var(--border);
}
.modal-head h2 {
    margin: 0;
    font-size: var(--text-title-sm);
}
.modal-close {
    min-height: 32px;
    padding: 0 0.5rem;
    font-size: 16px;
    line-height: 1;
}
.modal-body {
    padding: 1.25rem;
    overflow-y: auto;
}
.modal-foot {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.9rem 1.25rem;
    border-top: 1px solid var(--border);
}
.modal--confirm {
    width: min(26rem, calc(100vw - 2rem));
}

/* ---- §04 Dashboard ---- */
.greeting {
    margin-bottom: 1.5rem;
}
.greeting h1 {
    margin: 0 0 1rem;
    font-size: 26px;
}
/* A <dl> because these are label/value pairs and that is what the
   element is for — a screen reader announces "Company: Default
   Company" rather than reading two unrelated runs of text. */
.greeting-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem 2.5rem;
    margin: 0;
}
.greeting-facts > div {
    min-width: 0;
}
.greeting-facts dt {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-muted);
}
.greeting-facts dd {
    margin: 0.15rem 0 0;
    font-size: 15px;
    font-weight: 500;
}

.section-heading {
    margin: 1.75rem 0 0.6rem;
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* A single-column form: label above input, one field per row.
   Mobile-first and left that way — a two-column form on a laptop
   saves a little scrolling and costs a clear reading order, and these
   forms are typed straight through from top to bottom. */
.stack-form {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    max-width: 34rem;
}

.stack-form .field {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.stack-form label {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--ink);
}

/* A checkbox is the one control that reads better beside its label
   than under it — the label IS the question. */
.stack-form .field:has(input[type="checkbox"]) {
    flex-direction: row-reverse;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
}

.stack-form input[type="checkbox"] {
    width: 1.15rem;
    height: 1.15rem;
    min-height: 0;
    accent-color: var(--royal-600);
}

.stack-form .muted {
    margin: 0;
}

/* A group of checkboxes that answer one question. A fieldset rather
   than loose inputs so a screen reader announces what the ticks are
   for; the border is dropped because the legend already says it. */
.role-picker {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem 0.9rem;
}

.role-picker legend {
    font-size: var(--text-sm);
    font-weight: 600;
    padding: 0 0.3rem;
}

.role-option {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 0.9rem 0.4rem 0;
    font-weight: 400;
}

.role-option input[type="checkbox"] {
    width: 1.15rem;
    height: 1.15rem;
    min-height: 0;
    accent-color: var(--royal-600);
}

/* A kind of a kind — Transporter under Vendor. Indented rather than
   listed flat, because a flat list of fourteen boxes hides the fact
   that ticking Transporter already means Vendor. */
.role-option-child {
    margin-left: 1.5rem;
}
.role-option-child::before {
    content: "B3";
    margin-right: 0.3rem;
    color: var(--ink-muted);
}

/* Required marker. Colour alone would say nothing to a screen reader
   or a monochrome printout, so it is a character first. */
.req {
    color: var(--danger-600);
    font-weight: 700;
}

/* A success message, sized and coloured as the mirror of .error so
   the two read as one pair rather than two unrelated boxes. */
.flash-success {
    color: var(--success-700);
    background: var(--success-50);
    border: 1px solid #a7f3d0;
    border-radius: var(--radius);
    padding: 0.6rem 0.85rem;
    margin: 0 0 1rem;
}

/* A warning, between .error and .flash-success in weight: something
   the user should act on, but nothing has gone wrong. */
.flash-warning {
    color: var(--warning-700);
    background: var(--warning-50);
    border: 1px solid #fde68a;
    border-radius: var(--radius);
    padding: 0.6rem 0.85rem;
    margin: 0 0 1rem;
}

/* A row of filter controls above a table. Wraps on a phone rather
   than scrolling sideways — a filter you have to scroll to reach is a
   filter nobody uses. */
.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.5rem;
    margin: 0 0 1rem;
}
.filter-bar label {
    align-self: center;
    font-size: var(--text-sm);
    color: var(--ink-muted);
}

/* Per-row controls. Kept on one line on desktop; on a phone the table
   is already a stack of cards, so they wrap under the row's facts. */
.row-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}
.row-actions .inline-form {
    display: flex;
    gap: 0.4rem;
    align-items: center;
    margin: 0;
}
.row-actions input[type="text"] {
    /* Narrow on purpose: it is an optional note beside a button, not
       the point of the row. */
    min-width: 8rem;
    max-width: 12rem;
}

/* §7 Universal Document Information.
   A grid of label/value pairs above the document body. auto-fill with
   a minimum rather than a fixed column count: on a phone it is one
   column, on a wide desktop it is four, and nothing in between needs a
   breakpoint written for it. */
.doc-facts {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 0.75rem 1.5rem;
    margin: 0 0 1.25rem;
    padding: 0.9rem 1rem;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.doc-facts dt {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-muted);
    margin-bottom: 0.15rem;
}

.doc-facts dd {
    margin: 0;
    font-size: 13px;
    color: var(--ink);
    /* Long branch names wrap rather than widening the column and
       pushing the next fact off the row. */
    overflow-wrap: anywhere;
}

.quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.quick-action {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: var(--control);
    padding: 0 0.9rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--gray-700);
    font-size: var(--text-table);
    font-weight: 500;
    text-decoration: none;
}
.quick-action:hover {
    border-color: var(--royal-400);
    background: var(--royal-50);
    color: var(--royal-700);
    text-decoration: none;
}

/* ---- KPI card ---- */
.stat {
    position: relative;
}
.stat-label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-muted);
}
.stat-value {
    display: block;
    margin-top: 0.3rem;
    font-family: var(--font-display);
    font-size: 22px;
    font-weight: 700;
    /* A column of KPI cards should line up as readably as a column in
       a table, so the figures are tabular here too. text-align is not
       inherited from .num's right — a card reads left. */
    text-align: left;
    width: auto;
}
.stat-hint {
    display: block;
    margin-top: 0.2rem;
    font-size: 11px;
    color: var(--ink-muted);
}
/* Stretched link: the whole card is the target, but the accessible
   name still comes from one anchor rather than the card's full text. */
.stat-link {
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

/* ---- Footer ---- */
.app-footer {
    padding: 0.9rem 1rem calc(0.9rem + 4.5rem);
    border-top: 1px solid var(--border);
    color: var(--ink-muted);
    font-size: 11px;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    align-items: center;
}
.app-footer .spacer {
    flex: 1;
}

.bottom-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 30;
    display: flex;
    justify-content: space-around;
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding: 0.3rem 0 calc(0.3rem + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -1px 3px rgb(11 19 42 / 0.06);
}
.bottom-nav a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1rem;
    min-width: var(--touch);
    min-height: var(--touch);
    font-size: 10px;
    color: var(--ink-muted);
    text-decoration: none;
}
.bottom-nav a span {
    max-width: 4.5rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.bottom-nav a.active {
    color: var(--royal-700);
    font-weight: 600;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-family: var(--font-display);
    font-size: 16px;
    font-weight: 600;
    color: #fff;
    padding-bottom: 0.9rem;
    margin-bottom: 0.9rem;
    /* The one gold line in the working chrome, and it is a rule, not
       a surface — 7.6:1 on this navy. */
    border-bottom: 1px solid rgb(201 162 39 / 0.45);
    white-space: nowrap;
    overflow: hidden;
}
.sidebar-brand img {
    width: 28px;
    height: 28px;
    border-radius: 7px;
    flex-shrink: 0;
}
.sidebar-company {
    display: block;
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 500;
    /* Gold ink: 11.4:1 on navy, weight 500, short label. Exactly the
       job this colour is allowed to do. */
    color: var(--gold-ink);
    letter-spacing: 0.01em;
}

.sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.sidebar-nav a,
.sidebar-group summary {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.5rem 0.6rem;
    border-radius: var(--radius);
    color: var(--navy-200);
    text-decoration: none;
    font-size: var(--text-table);
    white-space: nowrap;
    overflow: hidden;
    cursor: pointer;
    list-style: none;
    transition: background 0.12s ease, color 0.12s ease;
}
.sidebar-nav a:hover,
.sidebar-group summary:hover {
    background: rgb(255 255 255 / 0.07);
    color: #fff;
    text-decoration: none;
}
.sidebar-nav a.active {
    background: var(--royal-600);
    color: #fff;
    font-weight: 600;
}
.sidebar-group summary::-webkit-details-marker {
    display: none;
}
.sidebar-group[open] > summary {
    color: #fff;
    font-weight: 600;
}
.sidebar-group a {
    padding-left: 1.9rem;
    font-size: 12.5px;
}

/* ---- Control Panel ---- */
.settings-module {
    margin-bottom: 1.75rem;
}
.settings-module > h2 {
    margin-bottom: 0.5rem;
}
.settings-group {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-soft);
    margin-bottom: 0.7rem;
    overflow: hidden;
}
.settings-group > summary {
    cursor: pointer;
    padding: 0.7rem 1rem;
    font-weight: 600;
    font-size: var(--text-base);
    list-style: none;
    min-height: var(--touch);
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.settings-group > summary::-webkit-details-marker {
    display: none;
}
.settings-group > summary::before {
    content: "▸";
    color: var(--ink-muted);
}
.settings-group[open] > summary {
    border-bottom: 1px solid var(--border);
}
.settings-group[open] > summary::before {
    content: "▾";
}
.settings-group table {
    border-radius: 0;
}
.row-cascaded-off td {
    opacity: 0.5;
}

/* ---- Wizard / line-item form (docs/FORM_PRESENTATION_SPEC.md) ----
   Both presentations are in the DOM; these rules pick one, and
   form-engine.js disables the inputs in whichever is hidden.
   Mobile-first: the wizard is the base, the table appears at 768px. */
.form-table {
    display: none;
}
.form-wizard {
    display: block;
}

.wizard-progress {
    font-size: var(--text-sm);
    color: var(--ink-muted);
    margin: 0 0 0.75rem;
}
.wizard-line {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem;
    margin-bottom: 0.75rem;
    background: var(--gray-50);
}
.wizard-line-no {
    margin: 0 0 0.5rem;
    font-size: 11px;
    font-weight: 600;
    color: var(--ink-muted);
}
.wizard-nav {
    display: flex;
    gap: 0.75rem;
    margin-top: 1rem;
}
.wizard-nav button {
    flex: 1;
    background: var(--surface);
    border-color: var(--royal-300);
    color: var(--royal-700);
}
.wizard-nav button:hover {
    background: var(--royal-50);
}
.more-options {
    margin-top: 0.5rem;
}
.more-options > summary {
    cursor: pointer;
    list-style: none;
    color: var(--royal-700);
    font-size: var(--text-table);
    font-weight: 500;
    min-height: var(--touch);
    display: flex;
    align-items: center;
}
.more-options > summary::-webkit-details-marker {
    display: none;
}
.more-options > summary::before {
    content: "＋ ";
}
.more-options[open] > summary::before {
    content: "－ ";
}

/* ============================================================
   TABLET (768px+): the navy rail appears, icon-only
   ============================================================ */
@media (min-width: 768px) {
    .app-shell {
        display: flex;
    }

    /* The icon rail — always visible from tablet up. */
    .sidebar-rail {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 0.2rem;
        width: var(--sidebar-rail);
        flex-shrink: 0;
        min-height: 100vh;
        padding: 0.9rem 0.4rem;
        background: var(--navy-950);
        border-right: 1px solid rgb(255 255 255 / 0.07);
        overflow-y: auto;
        overflow-x: hidden;
    }
    .rail-brand {
        display: block;
        margin-bottom: 0.6rem;
        padding-bottom: 0.6rem;
        border-bottom: 1px solid rgb(201 162 39 / 0.45);
        width: 100%;
        text-align: center;
    }
    .rail-brand img {
        width: 30px;
        height: 30px;
        border-radius: 8px;
    }
    .rail-nav {
        display: flex;
        flex-direction: column;
        gap: 0.15rem;
        width: 100%;
        align-items: center;
    }
    .rail-item {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        border-radius: var(--radius);
        font-size: 17px;
        line-height: 1;
        text-decoration: none;
        /* A left bar marks the current module, because the fill alone
           is easy to lose against the navy at a glance. */
        border-left: 3px solid transparent;
    }
    .rail-item:hover {
        background: rgb(255 255 255 / 0.09);
        text-decoration: none;
    }
    .rail-item.active {
        background: var(--royal-600);
        border-left-color: var(--gold-line);
    }

    /* The menu panel — the tree, its search box and Recent. */
    .sidebar {
        display: flex;
        flex-direction: column;
        width: var(--sidebar-w);
        flex-shrink: 0;
        min-height: 100vh;
        padding: 1.15rem 0.75rem;
        background: var(--navy-900);
        border-right: 1px solid rgb(255 255 255 / 0.06);
        overflow-y: auto;
        overflow-x: hidden;
    }
    .shell-main {
        padding: 1.75rem;
    }
    .bottom-nav {
        display: none;
    }
    .card-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    /* The rail carries the brand from here up, so the topbar stops
       repeating it and gives the space to the company and the user. */
    .topbar-brand {
        display: none;
    }
    .topbar {
        padding: 0 1.25rem;
    }
    .topbar-org .org-branch {
        display: inline;
    }
    .topbar-org .org-name {
        max-width: 22ch;
    }
    .topbar-search {
        display: block;
    }
    .topbar-user .who {
        display: block;
        text-align: right;
    }
    .app-footer {
        padding: 0.9rem 1.75rem;
    }
    .page-header h1 {
        font-size: var(--text-title-lg);
    }

    /* Tablet: the rail only. Rail plus panel is 312px, which is forty
       per cent of a 768px screen — on a device this size the content
       needs it more than the navigation does. The panel returns at
       the laptop tier. */
    .sidebar {
        display: none;
    }

    /* Table replaces the wizard from tablet up. */
    .form-table {
        display: block;
    }
    .form-wizard {
        display: none;
    }
    .form-header-grid {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 0 1rem;
    }
    .form-actions .primary {
        width: auto;
        min-width: 180px;
    }
    .form-table td input,
    .form-table td select {
        min-height: 34px;
        padding: 0.3rem 0.5rem;
        font-size: var(--text-table);
    }
}

/* ============================================================
   LAPTOP (1024px+): the standard layout, labels permanently on
   ============================================================ */
@media (min-width: 1024px) {
    /* Laptop: both columns — the rail for people who know where they
       are going, the panel for people looking for something. */
    .sidebar {
        display: flex;
    }
    .card-grid {
        grid-template-columns: repeat(4, 1fr);
    }
    .form-header-grid {
        grid-template-columns: repeat(3, 1fr);
    }
    .shell-main {
        padding: 2rem 2.25rem;
    }
}

/* ============================================================
   LARGE MONITOR (1440px+): more data per screen
   ============================================================ */
@media (min-width: 1440px) {
    .sidebar {
        width: 272px;
    }
    .shell-main {
        padding: 2.25rem 3rem;
    }
    .card-grid {
        grid-template-columns: repeat(5, 1fr);
    }

    /* A three-column report on a 27" monitor put the debit figure
       about 1100px from the account it belongs to. The eye has to
       carry the row across that gap, and on a long statement it stops
       managing it. Capping the measure is what a printed statement
       does and it is why they are readable. */
    .shell-main > table,
    .shell-main > .card > table {
        max-width: 1100px;
    }
}

/* ============================================================
   Touch devices: no hover, bigger targets
   `hover: none` is true on phones and tablets regardless of width,
   so this catches a 1024px tablet the width query would have
   treated as a laptop. Hover must never be the only way to reach
   something — docs/RESPONSIVE_SPEC.md §5.
   ============================================================ */
@media (hover: none) {
    /* Nothing here depends on hover any more — the rail and the panel
       are both permanently visible rather than hover-expanded — so
       what is left is target size, and undoing the hover affordances
       that a touch device can never trigger but can get stuck in. */
    .sidebar-nav a,
    .sidebar-group summary,
    .rail-item,
    .recent-list a {
        min-height: var(--touch);
    }
    button,
    .btn,
    select,
    input[type="submit"] {
        min-height: var(--touch);
    }
    tbody tr:hover {
        background: inherit;
    }
    .card:hover {
        box-shadow: var(--shadow-soft);
    }
}

/* ============================================================
   Reduced motion
   Somebody who has asked their system to stop animating things has
   usually asked for a reason — vestibular disorders make moving
   interfaces genuinely unpleasant. Honour it everywhere, not just
   where it was remembered.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================================
   Print — docs/FORM_PRESENTATION_SPEC.md §9
   Chrome the paper does not need is dropped; without this the first
   printed page is mostly navigation.
   ============================================================ */
@media print {
    .sidebar,
    .bottom-nav,
    .lang-switch,
    .form-wizard,
    .form-actions,
    .no-print {
        display: none !important;
    }
    body {
        background: #fff;
        color: #000;
    }
    .shell-main {
        padding: 0;
    }
    .card {
        box-shadow: none;
        border: 1px solid #999;
    }
    table {
        font-size: 11px;
    }
    thead th {
        background: #eee;
    }
    /* The gold rule survives onto paper — it is the one place an
       invoice is allowed to look like the brand. */
    h1 {
        border-bottom: 2px solid var(--gold-line-strong);
        padding-bottom: 0.35rem;
    }
}

/* ============================================================
   Direct Sales / Direct Purchase — the counter screens.

   A dense, keyboard-first strip for somebody standing at a counter
   with a customer in front of them, rather than a form filled in at
   leisure. Ported from ubos-dms's Direct Delivery Challan.

   One control height throughout, so a row of boxes reads as a row and
   not as a fence.
   ============================================================ */
.counter-head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem 1rem;
    align-items: flex-end;
    margin-bottom: 0.75rem;
}

.counter-head .field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 11rem;
    flex: 1 1 11rem;
}

.counter-head label {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* The customer's credit, beside their name. Gold is ornament and
   never goes behind a figure, so this is the plain surface colour with
   a rule — the numbers have to be read, not admired. */
.counter-credit {
    display: flex;
    align-items: baseline;
    gap: 0.4rem 0.75rem;
    flex-wrap: wrap;
    padding: 0.5rem 0.85rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-sunken);
}

.counter-credit-label {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.counter-credit-blocked {
    border-color: var(--danger-600);
    background: var(--danger-50);
}

/* ---- The entry strip ---- */
.counter-strip {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.counter-pick {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.counter-pick label {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* The picked item, said once and said large. The operator is looking
   at a customer, not at the screen, and a 13px line in a grid is not
   something you can confirm out of the corner of an eye. */
.counter-tile {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    padding: 0.65rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-sunken);
}

.counter-tile-name {
    font-family: var(--font-display);
    font-size: 18px;
    font-weight: 600;
    color: var(--ink);
    overflow-wrap: anywhere;
}

.counter-tile-stock {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-muted);
}

.counter-tile-stock strong {
    font-size: 18px;
    font-weight: 700;
    color: var(--ink);
}

.counter-inputs {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.5rem 0.75rem;
}

.counter-inputs .field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    /* Each box at least as wide as its own heading. The whole cause of
       misalignment on the screen this was ported from: a heading wider
       than its box overflows and shunts every cell after it sideways,
       leaving each label sitting over its neighbour's input — and
       somebody types the rate into the discount. */
    min-width: 7rem;
    flex: 1 1 7rem;
}

.counter-inputs label {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-muted);
    white-space: nowrap;
}

@media (min-width: 1024px) {
    .counter-strip {
        grid-template-columns: minmax(16rem, 1fr) minmax(14rem, 1fr);
        grid-template-areas:
            "pick tile"
            "inputs inputs";
        align-items: end;
    }
    .counter-pick { grid-area: pick; }
    .counter-tile { grid-area: tile; }
    .counter-inputs { grid-area: inputs; }
}


/* ============================================================
   Icons.

   Line icons on a 24×24 grid, stroked in currentColor — so the same
   icon is white in the navy rail and grey in a light menu, from one
   definition. See app/templates/_icons.html for why they are not
   emoji.
   ============================================================ */
.icon {
    flex: 0 0 auto;
    /* Optical centring against a 13px line. Icons sit a hair low
       against Latin text and correctly against Bangla, which has a
       lower x-height — this splits the difference. */
    vertical-align: -0.18em;
}

/* An icon beside a label needs a gap the label does not have to
   carry. Set on the containers rather than with a margin on .icon,
   so an icon used alone in a button is not pushed off-centre. */
.sidebar-nav a,
.sidebar-nav summary,
.sidebar-recent summary,
.bottom-nav a {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}

/* The rail is icons only, so they centre in their own box. */
.rail-item {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Slightly lighter stroke inside the navy rail: a 1.75 line that
   reads correctly on white looks heavy reversed out of dark navy,
   which is why reversed type is set lighter too. */
.sidebar-rail .icon,
.sidebar .icon {
    stroke-width: 1.6;
}


/* ============================================================
   Profile photo and its cropper.
   ============================================================ */
.avatar-large {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 96px;
    height: 96px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid var(--border);
    background: var(--surface-sunken);
}

/* Initials, not a stock silhouette. A made-up face is worse than no
   face; initials are always right about somebody. */
.avatar-initials {
    font-family: var(--font-display);
    font-size: 30px;
    font-weight: 700;
    color: var(--royal-700);
    letter-spacing: 0.02em;
}

.avatar-stage {
    position: relative;
    display: inline-block;
    max-width: 100%;
    line-height: 0;
    /* The checkerboard shows through a transparent PNG, so somebody
       can see what they are actually cropping rather than a white
       shape on white. */
    background:
        repeating-conic-gradient(var(--gray-100) 0% 25%, var(--surface) 0% 50%)
        50% / 16px 16px;
}

.avatar-stage img {
    max-width: min(420px, 100%);
    max-height: 420px;
    display: block;
    user-select: none;
    -webkit-user-drag: none;
}

.avatar-frame {
    position: absolute;
    box-sizing: border-box;
    border: 2px solid #fff;
    /* The shadow is what makes the crop legible: everything outside
       the square darkens, so the eye reads the square as the picture
       rather than as a box drawn on top of it. */
    box-shadow: 0 0 0 9999px rgb(0 0 0 / 0.45);
    cursor: move;
    touch-action: none;
}

.avatar-handle {
    position: absolute;
    right: -8px;
    bottom: -8px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--royal-600);
    cursor: nwse-resize;
    touch-action: none;
}


/* An unread notification. A left rule rather than a background tint:
   the row still has to be read, and a tinted row reads as disabled. */
tr.is-unread td:first-child {
    box-shadow: inset 3px 0 0 var(--royal-600);
    font-weight: 600;
}

.topbar-bell {
    position: relative;
}

/* A number a Control Panel switch carries — a fence radius, a cash
   limit. Narrow on purpose: a full-width box beside a switch reads as
   the main control, and it is not. */
.input-number {
    width: 8ch;
    text-align: right;
}

/* A plan's progress. Two marks, not one: the fill is how much has
   been done, the tick is how much of the period has gone. 40% at the
   halfway point and 40% at month-end are different sentences, and a
   bar with only the fill shows them identically. */
.plan-bar {
    position: relative;
    display: inline-block;
    width: 140px;
    height: 8px;
    vertical-align: middle;
    margin-inline-end: 8px;
    background: var(--border);
    border-radius: 4px;
    overflow: hidden;
}
.plan-fill {
    display: block;
    height: 100%;
    background: var(--royal-600);
}
.plan-fill.is-behind { background: var(--warning, #b8860b); }
.plan-fill.is-over { background: var(--danger, #b23c3c); }
.plan-elapsed {
    position: absolute;
    top: -2px;
    width: 2px;
    height: 12px;
    background: var(--text);
    opacity: 0.55;
}
.row-over td { box-shadow: inset 3px 0 0 var(--danger, #b23c3c); }

/* An internal note in a support thread. Marked, not merely tinted:
   somebody about to paste a line into an email needs to see which
   lines were never meant to leave the building. */
tr.is-internal td:first-child {
    box-shadow: inset 3px 0 0 var(--warning, #b8860b);
}

/* The restore command. Selectable, wrapping, monospace — it exists to
   be copied into a terminal, and a line that scrolls off the right
   edge is a line somebody copies half of. */
.restore-command {
    padding: 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
    font-size: 13px;
    white-space: pre-wrap;
    word-break: break-all;
    user-select: all;
}
