/* ============================================================================
   OrderPoint — "Nocturne" design system
   ----------------------------------------------------------------------------
   A quiet, compact, dark interface for the customer ordering app.

     ground      near-neutral blue-grey  #161826
     surface     one step up             #232532
     accent      a single blurple        #9184d9 / #d2cefd (text)

   The accent is used as a line and a glow, never a flood: buttons are an accent
   outline on transparent, prices carry the accent, and separators are hairline
   rules that fade at their ends. Contrast comes from the tonal ramp, not from
   saturation. Inter throughout, with IBM Plex Mono reserved for the small
   letter-spaced labels. Every primary target sits in the bottom third of the
   screen, within a thumb's reach.

   No Bootstrap: the app loads on a phone in a cold shop, so every kilobyte of
   framework CSS we don't ship is a kilobyte faster.
   ============================================================================ */

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');

:root {
    /* ── Ground and surfaces ── */
    /* The dark ramp — a soft slate rather than near-black, so light text doesn't read as
       harsh against it. Cards keep a clear lift above the ground. */
    --surface: #363b50;        /* cards, inputs, header, sheets */
    --surface-2: #262a3a;      /* the page ground */
    --surface-3: #2f3446;      /* raised sheet / subtle fill */

    /* ── Ink ── */
    --ink: #e9e9ed;
    --ink-2: #b2b6ca;
    --ink-3: #75798c;
    --ink-4: #595d6c;

    /* ── Hairlines ── */
    --line: #3f424d;           /* visible borders: inputs, cards, frame */
    --line-2: #23252f;         /* subtle list separators */

    /* ── Accent: a single blurple, used as a line and a glow ── */
    --accent: #9184d9;         /* borders, active marks */
    --accent-ink: #d2cefd;     /* text and prices on the dark ground */
    --accent-2: #b5abfc;       /* secondary accent text */
    --accent-hover: #2b2741;   /* accent-900 tint, for hovers */
    --accent-soft: #2b2741;    /* accent tint fill */
    --accent-line: #423a6a;    /* accent-800 border */

    /* Errors are the one place a warm hue appears — desaturated, so it reads as
       a fault without shouting on a calm dark ground. */
    --urgent: #e0a1a1;
    --urgent-bg: #2a1f26;
    --urgent-line: #5c3a44;

    /* Offers reuse the accent — the mono palette has no second hue. */
    --offer: #b5abfc;

    /* ── Shape ── */
    --r-panel: 14px;
    --r-card: 8px;
    --r-row: 8px;
    --r-control: 8px;
    --r-chip: 8px;

    /* Elevation on a dark ground is a hairline edge plus ambient darkness, never
       a stack of soft shadows. */
    --shadow-card: 0 0 0 1px var(--line);
    --shadow-lift: 0 -24px 60px rgba(0, 0, 0, .55);
    --scrim: rgba(10, 11, 20, .6);

    /* ── Type ── */
    --font: 'Inter', system-ui, -apple-system, sans-serif;
    --font-mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', monospace;

    /* Safe-area aware bottom chrome, for phones with a home indicator. */
    --tab-h: calc(58px + env(safe-area-inset-bottom, 0px));
    --bar-h: calc(64px + env(safe-area-inset-bottom, 0px));
}

/* Wholesaler branding overrides --accent at runtime; everything else follows. */

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    font-family: var(--font);
    font-size: 15px;
    line-height: 1.5;
    color: var(--ink);
    background: var(--surface-2);
    -webkit-font-smoothing: antialiased;
    /* Stops iOS zooming when a form field is focused. */
    -webkit-text-size-adjust: 100%;
}

h1, h2, h3, h4 { margin: 0; font-weight: 500; letter-spacing: -.03em; color: var(--ink); }
h1 { font-size: 30px; }
h2 { font-size: 24px; }
h3 { font-size: 20px; }
p { margin: 0 0 10px; color: var(--ink-2); }
a { color: var(--accent-ink); text-decoration: none; }
a:hover { color: var(--accent-2); }

:focus { outline: none; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--accent) 30%, transparent); }

/* Numerics — prices, quantities, weights — line up with tabular figures. The
   design sets these in Inter, not a monospace, so they sit with the body text. */
.num, .price, .qty { font-variant-numeric: tabular-nums; }
.price { color: var(--accent-ink); font-weight: 600; letter-spacing: -.02em; }
.accent { color: var(--accent-ink); }

.muted { color: var(--ink-3); }
.small { font-size: 13px; }
.tiny { font-size: 11.5px; }
.eyebrow {
    font-family: var(--font-mono);
    font-size: 11px; font-weight: 500; letter-spacing: .16em;
    text-transform: uppercase; color: var(--ink-3);
}

/* ── Layout ────────────────────────────────────────────────────────── */

.op-shell { min-height: 100dvh; display: flex; flex-direction: column; }

.op-header {
    position: sticky; top: 0; z-index: 40;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line-2);
    padding: 12px 16px;
}
.op-header-row { display: flex; align-items: center; gap: 10px; }
.op-header img.logo { height: 28px; width: auto; }
.op-header .title { font-weight: 600; font-size: 16px; letter-spacing: -.015em; }

/* The slim next-delivery / cut-off line under the brand row: compact, always visible, and a
   tap-through to the full delivery-options page. */
.op-deliverystrip {
    display: flex; align-items: baseline; gap: 8px;
    margin-top: 8px; font-size: 12px; color: var(--ink-3);
    text-decoration: none;
}
.op-deliverystrip:hover { color: var(--ink-2); }
.op-deliverystrip .grow {
    flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.op-deliverystrip .left { color: var(--accent-ink); font-weight: 500; white-space: nowrap; }
.op-deliverystrip .chev { color: var(--ink-3); }
/* The supplier's own mark — a colour swatch stands in when there's no logo. */
.op-brand-mark {
    width: 26px; height: 26px; flex: none; border-radius: 7px;
    background: var(--accent);
}

.op-main { flex: 1; padding: 16px; padding-bottom: calc(var(--tab-h) + 20px); }
.op-main.no-tabs { padding-bottom: 20px; }
.op-main.has-bar { padding-bottom: calc(var(--tab-h) + var(--bar-h) + 12px); }

.container-narrow { max-width: 760px; margin: 0 auto; }

/* Ordering-for banner. Deliberately prominent: with an active-customer switcher
   the real risk is human — placing an order against the wrong account. */
.acting-as {
    display: flex; align-items: center; gap: 10px;
    background: var(--accent-soft);
    border: 1px solid var(--accent-line);
    color: var(--accent-ink);
    border-radius: var(--r-row);
    padding: 10px 13px;
    font-size: 13.5px;
    margin-bottom: 14px;
}
.acting-as strong { font-weight: 600; }
.acting-as button { margin-left: auto; }

/* Staff placing an order on a customer's behalf. Deliberately the urgent hue rather than the
   accent: this is the one banner that must survive being glanced past. An operator taking calls
   all morning who forgets which account they are on sends the wrong order to a real kitchen,
   and it is picked and loaded before anyone notices.

   This palette is monochrome apart from --urgent, so that IS the only way to make a band read
   as different from everything else. (An earlier version reached for --soon-bg, which exists in
   the admin console's stylesheet and not this one, and rendered transparent — a warning banner
   that looked like nothing at all.) */
.acting-as.staff {
    background: var(--urgent-bg);
    border-color: var(--urgent-line);
    color: var(--urgent);
    align-items: flex-start;
    flex-wrap: wrap;
}
.acting-as.staff strong { color: var(--urgent); }
.acting-as.staff button { color: var(--urgent); }

/* ── Cards and rows ────────────────────────────────────────────────── */

.card {
    background: var(--surface);
    border-radius: var(--r-card);
    padding: 16px;
}
.card + .card { margin-top: 12px; }
.card-h { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }

/* Panels group a list of rows on the plain ground — no fill, the rules do the
   work of separating them. */
.panel { border-radius: var(--r-card); overflow: hidden; }

.row {
    display: flex; align-items: center; gap: 14px;
    padding: 15px 0;
    background: transparent;
}
.row + .row { border-top: 1px solid var(--line-2); }
.row .grow { flex: 1; min-width: 0; }
.row .name { font-weight: 500; letter-spacing: -.01em; }
/* min-width:0 on the flex child so long product names truncate instead of
   forcing the row wider than a phone screen. */
.row .name, .row .meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .meta { font-size: 12.5px; color: var(--ink-3); margin-top: 3px; }

.list-sep > * + * { border-top: 1px solid var(--line-2); }

/* An info row that stacks its label above its value — for long values (a week's worth of
   day names) that would otherwise collide with the label on a narrow phone. */
.row.info-stack { flex-direction: column; align-items: flex-start; gap: 4px; }

/* Product rows get a more pronounced divider than the subtle list rule — a full
   catalogue scrolls past quickly and the eye needs a clear break between lines. */
.row.prow { padding: 17px 0; }
.row.prow + .row.prow { border-top: 1px solid var(--line); }


/* ── Buttons — an accent outline on transparent, never a fill ──────── */

.btn {
    appearance: none;
    font-family: inherit; font-size: 15px; font-weight: 500;
    border-radius: var(--r-control);
    background: transparent;
    border: 1px solid transparent;
    color: var(--ink);
    padding: 12px 18px;
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    /* 44px is the accepted minimum touch target; these are used in a cold shop
       with gloved or wet hands. */
    min-height: 44px;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.btn:disabled { opacity: .45; cursor: not-allowed; }

.btn-primary { border-color: var(--accent); color: var(--accent-ink); }
.btn-primary:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.btn-primary:active:not(:disabled) { background: color-mix(in srgb, var(--accent) 24%, transparent); }

.btn-secondary { border-color: var(--line); color: var(--ink-2); }
.btn-secondary:hover:not(:disabled) { background: color-mix(in srgb, var(--ink) 7%, transparent); }

.btn-ghost { background: transparent; color: var(--accent-ink); }
.btn-ghost:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 10%, transparent); }

.btn-sm { min-height: 36px; padding: 8px 13px; font-size: 13.5px; }
.btn-block { width: 100%; }

/* ── Inputs ────────────────────────────────────────────────────────── */

.field { margin-bottom: 14px; }
.field label {
    display: block; font-size: 12px; letter-spacing: .04em; font-weight: 500;
    color: var(--ink-3); margin-bottom: 7px;
}

.input, select.input, textarea.input {
    width: 100%;
    font-family: inherit;
    /* 16px minimum: anything smaller makes iOS zoom the viewport on focus. */
    font-size: 16px;
    color: var(--ink);
    caret-color: var(--accent);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    padding: 14px;
    min-height: 48px;
}
.input::placeholder { color: var(--ink-3); }
.input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

/* Native select: keeps the OS picker (best on a phone), but the closed control is fully
   themed — its own chrome removed and a custom accent chevron drawn in, so it reads as a
   filter rather than a second text box. Must sit AFTER the shared .input rule above, which
   lists select.input in its group at equal specificity and would otherwise win on order
   (its `background` shorthand clears the chevron, its `padding` the chevron gutter). The
   open option list is OS-drawn, so color-scheme hints it dark where the browser honours it. */
select.input {
    color-scheme: dark;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 42px;
    font-weight: 500;
    background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%239184d9'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M6%209l6%206%206-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 14px center;
    background-size: 18px;
}
select.input::-ms-expand { display: none; }
select.input:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }

/* The OPEN list, which is a separate problem from the closed control. Styling the select does
   nothing for its options: they inherit the browser's own defaults, which on Android render
   small and tightly packed — a wall of thin grey lines to hit with a thumb in a cold shop.
   Android Chrome honours font-size, padding, line-height and colour on an option, so they are
   set explicitly rather than left to the default.

   iOS draws a native wheel and ignores nearly all of this; there is no CSS fix there, and the
   16px on the select is what keeps that wheel legible. Desktop honours most of it. So this
   improves Android a lot, iOS not at all, and harms nothing. */
select.input option {
    font-size: 16px;
    line-height: 1.7;
    padding: 12px 14px;
    background: var(--surface);
    color: var(--ink);
}
select.input option:checked { color: var(--accent-ink); }

/* ── Badges and chips ──────────────────────────────────────────────── */

.badge {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 10px; font-weight: 500; letter-spacing: .09em; text-transform: uppercase;
    padding: 3px 7px; border-radius: 5px;
    white-space: nowrap;
}
.badge-offer { border: 1px solid var(--accent-line); color: var(--offer); }
.badge-weighed { border: 1px solid var(--line); color: var(--ink-2); }
.badge-urgent {
    text-transform: none; letter-spacing: 0; font-size: 13px; font-weight: 400;
    background: var(--urgent-bg); border: 1px solid var(--urgent-line); color: var(--urgent);
    border-radius: var(--r-control);
}

.chip {
    font-family: inherit; font-size: 13px; font-weight: 500;
    padding: 9px 15px; border-radius: var(--r-chip);
    border: 1px solid var(--line); background: transparent; color: var(--ink-2);
    cursor: pointer; min-height: 40px; white-space: nowrap;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.chip:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.chip.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-ink); }

.chip-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* Delivery vs collection is a MODE, not another option in a list, so it gets its own shape:
   one control split into equal halves rather than two loose chips that look exactly like the
   date choices below them. */
.segmented {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    border: 1px solid var(--line); border-radius: var(--r-control); overflow: hidden;
}
.segmented button {
    font-family: inherit; font-size: 14px; font-weight: 500;
    min-height: 44px; padding: 0 12px;
    border: 0; background: transparent; color: var(--ink-2); cursor: pointer;
    transition: background .15s ease, color .15s ease;
}
.segmented button + button { border-left: 1px solid var(--line); }
.segmented button:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.segmented button.on { background: var(--accent-soft); color: var(--accent-ink); font-weight: 600; }

/* Dates on an even grid. As wrapping chips they were ragged: "Tue 1 Sept" and "Thu 20 Aug"
   are different widths, so no two rows ever lined up and the block read as untidy. Equal
   columns also give every date the same tap target. */
.date-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.date-grid .chip { width: 100%; padding: 9px 6px; text-align: center; }

/* One rhythm for the whole "when do you need it" block, so the mode, the dates, the date
   field and the cut-off read as one stack rather than four separate ideas. */
.when-block > * + * { margin-top: 12px; }
.when-label { font-size: 12px; color: var(--ink-3); margin: 0 0 6px; }

/* ── Quantity entry ────────────────────────────────────────────────── */

.stepper { display: flex; align-items: center; gap: 12px; }
.stepper button {
    width: 52px; height: 52px; flex: none;
    border-radius: var(--r-control);
    border: 1px solid var(--line); background: transparent;
    font-size: 24px; font-weight: 400; color: var(--accent-ink); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .15s ease, border-color .15s ease;
}
.stepper button:last-of-type { border-color: var(--accent); }
.stepper button:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.stepper input {
    width: 66px; text-align: center;
    font-family: inherit; font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums;
    color: var(--ink); background: transparent;
    border: 1px solid var(--line); border-radius: var(--r-control);
    padding: 10px 4px; min-height: 52px;
}

/* Weighed goods can only ever be priced approximately — the butcher cuts to
   roughly the requested weight — so estimated totals are always marked. */
.approx::before { content: "approx "; color: var(--ink-3); font-weight: 400; }

/* The + affordance on a catalogue / usuals / offers row. */
.add-btn {
    width: 48px; height: 48px; flex: none;
    border-radius: 12px; border: 1px solid var(--accent);
    background: transparent; color: var(--accent-ink);
    font-size: 22px; cursor: pointer;
    display: grid; place-items: center;
}
.add-btn:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }

/* ── Prep notes / instructions ─────────────────────────────────────── */
/* A per-line note the supplier sees on the picking sheet. Generic wording — not every
   supplier is a butcher. Native <details> so it needs no script. */
.prep { width: 100%; margin-top: 12px; }
.prep summary {
    list-style: none; cursor: pointer;
    font-size: 12.5px; color: var(--accent-ink);
}
.prep summary::-webkit-details-marker { display: none; }
.prep summary::before { content: "＋ "; color: var(--ink-3); }
.prep[open] summary::before { content: "− "; }
.prep textarea { margin-top: 8px; font-size: 15px; min-height: 64px; }

/* A saved note shown read-only against a basket line. */
.line-note {
    margin-top: 9px; padding: 8px 11px;
    background: var(--surface); border-radius: var(--r-row);
    font-size: 12px; color: var(--accent-2);
}

/* ── Offline banner ────────────────────────────────────────────────── */
.offline-banner {
    background: var(--surface-3);
    border-bottom: 1px solid var(--line-2);
    color: var(--ink-2);
    font-size: 12.5px; text-align: center;
    padding: 9px 16px;
}

/* ── Cut-off strip — the real deadline carries the accent ──────────── */

.cutoff {
    padding: 14px 15px; border-radius: var(--r-card);
    background: linear-gradient(90deg, var(--accent-soft), var(--surface-3));
    border: 1px solid var(--accent-line);
}
/* Stacked, not two columns. The label is a whole sentence ("Order by 10:00pm for the earliest
   date") and the time beside it is another ("about 13 hours left"); pushed to opposite ends of
   a 375px phone they end up jammed against each other with no gap, reading as one run-on line.
   The time is the part that actually matters, so it gets its own line and the emphasis. */
.cutoff-head { display: flex; flex-direction: column; gap: 3px; margin-bottom: 10px; }
.cutoff-head .label { font-size: 13px; color: var(--accent-2); line-height: 1.4; }
.cutoff-head .time { font-size: 17px; font-weight: 600; color: var(--accent-ink); line-height: 1.2; }
.cutoff-track { height: 2px; background: var(--line-2); position: relative; margin-bottom: 9px; }
.cutoff-track > i {
    position: absolute; left: 0; top: 0; height: 2px;
    background: linear-gradient(90deg, var(--accent), rgba(145, 132, 217, 0));
}

/* ── Sticky bottom bar (review order) — sits above the tab bar ─────── */

.op-bar {
    position: fixed; left: 0; right: 0; z-index: 45;
    bottom: var(--tab-h);
    background: var(--surface-3);
    border-top: 1px solid var(--line-2);
    padding: 12px 16px;
    display: flex; align-items: center; gap: 12px;
}
.op-bar .total { font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }

/* ── Bottom tab bar ────────────────────────────────────────────────── */

.op-tabs {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    display: flex;
    background: var(--surface-2);
    border-top: 1px solid var(--line-2);
    padding-bottom: env(safe-area-inset-bottom, 0px);
}
.op-tab {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px;
    padding: 10px 0 9px;
    color: var(--ink-3); text-decoration: none;
    font-size: 10.5px; letter-spacing: .01em;
}
.op-tab svg { width: 20px; height: 20px; display: block; }
.op-tab:hover { color: var(--ink-2); }
.op-tab.active { color: var(--accent-ink); }

/* ── "powered by orderpoint." sign-off ─────────────────────────────── */

.op-foot {
    display: flex; align-items: center; justify-content: center; gap: 9px;
    padding: 26px 0 8px; color: var(--ink-4);
    font-size: 11.5px;
}
.op-foot .mark {
    display: grid; grid-template-columns: repeat(2, 7px); grid-template-rows: repeat(2, 7px); gap: 2px;
}
.op-foot .mark > i { border-radius: 1px; background: var(--line); }
.op-foot .mark > i:last-child { background: var(--accent); }

/* ── Empty states ──────────────────────────────────────────────────── */
/* Every empty state explains what to do next — never a bare "no results". */

.empty { text-align: center; padding: 48px 20px; color: var(--ink-3); }
.empty h3 { color: var(--ink); margin-bottom: 8px; }
.empty p { color: var(--ink-3); }

/* Order-placed confirmation tick. */
.tick {
    width: 64px; height: 64px; margin: 0 auto 24px;
    border-radius: 32px; border: 1px solid var(--accent);
    display: grid; place-items: center;
    box-shadow: 0 0 34px rgba(145, 132, 217, .35);
}
.tick > i {
    width: 20px; height: 11px; margin-top: -5px;
    border-left: 2px solid var(--accent-ink); border-bottom: 2px solid var(--accent-ink);
    transform: rotate(-45deg);
}

/* ── Scrim and dialog / bottom sheet ───────────────────────────────── */

.scrim { position: fixed; inset: 0; background: var(--scrim); z-index: 1000; }
.dialog {
    position: fixed; z-index: 1001; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: min(440px, calc(100vw - 32px));
    max-height: min(600px, calc(100dvh - 48px));
    display: flex; flex-direction: column;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--r-panel); box-shadow: var(--shadow-lift);
    overflow: hidden;
}
.dialog-h { padding: 16px 18px; border-bottom: 1px solid var(--line-2); font-weight: 500; }
.dialog-b { padding: 16px 18px; overflow-y: auto; }
.dialog-f { padding: 14px 18px; border-top: 1px solid var(--line-2); display: flex; gap: 10px; justify-content: flex-end; }

/* On a phone a centred dialog is worse than a sheet that comes up from the
   bottom — it is closer to the thumb and matches platform convention. */
@media (max-width: 560px) {
    .dialog {
        top: auto; bottom: 0; left: 0; transform: none;
        width: 100%; max-height: 88dvh;
        border-radius: var(--r-panel) var(--r-panel) 0 0;
        border-bottom: none;
    }
}

/* ── Loading ───────────────────────────────────────────────────────── */

.skeleton {
    background: linear-gradient(90deg, var(--surface) 25%, var(--surface-3) 50%, var(--surface) 75%);
    background-size: 200% 100%;
    animation: shimmer 1.3s infinite;
    border-radius: var(--r-row);
}
@keyframes shimmer { to { background-position: -200% 0; } }

@media (prefers-reduced-motion: reduce) {
    * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ── Favourite star ────────────────────────────────────────────────── */

.fav {
    background: none; border: none; cursor: pointer;
    font-size: 20px; line-height: 1; color: var(--ink-3);
    width: 34px; height: 34px; flex: none;
    display: flex; align-items: center; justify-content: center;
    align-self: flex-start;
}
.fav.on { color: var(--accent); }

/* ── Header nav ─────────────────────────────────────────────────────── */

.op-nav { display: flex; gap: 4px; margin-left: auto; }
.op-nav a {
    font-size: 13.5px; font-weight: 500; color: var(--ink-3);
    padding: 7px 11px; border-radius: var(--r-control);
}
.op-nav a.active { color: var(--accent-ink); }
.op-nav a:hover { color: var(--ink); }

/* ── Boot and error ────────────────────────────────────────────────── */

.boot {
    min-height: 100dvh;
    display: flex; align-items: center; justify-content: center;
    color: var(--ink-3); font-size: 14.5px;
    background: var(--surface-2);
}

#blazor-error-ui {
    display: none;
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 2000;
    background: var(--urgent-bg); color: var(--urgent);
    border-top: 1px solid var(--urgent-line);
    padding: 12px 16px; font-size: 14px;
}
#blazor-error-ui .reload { color: var(--urgent); text-decoration: underline; margin-left: 8px; }
#blazor-error-ui .dismiss { float: right; cursor: pointer; font-size: 18px; line-height: 1; }
