/* ============================================================================
   OrderPoint — "Daylight" design system
   ----------------------------------------------------------------------------
   A bright, high-contrast interface for the customer ordering app.

     ground      near-white, faintly cool   #f6f7f7
     surface     plain white                #ffffff
     accent      a single teal              #00ccbc / #00776e (text)

   ── Why this replaced the dark "Nocturne" theme, 2026-08-24 ──

   Customer feedback, repeatedly: the dark scheme did not work for them. That is
   the whole reason, and it beats any argument from the drawing board — this app
   is used by butchers' customers on their own phones, not by us.

   The accent is a FLOOD on the primary action and a line everywhere else. That
   is the one real departure from Nocturne, which used the accent only as a line:
   on a white ground an outlined button is nearly invisible, and the thing people
   press has to be the loudest thing on the screen.

   ⚠️ Dark text on teal, never white. #00ccbc against white text is 2.0:1 — it
   fails AA outright, and the apps that do it are getting away with something
   rather than getting it right. Against --ink it is above 7:1. A phone in a cold
   shop in poor light is the worst viewing condition we ship into, and it is the
   one this app was built for.

   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 ──
       Cards are plain white and sit ON a faintly cool near-white page, so a card
       reads as lifted without needing a shadow to say so. */
    --surface: #ffffff;        /* cards, inputs, header, sheets */
    --surface-2: #f6f7f7;      /* the page ground */
    --surface-3: #eceeee;      /* raised sheet / subtle fill */

    /* ── Ink ──
       Every one of these is measured against BOTH grounds — white and the page —
       because a value that passes on one and fails on the other is a value that
       fails wherever it is least expected. The floor is 4.5:1.

         --ink    #1f2828 on white 15.2:1   on page 13.8:1
         --ink-2  #46524f on white  8.3:1   on page  7.5:1
         --ink-3  #5f6a6a on white  5.6:1   on page  5.0:1
         --ink-4  #6c7676 on white  4.8:1   on page  4.4:1  ← large text only

       The dark theme had this same table and it was earned the hard way: the two
       quiet inks shipped below 3:1 and nobody noticed for months. */
    --ink: #1f2828;
    --ink-2: #46524f;
    --ink-3: #5f6a6a;
    --ink-4: #6c7676;

    /* ── Hairlines ── */
    --line: #dde2e2;           /* visible borders: inputs, cards, frame */
    --line-2: #ebeeee;         /* subtle list separators */

    /* ── Accent: a single teal, flooded on the primary action ── */
    --accent: #00ccbc;         /* fills, borders, active marks */
    --accent-ink: #00776e;     /* text and prices — 4.9:1 on white */
    --accent-2: #00998e;       /* secondary accent text */
    --accent-hover: #d9f7f4;   /* hover tint */
    --accent-soft: #e8faf8;    /* accent tint fill */
    --accent-line: #9de7e0;    /* accent border */

    /* What goes ON the accent when it is flooded. See the warning above. */
    --on-accent: #10201e;

    /* Errors: a warm hue that reads as a fault on a bright ground without
       shouting. Darker than the dark theme's rose, which would vanish here. */
    --urgent: #a8271e;
    --urgent-bg: #fdecea;
    --urgent-line: #f2b8b3;

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

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

    /* Elevation on a light ground is a soft shadow, not a hairline: the trick
       the dark theme used — an edge plus ambient darkness — has nothing to work
       with here. Kept shallow, because a page of floating cards reads as clutter. */
    --shadow-card: 0 1px 2px rgb(16 32 30 / 6%);
    --shadow-lift: 0 -12px 40px rgb(16 32 30 / 12%);
    --shadow-pop: 0 6px 20px rgb(16 32 30 / 14%);
    --scrim: rgb(16 32 30 / 35%);

    /* ── 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%;
}

/*
    Headings do not carry the body's leading.

    At 30px, line-height 1.5 puts a 45px line box round the text and roughly eight points of
    empty space above the cap — which, on top of the main padding, read as a gap somebody could
    see on every page and nothing accounted for. Display type wants tight leading anyway.
*/
h1, h2, h3, h4 {
    margin: 0;
    font-weight: 500;
    line-height: 1.2;
    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; }
/* --accent-2 rather than --accent: the focus ring is the only thing on screen telling a
   keyboard user where they are, and the bare teal is about 2:1 on white. */
:focus-visible { outline: 2px solid var(--accent-2); 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; }

/* White, lifting off the near-white page. On the dark theme the header and the page shared a
   colour and a rule did the separating; on a bright ground the rule alone is too quiet. */
.op-header {
    position: sticky; top: 0; z-index: 40;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    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);
}

/* Barely anything at the top. The header is already a separated band with a rule under it, so
   the page needs no second gap to say it has begun — and the heading below carries its own
   leading, which was reading as space nothing accounted for. */
.op-main { flex: 1; padding: 4px 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; }


/* ── 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); }

/*
    A basket line hanging under its product.

    Indented and marked with an accent rule so it reads as belonging to the row above rather than
    as another product. There can be several — that is the point of them — so they are quieter
    than the product itself: smaller, dimmer, and with no separating border of their own, which
    would make three weights of one cut look like three items.
*/
.row.prow-line {
    padding: 10px 0 10px 16px;
    margin-left: 2px;
    border-left: 2px solid var(--accent-line);
}

.row.prow + .row.prow-line { border-top: none; }

/*
    The editor for one line.

    Deliberately inside the list rather than a modal: on a phone a sheet over the catalogue hides
    what somebody is ordering against, and the back gesture then closes the wrong thing.
*/
.prow-panel {
    padding: 14px 16px;
    margin: 0 0 4px 2px;
    border-left: 2px solid var(--accent-2);
    background: var(--surface-3);
    border-radius: 0 var(--r-control) var(--r-control) 0;
}


/* ── 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; }

/*
    The primary action is a FLOOD, not an outline.

    The dark theme used the accent as a line and nothing else, which worked on a near-black
    ground where an outline is the brightest thing in view. On white it is nearly invisible, and
    the thing people press has to be the loudest object on the screen.

    Dark ink on the teal, never white — see the warning in the header. White on #00ccbc is 2.0:1.
*/
.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--on-accent);
    font-weight: 600;
}

.btn-primary:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 88%, #000); }
.btn-primary:active:not(:disabled) { background: color-mix(in srgb, var(--accent) 80%, #000); }

.btn-secondary { background: var(--surface); border-color: var(--line); color: var(--ink); }
.btn-secondary:hover:not(:disabled) { background: var(--surface-3); }

.btn-ghost { background: transparent; color: var(--accent-ink); }
.btn-ghost:hover:not(:disabled) { background: var(--accent-soft); }

.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-2);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    padding: 14px;
    min-height: 48px;
}
/*
    A date input on iOS, which does not sit where every other field does.

    Safari gives input[type=date] its own inner chrome: the value is centred in the remaining
    space rather than left-aligned to the padding, so the field reads as indented next to the
    text inputs and selects above it. Removing the native appearance and stating the alignment
    puts it back on the same left edge as everything else.

    min-height is repeated deliberately. Stripping -webkit-appearance also strips the intrinsic
    height Safari gives this control, and without it the field collapses to something shorter
    than its neighbours — trading a small misalignment for an obvious one.
*/
input[type="date"].input {
    -webkit-appearance: none;
    appearance: none;
    text-align: left;
    min-height: 48px;
}

/* The calendar button Safari draws inside the field, nudged back to the padding edge. */
input[type="date"].input::-webkit-calendar-picker-indicator {
    margin-inline-start: auto;
    opacity: 0.6;
}

/* Safari wraps the value in these; left to itself the group is centred in the field. */
input[type="date"].input::-webkit-date-and-time-value { text-align: left; }
input[type="date"].input::-webkit-datetime-edit { padding: 0; }

.input::placeholder { color: var(--ink-3); }
.input:focus {
    outline: none;
    border-color: var(--accent-2);
    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-2) 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); }
/*
    The one badge that carries a sentence rather than a word.

    .badge is nowrap, which is right for "OFFER" and "BY WEIGHT" and catastrophic here: the
    date-unavailable message is a full sentence, so it rendered as a single unbreakable line,
    pushed the page wider than the phone, and squashed every column to fit. A message telling
    somebody their date will not work should not also break the screen it appears on.
*/
.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);
    white-space: normal;
    line-height: 1.45;
    text-align: left;
}

.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-2); 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-2); }
.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; }

/* ── 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), transparent);
}

/* ── 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);
    border-top: 1px solid var(--line);
    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 ────────────────────────────────────────────────── */

/* White, like the header. Two near-white bands separated by a hairline is a seam nobody sees;
   the chrome should read as chrome. */
.op-tabs {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    display: flex;
    background: var(--surface);
    border-top: 1px solid var(--line);
    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. */
/* A filled disc, not a ring with a glow round it. The glow was a dark-theme device: it needs
   an unlit ground to bloom into, and on white it is a smudge. */
.tick {
    width: 64px; height: 64px; margin: 0 auto 24px;
    border-radius: 32px;
    border: 1px solid var(--accent-2);
    background: var(--accent-soft);
    display: grid; place-items: center;
}
.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-2); }

/* ── 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; }

/* ── Update strip — offered, not forced ────────────────────────────── */

/*
    Sits where the offline banner does and reads the same way, because they are the same kind of
    message: something about the app itself rather than about the order. The accent is on the
    button, not the strip — an update is available, not urgent, and a full-width coloured bar
    above somebody's basket would say otherwise.
*/
.update-banner {
    display: flex; align-items: center; justify-content: center; gap: 12px;
    background: var(--surface-3);
    border-bottom: 1px solid var(--line-2);
    color: var(--ink-2);
    font-size: 12.5px; text-align: center;
    padding: 7px 16px;
}

/*
    No focus ring on a heading.

    App.razor carries <FocusOnNavigate Selector="h1" />, the Blazor template's default: after
    every navigation it focuses the page's first heading so a screen reader announces the new
    page instead of leaving somebody stranded on the old one. That is worth keeping.

    But the focus is PROGRAMMATIC — nobody tabbed to it — and the ring drew a box around the
    words at the top of every screen, which reads as a control that has somehow been selected.
    A heading is not interactive, so the ring communicates nothing to a sighted user; the
    announcement, which is the actual point, is unaffected.

    Scoped to headings on purpose. Every real control keeps its ring, because for those it means
    "you are here" and removing it would strand keyboard users for real.
*/
h1:focus, h2:focus, h3:focus { outline: none; }

/* ── Saved and repeat order contents ───────────────────────────────────

   What is actually in a saved order or a repeat order. Both screens used to show a name and
   "8 items", which is not a description of a list — and for a repeat order, which places
   itself, it is the only thing standing between the customer and an order nobody read.

   The quantity column is fixed and tabular so the numbers line up down the list; the name
   takes the rest and truncation is left to it rather than to the row. */
.ro-lines { list-style: none; margin: 10px 0 0; padding: 0; }

.ro-lines li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
    padding: 6px 0;
    font-size: 13.5px;
    line-height: 1.45;
    border-top: 1px solid var(--line-2);
}

.ro-lines li:first-child { border-top: 0; }

.ro-lines .q {
    min-width: 76px;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}

.ro-lines .n { flex: 1 1 140px; min-width: 0; }

/* A product that has left this customer's list. Struck through rather than removed: the line
   is still in their repeat order, and it is the fact that it will be dropped that they need. */
.ro-lines li.gone .n { text-decoration: line-through; color: var(--ink-3); }
.ro-lines .warn { color: var(--urgent); }

/* A region of a row that is a button without dressing as one — the tappable part of a saved
   order, which opens it. */
.linkish {
    background: none;
    border: 0;
    margin: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    min-width: 0;
}

/*
    Feedback after pausing, resuming or deleting a repeat order.

    These existed in the markup and nowhere in this stylesheet, so the message rendered as bare
    text with no band at all. A class borrowed from the admin console's sheet looks correct in the
    Razor and shows nothing on the phone — there is no error, no warning, just an unstyled line
    somebody has to notice. Two screens have now done it, which makes it a pattern rather than a
    slip: if a class here is not defined in THIS file, it does not exist.
*/
.note {
    display: block;
    margin: 0 0 14px;
    padding: 11px 14px;
    border: 1px solid var(--line);
    border-radius: var(--r-card);
    background: var(--surface);
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--ink);
    text-wrap: pretty;
}

.note.ok { border-color: var(--accent-2); box-shadow: inset 3px 0 0 var(--accent-2); }

.note.warn {
    border-color: var(--urgent-line);
    background: var(--urgent-bg);
    box-shadow: inset 3px 0 0 var(--urgent);
    color: var(--urgent);
}

/* ── Ordering assistant ────────────────────────────────────────────────

   One box that takes an order the way somebody would say it. Given the top of the browse
   screen because it replaces searching rather than refining it — and because a feature you
   have to go and find does not beat a WhatsApp message. */
.asst { margin-top: 14px; }

/* The box and the button. The button carries the mark rather than the words — "Ask Mabel" is
   already the heading directly above it, so the label was repeating itself at twice the width
   it needed. */
/* align-items:flex-start, not stretch — with a ten-line textarea beside it, a stretched button
   became a 170px-tall slab. It sits at the top of the box instead, where the cursor starts. */
.asst-row { display: flex; gap: 10px; align-items: flex-start; }
.asst-row .input { flex: 1 1 auto; min-width: 0; }
.asst-row .btn { flex: 0 0 auto; width: 64px; height: 52px; padding: 0; }
.asst-row .btn .asst-avatar { width: 34px; height: 34px; background: none; }
.asst-row .btn .asst-avatar svg { width: 26px; height: 26px; }

/* The mark sits ON the teal flood here, so it wears the label's colour rather than the accent
   ink it uses everywhere else — accent ink on accent is about 2:1 and the figure disappears. */
.asst-row .btn .asst-avatar-head,
.asst-row .btn .asst-avatar-body { stroke: currentColor; }
.asst-row .btn .asst-avatar-spark { fill: currentColor; }

/* Ten lines deep, because an order is a list and people write lists a line at a time. Enter now
   makes a new line rather than sending it — the key that ends a line was the key that sent it
   half-written. */
.asst-input { resize: vertical; min-height: 168px; line-height: 1.5; }

.asst-hint { margin-top: 6px; }
.asst-note { margin-top: 10px; }
.asst-panel { margin-top: 10px; }

.asst-line {
    padding: 12px 0;
    border-top: 1px solid var(--line-2);
}

.asst-line:first-of-type { border-top: 0; padding-top: 2px; }

.asst-line-head { display: flex; align-items: flex-start; gap: 10px; }
.asst-line-head .grow { flex: 1 1 auto; min-width: 0; }
.asst-line-head .name { font-weight: 500; }

/* The remove cross, pinned to the product it removes. It used to sit at the end of the control
   row and wrapped onto a line of its own at narrow widths, where it belonged to nothing. */
.asst-line-x {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 0;
    border-radius: var(--r-control);
    background: none;
    color: var(--ink-3);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
}

.asst-line-x:hover { background: var(--surface-3); color: var(--ink); }

/*
    The controls, on their own row and LABELLED.

    They used to sit unlabelled beside the product name — a bare number box and a bare dropdown,
    wrapping differently at different widths. Nothing said which was the count and which was the
    size, on the one screen whose entire job is letting somebody check.
*/
.asst-fields { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }

.asst-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.asst-field-label {
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.asst-field .input { padding: 8px 10px; font-size: 14px; }
.asst-field input.input { width: 84px; }
.asst-field select.input { width: auto; min-width: 104px; }

/* What it could not match. The warn hue on purpose: this is the one part of the panel that
   is a to-do rather than a result, and it is the part somebody skimming will otherwise miss. */
.asst-unmatched {
    margin-top: 10px;
    padding: 9px 11px;
    border-radius: var(--r-control);
    background: var(--urgent-bg);
    border: 1px solid var(--urgent-line);
    color: var(--urgent);
    font-size: 12.5px;
}

.asst-unmatched ul { margin: 4px 0 0; padding-left: 18px; }
.asst-unmatched li { line-height: 1.5; }

.asst-actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    margin-top: 12px;
}

/* ── The assistant's mark, and the way in ──────────────────────────────

   A proper button at the top of the browse screen rather than a line of text. It replaces a
   box that sat immediately above "Search products" — two identical text fields doing entirely
   different things, with nothing on screen to tell them apart. */
.asst-avatar {
    flex: 0 0 auto;
    display: inline-flex;
    width: 38px;
    height: 38px;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--accent) 18%, transparent);
}

.asst-avatar svg { width: 26px; height: 26px; }
.asst-avatar-lg { width: 54px; height: 54px; }
.asst-avatar-lg svg { width: 38px; height: 38px; }

.asst-avatar-head, .asst-avatar-body {
    fill: none;
    stroke: var(--accent-ink);
    stroke-width: 2.1;
    stroke-linecap: round;
}

/* Filled rather than stroked, so it still reads at button size. */
.asst-avatar-spark { fill: var(--accent-ink); }

/*
    Mabel in the tab bar, leftmost.

    She was a floating pill above the bar. That hovered over the last row of whatever page you
    were on, and it was a layer of its own for the one feature that ought to look like part of
    the app. In the bar she is one of five equals.

    The mark drops its tinted disc here and takes the tab colour like every other icon, so the
    active state reads the same way across the row. The sparkle inside the glyph is what still
    says "a machine reads this" — that part is not decoration and does not come off.
*/
.op-tab-asst .asst-avatar {
    width: 20px;
    height: 20px;
    background: none;
}

.op-tab-asst .asst-avatar svg { width: 20px; height: 20px; }

.op-tab-asst .asst-avatar-head,
.op-tab-asst .asst-avatar-body { stroke: currentColor; stroke-width: 2.4; }
.op-tab-asst .asst-avatar-spark { fill: currentColor; }

/* A name can be anything the wholesaler types. One tab of five is not wide enough to argue
   with, so it is cut rather than allowed to wrap the row taller. */
.op-tab-asst > span {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The assistant page's heading: the mark, the name, and what it actually is. */
.asst-head { display: flex; align-items: center; gap: 14px; }
.asst-head h1 { margin: 0; }

.asst-head-sub {
    font-size: 13.5px;
    color: var(--accent-ink);
    margin-top: 3px;
    letter-spacing: 0.01em;
}
/* Examples on the assistant's own page — the fastest way to show what "in your own words"
   actually permits, which a placeholder has no room for. */
.asst-examples { margin-top: 16px; }
.asst-examples ul { margin: 6px 0 0; padding-left: 18px; }
.asst-examples li { font-size: 13px; color: var(--ink-2); line-height: 1.7; }

/*
    The working indicator: a bar sweeping back and forth across the button.

    It was a spinner that REPLACED the label, which shrank the button to the spinner's width and
    pushed the text field wider and back again on every attempt — the row jumped twice per order.
    The label now stays put, hidden, and this lies over the top, so the button cannot resize.

    currentColor throughout, so it inherits whatever button it sits on with no second variable to
    keep in step.
*/
.asst-go { position: relative; }

/* Hidden, not removed: it is still holding the button open at its natural width. */
.asst-go-label.is-working { visibility: hidden; }

.asst-sweep {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    overflow: hidden;
    pointer-events: none;
}

.asst-sweep::after {
    content: "";
    position: absolute;
    top: 50%;
    margin-top: -3px;
    height: 6px;
    width: 32%;
    border-radius: 3px;

    /*
        Dark, on the button's own teal.

        It was red — a scanner bar, which is the reference. Red belonged on the dark theme,
        where the button was an outline on near-black. The button is a teal flood now, and a red
        bar across it reads as a fault rather than as work in progress. Dark ink is the same
        colour the button's label is already wearing, so the sweep looks like part of it.

        The trail is what makes it a sweep rather than a sliding block; the ends fade out so it
        does not look clipped by the button's edge.
    */
    background: linear-gradient(90deg,
        transparent,
        color-mix(in srgb, var(--on-accent) 55%, transparent) 42%,
        var(--on-accent) 50%,
        color-mix(in srgb, var(--on-accent) 55%, transparent) 58%,
        transparent);
    animation: asst-sweep 1.05s ease-in-out infinite alternate;
}

@keyframes asst-sweep {
    from { left: 8%; }
    to { left: 60%; }
}

/* Slowed rather than stopped. Since the words went this is the only sign anything is happening,
   and a dead button for three seconds is worse for the people the setting exists to help. */
@media (prefers-reduced-motion: reduce) {
    .asst-sweep::after { animation-duration: 3s; }
}

/* ── Who the order is for, in the header ───────────────────────────────

   It used to be a band across the top of the page content: a lot of room for one line, and it
   scrolled away — a switcher you can only read at the top of the page is no use halfway down a
   product list, which is where somebody is when they stop being sure. In the sticky header it
   costs almost nothing and cannot be scrolled past. */
.op-acting {
    margin-left: auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
    min-width: 0;
    max-width: 55%;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: right;
    cursor: pointer;
}

.op-acting-label {
    font-size: 10.5px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-4);
    line-height: 1.2;
}

.op-acting-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--ink-2);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
    A customer login's switcher: the venue name and a chevron, on one line.

    It was a <select>, which is fine for the three venues a café group has and wrong for what
    this is actually for — a login on dozens of sites, scrolled through a native picker with no
    search in it. It opens the same page staff use now, which searches once the list stops being
    scannable. One behaviour for both kinds of login, and the one that still works at a hundred.

    No "Ordering for" caption above it. A venue name in a header needs no label to be understood,
    and two lines of small text in a sticky header is a lot of chrome for one word. The name takes
    the room instead, at a size somebody can read at arm's length in a shop; the label survives as
    aria-label, where the visual context does not exist.
*/
.op-acting-row {
    flex-direction: row;
    align-items: center;
    gap: 2px;
    max-width: 60%;
}

.op-acting-row .op-acting-name { font-size: 15px; color: var(--ink); }

.op-acting-chev { color: var(--ink-3); font-size: 18px; line-height: 1; flex: 0 0 auto; }

.op-acting:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; border-radius: 6px; }

/* Staff keep the urgent hue. Not decoration: a customer switching between their own two venues
   inconveniences only themselves, while an operator taking calls all morning who loses track
   sends Tuesday's order to the wrong kitchen, and it is picked before anyone notices. */
.op-acting-staff .op-acting-label { color: var(--urgent); }
.op-acting-staff .op-acting-name { color: var(--urgent); font-weight: 600; }

/* ── Products, grouped by category ─────────────────────────────────────

   A flat list of two hundred rows is a scroll with no landmarks. The heading sticks under the
   app header so the answer to "where am I" stays on screen while you are inside a group, which
   is the moment the question comes up. */
.cat-head {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: 18px -16px 2px;
    padding: 7px 16px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line-2);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.cat-head:first-of-type { margin-top: 4px; }
.cat-count { margin-left: auto; letter-spacing: 0; font-variant-numeric: tabular-nums; }

/* ── The product row, once it stopped carrying its own control ─────────

   Every row used to end in one: an Add button on a weighed product, a stepper on a counted one.
   Two hundred live controls is a wall of widgets to read past, and the two kinds never lined up.
   The row now opens what that product needs, and what is left on it is the ANSWER — how much of
   this is on order. */
.prow-open {
    flex: 1 1 auto;
    min-width: 0;
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.prow-state { display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; }

/* What is on the order. For a weighed product this is a total weight, not a count: "7" for
   2×500g and 5×1kg is a sum of unlike things and means nothing. */
.prow-count {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--accent-ink);
    background: var(--accent-soft);
    border: 1px solid var(--accent-line);
    border-radius: 999px;
    padding: 2px 9px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.prow-chev {
    color: var(--ink-3);
    font-size: 19px;
    line-height: 1;
    transition: transform .15s ease;
}

.prow-chev.open { transform: rotate(90deg); }
.prow.is-open { background: var(--surface-3); }

/* A basket line reads as open the same way its product does. The rule down its left goes from
   the pale accent line to the darker one at the same time, so the line and the panel hanging
   off it share one edge rather than looking like two stacked things. */
.prow-line.is-open { background: var(--surface-3); border-left-color: var(--accent-2); }
.prow-line .prow-chev { font-size: 17px; }

/* A review-screen line. Same open/shut behaviour as a catalogue row, but flat: it sits inside
   a panel that already draws the separators, so it takes no rule of its own. */
.prow-line-flat.is-open { background: var(--surface-3); }

/*
    The heading of a wizard step, with the way back beside it.

    It was a ghost button ABOVE the title, which put a thing to press in the space a page title
    occupies and made it compete with Place order. Beside the title it is the control every
    phone app puts in that corner, and it reads as navigation rather than as another option.
*/
.step-head { display: flex; align-items: center; gap: 4px; margin-left: -10px; }
.step-head h1 { min-width: 0; }

.step-back {
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    display: grid;
    place-items: center;
    border: 0;
    background: none;
    color: var(--ink-2);
    font: inherit;
    font-size: 30px;
    line-height: 1;
    border-radius: 50%;
    cursor: pointer;
}

.step-back:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
