/* ==========================================================================
   QuoteFlow — design system
   --------------------------------------------------------------------------
   Same visual language as /purchase-requisition and /purchase-requisition-status:
   compact 11–12px type, gray-400 hairline borders, tinted section header bars,
   sticky table heads, summary tiles and a bordered pager strip.

   Written as plain CSS (not Tailwind utilities) on purpose: wwwroot/css/output.css
   is a PRE-BUILT Tailwind bundle, so any class it does not already contain renders
   with no style at runtime. Everything QuoteFlow needs lives here instead.
   ========================================================================== */

:root {
    --qf-blue: #2563eb;
    --qf-blue-dark: #1d4ed8;
    --qf-blue-ink: #1e40af;
    --qf-blue-soft: #dbeafe;
    --qf-blue-faint: #eff6ff;
    --qf-line: #9ca3af; /* gray-400 — the grid hairline */
    --qf-line-soft: #d1d5db; /* gray-300 — input + divider */
    --qf-bg: #f3f4f6;
    --qf-bg-soft: #f9fafb;
    --qf-ink: #111827;
    --qf-ink-2: #374151;
    --qf-muted: #6b7280;
    --qf-radius: 0.25rem;
}

/* ── Shell ───────────────────────────────────────────────────────────────── */

.qf {
    color: var(--qf-ink-2);
    font-size: 0.75rem;
    line-height: 1.45;
}

/* Full-height working surface under the 70px PageHeader. Below 1024px the lock is
   released so the PAGE scrolls instead of trapping content inside clipped boxes. */
.qf-shell {
    display: flex;
    flex-direction: column;
    min-height: calc(100vh - 70px);
    background: var(--qf-bg);
    padding: 0.5rem;
    gap: 0.5rem;
    min-width: 0;
}

/* The locked, app-like shell — no page scroll, panels scroll inside themselves — needs the screen
   to be tall as well as wide. It was gated on width alone, so a 14" or 16" laptop (1366x768,
   1920x1080 at 125%) got the lock at ~700-790px of usable height and every section below the
   first was pushed into a clipped box the page could not scroll to.
   Below this height the lock is released and the PAGE scrolls, which is the right behaviour on a
   short screen: reaching the rest of a form by scrolling the window always works. */
@media (min-width: 1024px) and (min-height: 820px) {
    .qf-shell {
        height: calc(100vh - 70px);
        min-height: 0;
        overflow: hidden;
        padding: 0.6rem;
    }
}

/* Wide but short: keep the roomier padding, keep the page scrolling. */
@media (min-width: 1024px) and (max-height: 819px) {
    .qf-shell {
        padding: 0.6rem;
    }

    /* Without this the panel keeps the internal scrollbar it needs only when the shell is
       locked, so the form would scroll inside a box AND the page would scroll behind it. */
    .qf-main {
        overflow: visible !important;
    }

    .qf-side {
        max-height: none;
    }
}

.qf-pane {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    min-width: 0;
    background: #fff;
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    padding: 0.6rem;
}

/* Scrollbars — matched to the .prs-scroll treatment on the requisition register. */
.qf-scroll {
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: #94a3b8 #f1f5f9;
}

    .qf-scroll::-webkit-scrollbar {
        width: 12px;
        height: 12px;
    }

    .qf-scroll::-webkit-scrollbar-track {
        background: #f1f5f9;
    }

    .qf-scroll::-webkit-scrollbar-thumb {
        background: #94a3b8;
        border-radius: 6px;
        border: 2px solid #f1f5f9;
    }

        .qf-scroll::-webkit-scrollbar-thumb:hover {
            background: #64748b;
        }

    .qf-scroll::-webkit-scrollbar-corner {
        background: #f1f5f9;
    }

/* ── Summary tiles ───────────────────────────────────────────────────────── */

.qf-tiles {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem;
}

@media (min-width: 640px) {
    .qf-tiles {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

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

    .qf-tiles-4 {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.qf-tile {
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    padding: 0.35rem 0.55rem;
    background: #fff;
    min-width: 0;
}

.qf-tile-label {
    font-size: 0.625rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--qf-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.qf-tile-value {
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1.3;
}

.qf-tile-sub {
    font-size: 0.625rem;
    color: var(--qf-muted);
}

/* A tile that doubles as a filter (the follow-up buckets). */
.qf-tile-click {
    cursor: pointer;
}

.qf-tile-on {
    outline: 2px solid var(--qf-blue);
}

.qf-t-slate {
    background: #f8fafc;
    border-color: #cbd5e1;
}

    .qf-t-slate .qf-tile-value {
        color: #334155;
    }

.qf-t-blue {
    background: #eff6ff;
    border-color: #93c5fd;
}

    .qf-t-blue .qf-tile-value {
        color: #1d4ed8;
    }

.qf-t-green {
    background: #f0fdf4;
    border-color: #86efac;
}

    .qf-t-green .qf-tile-value {
        color: #15803d;
    }

.qf-t-amber {
    background: #fffbeb;
    border-color: #fcd34d;
}

    .qf-t-amber .qf-tile-value {
        color: #b45309;
    }

.qf-t-red {
    background: #fef2f2;
    border-color: #fca5a5;
}

    .qf-t-red .qf-tile-value {
        color: #b91c1c;
    }

.qf-t-indigo {
    background: #eef2ff;
    border-color: #a5b4fc;
}

    .qf-t-indigo .qf-tile-value {
        color: #4338ca;
    }

.qf-t-purple {
    background: #faf5ff;
    border-color: #d8b4fe;
}

    .qf-t-purple .qf-tile-value {
        color: #7e22ce;
    }

.qf-t-teal {
    background: #f0fdfa;
    border-color: #5eead4;
}

    .qf-t-teal .qf-tile-value {
        color: #0f766e;
    }

/* ── Bordered section (fieldset look from the register) ──────────────────── */

.qf-sec {
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    background: #fff;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
}

.qf-sec-head {
    background: var(--qf-blue-soft);
    border-bottom: 1px solid var(--qf-line-soft);
    padding: 0.35rem 0.6rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
}

.qf-sec-title {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--qf-blue-ink);
    margin: 0;
}

.qf-sec-head-gray {
    background: #f3f4f6;
}

    /* A gray header carries a plain title, not the blue one — set here rather than by an
       inline style on every heading, so night mode can restate it in one place. */
    .qf-sec-head-gray .qf-sec-title {
        color: var(--qf-ink-2);
    }

.qf-sec-body {
    padding: 0.6rem;
    min-height: 0;
    min-width: 0;
}

.qf-fieldset {
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    padding: 0.5rem;
    min-width: 0;
}

.qf-legend {
    font-size: 0.7rem;
    font-weight: 600;
    padding: 0 0.4rem;
    color: var(--qf-ink-2);
}

/* ── Filter bar ──────────────────────────────────────────────────────────── */

.qf-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 0.8rem;
    font-size: 0.7rem;
}

.qf-radioset {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    padding: 0.1rem 0.4rem;
    background: #fff;
    white-space: nowrap;
}

    .qf-radioset > span:first-child {
        font-weight: 600;
    }

    .qf-radioset label {
        display: inline-flex;
        align-items: center;
        gap: 0.25rem;
        cursor: pointer;
        user-select: none;
    }

.qf-fld {
    display: flex;
    align-items: center;
    gap: 0.3rem;
}

    .qf-fld > label {
        font-weight: 600;
        flex-shrink: 0;
    }

/* ── Form controls ───────────────────────────────────────────────────────── */

.qf-input,
.qf-select,
.qf-textarea {
    border: 1px solid var(--qf-line-soft);
    background: #fff;
    padding: 0.22rem 0.45rem;
    font-size: 0.72rem;
    color: var(--qf-ink);
    border-radius: var(--qf-radius);
    outline: none;
    width: 100%;
    min-width: 0;
}

.qf-textarea {
    resize: vertical;
}

    .qf-input:focus,
    .qf-select:focus,
    .qf-textarea:focus {
        border-color: var(--qf-blue);
        box-shadow: 0 0 0 2px rgba(37, 122, 235, 0.18);
    }

    .qf-input[readonly],
    .qf-input:disabled,
    .qf-select:disabled,
    .qf-textarea:disabled {
        background: #f3f4f6;
        color: var(--qf-muted);
        cursor: not-allowed;
    }

/* Key fields carry the same yellow tint as the requisition entry screen. */
.qf-input-key {
    background: #fefce8;
}

    .qf-input-key[readonly] {
        background: #fefce8;
        color: var(--qf-ink);
        cursor: default;
    }

.qf-input-auto {
    background: #fefce8;
    font-style: italic;
    color: var(--qf-muted);
}

.qf-w-auto {
    width: auto;
}

/* Entry form: label on the left, control filling the rest — three columns wide. */
.qf-form-grid {
    display: grid;
    grid-template-columns: repeat(1, minmax(0, 1fr));
    gap: 0.4rem 1.4rem;
}

@media (min-width: 640px) {
    .qf-form-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1100px) {
    .qf-form-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .qf-form-grid-2 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.qf-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
}

    .qf-row > label {
        width: 96px;
        flex-shrink: 0;
        font-weight: 500;
        font-size: 0.7rem;
    }

    .qf-row.qf-row-wide > label {
        width: 120px;
    }

    .qf-row > .qf-input,
    .qf-row > .qf-select,
    .qf-row > .qf-textarea,
    .qf-row > .qf-ctl {
        flex: 1;
        min-width: 0;
    }

/* Sized in em, not rem: it has to stay readable beside a 0.62rem caption on the
   till and beside a full-size form label, and one fixed size cannot do both. */
.qf-req {
    color: #ef4444;
    margin-left: 0.1rem;
    font-size: 1.25em;
    font-weight: 700;
    line-height: 1;
}

/* Input with a trailing action button (the "pick" pattern from the PR screen). */
.qf-pick {
    position: relative;
    flex: 1;
    min-width: 0;
}

    .qf-pick .qf-input {
        padding-right: 1.9rem;
    }

.qf-pick-btn {
    position: absolute;
    top: 0;
    right: 0;
    height: 100%;
    width: 1.7rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--qf-blue);
    color: #fff;
    border: 1px solid var(--qf-blue);
    border-radius: 0 var(--qf-radius) var(--qf-radius) 0;
    cursor: pointer;
    font-weight: 700;
}

    .qf-pick-btn:hover {
        background: var(--qf-blue-dark);
    }

/* ── Buttons ─────────────────────────────────────────────────────────────── */

.qf-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.6rem;
    font-size: 0.7rem;
    font-weight: 500;
    border-radius: var(--qf-radius);
    border: 1px solid transparent;
    cursor: pointer;
    white-space: nowrap;
    line-height: 1.4;
    background: #fff;
    color: var(--qf-ink-2);
    transition: background-color 0.12s ease;
}

    .qf-btn:disabled {
        opacity: 0.45;
        cursor: not-allowed;
    }

/* An anchor styled as a button has no disabled state of its own — this is how one is shown as
   unavailable, alongside dropping its href so it cannot be followed. */
.qf-btn-off {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;
}

.qf-btn-xs {
    padding: 0.1rem 0.4rem;
    font-size: 0.625rem;
}

.qf-btn-lg {
    padding: 0.4rem 0.9rem;
    font-size: 0.78rem;
}

.qf-btn-primary {
    background: var(--qf-blue);
    border-color: var(--qf-blue-dark);
    color: #fff;
}

    .qf-btn-primary:hover:not(:disabled) {
        background: var(--qf-blue-dark);
    }

.qf-btn-success {
    background: #16a34a;
    border-color: #15803d;
    color: #fff;
}

    .qf-btn-success:hover:not(:disabled) {
        background: #15803d;
    }

.qf-btn-danger {
    background: #ef4444;
    border-color: #dc2626;
    color: #fff;
}

    .qf-btn-danger:hover:not(:disabled) {
        background: #dc2626;
    }

.qf-btn-warn {
    background: #f59e0b;
    border-color: #d97706;
    color: #fff;
}

    .qf-btn-warn:hover:not(:disabled) {
        background: #d97706;
    }

.qf-btn-neutral {
    background: #6b7280;
    border-color: #4b5563;
    color: #fff;
}

    .qf-btn-neutral:hover:not(:disabled) {
        background: #4b5563;
    }

.qf-btn-purple {
    background: #7c3aed;
    border-color: #6d28d9;
    color: #fff;
}

    .qf-btn-purple:hover:not(:disabled) {
        background: #6d28d9;
    }

.qf-btn-teal {
    background: #0d9488;
    border-color: #0f766e;
    color: #fff;
}

    .qf-btn-teal:hover:not(:disabled) {
        background: #0f766e;
    }

.qf-btn-wa {
    background: #25d366;
    border-color: #1ea952;
    color: #06331a;
    font-weight: 600;
}

    .qf-btn-wa:hover:not(:disabled) {
        background: #1ea952;
        color: #fff;
    }

.qf-btn-ghost {
    background: #fff;
    border-color: var(--qf-line-soft);
    color: var(--qf-ink-2);
}

    .qf-btn-ghost:hover:not(:disabled) {
        background: #f3f4f6;
    }

.qf-btn-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
}

/* A slimmer action row. The label stays the same size — only the vertical padding and the leading
   go, so the strip of buttons above a form costs less height without the text getting harder to
   read or the hit target narrowing sideways. */
.qf-btn-row-slim .qf-btn {
    padding-top: 0.08rem;
    padding-bottom: 0.08rem;
    line-height: 1.25;
}

    .qf-btn-row-slim .qf-btn i {
        font-size: 0.68rem;
        line-height: 1;
    }

/* ── Tables ──────────────────────────────────────────────────────────────── */

.qf-grid {
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    max-width: 100%;
    min-width: 0;
    overflow: auto;
    max-height: 46vh;
    min-height: 140px;
}

@media (min-width: 1024px) {
    .qf-grid {
        max-height: none;
        flex: 1;
        min-height: 150px;
    }
}

.qf-grid-fixed-sm {
    max-height: 30vh;
    min-height: 110px;
    flex: none;
}

/* ── Statement rows ───────────────────────────────────────────────────────
   A profit and loss is one table that has to read as four blocks. Three row
   kinds do it: a section heading, a subtotal that closes the section, and the
   conclusion drawn from the sections above it. Shading rather than rules —
   the table already has borders, and more lines would only make it busier. */

.qf-pl-head td {
    background: var(--qf-bg-soft);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 0.66rem;
    color: var(--qf-ink-2);
}

.qf-pl-total td {
    background: var(--qf-bg);
}

/* Gross profit, and anything else the reader is meant to stop at. Heavier than
   a subtotal on purpose: it is an answer, not another running sum. */
.qf-pl-result td {
    background: var(--qf-blue-faint);
    border-top: 2px solid var(--qf-blue);
    border-bottom: 2px solid var(--qf-blue);
    color: var(--qf-blue-ink);
}

/* Rows on white, the way the POS basket reads. Opt-in per screen rather than a
   rule on .qf-table: most grids sit in a transparent fieldset and are meant to
   pick up whatever is behind them, and only the ones that are a LIST of things
   want to read as paper. Painted on the row, not the cell, so the hover
   highlight still lands on top of it. */
.qf-grid-paper .qf-table tbody tr {
    background: #fff;
}

    .qf-grid-paper .qf-table tbody tr:hover {
        background: var(--qf-blue-faint);
    }

.qf-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.7rem;
}

    .qf-table thead {
        position: sticky;
        top: 0;
        z-index: 10;
    }

    .qf-table thead th {
        background: #e5e7eb;
        border: 1px solid var(--qf-line);
        padding: 0.3rem 0.5rem;
        font-weight: 600;
        text-align: left;
        white-space: nowrap;
        color: var(--qf-ink);
    }

    .qf-table tbody td {
        border: 1px solid var(--qf-line);
        padding: 0.25rem 0.5rem;
        vertical-align: middle;
    }

    .qf-table tbody tr:hover {
        background: var(--qf-blue-faint);
    }

    .qf-table tbody tr.qf-sel,
    .qf-table tbody tr.qf-sel:hover {
        background: var(--qf-blue-soft);
    }

    .qf-table tfoot {
        position: sticky;
        bottom: 0;
        z-index: 10;
    }

        .qf-table tfoot td {
            background: #e5e7eb;
            border: 1px solid var(--qf-line);
            padding: 0.3rem 0.5rem;
            font-weight: 600;
        }

.qf-th-sort {
    cursor: pointer;
    user-select: none;
}

    .qf-th-sort:hover {
        background: #d1d5db;
    }

/* Inline column-search row under the header. */
.qf-searchrow td {
    background: #dbeafe;
    padding: 0.15rem 0.25rem !important;
}

.qf-colsearch {
    width: 100%;
    background: transparent;
    border: 0;
    font-size: 0.68rem;
    outline: none;
    color: var(--qf-ink);
}

    .qf-colsearch::placeholder {
        color: #64748b;
    }

.qf-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.qf-mid {
    text-align: center;
}

.qf-nowrap {
    white-space: nowrap;
}

.qf-strong {
    font-weight: 600;
}

.qf-link {
    color: var(--qf-blue-dark);
    font-weight: 600;
    cursor: pointer;
}

    .qf-link:hover {
        text-decoration: underline;
    }

.qf-empty {
    text-align: center;
    color: #9ca3af;
    padding: 1.6rem 0.5rem;
}

/* Cell-level editors inside a grid row (quotation / invoice item lines). */
.qf-cell-input {
    width: 100%;
    border: 1px solid transparent;
    background: transparent;
    font-size: 0.7rem;
    padding: 0.1rem 0.25rem;
    outline: none;
    color: var(--qf-ink);
    border-radius: 2px;
}

    .qf-cell-input:hover {
        border-color: var(--qf-line-soft);
    }

    .qf-cell-input:focus {
        border-color: var(--qf-blue);
        background: #fff;
    }

    .qf-cell-input.qf-num {
        text-align: right;
    }

/* ── Status chips ────────────────────────────────────────────────────────── */

.qf-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    font-size: 0.625rem;
    font-weight: 600;
    white-space: nowrap;
    background: #f3f4f6;
    color: #4b5563;
}

.qf-chip-draft {
    background: #f1f5f9;
    color: #475569;
}

.qf-chip-sent {
    background: #dbeafe;
    color: #1d4ed8;
}

.qf-chip-viewed {
    background: #e0e7ff;
    color: #4338ca;
}

.qf-chip-accepted {
    background: #dcfce7;
    color: #15803d;
}

.qf-chip-rejected {
    background: #fee2e2;
    color: #b91c1c;
}

.qf-chip-changes {
    background: #ffedd5;
    color: #c2410c;
}

.qf-chip-expired {
    background: #f3f4f6;
    color: #6b7280;
}

.qf-chip-converted {
    background: #ccfbf1;
    color: #0f766e;
}

.qf-chip-paid {
    background: #dcfce7;
    color: #15803d;
}

.qf-chip-partial {
    background: #fef9c3;
    color: #a16207;
}

.qf-chip-due {
    background: #fff7ed;
    color: #c2410c;
}

/* Which price list the buyer is on. Deliberately NOT one of the status colours: a tier is a
   property of the customer, not a state the document moves through. */
.qf-chip-tier {
    background: #ede9fe;
    color: #5b21b6;
}

.qf-chip-overdue {
    background: #fee2e2;
    color: #b91c1c;
}

.qf-chip-upcoming {
    background: #e0f2fe;
    color: #0369a1;
}

.qf-chip-done {
    background: #dcfce7;
    color: #15803d;
}

/* Channel pills for follow-ups. */
.qf-ch {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-size: 0.625rem;
    font-weight: 600;
    padding: 0.05rem 0.35rem;
    border-radius: var(--qf-radius);
}

.qf-ch-wa {
    background: #dcfce7;
    color: #15803d;
}

.qf-ch-em {
    background: #dbeafe;
    color: #1d4ed8;
}

.qf-ch-cl {
    background: #ede9fe;
    color: #6d28d9;
}

/* ── Pager strip ─────────────────────────────────────────────────────────── */

.qf-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    row-gap: 0.25rem;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    background: var(--qf-bg-soft);
    font-size: 0.7rem;
    flex-shrink: 0;
}

@media (min-width: 1024px) {
    .qf-pager {
        flex-wrap: nowrap;
        justify-content: space-between;
    }
}

.qf-pager-info {
    color: var(--qf-muted);
    padding: 0.25rem 0.7rem;
    white-space: nowrap;
}

.qf-pager-btns {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.2rem;
    padding: 0.25rem 0.7rem;
    flex: 1;
    justify-content: center;
}

.qf-pg {
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    background: #fff;
    cursor: pointer;
}

    .qf-pg:hover:not(:disabled) {
        background: #f3f4f6;
    }

    .qf-pg:disabled {
        opacity: 0.4;
        cursor: not-allowed;
    }

    .qf-pg.qf-pg-on {
        background: var(--qf-blue);
        border-color: var(--qf-blue);
        color: #fff;
    }

/* ── Two-panel entry screen (left list + right form) ─────────────────────── */

.qf-split {
    display: flex;
    min-height: 0;
    flex: 1;
    gap: 0;
}

.qf-side {
    width: 290px;
    flex-shrink: 0;
    background: #fff;
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    display: flex;
    flex-direction: column;
    position: relative;
    overflow: hidden;
    transition: width 0.2s ease;
    min-height: 0;
}

    .qf-side.qf-side-off {
        width: 2.2rem;
    }

/* Sits just inside the panel's right edge, not hanging off it: .qf-side clips its
   overflow so the collapse animation stays clean, and a hanging button would be cut. */
.qf-side-toggle {
    position: absolute;
    right: 0.25rem;
    top: 0.4rem;
    z-index: 20;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 999px;
    background: var(--qf-blue);
    color: #fff;
    border: none;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
    font-size: 0.65rem;
}

    .qf-side-toggle:hover {
        background: var(--qf-blue-dark);
    }

.qf-side-filter {
    /* Right padding leaves room for the collapse button pinned in the corner. */
    padding: 0.6rem 2rem 0.6rem 0.6rem;
    border-bottom: 1px solid var(--qf-line-soft);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.qf-side-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    border-bottom: 1px solid var(--qf-line-soft);
}

.qf-side-item {
    padding: 0.35rem 0.55rem;
    border-bottom: 1px solid #f3f4f6;
    cursor: pointer;
    font-size: 0.7rem;
}

    .qf-side-item:hover {
        background: #f3f4f6;
    }

    .qf-side-item.qf-sel {
        background: var(--qf-blue);
        color: #fff;
    }

        .qf-side-item.qf-sel .qf-side-item-sub {
            color: #dbeafe;
        }

.qf-side-item-main {
    font-weight: 600;
}

.qf-side-item-sub {
    color: var(--qf-muted);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.3rem;
}

.qf-side-foot {
    padding: 0.35rem 0.5rem;
    background: #f3f4f6;
    font-size: 0.65rem;
    color: var(--qf-muted);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.3rem;
    flex-shrink: 0;
}

.qf-main {
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    margin-left: 0.5rem;
    gap: 0.5rem;
}

/* Children of the scrolling pane keep their natural height. With flex-shrink left at 1 the
   form grid was squeezed to fit the pane instead of making it scroll — on a locked shell
   (big screen, no page scroll) its bottom rows spilled out of the grid and were painted
   under the section that follows, with no scrollbar anywhere to reach them. */
.qf-main > * {
    flex-shrink: 0;
}

@media (max-width: 1023px) {
    .qf-split {
        flex-direction: column;
    }

    .qf-side {
        width: 100%;
        max-height: 45vh;
    }

        .qf-side.qf-side-off {
            width: 100%;
            max-height: 2.6rem;
        }

    .qf-main {
        margin-left: 0;
        margin-top: 0.75rem;
    }
}

/* ── Dashboard bits ──────────────────────────────────────────────────────── */

.qf-cols {
    display: grid;
    grid-template-columns: repeat(1, minmax(0, 1fr));
    gap: 0.6rem;
    min-height: 0;
}

@media (min-width: 1024px) {
    .qf-cols-2 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .qf-cols-3 {
        grid-template-columns: 1fr 1fr 1fr;
    }

    .qf-cols-21 {
        grid-template-columns: 2fr 1fr;
    }

    /* The mirror of -21, for a screen whose WIDE content is on the right: the variant matrix
       is a seven-column table and was being given the 1fr third of the page, so only two of
       its columns were ever on screen. Used by /qf-variants. */
    .qf-cols-12 {
        grid-template-columns: 1fr 2fr;
    }
}

.qf-quick {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem;
}

@media (min-width: 640px) {
    .qf-quick {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.qf-quick-btn {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.7rem;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    background: #fff;
    cursor: pointer;
    text-align: left;
    color: var(--qf-ink-2);
    font-size: 0.72rem;
    font-weight: 600;
    transition: border-color 0.12s, background 0.12s;
}

    .qf-quick-btn:hover {
        border-color: var(--qf-blue);
        background: var(--qf-blue-faint);
    }

.qf-quick-ico {
    width: 1.8rem;
    height: 1.8rem;
    border-radius: var(--qf-radius);
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 0.85rem;
    flex-shrink: 0;
}

.qf-ico-blue {
    background: var(--qf-blue);
}

.qf-ico-green {
    background: #16a34a;
}

.qf-ico-purple {
    background: #7c3aed;
}

.qf-ico-amber {
    background: #f59e0b;
}

.qf-ico-teal {
    background: #0d9488;
}

.qf-ico-red {
    background: #ef4444;
}

/* Simple CSS bar strip — the dashboard "trend" without pulling in a chart lib. */
.qf-bars {
    display: flex;
    align-items: flex-end;
    gap: 0.4rem;
    height: 92px;
    padding: 0.3rem 0.2rem 0;
}

.qf-bar-col {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    min-width: 0;
}

.qf-bar {
    width: 100%;
    background: linear-gradient(180deg, #60a5fa 0%, #2563eb 100%);
    border-radius: 2px 2px 0 0;
    min-height: 2px;
}

.qf-bar-alt {
    background: linear-gradient(180deg, #6ee7b7 0%, #059669 100%);
}

.qf-bar-lbl {
    font-size: 0.6rem;
    color: var(--qf-muted);
    white-space: nowrap;
}

/* Legend key. Shares .qf-bar so it can never drift from the bars it explains. */
.qf-swatch {
    width: 9px;
    height: 9px;
    min-height: 0;
    border-radius: 2px;
    display: inline-block;
}

/* Progress meter — paid vs due on an invoice. */
.qf-meter {
    height: 6px;
    border-radius: 999px;
    background: #e5e7eb;
    overflow: hidden;
    min-width: 60px;
}

.qf-meter-fill {
    height: 100%;
    background: #16a34a;
}

.qf-meter-fill-warn {
    background: #f59e0b;
}

.qf-meter-fill-bad {
    background: #ef4444;
}

/* ── Totals block (quotation / invoice footer) ───────────────────────────── */

.qf-totals {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 260px;
    font-size: 0.72rem;
}

.qf-total-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.15rem 0;
}

    .qf-total-line span:last-child {
        font-variant-numeric: tabular-nums;
        font-weight: 600;
    }

.qf-total-grand {
    border-top: 2px solid var(--qf-line);
    margin-top: 0.2rem;
    padding-top: 0.3rem;
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--qf-blue-ink);
}

/* A typed figure sitting in the totals column — shipping and adjustment. Sized to line up
   with the computed amounts above it, so the column still reads as one stack of numbers. */
.qf-total-input {
    width: 110px;
    flex: none;
    text-align: right;
    font-size: 0.72rem;
    padding: 0.1rem 0.3rem;
    font-variant-numeric: tabular-nums;
}

/* ── Line discount: one figure plus the switch that says how to read it ──── */

.qf-disc-cell {
    display: flex;
    align-items: center;
    gap: 0.15rem;
}

    .qf-disc-cell .qf-cell-input {
        flex: 1;
        min-width: 0;
    }

.qf-disc-mode {
    flex: none;
    min-width: 30px;
    border: 1px solid var(--qf-line);
    background: #f1f5f9;
    color: #475569;
    font-size: 0.6rem;
    font-weight: 700;
    line-height: 1;
    padding: 0.2rem 0.25rem;
    border-radius: 3px;
    cursor: pointer;
}

    .qf-disc-mode:hover:not(:disabled) {
        background: #e2e8f0;
    }

    /* A fixed discount is the unusual one, so it is the one that looks different — a glance
       down the column shows which lines are taking an amount off rather than a percentage. */
    .qf-disc-mode.qf-disc-fixed {
        background: #fef3c7;
        border-color: #fcd34d;
        color: #92400e;
    }

    .qf-disc-mode:disabled {
        cursor: default;
        opacity: 0.55;
    }

/* ── Line type badge ─────────────────────────────────────────────────────── */

.qf-tag {
    display: inline-block;
    font-size: 0.58rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    padding: 0.12rem 0.3rem;
    border-radius: 2px;
    vertical-align: middle;
}

.qf-tag-prd {
    background: #e0e7ff;
    color: #3730a3;
}

.qf-tag-svc {
    background: #ccfbf1;
    color: #0f766e;
}

.qf-tag-cus {
    background: #fae8ff;
    color: #86198f;
}

/* ── Tabs ────────────────────────────────────────────────────────────────── */

.qf-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem;
    border-bottom: 1px solid var(--qf-line-soft);
}

.qf-tab {
    padding: 0.3rem 0.7rem;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--qf-muted);
    background: transparent;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: var(--qf-radius) var(--qf-radius) 0 0;
    cursor: pointer;
    margin-bottom: -1px;
}

    .qf-tab:hover {
        color: var(--qf-ink);
        background: #f3f4f6;
    }

    .qf-tab.qf-tab-on {
        background: #fff;
        color: var(--qf-blue-ink);
        border-color: var(--qf-line-soft);
        border-bottom: 1px solid #fff;
    }

/* ── Modal ───────────────────────────────────────────────────────────────── */

.qf-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.75rem;
}

.qf-modal {
    background: #fff;
    border-radius: 0.5rem;
    box-shadow: 0 20px 45px rgba(0, 0, 0, 0.3);
    width: min(980px, 96vw);
    max-height: 88vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.qf-modal-sm {
    width: min(560px, 96vw);
}

.qf-modal-head {
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--qf-line-soft);
    background: #f3f4f6;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-shrink: 0;
    flex-wrap: wrap;
}

.qf-modal-title {
    font-size: 0.8rem;
    font-weight: 700;
    margin: 0;
}

.qf-modal-x {
    margin-left: auto;
    background: none;
    border: none;
    font-size: 1.4rem;
    line-height: 1;
    color: #ef4444;
    cursor: pointer;
    width: 1.8rem;
    height: 1.8rem;
    border-radius: var(--qf-radius);
}

    .qf-modal-x:hover {
        background: #fef2f2;
    }

.qf-modal-body {
    padding: 0.75rem;
    overflow: auto;
    min-height: 0;
}

.qf-modal-foot {
    padding: 0.6rem 0.75rem;
    border-top: 1px solid var(--qf-line-soft);
    background: var(--qf-bg-soft);
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
    flex-wrap: wrap;
}

/* ── Misc helpers ────────────────────────────────────────────────────────── */

.qf-muted {
    color: var(--qf-muted);
}

/* Money semantics. Classes rather than inline styles, because an inline colour is the one
   thing night mode cannot restyle — it always wins over a stylesheet. */
.qf-pos {
    color: #15803d; /* received / settled */
}

.qf-neg {
    color: #b91c1c; /* outstanding / overdue */
}

.qf-warn {
    color: #b45309; /* deductions */
}

.qf-ink-danger {
    color: #ef4444;
}

.qf-mini {
    font-size: 0.65rem;
}

.qf-push {
    margin-left: auto;
}

.qf-hr {
    border: 0;
    border-top: 1px solid var(--qf-line-soft);
    margin: 0.5rem 0;
}

.qf-note {
    border: 1px solid #bfdbfe;
    background: #eff6ff;
    color: #1e40af;
    border-radius: var(--qf-radius);
    padding: 0.4rem 0.6rem;
    font-size: 0.68rem;

    /* A note can carry a server message it did not write — a SQL error naming a
       column, a constraint, a path — and none of those break on a space. Without
       this, one of them widens the panel it sits in instead of wrapping. */
    overflow-wrap: anywhere;
}

.qf-note-warn {
    border-color: #fcd34d;
    background: #fffbeb;
    color: #92400e;
}

/* A save that worked. Green because "saved" and "could not save" must not look alike at a
   glance — the one moment a user checks this strip is the moment they are unsure. */
.qf-note-ok {
    border-color: #86efac;
    background: #f0fdf4;
    color: #166534;
}

.qf-flex {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}

.qf-stack {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 0;
}

.qf-avatar {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 999px;
    background: #c7d2fe;
    color: #3730a3;
    font-size: 0.6rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* Wide item grids scroll inside their own box rather than pushing the page. */
.qf-table-wide {
    min-width: 1180px;
}

.qf-table-xwide {
    min-width: 1500px;
}

/* The variant matrix is the one grid read as a whole — the point is comparing a size's
   stock and price at a glance, not scrolling between two halves of one row. It may
   shrink to fit; below this floor it scrolls like the other wide tables. */
.qf-table-variants {
    min-width: 620px;
}

/* ── Public quotation page (no sidebar, customer-facing) ─────────────────── */

.qf-public {
    min-height: 100vh;
    background: #eef2f7;
    padding: 1.5rem 1rem;
}

.qf-paper {
    width: min(900px, 100%);
    margin: 0 auto;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: 0.5rem;
    box-shadow: 0 6px 24px rgba(15, 23, 42, 0.08);
    overflow: hidden;
}

.qf-paper-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.2rem 1.4rem;
    border-bottom: 3px solid var(--qf-blue);
}

.qf-paper-body {
    padding: 1.2rem 1.4rem;
}

.qf-paper-foot {
    padding: 1rem 1.4rem;
    background: var(--qf-bg-soft);
    border-top: 1px solid var(--qf-line-soft);
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

/* ── Quotation attachments (public page) ─────────────────────────────────── */

.qf-attach {
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    margin-bottom: 0.6rem;
    overflow: hidden;
}

.qf-attach-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    padding: 0.4rem 0.6rem;
    background: var(--qf-bg-soft);
    border-bottom: 1px solid var(--qf-line-soft);
    font-size: 0.72rem;
}

/* Tall enough that a page of A4 is actually readable without scrolling the frame to death,
   but capped against the viewport so the quotation itself never scrolls out of reach. */
.qf-attach-frame {
    display: block;
    width: 100%;
    height: min(75vh, 780px);
    border: 0;
    background: #fff;
}

.qf-attach-img {
    display: block;
    width: 100%;
    height: auto;
    max-height: 75vh;
    object-fit: contain;
    background: #fff;
}

@media (max-width: 640px) {
    /* Mobile browsers mostly refuse to render a PDF in an iframe — it comes out blank or as a
       download prompt. Rather than leave a dead grey box under the file, the frame is dropped
       and the Download / Open buttons in the header carry the job. */
    .qf-attach-frame {
        display: none;
    }
}

.qf-brand {
    width: 46px;
    height: 46px;
    border-radius: 0.4rem;
    background: var(--qf-blue);
    color: #fff;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9rem;
    flex-shrink: 0;
}

/* ── Template designer (/qf-template) ────────────────────────────────────────
   GrapesJS brings its own chrome — panels, block palette, layer tree. All this sheet does is
   give it a bounded stage inside the QuoteFlow shell, because the editor sizes itself to its
   container and would otherwise collapse to nothing. */

.qf-tpl-wrap {
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    overflow: hidden;
    background: #fff;
    /* Tall enough to lay out a page against, short enough that the toolbar stays on screen. */
    height: calc(100vh - 250px);
    min-height: 460px;
}

.qf-tpl-canvas {
    height: 100%;
}

/* The editor's own font is a size larger than everything around it, which makes the designer
   feel like a different application bolted on. */
.qf-tpl-wrap .gjs-one-bg,
.qf-tpl-wrap .gjs-block,
.qf-tpl-wrap .gjs-title,
.qf-tpl-wrap .gjs-label {
    font-size: 0.72rem;
}

.qf-tpl-wrap .gjs-block {
    min-height: auto;
    padding: 0.5rem 0.35rem;
}

/* Sample render inside the preview modal. Sandboxed, so it needs its own white ground —
   nothing from this page reaches inside it. */
.qf-tpl-preview {
    width: 100%;
    height: 70vh;
    border: 0;
    background: #fff;
    display: block;
}

/* The designed quotation on the public page. Height is set by the frame's own onload handler
   once its content has laid out — the value here only covers the moment before that runs. */
.qf-doc-frame {
    width: 100%;
    height: 900px;
    border: 0;
    display: block;
    background: #fff;
}

/* ── Ready-made design gallery (/qf-template) ────────────────────────────────
   Each card holds the real design rendered at full size and scaled down, rather than a
   screenshot: a change to a preset shows up in its card automatically, and there is no image
   to keep in step. */

.qf-gal {
    display: grid;
    grid-template-columns: repeat(auto-fit, 250px);
    justify-content: center;
    gap: 0.8rem;
}

.qf-gal-card {
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    overflow: hidden;
    background: #fff;
    cursor: pointer;
    transition: border-color 0.12s, box-shadow 0.12s;
}

    .qf-gal-card:hover {
        border-color: var(--qf-blue);
        box-shadow: 0 4px 16px rgba(37, 122, 235, 0.2);
    }

.qf-gal-shot {
    height: 215px;
    overflow: hidden;
    background: #fff;
    border-bottom: 1px solid var(--qf-line-soft);
}

/* Rendered at a real page width, then shrunk to the card. pointer-events is off so the click
   lands on the card and not inside the document being previewed. */
.qf-gal-frame {
    width: 780px;
    height: 660px;
    border: 0;
    transform: scale(0.32);
    transform-origin: top left;
    pointer-events: none;
}

.qf-gal-body {
    padding: 0.45rem 0.6rem;
}

/* ── Public invoice: the balance band ────────────────────────────────────────
   Sits between the letterhead and the document because it answers the only question most
   customers open the link for. Deliberately outside the designed frame, so it is there whether
   or not somebody's own layout includes a balance field. */

.qf-bill-band {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    padding: 0.6rem 0.9rem;
    background: var(--qf-blue-faint);
    border-bottom: 1px solid var(--qf-line-soft);
    color: var(--qf-blue-ink);
}

.qf-bill-amount {
    font-size: 1.35rem;
    font-weight: 700;
    line-height: 1.2;
}

.qf-bill-late {
    background: #fef2f2;
    color: #b91c1c;
}

.qf-bill-clear {
    background: #f0fdf4;
    color: #15803d;
}

/* ── Calendar ────────────────────────────────────────────────────────────────
   A month is seven columns and up to six rows, and the rows have to share the
   height that is left after the toolbar — so the grid is `1fr` rows inside a
   flexed pane rather than fixed-height cells. Each cell scrolls its own events;
   a cell that grows with its contents makes one busy Tuesday push January off
   the bottom of the screen. */

.qf-cal {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    border: 1px solid var(--qf-line);
    border-radius: var(--qf-radius);
    background: #fff;
    overflow: hidden;
}

.qf-cal-dow {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    background: var(--qf-blue-faint);
    border-bottom: 1px solid var(--qf-line);
}

.qf-cal-dow > div {
    padding: 0.3rem 0.4rem;
    font-size: 0.68rem;
    font-weight: 600;
    color: var(--qf-blue-ink);
    text-align: center;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.qf-cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    grid-auto-rows: minmax(84px, 1fr);
    flex: 1;
    min-height: 0;
}

.qf-cal-cell {
    border-right: 1px solid var(--qf-line-soft);
    border-bottom: 1px solid var(--qf-line-soft);
    padding: 0.2rem;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
    overflow: hidden;
    background: #fff;
}

.qf-cal-cell:nth-child(7n) {
    border-right: 0;
}

/* Days from the neighbouring month. Kept visible rather than blanked — a month
   view that stops mid-week hides the deadline that falls on the 1st. */
.qf-cal-out {
    background: var(--qf-bg-soft);
}

.qf-cal-out .qf-cal-num {
    color: #9ca3af;
}

.qf-cal-today {
    background: var(--qf-blue-faint);
    box-shadow: inset 0 0 0 2px var(--qf-blue);
}

.qf-cal-sel {
    box-shadow: inset 0 0 0 2px var(--qf-blue-dark);
}

.qf-cal-num {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--qf-ink);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.2rem;
    flex: none;
}

.qf-cal-count {
    font-size: 0.6rem;
    color: var(--qf-muted);
    font-weight: 500;
}

.qf-cal-events {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    overflow-y: auto;
    min-height: 0;
}

/* One event inside a day cell. Deliberately flatter than .qf-chip: a cell holds
   five of these at 11px, so they have to read as a colour band, not a badge. */
.qf-ev {
    display: block;
    width: 100%;
    text-align: left;
    font-size: 0.63rem;
    line-height: 1.25;
    padding: 0.05rem 0.25rem;
    border-radius: 2px;
    border: 0;
    border-left: 3px solid var(--qf-muted);
    background: #f3f4f6;
    color: var(--qf-ink-2);
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.qf-ev:hover {
    filter: brightness(0.96);
}

.qf-ev-done {
    opacity: 0.5;
    text-decoration: line-through;
}

.qf-ev-meet { border-left-color: #7c3aed; background: #f5f3ff; }
.qf-ev-remind { border-left-color: #2563eb; background: #eff6ff; }
.qf-ev-task { border-left-color: #0891b2; background: #ecfeff; }
.qf-ev-follow { border-left-color: #d97706; background: #fffbeb; }
.qf-ev-invoice { border-left-color: #dc2626; background: #fef2f2; }
.qf-ev-milestone { border-left-color: #16a34a; background: #f0fdf4; }
.qf-ev-recur { border-left-color: #4f46e5; background: #eef2ff; }

/* Week and day views reuse the same cells, just fewer columns and taller rows. */
.qf-cal-grid-week {
    grid-auto-rows: minmax(0, 1fr);
}

.qf-cal-grid-day {
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: minmax(0, 1fr);
}

.qf-cal-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    font-size: 0.65rem;
    color: var(--qf-muted);
}

.qf-cal-legend span.qf-ev {
    width: auto;
    display: inline-block;
    cursor: default;
}

/* ── "What do you sell?" (products / services / both) ────────────────────── */
/* Three wide tiles above the trade grid, deliberately unlike the trade tiles: this is the
   question that narrows the list, not one of the answers in it. Three columns that collapse
   to one, because three is the whole set and it never grows. */

.qf-kindgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 0.6rem;
}

.qf-kindcard {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    width: 100%;
    text-align: left;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: 0.6rem;
    padding: 0.8rem;
    cursor: pointer;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

    .qf-kindcard:hover {
        border-color: var(--qf-blue);
    }

    .qf-kindcard.qf-on {
        border-color: var(--qf-blue);
        box-shadow: 0 0 0 2px var(--qf-blue-soft);
    }

.qf-kindcard-icon {
    width: 2.2rem;
    height: 2.2rem;
    flex: none;
    border-radius: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 1rem;
}

.qf-kindcard-name {
    font-weight: 700;
    font-size: 0.85rem;
    color: var(--qf-ink);
}

.qf-kindcard-sub {
    font-size: 0.68rem;
    line-height: 1.35;
    color: var(--qf-muted);
    /* Room for the tick, so a two-line description never runs under it. */
    padding-right: 1.1rem;
}

.qf-kindcard-tick {
    position: absolute;
    top: 0.55rem;
    right: 0.55rem;
    font-size: 0.85rem;
}

/* ── Business type picker (phase 5) ──────────────────────────────────────── */
/* The tiles on /qf-business-setup. A card rather than a radio list because the choice
   is made once, by an owner who has never seen the product before, and nine words in a
   dropdown do not tell anyone which one is theirs. */

.qf-typegrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(185px, 1fr));
    gap: 0.6rem;
}

.qf-typecard {
    display: block;
    width: 100%;
    text-align: left;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: 0.6rem;
    padding: 0.85rem 0.8rem;
    cursor: pointer;
    transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease;
}

    .qf-typecard:hover {
        border-color: var(--qf-blue);
        transform: translateY(-1px);
        box-shadow: 0 3px 10px -4px rgba(37, 99, 235, 0.35);
    }

    .qf-typecard.qf-on {
        border-color: var(--qf-blue);
        box-shadow: 0 0 0 2px var(--qf-blue-soft);
    }

.qf-typecard-icon {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 0.6rem;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 1.1rem;
    margin-bottom: 0.6rem;
}

.qf-typecard-name {
    font-weight: 600;
    font-size: 0.78rem;
    color: var(--qf-ink);
}

.qf-typecard-sub {
    font-size: 0.67rem;
    color: var(--qf-muted);
}

/* ── Feature switches ────────────────────────────────────────────────────── */

.qf-switchgrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(235px, 1fr));
    gap: 0.4rem;
}

.qf-switch {
    display: flex;
    align-items: flex-start;
    gap: 0.45rem;
    padding: 0.45rem 0.55rem;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    cursor: pointer;
}

    .qf-switch:hover {
        border-color: var(--qf-blue);
    }

    .qf-switch input {
        margin-top: 0.2rem;
        flex-shrink: 0;
    }

.qf-switch-name {
    font-weight: 600;
    font-size: 0.72rem;
    color: var(--qf-ink);
    display: block;
}

.qf-switch-help {
    font-size: 0.65rem;
    color: var(--qf-muted);
    display: block;
    line-height: 1.35;
}

/* A switch its business type never uses is dimmed rather than hidden: seeing that batch
   tracking exists is how an owner learns the product can do it. */
.qf-switch-dim {
    opacity: 0.6;
}

/* A switch the chosen trade cannot use. Kept visible — the owner should see what the trade
   does not get — but not operable, so the wizard cannot contradict the trade picked earlier. */
.qf-switch-locked {
    opacity: 0.55;
    cursor: not-allowed;
}

/* ── Setup stepper (phase 5) ─────────────────────────────────────────────── */
/* Used by /qf-business-setup. The steps stay clickable once reached: an owner who
   wants to change the type they picked two screens ago should not have to start over. */

.qf-steps {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}

.qf-step {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.28rem 0.65rem;
    border: 1px solid var(--qf-line-soft);
    border-radius: 999px;
    background: #fff;
    font-size: 0.7rem;
    color: var(--qf-muted);
    cursor: pointer;
    white-space: nowrap;
}

    .qf-step:disabled {
        cursor: default;
    }

.qf-step-no {
    width: 1.2rem;
    height: 1.2rem;
    border-radius: 50%;
    background: var(--qf-bg);
    color: var(--qf-ink-2);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.62rem;
    font-weight: 700;
    flex-shrink: 0;
}

.qf-step.qf-on {
    border-color: var(--qf-blue);
    background: var(--qf-blue-faint);
    color: var(--qf-blue-ink);
    font-weight: 600;
}

    .qf-step.qf-on .qf-step-no {
        background: var(--qf-blue);
        color: #fff;
    }

.qf-step.qf-done .qf-step-no {
    background: #16a34a;
    color: #fff;
}

.qf-step-line {
    width: 1rem;
    height: 1px;
    background: var(--qf-line-soft);
    flex-shrink: 0;
}

/* ── Point of sale ───────────────────────────────────────────────────────────
   A counter screen, not a form. Two columns that each scroll on their own, big
   touch targets, and totals that never leave the viewport — a cashier reads the
   amount due while looking at the customer, not at the page. */

.qf-pos-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.4rem 0.5rem;
    margin-bottom: 0.5rem;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
}

.qf-pos-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.6rem;
    align-items: start;
}

@media (min-width: 1024px) {
    .qf-pos-grid {
        /* The basket is the wider half deliberately: it is the half being read
           aloud to the customer, and the half that has to hold prices, quantities
           and a running total without wrapping. */
        grid-template-columns: 1fr 1.15fr;
    }
}

.qf-pos-pick,
.qf-pos-cart {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
}

.qf-pos-scan {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem;
    border-bottom: 1px solid var(--qf-line-soft);
    background: var(--qf-blue-faint);
}

    .qf-pos-scan .qf-input {
        flex: 1;
        font-size: 0.95rem;
    }

    .qf-pos-scan .bi {
        font-size: 1.1rem;
        color: var(--qf-blue-dark);
    }

.qf-pos-items {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.4rem;
    padding: 0.5rem;
    max-height: 58vh;
    overflow-y: auto;
}

.qf-pos-tile {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    text-align: left;
    padding: 0.5rem;
    min-height: 4.2rem;
    background: var(--qf-bg-soft);
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    cursor: pointer;
}

/* A product photo on a pick tile. Fixed height and object-fit: cover so the tile grid stays a
   grid whatever aspect ratio the photograph happened to be. Off unless the business turned
   images on — see UsesItemImages. */
.qf-pos-tile-img {
    width: 100%;
    height: 64px;
    object-fit: cover;
    border-radius: var(--qf-radius);
    margin-bottom: 0.2rem;
    background: var(--qf-bg);
}

    .qf-pos-tile:hover {
        background: var(--qf-blue-faint);
        border-color: var(--qf-blue);
    }

.qf-pos-tile-name {
    font-weight: 600;
    font-size: 0.78rem;
    line-height: 1.2;
}

.qf-pos-tile-sub {
    font-size: 0.68rem;
    color: var(--qf-muted);
}

.qf-pos-tile-price {
    margin-top: auto;
    font-weight: 700;
    font-size: 0.85rem;
    color: var(--qf-blue-ink);
}

.qf-pos-cart-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.45rem 0.5rem;
    border-bottom: 1px solid var(--qf-line-soft);
    background: var(--qf-bg-soft);
}

.qf-pos-lines {
    max-height: 42vh;
    overflow-y: auto;
}

    /* Basket lines stay white, so the list reads as paper under the grey cart
       header and the grey totals below it. Stated on the row rather than left
       to the panel behind it, so the colour survives whatever the cart's own
       background becomes — and painted on the ROW, not the cell, because a cell
       background would cover the hover highlight. */
    .qf-pos-lines .qf-table tbody tr {
        background: #fff;
    }

        .qf-pos-lines .qf-table tbody tr:hover {
            background: var(--qf-blue-faint);
        }

.qf-pos-qty {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
}

    .qf-pos-qty .qf-cell-input {
        width: 3rem;
        text-align: center;
    }

.qf-pos-totals {
    padding: 0.5rem;
    border-top: 1px solid var(--qf-line-soft);
    background: var(--qf-bg-soft);
}

.qf-total-grand {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--qf-blue-ink);
    border-top: 1px solid var(--qf-line);
    margin-top: 0.3rem;
    padding-top: 0.3rem;
}

.qf-pos-actions {
    display: flex;
    gap: 0.4rem;
    padding: 0.5rem;
    border-top: 1px solid var(--qf-line-soft);
    flex-wrap: wrap;
}

.qf-pos-charge {
    /* The one button on the screen that must be unmissable and impossible to
       hit by accident: it is the last thing before the customer's money moves. */
    margin-left: auto;
    min-width: 12rem;
    font-size: 0.95rem;
    padding: 0.55rem 0.9rem;
}

/* ══ Dashboard charts ═════════════════════════════════════════════════════
   Hand-drawn in CSS and inline SVG rather than through a chart library. The
   dashboard is the most-hit screen in the product and a chart bundle is the
   single biggest thing that could be added to its first paint; none of these
   shapes — grouped columns, a donut, a run of horizontal bars — needs one.

   Every colour below is a token, so the whole set follows the app into night
   mode from one place (darkmode.css) instead of nine hard-coded palettes. */

:root {
    /* Categorical series. Ordered so the first two — the pair almost every
       chart here uses — read as "money in" and "money collected". */
    --qf-s1: #2563eb; /* blue   — sales / primary */
    --qf-s2: #059669; /* green  — collected / positive */
    --qf-s3: #f59e0b; /* amber  — due / warning */
    --qf-s4: #7c3aed; /* purple */
    --qf-s5: #0d9488; /* teal */
    --qf-s6: #ef4444; /* red    — overdue / negative */
    --qf-s7: #64748b; /* slate  — "other" */
    --qf-chart-grid: #e5e7eb;
    --qf-chart-track: #eef0f3;
}

/* A shell whose panels stack at their own height and scroll past the fold.

   .qf-shell is a flex column pinned to the viewport, and a flex child shrinks by
   default. That is right for an entry screen — one grid grows to fill whatever is
   left — and wrong for a dashboard, where every panel has a content height of its
   own and shrinking just clips it against .qf-sec's overflow:hidden. */
.qf-shell-stack > * {
    flex: 0 0 auto;
}

.qf-chart {
    padding: 0.15rem 0.1rem 0;
}

.qf-chart-body {
    display: flex;
    align-items: flex-start;
    gap: 0.45rem;
}

/* Value axis. Fixed to the plot's height and justified apart, so the labels
   land on the gridlines without either side knowing the other's numbers. */
.qf-chart-y {
    height: 180px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: flex-end;
    min-width: 2.4rem;
    font-size: 0.58rem;
    line-height: 1;
    color: var(--qf-muted);
    padding-top: 1px;
}

.qf-chart-main {
    flex: 1;
    min-width: 0;
}

.qf-chart-plot {
    position: relative;
    height: 180px;
    border-left: 1px solid var(--qf-line-soft);
    border-bottom: 1px solid var(--qf-line-soft);
}

.qf-chart-plot-sm {
    height: 130px;
}

.qf-chart-y-sm {
    height: 130px;
}

/* Four evenly spaced hairlines drawn from one gradient — no extra elements,
   and they restripe themselves if the plot height ever changes. */
.qf-chart-grid {
    position: absolute;
    inset: 0;
    background: repeating-linear-gradient(to top, var(--qf-chart-grid) 0 1px, transparent 1px 25%);
    pointer-events: none;
}

.qf-cbars {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-end;
    padding: 0 0.2rem;
}

.qf-cbar-group {
    flex: 1;
    height: 100%;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 2px;
    min-width: 0;
}

.qf-cbar {
    width: 100%;
    max-width: 15px;
    min-height: 2px;
    border-radius: 2px 2px 0 0;
    background: var(--qf-s1);
    transition: opacity 0.12s;
}

    .qf-cbar:hover {
        opacity: 0.75;
    }

/* Category axis. Mirrors .qf-cbars' padding so tick and column stay in step. */
.qf-chart-x {
    display: flex;
    padding: 0.25rem 0.2rem 0;
}

    .qf-chart-x > span {
        flex: 1;
        min-width: 0;
        text-align: center;
        font-size: 0.58rem;
        color: var(--qf-muted);
        white-space: nowrap;
        overflow: hidden;
    }

/* Line / area chart. preserveAspectRatio is dropped so the plot stretches to
   whatever width the column gives it; non-scaling-stroke keeps the line one
   pixel wide through that stretch instead of smearing it. */
.qf-spark {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

    .qf-spark path,
    .qf-spark polyline {
        vector-effect: non-scaling-stroke;
    }

/* Donut. r is 15.9155 so the circumference is exactly 100 and every
   dash array can be written as a straight percentage. */
.qf-donut-wrap {
    position: relative;
    width: 100%;
    max-width: 168px;
    margin: 0 auto;
}

.qf-donut {
    width: 100%;
    display: block;
}

    .qf-donut circle {
        fill: none;
        stroke-width: 3.6;
    }

.qf-donut-track {
    stroke: var(--qf-chart-track);
}

/* The centre label is HTML over the SVG, not an SVG text node: Razor treats a
   literal <text> tag as its own construct and never emits it, and real text
   here sizes in rem with everything else instead of in viewBox units. */
.qf-donut-centre {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.05rem;
    pointer-events: none;
    text-align: center;
}

    .qf-donut-centre b {
        font-size: 1.05rem;
        font-weight: 700;
        color: var(--qf-ink);
        line-height: 1.1;
    }

    .qf-donut-centre span {
        font-size: 0.6rem;
        color: var(--qf-muted);
    }

/* Horizontal bar list — top customers, ageing buckets, anything ranked. */
.qf-hbars {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.qf-hbar-head {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    font-size: 0.7rem;
    margin-bottom: 0.2rem;
}

    .qf-hbar-head b {
        font-weight: 600;
        color: var(--qf-ink-2);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .qf-hbar-head .qf-push {
        font-weight: 700;
        color: var(--qf-ink);
        white-space: nowrap;
    }

.qf-hbar-track {
    height: 8px;
    border-radius: 999px;
    background: var(--qf-chart-track);
    overflow: hidden;
}

.qf-hbar-fill {
    height: 100%;
    border-radius: 999px;
    background: var(--qf-s1);
    min-width: 3px;
}

/* Funnel — the same bars, but each step's width is read against the FIRST
   step rather than the widest, so a stage that gains count cannot look like
   a stage that lost one. */
.qf-funnel {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.qf-funnel-bar {
    height: 26px;
    border-radius: 3px;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0 0.5rem;
    color: #fff;
    font-size: 0.68rem;
    font-weight: 700;
    white-space: nowrap;
    min-width: 4.5rem;
    background: var(--qf-s1);
}

    .qf-funnel-bar span {
        font-weight: 500;
        opacity: 0.9;
    }

.qf-funnel-row {
    display: flex;
    align-items: center;
    gap: 0.45rem;
}

/* The bar's width is a percentage, so it needs a box of its own to be a
   percentage OF — otherwise the full-width first step and the label beside it
   add up to more than the row. */
.qf-funnel-track {
    flex: 1;
    min-width: 0;
}

.qf-funnel-drop {
    font-size: 0.62rem;
    color: var(--qf-muted);
    white-space: nowrap;
}

/* One bar cut into shares — payment mix, channel mix. Named qf-sbar, NOT
   qf-stack: that one is the form field stack, on 38 screens. */
.qf-sbar {
    display: flex;
    height: 15px;
    border-radius: 999px;
    overflow: hidden;
    background: var(--qf-chart-track);
}

    .qf-sbar > span {
        height: 100%;
        min-width: 2px;
    }

/* Chart key. Named qf-key, not qf-legend: that one is a fieldset's legend. */
.qf-key {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.85rem;
    font-size: 0.66rem;
    color: var(--qf-ink-2);
}

.qf-key-item {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    min-width: 0;
}

.qf-key-dot {
    width: 9px;
    height: 9px;
    border-radius: 2px;
    flex: none;
    background: var(--qf-s1);
}

/* Key as a stacked table — label left, money and share right — for the panels
   where the key IS the readout and not just a colour explanation. */
.qf-key-rows {
    flex-direction: column;
    gap: 0.32rem;
}

    .qf-key-rows .qf-key-item {
        width: 100%;
    }

.qf-key-pct {
    min-width: 2.5rem;
    text-align: right;
    color: var(--qf-muted);
}

/* Series paint. Applied to whichever primitive needs it — bar, dot, slice. */
.qf-f1 { background: var(--qf-s1); }
.qf-f2 { background: var(--qf-s2); }
.qf-f3 { background: var(--qf-s3); }
.qf-f4 { background: var(--qf-s4); }
.qf-f5 { background: var(--qf-s5); }
.qf-f6 { background: var(--qf-s6); }
.qf-f7 { background: var(--qf-s7); }

/* A stat line under a chart — the one or two numbers the picture cannot say. */
.qf-chart-foot {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 1rem;
    font-size: 0.66rem;
    color: var(--qf-muted);
}

    .qf-chart-foot b {
        color: var(--qf-ink);
        font-weight: 700;
    }

/* ══ Header section strip ══════════════════════════════════════════════════
   The pages of the open sidebar section, laid beside the page title. Fed by
   MenuSectionState; see PageHeader.razor.

   It scrolls sideways rather than wrapping or truncating: the bar is a fixed
   70px and some sections have a dozen pages, so wrapping would burst the bar.

   The scrollbar is thin but VISIBLE. It used to be hidden to keep the strip's
   height tidy, and on a section with more pages than fit — Accounting is the
   one that hits this on a 1366 laptop — the links past the right edge simply
   vanished, with nothing on screen to say the list continued. */
.hdr-links {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    flex: 1;
    min-width: 0;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: #94a3b8 #eef2f7;
    /* Room under the links for the bar itself. */
    padding-bottom: 0.2rem;
    border-left: 1px solid #e5e7eb;
    padding-left: 0.85rem;
}

    .hdr-links::-webkit-scrollbar {
        height: 6px;
    }

    .hdr-links::-webkit-scrollbar-track {
        background: #eef2f7;
        border-radius: 999px;
    }

    .hdr-links::-webkit-scrollbar-thumb {
        background: #94a3b8;
        border-radius: 999px;
    }

        .hdr-links::-webkit-scrollbar-thumb:hover {
            background: #64748b;
        }

/* Which section these belong to, as the sidebar's own icon. Present because
   the links alone do not say it — "Register" under Quotations and under
   Invoices read identically. */
.hdr-links-icon {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex: none;
    margin-right: 0.45rem;
    border-radius: 0.35rem;
    background: var(--qf-blue-faint);
    color: var(--qf-blue);
    font-size: 1.2rem;
    line-height: 1;
}

/* The icon markup comes from the menu row, so it can be an i, an svg or a bare
   character — normalise whatever arrives to the box above. */
    .hdr-links-icon > * {
        font-size: inherit;
        line-height: 1;
    }

/* Fallback for a section with no icon set. */
.hdr-links-label {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #9ca3af;
    white-space: nowrap;
    flex: none;
    margin-right: 0.35rem;
}

.hdr-link {
    display: inline-flex;
    align-items: center;
    padding: 0.4rem 0.7rem;
    border-radius: 0.3rem;
    font-size: 0.95rem;
    font-weight: 500;
    color: #4b5563;
    text-decoration: none;
    white-space: nowrap;
    flex: none;
    transition: background 0.12s, color 0.12s;
}

    .hdr-link:hover {
        background: #eff6ff;
        color: #2563eb;
        text-decoration: none;
    }

    /* NavLink adds .active on the page you are actually looking at. */
    .hdr-link.active {
        background: #2563eb;
        color: #fff;
    }

        .hdr-link.active:hover {
            background: #1d4ed8;
            color: #fff;
        }

/* Narrow screens keep the strip — a section with more pages than fit is exactly
   where somebody needs a way through the list. There is no room for the title and
   the strip together once the sidebar has taken its 260px, so the title steps
   aside: it is already in the browser tab and highlighted in the sidebar, while
   the strip is the only way to the section's other pages. Below 640px the bar has
   the menu button, the title and three icons in it and nothing left over, so the
   strip steps aside instead; the sidebar still carries every one of these links. */
@media (min-width: 641px) and (max-width: 1024px) {
    .app-header:has(.hdr-links) h1[data-testid="page-title"] {
        display: none;
    }
}

@media (max-width: 640px) {
    .hdr-links {
        display: none;
    }
}

/* The sidebar section whose pages are currently on the header strip.

   Lives here rather than beside the other .nav-link-category rules, which are in
   input.css — that file is Tailwind's SOURCE and only reaches the browser through
   the pre-built output.css, so an edit there does nothing until someone runs the
   Tailwind build. quoteflow.css is loaded after output.css, so this wins. */
.nav-link-category.active {
    background: #eff6ff;
    border-left-color: #2563eb;
    color: #1d4ed8;
}

    /* Equal specificity to :hover and later in the cascade, so an open section
       keeps its blue instead of flicking to the hover green under the pointer. */
    .nav-link-category.active:hover {
        background: #dbeafe;
        border-left-color: #2563eb;
    }

/* Sidebar icons: one colour per destination, on a tinted tile.

   The glyph is data — FormOrPages.Icon holds <i class="bi bi-..."></i> — so the
   colour is keyed on that class here rather than stored per menu. A row seeded
   tomorrow with a class not listed below keeps the sidebar's text colour and
   simply has no tile tint until a colour is added. The tile is the <i> itself:
   no wrapper, no markup change, no database row touched.

   color-mix against currentColor means one definition works on the light and the
   dark sidebar: the fill is the icon's own colour at low opacity. */
.sidebar-nav .nav-link > span:first-child > i,
.sidebar-nav .nav-link-category > span:first-child > i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    flex: none;
    border-radius: 0.5rem;
    background: color-mix(in srgb, currentColor 14%, transparent);
    vertical-align: middle;
    transition: background-color 0.15s ease;
}

.sidebar-nav .nav-link:hover > span:first-child > i,
.sidebar-nav .nav-link-category:hover > span:first-child > i {
    background: color-mix(in srgb, currentColor 24%, transparent);
}

html.dark .sidebar-nav .nav-link > span:first-child > i,
html.dark .sidebar-nav .nav-link-category > span:first-child > i {
    background: color-mix(in srgb, currentColor 22%, transparent);
}

html.dark .sidebar-nav .nav-link:hover > span:first-child > i,
html.dark .sidebar-nav .nav-link-category:hover > span:first-child > i {
    background: color-mix(in srgb, currentColor 32%, transparent);
}

/* The tiles are taller than a line of text was, so take the difference back out
   of the row padding: the menu keeps its previous density and still scrolls. */
.sidebar-nav .nav-link { padding-block: 0.14rem; }
.sidebar-nav .nav-link-category { padding-block: 0.2rem; }

.sidebar-nav .bi-shop            { color: #f97316; }
.sidebar-nav .bi-cup-hot         { color: #e11d48; }
.sidebar-nav .bi-speedometer2    { color: #2563eb; }
.sidebar-nav .bi-bell            { color: #f59e0b; }
.sidebar-nav .bi-person-lines-fill { color: #8b5cf6; }
.sidebar-nav .bi-globe2          { color: #0ea5e9; }
.sidebar-nav .bi-file-earmark-text { color: #4f46e5; }
.sidebar-nav .bi-clipboard-check { color: #0d9488; }
.sidebar-nav .bi-receipt         { color: #059669; }
.sidebar-nav .bi-arrow-repeat    { color: #0891b2; }
.sidebar-nav .bi-arrow-return-left { color: #db2777; }
.sidebar-nav .bi-people          { color: #7c3aed; }
.sidebar-nav .bi-boxes           { color: #d97706; }
.sidebar-nav .bi-calendar3       { color: #ec4899; }
.sidebar-nav .bi-box-seam        { color: #65a30d; }
.sidebar-nav .bi-gear            { color: #64748b; }
.sidebar-nav .bi-shop-window     { color: #c2410c; }
.sidebar-nav .bi-buildings       { color: #475569; }
.sidebar-nav .bi-hourglass-split { color: #ca8a04; }
.sidebar-nav .bi-brush           { color: #c026d3; }
.sidebar-nav .bi-truck           { color: #0f766e; }
.sidebar-nav .bi-tags            { color: #d946ef; }
.sidebar-nav .bi-tools           { color: #f43f5e; }
.sidebar-nav .bi-cart-plus       { color: #ea580c; }
.sidebar-nav .bi-calculator      { color: #16a34a; }
.sidebar-nav .bi-receipt-cutoff  { color: #dc2626; }
.sidebar-nav .bi-cash-coin       { color: #15803d; }
.sidebar-nav .bi-kanban          { color: #9333ea; }
.sidebar-nav .bi-check2-square   { color: #22c55e; }
.sidebar-nav .bi-shield-lock     { color: #1d4ed8; }
.sidebar-nav .bi-mortarboard     { color: #3b82f6; }
.sidebar-nav .bi-award           { color: #eab308; }
.sidebar-nav .bi-download        { color: #0284c7; }
.sidebar-nav .bi-upc-scan        { color: #737373; }

/* The header's icon row, given the sidebar's treatment: one colour per
   destination on a round tinted tile, so the bell, the night switch, the
   language and your own profile are told apart before they are read.

   The tile is drawn on the <i>, not on the button: these buttons already carry
   their padding and colour in an inline style, and a rule naming the glyph wins
   over an inherited colour anyway — which is why the palette lives here and not
   in the markup. */
.hdr-icons button > i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.85rem;
    height: 1.85rem;
    border-radius: 50%;
    background: color-mix(in srgb, currentColor 14%, transparent);
    transition: background-color 0.15s ease;
}

.hdr-icons button:hover > i {
    background: color-mix(in srgb, currentColor 26%, transparent);
}

html.dark .hdr-icons button > i {
    background: color-mix(in srgb, currentColor 24%, transparent);
}

html.dark .hdr-icons button:hover > i {
    background: color-mix(in srgb, currentColor 34%, transparent);
}

.hdr-icons .bi-bell,
.hdr-icons .bi-bell-fill   { color: #f59e0b; }
.hdr-icons .bi-moon-stars  { color: #6366f1; }
.hdr-icons .bi-sun-fill    { color: #eab308; }
.hdr-icons .bi-globe       { color: #0ea5e9; }
.hdr-icons .bi-person-circle { color: #8b5cf6; }


/* ══ Sign-in panel ════════════════════════════════════════════════════════
   The login form, wherever it is shown — today that is the popover in the
   landing page pinned to the viewport corner. Styled from the --qf-* tokens rather than the
   landing page's own --l-* set, so the component carries its own look and can
   be dropped anywhere in the app without arriving unstyled. */

.lgp {
    padding: 0.9rem 1rem 1rem;
    font-size: 0.8rem;
    color: var(--qf-ink-2);
}

/* Title on the left, close button on the right — the button's own margin-left:auto does the
   splitting, so the row still sits right when there is no button to show. */
.lgp-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}

.lgp-title {
    font-size: 1rem;
    font-weight: 700;
    color: var(--qf-ink);
    letter-spacing: -0.01em;
}

.lgp-x {
    margin-left: auto;
    background: none;
    border: 0;
    color: var(--qf-muted);
    cursor: pointer;
    font-size: 0.8rem;
    line-height: 1;
    padding: 0.25rem;
    border-radius: var(--qf-radius);
}

    .lgp-x:hover {
        background: var(--qf-bg-soft);
        color: var(--qf-ink);
    }

.lgp-err {
    margin-bottom: 0.65rem;
    padding: 0.45rem 0.55rem;
    border: 1px solid #fca5a5;
    background: #fef2f2;
    color: #b91c1c;
    border-radius: var(--qf-radius);
    font-size: 0.73rem;
}

.lgp-field {
    margin-bottom: 0.6rem;
}

    .lgp-field > label {
        display: block;
        font-size: 0.72rem;
        font-weight: 600;
        color: var(--qf-ink-2);
        margin-bottom: 0.2rem;
    }

    .lgp-field input,
    .lgp-field select {
        width: 100%;
        padding: 0.45rem 0.55rem;
        font-size: 0.82rem;
        color: var(--qf-ink);
        background: #fff;
        border: 1px solid var(--qf-line-soft);
        border-radius: var(--qf-radius);
        outline: none;
    }

        .lgp-field input:focus,
        .lgp-field select:focus {
            border-color: var(--qf-blue);
            box-shadow: 0 0 0 2px rgba(37, 99, 235, .15);
        }

/* Remember-me row: a small choice under the password, not a full field. */
.lgp-check {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.72rem;
    color: var(--qf-ink-2);
    cursor: pointer;
    user-select: none;
}

    /* The checkbox must not inherit the full-width input styling of .lgp-field. */
    .lgp-check input {
        width: auto;
        margin: 0;
        flex: none;
    }

.lgp-remember {
    margin-top: -0.15rem;
}

/* Label and its status note ("looking up…", "selected for you") on one line. */
.lgp-label-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.4rem;
}

.lgp-hint {
    font-size: 0.65rem;
    color: var(--qf-muted);
}

.lgp-hint-on {
    color: var(--qf-blue);
}

.lgp-go {
    width: 100%;
    margin-top: 0.25rem;
    padding: 0.5rem 0.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    font-size: 0.85rem;
    font-weight: 700;
    color: #fff;
    background: var(--qf-blue);
    border: 0;
    border-radius: var(--qf-radius);
    cursor: pointer;
    transition: background 0.12s;
}

    .lgp-go:hover:not(:disabled) {
        background: var(--qf-blue-dark);
    }

    .lgp-go:disabled {
        opacity: 0.55;
        cursor: not-allowed;
    }

.lgp-foot {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 0.75rem;
    margin-top: 0.7rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--qf-line-soft);
    font-size: 0.72rem;
}

    .lgp-foot a {
        color: var(--qf-blue);
        text-decoration: none;
    }

        .lgp-foot a:hover {
            text-decoration: underline;
        }

.lgp-push {
    margin-left: auto;
}


/* ══ Theme toggle ═════════════════════════════════════════════════════════
   The night/day switch appears twice in the app shell, but never both at once.

   In the header, matching the bell and help buttons beside it — that is where
   it has always been and where people look for it. */

.hdr-theme-toggle {
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 50%;
    color: #6b7280;
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
}

    .hdr-theme-toggle:hover { color: #111827; }

/* And floating in MainLayout, as the backstop for the pages that draw no
   PageHeader and so have no bar to hold the one above. Docking it was not an
   option: the shell is a sidebar plus a scrolling main with no bar of its own,
   and the sidebar collapses to nothing, taking any button in it along. */

.app-theme-toggle {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 900;
    display: grid;
    place-items: center;
    width: 42px;
    height: 42px;
    padding: 0;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: 50%;
    color: var(--qf-muted);
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(15, 23, 42, .14);
}

    .app-theme-toggle:hover {
        color: var(--qf-ink);
        box-shadow: 0 6px 18px rgba(15, 23, 42, .2);
    }

/* One or the other, never both: when the page inside main draws a PageHeader, the header's own
   toggle is the visible one and the floating backstop stands down. */
.root-layout:has(main .app-header) .app-theme-toggle {
    display: none;
}


/* ══ Public shell ═════════════════════════════════════════════════════════
   The bar and footer PublicLayout wraps the anonymous pages in. Column flex
   with the body on flex:1 so the footer sits at the bottom of a short page
   instead of floating halfway up it. */

.pub-shell {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background: #eef2f7;
}

/* position + z-index are for the sign-in panel below: relative makes this the box the panel's
   top:100% measures against, and 40 keeps the bar and its panel above the click-catcher. */
.pub-head {
    position: relative;
    z-index: 40;
    background: #fff;
    border-bottom: 1px solid var(--qf-line-soft);
}

.pub-head-in {
    width: min(1100px, 100%);
    margin: 0 auto;
    padding: 0 1rem;
    min-height: 62px;
    display: flex;
    align-items: center;
    gap: 1rem;
}

.pub-brand {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 1.05rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--qf-ink);
    text-decoration: none;
}

/* The brand mark is the supplied artwork itself, not a CSS gradient redraw: one file shared
   with the landing page's bar and the footer. Sized by height only, so a re-export at another
   aspect ratio needs no change here. Its white field is already transparent — see the PNG. */
.pub-mark {
    display: block;
    width: auto;
    height: 34px;
    flex: none;
}

/* margin-left:auto rather than justify-content:space-between, so the pair stays hard right even
   if something is added between it and the brand later. */
.pub-head-cta {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
}

.pub-theme-toggle {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 50%;
    color: var(--qf-muted);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}

    .pub-theme-toggle:hover {
        background: var(--qf-bg-soft);
        color: var(--qf-ink);
    }

.pub-body {
    flex: 1;
}

/* The pages inside bring their own .qf-public padding and full-height background, both of which
   fight the shell — the min-height would push the footer off a short page. */
.pub-body .qf-public {
    min-height: 0;
    background: none;
}

.pub-foot {
    background: #fff;
    border-top: 1px solid var(--qf-line-soft);
    font-size: 0.78rem;
    color: var(--qf-muted);
}

.pub-foot-in {
    width: min(1100px, 100%);
    margin: 0 auto;
    padding: 1.2rem 1rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.pub-foot-brand {
    font-weight: 700;
    color: var(--qf-ink-2);
}

/* The per-browser "Choose your first page" link in the public shell's footer. */
.pub-foot-link {
    color: var(--qf-blue);
    font-weight: 600;
    text-decoration: none;
}

.pub-foot-link:hover { text-decoration: underline; }


/* ══ Sign-up tips ═════════════════════════════════════════════════════════
   The bilingual walkthrough under the sign-up form. Bangla is the primary
   line and English the gloss under it, so the English gets a lighter colour
   and its own line rather than sitting inline and competing. */

.qf-tips {
    width: min(980px, 100%);
    margin: 1rem auto 0;
    padding: 1.1rem 1.3rem 1.2rem;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: 0.5rem;
    font-size: 0.78rem;
    line-height: 1.75;
    color: var(--qf-ink-2);
}

.qf-tips-title {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.7rem;
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--qf-ink);
}

/* Every English gloss, wherever it sits — its own line under the Bangla. */
.qf-tips-en {
    display: block;
    color: var(--qf-muted);
    font-weight: 400;
    font-size: 0.74rem;
}

    /* Except in the heading, where it is a tail on the same line. */
    .qf-tips-title .qf-tips-en {
        display: inline;
        font-size: 0.8rem;
    }

.qf-tips-list {
    margin: 0;
    padding-left: 1.2rem;
    list-style: decimal;
}

    .qf-tips-list li {
        margin-bottom: 0.55rem;
    }

    .qf-tips-list b {
        color: var(--qf-ink);
    }

.qf-tips-note {
    display: flex;
    gap: 0.55rem;
    margin-top: 0.9rem;
    padding: 0.7rem 0.8rem;
    background: #fffbeb;
    border: 1px solid #fcd34d;
    border-radius: var(--qf-radius);
    color: #92400e;
}

    .qf-tips-note .qf-tips-en {
        color: #b45309;
    }

    .qf-tips-note i {
        flex: none;
        margin-top: 0.2rem;
    }

.qf-tips-foot {
    margin: 0.9rem 0 0;
    padding-top: 0.7rem;
    border-top: 1px solid var(--qf-line-soft);
}

.qf-tips a {
    color: var(--qf-blue);
    font-weight: 600;
    text-decoration: none;
}

    .qf-tips a:hover {
        text-decoration: underline;
    }


/* ── Sign-in panel on the public shell ────────────────────────────────────
   Hangs off the bottom edge of the bar, flush to the right — the same corner
   treatment the landing page uses, so the form opens where the button already
   is. Absolute against .pub-head rather than fixed to the viewport, so
   top:100% means "however tall the bar actually is" at any width. */

.pub-signin-scrim {
    position: fixed;
    inset: 0;
    z-index: 30;
}

.pub-signin-pop {
    position: absolute;
    top: 100%;
    right: 0;
    z-index: 61;
    /* Never wider than the phone it is on, and never narrower than usable. */
    width: min(330px, 100vw);
    background: #fff;
    /* Only the two edges still facing the page get a border and a corner. */
    border: 1px solid var(--qf-line-soft);
    border-top: 0;
    border-right: 0;
    border-radius: 0 0 0 12px;
    box-shadow: 0 16px 44px rgba(15, 23, 42, .22);
    transform-origin: top right;
    animation: pub-pop .14s ease-out;
}

@keyframes pub-pop {
    from { opacity: 0; transform: translateY(-4px) scale(.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}


/* ══ Grid search row and pager ════════════════════════════════════════════
   A second header row of inputs, one per searchable column, and a footer
   saying which slice of the result is on screen. */

/* Lighter than the header above it, so the two rows do not read as one thick
   band and it is obvious which one holds the column names. */
.qf-filter-row > th {
    padding: 0.2rem 0.3rem;
    background: var(--qf-bg-soft);
    border-bottom: 1px solid var(--qf-line);
    font-weight: 400;
    vertical-align: middle;
}

/* Small enough that a whole row of them does not make the grid header taller
   than the rows it labels. */
.qf-input-xs {
    width: 100%;
    padding: 0.15rem 0.35rem;
    font-size: 0.7rem;
    line-height: 1.3;
    height: auto;
}

.qf-grid-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.45rem 0.6rem;
    background: var(--qf-bg-soft);
    border-top: 1px solid var(--qf-line-soft);
}


/* ══ Combo box ════════════════════════════════════════════════════════════
   The styled replacement for a native datalist. See QfCombo. */

.qfc {
    position: relative;
    display: block;
    width: 100%;
}

/* Room on the right for the caret, so a long value does not run under it. */
.qfc-input {
    width: 100%;
    padding-right: 1.5rem;
}

.qfc-caret {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 1.5rem;
    display: grid;
    place-items: center;
    background: none;
    border: 0;
    color: var(--qf-muted);
    font-size: 0.65rem;
    cursor: pointer;
}

    .qfc-caret:hover:not(:disabled) { color: var(--qf-ink); }
    .qfc-caret:disabled { cursor: default; opacity: 0.5; }

.qfc-open .qfc-caret { transform: rotate(180deg); }

/* Placed against the VIEWPORT by wwwroot/js/qfCombo.js the moment it opens —
   position, width and height all come from there, and it flips above the box when
   there is no room below. What is written here is only where it lands for the one
   frame before that runs, and where it lands if script is unavailable.

   z-index above everything it can open in front of: sticky grid headers and total
   bars, the modal it can itself be opened inside, and the floating theme button. */
.qfc-menu {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    min-width: 15rem;
    z-index: 950;
    max-height: 13rem;
    overflow-y: auto;
    margin: 0;
    padding: 0.15rem;
    list-style: none;
    background: #fff;
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.16);
}

.qfc-opt {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.28rem 0.45rem;
    border-radius: var(--qf-radius);
    font-size: 0.75rem;
    color: var(--qf-ink-2);
    cursor: pointer;
    white-space: nowrap;
}

/* The two spans are invisible to the pointer, so a press always reports the row
   itself as its target. The row is where the handler and its preventDefault live,
   and a press that reports a child instead is a press that can escape both. */
.qfc-opt-main,
.qfc-opt-hint {
    pointer-events: none;
}

/* The name takes the squeeze; the hint is short and keeps its width, because a
   price clipped to "1,2…" is worse than a name clipped to "Stainless Steel Sc…". */
.qfc-opt-main {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.qfc-opt-hint {
    flex: none;
    font-size: 0.66rem;
    color: var(--qf-muted);
}

.qfc-opt.qfc-on .qfc-opt-hint {
    color: inherit;
    opacity: 0.75;
}

/* Hover is CSS, the keyboard cursor is a class — see the note in QfCombo about
   why the pointer no longer tells the server where it is. Soft yellow rather
   than a saturated system blue: it reads as "this one" without shouting over
   the rest of the form. */
    .qfc-opt:hover,
    .qfc-opt.qfc-on {
        background: #fef9c3;
        color: #713f12;
    }


/* ══ Category tree ════════════════════════════════════════════════════════
   The classification tree on /qf-item-setup. Indent comes from inline
   padding-left computed per row, so the depth is unlimited without a rule
   per level. */

.qf-tree-row {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.4rem 0.25rem 0.35rem;
    border-radius: var(--qf-radius);
    font-size: 0.75rem;
    color: var(--qf-ink-2);
    cursor: pointer;
    white-space: nowrap;
}

    .qf-tree-row:hover { background: var(--qf-bg-soft); }

    .qf-tree-row.qf-sel {
        background: var(--qf-blue-faint);
        color: var(--qf-blue-ink);
        font-weight: 600;
    }

/* Same width as the toggle, so a leaf's name lines up with its siblings'
   rather than shifting left by the width of a missing chevron. */
.qf-tree-gap,
.qf-tree-toggle {
    width: 14px;
    flex: none;
    display: grid;
    place-items: center;
    font-size: 0.6rem;
}

.qf-tree-toggle { color: var(--qf-muted); }
.qf-tree-toggle:hover { color: var(--qf-ink); }

.qf-tree-icon {
    flex: none;
    font-size: 0.8rem;
    color: var(--qf-blue);
}

/* The name is what gives way when the panel is narrow — the count and the
   actions are fixed-width and useless truncated. */
.qf-tree-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.qf-tree-count {
    flex: none;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: var(--qf-bg-soft);
    font-size: 0.65rem;
    color: var(--qf-muted);
}

/* Hidden until the row is hovered: three icons on every row of a long tree is
   more clutter than the tree itself. */
.qf-tree-actions {
    flex: none;
    display: flex;
    gap: 0.3rem;
    visibility: hidden;
    font-size: 0.7rem;
    color: var(--qf-muted);
}

    .qf-tree-row:hover .qf-tree-actions { visibility: visible; }

    .qf-tree-actions i { cursor: pointer; }
    .qf-tree-actions i:hover { color: var(--qf-blue); }

/* A row whose outlet is at or under its reorder level. */
.qf-warn-row > td { background: #fffbeb; }

/* Checkbox and its label on one line, in the stacks where a qf-row would put
   the label in a column of its own and leave the box floating. */
.qf-check {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.75rem;
    cursor: pointer;
}

/* Inside a .qf-row the label rules above hand EVERY label the field-name width, and a
   checkbox label is not a field name — it is the control. Forced to 120px its text
   spilled out of the row (Pack Sizes, Batches, Serials). It takes the rest of the row
   and wraps instead. */
.qf-row > .qf-check,
.qf-row.qf-row-wide > .qf-check {
    width: auto;
    flex: 1;
    min-width: 0;
}


/* ══ Container-aware form layout ══════════════════════════════════════════
   The grid breakpoints above measure the VIEWPORT, but a form on a two-panel
   screen never gets the viewport. It gets what is left after the 290px side
   panel, and then two thirds of that again inside .qf-cols-21.

   On a 1366-wide laptop that is roughly 1366 − 290 − padding ≈ 1050 for the
   panel, ≈ 640 for the form column — while the viewport rules, seeing 1366,
   hand it three columns. Each column ends up ~210px, a 120px label leaves ~90px
   of input, and the row spills.

   These rules correct for that by measuring the actual container. They only
   ever REDUCE the column count, so a browser without container-query support
   keeps exactly the behaviour it has today rather than losing the layout. */

.qf-main,
.qf-shell {
    container-type: inline-size;
}

@container (max-width: 1000px) {
    /* Three columns need ~340px each to hold a label and a usable control. */
    .qf-form-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (max-width: 680px) {
    .qf-form-grid,
    .qf-form-grid-2 {
        grid-template-columns: repeat(1, minmax(0, 1fr));
    }

    /* 2fr/1fr in less than 680px leaves the narrow column too thin to read, and
       the wide one still cramped. One under the other beats both. */
    .qf-cols-21,
    .qf-cols-2,
    .qf-cols-3 {
        grid-template-columns: repeat(1, minmax(0, 1fr));
    }
}

/* The variant matrix stops being readable long before 680px — a seven-column table
   squeezed into a third of the page shows two columns and a scrollbar. It stacks to
   one full-width panel while there is still room to waste, rather than at the point
   where the narrow column has already collapsed to a single column of pixels. */
@container (max-width: 900px) {
    .qf-cols-12 {
        grid-template-columns: repeat(1, minmax(0, 1fr));
    }
}

/* Last resort before the control disappears: put the label on its own line so
   the input keeps the full width instead of sharing it with a 120px label. */
@container (max-width: 420px) {
    .qf-row {
        flex-direction: column;
        align-items: stretch;
        gap: 0.15rem;
    }

        .qf-row > label,
        .qf-row.qf-row-wide > label {
            width: auto;
        }
}

/* A margin that has gone the wrong way — shown, not hidden, because a dish that
   costs more to make than it sells for is the one thing this grid is for. */
.qf-warn-text {
    color: #b45309;
    font-weight: 600;
}

/* ── Sign-up: menus and starter data ─────────────────────────────────────────
   Two steps that are lists of SUGGESTIONS, not forms. Everything here is already
   ticked when the screen opens, so the visual job is to make "we chose this for
   you" readable at a glance and unticking obvious — not to draw attention to
   empty boxes waiting to be filled. */

/* The word "suggested" beside a module name. Small, quiet, and never the same
   weight as the name it qualifies. */
.qf-tagmini {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: var(--qf-blue-faint);
    color: var(--qf-blue-ink);
    font-size: 0.58rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    vertical-align: middle;
}

/* A row of small toggles — units, sub-categories. A chip rather than a switch
   card because these come thirty at a time and their labels are one word: a
   card per unit would push the category tree off the first screen. */
.qf-chipset {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
}

.qf-chippick {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.22rem 0.5rem;
    border: 1px solid var(--qf-line-soft);
    border-radius: 999px;
    background: #fff;
    font-size: 0.7rem;
    color: var(--qf-ink-2);
    cursor: pointer;
    user-select: none;
}

    .qf-chippick:hover {
        border-color: var(--qf-blue);
    }

    .qf-chippick.qf-on {
        border-color: var(--qf-blue);
        background: var(--qf-blue-faint);
        color: var(--qf-blue-ink);
        font-weight: 600;
    }

    .qf-chippick input {
        margin: 0;
        flex-shrink: 0;
    }

.qf-chippick-sm {
    font-size: 0.66rem;
    padding: 0.18rem 0.45rem;
}

/* One card per top-level category, with its children as chips inside it. The
   card is what makes "unticking the parent takes the children with it" look
   like a rule rather than a surprise. */
.qf-catgrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 0.4rem;
}

.qf-catcard {
    border: 1px solid var(--qf-line-soft);
    border-radius: var(--qf-radius);
    background: #fff;
    padding: 0.45rem 0.55rem;
}

    .qf-catcard.qf-on {
        border-color: var(--qf-blue);
    }

.qf-catcard-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
    margin-bottom: 0.35rem;
}

    .qf-catcard-head input {
        margin: 0;
        flex-shrink: 0;
    }

/* A link that is a button, because it moves the wizard rather than navigating. */
button.qf-link {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    text-decoration: underline;
}

/* ── POS: finding an item, and who bought it ─────────────────────────────────
   A counter is asked two questions a scan box cannot answer — "what have you got
   in X" and "have you got any left" — so the filters sit directly over the tiles,
   one row, no dialog. And the customer boxes sit on the basket head because a name
   is given while the goods are being rung up, not at a separate step. */

.qf-pos-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    padding: 0 0 0.4rem;
}

    .qf-pos-filters .qf-select {
        width: auto;
        max-width: 14rem;
        flex: 0 1 auto;
    }

/* Three states of one question, so they read as one control rather than three
   buttons that happen to sit together. */
.qf-pos-seg {
    display: inline-flex;
    border: 1px solid var(--qf-line-soft);
    border-radius: 999px;
    overflow: hidden;
    background: #fff;
}

.qf-pos-segbtn {
    border: 0;
    background: none;
    padding: 0.22rem 0.6rem;
    font-size: 0.68rem;
    color: var(--qf-muted);
    cursor: pointer;
    white-space: nowrap;
}

    .qf-pos-segbtn:hover {
        color: var(--qf-ink);
    }

    .qf-pos-segbtn.qf-on {
        background: var(--qf-blue);
        color: #fff;
        font-weight: 600;
    }

/* ── Who is buying ────────────────────────────────────────────────────────
   Tucked into the top corner of the basket, where the two boxes it replaces
   used to sit. A panel rather than a fold-out: every detail is on screen at
   once, in small cells over two or three short rows, so the whole thing costs
   about as much height as the one row it replaces and nothing costs a click to
   reach. Small deliberately — the basket below it is what the counter reads
   aloud, and this must never compete with it. */

.qf-pos-customer {
    margin: 0.4rem 0.4rem 0;
    padding: 0.3rem 0.4rem 0.4rem;
    border-color: var(--qf-line-soft);
    background: var(--qf-bg-soft);
}

.qf-pos-customer-grid {
    display: grid;
    /* Two per row at the width a basket actually gets; three once there is room,
       which folds the six fields down to two rows. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.25rem 0.4rem;
    align-items: end;
}

@media (min-width: 1500px) {
    .qf-pos-customer-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    /* Two of three rather than the whole row: the salesperson then fills the
       column beside it and the panel is two clean rows. */
    .qf-pos-customer-grid .qf-pos-fld-wide {
        grid-column: span 2;
    }
}

.qf-pos-fld {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

/* An email is the longest thing typed here and the least abbreviatable, so it
   takes the whole row rather than half of one. */
.qf-pos-fld-wide {
    grid-column: 1 / -1;
}

    .qf-pos-fld > label {
        font-size: 0.62rem;
        font-weight: 600;
        line-height: 1.2;
        color: var(--qf-ink-2);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

/* The email box and its tick on one line — but on the SAME tracks as the rows
   above, not on a flex line of their own. The cell already spans two columns,
   so repeating the parent's two columns and its column gap inside it puts the
   box exactly over the box above it and the tick exactly where the column
   beside it starts. A flex row here sized the box to "whatever the tick left
   over", which lined up with nothing. */
.qf-pos-email {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 0.4rem;
    align-items: center;
    min-width: 0;
}

    .qf-pos-fld-check {
        font-size: 0.62rem;
        font-weight: 600;
        gap: 0.25rem;
        white-space: nowrap;
        color: var(--qf-ink-2);
        user-select: none;
    }

        .qf-pos-fld-check input {
            margin: 0;
        }

    .qf-pos-fld .qf-cell-input {
        width: 100%;
        border-color: var(--qf-line-soft);
        background: #fff;
    }

/* ── Sales order entry (/qf-order) in the till's clothes ─────────────────────
   Same two panels as the POS, but the right one carries far more than a basket —
   customer, order terms, lines — so it cannot simply grow: inside the locked
   shell it would be clipped. Instead the grid takes whatever height the shell has
   left, the tiles and the order body each scroll on their own, and the totals and
   buttons stay pinned under them. Only on a screen tall enough for the lock; on a
   short one the page scrolls and the panels keep their natural height. */

@media (min-width: 1024px) and (min-height: 820px) {
    .qf-so-grid {
        flex: 1 1 auto;
        min-height: 0;
        grid-template-rows: minmax(0, 1fr);
        align-items: stretch;
    }

        .qf-so-grid > .qf-pos-pick,
        .qf-so-grid > .qf-pos-cart {
            min-height: 0;
            overflow: hidden;
        }

        .qf-so-grid .qf-pos-items {
            flex: 1 1 auto;
            max-height: none;
            align-content: start;
        }

    .qf-so-cart-body {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
    }
}

/* The body above already scrolls; a second scrollbar inside it for the lines
   would leave two thumbs to drag on one panel. */
.qf-so-lines {
    max-height: none;
    overflow: visible;
}

.qf-pos-tile:disabled {
    cursor: default;
    opacity: 0.6;
}

/* ── Invoice entry (/qf-invoice) on the same layout ─────────────────────────
   An invoice line carries more than a basket line — unit and description as
   well as the numbers — and half a screen cannot hold them all as columns. Those
   two ride in a small row under the item name instead, the way the till puts
   "per kg" under a line: still editable, just not each a column of its own. */

/* Product / Service over the tiles, for a business that sells both. Real radios,
   bordered as one group so the pair reads as a single either-or. */
.qf-kind-radio {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.15rem 0.55rem;
    border: 1px solid var(--qf-line-soft);
    border-radius: 999px;
    background: #fff;
}

    .qf-kind-radio label {
        display: inline-flex;
        align-items: center;
        gap: 0.2rem;
        font-size: 0.7rem;
        font-weight: 600;
        color: var(--qf-ink-2);
        cursor: pointer;
        user-select: none;
    }

        .qf-kind-radio input {
            margin: 0;
        }

/* A one-off line's name and unit — the only line that has no catalogue row to
   show them from, so the only one with boxes for them. */
.qf-inv-line-meta {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 4rem;
    gap: 0.2rem;
}

    .qf-inv-line-meta .qf-cell-input {
        width: 100%;
        min-width: 0;
    }

/* The invoice gives the document the larger share — its line grid has a
   description column the till's basket does not — but not so much that the left
   panel, which also carries payments and the customer link, turns into a strip.
   40 / 60 was settled on after a third proved too narrow. */
@media (min-width: 1024px) {
    .qf-inv-grid {
        grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    }
}

/* Invoice Info on a twelve-track grid, so each field takes the width its content
   needs rather than an equal share: three dates across the top, the four short
   choices under them, and Salesperson / Delivery Terms / Notes together on the
   last row — three free-text boxes that belong side by side. Each field says its
   own share with --span. Measured on the right panel, not the window, because
   that panel's width is what the fields actually get; narrower than this, the
   two-column fallback above still applies. */
.qf-so-cart-body {
    container-type: inline-size;
}

@container (min-width: 560px) {
    .qf-inv-info-grid {
        grid-template-columns: repeat(12, minmax(0, 1fr));
    }

        .qf-inv-info-grid > .qf-pos-fld {
            grid-column: span var(--span, 4);
        }
}

/* Payments and the customer link, pinned under the tiles. flex:none so it keeps
   its height while the tiles above take what is left and scroll. */
.qf-inv-pick-foot {
    flex: none;
    border-top: 1px solid var(--qf-line);
    background: var(--qf-bg-soft);
}

    .qf-inv-pick-foot .qf-pos-cart-head {
        padding: 0.3rem 0.5rem;
    }

/* A handful of payments (or, on the quotation, attachments) is the normal case;
   past that the list scrolls in its own box rather than growing into the tiles. */
.qf-inv-payments,
.qf-inv-attachments {
    max-height: 8.5rem;
    overflow-y: auto;
    background: #fff;
}

.qf-inv-send {
    padding: 0.4rem 0.5rem;
}

/* The invoice summary, one figure a row in the order the sum runs. Every value —
   printed figure or input — ends on the same right edge, so the column reads down
   like a sum on paper. Rows are kept tight because the block is pinned: height
   spent here is height taken from the lines above it. */
.qf-inv-totals {
    display: flex;
    flex-direction: column;
    padding: 0.35rem 0.6rem;
}

    /* Labels as heavy as their figures, as on the till's totals — in a pinned
       summary read at a glance, a thin label beside a bold number is the half
       that gets skipped. */
    .qf-inv-totals .qf-total-line {
        padding: 0.08rem 0;
        font-weight: 600;
        color: var(--qf-ink);
    }

        .qf-inv-totals .qf-total-line > span:last-child,
        .qf-inv-totals .qf-inv-total-input {
            text-align: right;
            min-width: 7.5rem;
        }

/* The two boxes somebody types into. Drawn as real inputs — border and white
   ground — so they read as "fill this in" among figures that are only printed. */
.qf-inv-total-input {
    width: 7.5rem;
    height: 1.55rem;
    padding: 0.1rem 0.4rem;
    border: 1px solid var(--qf-line);
    border-radius: 4px;
    background: #fff;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

    .qf-inv-total-input:focus {
        outline: none;
        border-color: var(--qf-blue);
        box-shadow: 0 0 0 2px var(--qf-blue-faint);
    }

/* Where the figures above are summed — a rule over it, as on paper. */
.qf-inv-total-sum {
    border-top: 1px solid var(--qf-line-soft);
    margin-top: 0.15rem;
    padding-top: 0.2rem !important;
    font-weight: 700;
}

.qf-pos-fld .qf-pick .qf-cell-input {
    padding-right: 1.9rem;
}

.qf-pos-bar .qf-meter {
    display: inline-block;
}

/* ══ Trade guide (/qf-tutorial) ═══════════════════════════════════════════
   The per-business-type tutorial. Built from the --qf-* tokens so night mode
   follows without a second sheet, and laid out so it survives four languages:
   Bangla and Hindi set wider than English and Arabic runs right to left, so
   nothing here is positioned by hand and no width is pinned to a word.

   [dir="rtl"] on the article is what mirrors it — the browser flips flex rows,
   list markers and text alignment on its own. The ONLY thing it does not flip
   is an arrow glyph, because that is content, so those are turned explicitly. */

/* The guide is a document, so the WINDOW scrolls — qfScrollToId moves the window, and the
   contents links would do nothing inside a clipped shell. Stated after the (min-height: 820px)
   lock at the top of this sheet so it wins on the tall screens that lock applies to. */
.qf-shell.qft-shell {
    height: auto;
    min-height: calc(100vh - 70px);
    overflow: visible;
}

/* Full width of the working surface, like every other screen. A centred reading column left a
   wide empty band beside it that looked like a layout that had failed to load. */
.qft {
    font-size: 0.8rem;
    line-height: 1.65;
    color: var(--qf-ink-2);
    width: 100%;
    padding: 0 0.4rem 3rem;
}

/* ── Head ─────────────────────────────────────────────────────────────── */

.qft-head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.9rem;
    align-items: flex-start;
    padding: 1rem 0 0.9rem;
    border-bottom: 2px solid var(--qft-accent, var(--qf-blue));
    margin-bottom: 1.1rem;
}

.qft-head-main {
    display: flex;
    gap: 0.8rem;
    align-items: flex-start;
    flex: 1 1 320px;
    min-width: 0;
}

.qft-head-ico {
    width: 44px;
    height: 44px;
    flex: none;
    border-radius: 12px;
    display: grid;
    place-items: center;
    font-size: 1.2rem;
    color: #fff;
    background: var(--qft-accent, var(--qf-blue));
}

.qft-h1 {
    margin: 0 0 0.25rem;
    font-size: 1.35rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.25;
    color: var(--qf-ink);
}

.qft-lead {
    margin: 0 0 0.45rem;
    font-size: 0.86rem;
    color: var(--qf-ink-2);
}

.qft-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.9rem;
    font-size: 0.7rem;
    color: var(--qf-muted);
}

.qft-meta-on {
    color: #15803d;
    font-weight: 700;
}

html.dark .qft-meta-on {
    color: #86efac;
}

/* ── Language switch ──────────────────────────────────────────────────────
   Each button carries its own language's name in its own script, so the reader
   who cannot read the language currently on screen can still find their own. */

.qft-langs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-inline-start: auto;
}

.qft-lang {
    padding: 0.25rem 0.6rem;
    font-size: 0.76rem;
    font-weight: 600;
    font-family: inherit;
    color: var(--qf-ink-2);
    background: var(--qf-bg-soft);
    border: 1px solid var(--qf-line-soft);
    border-radius: 999px;
    cursor: pointer;
    line-height: 1.5;
}

    .qft-lang:hover:not(:disabled) {
        border-color: var(--qf-blue);
        color: var(--qf-blue);
    }

    .qft-lang:disabled {
        opacity: 0.4;
        cursor: not-allowed;
    }

.qft-lang-on,
.qft-lang-on:hover {
    color: #fff;
    background: var(--qft-accent, var(--qf-blue));
    border-color: var(--qft-accent, var(--qf-blue));
}

/* ── Contents ─────────────────────────────────────────────────────────────
   Worth its space: the reader who comes back next week wants step 5, not 1. */

.qft-toc {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.15rem;
    padding: 0.7rem 0.8rem;
    margin-bottom: 1.4rem;
    background: var(--qf-bg-soft);
    border: 1px solid var(--qf-line-soft);
    border-radius: 10px;
}

@media (min-width: 700px) {
    .qft-toc {
        grid-template-columns: 1fr 1fr;
        gap: 0.15rem 1.2rem;
    }
}

/* Now that the guide spans the full surface, two columns would leave ten steps as a tall, mostly
   empty box on a desktop. */
@media (min-width: 1200px) {
    .qft-toc {
        grid-template-columns: repeat(3, 1fr);
    }
}

.qft-toc-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.22rem 0.15rem;
    font-size: 0.78rem;
    color: var(--qf-ink-2);
    text-decoration: none;
    border-radius: 6px;
}

    .qft-toc-item:hover {
        color: var(--qf-blue);
        background: rgba(37, 99, 235, .07);
    }

.qft-toc-n {
    width: 20px;
    height: 20px;
    flex: none;
    display: grid;
    place-items: center;
    border-radius: 50%;
    font-size: 0.66rem;
    font-weight: 800;
}

/* ── A step ───────────────────────────────────────────────────────────── */

.qft-step {
    padding: 1.3rem 0;
    border-top: 1px solid var(--qf-line-soft);
    scroll-margin-top: 90px;
}

    .qft-step:first-of-type {
        border-top: 0;
    }

.qft-step-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.55rem;
}

.qft-step-n {
    width: 26px;
    height: 26px;
    flex: none;
    display: grid;
    place-items: center;
    border-radius: 50%;
    font-size: 0.78rem;
    font-weight: 800;
}

.qft-step-ico {
    width: 26px;
    height: 26px;
    flex: none;
    display: grid;
    place-items: center;
    border-radius: 7px;
    font-size: 0.85rem;
}

.qft-h2 {
    margin: 0;
    font-size: 1.02rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.35;
    color: var(--qf-ink);
}

.qft-body {
    margin: 0 0 0.85rem;
}

.qft-points {
    list-style: none;
    margin: 0.9rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.35rem;
}

    .qft-points li {
        display: flex;
        gap: 0.5rem;
        align-items: flex-start;
    }

    .qft-points i {
        color: #16a34a;
        font-size: 0.85rem;
        line-height: 1.6;
        flex: none;
    }

html.dark .qft-points i {
    color: #86efac;
}

.qft-tip {
    display: flex;
    gap: 0.55rem;
    align-items: flex-start;
    margin-top: 0.9rem;
    padding: 0.55rem 0.7rem;
    background: #fffbeb;
    border: 1px solid #fcd34d;
    border-radius: 8px;
    font-size: 0.78rem;
    color: #92400e;
}

    .qft-tip i {
        flex: none;
        line-height: 1.6;
    }

html.dark .qft-tip {
    background: #3a2f12;
    border-color: #7c5e14;
    color: #fcd34d;
}

.qft-do {
    margin-top: 0.9rem;
}

.qft-other {
    padding-top: 1.3rem;
    border-top: 1px solid var(--qf-line-soft);
}

/* ── Figures ──────────────────────────────────────────────────────────── */

.qft-fig {
    margin: 0.9rem 0 0;
}

.qft-fig-cap {
    margin-top: 0.45rem;
    font-size: 0.72rem;
    color: var(--qf-muted);
    text-align: center;
}

/* A chain. Wraps rather than scrolls: a reader on a phone should get four small
   rows they can read, not one row they have to drag sideways. */
.qft-row {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    justify-content: center;
    gap: 0.4rem;
}

.qft-node {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 1 auto;
    min-width: 0;
    padding: 0.5rem 0.65rem;
    border: 1px solid;
    border-radius: 9px;
}

.qft-node-ico {
    width: 26px;
    height: 26px;
    flex: none;
    display: grid;
    place-items: center;
    border-radius: 7px;
    font-size: 0.85rem;
    background: rgba(255, 255, 255, .65);
}

html.dark .qft-node-ico {
    background: rgba(0, 0, 0, .25);
}

.qft-node-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

    .qft-node-text b {
        font-size: 0.76rem;
        font-weight: 700;
        line-height: 1.4;
    }

    .qft-node-text span {
        font-size: 0.66rem;
        opacity: .78;
        line-height: 1.4;
    }

.qft-arrow {
    display: grid;
    place-items: center;
    color: var(--qf-muted);
    font-size: 0.9rem;
    flex: none;
}

/* The one thing the browser will not mirror for us: an arrow is content, not layout. */
[dir="rtl"] .qft-arrow {
    transform: scaleX(-1);
}

/* Split: a source, a stem, and the things it feeds. */
.qft-split {
    display: grid;
    justify-items: center;
}

.qft-split-stem {
    width: 2px;
    height: 16px;
    background: var(--qf-line);
}

.qft-split-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.4rem;
}

.qft-split-leg {
    display: grid;
    justify-items: center;
}

.qft-split-tick {
    width: 2px;
    height: 10px;
    background: var(--qf-line);
}

/* Compare: two columns, and the point is the contrast between them. */
.qft-cmp {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.6rem;
}

@media (min-width: 640px) {
    .qft-cmp {
        grid-template-columns: 1fr 1fr;
    }
}

.qft-cmp-col {
    display: grid;
    gap: 0.35rem;
    align-content: start;
}

.qft-cmp-head {
    font-size: 0.72rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .05em;
    padding: 0.25rem 0.5rem;
    border-radius: 6px;
    text-align: center;
}

.qft-cmp-head-bad {
    background: #fee2e2;
    color: #b91c1c;
}

.qft-cmp-head-good {
    background: #dcfce7;
    color: #15803d;
}

html.dark .qft-cmp-head-bad {
    background: #3f1212;
    color: #fca5a5;
}

html.dark .qft-cmp-head-good {
    background: #052e1b;
    color: #86efac;
}

.qft-cmp .qft-node {
    width: 100%;
}

/* Stack: numbered layers, read downward. */
.qft-stack {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.35rem;
    counter-reset: qftstep;
}

.qft-stack-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.65rem;
    background: var(--qf-bg-soft);
    border: 1px solid var(--qf-line-soft);
    border-radius: 9px;
}

    .qft-stack-row::before {
        counter-increment: qftstep;
        content: counter(qftstep);
        width: 20px;
        flex: none;
        font-size: 0.72rem;
        font-weight: 800;
        color: var(--qf-muted);
        text-align: center;
    }

.qft-stack-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

    .qft-stack-text b {
        font-size: 0.78rem;
        font-weight: 700;
        color: var(--qf-ink);
    }

    .qft-stack-text span {
        font-size: 0.7rem;
        color: var(--qf-muted);
    }

/* Cycle: the same chain, plus the bar that says it starts again tomorrow. */
.qft-cycle {
    display: grid;
    gap: 0.4rem;
}

.qft-cycle-back {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.3rem 0.6rem;
    border: 1px dashed var(--qf-line);
    border-radius: 999px;
    font-size: 0.7rem;
    color: var(--qf-muted);
}

/* ── Node tones ───────────────────────────────────────────────────────────
   One palette for every figure, every step number and every contents bullet,
   so a colour means the same thing wherever the reader meets it. */

.qft-n-blue {
    background: #eff6ff;
    border-color: #bfdbfe;
    color: #1d4ed8;
}

.qft-n-green {
    background: #f0fdf4;
    border-color: #bbf7d0;
    color: #15803d;
}

.qft-n-amber {
    background: #fffbeb;
    border-color: #fde68a;
    color: #b45309;
}

.qft-n-red {
    background: #fef2f2;
    border-color: #fecaca;
    color: #b91c1c;
}

.qft-n-purple {
    background: #f5f3ff;
    border-color: #ddd6fe;
    color: #6d28d9;
}

.qft-n-teal {
    background: #f0fdfa;
    border-color: #99f6e4;
    color: #0f766e;
}

.qft-n-slate {
    background: #f8fafc;
    border-color: #cbd5e1;
    color: #475569;
}

html.dark .qft-n-blue {
    background: #17253f;
    border-color: #29406b;
    color: #93c5fd;
}

html.dark .qft-n-green {
    background: #12291c;
    border-color: #1f5133;
    color: #86efac;
}

html.dark .qft-n-amber {
    background: #33280f;
    border-color: #6b5117;
    color: #fcd34d;
}

html.dark .qft-n-red {
    background: #331617;
    border-color: #6b2427;
    color: #fca5a5;
}

html.dark .qft-n-purple {
    background: #241b3d;
    border-color: #43317a;
    color: #c4b5fd;
}

html.dark .qft-n-teal {
    background: #122b29;
    border-color: #1d534f;
    color: #5eead4;
}

html.dark .qft-n-slate {
    background: #2b2926;
    border-color: #4a443e;
    color: #cbd5e1;
}

/* ── Script sizing ────────────────────────────────────────────────────────
   Arabic needs a little more size and a lot more leading than Latin at the same
   px to be comfortable rather than merely present; Bangla and Hindi both carry a
   headline stroke that crowds the line above it. */

.qft[lang="ar"] {
    font-size: 0.88rem;
    line-height: 1.95;
}

    .qft[lang="ar"] .qft-node-text b {
        font-size: 0.82rem;
    }

    .qft[lang="ar"] .qft-node-text span {
        font-size: 0.72rem;
    }

.qft[lang="bn"],
.qft[lang="hi"] {
    line-height: 1.85;
}

/* ── RTL: the Arabic mirror ─────────────────────────────────────────────
   dir="rtl" on the layout root already flips flex/grid direction, table column
   order and floats of the document flow. What it cannot flip is physical CSS
   (margin-left, border-left, right:20px) and a few left-pinned labels, so those
   are mirrored here. Numbers, codes and dates stay put — digits read left to
   right inside Arabic copy, and mirroring them would misread the money. */
[dir="rtl"] .qf-push { margin-left: 0; margin-right: auto; }
[dir="rtl"] .qf-modal-x { margin-left: 0; margin-right: auto; }
[dir="rtl"] .qf-table th,
[dir="rtl"] .qf-table td { text-align: right; }
[dir="rtl"] .qf-table .qf-mid { text-align: center; }
[dir="rtl"] .qf-ev,
[dir="rtl"] .qf-chip-row,
[dir="rtl"] .qf-card,
[dir="rtl"] .qf-cal-cell { text-align: right; }
[dir="rtl"] .qf-ev-meet { border-left: none; border-right: 3px solid #7c3aed; }
[dir="rtl"] .qf-ev-remind { border-left: none; border-right: 3px solid #2563eb; }
[dir="rtl"] .qf-ev-task { border-left: none; border-right: 3px solid #0891b2; }
[dir="rtl"] .qf-ev-follow { border-left: none; border-right: 3px solid #d97706; }
[dir="rtl"] .qf-ev-invoice { border-left: none; border-right: 3px solid #dc2626; }
[dir="rtl"] .qf-ev-milestone { border-left: none; border-right: 3px solid #16a34a; }
[dir="rtl"] .qf-ev-recur { border-left: none; border-right: 3px solid #4f46e5; }
[dir="rtl"] .qfc-panel { right: auto !important; left: 20px !important; }
[dir="rtl"] .hdr-drop { right: auto !important; left: 0 !important; }
[dir="rtl"] .hdr-drop-fixed { right: auto !important; left: 20px !important; }
