/* ==========================================================================
   Reikia Nupirkti — single stylesheet.

   Colour is driven entirely by the custom properties below, in three states:
     :root                                    -> light palette (the default)
     @media (prefers-color-scheme: dark)      -> dark palette, unless the user
                                                 explicitly picked light
     :root[data-theme="dark"]                 -> dark palette, forced
   The server stamps data-theme on <html> from the rn.theme cookie, so a dark-mode
   user never gets a flash of white (the strict CSP rules out the usual inline
   <head> script). The two dark blocks are identical — keep them in sync.
   ========================================================================== */

:root {
  color-scheme: light dark;

  --bg: #f4f5f7;
  --bg-soft: #fafbfc;
  --card: #ffffff;
  --card-hover: #fafbff;
  --ink: #1c2430;
  --muted: #6b7686;
  --line: #e2e6ec;

  /* --brand is for text/links/borders; --brand-fill is a filled button's background
     (they diverge in dark mode, where a readable link colour is too light to sit
     under white button text). */
  --brand: #2563eb;
  --brand-fill: #2563eb;
  --brand-fill-hover: #1d4ed8;
  --brand-fill-ink: #ffffff;
  --brand-soft: #e8efff;
  --brand-soft-2: #eef4ff;

  --hover: #eef1f6;
  --btn-bg: #ffffff;
  --btn-hover: #f7f9fc;
  --input-bg: #ffffff;
  --focus: #bcd1ff;
  --code-bg: #f0f2f5;

  --danger: #dc2626;
  --danger-ink: #97211f;
  --danger-soft: #fdecec;
  --danger-line: #f4c7c7;
  --danger-hover: #fdf2f2;

  --ok: #16a34a;
  --ok-ink: #14672f;
  --ok-soft: #e9f8ef;
  --ok-soft-2: #ecfdf3;
  --ok-line: #b7e4c7;

  --warn: #d97706;
  --warn-ink: #9a5b00;
  --warn-soft: #fff3e0;

  --info-ink: #2a4ba0;
  --info-soft: #e7eefc;
  --info-line: #c3d4f7;

  /* The dark bulk-action bar keeps its own tokens: it is deliberately inverted in
     light mode, so it cannot simply reuse --ink/--card. */
  --bar-bg: #1c2430;
  --bar-ink: #ffffff;
  --bar-muted: #c7d0dd;

  --overlay: rgba(16, 24, 40, 0.45);
  --radius: 10px;
  --shadow: 0 1px 3px rgba(16, 24, 40, 0.08), 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow-lg: 0 10px 30px rgba(16, 24, 40, 0.25);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f1319;
    --bg-soft: #161c24;
    --card: #171d26;
    --card-hover: #1c2432;
    --ink: #e7ecf3;
    --muted: #9aa8ba;
    --line: #2a323e;

    --brand: #7aa5ff;
    --brand-fill: #2f6ffb;
    --brand-fill-hover: #4a83ff;
    --brand-fill-ink: #ffffff;
    --brand-soft: rgba(122, 165, 255, 0.18);
    --brand-soft-2: rgba(122, 165, 255, 0.12);

    --hover: #212a36;
    --btn-bg: #1d242e;
    --btn-hover: #263040;
    --input-bg: #131922;
    --focus: rgba(122, 165, 255, 0.55);
    --code-bg: #1e2530;

    --danger: #f87171;
    --danger-ink: #ffb9b9;
    --danger-soft: rgba(248, 113, 113, 0.15);
    --danger-line: rgba(248, 113, 113, 0.38);
    --danger-hover: rgba(248, 113, 113, 0.12);

    --ok: #34d399;
    --ok-ink: #8fe8c4;
    --ok-soft: rgba(52, 211, 153, 0.15);
    --ok-soft-2: rgba(52, 211, 153, 0.18);
    --ok-line: rgba(52, 211, 153, 0.38);

    --warn: #fbbf24;
    --warn-ink: #f3c876;
    --warn-soft: rgba(251, 191, 36, 0.15);

    --info-ink: #a8c4ff;
    --info-soft: rgba(122, 165, 255, 0.15);
    --info-line: rgba(122, 165, 255, 0.3);

    --bar-bg: #232c3a;
    --bar-ink: #eef2f8;
    --bar-muted: #9fb0c6;

    --overlay: rgba(4, 7, 12, 0.62);
    --shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 12px 34px rgba(0, 0, 0, 0.55);
  }
}

/* Forced dark — same palette as the block above (keep in sync). Placed after it so
   it wins at equal specificity when the OS says light but the user picked dark. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #0f1319;
  --bg-soft: #161c24;
  --card: #171d26;
  --card-hover: #1c2432;
  --ink: #e7ecf3;
  --muted: #9aa8ba;
  --line: #2a323e;

  --brand: #7aa5ff;
  --brand-fill: #2f6ffb;
  --brand-fill-hover: #4a83ff;
  --brand-fill-ink: #ffffff;
  --brand-soft: rgba(122, 165, 255, 0.18);
  --brand-soft-2: rgba(122, 165, 255, 0.12);

  --hover: #212a36;
  --btn-bg: #1d242e;
  --btn-hover: #263040;
  --input-bg: #131922;
  --focus: rgba(122, 165, 255, 0.55);
  --code-bg: #1e2530;

  --danger: #f87171;
  --danger-ink: #ffb9b9;
  --danger-soft: rgba(248, 113, 113, 0.15);
  --danger-line: rgba(248, 113, 113, 0.38);
  --danger-hover: rgba(248, 113, 113, 0.12);

  --ok: #34d399;
  --ok-ink: #8fe8c4;
  --ok-soft: rgba(52, 211, 153, 0.15);
  --ok-soft-2: rgba(52, 211, 153, 0.18);
  --ok-line: rgba(52, 211, 153, 0.38);

  --warn: #fbbf24;
  --warn-ink: #f3c876;
  --warn-soft: rgba(251, 191, 36, 0.15);

  --info-ink: #a8c4ff;
  --info-soft: rgba(122, 165, 255, 0.15);
  --info-line: rgba(122, 165, 255, 0.3);

  --bar-bg: #232c3a;
  --bar-ink: #eef2f8;
  --bar-muted: #9fb0c6;

  --overlay: rgba(4, 7, 12, 0.62);
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 12px 34px rgba(0, 0, 0, 0.55);
}

:root[data-theme="light"] { color-scheme: light; }

* { box-sizing: border-box; }

/* The `hidden` attribute must always win, even over components that set their
   own display (e.g. `.btn { display: inline-flex }`). Without this, any hidden
   button — like the image field's "Remove image" — would still show. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Stop mobile browsers from inflating text in landscape / on rotation. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
h1 { font-size: 1.5rem; margin: 0; }
h2 { font-size: 1.1rem; margin: 0 0 .75rem; }
code { background: var(--code-bg); padding: .1em .35em; border-radius: 5px; font-size: .9em; }

.container { max-width: 1080px; margin: 0 auto; padding: 1.25rem; }
.container.narrow { max-width: 560px; }
.muted { color: var(--muted); }
.small { font-size: .82rem; }
.nowrap { white-space: nowrap; }

/* ---- Top bar ---- */
.topbar { background: var(--card); border-bottom: 1px solid var(--line); }
.topbar-inner { display: flex; align-items: center; gap: 1rem; padding-top: .6rem; padding-bottom: .6rem; }
.brand { font-weight: 700; color: var(--ink); font-size: 1.05rem; }
.brand:hover { text-decoration: none; }
.nav { display: flex; gap: .25rem; margin-left: 1rem; }
.nav a { padding: .35rem .7rem; border-radius: 7px; color: var(--muted); }
.nav a:hover { background: var(--hover); text-decoration: none; }
.nav a.active { color: var(--brand); background: var(--brand-soft); }
.user-menu { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
.user-link { color: var(--muted); }

/* ---- Theme toggle (light / dark / follow the system) ---- */
/* A real form so it works with JavaScript off; app.js hijacks it to switch instantly.
   Which glyph shows is decided by the data-theme attribute on <html>, so the same
   markup serves both paths — JS only has to flip that one attribute. */
.theme-form { display: inline-flex; }
.theme-btn { padding: .35rem .5rem; color: var(--muted); }
.theme-btn:hover { color: var(--ink); }
.theme-ico { display: none; line-height: 0; }
.theme-ico svg { width: 17px; height: 17px; display: block; }
:root:not([data-theme]) .theme-ico-auto,
:root[data-theme="light"] .theme-ico-light,
:root[data-theme="dark"] .theme-ico-dark { display: inline-flex; }

/* ---- Buttons ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); background: var(--btn-bg); color: var(--ink);
  padding: .5rem .9rem; border-radius: 8px; font-size: .9rem; cursor: pointer;
  font-family: inherit; line-height: 1; white-space: nowrap;
}
.btn:hover { background: var(--btn-hover); text-decoration: none; }
.btn-sm { padding: .32rem .6rem; font-size: .82rem; }
.btn-block { width: 100%; }
.btn-primary { background: var(--brand-fill); border-color: var(--brand-fill); color: var(--brand-fill-ink); }
.btn-primary:hover { background: var(--brand-fill-hover); border-color: var(--brand-fill-hover); }
.btn-danger { background: var(--btn-bg); border-color: var(--danger-line); color: var(--danger); }
.btn-danger:hover { background: var(--danger-hover); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn-ghost:hover { background: var(--hover); color: var(--ink); }
.inline { display: inline; }

/* ---- Cards / layout ---- */
.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem 1.1rem; box-shadow: var(--shadow); margin-bottom: 1rem; }
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.page-actions { display: flex; gap: .4rem; }
.empty { color: var(--muted); padding: 2rem; text-align: center; background: var(--card); border: 1px dashed var(--line); border-radius: var(--radius); }

/* ---- Flash ---- */
.flash { padding: .65rem .9rem; border-radius: 8px; margin-bottom: 1rem; font-size: .9rem; }
.flash-success { background: var(--ok-soft); color: var(--ok-ink); border: 1px solid var(--ok-line); }
.flash-error { background: var(--danger-soft); color: var(--danger-ink); border: 1px solid var(--danger-line); }
.flash-info { background: var(--info-soft); color: var(--info-ink); border: 1px solid var(--info-line); }
.creds-box .cred { display: inline-block; font-size: 1.05rem; padding: .2em .5em; margin: .3rem 0; }

/* ---- Forms ---- */
.field { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .8rem; }
.field > span { font-size: .82rem; color: var(--muted); font-weight: 600; }
.field input, .field select, .field textarea {
  font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 8px; background: var(--input-bg); color: var(--ink); width: 100%;
}
.field input:focus, .field select:focus, .field textarea:focus { outline: 2px solid var(--focus); border-color: var(--brand); }
.stack { display: flex; flex-direction: column; }
.form-actions { display: flex; gap: .5rem; margin-top: .4rem; }

.grid-form { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 1rem; }
.grid-form .span-2 { grid-column: span 2; }
.grid-form .span-3 { grid-column: span 3; }

.add-item summary { cursor: pointer; font-weight: 600; padding: .25rem 0; }
.add-item[open] summary { margin-bottom: .8rem; }

/* ---- Part-name typeahead ---- */
.typeahead { position: relative; }
.typeahead-list {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 30;
  margin: 3px 0 0; padding: 0; list-style: none;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: var(--shadow); max-height: 280px; overflow-y: auto;
}
.typeahead-list li {
  padding: .5rem .65rem; cursor: pointer; border-bottom: 1px solid var(--line);
  display: flex; align-items: baseline; flex-wrap: wrap;
}
.typeahead-list li:last-child { border-bottom: none; }
.typeahead-list li:hover, .typeahead-list li.active { background: var(--brand-soft-2); }
.typeahead-name { font-weight: 600; }
.typeahead-meta { color: var(--muted); font-size: .8rem; }

/* ---- Add a needed item to an open order ---- */
.add-to-order { display: flex; gap: .8rem; align-items: flex-end; flex-wrap: wrap; }
.add-to-order .field { margin-bottom: 0; flex: 1; min-width: 200px; }

.filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .8rem; }
.filters .field { margin-bottom: 0; min-width: 130px; }
.filters .grow { flex: 1; min-width: 180px; }
.field-btn { display: flex; flex-direction: row; gap: .4rem; align-items: flex-end; }

/* ---- Tables ---- */
.table-wrap { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow-x: auto; }
.items-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.items-table th, .items-table td { text-align: left; padding: .6rem .7rem; border-bottom: 1px solid var(--line); vertical-align: top; }
.items-table thead th { background: var(--bg-soft); color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .03em; }
.items-table tbody tr:hover { background: var(--card-hover); }
.col-check { width: 34px; }
.col-actions { white-space: nowrap; text-align: right; }
.item-name { font-weight: 600; }
.item-source { font-size: .82rem; }
.item-notes { font-size: .82rem; margin-top: .15rem; max-width: 320px; }
tr.status-cancelled { opacity: .55; }
tr.status-received td .item-name { text-decoration: line-through; }

/* ---- Items grouped by source ---- */
/* Orders are placed per supplier, so the list is one card per source with a gap
   between them. The header carries the supplier, what is still to order from them,
   and the button that turns exactly those rows into an order. */
.item-groups { display: flex; flex-direction: column; gap: 1.7rem; }
.group-head {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: 0 .2rem .5rem;
}
.group-title { display: flex; align-items: center; gap: .5rem; min-width: 0; flex-wrap: wrap; }
/* A supplier name is free text: break it rather than let one long unspaced string
   (a pasted URL, say) stretch the header and push the Order button off the row. */
.group-name { font-size: 1.02rem; font-weight: 700; margin: 0; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
.group-check { width: 16px; height: 16px; flex: 0 0 auto; cursor: pointer; }
/* Controls that only mean something once app.js is running (it adds .rn-js to <html>).
   Without JavaScript they are never shown, so nothing on the page is inert. */
.js-only { display: none; }
html.rn-js input.js-only { display: inline-block; }
html.rn-js .btn.js-only { display: inline-flex; }
.group-site { color: var(--muted); font-size: .82rem; }
.group-site:hover { color: var(--brand); }
.group-meta { color: var(--muted); font-size: .82rem; }
.group-meta .group-est { color: var(--ink); font-weight: 600; }
.group-order { margin-left: auto; display: flex; align-items: center; gap: .4rem; }

/* ---- Inline quantity edit ---- */
/* The quantity reads as an editable chip: dashed underline hints it's clickable,
   hover/focus lifts it, and brief green/red flashes confirm a save or an error. */
.qty-edit {
  display: inline-flex; align-items: baseline; gap: .25rem;
  cursor: pointer; border-radius: 6px; padding: .06rem .3rem; margin: -.06rem -.3rem;
  border-bottom: 1px dashed var(--line); transition: background .12s ease, border-color .12s ease;
}
.qty-edit:hover { background: var(--brand-soft-2); border-bottom-color: var(--brand); }
/* Real keyboard focus ring (matches .field input / .qty-input), distinct from hover. */
.qty-edit:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; background: var(--brand-soft-2); border-bottom-color: var(--brand); }
.qty-edit.editing { border-bottom-color: transparent; background: transparent; }
.qty-edit.saving { opacity: .6; }
.qty-edit.saved { background: var(--ok-soft-2); border-bottom-color: var(--ok); }
.qty-edit.save-error { background: var(--danger-soft); border-bottom-color: var(--danger); }
.qty-unit { color: var(--muted); font-size: .9em; }
.qty-input {
  width: 5rem; font: inherit; padding: .2rem .4rem;
  border: 1px solid var(--brand); border-radius: 6px; background: var(--input-bg); color: var(--ink);
}
.qty-input:focus { outline: 2px solid var(--focus); }
.qty-error { display: block; color: var(--danger); font-size: .75rem; margin-top: .2rem; }

/* ---- Item image (list thumbnail + cell layout) ---- */
.item-cell { display: flex; gap: .6rem; align-items: flex-start; }
.item-cell-main { min-width: 0; }
.item-thumb { flex: 0 0 auto; display: block; line-height: 0; }
.item-thumb img {
  width: 46px; height: 46px; object-fit: cover; display: block;
  border: 1px solid var(--line); border-radius: 8px; background: var(--card);
}
.item-thumb:hover img { border-color: var(--brand); }

/* ---- Image upload field ---- */
.image-drop {
  display: flex; flex-direction: column; align-items: center; gap: .6rem;
  text-align: center; cursor: pointer; padding: 1rem;
  border: 1.5px dashed var(--line); border-radius: 10px; background: var(--bg-soft);
  transition: border-color .15s ease, background .15s ease; margin-bottom: .8rem;
}
.image-drop:hover, .image-drop:focus, .image-drop.dragover {
  border-color: var(--brand); background: var(--brand-soft-2); outline: none;
}
.image-drop.has-image { padding: .7rem; }
/* Brief green flash when an image is attached (esp. via paste) so it's obvious. */
.image-drop.just-added { border-color: var(--ok); background: var(--ok-soft-2); animation: image-pop .4s ease; }
@keyframes image-pop { 0% { transform: scale(.985); } 60% { transform: scale(1.008); } 100% { transform: scale(1); } }
.image-preview { max-width: 100%; max-height: 240px; border-radius: 8px; border: 1px solid var(--line); background: var(--card); }
.image-hint { color: var(--muted); font-size: .88rem; }
/* Show the touch wording (camera/gallery) only on touch devices; paste/drag
   wording everywhere else. Keyed off the pointer, not width, so a touch tablet
   gets the right guidance too. */
.image-hint .touch-hint { display: none; }
@media (hover: none) and (pointer: coarse) {
  .image-hint .desktop-hint { display: none; }
  .image-hint .touch-hint { display: block; }
}

/* ---- Badges ---- */
.badge { display: inline-block; padding: .12em .55em; border-radius: 999px; font-size: .75rem; font-weight: 600; background: var(--hover); color: var(--muted); text-transform: capitalize; }
.urgency-urgent { background: var(--danger-soft); color: var(--danger-ink); }
.urgency-normal { background: var(--info-soft); color: var(--info-ink); }
.urgency-low { background: var(--hover); color: var(--muted); }
.status-badge-needed { background: var(--warn-soft); color: var(--warn-ink); }
.status-badge-ordered { background: var(--info-soft); color: var(--info-ink); }
.status-badge-received { background: var(--ok-soft); color: var(--ok-ink); }
.status-badge-cancelled { background: var(--hover); color: var(--muted); }
.order-open { background: var(--warn-soft); color: var(--warn-ink); }
.order-placed { background: var(--info-soft); color: var(--info-ink); }
.order-received { background: var(--ok-soft); color: var(--ok-ink); }
.badge-overdue { background: var(--danger-soft); color: var(--danger-ink); }
.overdue-date { color: var(--danger); font-weight: 600; }
.badge-current { background: var(--ok-soft); color: var(--ok-ink); text-transform: none; }

/* ---- Bulk action bar ---- */
.bulkbar {
  position: sticky; bottom: 0; margin-top: 1rem;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  background: var(--bar-bg); color: var(--bar-ink); padding: .7rem 1rem; border-radius: 10px; box-shadow: var(--shadow);
}
.bulkbar .btn-primary { background: var(--brand-fill); border-color: var(--brand-fill); color: var(--brand-fill-ink); }
.bulkbar .btn-ghost { color: var(--bar-muted); }
.bulkbar .btn-ghost:hover { background: rgba(255, 255, 255, .12); color: var(--bar-ink); }
/* The [hidden] attribute must win over the display:flex above. */
.bulkbar[hidden] { display: none; }
.bulkbar-actions { display: flex; align-items: center; gap: .4rem; }
/* Running subtotal of the selected items (per currency), so a purchaser can batch
   against a budget or free-shipping threshold without creating an order to check. */
.bulkbar-subtotal { margin-left: .75rem; color: var(--bar-muted); font-size: .85rem; }
.bulkbar-subtotal:empty { display: none; }

/* ---- Auth ---- */
.auth-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 1.5rem; }
.auth-card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); padding: 2rem; width: 100%; max-width: 380px; }
.auth-brand { text-align: center; margin-bottom: 1.25rem; }
.auth-foot { text-align: center; margin-top: 1rem; }

/* ---- Order details (supplier order no. / tracking / expected / notes) ---- */
.order-details summary { cursor: pointer; font-weight: 600; padding: .25rem 0; font-size: .9rem; color: var(--muted); }
.order-details[open] summary { margin-bottom: .8rem; color: var(--ink); }
/* Notes can be long / multi-line: give them their own full-width row in the meta card. */
.order-notes { width: 100%; white-space: pre-wrap; overflow-wrap: anywhere; }
.order-notes .muted { margin-right: .4rem; }

/* ---- Per-item receive (partial receiving) ---- */
.receive-form { display: inline-flex; align-items: center; gap: .3rem; }
.receive-qty {
  width: 4.8rem; font: inherit; padding: .3rem .45rem;
  border: 1px solid var(--line); border-radius: 6px; background: var(--input-bg); color: var(--ink);
}
.receive-qty:focus { outline: 2px solid var(--focus); border-color: var(--brand); }

/* ---- Misc ---- */
.meta-card { display: flex; flex-wrap: wrap; gap: 1.2rem; align-items: center; }
.order-actions { margin-left: auto; display: flex; gap: .5rem; align-items: center; }
.inline-field { display: inline-flex; align-items: center; gap: .4rem; font-size: .85rem; color: var(--muted); }
.inline-field input { width: 110px; padding: .35rem .5rem; border: 1px solid var(--line); border-radius: 8px; font: inherit; background: var(--input-bg); color: var(--ink); }
.place-form, .shipping-form { display: inline-flex; align-items: center; gap: .5rem; }
.totals-card { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; justify-content: flex-end; }
.totals-card .grand { border-left: 1px solid var(--line); padding-left: 1.5rem; }
.backup-codes { display: grid; grid-template-columns: repeat(2, 1fr); gap: .4rem; list-style: none; padding: 0; }
.backup-codes code { font-size: 1rem; }
.qr { text-align: center; margin: 1rem 0; }
.qr img { border: 1px solid var(--line); border-radius: 8px; background: #fff; }
.setup-steps { color: var(--muted); }
.error-card { text-align: center; }
.footer { text-align: center; color: var(--muted); font-size: .8rem; padding: 2rem 1rem; }

/* ---- Modal ---- */
.modal-overlay { position: fixed; inset: 0; background: var(--overlay); display: flex; align-items: center; justify-content: center; z-index: 50; padding: 1rem; }
.modal-box { background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: 12px; padding: 1.3rem; max-width: 380px; width: 100%; box-shadow: var(--shadow-lg); }
.modal-text { margin: 0 0 1.1rem; }
.modal-actions { display: flex; justify-content: flex-end; gap: .5rem; }

/* ---- Export dialog ---- */
.export-box { max-width: 480px; max-height: 86vh; overflow-y: auto; text-align: left; }
.export-title { margin: 0 0 1rem; font-size: 1.1rem; }
.export-format { display: flex; gap: 1.2rem; }
.export-radio { display: inline-flex; align-items: center; gap: .35rem; font-weight: 400; color: var(--ink); margin: 0; }
.export-radio input { width: auto; }
.export-columns { display: grid; grid-template-columns: 1fr 1fr; gap: .3rem .9rem; }
.export-col { display: flex; align-items: center; gap: .45rem; font-weight: 400; color: var(--ink); margin: 0; }
.export-col input { width: auto; }
.export-note { margin: .4rem 0 0; }
.btn.is-disabled { opacity: .5; pointer-events: none; }

/* ---- Changelog ("What's new") ---- */
.changelog { max-width: 780px; }
.changelog-version h2 { display: flex; align-items: center; gap: .6rem; margin-bottom: .3rem; }
.changelog-list { margin: .2rem 0 0; padding-left: 1.25rem; }
.changelog-list li { margin: .5rem 0; }
.changelog-list ul { padding-left: 1.1rem; margin: .35rem 0 0; }
.changelog-list ul li { margin: .3rem 0; }

/* ---- Print ---- */
.print-page { max-width: 1000px; margin: 0 auto; padding: 1.5rem; }
.print-head { margin-bottom: 1rem; }
.print-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.print-table th, .print-table td { border: 1px solid var(--line); padding: .35rem .5rem; text-align: left; }
@media print {
  /* Force the light palette on paper: printers don't lay down the dark background, so
     dark-mode text tokens would print near-white on white. The selector list has to
     MATCH the specificity of the two dark blocks above (a bare :root is weaker than
     `:root:not([data-theme="light"])`, and would lose) — being later in the file is
     what makes it win. */
  :root, :root:not([data-theme="light"]), :root[data-theme="dark"], :root[data-theme="light"] {
    --bg: #fff; --bg-soft: #fff; --card: #fff; --card-hover: #fff;
    --ink: #000; --muted: #444; --line: #999;
    /* The badge/link/flash tokens too: a dark-mode --warn-ink (pale yellow) or --brand
       (light blue) would print near-invisible on white paper. */
    --brand: #1746b8; --brand-fill: #1746b8; --brand-fill-hover: #1746b8; --brand-fill-ink: #fff;
    --brand-soft: #fff; --brand-soft-2: #fff;
    --hover: #f0f0f0; --btn-bg: #fff; --btn-hover: #fff; --input-bg: #fff;
    --focus: #999; --code-bg: #f0f0f0;
    --danger: #a11212; --danger-ink: #a11212; --danger-soft: #fff; --danger-line: #999; --danger-hover: #fff;
    --ok: #146c34; --ok-ink: #146c34; --ok-soft: #fff; --ok-soft-2: #fff; --ok-line: #999;
    --warn: #8a4b00; --warn-ink: #8a4b00; --warn-soft: #fff;
    --info-ink: #1746b8; --info-soft: #fff; --info-line: #999;
    --bar-bg: #fff; --bar-ink: #000; --bar-muted: #444;
    --overlay: rgba(0, 0, 0, .3); --shadow: none; --shadow-lg: none;
  }
  .no-print { display: none !important; }
  .print-table th { background: #eee !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  /* Never print a half-played animation: a card caught mid-entrance would come out
     faded or offset on paper. */
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ==========================================================================
   Motion
   Every animation lives here. All of it is decoration layered on top of a
   layout that is already correct — transforms, opacity and filters only, no
   geometry — so the reduced-motion block at the end of this file can switch the
   whole lot off. Values that vary per element (stagger indices) are set from
   app.js through CSSOM (el.style.setProperty), never as a style attribute,
   which the CSP forbids.
   ========================================================================== */

/* ---- Micro-interactions ---- */
.btn { transition: background-color .14s ease, border-color .14s ease, color .14s ease, transform .09s ease; }
.btn:active { transform: translateY(1px) scale(.99); }
.nav a, .user-link, .theme-btn { transition: background-color .14s ease, color .14s ease; }
.card, .table-wrap { transition: border-color .18s ease, box-shadow .18s ease; }
.items-table tbody td { transition: background-color .16s ease; }
.item-thumb img { transition: transform .18s ease, border-color .14s ease; }
.item-thumb:hover img { transform: scale(1.07); }
.image-preview { transition: transform .18s ease; }
.group-head { transition: opacity .2s ease; }

/* ---- Entrances ---- */
@keyframes rn-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes rn-drop { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes rn-arrive { from { opacity: 0; transform: translateX(-42px); } to { opacity: 1; transform: none; } }
@keyframes rn-pop { 0% { transform: scale(1); } 40% { transform: scale(1.32); } 100% { transform: scale(1); } }

/* Source groups fan in on load. The stagger uses :nth-child rather than a custom
   property so it needs no per-element style (CSP) and runs before app.js loads. */
.item-group { animation: rn-rise .3s cubic-bezier(.2, .7, .3, 1) both; }
.item-group:nth-child(2) { animation-delay: .05s; }
.item-group:nth-child(3) { animation-delay: .1s; }
.item-group:nth-child(4) { animation-delay: .15s; }
.item-group:nth-child(5) { animation-delay: .2s; }
.item-group:nth-child(n+6) { animation-delay: .25s; }

.flash { animation: rn-drop .3s cubic-bezier(.2, .8, .3, 1) both; }
.typeahead-list { animation: rn-drop .16s ease both; }

/* Items of a just-created order slide in from the left — the other half of the
   "the group flew over here" gesture that started on the items page. */
.is-arriving .items-table tbody tr { animation: rn-arrive .4s cubic-bezier(.2, .8, .3, 1) both; }
.is-arriving .items-table tbody tr:nth-child(2) { animation-delay: .05s; }
.is-arriving .items-table tbody tr:nth-child(3) { animation-delay: .1s; }
.is-arriving .items-table tbody tr:nth-child(4) { animation-delay: .15s; }
.is-arriving .items-table tbody tr:nth-child(5) { animation-delay: .2s; }
.is-arriving .items-table tbody tr:nth-child(6) { animation-delay: .25s; }
.is-arriving .items-table tbody tr:nth-child(n+7) { animation-delay: .3s; }

/* ---- Ordering: the group leaves for the Orders tab ---- */
/* A short wind-up to the left, then out to the right — the direction of travel the
   nav bar implies. The containers get overflow-x: clip only while this runs, so a
   card on its way out can never add a horizontal scrollbar. */
@keyframes rn-fly-out {
  0% { opacity: 1; transform: none; }
  18% { transform: translateX(-10px) scale(.99); }
  100% { opacity: 0; transform: translateX(115%) rotate(2.5deg) scale(.88); }
}
@keyframes rn-row-out {
  0% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateX(70%) scale(.94); }
}
/* Two class names deep on purpose: it has to beat the mobile `.table-wrap
   { overflow-x: visible }` rule later in the file, or a card on its way out would
   widen the page on a phone. */
.item-groups.is-animating, .table-wrap.is-animating { overflow-x: clip; }
/* `animation: none` first: the entrance animation's `both` fill parks opacity 1 on the
   card, and an animation's value beats a plain declaration — without cancelling it the
   dimming below would silently do nothing. (A card is never dimmed and flying at once.) */
.item-group.is-dimmed { animation: none; opacity: .45; transition: opacity .2s ease; }
.item-group.is-flying { animation: rn-fly-out .52s cubic-bezier(.5, -0.25, .75, .4) forwards; }
/* --fly-i (row index) and --fly-step (gap between rows) come from app.js, which shrinks
   the step for a big selection so 40 rows don't take four seconds to leave. */
.items-table tr.is-flying { animation: rn-row-out .44s cubic-bezier(.45, 0, .7, .3) forwards; animation-delay: calc(var(--fly-i, 0) * var(--fly-step, 45ms)); }

/* ---- Deleting: the row disintegrates, cell by cell ---- */
@keyframes rn-dust {
  0% { opacity: 1; filter: blur(0); transform: none; }
  55% { opacity: .4; filter: blur(2.5px); transform: translateX(6px) scale(.99); }
  100% { opacity: 0; filter: blur(7px); transform: translateX(18px) scale(.96); }
}
tr.is-dusting > td { animation: rn-dust .46s ease-in forwards; animation-delay: calc(var(--cell-i, 0) * 26ms); }
tr.is-dusting { background: transparent !important; }

/* Cancelling isn't destruction — the row just drains of colour. */
@keyframes rn-drain { to { opacity: .3; filter: grayscale(1); } }
tr.is-draining > td { animation: rn-drain .3s ease forwards; }

/* ---- Highlight the row a write just touched ---- */
/* After adding, editing, reopening or receiving an item the server names that row,
   so it announces itself instead of leaving you to hunt for it in a grouped list. */
@keyframes rn-glow {
  0% { background-color: var(--ok-soft-2); }
  60% { background-color: var(--ok-soft-2); }
  100% { background-color: transparent; }
}
@keyframes rn-glow-edge {
  0% { box-shadow: inset 3px 0 0 var(--ok); }
  60% { box-shadow: inset 3px 0 0 var(--ok); }
  100% { box-shadow: inset 3px 0 0 transparent; }
}
/* Two deliberate details:
   - no `both` fill: a forwards fill would park `background-color: transparent` on those
     cells for good, and the row could then never show a selection or hover wash again;
   - :not() on the exit classes: `tr.is-dusting > td` / `tr.is-draining > td` above have the
     same specificity and come earlier, so a bare `tr.is-new > td` would win the animation
     property — the row a write just highlighted would play NO exit animation, and since its
     computed animation-name never changed, no animationend would fire and the delete would
     sit there until whenAnimationsEnd timed out. */
tr.is-new:not(.is-dusting):not(.is-draining) > td { animation: rn-glow 1.9s ease-out; }
tr.is-new:not(.is-dusting):not(.is-draining) > td:first-child { animation: rn-glow 1.9s ease-out, rn-glow-edge 1.9s ease-out; }

/* ---- Selection ---- */
.items-table tbody tr.is-selected > td { background-color: var(--brand-soft-2); }
.items-table tbody tr.is-selected > td:first-child { box-shadow: inset 3px 0 0 var(--brand); }

@keyframes rn-bar-in { from { opacity: 0; transform: translateY(120%); } to { opacity: 1; transform: none; } }
@keyframes rn-bar-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(120%); } }
.bulkbar.is-in { animation: rn-bar-in .26s cubic-bezier(.2, .8, .3, 1) both; }
.bulkbar.is-out { animation: rn-bar-out .18s ease-in both; }
.bulkbar-count strong { display: inline-block; }
.bulkbar-count strong.is-pop { animation: rn-pop .28s ease; }

/* ---- Saving a quantity inline ---- */
.qty-edit .qty-num { display: inline-block; }
.qty-edit.saved .qty-num { animation: rn-pop .3s ease; }

/* ---- Modal in / out ---- */
@keyframes rn-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rn-box-in { from { opacity: 0; transform: translateY(12px) scale(.96); } to { opacity: 1; transform: none; } }
.modal-overlay { animation: rn-fade-in .16s ease both; }
.modal-box { animation: rn-box-in .22s cubic-bezier(.2, .8, .3, 1) both; }
.modal-overlay.is-closing { animation: rn-fade-in .16s ease reverse both; }
.modal-overlay.is-closing .modal-box { animation: rn-box-in .16s ease reverse both; }

/* ---- An order advancing gets stamped ---- */
@keyframes rn-stamp {
  0% { opacity: 0; transform: scale(1.9) rotate(-9deg); }
  55% { opacity: 1; transform: scale(.94) rotate(1.5deg); }
  100% { opacity: 1; transform: none; }
}
.badge.is-stamped { animation: rn-stamp .5s cubic-bezier(.2, .8, .3, 1) both; }

/* ---- Switching theme ---- */
/* Colours cross-fade instead of snapping. app.js adds this class for ~320ms around
   the switch only, so nothing else in the app pays for a global transition. */
html.theme-anim, html.theme-anim * {
  transition: background-color .3s ease, color .3s ease, border-color .3s ease, box-shadow .3s ease !important;
}

/* ---- Page-to-page transitions (cross-document view transitions) ---- */
/* Every action here is POST -> redirect -> GET, so each one is a full navigation. Opting
   in lets the browser cross-fade the two documents instead of blanking between them, which
   is what turns a series of page loads into one continuous app — and it finishes the
   "ordered group flies out, order page arrives" gesture rather than cutting it.
   Purely additive: a browser without support (Firefox today) ignores the at-rule and
   navigates exactly as before. No JavaScript, so it also works with JS off. */
@view-transition { navigation: auto; }

/* Snappier than the UA's 250ms default — this is a tool, not a slideshow. Only the
   duration is overridden, so the UA keeps its correctly-paired cross-fade (the default
   plus-lighter blend that stops the two layers washing out mid-fade). */
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .2s; }

/* The top bar is identical on every page. Naming it takes it OUT of the page snapshot, so
   it stays visually put across the navigation instead of cross-fading with itself (which
   ghosts the nav labels). Its content then swaps instantly: `mix-blend-mode: normal` so the
   two identical layers paint over each other rather than adding up into a bright flash. */
.topbar { view-transition-name: rn-topbar; }
::view-transition-old(rn-topbar),
::view-transition-new(rn-topbar) { animation: none; mix-blend-mode: normal; }

/* Don't play the source-group entrance underneath a page transition — the cross-fade IS
   the entrance, and doing both reads as a stutter. `:not(.is-flying)` matters: a card on
   its way out is still mid-animation when the outgoing snapshot is taken, and cancelling
   that animation would snap it back into place in the picture the browser keeps. No-ops
   where :active-view-transition is unsupported, i.e. today's behaviour. */
:root:active-view-transition .item-group:not(.is-flying) { animation: none; }

@media (max-width: 768px) {
  /* ---- General spacing ---- */
  .container { padding: 1rem .9rem; }
  .page-head { flex-wrap: wrap; }
  .page-head h1 { font-size: 1.3rem; }

  /* ---- Top bar: keep EVERY destination reachable. The nav wraps onto its
     own full-width row (horizontally scrollable if it ever overflows) instead
     of being hidden — no JS, so it can never break. ---- */
  .topbar-inner { flex-wrap: wrap; gap: .35rem .8rem; }
  .nav { order: 3; width: 100%; margin-left: 0; gap: .3rem; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: .1rem; }
  .nav a { white-space: nowrap; padding: .55rem .85rem; }
  .user-menu { margin-left: auto; }

  /* ---- Forms: single column; 16px controls so iOS doesn't zoom on focus ---- */
  .grid-form { grid-template-columns: 1fr; }
  .grid-form .span-2, .grid-form .span-3 { grid-column: span 1; }
  .export-columns { grid-template-columns: 1fr; }
  .field input, .field select, .field textarea { font-size: 16px; padding: .6rem .65rem; }
  .filters { flex-direction: column; align-items: stretch; }
  .filters .field, .filters .grow { min-width: 0; width: 100%; }
  .field-btn .btn { flex: 1; }

  /* ---- Bigger touch targets ---- */
  .btn { padding: .6rem 1rem; }
  .btn-sm { padding: .5rem .8rem; font-size: .85rem; }
  input[type="checkbox"] { width: 20px; height: 20px; }
  .theme-btn { padding: .5rem .55rem; }

  /* ---- Tables become stacked cards (each row = a labelled card) ---- */
  .table-wrap { overflow-x: visible; background: transparent; border: none; box-shadow: none; }
  .items-table { display: block; }
  .items-table thead { display: none; }
  .items-table tbody { display: block; }
  .items-table tr { display: block; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: .75rem; padding: .2rem 0; }
  .items-table tbody tr:hover { background: var(--card); }
  .items-table td { display: block; position: relative; text-align: right; border: none; padding: .4rem .9rem .4rem 42%; overflow-wrap: anywhere; }
  .items-table td::before {
    content: attr(data-label); position: absolute; top: .4rem; left: .9rem; width: 34%;
    text-align: left; font-weight: 600; color: var(--muted); font-size: .78rem;
    text-transform: uppercase; letter-spacing: .02em;
  }
  /* Main cell (the part / first column): full width, left-aligned, no label. */
  .items-table td.cell-main { text-align: left; padding: .6rem .9rem; }
  .items-table td.cell-main::before { content: none; }
  /* Selection checkbox: use the SAME label-left / value-right layout as every other data
     cell (the generic td rule above) — "Add to order" label in the left gutter, the checkbox
     right-aligned. The desktop `.col-check { width: 34px }` rule must be reset to auto here,
     or the stacked-card cell stays 34px wide and "Add to order" wraps vertically (the bug).
     Only shown when the cell actually holds a checkbox. */
  .items-table td.col-check { width: auto; }
  .items-table td.col-check:not(:has(input)) { display: none; }
  /* Actions: a wrapping row of comfortably-sized buttons. */
  .items-table td.col-actions { display: flex; flex-wrap: wrap; gap: .45rem; text-align: left; padding: .6rem .9rem; border-top: 1px solid var(--line); margin-top: .2rem; }
  .items-table td.col-actions::before { content: none; }
  .items-table td.col-actions form.inline { display: inline-flex; }
  .items-table td.col-actions .btn { min-height: 40px; }
  .item-notes { max-width: none; }
  /* Inline qty: 16px input so iOS doesn't zoom on focus; a roomier tap target. */
  .qty-edit { padding: .3rem .5rem; margin: 0; }
  .qty-input { font-size: 16px; width: 6rem; }
  .receive-qty { font-size: 16px; width: 5.5rem; }

  /* Selected/new cards read by their card edge, not a cell edge, once stacked. */
  .items-table tbody tr.is-selected > td { background-color: transparent; }
  .items-table tbody tr.is-selected { background: var(--brand-soft-2); border-color: var(--brand); }
  .items-table tbody tr.is-selected > td:first-child { box-shadow: none; }
  /* No inset edge on a stacked card — the wash alone reads it. The selector has to repeat
     the :not() guards from the motion section, or it loses on specificity and the edge
     animation comes back. */
  tr.is-new:not(.is-dusting):not(.is-draining) > td:first-child { animation: rn-glow 1.9s ease-out; }

  /* ---- Grouped list: the header sits above its stack of cards ---- */
  .item-groups { gap: 1.4rem; }
  .group-head { padding: 0 .1rem .35rem; }
  .group-order { margin-left: 0; width: 100%; }
  .group-order .btn { flex: 1; }

  /* ---- Order detail: stack meta / actions / totals ---- */
  .meta-card { flex-direction: column; align-items: stretch; gap: .55rem; }
  .order-actions { margin-left: 0; flex-direction: column; align-items: stretch; gap: .55rem; }
  .place-form, .shipping-form { flex-wrap: wrap; gap: .5rem; }
  .inline-field, .inline-field input { width: 100%; }
  .order-actions .btn, .place-form .btn, .shipping-form .btn { width: 100%; }
  .totals-card { flex-direction: column; align-items: stretch; gap: .5rem; justify-content: flex-start; }
  .totals-card .grand { border-left: none; border-top: 1px solid var(--line); padding-left: 0; padding-top: .5rem; }

  /* ---- Bulk action bar ---- */
  .bulkbar { flex-wrap: wrap; gap: .6rem; justify-content: center; }
  .bulkbar-actions { width: 100%; }
  .bulkbar .btn-primary { flex: 1; }
}

/* Reduced motion: keep every state change (a class still lands, so JS that waits for
   animationend still fires), but collapse the duration so nothing moves. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  .item-thumb:hover img { transform: none; }
  .btn:active { transform: none; }
  /* The view-transition pseudo-elements live outside the element tree, so the `*` rules
     above never reach them. Killing their animations makes a navigation an instant swap
     while leaving the opt-in itself alone (a browser that ignored a media-query-wrapped
     @view-transition would otherwise lose the feature for everyone). */
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
