/* ═══════════════════════════════════════════════════════════════════════════
   InvoiceNET Customer Portal — one stylesheet, mobile-first.

   The layout flips at 900px and nowhere else:
     • phone   → top bar + scrolling content + fixed bottom tab bar
     • desktop → fixed left sidebar + content, no bottom bar

   Everything is driven by the custom properties below, so a reseller can
   rebrand the portal by overriding :root and nothing else.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
    --brand: #0f3d5c;
    --brand-dark: #0a2c43;
    --brand-light: #1b5c88;
    --accent: #0f9b8e;
    --accent-soft: #e6f5f3;

    --bg: #f4f6f8;
    --surface: #ffffff;
    --surface-alt: #f9fafb;
    --border: #e2e6ea;
    --text: #16232e;
    --text-muted: #66757f;
    --text-faint: #93a1ab;

    --ok: #1a7f4b;
    --ok-soft: #e7f5ed;
    --warn: #a86200;
    --warn-soft: #fdf2e2;
    --danger: #b3261e;
    --danger-soft: #fdecea;
    /* Foreground to go with --info-soft. Same value the info banner already
       uses, promoted to a token so the badge and the banner cannot drift. */
    --info: #123f5e;
    --info-soft: #e8f1f8;

    --radius: 12px;
    --radius-sm: 8px;
    --shadow: 0 1px 2px rgba(16, 35, 47, .06), 0 4px 12px rgba(16, 35, 47, .06);
    --shadow-lg: 0 8px 30px rgba(16, 35, 47, .14);

    --sidebar-w: 248px;
    --topbar-h: 56px;
    --tabbar-h: 60px;

    --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    font-family: var(--font);
    font-size: 15px;
    line-height: 1.45;
    color: var(--text);
    background: var(--bg);
    -webkit-text-size-adjust: 100%;
}

h1, h2, h3, h4 { margin: 0 0 .5rem; font-weight: 650; line-height: 1.25; }
h1 { font-size: 1.35rem; }
h2 { font-size: 1.1rem; }
h3 { font-size: .98rem; }
p { margin: 0 0 .75rem; }
a { color: var(--brand-light); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── boot screen ──────────────────────────────────────────────────────── */

.boot {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    color: var(--text-muted);
}

.boot__spinner, .spinner {
    width: 34px;
    height: 34px;
    border: 3px solid var(--border);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: spin .8s linear infinite;
}

.spinner--sm { width: 18px; height: 18px; border-width: 2px; }

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

/* ── app shell ────────────────────────────────────────────────────────── */

.shell { min-height: 100vh; }

.topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    height: var(--topbar-h);
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: 0 1rem;
    background: var(--brand);
    color: #fff;
    box-shadow: var(--shadow);
}

.topbar__title {
    font-weight: 650;
    font-size: 1.02rem;
    flex: 1;
    min-width: 0;
    text-align: left;
}
.topbar__title span {
    display: block;
    font-size: .72rem;
    font-weight: 400;
    opacity: .82;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topbar__title strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Same line, now a button — reset the UA styling, keep the layout. */
.topbar__title--action {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    color: inherit;
    font: inherit;
    font-weight: 650;
    cursor: pointer;
    border-radius: var(--radius-sm);
}
.topbar__title--action:hover span { opacity: 1; text-decoration: underline; }
.topbar__title--action:focus-visible { outline: 2px solid rgba(255, 255, 255, .7); outline-offset: 2px; }

.topbar__btn {
    background: rgba(255, 255, 255, .14);
    border: 0;
    color: #fff;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    font-size: 1rem;
    cursor: pointer;
    display: grid;
    place-items: center;
}
.topbar__btn:hover { background: rgba(255, 255, 255, .24); }

.content {
    padding: 1rem;
    padding-bottom: calc(var(--tabbar-h) + 1.5rem + env(safe-area-inset-bottom));
    max-width: 1180px;
    margin: 0 auto;
}

/* ── bottom tab bar (phone) ───────────────────────────────────────────── */

.tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    display: grid;
    grid-auto-flow: column;
    background: var(--surface);
    border-top: 1px solid var(--border);
}

.tabbar a {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    font-size: .68rem;
    color: var(--text-muted);
    text-decoration: none;
    padding-top: 4px;
}

.tabbar a.active { color: var(--brand); font-weight: 600; }
.tabbar .tabbar__icon { font-size: 1.15rem; line-height: 1; }

/* ── sidebar (desktop) ────────────────────────────────────────────────── */

.sidebar { display: none; }

@media (min-width: 900px) {
    .tabbar { display: none; }

    .sidebar {
        display: flex;
        flex-direction: column;
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        width: var(--sidebar-w);
        background: var(--brand-dark);
        color: #dbe6ee;
        padding: 1rem 0;
        z-index: 50;
        overflow-y: auto;
    }

    .sidebar__brand {
        padding: .25rem 1.25rem 1.25rem;
        font-weight: 700;
        font-size: 1.05rem;
        color: #fff;
    }
    .sidebar__brand sup { font-size: .55em; top: -.55em; }
    .sidebar__brand span { display: block; font-weight: 400; font-size: .74rem; opacity: .7; margin-top: .15rem; }

    .sidebar__group {
        padding: 1rem 1.25rem .35rem;
        font-size: .68rem;
        text-transform: uppercase;
        letter-spacing: .07em;
        color: #7f9bb0;
    }

    .sidebar a {
        display: flex;
        align-items: center;
        gap: .65rem;
        padding: .55rem 1.25rem;
        color: #dbe6ee;
        text-decoration: none;
        font-size: .92rem;
        border-left: 3px solid transparent;
    }
    .sidebar a:hover { background: rgba(255, 255, 255, .06); text-decoration: none; }
    .sidebar a.active { background: rgba(255, 255, 255, .1); border-left-color: var(--accent); color: #fff; font-weight: 600; }

    .sidebar__foot { margin-top: auto; padding: 1rem 1.25rem 0; font-size: .74rem; color: #7f9bb0; }

    .sidebar__switch {
        display: block;
        width: 100%;
        margin-top: .6rem;
        padding: .35rem .5rem;
        background: rgba(255, 255, 255, .08);
        border: 1px solid rgba(255, 255, 255, .14);
        border-radius: var(--radius-sm);
        color: #dbe6ee;
        font: inherit;
        font-size: .74rem;
        cursor: pointer;
    }
    .sidebar__switch:hover { background: rgba(255, 255, 255, .16); }

    .sidebar__powered {
        margin: .7rem 0 0;
        padding-top: .7rem;
        border-top: 1px solid rgba(255, 255, 255, .12);
        font-size: .72rem;
        color: #7f9bb0;
    }
    .sidebar__powered a { color: #a9c6da; }

    /* The mobile page foot is redundant next to the sidebar one. */
    .app-foot { display: none; }

    .shell { padding-left: var(--sidebar-w); }
    .topbar { padding-left: 1.5rem; }
    .content { padding: 1.5rem; padding-bottom: 3rem; }
}

/* ── cards, sections ──────────────────────────────────────────────────── */

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 1rem;
    margin-bottom: 1rem;
}

.card--flush { padding: 0; overflow: hidden; }
.card__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .85rem 1rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface-alt);
}
.card__head h2, .card__head h3 { margin: 0; }
.card__body { padding: 1rem; }

.section-title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin: 1.25rem 0 .6rem;
}
.section-title h2 { margin: 0; }

/* ── stat tiles ───────────────────────────────────────────────────────── */

.stats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .75rem;
    margin-bottom: 1rem;
}

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

.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: .8rem .9rem;
    box-shadow: var(--shadow);
}

.stat__label { font-size: .72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; }
.stat__value { font-size: 1.28rem; font-weight: 680; margin-top: .15rem; letter-spacing: -.01em; }
.stat__hint { font-size: .74rem; color: var(--text-faint); margin-top: .1rem; }
.stat--danger .stat__value { color: var(--danger); }
.stat--ok .stat__value { color: var(--ok); }
.stat--warn .stat__value { color: var(--warn); }

/* ── list rows (the mobile substitute for a table) ────────────────────── */

.rows { display: flex; flex-direction: column; }

.row-item {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    padding: .8rem 1rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    text-align: left;
    width: 100%;
    border-left: 0;
    border-right: 0;
    border-top: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.row-item:last-child { border-bottom: 0; }
.row-item:hover { background: var(--surface-alt); }
.row-item--static { cursor: default; }
.row-item--static:hover { background: var(--surface); }

/* Post-dated cheque already given against a bill. Tinted like a warning
   rather than muted like metadata — a customer scanning an overdue list needs
   to SEE that this one is already covered. */
.bill-cheque {
    display: inline-block;
    margin-top: .2rem;
    padding: .1rem .4rem;
    border-radius: 4px;
    font-size: .74rem;
    background: var(--warn-soft);
    color: var(--warn);
    overflow-wrap: anywhere;
}

/* A list row with an action beside it. The row button keeps all the width it
   had; the action is only as wide as it needs. */
.row-pay {
    display: flex;
    align-items: center;
    border-bottom: 1px solid var(--border);
}
.row-pay:last-child { border-bottom: 0; }
.row-pay > .row-item {
    flex: 1 1 auto;
    min-width: 0;
    border-bottom: 0;
}
.row-pay__btn {
    flex: 0 0 auto;
    margin-right: 1rem;
    margin-left: .5rem;
}

/* Development payment simulator. Deliberately unlike every other card —
   a test affordance that looked like the real checkout would be the problem. */
.sim-card {
    border: 2px dashed var(--warn);
    background: var(--warn-soft);
}
.sim-card__tag {
    display: inline-block;
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--warn);
    margin-bottom: .4rem;
}
.sim-card h3 { margin: 0 0 .25rem; }

/* Action strip at the foot of a flush card. */
.card__foot {
    padding: .7rem 1rem;
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: flex-end;
}

.row-item__main { flex: 1; min-width: 0; }
.row-item__title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-item__sub { font-size: .78rem; color: var(--text-muted); margin-top: .12rem; }
.row-item__side { text-align: right; white-space: nowrap; }
.row-item__amount { font-weight: 650; }

/* ── tables (desktop-friendly, scroll on phone) ───────────────────────── */

.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

table.data {
    width: 100%;
    border-collapse: collapse;
    font-size: .88rem;
    min-width: 560px;
}

table.data th, table.data td {
    padding: .55rem .7rem;
    border-bottom: 1px solid var(--border);
    text-align: left;
    vertical-align: top;
}

table.data th {
    background: var(--surface-alt);
    font-size: .74rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    font-weight: 650;
    white-space: normal;
    position: sticky;
    top: 0;
}

/* Sortable headers. The arrow marks the sorted column, so the header needs
   no extra width reserved for one that is not shown. */
table.data th.th-sort { cursor: pointer; user-select: none; }
table.data th.th-sort:hover { color: var(--text); }

/* A group heading inside the body. It reads as a band across the table rather
   than a row of data, so it cannot be mistaken for a product. */
table.data tr.row-group > td {
    background: color-mix(in srgb, var(--surface-alt) 80%, var(--brand) 20%);
    font-weight: 650;
    letter-spacing: .01em;
    border-bottom: 1px solid var(--border);
}

table.data tr.row-group:hover > td { background: color-mix(in srgb, var(--surface-alt) 80%, var(--brand) 20%); }

.row-group__count {
    margin-left: .5rem;
    font-weight: 500;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.pricelist__groupby {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    margin: 0 0 .6rem;
    cursor: pointer;
}

/* Numbers right-align, everywhere, same as the ERP client. */
table.data td.num, table.data th.num, .num { text-align: right; font-variant-numeric: tabular-nums; }
table.data tfoot td { font-weight: 650; background: var(--surface-alt); }
table.data tbody tr:hover { background: var(--surface-alt); }

/* ── badges ───────────────────────────────────────────────────────────── */

.badge {
    display: inline-block;
    padding: .12rem .5rem;
    border-radius: 999px;
    font-size: .7rem;
    font-weight: 650;
    background: var(--info-soft);
    color: var(--brand);
    white-space: nowrap;
}
/* Document-status badges. See Services/DocStatusStyle.cs for which status maps
   to which — the colours are chosen there, not per page.
     ok      settled, nothing to do        warn   held / rejected / needs a look
     info    live and on track             danger cancelled only
   `info` is a real colour rather than grey: an open order is the normal case,
   and grey reads as "nothing is happening". */
.badge--ok { background: var(--ok-soft); color: var(--ok); }
.badge--warn { background: var(--warn-soft); color: var(--warn); }
.badge--danger { background: var(--danger-soft); color: var(--danger); }
.badge--info { background: var(--info-soft); color: var(--info); }
.badge--muted { background: var(--surface-alt); color: var(--text-muted); }

/* ── forms ────────────────────────────────────────────────────────────── */

.field { margin-bottom: .9rem; }
.field label { display: block; font-size: .78rem; color: var(--text-muted); margin-bottom: .25rem; font-weight: 600; }

input[type=text], input[type=password], input[type=email], input[type=tel],
input[type=number], input[type=date], input[type=search], select, textarea {
    width: 100%;
    padding: .58rem .7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font: inherit;
    background: var(--surface);
    color: var(--text);
    /* 16px stops iOS Safari zooming the page on focus. */
    font-size: 16px;
}

input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--brand-light);
    box-shadow: 0 0 0 3px rgba(27, 92, 136, .14);
}

input[type=number], .input--num { text-align: right; font-variant-numeric: tabular-nums; }
textarea { min-height: 84px; resize: vertical; }

.form-grid { display: grid; gap: 0 1rem; grid-template-columns: 1fr; }
@media (min-width: 720px) { .form-grid--2 { grid-template-columns: 1fr 1fr; } }

.filters {
    display: flex;
    flex-wrap: wrap;
    gap: .6rem;
    margin-bottom: 1rem;
}
.filters > * { flex: 1 1 160px; min-width: 0; }

/* ── buttons ──────────────────────────────────────────────────────────── */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    padding: .58rem 1rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    background: var(--surface);
    color: var(--text);
    border-color: var(--border);
    min-height: 42px;
}
.btn:hover:not(:disabled) { background: var(--surface-alt); }
.btn:disabled { opacity: .55; cursor: not-allowed; }

.btn--primary { background: var(--brand); color: #fff; border-color: var(--brand); }
.btn--primary:hover:not(:disabled) { background: var(--brand-light); }
.btn--accent { background: var(--accent); color: #fff; border-color: var(--accent); }
.btn--danger { background: var(--danger); color: #fff; border-color: var(--danger); }
.btn--ghost { background: transparent; border-color: transparent; color: var(--brand-light); }
.btn--sm { padding: .3rem .6rem; min-height: 34px; font-size: .82rem; }
.btn--block { width: 100%; }

.btn-row { display: flex; gap: .6rem; flex-wrap: wrap; }
.btn-row--end { justify-content: flex-end; }

/* ── banners ──────────────────────────────────────────────────────────── */

.banner {
    display: flex;
    align-items: flex-start;
    gap: .6rem;
    padding: .7rem .85rem;
    border-radius: var(--radius-sm);
    margin-bottom: 1rem;
    font-size: .88rem;
    border: 1px solid transparent;
}
.banner--error { background: var(--danger-soft); border-color: #f3c8c4; color: #7d1a15; }
.banner--warn { background: var(--warn-soft); border-color: #f0d9b3; color: #7a4700; }
.banner--ok { background: var(--ok-soft); border-color: #bde0cc; color: #10603a; }
.banner--info { background: var(--info-soft); border-color: #c6dcec; color: #123f5e; }

/* A newly shown message pulses once, so a banner that was already on screen
   still reads as new. Colour-independent — it lifts and settles rather than
   flashing a hue, which would fight the severity colour. */
.banner--reveal { animation: banner-reveal .9s ease-out 1; }

@keyframes banner-reveal {
    0%   { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
    18%  { transform: scale(1.012); box-shadow: 0 6px 18px -8px rgba(0, 0, 0, .45); }
    100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
}

/* Anyone who has asked for less motion gets the scroll but not the pulse. */
@media (prefers-reduced-motion: reduce) {
    .banner--reveal { animation: none; }
}
.banner__close { margin-left: auto; background: none; border: 0; cursor: pointer; font-size: 1rem; color: inherit; opacity: .6; }
.banner__close:hover { opacity: 1; }

/* ── empty / loading states ───────────────────────────────────────────── */

.empty {
    text-align: center;
    padding: 2.5rem 1rem;
    color: var(--text-muted);
}
.empty__icon { font-size: 2rem; opacity: .5; }
.empty h3 { margin: .6rem 0 .25rem; color: var(--text); }
.empty p { font-size: .88rem; max-width: 34ch; margin: 0 auto; }

.loading { display: flex; align-items: center; gap: .6rem; padding: 1.5rem; color: var(--text-muted); justify-content: center; }

/* ── login ────────────────────────────────────────────────────────────── */

.login {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 1.25rem;
    background: linear-gradient(160deg, var(--brand) 0%, var(--brand-dark) 100%);
}

.login__card {
    width: 100%;
    max-width: 400px;
    background: var(--surface);
    border-radius: 16px;
    box-shadow: var(--shadow-lg);
    padding: 1.5rem;
}

/* The chooser lists companies, so it needs more room than a sign-in form. */
.login__card--wide { max-width: 520px; }

.chooser {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: .85rem;
    margin-bottom: .75rem;
    background: var(--surface);
}
.chooser--current { border-color: var(--accent); background: var(--accent-soft); }
.chooser__head { display: flex; align-items: flex-start; justify-content: space-between; gap: .6rem; }
.chooser__name { font-weight: 650; }
.chooser__sub { font-size: .78rem; color: var(--text-muted); margin-top: .1rem; }
.chooser__branches { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: .7rem; }

.login__brand { text-align: center; margin-bottom: 1.25rem; }
.login__brand h1 { margin: 0; color: var(--brand); font-size: 1.5rem; letter-spacing: -.01em; }
/* The ® rides high and small rather than sitting on the baseline at full size. */
.login__brand h1 sup { font-size: .5em; top: -.6em; margin-left: .05em; }
.login__brand p { color: var(--text-muted); font-size: .9rem; margin: .3rem 0 0; font-weight: 550; }

.login__foot {
    margin: 1.1rem 0 0;
    padding-top: .9rem;
    border-top: 1px solid var(--border);
    text-align: center;
    font-size: .78rem;
    color: var(--text-faint);
}
.login__foot a { color: var(--brand-light); }

/* ── sign-up request ──────────────────────────────────────────────────────
   Longer than any other card in the portal, so it earns headings and a
   two-up row on anything wider than a phone. Everything below reuses .field
   and .btn — only the arrangement is new. */

.signup__head { margin: 0 0 .35rem; font-size: 1.15rem; }
.signup__lead { margin: 0 0 1.1rem; }

.signup__section {
    margin: 1.2rem 0 .6rem;
    padding-bottom: .3rem;
    border-bottom: 1px solid var(--border);
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* The asterisk is the only thing marking a field compulsory, so it has to
   read as deliberate rather than as a stray character. */
.signup__req { color: #b3261e; font-weight: 700; }

.signup__pair,
.signup__triple { display: grid; gap: 0 .75rem; }

@media (min-width: 560px) {
    .signup__pair { grid-template-columns: 1fr 1fr; }
    .signup__triple { grid-template-columns: 1fr 1fr .7fr; }
}

.signup__captcha {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: center;
    gap: .5rem .6rem;
}

.signup__captcha-img {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 150px;
    min-height: 52px;
    padding: .2rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: #f4f6f8;
    /* The SVG carries its own width; this keeps it inside the card on a
       narrow phone rather than pushing the row sideways. */
    overflow: hidden;
}

.signup__captcha-img svg { max-width: 100%; height: auto; }

.signup__refresh { padding: .35rem .6rem; font-size: 1.1rem; line-height: 1; }

/* The answer box keeps its label above it like every other field, so the row
   aligns on the input rather than on the text. */
.signup__captcha-field { margin-bottom: 0; }

.signup__foot { margin: 1rem 0 0; text-align: center; }

@media (max-width: 559px) {
    /* Stacked on a phone: three columns of captcha would leave the answer box
       too narrow to read what was typed. */
    .signup__captcha { grid-template-columns: 1fr auto; }
    .signup__captcha-field { grid-column: 1 / -1; margin-top: .4rem; }
}

/* Signed-in pages, phone layout: sits under the page content, above the tab
   bar. Hidden from 900px up, where the sidebar carries it instead. */
.app-foot {
    margin: 1.5rem 0 0;
    padding-top: .9rem;
    border-top: 1px solid var(--border);
    text-align: center;
    font-size: .74rem;
    color: var(--text-faint);
}
.app-foot a { color: var(--brand-light); }

/* ── cart / order ─────────────────────────────────────────────────────── */

.cart-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
    z-index: 35;
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .6rem 1rem;
    background: var(--brand);
    color: #fff;
    box-shadow: 0 -2px 12px rgba(0, 0, 0, .18);
}
.cart-bar__text { flex: 1; font-size: .88rem; }
.cart-bar__text strong { display: block; font-size: 1rem; }

@media (min-width: 900px) {
    .cart-bar { bottom: 0; left: var(--sidebar-w); }
}

.qty {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--surface);
}
.qty button {
    width: 34px;
    height: 34px;
    border: 0;
    background: var(--surface-alt);
    font-size: 1rem;
    cursor: pointer;
    color: var(--text);
}
.qty button:hover { background: var(--border); }
.qty input {
    width: 62px;
    border: 0;
    text-align: center;
    font-size: 15px;
    padding: .25rem;
}
.qty input:focus { box-shadow: none; }

/* ── product list ─────────────────────────────────────────────────────── */

.product {
    display: flex;
    gap: .75rem;
    align-items: flex-start;
    padding: .85rem 1rem;
    border-bottom: 1px solid var(--border);
}
.product:last-child { border-bottom: 0; }
.product__main { flex: 1; min-width: 0; }
.product__name { font-weight: 600; }
/* Item description (OpStock.Alias). Darker than the code/unit line — it is
   what the customer reads to recognise the item — and wraps rather than
   truncating, because a description cut in half is worse than a taller row. */
.product__desc {
    font-size: .82rem;
    color: var(--text);
    margin-top: .12rem;
    overflow-wrap: anywhere;
}
.product__meta { font-size: .76rem; color: var(--text-muted); margin-top: .1rem; }

/* Same description, inside a table cell (price list). */
.cell-desc {
    font-size: .76rem;
    color: var(--text-muted);
    margin-top: .1rem;
    overflow-wrap: anywhere;
}
.product__price { font-weight: 650; white-space: nowrap; }
.product__side { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: .35rem; }

/* ── support thread ───────────────────────────────────────────────────── */

/* The BUBBLES scroll; everything under them — the typing hint, the composer
   and its buttons — stays where the customer left it. A thread that grows the
   card pushes the reply box off the bottom of the screen, so answering a long
   conversation means scrolling past the whole history first.

   overscroll-behavior: contain stops the page taking over once the thread
   reaches its end, which is what makes it feel like a pane rather than a
   stretch of page. */
.thread {
    display: flex;
    flex-direction: column;
    gap: .7rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* 46vh, not more: the ticket header above and the composer below have to
       fit too, and on a 360x640 phone the fixed tab bar takes another 60px.
       Measured — at 52vh the Send button sat under the tab bar on that size. */
    max-height: min(46vh, 440px);
    /* Room for the scrollbar so a right-aligned bubble is not clipped by it. */
    padding-right: .35rem;
}

.msg {
    max-width: 88%;
    padding: .6rem .75rem;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--surface-alt);
}
/* The customer's own messages sit right, the supplier's left — the shape
   everyone already reads as a conversation. */
.msg--mine { align-self: flex-end; background: var(--accent-soft); border-color: #bfe4df; }
.msg--theirs { align-self: flex-start; }
.msg__meta { font-size: .72rem; color: var(--text-muted); margin-bottom: .25rem; }
/* Customers type paragraphs; keep their line breaks without letting a long
   unbroken string stretch the card. */
.msg__body { white-space: pre-wrap; overflow-wrap: anywhere; font-size: .92rem; }

@media (min-width: 720px) {
    .msg { max-width: 74%; }
    /* More room on a desktop — no tab bar to clear — but not so much that the
       Send button drops below the fold on a 720px-tall window. Measured. */
    .thread { max-height: min(54vh, 560px); }
}

/* ── chart ────────────────────────────────────────────────────────────── */

.chart { width: 100%; height: 160px; display: block; }
.chart__bar { fill: var(--brand-light); }
.chart__bar--now { fill: var(--accent); }
.chart__label { font-size: 9px; fill: var(--text-muted); }

/* ── misc ─────────────────────────────────────────────────────────────── */

.muted { color: var(--text-muted); }
.small { font-size: .8rem; }
.right { text-align: right; }
.nowrap { white-space: nowrap; }
.mono { font-family: var(--mono); }
.stack > * + * { margin-top: .75rem; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.hide-sm { display: none; }
@media (min-width: 720px) { .hide-sm { display: revert; } .hide-lg { display: none; } }

.kv { display: grid; grid-template-columns: minmax(90px, 38%) 1fr; gap: .35rem .75rem; font-size: .88rem; }
.kv dt { color: var(--text-muted); }
.kv dd { margin: 0; font-weight: 550; overflow-wrap: anywhere; }

.sheet-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(12, 24, 33, .45);
    z-index: 60;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}
.sheet {
    background: var(--surface);
    width: 100%;
    max-width: 560px;
    max-height: 88vh;
    overflow-y: auto;
    border-radius: 16px 16px 0 0;
    padding: 1rem 1rem calc(1rem + env(safe-area-inset-bottom));
}
@media (min-width: 720px) {
    .sheet-backdrop { align-items: center; }
    .sheet { border-radius: var(--radius); max-height: 84vh; }
}

#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    background: #fde9a9;
    padding: .8rem 1.2rem;
    box-shadow: 0 -1px 3px rgba(0, 0, 0, .2);
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: .9rem; top: .6rem; }

@media print {
    .topbar, .tabbar, .sidebar, .btn, .filters, .cart-bar { display: none !important; }
    .shell { padding-left: 0; }
    .content { padding: 0; max-width: none; }
    .card { box-shadow: none; border: 0; break-inside: avoid; }
    table.data { min-width: 0; font-size: .78rem; }
    /* Repeat the header on every printed page — same rule the ERP prints by. */
    table.data thead { display: table-header-group; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MODERN UI LAYER — cosmetics only, appended last so it wins ties.

   Same philosophy as the ERP client's modernization: no layout, sizing,
   breakpoint or behavioural rule is touched — this block only refines how
   the existing classes LOOK (depth, gradients, radii, focus rings, motion).
   Everything keys off the :root tokens above, so a reseller rebrand still
   only overrides :root. Remove this whole block and the portal falls back
   to the flat look, fully functional.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
    --radius: 14px;
    --radius-lg: 18px;
    --shadow: 0 1px 2px rgba(16, 35, 47, .05), 0 6px 18px rgba(16, 35, 47, .07);
    --shadow-lg: 0 12px 40px rgba(16, 35, 47, .18);
    --ring: rgba(15, 155, 142, .22);
    --brand-grad: linear-gradient(135deg, var(--brand-dark) 0%, var(--brand) 55%, var(--brand-light) 100%);
    --accent-grad: linear-gradient(135deg, var(--accent) 0%, #12b3a4 100%);
}

/* ── shell chrome ── */
.topbar {
    background: var(--brand-grad);
    box-shadow: 0 2px 14px rgba(10, 44, 67, .28);
}

/* Phone tab bar: frosted glass so content scrolls "under" it. */
.tabbar {
    background: rgba(255, 255, 255, .82);
    -webkit-backdrop-filter: blur(12px) saturate(1.3);
    backdrop-filter: blur(12px) saturate(1.3);
    border-top: 1px solid rgba(226, 230, 234, .8);
    box-shadow: 0 -4px 18px rgba(16, 35, 47, .08);
}
.tabbar a { transition: color .15s ease, transform .15s ease; }
.tabbar a.active .tabbar__icon { transform: translateY(-1px); }

@media (min-width: 900px) {
    .sidebar { background: linear-gradient(180deg, var(--brand-dark) 0%, #0c3450 70%, var(--brand-dark) 100%); }
    /* Nav entries become soft pills; the accent edge stays as the active mark. */
    .sidebar a {
        margin: .1rem .6rem;
        padding: .55rem .85rem;
        border-radius: 10px;
        border-left: 3px solid transparent;
        transition: background .15s ease, color .15s ease;
    }
    .sidebar a.active { border-left-color: var(--accent); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08); }
}

/* ── surfaces ── */
.card {
    border-radius: var(--radius);
    border-color: color-mix(in srgb, var(--border) 80%, transparent);
    box-shadow: var(--shadow);
}
.card__head { background: linear-gradient(180deg, var(--surface-alt), transparent); }

.stat {
    border-radius: var(--radius);
    background: linear-gradient(180deg, var(--surface) 0%, var(--surface-alt) 140%);
    position: relative;
    overflow: hidden;
    transition: transform .15s ease, box-shadow .15s ease;
}
.stat::after {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--accent-grad);
    opacity: .55;
}
.stat--danger::after { background: linear-gradient(90deg, var(--danger), #d9534a); }
.stat--warn::after { background: linear-gradient(90deg, var(--warn), #c98a2e); }
.stat--ok::after { background: linear-gradient(90deg, var(--ok), #27a065); }
.stat:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }

/* ── rows & products: a breath of motion on hover ── */
.row-item, .product { transition: background .12s ease; }

/* ── tables ── */
table.data th { background: color-mix(in srgb, var(--surface-alt) 92%, var(--brand) 8%); }
table.data tbody tr { transition: background .12s ease; }

/* ── buttons: pills with depth ── */
.btn {
    border-radius: 999px;
    transition: transform .12s ease, box-shadow .12s ease, background .12s ease, filter .12s ease;
}
.btn--primary { background: var(--brand-grad); border-color: transparent; }
.btn--primary:hover:not(:disabled) { background: var(--brand-grad); filter: brightness(1.08); box-shadow: 0 6px 18px rgba(15, 61, 92, .3); transform: translateY(-1px); }
.btn--accent { background: var(--accent-grad); border-color: transparent; }
.btn--accent:hover:not(:disabled) { filter: brightness(1.06); box-shadow: 0 6px 18px rgba(15, 155, 142, .3); transform: translateY(-1px); }
.btn--danger:hover:not(:disabled) { filter: brightness(1.06); transform: translateY(-1px); }
.btn:hover:not(:disabled) { transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0); box-shadow: none; }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); }

/* ── forms: accent focus ring ── */
input:focus, select:focus, textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--ring);
}

/* ── list rows: a row can carry several status badges (voucher / desk /
   billed); they wrap under the amount instead of widening the row ── */
.row-item__badges {
    display: flex;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: .3rem;
    margin-top: .25rem;
    white-space: normal;
    max-width: 180px;
}

/* ── filters: the Apply button keeps its own size and lines up with the
   inputs instead of stretching to the shared 160px flex basis ── */
.filters .filters__apply {
    flex: 0 0 auto;
    min-width: 96px;
    min-height: 42px;
    align-self: flex-end;
}

/* ── badges: true pills with a hairline ── */
.badge { border-radius: 999px; box-shadow: inset 0 0 0 1px rgba(16, 35, 47, .06); }

/* ── banners: severity edge ── */
.banner { border-left-width: 4px; border-radius: var(--radius-sm); }

/* ── login: the card floats on a branded wash ── */
.login { background: linear-gradient(160deg, var(--accent-soft) 0%, var(--bg) 40%, #e8eef3 100%); }
.login__card { border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); border: 1px solid rgba(226, 230, 234, .7); }

/* ── sheets: blurred backdrop, floating panel ── */
.sheet-backdrop {
    background: rgba(12, 24, 33, .45);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
}
.sheet { box-shadow: var(--shadow-lg); }
@media (min-width: 720px) { .sheet { border-radius: var(--radius-lg); } }

/* ── support thread bubbles ── */
.msg { border-radius: 14px; box-shadow: 0 1px 2px rgba(16, 35, 47, .05); }
.msg--mine { border-radius: 14px 14px 4px 14px; }
.msg--theirs { border-radius: 14px 14px 14px 4px; }

/* ── cart bar: gets the same glass as the tab bar ── */
.cart-bar {
    -webkit-backdrop-filter: blur(10px) saturate(1.2);
    backdrop-filter: blur(10px) saturate(1.2);
    box-shadow: 0 -6px 24px rgba(16, 35, 47, .14);
}

/* ── scrollbars (WebKit + Firefox) ── */
* { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--brand-light) 35%, transparent) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--brand-light) 30%, transparent); border-radius: 999px; }
*::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--brand-light) 50%, transparent); }

/* Motion is decoration here — honour the OS preference. */
@media (prefers-reduced-motion: reduce) {
    .btn, .stat, .tabbar a, .sidebar a, .row-item, .product, table.data tbody tr { transition: none !important; }
    .stat:hover, .btn:hover:not(:disabled) { transform: none; }
}

/* ── Terms & conditions ─────────────────────────────────────────────────────
   Shown full-screen as a gate before acceptance, and as an ordinary page
   afterwards, so it centres itself rather than assuming the shell's column. */
.terms {
    max-width: 52rem;
    margin: 0 auto;
    padding: 1rem 1rem 3rem;
}

.terms h1 { margin: 0 0 .25rem; font-size: 1.35rem; }
.terms__meta { margin: 0 0 .9rem; color: var(--text-muted); font-size: .85rem; }
.terms__head p { line-height: 1.55; }

.terms__section { margin-top: .75rem; }
.terms__section summary {
    cursor: pointer;
    font-weight: 600;
    padding: .15rem 0;
}
.terms__section p, .terms__section li { line-height: 1.55; }
.terms__section ul { margin: .4rem 0 .4rem 1.1rem; padding: 0; }
.terms__section li { margin-bottom: .3rem; }

/* In the FOOTER-LINK mode this simply sits under the document.
   `position: sticky` was tried here and does nothing: the panel is the LAST
   child of .terms, so its containing block ends where it ends and sticky has
   no range to travel in. It silently rendered as static, below every section —
   which is how the gate came to have no visible way out. */
.terms__accept { margin-top: 1rem; }

/* GATE MODE. This page is the whole application until the terms are answered,
   so the bar is pinned to the viewport and carries both ways out. Matched to
   the document column so it reads as part of the page rather than a banner. */
.terms__accept--pinned {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    width: min(52rem, calc(100% - 2rem));
    bottom: .75rem;
    z-index: 20;
    box-shadow: 0 -2px 14px rgba(0, 0, 0, .13);
    max-height: 60vh;
    overflow-y: auto;
}

/* Room for the pinned bar, so the last paragraph is readable rather than
   hidden behind it. Generous because the bar wraps on a narrow screen. */
.terms--gate { padding-bottom: 15rem; }

.terms__gatehead {
    border-left: 3px solid var(--accent, #0f766e);
    font-size: .9rem;
    line-height: 1.5;
}

/* The tick and its wording sit together — the sentence IS the consent, so it
   must not be able to scroll away from the box that records it. */
.terms__check {
    display: flex;
    gap: .6rem;
    align-items: flex-start;
    line-height: 1.5;
    margin-bottom: .85rem;
}
.terms__check input { width: auto; margin-top: .2rem; flex: 0 0 auto; }

.terms__actions { display: flex; flex-wrap: wrap; gap: .6rem; }
.terms__note { margin: .75rem 0 0; font-size: .85rem; }
.terms__error { color: var(--danger, #b3261e); margin: 0 0 .75rem; }

.terms__accepted {
    margin-top: 1rem;
    color: var(--text-muted);
    font-size: .9rem;
}

/* The tools sit with the title, and vanish on paper — .btn is already hidden
   by the print block above, this just keeps the row from leaving a gap. */
.terms__tools {
    float: right;
    display: flex;
    gap: .4rem;
}

@media print {
    .terms { max-width: none; padding: 0; }
    .terms__tools,
    .terms__accept { display: none !important; }

    /* A section the reader collapsed is still part of the document. Without
       this the browser prints the headings and drops the clauses under them —
       a terms copy missing its terms. */
    .terms__section > *:not(summary) { display: block !important; }
    .terms__section summary { list-style: none; font-weight: 700; }
    .terms__section summary::-webkit-details-marker { display: none; }

    /* Keep a clause with its heading wherever the page happens to break. */
    .terms__section { break-inside: avoid; page-break-inside: avoid; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Phone drawer — the full navigation the sidebar shows on desktop.

   The tab bar carries the five journeys people repeat all day; everything
   else (reports, policies, price list) lives behind the ☰ rather than being
   dropped from the phone entirely.
   ═══════════════════════════════════════════════════════════════════════════ */

.topbar__menu {
    flex: 0 0 auto;
    border: 0;
    background: rgba(255, 255, 255, .12);
    color: #fff;
    border-radius: 10px;
    width: 38px;
    height: 38px;
    font-size: 1.15rem;
    line-height: 1;
    cursor: pointer;
    margin-right: .5rem;
}

.topbar__menu:hover { background: rgba(255, 255, 255, .2); }

.drawer {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(84vw, 320px);
    z-index: 60;                  /* over the tab bar (40) and the cart bar */
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    background: linear-gradient(180deg, var(--brand-dark) 0%, #0c3450 70%, var(--brand-dark) 100%);
    color: #cfe0ec;
    padding: 1rem 0 1.25rem;

    /* Off-canvas by default. transform rather than left/display so the slide
       is composited and nothing reflows behind it. */
    transform: translateX(-100%);
    transition: transform .22s ease;
    box-shadow: 4px 0 24px rgba(0, 0, 0, .35);
}

.drawer--open { transform: translateX(0); }

.drawer__scrim {
    position: fixed;
    inset: 0;
    z-index: 55;
    border: 0;
    padding: 0;
    background: rgba(6, 24, 38, .5);
    cursor: pointer;
}

/* The drawer reuses the sidebar's own classes, which are only styled inside
   the desktop media query — so the parts it needs are restated here. */
.drawer .sidebar__brand {
    padding: 0 1.25rem 1rem;
    font-size: 1.05rem;
    font-weight: 700;
    color: #fff;
    line-height: 1.2;
}
.drawer .sidebar__brand sup { font-size: .55em; top: -.55em; }
.drawer .sidebar__brand span { display: block; font-weight: 400; font-size: .74rem; opacity: .7; margin-top: .15rem; }

.drawer .sidebar__group {
    padding: .9rem 1.25rem .3rem;
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: #7f9bb0;
}

.drawer a {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .6rem 1.25rem;
    color: #cfe0ec;
    text-decoration: none;
    border-left: 3px solid transparent;
}

.drawer a:hover { background: rgba(255, 255, 255, .06); text-decoration: none; }
.drawer a.active {
    background: rgba(255, 255, 255, .1);
    border-left-color: var(--accent);
    color: #fff;
    font-weight: 600;
}

.drawer .sidebar__foot { margin-top: auto; padding: 1rem 1.25rem 0; font-size: .74rem; color: #7f9bb0; }
.drawer .sidebar__switch {
    display: block;
    width: 100%;
    margin-top: .5rem;
    padding: .45rem .6rem;
    border: 0;
    border-radius: 8px;
    background: rgba(255, 255, 255, .1);
    color: #cfe0ec;
    cursor: pointer;
}
.drawer .sidebar__switch:hover { background: rgba(255, 255, 255, .16); }
.drawer .sidebar__powered { margin-top: .75rem; }
.drawer .sidebar__powered a { color: #a9c6da; }

/* From 900px the sidebar is permanently on screen, so neither the button nor
   the drawer has anything to do. */
@media (min-width: 900px) {
    .topbar__menu,
    .drawer,
    .drawer__scrim { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .drawer { transition: none; }
}

@media print {
    .drawer, .drawer__scrim, .topbar__menu { display: none !important; }
}
