/* ============================================================================
   JustKnits — "Mill" redesign theme  (ported from the Claude Design handoff
   assets/theme.css + assets/swatch.css)
   ----------------------------------------------------------------------------
   A warm, editorial operations system. Parchment surfaces, ink + terracotta +
   pine palette pulled from the yarn-swatch tones, Spectral display serif over a
   Hanken Grotesk UI grotesk. Reuses the knit-swatch texture + the existing
   censor mechanic (.jk-censored / .jk-money — see accounting-censor.js).

   Loaded globally from base.html (after base.css so Mill tokens win). Component
   classes are authored bare (.btn/.card/.pill/.field…) exactly as the prototype
   so templates read like the design source.
   ============================================================================ */

:root {
  /* ---- Surfaces (warm neutrals) ---- */
  --paper:        #F4F1EA;   /* app background — warm parchment */
  --paper-2:      #EDE8DC;   /* sunken wells, rails */
  --surface:      #FCFAF4;   /* cards, panels — paperwhite */
  --surface-2:    #FBF9F4;   /* tinted surface, table head */
  --border:       #E5DFD1;   /* hairline */
  --border-2:     #D8D0BD;   /* stronger / inputs */

  /* ---- Ink ramp (warm near-blacks) ---- */
  --ink:          #211C16;   /* headings, primary text */
  --ink-2:        #514A3E;   /* body */
  --ink-3:        #837A69;   /* muted labels */
  --ink-4:        #A89E89;   /* faint meta, placeholder */

  /* ---- Brand accent: terracotta (knit-clay) ---- */
  --clay:         #B0492B;
  --clay-hover:   #97391F;
  --clay-50:      #FBEDE6;
  --clay-100:     #F4D8CA;
  --clay-700:     #7E3019;

  /* ---- Semantic (warmed) ---- */
  --pine:         #2F6B4F;   /* revenue / positive / in-stock */
  --pine-50:      #E5EFE8;
  --pine-100:     #CFE4D5;
  --pine-700:     #1F4F39;

  /* Sales Performance comparison palette — categorical hues indexed by age 1..4.
     All four clear 3:1 against --surface; the normal-vision adjacent floor is
     ΔE 15.8.

     ACCESSIBILITY, LOAD-BEARING: the worst adjacent CVD pair is --chart-past-3
     / --chart-past-4 at ΔE 7.9 under deuteranopia. That is inside the 6–8 legal
     band only with mandatory secondary encoding: direct year tags and the
     labelled legend. Keep the 2.4px current-year vs 1.6px comparison stroke as
     an additional current/past channel, and revalidate any palette/encoding change.
     A 6th simultaneous line would push adjacent-pair CVD below the legal band,
     which is why the year picker caps at 4 prior years.
     static/js/sales-charts.js repeats these as literals (SVG attributes cannot
     read custom properties); keep the two in step. */
  --chart-past-1: #C1782B;   /* last year — ochre */
  --chart-past-2: #6A5AA8;   /* violet */
  --chart-past-3: #B85C7A;   /* rose */
  --chart-past-4: #1B8EA6;   /* oldest offered — teal */

  /* The Sales by Country comparison bars reuse --chart-past-1..4 above, indexed
     by the year's AGE — never by its index in the selection, so adding a year
     never repaints the others. Deliberately NOT a second set of tokens: the bar
     and the chart line for a given year must be one colour by construction, and
     the CVD analysis noted above was done on these values. NOTE the "3:1" above
     is measured against --surface; the panel tracks its bars on --paper-2, where
     --chart-past-1 reaches only 2.87:1 at full opacity — which is why those bars
     carry a 1px inset edge rather than relying on the fill. TWIN: the PAST array
     in static/js/sales-charts.js (SVG presentation attributes cannot read custom
     properties, so that copy is literals). A test pins the two equal. */

  --brick:        #C13A37;   /* danger / negative / sale tag */
  --brick-50:     #FBE9E7;
  --brick-100:    #F5D2CE;
  --brick-700:    #8F2A28;

  --ochre:        #C0871A;   /* caution / low-stock */
  --ochre-50:     #F8EFD7;
  --ochre-100:    #EFDFB3;
  --ochre-700:    #8A5E0E;

  --plum:         #6D4A8A;   /* admin / re-orders */
  --plum-50:      #EFE8F3;
  --plum-100:     #DFD0E6;
  --plum-700:     #4E3266;

  --slate:        #3E5C7E;   /* info / calendar */
  --slate-50:     #E7ECF2;
  --slate-100:    #CFDAE6;

  /* ---- Radii ---- */
  --r-sm:  6px;
  --r:     10px;
  --r-lg:  14px;
  --r-xl:  20px;
  --r-pill: 999px;

  /* ---- Shadows (warm, low) ---- */
  --sh-sm: 0 1px 2px rgba(60,48,30,.06);
  --sh:    0 1px 3px rgba(60,48,30,.08), 0 1px 2px rgba(60,48,30,.05);
  --sh-md: 0 6px 16px -6px rgba(60,48,30,.16), 0 2px 6px rgba(60,48,30,.06);
  --sh-lg: 0 18px 40px -12px rgba(50,40,24,.26), 0 6px 14px rgba(50,40,24,.08);

  /* ---- Motion ---- */
  --ease:       cubic-bezier(.22,.61,.36,1);
  --ease-press: cubic-bezier(.34,1.4,.5,1.01);
  --dur-fast: .16s;
  --dur:      .28s;

  /* ---- Type ---- */
  --font-ui:      'Hanken Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-display: 'Spectral', Georgia, 'Times New Roman', serif;
  --font-mono:    'Spectral', ui-monospace, monospace;
}

/* ============================ Base ============================ */
.jk-mill,
.jk-mill * { box-sizing: border-box; }
body.jk-mill {
  margin: 0;
  font-family: var(--font-ui);
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "tnum" 0;
}
.font-display { font-family: var(--font-display); font-weight: 600; letter-spacing: -.01em; }
.tnum { font-variant-numeric: tabular-nums; }
.font-mono { font-family: var(--font-mono); }

/* Eyebrow / uppercase label */
.eyebrow {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .12em; color: var(--ink-3);
}

.jk-mill ::selection { background: var(--clay-100); color: var(--clay-700); }

/* The `hidden` attribute must actually hide. The UA stylesheet's
   [hidden]{display:none} is beaten by ANY author rule that sets display, and
   eleven component classes here do (.pill, .chip, .dot, …). So an element written
   as `<a class="pill" hidden>` renders as an empty coloured nub, and JS toggling
   `el.hidden = true` silently fails to hide it. Caught on the Sales Performance
   drill pill, which sat visible-but-empty under the legend at all times. */
[hidden] { display: none !important; }

/* Focus ring */
.jk-mill .focusable:focus-visible,
.jk-mill input:focus-visible, .jk-mill select:focus-visible,
.jk-mill textarea:focus-visible, .jk-mill button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--clay-50), 0 0 0 1px var(--clay);
}

/* ============================ Scrollbars ============================ */
.thin-scroll::-webkit-scrollbar { height: 8px; width: 8px; }
.thin-scroll::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 99px; }
.thin-scroll::-webkit-scrollbar-track { background: transparent; }

/* ============================ Buttons ============================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-ui); font-weight: 600; font-size: 14px;
  border-radius: var(--r); border: 1px solid transparent; cursor: pointer;
  padding: 9px 16px; transition: background var(--dur-fast) var(--ease),
    border-color var(--dur-fast), color var(--dur-fast), transform var(--dur-fast) var(--ease-press), box-shadow var(--dur-fast);
  white-space: nowrap; user-select: none; line-height: 1.1; text-decoration: none;
}
.btn:active { transform: scale(.97); }
.btn-primary { background: var(--clay); color: #fff; box-shadow: var(--sh-sm); }
.btn-primary:hover { background: var(--clay-hover); color: #fff; }
.btn-ink { background: var(--ink); color: var(--paper); }
.btn-ink:hover { background: #34291c; color: var(--paper); }
.btn-danger { background: var(--brick); color: #fff; box-shadow: var(--sh-sm); }
.btn-danger:hover { background: var(--brick-700); color: #fff; }
.btn-ghost { background: var(--surface); color: var(--ink-2); border-color: var(--border-2); box-shadow: var(--sh-sm); }
.btn-ghost:hover { background: var(--surface-2); border-color: var(--ink-4); color: var(--ink-2); }
.btn-outline { background: var(--surface); color: var(--clay-700); border-color: var(--clay); box-shadow: var(--sh-sm); }
.btn-outline:hover { background: var(--clay-50); color: var(--clay-700); }
.btn-quiet { background: transparent; color: var(--ink-2); }
.btn-quiet:hover { background: var(--paper-2); color: var(--ink-2); }
.btn-sm { padding: 6px 12px; font-size: 13px; border-radius: var(--r-sm); }
.btn-lg { padding: 12px 22px; font-size: 15px; }
.btn-block { width: 100%; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* icon press */
.press { transition: transform var(--dur-fast) var(--ease-press), background var(--dur-fast), color var(--dur-fast); }
.press:active { transform: scale(.9); }

/* ============================ Cards ============================ */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--sh);
}
.card-hover { transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease), border-color var(--dur); }
.card-hover:hover { box-shadow: var(--sh-md); border-color: var(--border-2); }

/* ============================ Pills / chips ============================ */
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: var(--r-pill);
  line-height: 1.4; white-space: nowrap;
}
.dot { width: 6px; height: 6px; border-radius: 99px; flex: none; }

.chip {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  font-size: 13px; font-weight: 600; padding: 7px 14px; border-radius: var(--r-pill);
  border: 1px solid var(--border-2); background: var(--surface); color: var(--ink-2);
  cursor: pointer; transition: all var(--dur-fast) var(--ease); text-decoration: none;
}
.chip:hover { border-color: var(--ink-4); }
.chip.is-active { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ============================ Inputs ============================ */
.field {
  width: 100%; font-family: var(--font-ui); font-size: 14px; color: var(--ink);
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--r);
  padding: 10px 13px; transition: box-shadow var(--dur-fast), border-color var(--dur-fast);
}
.field::placeholder { color: var(--ink-4); }
.field:focus { outline: none; border-color: var(--clay); box-shadow: 0 0 0 3px var(--clay-50); }
.select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23837A69' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center; padding-right: 36px;
}
/* A `.select` inside a `.jk-fields` wrapper: the wrapper's `background` shorthand (specificity
   0,1,1) would otherwise wipe `.select`'s chevron image and override its padding-right. Re-assert
   them at `.jk-fields select.select` (0,2,1). `appearance:none` still comes from `.select`, so the
   native arrow stays hidden. Opt-in — only affects selects that carry the `.select` class. */
.jk-fields select.select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23837A69' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center; padding-right: 34px;
}

/* qty stepper */
.stepper { display: inline-flex; align-items: stretch; border: 1px solid var(--border-2); border-radius: var(--r); overflow: hidden; background: var(--surface); }
.stepper button { width: 32px; display: grid; place-items: center; color: var(--ink-2); background: transparent; border: none; cursor: pointer; }
.stepper button:hover { background: var(--surface-2); }
.stepper input { width: 40px; border: none; border-left: 1px solid var(--border); border-right: 1px solid var(--border); text-align: center; font-weight: 600; font-size: 14px; color: var(--ink); font-family: var(--font-ui); }
.stepper input:focus { outline: none; background: var(--clay-50); }

/* ============================ Tables ============================ */
.th {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-3); text-align: center; padding: 10px 14px; white-space: nowrap;
}
.trow { transition: background var(--dur-fast); }
.trow:hover { background: var(--surface-2); }
/* Row cells: content sits in the center of the cell, both axes (mirrors .th).
   Scoped to .jk-mill (the <body> class from base.html) so standalone print/PDF
   documents keep their own layout. Specificity (0,1,2) intentionally beats
   per-cell text-left/right utilities (0,1,0). */
.jk-mill table td { text-align: center; vertical-align: middle; }
/* ---- Editor tables --------------------------------------------------------
   The rule above is right for read-only lists and wrong for editors. Its
   specificity (0,1,2) deliberately beats per-cell text-left/right utilities
   (0,1,0), so an editor table cannot opt out one cell at a time — every one
   would need an inline style, and the first person to forget gets a centred
   column of product codes.

   Opt the whole table in with .jk-table-editor: text-heavy cells go left, and
   a column marks itself numeric with data-align="right" (or re-centres with
   data-align="center") on BOTH its <th> and its <td>. Specificity here is
   (0,2,2), so it wins without any !important.

       <table class="jk-no-stack jk-table-editor">
         <th class="th">Product</th>            <!-- left, the default -->
         <th class="th" data-align="right">Boxes</th>
         ...
         <td data-align="right">12</td>

   Editor tables also want .jk-no-stack: the ≤900px block below turns tables
   into labelled cards, which is right for a list and wrong for a form. */
.jk-mill table.jk-table-editor td,
.jk-mill table.jk-table-editor th.th { text-align: left; }
.jk-mill table.jk-table-editor td[data-align="right"],
.jk-mill table.jk-table-editor th.th[data-align="right"] { text-align: right; }
.jk-mill table.jk-table-editor td[data-align="center"],
.jk-mill table.jk-table-editor th.th[data-align="center"] { text-align: center; }
/* Block-level flex wrappers directly inside cells ignore the cell's text-align —
   center them too. Deeper flexes need no help: their wrappers are inline-flex
   (centered by text-align) or content-sized flex items. .justify-between is
   spared: it spans the cell on purpose. */
.jk-mill table td > .flex:not(.justify-between) { justify-content: center; }
/* Documents printed from inside the app shell (e.g. the D.D.T. transport
   document) opt out of the centered-cell default: their layout tables read as
   classic left-aligned paper forms. Wrap the document in .jk-doc-flow. */
.jk-mill .jk-doc-flow table td { text-align: left; vertical-align: middle; }

/* ---- Mobile table density (≤640px) ----
   The tables that still scroll horizontally on a phone — the .jk-no-stack editor
   tables — get tighter cell padding + typography here (plus momentum scrolling) so
   more fits at once. Read-only list tables instead collapse into labeled cards (see
   the ≤900px block below), so this density no longer applies to them. .jk-table-wrap
   / .overflow-x-auto are the reusable horizontal-scroll containers. */
.jk-table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.jk-mill .overflow-x-auto { -webkit-overflow-scrolling: touch; }
@media (max-width: 640px) {
  .jk-mill table.jk-no-stack { font-size: 12px; }
  .jk-mill table.jk-no-stack .th { padding: 7px 9px; font-size: 10px; letter-spacing: .04em; }
  .jk-mill table.jk-no-stack td { padding: 7px 9px; }
}

/* ---- Mobile: stack wide tables into labeled cards (≤900px) ----
   Below 900px every read-only list table collapses from a horizontally-scrolling
   grid into a vertical stack of cards: <thead> is hidden and each tbody/tfoot row
   becomes a card whose cells are "LABEL  value" rows (mirroring the .kv-row idiom).
   Column labels come from data-label="…" written by mobile-tables.js, which also
   wraps each labeled cell's content in .jk-cellval so multi-child cells (e.g. name
   + barcode) stay tidy. Rows the labeler skips (colspan totals / empty-state /
   header-less tables) fall back to plain centered blocks. Result: every column is
   readable with no horizontal scroll. Desktop (≥901px) is untouched. Opt out with
   .jk-no-stack — the interactive editor tables and the .jk-doc-flow transport
   document both carry it, so this never touches a print/paper layout. NOTE: do NOT
   add a blanket `.overflow-x-auto { overflow-x: visible }` here — that class is the
   horizontal scroller for every sub-nav and the shop category row. */
.jk-only-stacked { display: none !important; }   /* shown only inside the ≤900px block */
@media (max-width: 900px) {
  .jk-mill table:not(.jk-no-stack) { display: block; width: 100%; min-width: 0 !important; }
  .jk-mill table:not(.jk-no-stack) > thead { display: none; }
  .jk-mill table:not(.jk-no-stack) > tbody,
  .jk-mill table:not(.jk-no-stack) > tfoot { display: block; }

  .jk-mill table:not(.jk-no-stack) > tbody > tr,
  .jk-mill table:not(.jk-no-stack) > tfoot > tr {
    display: block; margin: 0 0 10px; padding: 2px 12px;
    border: 1px solid var(--border); border-radius: var(--r);
    background: var(--surface); box-shadow: var(--sh-sm);
  }
  .jk-mill table:not(.jk-no-stack) > tbody > tr:last-child,
  .jk-mill table:not(.jk-no-stack) > tfoot > tr:last-child { margin-bottom: 0; }

  /* base cell: a plain centered block. Rows the labeler skips (colspan totals,
     empty-state, header-less tables) keep this and read as simple stacked lines. */
  .jk-mill table:not(.jk-no-stack) > tbody > tr > td,
  .jk-mill table:not(.jk-no-stack) > tfoot > tr > td {
    display: block; text-align: center; padding: 9px 0 !important;
    border: 0 !important; border-bottom: 1px solid var(--border) !important;
    white-space: normal; font-size: 13.5px;
  }
  .jk-mill table:not(.jk-no-stack) > tbody > tr > td:last-child,
  .jk-mill table:not(.jk-no-stack) > tfoot > tr > td:last-child { border-bottom: 0 !important; }
  /* drop empty placeholder cells (common in colspan totals rows) */
  .jk-mill table:not(.jk-no-stack) > tbody > tr > td:empty,
  .jk-mill table:not(.jk-no-stack) > tfoot > tr > td:empty { display: none; }

  /* labeled cells become "LABEL : value" rows (label left, value right) */
  .jk-mill table:not(.jk-no-stack) > tbody > tr > td[data-label],
  .jk-mill table:not(.jk-no-stack) > tfoot > tr > td[data-label] {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 16px; text-align: right;
  }
  .jk-mill table:not(.jk-no-stack) > tbody > tr > td[data-label]::before,
  .jk-mill table:not(.jk-no-stack) > tfoot > tr > td[data-label]::before {
    content: attr(data-label); flex: 0 0 auto; text-align: left; padding-top: 1px;
    font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    color: var(--ink-3); white-space: nowrap;
  }
  /* checkbox / icon-only columns have an empty header → no label chip */
  .jk-mill table:not(.jk-no-stack) > tbody > tr > td[data-label=""]::before,
  .jk-mill table:not(.jk-no-stack) > tfoot > tr > td[data-label=""]::before { content: none; }

  /* value wrapper (added by mobile-tables.js): stacks a cell's content right-aligned
     so multi-child cells aren't torn to opposite edges by space-between. min-width:0
     + overflow-wrap let a long unbreakable value (long names, tokens) break instead
     of spilling over the label. */
  .jk-mill table:not(.jk-no-stack) .jk-cellval {
    display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
    min-width: 0; text-align: right;
    white-space: normal; overflow-wrap: anywhere; word-break: break-word;
  }

  .jk-mill .jk-only-stacked { display: inline-flex !important; }   /* mobile-only controls */
}
/* .jk-doc-flow paper documents (transport_document) never stack: their tables carry
   .jk-no-stack in the template, and mobile-tables.js also tags any .jk-doc-flow table
   .jk-no-stack at mobile width — so the cell rules above never touch their inline
   borders/padding. (A CSS revert can't restore inline styles past the !important
   cell rules, hence the class-based opt-out instead.) */

/* ============================ Censor (hide figures) ============================
   Reuses the app's existing mechanic: accounting-censor.js toggles .jk-censored
   on #jkRoot and .revealed on individual .jk-money spans. We restyle the blur. */
/* padding enlarges the hover/click hit area (~matching the 7px blur bleed) so the
   cursor needn't be pixel-perfect on the text; the equal negative margin keeps
   layout unchanged (horizontal cancels exactly; inline boxes ignore the vertical
   margin, but the small vertical padding doesn't grow the line box). */
.jk-censored .jk-money { filter: blur(7px); cursor: pointer; user-select: none; border-radius: 4px; padding: 0.2em 0.5em; margin: -0.2em -0.5em; }
/* Once a figure is revealed (click-to-reveal, or "Show amounts" off), it must be
   selectable so prices/costs can be copied — the blur rule above kills user-select. */
.jk-censored .jk-money.revealed { filter: none; user-select: text; cursor: auto; }
/* Hover-to-peek: on pointer devices, moving the cursor over a fogged figure
   reveals it transiently (no click needed); it re-fogs on mouse-out. Click still
   toggles a persistent reveal. Guarded to (hover: hover) so touch taps keep the
   click-to-toggle behaviour instead of a sticky hover. Loaded after output.css so
   this full reveal wins over that layer's partial-blur hover rule. opacity:1 is
   set explicitly so the reveal is self-contained (not reliant on output.css). */
@media (hover: hover) {
  .jk-censored .jk-money:hover { filter: none; opacity: 1; user-select: text; }
  .censored .money:hover { filter: none; opacity: 1; user-select: text; }
}
/* Keyboard parity: reveal a fogged figure when a link/control inside it gains
   focus, so tab users can read what they're about to activate (hover is mouse-
   only). Not gated on (hover: hover) — keyboard focus applies on every device. */
.jk-censored .jk-money:focus-within { filter: none; opacity: 1; user-select: text; }
.censored .money:focus-within { filter: none; opacity: 1; user-select: text; }
.jk-money { transition: none; }
/* prototype-native aliases (bare .money inside .censored) */
.censored .money { filter: blur(7px); cursor: pointer; user-select: none; border-radius: 4px; padding: 0.2em 0.5em; margin: -0.2em -0.5em; }
.censored .money.revealed { filter: none; user-select: text; cursor: auto; }

/* ============================ Avatars ============================ */
.avatar { border-radius: 999px; display: grid; place-items: center; font-weight: 700; flex: none; letter-spacing: .01em; }

/* ============================ Motion ============================ */
@keyframes jk-view-in { from { transform: translateY(8px); } to { transform: none; } }
.view-in { animation: jk-view-in var(--dur) var(--ease); }
@keyframes jk-reveal-up { from { transform: translateY(12px); } to { transform: none; } }
@keyframes jk-pop { 0% { transform: scale(.4); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }
.pop { animation: jk-pop .42s var(--ease-press) both; }
@keyframes jk-spin { to { transform: rotate(360deg); } }
.spin { animation: jk-spin .7s linear infinite; }
@keyframes jk-toast-in { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: none; } }
.toast-in { animation: jk-toast-in .3s var(--ease) both; }

/* staggered card reveal — visible state is the base style (print/reduced-motion safe) */
.stagger > * { animation: jk-reveal-up .5s var(--ease); }

@media (prefers-reduced-motion: reduce) {
  .jk-mill *, .jk-mill *::before, .jk-mill *::after { animation-duration: .001ms !important; animation-delay: 0ms !important; transition-duration: .001ms !important; }
}

/* ============================ Sticky frosted bars ============================ */
.glass { background: color-mix(in srgb, var(--paper) 78%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }

/* hairline divider */
.hr { height: 1px; background: var(--border); border: 0; }

/* link */
.link { color: var(--clay); font-weight: 600; cursor: pointer; }
.link:hover { color: var(--clay-hover); text-decoration: underline; text-underline-offset: 2px; }
/* A .link button can be disabled (the customer-group picker disables bulk-select
   while availability loads). Without this it keeps full clay colour and the
   pointer cursor, so it reads as live while doing nothing when clicked. */
.link:disabled { opacity: .5; cursor: not-allowed; }
.link:disabled:hover { color: var(--clay); text-decoration: none; }

/* detail page main + side layout */
.detail-2col { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(260px, 1fr); gap: 16px; align-items: start; }
@media (max-width: 880px) { .detail-2col { grid-template-columns: 1fr; } }
.kv-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--border); }
.kv-row:last-child { border-bottom: 0; }
.kv-row .kv-k { font-size: 13px; color: var(--ink-3); }
.kv-row .kv-v { font-size: 13.5px; font-weight: 600; color: var(--ink); text-align: right; }

/* number flair: ledger column */
.ledger { font-variant-numeric: tabular-nums; font-family: var(--font-ui); }

/* ============================ Responsive helpers ============================ */
.desktop-only { display: flex; }
.mobile-only { display: none; }
@media (max-width: 880px) {
  .desktop-only { display: none !important; }
  .mobile-only { display: grid !important; place-items: center; }
}

/* Staff section nav: condense labels → icon-only before the bar overflows. */
.secbtn { padding: 7px 9px; }
.staff-sections .nav-label { font-size: 13px; }
@media (max-width: 1180px) {
  .staff-sections .secbtn { padding: 8px 8px; }
  .staff-sections .nav-label { display: none !important; }
}

/* cart layout: two columns on wide, stacked on narrow */
.cart-grid { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 860px) {
  .cart-grid { grid-template-columns: 1fr 330px; }
  .cart-summary { position: sticky; top: 84px; }
}

/* ============================================================================
   Overlay stacking ladder — the whole app's z-index budget, in one place.
   ============================================================================
     1800  .jk-toast-layer    toasts + fly-to-cart feedback, above everything;
                              transient feedback must stay visible and does not
                              accept pointer events
     1700  .jk-tip            tooltips explain whatever sits under them
     1650  .jk-dialog-layer   global confirm dialog; may open from any overlay
     1600  .drawer            mobile right drawer
     1500  .drawer-scrim
     1400  .jk-modal-layer    modals: above ALL page chrome
     1300  .jk-popover-layer  page-root menus and suggestion popups: above nav,
                              below every modal surface. Deliberately outranks
                              the mobile drawer (1002): its consumers are
                              focus-anchored popups that close on blur, so a
                              drawer can never open under a live popup
     1002  mobile sidebar     (templates/base.html)
     1001  mobile overlay     (templates/base.html)
     1000  nav.navbar-fixed   (static/css/base.css) — .navdrop's 1200 lives
                              INSIDE this stacking context and never competes
                              at the root, so 1000 is the nav's real ceiling
      990  .jk-float          fixed utility controls (scroll-to-top buttons):
                              above the app sub-navs, below the navbar and the
                              mobile drawer — page furniture, not an overlay
      900  app sub-navs       (including inventory/_components/subnav.html)

   Every modal in this app used to be Tailwind's `z-50`, i.e. 950 below the
   navbar it was supposed to cover: the nav painted on top of the modal and
   stayed undimmed. Twelve overlay sites shared that bug, so the fix is one
   class, not twelve numbers.

   Modals sit BELOW the drawer deliberately — the drawer IS the mobile nav
   surface and is mutually exclusive with a modal in practice.

   Do NOT write `z-[1400]` in a template instead of this class. Tailwind only
   emits literals it finds at build time and that one is not in
   static/dist/output.css, so it would resolve to no rule at all until
   `npm run build` runs — silently, with no error and no 404. */
.jk-toast-layer { z-index: 1800; }
.jk-dialog-layer { z-index: 1650; }
.jk-modal-layer { z-index: 1400; }
.jk-popover-layer { z-index: 1300; }
.jk-float { z-index: 990; }

/* mobile drawer */
.drawer-scrim { position: fixed; inset: 0; background: rgba(33,28,22,.45); z-index: 1500; animation: jk-view-in .2s var(--ease) both; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(320px, 86vw); z-index: 1600;
  background: var(--surface); box-shadow: var(--sh-lg); padding: 18px;
  display: flex; flex-direction: column; gap: 4px;
  animation: jk-drawer-in .28s var(--ease) both;
}
@keyframes jk-drawer-in { from { transform: translateX(100%); } to { transform: none; } }
.drawer-item {
  display: flex; align-items: center; gap: 12px; padding: 13px 14px; border-radius: var(--r);
  font-size: 15px; font-weight: 600; color: var(--ink-2); background: transparent; border: none;
  cursor: pointer; text-align: left; width: 100%; transition: background var(--dur-fast); text-decoration: none;
}
.drawer-item:hover { background: var(--paper-2); }
.drawer-item.is-active { background: var(--clay-50); color: var(--clay-700); }

/* nav dropdown */
.navdrop {
  position: absolute; top: calc(100% + 6px); left: 0; min-width: 200px; z-index: 1200;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--sh-lg); padding: 6px; animation: jk-view-in .16s var(--ease) both;
}
.navdrop a, .navdrop button {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 11px; border-radius: var(--r-sm); border: none; background: transparent;
  font-size: 13.5px; font-weight: 500; color: var(--ink-2); cursor: pointer; white-space: nowrap; text-decoration: none;
}
.navdrop a:hover, .navdrop button:hover { background: var(--paper-2); color: var(--ink); }
.navdrop a.is-active, .navdrop button.is-active { color: var(--clay); font-weight: 600; }

/* lang switch */
.lang-switch { display: inline-flex; border: 1px solid var(--border-2); border-radius: var(--r-sm); overflow: hidden; }
.lang-switch button { padding: 5px 10px; font-size: 12px; font-weight: 600; border: none; cursor: pointer; background: var(--surface); color: var(--ink-3); }
.lang-switch button.is-active { background: var(--ink); color: var(--paper); }

/* ============================ Login (centered card) ============================ */
.login-page { min-height: 100vh; display: grid; place-items: center; padding: 40px 20px; background: var(--paper); }
.login-card { width: 100%; max-width: 392px; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; box-shadow: 0 18px 40px -24px rgba(50,40,24,.45); overflow: hidden; }
/* 6px three-band knit strip — the lone brand signal in the minimal card */
.login-strip { display: flex; height: 6px; }
.login-strip > span { flex: 1; }
.login-body { padding: 32px 32px 28px; }
/* password field + show/hide toggle */
.login-pw { position: relative; }
.login-pw input { padding-right: 48px; min-height: 46px; }
.login-toggle { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); width: 44px; height: 44px; min-width: 44px; display: grid; place-items: center; border: none; background: transparent; color: var(--ink-3); cursor: pointer; border-radius: var(--r-sm); }
.login-toggle:hover { color: var(--ink-2); background: var(--paper-2); }
.login-toggle:focus-visible { outline: 2px solid var(--clay); outline-offset: 1px; }
/* caps-lock note — muted, not an error (caps lock isn't a failure) */
.login-caps { margin-top: 6px; font-size: 12px; color: var(--ink-3); align-items: center; gap: 6px; display: none; }
.login-caps.is-on { display: flex; }
/* submit spinner — swaps in for the arrow while the form posts */
.login-spin { display: none; width: 15px; height: 15px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: login-spin .6s linear infinite; }
.btn.is-loading .login-spin { display: inline-block; }
.btn.is-loading .login-arrow { display: none; }
@keyframes login-spin { to { transform: rotate(360deg); } }

/* ============================ Forced mobile (device=phone preview) ============================ */
body.force-mobile { max-width: 402px; margin: 0 auto; min-height: 100vh; box-shadow: 0 0 0 1px var(--border), 0 30px 60px -20px rgba(50,40,24,.4); background: var(--paper); overflow-x: hidden; }
html:has(body.force-mobile) { background: #E4DDCD; }
body.force-mobile .desktop-only { display: none !important; }
body.force-mobile .mobile-only { display: grid !important; place-items: center; }
body.force-mobile .cart-grid { grid-template-columns: minmax(0, 1fr) !important; }
body.force-mobile .cart-summary { position: static !important; }

/* ============================================================================
   Generated knit-swatch texture (from assets/swatch.css)
   A muted heathered yarn color overlaid with two crossing rib lines + a darker
   ribbed hem band. Fills shop product tiles with no photo. Set base tone via the
   inline `--swatch` var:  <div class="jk-swatch" style="--swatch:#a8593f"></div>
   ============================================================================ */
.jk-swatch {
  position: relative;
  background-color: var(--swatch, #6b7f99);
  background-image:
    radial-gradient(120% 75% at 50% -8%, rgba(255, 255, 255, .42), transparent 58%),
    radial-gradient(110% 70% at 50% 120%, rgba(0, 0, 0, .18), transparent 55%),
    repeating-linear-gradient(63deg, rgba(255, 255, 255, .13) 0 2px, transparent 2px 9px),
    repeating-linear-gradient(-63deg, rgba(255, 255, 255, .13) 0 2px, transparent 2px 9px),
    repeating-linear-gradient(0deg, rgba(0, 0, 0, .05) 0 1px, transparent 1px 18px);
  background-size: 100% 100%, 100% 100%, 18px 18px, 18px 18px, 100% 18px;
  overflow: hidden;
}
.jk-swatch::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 26px;
  background-image: repeating-linear-gradient(90deg, rgba(0, 0, 0, .10) 0 2px, rgba(255, 255, 255, .06) 2px 6px);
  opacity: .6;
}
.jk-swatch--thumb::after { height: 12px; }

/* ============================================================================
   Global chrome — top nav + mobile sidebar (base.html)
   Restyles the existing nav structure (dropdown hooks for dropdowns.js are
   preserved in the markup; these classes only re-skin it to Mill).
   ============================================================================ */
.jk-topnav { background: var(--surface); border-bottom: 1px solid var(--border); box-shadow: var(--sh-sm); }
/* The nav holds a lot (8 top-level links for admins + the user cluster). Stop the
   two groups (links / user cluster) from shrinking into each other: with both
   non-shrinking the links can never spill over the user controls — at worst the
   row overflows the (already minimal) gutters instead of overlapping. */
.jk-topnav > div > .flex.justify-between > div { flex-shrink: 0; }
/* Top-level links are text-only so all eight app labels fit without overlapping;
   the per-section icons still appear in the mobile sidebar. Dropdown chevrons are
   kept (.jk-chev) so the menus still read as expandable. */
.jk-navrail .jk-navlink > .jk-ic { display: none; }
/* ...except a navlink that IS its icon (Quick Pages has no text label): the rule
   above would blank it entirely, leaving an empty 16px clickable box (the 8px
   padding either side, with zero content between). Pad it out to a 38x38 square
   to match .jk-star, the other icon-only control in this feature — the rail is
   `hidden md:flex`, so it renders at exactly 768px, a touch width, with only a
   3px gap to the next link. Mobile is unaffected: the sidebar uses .jk-mlink
   with a full "Manage Quick Pages" label.
   Both classes are named in the selector on purpose: .jk-navlink-icon alone ties
   with the rule above at (0,3,0) and would win only by sitting later in the file,
   so reordering these lines would silently reinstate the blank box. */
.jk-navrail .jk-navlink.jk-navlink-icon > .jk-ic { display: block; }
.jk-navrail .jk-navlink.jk-navlink-icon { padding: 0 11px; }
/* .jk-navlink.is-active differs from :hover only by font-weight, which is inert
   on an SVG — so this link, the one that IS active on the Manage page, would show
   no "you are here" at all. Carry it on colour instead, reusing the clay accent
   .jk-star.is-on already uses for the on-state of the same feature. */
.jk-navrail .jk-navlink.jk-navlink-icon.is-active { color: var(--clay); }
.jk-navrail { gap: 3px; }
.jk-wordmark { display: inline-flex; align-items: baseline; gap: 1px; text-decoration: none; line-height: 1; }
.jk-wordmark .w1 { font-family: var(--font-display); font-size: 22px; font-weight: 700; color: var(--ink); letter-spacing: -.02em; }
.jk-wordmark .w2 { font-family: var(--font-display); font-size: 22px; font-weight: 700; color: var(--clay); letter-spacing: -.02em; }

.jk-navlink {
  display: inline-flex; align-items: center; gap: 5px; height: 38px; padding: 0 8px;
  border-radius: var(--r); font-size: 13.5px; font-weight: 500; color: var(--ink-3);
  text-decoration: none; white-space: nowrap; flex-shrink: 0;
  transition: background var(--dur-fast), color var(--dur-fast);
}
/* The top nav holds many items; keep its chevron tight so labels have room. */
.jk-navlink .jk-chev { margin-left: -1px; }
.jk-navlink:hover { background: var(--paper-2); color: var(--ink); }
.jk-navlink.is-active { background: var(--paper-2); color: var(--ink); font-weight: 700; }

/* dropdown menu card (keeps .{section}-dropdown-menu + hidden for the JS) */
.jk-menu { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); box-shadow: var(--sh-lg); padding: 6px; }
.jk-menu .jk-menu-label { padding: 8px 11px 4px; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-4); }
.jk-menu a { display: flex; align-items: center; gap: 9px; padding: 8px 11px; border-radius: var(--r-sm); font-size: 13.5px; font-weight: 500; color: var(--ink-2); text-decoration: none; white-space: nowrap; }
.jk-menu a:hover { background: var(--paper-2); color: var(--clay); }
.jk-menu .jk-menu-sep { height: 1px; background: var(--border); margin: 5px 0; }

/* Quick Pages star toggle (sits next to page titles on list/index pages) */
.jk-star-form { margin: 0; display: inline-flex; line-height: 0; }
.jk-star { display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; padding: 0; border: none; background: transparent; color: var(--ink-4); cursor: pointer; border-radius: var(--r-sm); transition: color .12s ease, background .12s ease; }
.jk-star:hover { color: var(--clay); background: var(--clay-50); }
.jk-star svg { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
.jk-star.is-on { color: var(--clay); }
.jk-star.is-on svg { fill: currentColor; stroke: currentColor; }
/* Quick Pages links inside the app-dropdown menus: user labels are free text, so
   ellipsis rather than let them blow out the fixed-width (w-64) menu. Overrides
   .jk-menu a's flex/nowrap (later + equal specificity). The current page's pin
   gets the clay accent so you can see where you are. */
.jk-menu a.jk-menu-pin { display: block; overflow: hidden; text-overflow: ellipsis; }
.jk-menu a.jk-menu-pin.is-active { color: var(--clay); font-weight: 700; }

/* nav badge (cart count / unread) */
.jk-navbadge { background: var(--brick); color: #fff; font-size: 10px; font-weight: 700; min-width: 17px; height: 17px; border-radius: 99px; display: inline-grid; place-items: center; padding: 0 4px; line-height: 1; }
/* jk-mill.css loads after Tailwind, so this component class would out-order .hidden;
   re-assert hiding so the count badges collapse to nothing at zero. */
.jk-navbadge.hidden { display: none; }

/* icon button (mobile hamburger / anon controls) */
.jk-iconbtn { width: 40px; height: 40px; border-radius: var(--r); border: none; background: var(--paper-2); color: var(--ink); cursor: pointer; display: grid; place-items: center; }
.jk-iconbtn:hover { background: var(--border); }

/* mobile sidebar (Mill) */
.jk-msidebar { background: var(--surface); }
.jk-mlink { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 11px 12px; border-radius: var(--r); font-size: 15px; font-weight: 600; color: var(--ink-2); text-decoration: none; }
.jk-mlink:hover { background: var(--paper-2); }
.jk-mlink.is-active { background: var(--clay-50); color: var(--clay-700); }
.jk-msub { display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-radius: var(--r-sm); font-size: 13.5px; color: var(--ink-3); text-decoration: none; }
.jk-msub:hover { background: var(--paper-2); color: var(--ink); }

/* form fields — restyle native/Django-widget inputs inside a .jk-fields wrapper
   (reused by auth + every create/edit form so we never touch the Python forms) */
.jk-fields input[type=text], .jk-fields input[type=email], .jk-fields input[type=password],
.jk-fields input[type=number], .jk-fields input[type=tel], .jk-fields input[type=date],
.jk-fields input[type=search], .jk-fields input[type=url], .jk-fields textarea, .jk-fields select {
  width: 100%; font-family: var(--font-ui); font-size: 14px; color: var(--ink);
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--r);
  padding: 10px 13px; transition: box-shadow var(--dur-fast), border-color var(--dur-fast);
  box-shadow: none;
}
.jk-fields input::placeholder, .jk-fields textarea::placeholder { color: var(--ink-4); }
.jk-fields input:focus, .jk-fields textarea:focus, .jk-fields select:focus {
  outline: none; border-color: var(--clay); box-shadow: 0 0 0 3px var(--clay-50);
}
.jk-fields input[type=checkbox], .jk-fields input[type=radio] { accent-color: var(--clay); width: 15px; height: 15px; }
.jk-field-label { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); margin-bottom: 6px; }
.jk-field-err { margin-top: 6px; font-size: 12.5px; color: var(--brick-700); }

/* App-wide de-blue: color-only field treatment for inputs that are NOT inside a
   .jk-fields wrapper (forms across accounting/sales/inventory). Element-qualified
   (specificity 0,1,1) so it deterministically beats `border-gray-300` and the
   colorless `focus:ring-2` default-blue ring regardless of stylesheet load order.
   Sets ONLY border-color + the terracotta focus ring — never width/padding/
   background — so existing Tailwind sizing utils (w-24, px-2, bg-gray-100,
   text-center) are preserved. Apply by adding `jk-input` to a blue input and
   stripping its border-gray-300 / focus:ring-* / *-blue-* utils. */
input.jk-input, textarea.jk-input, select.jk-input { border-color: var(--border-2); }
input.jk-input:focus, textarea.jk-input:focus, select.jk-input:focus {
  outline: none; border-color: var(--clay); box-shadow: 0 0 0 3px var(--clay-50);
}
/* checkboxes/radios carry a blue check via text-blue-600; clay accent replaces it */
input.jk-input[type=checkbox], input.jk-input[type=radio] { accent-color: var(--clay); }

/* Typeahead input holding a typed name with NO dropdown selection behind it.
   The hidden .supplier-client-id is wiped on every keystroke, so a name sitting
   in the box with no id is a row the server refuses (or, clear-only, silently
   ignores). Stated for :focus too so it beats input.jk-input:focus on
   SPECIFICITY (0,3,1 vs 0,2,1) rather than on source order — reordering two
   lines must not silently un-paint it. */
input.jk-input.is-unlinked {
  background: var(--brick-50);
  border-color: var(--brick);
}
input.jk-input.is-unlinked:focus {
  border-color: var(--brick);
  box-shadow: 0 0 0 3px var(--brick-100);
}

/* period filter chips (dashboards: Today / This Week / This Month / Custom) */
.jk-period { padding: 8px 16px; font-size: 14px; font-weight: 600; border-radius: var(--r); background: var(--paper-2); color: var(--ink-2); text-decoration: none; border: none; cursor: pointer; transition: background var(--dur-fast), color var(--dur-fast); white-space: nowrap; }
.jk-period:hover { background: var(--border); }
.jk-period.is-active { background: var(--ink); color: var(--paper); }

/* autocomplete suggestion dropdown items (sales/inventory search boxes) */
.jk-sugg { padding: 8px 14px; cursor: pointer; }
.jk-sugg:hover, .jk-sugg.is-active { background: var(--paper-2); }

/* sticky section sub-nav tabs (staff sections) */
.jk-subtab { color: var(--ink-3); border-bottom: 2px solid transparent; font-weight: 500; text-decoration: none; transition: color var(--dur-fast), border-color var(--dur-fast); }
.jk-subtab:hover { color: var(--ink); }
.jk-subtab.is-active { color: var(--clay); border-bottom-color: var(--clay); font-weight: 700; }

/* flash messages (Mill tones) */
.jk-flash { border-radius: var(--r); padding: 13px 16px; margin-bottom: 12px; font-size: 13.5px; display: flex; gap: 10px; align-items: flex-start; border: 1px solid var(--border); background: var(--surface); color: var(--ink-2); }
/* jk-mill.css loads after Tailwind, so .jk-flash's display:flex would out-order
   .hidden; re-assert so a JS-toggled flash (e.g. #uploadError) can actually hide. */
.jk-flash.hidden { display: none; }
.jk-flash.is-error { background: var(--brick-50); border-color: var(--brick-100); color: var(--brick-700); }
.jk-flash.is-success { background: var(--pine-50); border-color: var(--pine-100); color: var(--pine-700); }
.jk-flash.is-warning { background: var(--ochre-50); border-color: var(--ochre-100); color: var(--ochre-700); }
.jk-flash.is-info { background: var(--slate-50); border-color: var(--slate-100); color: #2c4259; }

/* App-wide de-blue (Tier 3) ----------------------------------------------------
   jk-mill.css loads after Tailwind output.css, so these win on source order.
   (1) Warm the neutral Tailwind border utility to the Mill hairline in ONE rule
       (replaces ~60 cool-gray card/container borders without per-site edits).
   (2) .jk-info — a Mill "info" surface (slate), mirroring .jk-flash.is-info, for
       the blue info callouts. Children inherit the slate text/icon color. */
.border-gray-300 { border-color: var(--border); }
.jk-info { background: var(--slate-50); border: 1px solid var(--slate-100); color: #34465c; }
.jk-info h1, .jk-info h2, .jk-info h3, .jk-info h4, .jk-info strong { color: #2c3e54; }
/* Balance-sheet status bar — JS toggles these instead of Tailwind bg-blue/red/gray.
   Balanced was BLUE (off-brand); de-blued to pine (positive). */
.jk-status { border-top-width: 4px; border-top-style: solid; }
.jk-status-neutral { background: var(--paper-2); border-top-color: var(--border-2); }
.jk-status-balanced { background: var(--pine-50); border-top-color: var(--pine); }
.jk-status-unbalanced { background: var(--brick-50); border-top-color: var(--brick); }
/* purple -> Mill plum (the account / admin accent), incl. JS-built dropdown rows */
.jk-plum { color: var(--plum); }
.jk-plum-surface { background: var(--plum-50); }

/* ============================================================================
   Compatibility shim — re-color legacy Tailwind utilities to Mill on pages
   still authored in the old blue palette (applied via .jk-shim on the section
   #jkRoot). Hand-converted pages use inline Mill styles + .card/.btn and don't
   carry these color utilities, so the shim is a no-op there. Lets large form
   pages adopt the warm look without a full markup rewrite.
   ============================================================================ */
.jk-shim h1, .jk-shim h2, .jk-shim h3 { font-family: var(--font-display); }
/* surfaces / text ramp */
.jk-shim .bg-white { background-color: var(--surface) !important; }
.jk-shim .bg-gray-50 { background-color: var(--surface-2) !important; }
.jk-shim .bg-gray-100 { background-color: var(--paper-2) !important; }
.jk-shim .bg-gray-200 { background-color: var(--border) !important; }
.jk-shim .text-gray-900, .jk-shim .text-gray-800 { color: var(--ink) !important; }
.jk-shim .text-gray-700, .jk-shim .text-gray-600 { color: var(--ink-2) !important; }
.jk-shim .text-gray-500 { color: var(--ink-3) !important; }
.jk-shim .text-gray-400, .jk-shim .text-gray-300 { color: var(--ink-4) !important; }
.jk-shim .border-gray-100, .jk-shim .border-gray-200 { border-color: var(--border) !important; }
.jk-shim .border-gray-300 { border-color: var(--border-2) !important; }
.jk-shim .divide-gray-200 > :not([hidden]) ~ :not([hidden]) { border-color: var(--border) !important; }
.jk-shim .hover\:bg-gray-50:hover { background-color: var(--surface-2) !important; }
.jk-shim .hover\:bg-gray-100:hover { background-color: var(--paper-2) !important; }
.jk-shim .hover\:bg-gray-200:hover { background-color: var(--border) !important; }
/* brand blue → terracotta */
.jk-shim .bg-blue-600 { background-color: var(--clay) !important; }
.jk-shim .bg-blue-700, .jk-shim .hover\:bg-blue-700:hover { background-color: var(--clay-hover) !important; }
.jk-shim .bg-blue-50 { background-color: var(--clay-50) !important; }
.jk-shim .hover\:bg-blue-50:hover { background-color: var(--clay-50) !important; }
.jk-shim .bg-blue-100 { background-color: var(--clay-100) !important; }
.jk-shim .text-blue-600, .jk-shim .hover\:text-blue-600:hover { color: var(--clay) !important; }
.jk-shim .text-blue-700, .jk-shim .text-blue-800, .jk-shim .hover\:text-blue-700:hover, .jk-shim .hover\:text-blue-800:hover { color: var(--clay-700) !important; }
.jk-shim .border-blue-200 { border-color: var(--clay-100) !important; }
.jk-shim .border-blue-500, .jk-shim .border-blue-600 { border-color: var(--clay) !important; }
/* semantics */
.jk-shim .text-green-600, .jk-shim .text-green-700, .jk-shim .text-emerald-600 { color: var(--pine) !important; }
.jk-shim .bg-green-600, .jk-shim .hover\:bg-green-700:hover { background-color: var(--pine) !important; }
.jk-shim .bg-green-50 { background-color: var(--pine-50) !important; }
.jk-shim .bg-green-100 { background-color: var(--pine-50) !important; }
.jk-shim .text-green-800 { color: var(--pine-700) !important; }
.jk-shim .border-green-200 { border-color: var(--pine-100) !important; }
.jk-shim .text-red-600, .jk-shim .text-red-700, .jk-shim .hover\:text-red-700:hover { color: var(--brick) !important; }
.jk-shim .bg-red-600, .jk-shim .hover\:bg-red-700:hover { background-color: var(--brick) !important; }
.jk-shim .bg-red-50, .jk-shim .hover\:bg-red-50:hover { background-color: var(--brick-50) !important; }
.jk-shim .bg-red-100 { background-color: var(--brick-50) !important; }
.jk-shim .text-red-800 { color: var(--brick-700) !important; }
.jk-shim .border-red-200, .jk-shim .border-red-300 { border-color: var(--brick-100) !important; }
.jk-shim .text-amber-600, .jk-shim .text-amber-700, .jk-shim .text-amber-800, .jk-shim .text-amber-900, .jk-shim .text-yellow-600, .jk-shim .text-yellow-800 { color: var(--ochre-700) !important; }
.jk-shim .bg-amber-50, .jk-shim .bg-yellow-50 { background-color: var(--ochre-50) !important; }
.jk-shim .bg-amber-100, .jk-shim .bg-yellow-100 { background-color: var(--ochre-50) !important; }
.jk-shim .border-amber-200, .jk-shim .border-yellow-200 { border-color: var(--ochre-100) !important; }
.jk-shim .bg-purple-100, .jk-shim .bg-indigo-100 { background-color: var(--plum-50) !important; }
.jk-shim .text-purple-800, .jk-shim .text-indigo-800, .jk-shim .text-purple-600, .jk-shim .text-indigo-600 { color: var(--plum-700) !important; }
.jk-shim .bg-purple-50, .jk-shim .bg-indigo-50 { background-color: var(--plum-50) !important; }
.jk-shim .bg-teal-50 { background-color: var(--pine-50) !important; }
.jk-shim .text-teal-600 { color: var(--pine) !important; }
.jk-shim .bg-orange-50 { background-color: var(--clay-50) !important; }
.jk-shim .text-orange-600, .jk-shim .text-orange-700 { color: var(--clay-700) !important; }
.jk-shim .border-orange-300 { border-color: var(--clay-100) !important; }
/* native inputs → Mill fields */
.jk-shim input[type=text], .jk-shim input[type=email], .jk-shim input[type=password], .jk-shim input[type=number], .jk-shim input[type=date], .jk-shim input[type=month], .jk-shim input[type=search], .jk-shim input[type=tel], .jk-shim input[type=url], .jk-shim select, .jk-shim textarea {
  background-color: var(--surface) !important; border-color: var(--border-2) !important; color: var(--ink) !important;
}
.jk-shim input:focus, .jk-shim select:focus, .jk-shim textarea:focus { border-color: var(--clay) !important; box-shadow: 0 0 0 3px var(--clay-50) !important; outline: none !important; }
.jk-shim input[type=checkbox], .jk-shim input[type=radio] { accent-color: var(--clay); }
/* focus rings that used blue */
.jk-shim .focus\:ring-blue-500:focus, .jk-shim .focus\:ring-blue-600:focus { --tw-ring-color: var(--clay) !important; }
.jk-shim .focus\:border-blue-500:focus { border-color: var(--clay) !important; }

/* ---- Sale form: out-of-stock line ----
   Toggled by checkInventoryAvailability() on the sale create/edit pages: the
   Boxes quantity input of an out-of-stock line goes bold red alongside the red
   product field, so the quantity that will turn into a Sale ReOrder is
   unmissable. Declared after the .jk-shim input overrides on purpose so the
   red border wins over the shim's neutral field styling. */
input.oos-boxes, .jk-shim input.oos-boxes {
  border-color: var(--brick) !important;
  border-width: 2px !important;
  color: var(--brick-700) !important;
  font-weight: 700;
}
input.oos-boxes:focus, .jk-shim input.oos-boxes:focus {
  border-color: var(--brick) !important;
  box-shadow: 0 0 0 3px var(--brick-50) !important;
}
/* Companion to .oos-boxes: the legacy inline red background on the product
   search field is defeated by the .jk-shim "input background !important"
   override above, so the same JS toggles this class to keep the product field
   visibly red as well (the inline style remains the JS-readable state). */
input.oos-product, .jk-shim input.oos-product {
  background-color: rgb(222, 171, 175) !important;
}

/* ============================================================================
   Permission tooltips (group create/edit form)
   ============================================================================
   Positioned fixed by static/js/jk-permission-tips.js. Fixed is mandatory:
   the permission card and every app section carry overflow:hidden, which
   would clip a CSS-only ::after tooltip inside its own box. */
.jk-tip {
  position: fixed;
  z-index: 1700;                  /* top of the overlay ladder above.
                                     Was 50 ("above the sticky save bar at
                                     z-10"), which put every tooltip BEHIND
                                     any .jk-modal-layer modal — this node is
                                     appended to <body>, so it competes at the
                                     root, not inside the modal it explains. */
  max-width: 320px;
  padding: 8px 11px;
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
  box-shadow: 0 6px 20px -6px rgba(60, 48, 30, .28);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-2);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity .12s ease;
}
.jk-tip.is-open { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .jk-tip { transition: none; } }

/* Sale-list variant: compact product names and quantities share the existing
   fixed, non-interactive tooltip surface. */
.jk-tip--table {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 12px;
  row-gap: 2px;
  max-width: 340px;
  max-height: 360px;
  overflow: hidden;
}
/* --ink-4 ("faint meta") is 2.54:1 on --surface, under the 4.5:1 AA floor. These
   three are not meta: the head states the true product count, the foot is the
   only disclosure that rows were truncated, and the empty state is the whole
   content an OLD- sale has. They carry --ink-3 (4.06:1) like the muted labels
   elsewhere on this page, and the empty state carries body ink outright. */
.jk-tip-head,
.jk-tip-foot,
.jk-tip-empty { grid-column: 1 / -1; color: var(--ink-3); }
.jk-tip-head {
  margin-bottom: 3px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  font-size: 11.5px;
}
.jk-tip-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-2);
}
.jk-tip-qty {
  text-align: right;
  white-space: nowrap;
  color: var(--ink-3);
}
.jk-tip-foot {
  margin-top: 3px;
  padding-top: 4px;
  border-top: 1px solid var(--border);
}
.jk-tip-empty { color: var(--ink-2); font-style: italic; }

/* Sale-form photo preview (static/js/jk-image-tip.js): a 240px thumbnail plus
   a one-line product caption, on the same fixed non-interactive surface.
   The image box is sized here rather than left to the file, so the panel is
   already its final height when the module measures it for edge clamping --
   an unsized <img> measures 0 until the bytes land, and the card would be
   positioned for a box it is about to outgrow.
   No max-width of its own: 240px + the base's padding and border is 264px,
   inside `.jk-tip`'s 320px, so the panel shrink-to-fits there already. */
.jk-tip--img .jk-tip-img {
  display: block;
  width: 240px;
  height: 180px;
  object-fit: contain;
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.jk-tip--img .jk-tip-cap {
  margin-top: 6px;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-2);
}

/* ---- Quick Pages Manage: drag-reorder rows + click-to-edit rename ---------
   The row is the same <form> the page always used; at rest it shows a grip, the
   pin's name, and Remove. The label input and its path only appear while you are
   actually renaming, which is what let the URL line come off every row without
   making two same-named pins (a list and a filtered view of it share a default
   label) impossible to tell apart. */
.jk-qp-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: nowrap;
  padding: 10px 12px 10px 6px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.jk-qp-group .jk-qp-row:last-child { border-bottom: none; }
.jk-qp-main { flex: 1; min-width: 0; }

/* The grip. touch-action:none is load-bearing, not polish: without it the browser
   claims a vertical drag for page scrolling and the whole feature is dead on a
   phone, even though every pointer event still fires. This page is reachable at
   768px, which is a touch width. */
.jk-qp-grip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 34px; flex-shrink: 0;
  padding: 0; border: none; background: transparent;
  color: var(--ink-4); cursor: grab; border-radius: var(--r-sm);
  touch-action: none;
  transition: color .12s ease, background .12s ease;
}
.jk-qp-grip:hover { color: var(--clay); background: var(--clay-50); }
.jk-qp-grip:active { cursor: grabbing; }
.jk-qp-grip svg { fill: currentColor; }
/* Focus ring: jk-mill scopes the global ring to .focusable/input/button, and this
   IS a button, but the grip is the one control here where position is the only
   cue — make it unmistakable. */
.jk-qp-grip:focus-visible { outline: 2px solid var(--clay); outline-offset: 1px; }

/* The name at rest: reads as text, behaves as a button. */
.jk-qp-name {
  display: block; width: 100%; text-align: left;
  padding: 4px 6px; margin: -4px -6px;
  border: none; background: transparent;
  font-size: 14px; font-weight: 500; color: var(--ink);
  cursor: text; border-radius: var(--r-sm);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.jk-qp-name:hover { background: var(--paper-2); }

.jk-qp-path { margin-top: 4px; font-size: 12px; color: var(--ink-4); }
.jk-qp-hint { margin-top: 2px; font-size: 11.5px; color: var(--ink-4); }

/* Dragging. The row is lifted with a transform only — it is never reparented
   mid-drag, so the pointer capture stays attached to a node that never left the
   tree; the single DOM move happens once, on drop. */
.jk-qp-row.is-dragging {
  position: relative; z-index: 5;
  box-shadow: var(--sh-lg);
  border-radius: var(--r-sm);
  opacity: .95;
}
.jk-qp-group.jk-qp-dragging { user-select: none; }
.jk-qp-group.jk-qp-dragging .jk-qp-row:not(.is-dragging) {
  transition: transform var(--dur-fast, .12s) ease;
}
