/* Oakfield stock demo — plain CSS with custom properties.
   Tokens come from the concept style guide: Oakfield green for the app, BrambleBrooke plum
   confined to the demo layer (banner and badge) so the demo is never confused with the agency.
   To re-skin, swap the tokens in :root. */

:root {
    --paper: #F7F5EE;
    --surface: #FFFFFF;
    --surface-alt: #FBFAF5;
    --line: #E2DED0;
    --ink: #1F2A24;
    --ink-muted: #5B675F;

    --oak: #2E6B3E;
    --oak-deep: #1F4D2C;
    --oak-tint: #E1EFE3;
    --oak-pale: #D5E6D9;
    --terracotta: #B4532A;
    --track: #ECE8DB;

    --bramble: #7B2D5B;
    --bramble-tint: #F4E5EE;

    --in-text: #2A6A3C;
    --in-fill: #E1EFE3;
    --low-text: #85570A;
    --low-fill: #FCEBC7;
    --out-text: #A8261E;
    --out-fill: #FBE1DE;
    --transfer-text: #2F5F8A;
    --transfer-fill: #DFEAF4;
    --sale-text: #9E4623;
    --sale-fill: #F7E4DA;

    --r-control: 12px;
    --r-card: 16px;
    --r-hero: 20px;

    --sidebar-width: 248px;
    --tabbar-height: 64px;

    --sans: 'DM Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --display: 'Fraunces', Georgia, serif;
}

* { box-sizing: border-box; }

/* The hidden attribute is only display:none from the browser's own stylesheet, so any author
   rule that sets display — .btn does — silently beats it. Without this, a button marked hidden
   in the markup shows anyway. */
[hidden] { display: none !important; }

html, body { margin: 0; padding: 0; }

body {
    background: var(--paper);
    color: var(--ink);
    font-family: var(--sans);
    font-size: 15px;
    line-height: 1.45;
    -webkit-text-size-adjust: 100%;
}

h1, h2, h3, .disp {
    font-family: var(--display);
    font-weight: 600;
    letter-spacing: -0.01em;
    margin: 0;
}

a { color: inherit; text-decoration: none; }
a:hover { text-decoration: none; }
.link { color: var(--oak); font-weight: 600; }
.link:hover { text-decoration: underline; }

button, input, select, textarea { font-family: inherit; font-size: inherit; }

.num { font-variant-numeric: tabular-nums; }
.muted { color: var(--ink-muted); }
.small { font-size: 13px; }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.center { text-align: center; }
.grow { flex-grow: 1; }
.visually-hidden {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---- demo layer ------------------------------------------------------------------------- */

.demo-banner {
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap; gap: 8px 16px;
    padding: 8px 16px;
    background: var(--bramble);
    color: #fff;
    font-size: 14px;
    text-align: center;
}

.demo-banner b { font-weight: 700; }

.demo-banner .btn-talk {
    height: 30px; padding: 0 14px;
    display: inline-flex; align-items: center;
    border-radius: 15px;
    background: #fff; color: var(--bramble);
    font-size: 13px; font-weight: 700;
}

/* Folded into the banner on phones, where a separate footer under a fixed tab bar cannot work. */
.demo-banner .powered { display: none; font-size: 12px; color: #F1D9E8; width: 100%; }

/* ---- shell ----------------------------------------------------------------------------- */

.shell { display: flex; min-height: 100vh; flex-direction: column; }
.shell-body { display: flex; flex-grow: 1; min-height: 0; }

.sidebar { display: none; }

.main { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; }

.app-header {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
    background: var(--paper);
}

.app-header h1 { font-size: 24px; line-height: 30px; }
.app-header .subtitle { font-size: 13px; color: var(--ink-muted); }

.brand-mobile { display: flex; align-items: center; gap: 10px; }

.brand-mark {
    width: 40px; height: 40px; flex-shrink: 0;
    border-radius: var(--r-control);
    background: var(--oak); color: #fff;
    display: flex; align-items: center; justify-content: center;
}

.sidebar .brand-mark { background: #fff; color: var(--oak); }

.brand-name { font-family: var(--display); font-weight: 600; font-size: 20px; line-height: 22px; }
.brand-sub { font-size: 12px; color: var(--ink-muted); }

.who { display: flex; align-items: center; gap: 8px; }

.avatar {
    width: 40px; height: 40px; flex-shrink: 0;
    border-radius: 20px;
    background: var(--oak-tint); color: var(--oak);
    display: flex; align-items: center; justify-content: center;
    font-size: 14px; font-weight: 700;
}

/* The clearance for the fixed tab bar lives on .site-footer, which now follows this, so the page
   itself just needs ordinary bottom spacing. */
.page { padding: 16px 16px 24px; display: flex; flex-direction: column; gap: 16px; }

/* Mobile: the privacy link only — the plum banner already carries the BrambleBrooke credit, so the
   logo stays out of here. It sits at the end of the scrolling content rather than being fixed
   chrome, so it costs no permanent screen space. Its own bottom padding clears the fixed tab bar:
   the concept drew a footer *underneath* that bar, which cannot work. */
.site-footer {
    display: flex; justify-content: center;
    padding: 4px 16px calc(var(--tabbar-height) + 20px);
}

.site-footer .powered { display: none; }

/* ---- bottom tabs (mobile) --------------------------------------------------------------- */

.tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
    height: var(--tabbar-height);
    display: flex;
    background: var(--surface);
    border-top: 1px solid var(--line);
}

.tabbar a {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
    font-size: 11px; font-weight: 600; color: var(--ink-muted);
}

.tabbar a.is-current { color: var(--oak); }
.tabbar a.is-current svg { stroke: var(--oak); }

/* ---- cards ----------------------------------------------------------------------------- */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-card);
    padding: 16px;
}

.card--flush { padding: 0; overflow: hidden; }

/* Wraps, because it has to. The reorder list's supplier header puts a name and contact details,
   a units-and-cost figure and the "Order PDF" button on one row, and the last two carry .nowrap
   so they cannot shrink. On a phone that pushed the button clean off the side of the card —
   the primary action on the screen, unreachable. There is room on a desktop, so nothing wraps
   there and the row is unchanged. */
.card-head {
    display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 12px;
    margin-bottom: 12px;
}

.card-head h2 { font-size: 20px; line-height: 26px; }

.stack { display: flex; flex-direction: column; gap: 16px; }
.row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.grid-2 { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }

/* ---- buttons --------------------------------------------------------------------------- */

.btn {
    height: 44px; padding: 0 16px;
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    border-radius: var(--r-control);
    font-size: 14px; font-weight: 700;
    white-space: nowrap; cursor: pointer;
    background: var(--surface); color: var(--oak);
    border: 1.5px solid var(--oak);
}

.btn:hover { background: var(--oak-tint); }

.btn--primary { background: var(--oak); color: #fff; border: 0; }
.btn--primary:hover { background: var(--oak-deep); }

.btn--sale { background: var(--terracotta); color: #fff; border: 0; }
.btn--sale:hover { filter: brightness(0.94); }

.btn--ghost { background: transparent; color: var(--ink-muted); border: 1px solid var(--line); }
.btn--ghost:hover { background: var(--surface); }

.btn--small { height: 36px; font-size: 13px; padding: 0 12px; }
.btn--block { width: 100%; }
.btn--lg { height: 52px; font-size: 16px; padding: 0 28px; }

.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.5; cursor: default; }

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

/* ---- badges and chips ------------------------------------------------------------------ */

.badge {
    display: inline-block; padding: 2px 10px;
    border-radius: 999px;
    font-size: 12px; font-weight: 600; line-height: 18px;
    white-space: nowrap;
}

.badge--in { background: var(--in-fill); color: var(--in-text); }
.badge--low { background: var(--low-fill); color: var(--low-text); }
.badge--out { background: var(--out-fill); color: var(--out-text); }
.badge--goodsin { background: var(--in-fill); color: var(--in-text); }
.badge--adjustment { background: var(--low-fill); color: var(--low-text); }
.badge--transfer { background: var(--transfer-fill); color: var(--transfer-text); }
.badge--sale { background: var(--sale-fill); color: var(--sale-text); }
.badge--archived { background: var(--track); color: var(--ink-muted); }
/* Price changes: the demo's fourth movement-type colour is taken, so these use the plum-free
   neutral so they read as "not a stock movement". */
.badge--price { background: var(--track); color: #6B5A2E; }

.chip {
    height: 40px; padding: 0 16px;
    display: inline-flex; align-items: center; gap: 6px;
    border-radius: 20px;
    border: 1px solid var(--line);
    background: var(--surface); color: var(--ink);
    font-size: 14px; font-weight: 600;
    cursor: pointer;
}

.chip.is-selected { background: var(--oak); border-color: var(--oak); color: #fff; }
.chip-row { display: flex; gap: 8px; flex-wrap: wrap; }

.tile {
    width: 44px; height: 44px; flex-shrink: 0;
    border-radius: var(--r-control);
    background: var(--oak-tint); color: var(--oak);
    display: flex; align-items: center; justify-content: center;
    font-size: 14px; font-weight: 700;
}

.tile--lg { width: 72px; height: 72px; border-radius: var(--r-hero); font-size: 24px; }

/* ---- tables ---------------------------------------------------------------------------- */

.table-scroll { overflow-x: auto; }

table.data { width: 100%; border-collapse: collapse; }

table.data th {
    padding: 12px;
    text-align: left;
    font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--ink-muted);
    background: var(--surface-alt);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

table.data td { padding: 10px 12px; font-size: 14px; border-bottom: 1px solid var(--line); }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--surface-alt); }
table.data .right, table.data th.right { text-align: right; }
table.data .total { font-weight: 700; }

.cell-product { display: flex; align-items: center; gap: 12px; }
.cell-product .name { display: block; font-size: 15px; font-weight: 600; }
.cell-product .sku { display: block; font-size: 13px; color: var(--ink-muted); }

.table-foot {
    display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
    padding: 12px 16px;
    font-size: 13px; color: var(--ink-muted);
    border-top: 1px solid var(--line);
}

/* Product cards: the phone view of the same list. */
.product-cards { display: flex; flex-direction: column; }

.product-cards a.item {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
}

.product-cards a.item:last-child { border-bottom: 0; }
/* Each on its own line. Left inline, the name, the "SKU · category" line and the per-location
   quantities ran together into one paragraph separated by identical middle dots, so
   "Compost & Soil · Shop Floor 1" read as a location called "Compost & Soil Shop Floor". On the
   desktop table these are three columns and the question never arises; the card view has to
   draw the distinction itself. */
.product-cards .item .name { display: block; font-size: 15px; font-weight: 600; }
.product-cards .item .meta { display: block; font-size: 13px; color: var(--ink-muted); }

/* The per-location quantities. A little air above them separates them from the SKU line, and
   the figure is the part worth reading, so it takes the weight and the location name stays
   the label. The separator is drawn between items rather than typed into the string, so the
   line never ends on a stray dot. */
.product-cards .item .meta--locations { margin-top: 3px; }
.product-cards .item .meta--locations .at + .at::before { content: " · "; color: var(--line); }
.product-cards .item .meta--locations b { font-weight: 700; color: var(--ink); }
.product-cards .item .figure { text-align: right; flex-shrink: 0; }
.product-cards .item .figure .qty { font-family: var(--display); font-size: 22px; line-height: 26px; }

/* ---- forms ----------------------------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field > label { font-size: 13px; font-weight: 600; }
.field .hint { font-size: 12px; color: var(--ink-muted); }

.field input[type=text], .field input[type=email], .field input[type=number],
.field input[type=password], .field input[type=search], .field input[type=date],
.field select, .field textarea {
    height: 44px; padding: 0 12px;
    border: 1px solid var(--line); border-radius: var(--r-control);
    background: var(--surface); color: var(--ink);
    width: 100%;
}

.field textarea { height: auto; min-height: 84px; padding: 10px 12px; line-height: 1.4; }

/* Selects get a drawn chevron.

   `appearance: none` was here on its own with `background-image: none` beside it, which strips
   the browser's arrow and puts nothing back — so all twelve selects on the movement and product
   forms looked exactly like text inputs, with no sign they open anything. It showed up on the
   record screen, where a long product name fills the box right to the edge.

   Drawn rather than native so it is the same on every platform, and the padding on the right
   reserves the space so a long option truncates before it reaches the arrow instead of running
   underneath it. */
.field select, .select-inline {
    appearance: none;
    padding-right: 38px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%236B7A72' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 13px center;
    background-size: 12px 8px;
    text-overflow: ellipsis;
}

.field input:focus, .field select:focus, .field textarea:focus {
    outline: 2px solid var(--oak); outline-offset: 1px; border-color: var(--oak);
}

.field-pair { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 16px; }

.search {
    height: 44px; padding: 0 12px;
    display: flex; align-items: center; gap: 8px;
    border: 1px solid var(--line); border-radius: var(--r-control);
    background: var(--surface); color: var(--ink-muted);
    flex-grow: 1; min-width: 0;
}

.search input { border: 0; outline: 0; background: transparent; flex-grow: 1; min-width: 0; color: var(--ink); }

/* background-COLOR, not the `background` shorthand: this rule sits after the one that gives every
   select its drawn chevron, and the shorthand resets background-image to none. It cost the two
   filter selects on the products list their arrows while the form ones kept theirs. */
.select-inline {
    height: 44px; padding: 0 38px 0 12px;
    border: 1px solid var(--line); border-radius: var(--r-control);
    background-color: var(--surface); color: var(--ink);
    font-weight: 600;
}

.toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* A date range that sits in a filter row rather than stacking like a form field. */
.date-range { display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap; }
.date-range .field { margin-bottom: 0; gap: 3px; }
.date-range .field > label { font-size: 12px; color: var(--ink-muted); }
.date-range .field input[type=date] { height: 36px; width: auto; min-width: 148px; font-size: 13px; }

/* One error block above the submit button — the house style, server-side validation only. */
.errors {
    padding: 12px 14px;
    border: 1px solid var(--out-text);
    border-left-width: 4px;
    border-radius: var(--r-control);
    background: var(--out-fill);
    color: #6E1A14;
    margin-bottom: 14px;
}

.errors p { margin: 0 0 4px; font-weight: 700; }
.errors ul { margin: 0; padding-left: 18px; }

.notice {
    padding: 12px 14px;
    border: 1px solid var(--oak);
    border-left-width: 4px;
    border-radius: var(--r-control);
    background: var(--oak-tint);
    color: var(--in-text);
    font-weight: 600;
}

.notice--low { border-color: var(--low-text); background: var(--low-fill); color: var(--low-text); }

.form-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 4px; }

/* ---- detail bits ----------------------------------------------------------------------- */

.kv { display: flex; justify-content: space-between; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.kv:last-child { border-bottom: 0; }
.kv .k { color: var(--ink-muted); }
.kv .v { font-weight: 600; text-align: right; }

.big-number { font-family: var(--display); font-size: 44px; line-height: 48px; }

.bar-row { display: flex; align-items: center; gap: 12px; }
.bar-row .label { width: 92px; flex-shrink: 0; font-size: 14px; font-weight: 500; }
.bar-row .track { flex-grow: 1; height: 8px; border-radius: 4px; background: var(--track); overflow: hidden; }
.bar-row .fill { height: 8px; border-radius: 4px; background: var(--oak); }
.bar-row .value { width: 32px; text-align: right; font-size: 15px; font-weight: 700; }

.timeline { display: flex; flex-direction: column; }
.timeline .entry { display: flex; gap: 12px; }
.timeline .rail { display: flex; flex-direction: column; align-items: center; }
.timeline .dot {
    width: 32px; height: 32px; flex-shrink: 0;
    border-radius: 16px;
    display: flex; align-items: center; justify-content: center;
    background: var(--track); color: var(--ink-muted);
}
.timeline .dot--goodsin { background: var(--in-fill); color: var(--in-text); }
.timeline .dot--adjustment { background: var(--low-fill); color: var(--low-text); }
.timeline .dot--transfer { background: var(--transfer-fill); color: var(--transfer-text); }
.timeline .dot--sale { background: var(--sale-fill); color: var(--sale-text); }
.timeline .dot--price { background: var(--track); color: #6B5A2E; }
.timeline .line { flex-grow: 1; width: 2px; margin: 4px 0; background: var(--line); }
.timeline .entry:last-child .line { display: none; }
.timeline .body { flex-grow: 1; min-width: 0; padding-bottom: 14px; }
.timeline .entry:last-child .body { padding-bottom: 0; }
.timeline .top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.timeline .qty { font-size: 15px; font-weight: 700; }
.timeline .when { font-size: 12px; color: var(--ink-muted); white-space: nowrap; }
.timeline .who-line { font-size: 14px; margin-top: 2px; }
.timeline .where { font-size: 13px; color: var(--ink-muted); }

/* ---- dashboard ------------------------------------------------------------------------- */

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

.stat { display: flex; flex-direction: column; gap: 2px; }
.stat .label { font-size: 13px; color: var(--ink-muted); font-weight: 600; }
.stat .figure { font-family: var(--display); font-size: 32px; line-height: 38px; }
.stat .note { font-size: 12px; color: var(--ink-muted); }
.stat--alert .figure { color: var(--low-text); }
.stat--out .figure { color: var(--out-text); }

.chart { width: 100%; height: auto; display: block; }
.chart .bar-in { fill: var(--oak); }
.chart .bar-out { fill: var(--terracotta); }
.chart .axis { stroke: var(--line); stroke-width: 1; }
.chart .tick { fill: var(--ink-muted); font-size: 10px; font-family: var(--sans); }
.chart .day:hover .bar-in, .chart .day:hover .bar-out { filter: brightness(1.15); }

.legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 13px; color: var(--ink-muted); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }

.panel-list { display: flex; flex-direction: column; }
.panel-list a.item, .panel-list .item {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--line);
}
.panel-list .item:last-child, .panel-list a.item:last-child { border-bottom: 0; }
.panel-list .item .name { display: block; font-size: 15px; font-weight: 600; }
.panel-list .item .meta { display: block; font-size: 13px; color: var(--ink-muted); }

/* The trailing figure — the reorder list's "24 / to order" — must not be squeezed by the name
   beside it, or it wraps and then clips. Same rule the product cards already carry. */
.panel-list .item .figure { flex-shrink: 0; }
.panel-list .item .figure.right { text-align: right; }

.empty { padding: 32px 16px; text-align: center; color: var(--ink-muted); }
.empty h2 { font-size: 20px; margin-bottom: 4px; color: var(--ink); }

.breadcrumb { display: flex; align-items: center; gap: 6px; font-size: 14px; color: var(--ink-muted); }

.pager { display: flex; gap: 8px; }

/* ---- login ----------------------------------------------------------------------------- */

.login-wrap {
    min-height: 100vh;
    display: flex; flex-direction: column;
}

.login-body {
    flex-grow: 1;
    display: flex; align-items: center; justify-content: center;
    padding: 24px 16px 40px;
}

.login-card { width: 100%; max-width: 460px; }

/* A block of prose given a reading measure — the privacy notice, in both the signed-out shell
   and the app one.

   `width: 100%` is doing the work here, not the max-width. `.login-body` centres its children,
   and a centred child of a *column* flex container is sized to its content, so a long paragraph
   lays itself out at its max-content width and sails straight off a phone. The bare
   `max-width: 720px` this replaces never came into it, because the content width was under the
   cap. Pin the width, then cap it. */
.doc { width: 100%; max-width: 720px; }

.login-card .lead { font-size: 15px; color: var(--ink-muted); margin: 8px 0 20px; }

.creds {
    background: var(--surface-alt);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    padding: 12px 14px;
    font-size: 13px;
}

.creds table { border-collapse: collapse; width: 100%; }
.creds td { padding: 3px 0; }
.creds td:first-child { color: var(--ink-muted); padding-right: 12px; }

.rule { display: flex; align-items: center; gap: 12px; color: var(--ink-muted); font-size: 13px; margin: 20px 0; }
.rule::before, .rule::after { content: ""; height: 1px; background: var(--line); flex-grow: 1; }

/* ---- desktop --------------------------------------------------------------------------- */

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

    .demo-banner { padding: 0 16px; height: 44px; }

    .sidebar {
        width: var(--sidebar-width); flex-shrink: 0;
        padding: 24px 16px;
        display: flex; flex-direction: column; gap: 24px;
        background: var(--oak-deep); color: #fff;
    }

    .sidebar .brand { display: flex; align-items: center; gap: 10px; padding: 0 4px; }
    .sidebar .brand-sub { color: var(--oak-pale); }

    .sidebar nav { display: flex; flex-direction: column; gap: 4px; }

    .sidebar nav a {
        height: 44px; padding: 0 12px;
        display: flex; align-items: center; gap: 12px;
        border-radius: var(--r-control);
        font-size: 15px; font-weight: 600;
        color: var(--oak-pale);
    }

    .sidebar nav a:hover { background: rgba(255, 255, 255, 0.10); color: #fff; }
    .sidebar nav a.is-current { background: rgba(255, 255, 255, 0.16); color: #fff; }

    .sidebar .role-note { font-size: 12px; line-height: 16px; color: var(--oak-pale); padding: 0 4px; }

    .sidebar .signout {
        height: 40px;
        display: flex; align-items: center; justify-content: center;
        border-radius: var(--r-control);
        border: 1px solid rgba(255, 255, 255, 0.35);
        color: #fff; font-size: 14px; font-weight: 600;
    }

    .sidebar .signout:hover { background: rgba(255, 255, 255, 0.12); }

    .app-header { padding: 14px 32px; min-height: 72px; background: transparent; }
    .app-header h1 { font-size: 26px; line-height: 30px; }

    .page { padding: 24px 32px 32px; gap: 24px; }

    .grid-2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 24px; }
    .field-pair { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; }
    .stat .figure { font-size: 36px; line-height: 42px; }

    .card { padding: 20px; }
    .card--flush { padding: 0; }

    .big-number { font-size: 52px; line-height: 56px; }
    .tile--lg { width: 96px; height: 96px; font-size: 30px; }

    /* Stacked: "Powered by" and the logo on one line, the privacy link centred under it.
       min-height rather than a fixed height, so the second line cannot crowd the logo. */
    .site-footer {
        display: flex; flex-direction: column;
        align-items: center; justify-content: center; gap: 6px;
        min-height: 96px; flex-shrink: 0; padding: 14px 0;
        background: var(--surface);
        border-top: 1px solid var(--line);
    }

    .site-footer .powered { display: flex; align-items: flex-end; gap: 12px; }
    .site-footer .powered span { font-size: 13px; line-height: 11px; height: 11px; color: var(--ink-muted); }
    .site-footer .powered img { display: block; height: 48px; width: auto; }
}

@media (max-width: 1023px) {
    .demo-banner .powered { display: block; }
    .hide-mobile { display: none !important; }

}

@media (min-width: 1024px) {
    .hide-desktop { display: none !important; }
}

/* ================================================================================================
   Phase 4 — live updates, the scanner and the exports.
   ================================================================================================ */

/* ---- toasts ---------------------------------------------------------------------------- */

/* Bottom right on a desktop, and across the bottom of a phone above the tab bar — never over
   it, which is the same mistake as drawing a footer under fixed chrome. */
.toast-stack {
    position: fixed;
    z-index: 60;
    left: 12px; right: 12px;
    bottom: calc(var(--tabbar-height) + 12px);
    display: flex; flex-direction: column-reverse; gap: 8px;
    pointer-events: none;
}

.toast {
    pointer-events: auto;
    display: block;
    padding: 12px 14px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 4px solid var(--oak);
    border-radius: var(--r-control);
    box-shadow: 0 8px 24px rgba(31, 42, 36, 0.16);
    color: var(--ink);
    animation: toast-in 220ms ease-out;
}

.toast--alert { border-left-color: var(--low-text); }
.toast--good { border-left-color: var(--in-text); }

.toast-head { display: block; font-size: 12px; font-weight: 700; letter-spacing: 0.02em; color: var(--ink-muted); text-transform: uppercase; }
.toast--alert .toast-head { color: var(--low-text); }
.toast--good .toast-head { color: var(--in-text); }
.toast-body { display: block; font-size: 14px; margin-top: 2px; }

.toast.is-leaving { animation: toast-out 240ms ease-in forwards; }

@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(6px); } }

/* A region that has just been replaced by a live update. Brief, and only a ring — moving or
   recolouring the content would make the figure harder to read at the moment it changes. */
.is-live-changed { animation: live-ring 1400ms ease-out; border-radius: var(--r-card); }

@keyframes live-ring {
    0% { box-shadow: 0 0 0 3px var(--oak-pale); }
    100% { box-shadow: 0 0 0 3px rgba(213, 230, 217, 0); }
}

/* ---- the scanner ----------------------------------------------------------------------- */

.scan-camera {
    position: relative;
    width: 100%;
    border-radius: var(--r-card);
    overflow: hidden;
    background: #14201A;
    min-height: 220px;
    display: flex; align-items: center; justify-content: center;
}

.scan-camera.is-idle { background: var(--surface-alt); border: 1px dashed var(--line); }

.scan-camera video, .scan-camera canvas { display: block; width: 100% !important; height: auto !important; }

.scan-placeholder { text-align: center; color: var(--ink-muted); padding: 24px 16px; }
.scan-placeholder p { margin: 8px 0 0; }

/* ---- barcodes -------------------------------------------------------------------------- */

/* White, with room around it. The quiet zones either side of an EAN-13 are part of the symbol,
   so a barcode drawn tight against a coloured card often will not read. */
.barcode-box {
    background: #FFFFFF;
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    padding: 14px 16px 10px;
    text-align: center;
}

/* Width, not height, and generously: 113 modules across 340px is about 3px a bar, which a phone
   camera can resolve off a screen. Sized down to 64px tall it was half a pixel a bar, and
   nothing would have read it. */
.barcode-box .barcode {
    display: block; margin: 0 auto;
    width: 100%; max-width: 340px; min-width: 240px; height: auto;
}

.barcode-digits {
    display: block;
    margin-top: 6px;
    font-size: 13px; letter-spacing: 0.22em;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

/* ---- exports --------------------------------------------------------------------------- */

.export-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }
.export-bar h2 { font-size: 17px; }

/* A square button for an icon on its own, so the header's scan control has a proper hit area
   on a phone rather than being a bare glyph. */
.btn--icon {
    width: 44px; height: 44px;
    padding: 0; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
}

.btn--icon.is-current { background: var(--oak-tint); border-color: var(--oak); color: var(--oak-deep); }

@media (min-width: 1024px) {
    .toast-stack { left: auto; right: 24px; bottom: 24px; width: 380px; }

    /* The sidebar already has the scanner, so the header control is a phone affordance. */
    .app-header .btn--icon { display: none; }
}

/* Movement, not information: the ring and the slide are decoration on top of a change the
   figures already carry. */
@media (prefers-reduced-motion: reduce) {
    .toast, .toast.is-leaving, .is-live-changed { animation: none; }
}

/* ---- role switcher --------------------------------------------------------------------- */

/* A two-segment control: the role you are in, and the one press that changes it. */
.role-switch {
    display: inline-flex;
    padding: 3px;
    gap: 3px;
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    background: var(--surface-alt);
}

.role-switch-option {
    display: inline-flex; align-items: center; gap: 6px;
    height: 34px; padding: 0 12px;
    border: 0; border-radius: 9px;
    background: transparent;
    font-family: var(--sans); font-size: 14px; font-weight: 600;
    color: var(--ink-muted);
    white-space: nowrap;
}

button.role-switch-option { cursor: pointer; }
button.role-switch-option:hover { background: var(--oak-tint); color: var(--oak-deep); }

.role-switch-option.is-current {
    background: var(--surface);
    color: var(--ink);
    box-shadow: 0 1px 2px rgba(31, 42, 36, 0.10);
}

/* The card on the two home screens, which has room to say why you would switch. */
.role-card { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }
.role-card h2 { font-size: 17px; }

/* In the sidebar the control sits on the dark green, so it needs its own colours rather than
   the paper ones. */
.sidebar .role-switch {
    display: flex;
    border-color: rgba(255, 255, 255, 0.28);
    background: rgba(0, 0, 0, 0.18);
}

.sidebar .role-switch-option { flex: 1; justify-content: center; padding: 0 8px; color: var(--oak-pale); }
.sidebar button.role-switch-option:hover { background: rgba(255, 255, 255, 0.14); color: #fff; }
.sidebar .role-switch-option.is-current { background: rgba(255, 255, 255, 0.18); color: #fff; box-shadow: none; }

/* ---- file inputs ----------------------------------------------------------------------- */

/* Left alone, a file input is the browser's own grey "Choose file" button, which is the one
   control on the import screen that does not look like it belongs to the app. The field is
   styled as a box and its button is dressed as a secondary button. */
input.file {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    background: var(--surface);
    font-family: var(--sans);
    font-size: 14px;
    color: var(--ink-muted);
}

input.file::file-selector-button {
    margin-right: 12px;
    padding: 7px 14px;
    border: 1px solid var(--line);
    border-radius: 9px;
    background: var(--surface-alt);
    font-family: var(--sans);
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
}

input.file::file-selector-button:hover { background: var(--oak-tint); border-color: var(--oak); color: var(--oak-deep); }
input.file:focus-visible { outline: 2px solid var(--oak); outline-offset: 1px; }

/* ================================================================================================
   Phase 6 — the guided tour (spec §6).

   A corner panel, deliberately not a modal. The demo exists to be clicked around, and a dialog
   that has to be dismissed before the app can be touched would cover the very screens the tour is
   describing. It sits where the toasts sit and uses the same language: across the bottom of a
   phone above the tab bar, bottom-right on a desktop.
   ================================================================================================ */

.tour {
    position: fixed;
    z-index: 70;                                   /* over the toasts, which are 60 */
    left: 12px; right: 12px;
    bottom: calc(var(--tabbar-height) + 12px);     /* never over the tab bar */
    padding: 16px;
    background: var(--surface);
    border: 1px solid var(--oak);
    border-left: 4px solid var(--oak);
    border-radius: var(--r-card);
    box-shadow: 0 10px 30px rgba(31, 42, 36, 0.20);
    animation: tour-in 200ms ease-out;
}

.tour-count {
    margin: 0;
    font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
    color: var(--oak);
}

.tour-title { font-size: 18px; line-height: 24px; margin: 4px 0 0; }

.tour-body {
    margin: 6px 0 0;
    font-size: 14px; line-height: 20px;
    color: var(--ink-muted);
}

.tour-actions {
    display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
    margin-top: 14px;
}

/* Text, not a button: leaving should be easy to find and never compete with going on. It is still
   a control in fixed chrome rather than a link in a sentence, so it gets the full 44px — the
   height comes from padding, which leaves it looking like text. */
.tour-skip {
    display: inline-flex; align-items: center;
    min-height: 44px; padding: 0 4px;
    font-size: 13px; font-weight: 600;
    color: var(--ink-muted);
    text-decoration: underline;
}

.tour-skip:hover { color: var(--ink); }

@keyframes tour-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* The ring round whatever the step is pointing at. Only a ring: moving or recolouring the thing
   being described would change the screen the tour is trying to explain. */
.is-tour-target {
    border-radius: var(--r-card);
    box-shadow: 0 0 0 3px var(--oak), 0 0 0 9px var(--oak-tint);
    animation: tour-pulse 2.4s ease-in-out infinite;

    /* tour.js scrolls the target to the top of the viewport when the panel is covering it. This
       keeps the ring off the very edge, and off the header on a desktop. */
    scroll-margin-top: 16px;
}

@keyframes tour-pulse {
    0%, 100% { box-shadow: 0 0 0 3px var(--oak), 0 0 0 9px var(--oak-tint); }
    50%      { box-shadow: 0 0 0 3px var(--oak), 0 0 0 5px var(--oak-tint); }
}

@media (prefers-reduced-motion: reduce) {
    .tour { animation: none; }
    .is-tour-target { animation: none; }
}

@media (min-width: 1024px) {
    .tour { left: auto; right: 24px; bottom: 24px; width: 380px; }
}

/* Room to scroll the last thing on a page out from behind the panel.

   Without this, a short screen simply cannot do it: the record form is 1218px on a 852px phone,
   so the furthest it scrolls is 366px, and its submit button — the one thing step 2 asks anyone to
   press — stays underneath the panel however hard you try. The fixed figure here is the fallback;
   tour.js replaces it with the panel's real height. It is CSS rather than script alone so the
   button is still reachable with JavaScript off, which is the state the rest of the tour works in.

   `.page`, not `.site-footer`: the footer's padding is the fixed tab bar's clearance and means
   something else. */
body:has(.tour) .page { padding-bottom: 260px; }

/* The tour panel stands exactly where the toasts do. Rather than guess a height to stack them by,
   the toasts move to the top of the screen while the tour is on — which matters most at step 2,
   the one that invites a live update from another device. Where :has() is unsupported the two
   simply overlap, which is what they did before this rule existed. */
body:has(.tour) .toast-stack {
    top: 12px;
    bottom: auto;
    flex-direction: column;
}

@media (min-width: 1024px) {
    body:has(.tour) .toast-stack { top: 24px; }
}

/* ================================================================================================
   Phase 6 — the collapsing filter panel.

   On a phone you scrolled past about 600px of search, selects and chips on /Products, and 700px of
   type chips, date presets and a from/to pair on /Movements, before reaching a single row of data.
   Nothing overflowed; the filter apparatus was simply the first thing a visitor saw.

   It is one <details> per screen. On a desktop there is room for all of it, so the summary is
   hidden and the panel is left open — the desktop layout is byte-for-byte what it was. The
   collapsing happens only below 1024px, and `_FilterCollapse.cshtml` explains why the closing is
   done in script rather than by overriding how <details> hides its content.
   ================================================================================================ */

.filters { display: block; }

/* The panel's own spacing has to match what the .stack gap gave the two rows it replaced, or
   every screen using it shifts by a few pixels. */
.filters > .filters-body { display: flex; flex-direction: column; gap: 16px; }

/* The chevron is a sibling of the text rather than part of it, so it stays pinned right and
   vertically centred when the label and the state wrap onto two lines at 320px. */
.filters-summary {
    display: flex; align-items: center; gap: 12px;
    padding: 8px 14px;
    min-height: 44px;
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    background: var(--surface);
    cursor: pointer;
    list-style: none;
    user-select: none;
}

/* Safari draws its own disclosure triangle through a pseudo-element that list-style cannot
   reach. */
.filters-summary::-webkit-details-marker { display: none; }

.filters-summary:hover { border-color: var(--oak); }
.filters-summary:focus-visible { outline: 2px solid var(--oak); outline-offset: 1px; }

/* Side by side while they fit, stacked when they do not. */
.filters-summary-text {
    flex: 1; min-width: 0;
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px;
}

.filters-summary-head {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: 14px; font-weight: 700; color: var(--ink);
}

.filters-summary-head svg { flex-shrink: 0; }

/* The part that earns the control its place: what is applied, while it is shut. */
.filters-summary-state { font-size: 13px; color: var(--ink-muted); }

/* A chevron that turns, so the control reads as something that opens rather than a status bar. */
.filters-summary::after {
    content: "";
    flex-shrink: 0;
    width: 10px; height: 10px;
    border-right: 2px solid var(--ink-muted);
    border-bottom: 2px solid var(--ink-muted);
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform 140ms ease;
}

.filters[open] > .filters-summary::after { transform: rotate(-135deg) translate(-2px, -2px); }

/* An open panel gets air between the summary and the controls; a shut one must not reserve it. */
.filters[open] > .filters-body { margin-top: 16px; }

/* A filtered view is worth pointing at: the list above it is not the whole story. */
.filters[data-filtered="true"] > .filters-summary {
    border-color: var(--oak);
    background: var(--oak-tint);
}

.filters[data-filtered="true"] > .filters-summary .filters-summary-state { color: var(--oak-deep); }
.filters[data-filtered="true"] > .filters-summary::after { border-color: var(--oak-deep); }

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

@media (min-width: 1024px) {
    /* Desktop is unchanged: no control, nothing to open, no extra spacing. The gap matches
       .page's own 24px, which is what separated these rows before they were wrapped. */
    .filters > .filters-summary { display: none; }
    .filters > .filters-body { gap: 24px; }
    .filters[open] > .filters-body { margin-top: 0; }
}

/* ================================================================================================
   Phase 6 — touch targets.

   Everything you press gets 44px, the figure a finger actually needs and the one WCAG 2.2's
   target-size rule is closest to. All of these are comfortable at their desktop sizes under a
   mouse, so the rules are confined to phone widths rather than applied everywhere — a 44px chip
   row on a 1440px screen just looks heavy.

   This block sits at the end of the file on purpose. It first went in beside the other
   `max-width: 1023px` rules around the shell, where `.role-switch-option` is *later* in the
   source and won on equal specificity, so the switch stayed 34px and the rule looked as though
   it had simply not worked. Last in the file is the one position that does not depend on
   remembering that.

   Inline links inside prose are deliberately left alone — the rule has never meant to break up a
   sentence, and padding a link inside a paragraph makes the lines collide.
   ================================================================================================ */

@media (max-width: 1023px) {
    /* The one button the whole demo exists to get pressed. */
    .demo-banner .btn-talk { height: 44px; padding: 0 20px; border-radius: 22px; font-size: 14px; }

    /* Still the smallest thing here, but it is a credit line rather than a call to action and
       the banner is already three lines deep on a phone. Padding rather than a full 44px. */
    .demo-banner .powered { padding: 6px 0; }

    .brand-mobile .brand-mark { width: 44px; height: 44px; }

    .chip { height: 44px; }

    /* Filters, "Order PDF", "Edit", paging, "Leave the demo". */
    .btn--small { height: 44px; padding: 0 14px; }

    /* 44 each makes the two-segment control 52px tall overall, with its 3px padding. */
    .role-switch-option { height: 44px; }

    /* Form controls, which iOS also stops zooming into once the text is 16px. The date pair on
       the movements filter was the one that showed up: two 36px boxes side by side. */
    input[type="text"], input[type="number"], input[type="date"], input[type="search"],
    input[type="email"], input[type="password"], select {
        min-height: 44px;
        font-size: 16px;
    }

    /* A <summary> is a real control — "Sign in with them instead", "Show the numbers" — not
       prose, and at 19px it reads as a caption people scroll past. */
    summary { min-height: 44px; display: flex; align-items: center; }
}
