/* ============================================================================
   Vanmech Workflow — design tokens
   ----------------------------------------------------------------------------
   Dark, low-glare theme. Staff sit on these screens for hours, so:
     - no pure black and no pure white (both raise perceived glare)
     - text tops out at ~87% white on a mid-dark ground
     - the brand lime is an ACCENT, never a fill: focus, totals, active state
     - 15px base and a taller line-height; money in tabular figures

   Brand greens are sampled from the letterhead in Intra_Quote.xlsx:
     logo body #5cc40b   rules #9fff3b   mid #70c62a

   Rules: no bare px, no bare colours in component styles. New value -> new token.
   ========================================================================== */
:root{
  /* One scale, tuned here and nowhere else. Sized for operators who spend the
     working day on these screens rather than for maximum rows per screen. */
  --sp-1:3px;  --sp-2:6px;  --sp-3:9px;  --sp-4:13px;
  --sp-5:18px; --sp-6:24px; --sp-7:32px; --sp-8:44px; --sp-9:64px;

  --fs-xs:13px; --fs-sm:14px; --fs-md:15px; --fs-base:17px;
  --fs-lg:21px; --fs-xl:30px; --fs-2xl:40px;
  --lh:1.55;
  --font:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;

  /* dark ramp — surfaces */
  --c-bg:#141719;        /* app ground        */
  --c-panel:#1c2023;     /* cards, tables     */
  --c-raise:#23282c;     /* header rows, chips*/
  --c-hover:#282e33;
  --c-line:#31383e;      /* borders           */
  --c-line-soft:#262c31;

  /* light ramp — ink */
  --c-ink:#dee3e7;       /* primary text  ~87% */
  --c-ink-2:#aeb7be;     /* secondary          */
  --c-ink-3:#7d868e;     /* tertiary / labels  */
  --c-ink-4:#59626a;     /* disabled, grips    */

  /* brand */
  --c-lime:#9fff3b;
  --c-lime-2:#7ee02a;
  --c-lime-3:#5cc40b;
  --c-lime-dim:rgba(159,255,59,.14);
  --c-lime-edge:rgba(159,255,59,.42);

  --c-warn:#ff8a5c; --c-warn-bg:rgba(255,138,92,.13);

  /* Late is its own signal, not the warning colour: a job three days from due
     and a job 121 days past it must not read as the same thing at a glance. */
  --c-late:#ff6b6b; --c-late-bg:rgba(255,107,107,.15); --c-late-edge:rgba(255,107,107,.4);
  --c-ok:var(--c-lime-2);

  --max-w:1680px;
  --thumb:52px;        /* the file preview, and the glyph box beside it */
  --bar-h:70px;
  --jump-w:520px;
  --radius:14px; --radius-md:10px; --radius-sm:7px; --radius-pill:99px;
  --shadow:0 6px 24px rgba(0,0,0,.34);
  --ring:0 0 0 3px var(--c-lime-dim);
  --t:.14s;
}

/* ---- light and dark ------------------------------------------------------
   Q21, answered 15 Sep 2026: both, saved per person, with dark grey where the
   dark theme uses lime ("for now — we'll get feedback during trial").

   Each colour is defined ONCE, below, as `light-dark(LIGHT, DARK)`, and
   `color-scheme` chooses which half applies — so a light palette and a dark
   palette cannot drift apart, which is how `.btn` went wrong. It also flips
   the browser's own controls: date pickers, selects, scrollbars.

   The plain `:root` tokens above are the dark palette and stay as the
   fallback. A browser without `light-dark()` (pre-2024) ignores this whole
   block and keeps the dark theme it has always had, rather than rendering
   invalid colours. `core/test_stylesheet.py` requires every dark half here to
   equal its plain token, so the fallback cannot go stale.

   Light greys were chosen by measured contrast: body text ~16:1 on a panel,
   labels 6:1, the primary button label 11.8:1, warning and late text >5:1. */
@supports (color: light-dark(#000, #fff)) {
  :root{
    color-scheme:light dark;

    --c-bg:light-dark(#eef0f2, #141719);
    --c-panel:light-dark(#fbfbfc, #1c2023);
    --c-raise:light-dark(#f1f3f5, #23282c);
    --c-hover:light-dark(#e7eaed, #282e33);
    --c-line:light-dark(#cfd5da, #31383e);
    --c-line-soft:light-dark(#e1e5e8, #262c31);

    --c-ink:light-dark(#1a1e21, #dee3e7);
    --c-ink-2:light-dark(#3a4248, #aeb7be);
    --c-ink-3:light-dark(#59626a, #7d868e);
    --c-ink-4:light-dark(#6b747c, #59626a);

    /* The brand accents. Dark grey in the light theme, lime in the dark. */
    --c-lime:light-dark(#2a2f33, #9fff3b);
    --c-lime-2:light-dark(#3b4248, #7ee02a);
    --c-lime-3:light-dark(#2a2f33, #5cc40b);
    --c-lime-dim:light-dark(rgba(42,47,51,.08), rgba(159,255,59,.14));
    --c-lime-edge:light-dark(rgba(42,47,51,.38), rgba(159,255,59,.42));

    /* Warning and late must darken on a light ground: the dark theme's
       #ff8a5c on near-white is 2.4:1 and unreadable as text. */
    --c-warn:light-dark(#a8420a, #ff8a5c);
    --c-warn-bg:light-dark(rgba(168,66,10,.10), rgba(255,138,92,.13));
    --c-late:light-dark(#bb222a, #ff6b6b);
    --c-late-bg:light-dark(rgba(187,34,42,.10), rgba(255,107,107,.15));
    --c-late-edge:light-dark(rgba(187,34,42,.38), rgba(255,107,107,.4));

    --shadow:0 6px 24px light-dark(rgba(16,24,32,.10), rgba(0,0,0,.34));
  }
  /* A person's saved choice overrides their operating system. */
  :root[data-theme="light"]{color-scheme:light}
  :root[data-theme="dark"]{color-scheme:dark}
}

*{box-sizing:border-box}
/* The fallback for browsers without light-dark(): the dark theme, as before. */
html{color-scheme:dark}
body{margin:0;background:var(--c-bg);color:var(--c-ink);
     font:var(--fs-base)/var(--lh) var(--font);-webkit-font-smoothing:antialiased}
h1{font-size:var(--fs-xl);margin:0 0 var(--sp-2);letter-spacing:-.4px;font-weight:650}
.muted{color:var(--c-ink-3)}
.warn{color:var(--c-warn);font-weight:600}
.money{font-variant-numeric:tabular-nums;letter-spacing:.1px}
.r{text-align:right}
::selection{background:var(--c-lime-dim)}

/* ---- the trial banner (F7) -----------------------------------------------
   Above the sticky app bar rather than inside it: it is a statement about the
   whole database, not a control, and it should scroll away once read instead
   of taking a permanent slice of a small screen. Warn colours, because it is
   a warning — but not the `late` red, which means a specific overdue job. */
.trial-bar{background:var(--c-warn-bg);border-bottom:1px solid var(--c-warn);
  color:var(--c-ink-2);font-size:var(--fs-sm);line-height:var(--lh);
  padding:var(--sp-3) var(--sp-7)}
.trial-bar strong{color:var(--c-warn)}

/* ---- chrome ------------------------------------------------------------- */
/* `flex-wrap` because the header is nine items wide on the board — brand,
   jump bar, a five-control filter form, four links and the user's name — and
   the rule written to make it responsive (`.jump{flex-basis:100%;order:9}`,
   below) was inert without it: with no wrap, `flex-basis:100%` only makes an
   item that shrinks and `order` only reshuffles the single row. Eight other
   flex containers in this file wrap; this was the one that needed it most. */
.bar{display:flex;flex-wrap:wrap;align-items:center;min-height:var(--bar-h);
     gap:var(--sp-4) var(--sp-5);padding:var(--sp-4) var(--sp-7);
     background:var(--c-panel);border-bottom:1px solid var(--c-line);
     position:sticky;top:0;z-index:5}
.brand{display:flex;align-items:center;gap:var(--sp-4);font-weight:700;
       text-decoration:none;color:var(--c-ink);letter-spacing:-.3px;font-size:var(--fs-lg)}
.brand img{height:36px;width:auto;display:block}
.brand span{color:var(--c-ink-3);font-weight:400}
.bar nav{display:flex;gap:var(--sp-3)}
.bar nav a{color:var(--c-ink-2);text-decoration:none;font-size:var(--fs-md);
           padding:var(--sp-2) var(--sp-4);border-radius:var(--radius-sm)}
.bar nav a:hover{color:var(--c-lime);background:var(--c-raise)}
main{max-width:var(--max-w);margin:var(--sp-8) auto var(--sp-9);padding:0 var(--sp-7)}

/* ---- tables ------------------------------------------------------------- */
.search{width:100%;padding:var(--sp-4) var(--sp-5);border:1px solid var(--c-line);
        border-radius:var(--radius-md);margin:var(--sp-5) 0 var(--sp-6);
        background:var(--c-panel);color:var(--c-ink);font-size:var(--fs-base)}
.search:focus{outline:none;border-color:var(--c-lime-edge);box-shadow:var(--ring)}
.search::placeholder{color:var(--c-ink-4)}


/* ---- inputs ------------------------------------------------------------- */
input{font:inherit;color:var(--c-ink);border:1px solid transparent;background:transparent;
      padding:var(--sp-3) var(--sp-4);border-radius:var(--radius-sm);width:100%;
      transition:border-color var(--t),background var(--t)}
input:hover{border-color:var(--c-line)}
/* A checkbox obeys `width` but not `padding` or `font`, so the rule above
   stretched every one of them to its container's full width. Chrome then draws
   the box centred in that width, which put the terms checkboxes in the middle
   of the panel with their labels pushed onto the next line. Fixed here, on the
   element, rather than per component: it was fixed once on `.field-check` and
   the next screen to use a checkbox inherited the bug again. */
input[type=checkbox],input[type=radio]{width:auto}
input:focus{outline:none;border-color:var(--c-lime-edge);background:var(--c-bg);
            box-shadow:var(--ring);position:relative;z-index:2}
input.num{text-align:right;font-variant-numeric:tabular-nums;max-width:140px}
tr:focus-within>td{background:var(--c-hover)}

/* ---- buttons ------------------------------------------------------------
   ONE definition, here, in the buttons section. There were two: this one and
   a second, unscoped `.btn` inside the login-card block, where every
   neighbouring rule is correctly prefixed `.auth-card`. Equal specificity and
   later in the file, so the login card's button was silently the button for
   the whole application — all eleven `class="btn"` controls, every one of them
   the committing action of a form.

   The merge was not merely cosmetic. The two `:hover` rules combined into one
   that took `color:var(--c-lime)` from here and `background:var(--c-lime-2)`
   from there: lime text on lime fill, **1.34:1**, where resting is 8.02:1. The
   label of every primary button in the app disappeared on hover, and neither
   rule was wrong by itself.

   Two roles now, named rather than inherited from whichever rule came last.
   ------------------------------------------------------------------------ */

/* The committing action: submit, accept, issue, save. */
/* `display` and `text-decoration` because both button classes are used on
   `<a>` as well as `<button>` — the invoice screen's Print and Credit are
   links — and a button with an underlined label is not a button. Neither of
   the two original rules reset it, so this predates the merge above. */
.btn{display:inline-block;text-decoration:none;text-align:center;
     background:var(--c-lime-3);color:var(--c-bg);border:0;
     border-radius:var(--radius-sm);padding:var(--sp-4) var(--sp-6);
     font:600 var(--fs-md)/1 var(--font);cursor:pointer;
     margin-top:var(--sp-5);transition:background var(--t)}
/* `color` is restated deliberately: the hover state must not inherit a text
   colour from anywhere, which is exactly how it went to 1.34:1. */
.btn:hover{background:var(--c-lime-2);color:var(--c-bg)}
.btn:focus-visible{outline:none;box-shadow:var(--ring)}

/* The alternative to the committing action — offered, not urged. This is the
   treatment the first `.btn` rule described, kept because a secondary button
   is a real role and this is what it should look like. */
.btn.quiet{background:var(--c-raise);color:var(--c-ink);
           border:1px solid var(--c-line);border-radius:var(--radius-md);
           font:inherit;margin:0}
.btn.quiet:hover{background:var(--c-hover);color:var(--c-lime);
                 border-color:var(--c-lime-edge)}
.ico{border:none;background:none;color:var(--c-ink-4);cursor:pointer;font-size:var(--fs-md);
     line-height:1;padding:var(--sp-2) var(--sp-3);border-radius:var(--radius-sm)}
.ico:hover{background:var(--c-hover);color:var(--c-lime)}
.ico.x:hover{background:var(--c-warn-bg);color:var(--c-warn)}
tr .ico{opacity:0;transition:opacity var(--t)}
tr:hover .ico,tr:focus-within .ico{opacity:1}

/* ---- drag --------------------------------------------------------------- */
/* The board's card drag was removed 15 Sep 2026 (Q3): the customer never asked
   for it. The one drag they want is reordering lines within a quote, which
   uses the same library, and SortableJS applies `.sortable-chosen` to the row
   being dragged. */
.sortable-chosen{box-shadow:var(--shadow)}

/* The handle is the only part of a row that starts a drag, so selecting text
   in a description never picks the line up. Hidden until the row is hovered or
   focused, like the row's other controls; on a touch screen the ↑/↓ buttons
   are the route, since there is no hover and a drag handle is a mouse idea. */
.grip{display:inline-block;cursor:grab;color:var(--c-ink-4);user-select:none;
  margin-right:var(--sp-2);opacity:0;transition:opacity var(--t)}
tr:hover .grip,tr:focus-within .grip{opacity:1}
.grip:active{cursor:grabbing}
.pos-no{font-variant-numeric:tabular-nums}

/* Wide enough for four buttons on a line — up, down, add costing, remove —
   rather than the three a costing row carries. It was a bare 104px sized for
   two, and would have overflowed. */
.grid th.acts,.grid td.acts{width:calc(var(--sp-8) * 3);white-space:nowrap;
  text-align:right;padding-right:var(--sp-4)}

/* ---- totals -------------------------------------------------------------
   The panel's own styling lives with the rest of the quote footer, further
   down. What stood here was written for a different markup — `.row`, an `hr`,
   a `label`, an `input`, a `.total` holding a `span` and a `b` — and
   `_totals.html` renders a `<dl>`. None of those selectors has matched
   anything for as long as the template has looked the way it does.
   ------------------------------------------------------------------------ */

/* ---- line / costing hierarchy ------------------------------------------- */
/* Column widths: description takes whatever the numbers do not need. */
.grid th.c-qty{width:118px}

/* A quote line opens a block; a lime rail ties its costing rows to it. */

/* Sub-header: the costing rows carry different columns to the line above. */


/* Margin as a chip reads faster than loose text in a column of numbers. */
.pill-warn{background:var(--c-warn-bg);color:var(--c-warn)}


/* The breakpoint that was here stacked `.footer`, which no longer exists.
   `.quote-foot` is the grid that replaced it and had no breakpoint at all:
   `1fr minmax(280px,360px)` cannot fit a phone, so the totals were pushing
   the page sideways. */
@media (max-width:900px){.quote-foot{grid-template-columns:1fr}
  .totals{grid-column:1;grid-row:2}
  .terms{grid-row:1}}

/* ---- authentication ------------------------------------------------------
   Added 2026-09-09. The legacy has no login screen that has ever been used,
   so this is the first thing a member of staff will see in the new system.
   No bare px and no bare colours: every value below is a token above.
   ------------------------------------------------------------------------ */
.auth{min-height:100vh;display:grid;place-items:center;padding:var(--sp-6)}
.auth-card{width:100%;max-width:380px;background:var(--c-panel);
  border:1px solid var(--c-line);border-radius:var(--radius);
  padding:var(--sp-7);box-shadow:var(--shadow)}
.auth-card h1{font-size:var(--fs-lg);margin-bottom:var(--sp-2)}
.auth-card .muted{font-size:var(--fs-sm);margin:0 0 var(--sp-6)}
.field{display:flex;flex-direction:column;gap:var(--sp-2);margin-bottom:var(--sp-5)}
.field label{font-size:var(--fs-sm);color:var(--c-ink-2)}
.field input{background:var(--c-bg);color:var(--c-ink);border:1px solid var(--c-line);
  border-radius:var(--radius-sm);padding:var(--sp-3) var(--sp-4);font:var(--fs-md)/var(--lh) var(--font)}
.field input:focus{outline:none;border-color:var(--c-lime-edge);box-shadow:var(--ring)}
/* Full width is right in a 380px card and nowhere else. The rest of the
   button lives in the buttons section with every other button. */
.auth-card .btn{width:100%;padding:var(--sp-4);margin-top:0}
.errors{background:var(--c-warn-bg);border:1px solid var(--c-warn);
  border-radius:var(--radius-sm);padding:var(--sp-3) var(--sp-4);
  margin-bottom:var(--sp-5);font-size:var(--fs-sm);color:var(--c-warn)}
.auth-foot{margin-top:var(--sp-6);padding-top:var(--sp-5);border-top:1px solid var(--c-line-soft);
  font-size:var(--fs-xs);color:var(--c-ink-4);text-align:center}

/* ---- job board -----------------------------------------------------------
   Replaces default.asp. Three columns, the same three states the legacy
   derives from date fields — here derived from documents that exist.
   ------------------------------------------------------------------------ */
.bar .brand{color:var(--c-ink);text-decoration:none;font-weight:650}
.filters{display:flex;gap:var(--sp-3);align-items:center;flex:1}
/* A `<select>` is as wide as its longest option, and the customer list holds
   346 of them: "Bearings International Division" made this control 405px, a
   fifth of the header, for a value that is usually "All customers". Capped, so
   one long name cannot set the width of the screen's furniture. */
.filters select{max-width:22ch}
.filters input,.filters select{background:var(--c-panel);color:var(--c-ink);
  border:1px solid var(--c-line);border-radius:var(--radius-sm);
  padding:var(--sp-2) var(--sp-3);font:var(--fs-sm)/1.4 var(--font)}
.filters input[type=search]{min-width:22ch}
.filters input:focus,.filters select:focus{outline:none;border-color:var(--c-lime-edge);box-shadow:var(--ring)}
.btn-sm{display:inline-block;text-decoration:none;
  background:var(--c-raise);color:var(--c-ink);border:1px solid var(--c-line);
  border-radius:var(--radius-sm);padding:var(--sp-2) var(--sp-4);cursor:pointer;font:var(--fs-sm)/1.4 var(--font)}
.btn-sm:hover{background:var(--c-hover)}
.clear{color:var(--c-ink-3);font-size:var(--fs-sm);text-decoration:none}
/* A control that must be a button for the right reason — it changes state —
   but reads as the link it sits beside. */
.signout{display:inline}
.theme-pick{margin:var(--sp-6) 0 0}
.theme-pick fieldset{border:0;margin:0 0 var(--sp-3);padding:0}
.theme-pick legend{font-size:var(--fs-md);font-weight:600;margin-bottom:var(--sp-3);color:var(--c-ink)}
.linky{background:none;border:0;padding:0;font:inherit;cursor:pointer;
  color:var(--c-ink-3);font-size:var(--fs-sm)}
.linky:hover{color:var(--c-ink)}
.who{color:var(--c-ink-2);font-size:var(--fs-sm);text-decoration:none}

#flash{padding:0 var(--sp-7)}
.flash{margin:var(--sp-3) 0;padding:var(--sp-3) var(--sp-4);border-radius:var(--radius-sm);
  font-size:var(--fs-sm);border:1px solid var(--c-line);background:var(--c-panel)}
.flash-success{border-color:var(--c-lime-edge);color:var(--c-lime-2)}
.flash-warning,.flash-error{border-color:var(--c-warn);color:var(--c-warn);background:var(--c-warn-bg)}

.board{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-5);
  padding:var(--sp-5) var(--sp-7);align-items:start;max-width:var(--max-w);margin:0 auto}
.col{background:var(--c-panel);border:1px solid var(--c-line);border-radius:var(--radius);
  display:flex;flex-direction:column;min-height:var(--sp-9)}
/* Every header is exactly two lines — heading row, then a one-line blurb — so
   the three columns' cards start level. "N late" sits on the heading row. */
.col-head{padding:var(--sp-4) var(--sp-5);border-bottom:1px solid var(--c-line-soft);
  display:grid;grid-template-columns:auto 1fr auto;align-items:baseline;gap:0 var(--sp-3)}
.col-head h2{margin:0;font-size:var(--fs-md);font-weight:650}
.col-count{grid-column:3;font-variant-numeric:tabular-nums;color:var(--c-ink-3);font-size:var(--fs-sm)}
.col-blurb{grid-column:1/-1;margin:var(--sp-1) 0 0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--c-ink-4);font-size:var(--fs-xs)}
.col-body{padding:var(--sp-3);display:flex;flex-direction:column;gap:var(--sp-3);
  max-height:70vh;overflow-y:auto;min-height:var(--sp-8)}
.col-empty,.col-more{color:var(--c-ink-4);font-size:var(--fs-xs);margin:var(--sp-3);text-align:center}

.card{background:var(--c-raise);border:1px solid var(--c-line);border-radius:var(--radius-sm);
  padding:var(--sp-3) var(--sp-4)}
.card:hover{background:var(--c-hover)}
.card header{display:flex;justify-content:space-between;align-items:baseline;gap:var(--sp-3)}
.card-no{color:var(--c-lime-2);text-decoration:none;font-weight:600;
  font-variant-numeric:tabular-nums}
.card-quote,.card-inv{color:var(--c-ink-4);font-size:var(--fs-xs);
  font-variant-numeric:tabular-nums;text-decoration:none}
.card-quote:hover,.card-inv:hover{color:var(--c-lime-2)}
.card-customer{margin:var(--sp-2) 0 0;font-size:var(--fs-sm)}
/* ---- the customer picker (F20) -------------------------------------------
   345 options in a native dropdown is a scroll wheel. The select is still the
   control that submits — this is what sits in front of it. */
.pick{position:relative;display:inline-block;min-width:22ch}
.pick-q{width:100%;background:var(--c-panel);color:var(--c-ink);
  border:1px solid var(--c-line);border-radius:var(--radius-sm);
  padding:var(--sp-2) var(--sp-3);font:var(--fs-sm)/1.4 var(--font)}
.pick-q:focus{outline:none;border-color:var(--c-lime-edge);box-shadow:var(--ring)}
.pick-q::placeholder{color:var(--c-ink-3)}
/* A chosen customer is a statement, not a half-typed query. */
.pick.is-chosen .pick-q{color:var(--c-ink);border-color:var(--c-lime-edge)}
.pick-results:empty{display:none}
.pick-results{position:absolute;z-index:6;top:calc(100% + var(--sp-1));left:0;right:0;
  min-width:28ch;max-height:46vh;overflow-y:auto;background:var(--c-panel);
  border:1px solid var(--c-line);border-radius:var(--radius-sm);
  box-shadow:var(--shadow);padding:var(--sp-1)}
.pick-hit{display:block;width:100%;text-align:left;background:none;border:0;
  color:var(--c-ink-2);font:var(--fs-sm)/1.5 var(--font);cursor:pointer;
  padding:var(--sp-2) var(--sp-3);border-radius:var(--radius-sm)}
.pick-hit:hover,.pick-hit.is-active{background:var(--c-hover);color:var(--c-ink)}
.pick-none{margin:0;padding:var(--sp-2) var(--sp-3);color:var(--c-ink-4);
  font-size:var(--fs-xs)}

/* A customer's name is a link wherever it is not already inside one (F15).
   Underlined only on hover: on the board every card carries one, and thirteen
   permanent underlines in a column read as clutter rather than as affordance.
   */
.party-link{color:inherit;text-decoration:none;cursor:pointer;
  border-bottom:1px solid transparent}
.party-link:hover{color:var(--c-lime-2);border-bottom-color:var(--c-lime-edge)}
.card-nocustomer{color:var(--c-warn)}
.card-ref{margin:var(--sp-1) 0 0;color:var(--c-ink-3);font-size:var(--fs-xs)}

.detail{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:var(--sp-5);padding:var(--sp-5) var(--sp-7);max-width:var(--max-w);margin:0 auto}
.panel{background:var(--c-panel);border:1px solid var(--c-line);
  border-radius:var(--radius);padding:var(--sp-5)}
.panel h2{margin:0 0 var(--sp-4);font-size:var(--fs-md)}
.panel p{margin:0 0 var(--sp-2);font-size:var(--fs-sm)}
.pill{display:inline-block;min-width:76px;padding:var(--sp-1) var(--sp-3);
  border-radius:var(--radius-pill);background:var(--c-raise);
  border:1px solid var(--c-line);font-size:var(--fs-xs);color:var(--c-ink-2);
  font-weight:600;font-variant-numeric:tabular-nums}
.pill-active{border-color:var(--c-lime-edge);color:var(--c-lime-2)}
.pill-complete{color:var(--c-ink-3)}

/* ---- transition forms ---------------------------------------------------- */
.form-page{max-width:760px;margin:0 auto;padding:var(--sp-5) var(--sp-7);
  display:flex;flex-direction:column;gap:var(--sp-5)}
.form-page .field{margin-bottom:var(--sp-5)}
.form-page .field input[type=text],.form-page .field input[type=date]{
  background:var(--c-bg);color:var(--c-ink);border:1px solid var(--c-line);
  border-radius:var(--radius-sm);padding:var(--sp-3) var(--sp-4);
  font:var(--fs-md)/var(--lh) var(--font);width:100%}
.form-page .field input:focus{outline:none;border-color:var(--c-lime-edge);box-shadow:var(--ring)}
.hint{display:block;color:var(--c-ink-4);font-size:var(--fs-xs);margin-top:var(--sp-2)}
.lines{list-style:none;padding:0;margin:var(--sp-4) 0 0;display:flex;flex-direction:column;gap:var(--sp-2)}
.lines label{display:grid;grid-template-columns:auto 1fr auto auto;gap:var(--sp-4);
  align-items:baseline;padding:var(--sp-3);border-radius:var(--radius-sm);
  background:var(--c-raise);font-size:var(--fs-sm);cursor:pointer}
.lines label:hover{background:var(--c-hover)}
.line-qty,.line-total{color:var(--c-ink-2);text-align:right}
.form-actions{display:flex;gap:var(--sp-5);align-items:center}
/* One rule, and no `width:auto`. There were two of these, in two sections,
   and both existed only to undo the login card's stray `width:100%` — the
   compensation outlived nothing, because `.btn` no longer sets a width.
   A button label is a phrase, not a paragraph: "Start the quote" wrapped to
   two lines inside the flex row, hence `nowrap`. */
.form-actions .btn{white-space:nowrap;padding:var(--sp-3) var(--sp-6)}

/* ---- quote builder ------------------------------------------------------- */
.quote{max-width:var(--max-w);margin:0 auto;padding:var(--sp-5) var(--sp-7);
  display:flex;flex-direction:column;gap:var(--sp-5)}
/* `margin-bottom` and `align-items` arrive from a superseded flex version of
   this rule; `align-items:flex-start` is load-bearing — without it the grid
   stretches every cell to the tallest. */
.head{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:var(--sp-5);margin-bottom:var(--sp-7);align-items:flex-start}
.head .field{margin:0}
.head label{display:block;font-size:var(--fs-xs);color:var(--c-ink-3);margin-bottom:var(--sp-2)}
.head input{width:100%;background:var(--c-bg);color:var(--c-ink);border:1px solid var(--c-line);
  border-radius:var(--radius-sm);padding:var(--sp-2) var(--sp-3);font:var(--fs-sm)/1.4 var(--font)}
.head .static{margin:0;font-size:var(--fs-sm)}
/* ---- the line/costing grid ----------------------------------------------
   One `.grid`, carrying the values that were *actually in effect*.

   There were two `.grid` designs, in two sections: an earlier one scoped to a
   `.lines` table with `.lineblock` wrappers, and this one. The editor's markup
   changed shape — it is `<table class="grid">` with `tbody.line`, `tr.line-row`
   and `tr.item-row`, and no `.lines` ancestor — so every `.lines …` rule
   stopped matching, silently. But the two bare `.grid` rules kept matching
   *both*, and the cascade resolves per property, not per rule: the live look
   was a merge nobody designed. `border-collapse` and the paddings came from
   here; `background`, `border`, `border-radius`, `overflow` and the uppercase
   header came from the block that was otherwise dead.

   The merged values are written out below, so the invoice and order documents
   — which share this table — look exactly as they did.
   ------------------------------------------------------------------------ */
.grid{width:100%;border-collapse:collapse;border-spacing:0;font-size:var(--fs-sm);
  background:var(--c-panel);border:1px solid var(--c-line);
  border-radius:var(--radius);overflow:hidden}
.grid th{text-align:left;font-weight:500;font-size:var(--fs-xs);color:var(--c-ink-3);
  text-transform:uppercase;letter-spacing:.7px;background:var(--c-raise);
  padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--c-line)}
.grid td{padding:var(--sp-1) var(--sp-3);border-bottom:1px solid var(--c-line-soft);
  vertical-align:middle}
.grid tbody:last-child tr:last-child td{border-bottom:none}

/* The one cue that was lost rather than merged, and worth having back: with
   two line blocks in a row nothing marked where the first ended. This is the
   old `.lineblock+.lineblock td` rule, pointed at the markup that exists. */
tbody.line+tbody.line td{border-top:1px solid var(--c-line)}
.grid input{width:100%;background:transparent;color:var(--c-ink);border:1px solid transparent;
  border-radius:var(--radius-sm);padding:var(--sp-2);font:var(--fs-sm)/1.4 var(--font)}
.grid input:hover{border-color:var(--c-line)}
.grid input:focus{outline:none;background:var(--c-bg);border-color:var(--c-lime-edge);box-shadow:var(--ring)}
.line-row{background:var(--c-raise)}
.item-row td{color:var(--c-ink-2)}
.item-row .indent{padding-left:var(--sp-6)}
.c-pos{width:calc(var(--sp-8) + var(--sp-5));white-space:nowrap;color:var(--c-ink-4);font-variant-numeric:tabular-nums}
.c-qty{width:9ch}
.c-money{width:13ch;text-align:right}
/* A percentage is not money: it gets its own narrower column, is not given the
   tabular money face, and carries a visible unit. Rendered in the money column
   with `class="money"` — which is what it was — a markup of 23 sits
   right-aligned under "Total" beneath a real R 246.00 and reads as R 23.00. */
.c-pct{width:8ch;text-align:right;white-space:nowrap}
.c-pct input{text-align:right;width:5ch;display:inline-block}
.c-pct .unit{color:var(--c-ink-4);font-size:var(--fs-sm);margin-left:var(--sp-1)}

/* The costing rows' own heading row. Quieter than the table head above it:
   it labels a block, it does not open the table. */
.grid .subhead td{background:var(--c-panel);border-bottom:1px solid var(--c-line-soft);
  font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.7px;
  color:var(--c-ink-4);font-weight:600;padding:var(--sp-2) var(--sp-5)}
.grid .subhead td.indent{padding-left:var(--sp-6);color:var(--c-ink-3)}
.grid .c-money input,.grid .c-qty input{text-align:right}
.strong{font-weight:600}
.quote-foot{display:grid;grid-template-columns:1fr minmax(280px,360px);gap:var(--sp-5);align-items:start}
/* `margin` and `box-shadow` came from the superseded block above; the
   `max-width:440px` and `flex` that came with them could never bind, because
   this sits in a grid column capped at 360px. */
.totals{margin:0;background:var(--c-panel);border:1px solid var(--c-line);
  border-radius:var(--radius);box-shadow:var(--shadow);
  padding:var(--sp-5);grid-column:2}
.totals dl{display:grid;grid-template-columns:1fr auto;gap:var(--sp-2) var(--sp-5);margin:0}
.totals dt{color:var(--c-ink-3);font-size:var(--fs-sm)}
.totals dd{margin:0;text-align:right;font-size:var(--fs-sm)}
.totals .grand{font-size:var(--fs-lg);font-weight:650;color:var(--c-ink)}
.totals .costing{margin-top:var(--sp-4);padding-top:var(--sp-4);border-top:1px solid var(--c-line-soft)}
.chip{display:inline-block;margin-left:var(--sp-2);padding:0 var(--sp-2);border-radius:var(--radius-pill);
  background:var(--c-lime-dim);color:var(--c-lime-2);font-size:var(--fs-xs)}
.terms{grid-row:1;grid-column:1}
/* `text-transform` and the rest came from a block written for a `<dl>` terms
   panel that no longer exists; they are the heading's actual appearance. */
.terms h2{margin:0 0 var(--sp-3);font-size:var(--fs-md);text-transform:uppercase;
  letter-spacing:.8px;color:var(--c-ink-4);font-weight:600}
.check{display:grid;grid-template-columns:auto auto 1fr;gap:var(--sp-3);align-items:baseline;
  font-size:var(--fs-sm);margin-bottom:var(--sp-2);color:var(--c-ink-2)}
.check-name{color:var(--c-ink-3);white-space:nowrap}
.check-text{color:var(--c-ink)}

/* ---- the invoice document ------------------------------------------------ */
.form-page.wide{max-width:1020px}
.doc{display:flex;flex-direction:column;gap:var(--sp-5)}
.doc-head{display:grid;grid-template-columns:1fr minmax(280px,380px);gap:var(--sp-7);
  align-items:start}
.doc-party h2{margin:0 0 var(--sp-3);font-size:var(--fs-lg)}
.doc-party p{margin:0 0 var(--sp-1);font-size:var(--fs-sm)}
.doc-meta{display:grid;grid-template-columns:auto 1fr;gap:var(--sp-2) var(--sp-5);margin:0;
  background:var(--c-raise);border-radius:var(--radius-md);padding:var(--sp-4) var(--sp-5)}
.doc-meta dt{color:var(--c-ink-3);font-size:var(--fs-xs)}
.doc-meta dd{margin:0;text-align:right;font-size:var(--fs-sm)}
/* The quote revision is the one link on the face of a document, and it is
   the C1 statement — which version the customer ordered against. It gets the
   accent, not the browser default. */
.doc-meta dd a{color:var(--c-lime-2);text-decoration:none}
.doc-meta dd a:hover{text-decoration:underline}
.doc-foot{display:flex;justify-content:flex-end}
.totals-dl{display:grid;grid-template-columns:1fr auto;gap:var(--sp-2) var(--sp-6);margin:0;
  min-width:320px;border-top:1px solid var(--c-line);padding-top:var(--sp-4)}
.totals-dl dt{color:var(--c-ink-3);font-size:var(--fs-sm)}
.totals-dl dd{margin:0;text-align:right;font-size:var(--fs-sm)}
.totals-dl .grand{font-size:var(--fs-lg);font-weight:650;color:var(--c-ink)}

/* ---- search -------------------------------------------------------------- */
.search-page{max-width:1020px;margin:0 auto;padding:var(--sp-5) var(--sp-7);
  display:flex;flex-direction:column;gap:var(--sp-5)}
.search-form label{display:block;font-size:var(--fs-xs);color:var(--c-ink-3);
  margin-bottom:var(--sp-2)}
.search-form input[type=search]{width:100%;background:var(--c-panel);color:var(--c-ink);
  border:1px solid var(--c-line);border-radius:var(--radius-md);
  padding:var(--sp-4) var(--sp-5);font:var(--fs-base)/1.4 var(--font)}
.search-form input[type=search]:focus{outline:none;border-color:var(--c-lime-edge);
  box-shadow:var(--ring)}
.htmx-indicator{opacity:0;transition:opacity var(--t)}
.htmx-request .htmx-indicator,.htmx-request.htmx-indicator{opacity:1}
.result-count{font-size:var(--fs-sm);color:var(--c-ink-2);margin:0}
.results{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--sp-3)}
.result{background:var(--c-panel);border:1px solid var(--c-line);
  border-radius:var(--radius-md);padding:var(--sp-4) var(--sp-5)}
.result-head{display:flex;align-items:baseline;gap:var(--sp-4);flex-wrap:wrap}
.result-job{color:var(--c-lime-2);text-decoration:none;font-weight:650;
  font-variant-numeric:tabular-nums}
.result-job:hover{color:var(--c-lime)}
.result-customer{font-size:var(--fs-sm);color:var(--c-ink)}
.result-meta{display:flex;gap:var(--sp-5);flex-wrap:wrap;margin-top:var(--sp-2);
  font-size:var(--fs-xs);color:var(--c-ink-2)}
.result-meta a{color:var(--c-ink-2)}
.result-lines{list-style:none;margin:var(--sp-3) 0 0;padding:var(--sp-3) 0 0;
  border-top:1px solid var(--c-line-soft);display:flex;flex-direction:column;gap:var(--sp-1)}
.result-lines li{display:grid;grid-template-columns:1fr 7ch 13ch;gap:var(--sp-4);
  font-size:var(--fs-sm);color:var(--c-ink-2)}
.result-qty,.result-amount{text-align:right}
.result-more{list-style:none;margin-top:var(--sp-3)}
mark{background:var(--c-lime-dim);color:var(--c-lime);border-radius:var(--radius-sm);
  padding:0 var(--sp-1)}

/* ---- delivery promise ---------------------------------------------------- */
/* default.asp paints these as full-width table cells in #ff0000, #ff7f27 and
   #80ff00 — pure saturated web colours on white. The same three states, toned
   for a dark ground and readable side by side. */
.due{display:flex;justify-content:space-between;align-items:baseline;gap:var(--sp-3);
  margin-top:var(--sp-3);padding:var(--sp-1) var(--sp-3);border-radius:var(--radius-sm);
  font-size:var(--fs-xs);border:1px solid transparent}
.due-label{font-weight:650}
.due-date{font-variant-numeric:tabular-nums;opacity:.8}
.due-overdue{background:var(--c-late-bg);color:var(--c-late);border-color:var(--c-late-edge)}
.due-soon{background:var(--c-warn-bg);color:var(--c-warn)}
.due-on-track{background:var(--c-lime-dim);color:var(--c-lime-2)}
.due-unknown{color:var(--c-ink-4)}
.col-late{color:var(--c-late);font-size:var(--fs-sm);font-weight:650}
.promise{display:grid;grid-template-columns:auto 1fr;gap:var(--sp-2) var(--sp-5);margin:0}
.promise dt{color:var(--c-ink-3);font-size:var(--fs-sm)}
.promise dd{margin:0;font-size:var(--fs-sm);font-variant-numeric:tabular-nums}

/* ---------------------------------------------------------------- jump bar
   The header search box. Present on every screen but the search page itself.
   Sits on --c-bg rather than the bar's --c-panel so it reads as a well cut
   into the header, not another chip sitting on it. */
.jump{position:relative;margin-left:auto;flex:0 1 var(--jump-w)}
.jump-q{width:100%;padding:var(--sp-3) var(--sp-8) var(--sp-3) var(--sp-4);
        background:var(--c-bg);color:var(--c-ink);
        border:1px solid var(--c-line);border-radius:var(--radius-md);
        font:var(--fs-md)/var(--lh) var(--font)}
.jump-q::placeholder{color:var(--c-ink-4)}
.jump-q:focus{outline:none;border-color:var(--c-lime-edge);box-shadow:var(--ring)}
.jump-q::-webkit-search-cancel-button{filter:grayscale(1) opacity(.5)}

.jump-key{position:absolute;right:var(--sp-3);top:50%;transform:translateY(-50%);
          pointer-events:none;font:var(--fs-xs)/1 var(--font);color:var(--c-ink-4);
          border:1px solid var(--c-line);border-radius:var(--radius-sm);
          padding:var(--sp-1) var(--sp-2)}
.jump-q:focus ~ .jump-key{opacity:0}

/* Empty until htmx puts something in it, so it must take no space when empty:
   :empty is what keeps a bare border from hanging under the header. */
.jump-results:empty{display:none}
.jump-results{position:absolute;z-index:20;top:calc(100% + var(--sp-2));left:0;right:0;
              background:var(--c-panel);border:1px solid var(--c-line);
              border-radius:var(--radius-md);box-shadow:var(--shadow);overflow:hidden}
.jump-list{list-style:none;margin:0;padding:var(--sp-2);
           display:flex;flex-direction:column;gap:var(--sp-1)}

/* One row per hit, and every cell placed explicitly. Auto-placement put the
   state chip in the first column, ahead of the job number — grid fills the
   first free cell, and a chip is not what the eye should land on. */
.jump-hit{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;
          align-items:baseline;column-gap:var(--sp-4);
          padding:var(--sp-2) var(--sp-3);border-radius:var(--radius-sm);
          text-decoration:none;color:var(--c-ink);white-space:nowrap}
.jump-hit:hover,.jump-hit.is-active{background:var(--c-hover)}
.jump-hit.is-active{box-shadow:inset 2px 0 0 var(--c-lime)}
.jump-no{grid-column:1;font-weight:650;font-variant-numeric:tabular-nums;
         color:var(--c-lime-2)}
.jump-cust{grid-column:2;overflow:hidden;text-overflow:ellipsis;
           font-size:var(--fs-md)}
.jump-nocust{color:var(--c-ink-4);font-style:italic}
.jump-why{grid-column:3;font-size:var(--fs-xs);color:var(--c-ink-3)}
.jump-state{grid-column:4;font-size:var(--fs-xs);min-width:7ch;text-align:right;
            color:var(--c-ink-3);text-transform:lowercase}
.jump-state-active{color:var(--c-lime-2)}
.jump-state-pending{color:var(--c-ink-2)}

/* The count line. It is a link, so a word that matches 5,692 jobs still has
   one obvious next step rather than a dead end. */
.jump-all{display:block;padding:var(--sp-3) var(--sp-4);
          border-top:1px solid var(--c-line-soft);background:var(--c-raise);
          font-size:var(--fs-sm);color:var(--c-ink-2);text-decoration:none}
.jump-list + .jump-all{border-top:1px solid var(--c-line)}
.jump-all:hover{color:var(--c-lime);background:var(--c-hover)}

/* The jump bar takes a row of its own once the header wraps. Written before
   `.bar` could wrap, so it did nothing for as long as it existed. */
@media (max-width:900px){
  .jump{flex-basis:100%;order:9;margin-left:0}
  /* A 32px gap between eight items is most of a narrow screen. */
  .bar{gap:var(--sp-3) var(--sp-5)}
  /* Only now: on a desktop the five filter controls belong on one line. */
  .filters{flex-wrap:wrap}
  /* 22ch is wider than the phone it is wrapping for. */
  .filters input[type=search]{min-width:12ch;flex:1 1 12ch}
}

/* ---- reports ------------------------------------------------------------
   A month-end screen: 45% of the legacy's 2,081 runs land on days 24-31. So
   it is read once, carefully, rather than watched — density serves it better
   than air, and the totals are what the eye should land on. */
.rpt-link{color:var(--c-ink-2);font-size:var(--fs-sm);text-decoration:none;
          margin-left:auto;padding:var(--sp-2) var(--sp-4);
          border:1px solid var(--c-line);border-radius:var(--radius-pill)}
.rpt-link:hover{background:var(--c-hover);color:var(--c-ink)}

.rpt-page{max-width:var(--max-w);margin:0 auto;padding:var(--sp-5) var(--sp-7)}
.rpt-pick{background:var(--c-panel);border:1px solid var(--c-line);
          border-radius:var(--radius);padding:var(--sp-5);margin-bottom:var(--sp-6)}

.rpt-choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
             gap:var(--sp-3)}
.rpt-choice{display:block;cursor:pointer;padding:var(--sp-4);
            background:var(--c-raise);border:1px solid var(--c-line);
            border-radius:var(--radius-md);transition:border-color var(--t)}
.rpt-choice:hover{border-color:var(--c-ink-4)}
.rpt-choice.on{border-color:var(--c-lime-edge);background:var(--c-lime-dim)}
/* Hidden without leaving the flow. `position:absolute` on a radio inside a
   grid cell places it at its static position with no containing block, and
   the four of them stretched the document to 3,278px against a 1,905px
   viewport — a horizontal scrollbar on every report, from four invisible
   controls. */
.rpt-choice input{appearance:none;width:0;height:0;margin:0;padding:0;border:0;
                  opacity:0}
.rpt-choice-t{display:block;font-weight:650;font-size:var(--fs-md)}
.rpt-choice-b{display:block;color:var(--c-ink-3);font-size:var(--fs-xs);
              margin-top:var(--sp-1)}

.rpt-scope{display:flex;flex-wrap:wrap;align-items:end;gap:var(--sp-4);
           margin-top:var(--sp-5)}
.rpt-scope label{display:flex;flex-direction:column;gap:var(--sp-1);
                 font-size:var(--fs-xs);color:var(--c-ink-3)}
.rpt-scope select{background:var(--c-raise);color:var(--c-ink);
                  border:1px solid var(--c-line);border-radius:var(--radius-sm);
                  padding:var(--sp-2) var(--sp-3);font-size:var(--fs-sm)}
/* Month and year belong to two of the four reports. Hidden rather than
   removed, so the row does not reflow as the choice changes. */
.rpt-month.off{visibility:hidden}

.rpt-head{margin-bottom:var(--sp-4)}
.rpt-meta{color:var(--c-ink-3);font-size:var(--fs-sm);margin:var(--sp-2) 0 0}
.rpt-meta a{color:var(--c-lime-2)}
.rpt-empty{color:var(--c-ink-3);padding:var(--sp-7) 0;text-align:center}

/* Orders Outstanding carries six leading columns before the item text, so on
   a laptop the table is wider than the window. It scrolls in its own box; the
   page does not scroll sideways. */
.rpt-wrap{overflow-x:auto}

/* No `overflow:hidden` here, however much the rounded corners want it: an
   overflow ancestor becomes the scrollport for `position:sticky`, and the
   header below then pins itself 70px down *inside the table*, painting over
   the second row of the first group. Caught in a screenshot, not in a test —
   the markup and the totals were both correct. */
.rpt{width:100%;border-collapse:collapse;font-size:var(--fs-sm);
     background:var(--c-panel);border:1px solid var(--c-line);
     border-radius:var(--radius)}
/* Not sticky. A sticky header needs no overflow ancestor, and this table has
   one by necessity (above) — with it, the header pins itself 70px down inside
   the table and paints over the first group's second row. The report is read
   top-down and downloaded; a following header is not worth that bug. */
.rpt th{background:var(--c-raise);color:var(--c-ink-3);font-size:var(--fs-xs);
        font-weight:600;text-align:left;padding:var(--sp-3) var(--sp-4);
        border-bottom:1px solid var(--c-line)}
.rpt td{padding:var(--sp-2) var(--sp-4);vertical-align:top;
        border-bottom:1px solid var(--c-line-soft)}
.rpt-g{border-top:1px solid var(--c-line)}
.rpt-lead{color:var(--c-ink-2);white-space:nowrap}
.rpt-lead a{color:var(--c-lime-2);font-weight:650;text-decoration:none;
            font-variant-numeric:tabular-nums}
.rpt-lead a:hover{text-decoration:underline}
.rpt-sub td{background:var(--c-raise);color:var(--c-ink-2);font-weight:650;
            border-bottom:1px solid var(--c-line)}

.rpt-total{text-align:right;font-size:var(--fs-lg);margin:var(--sp-5) 0 var(--sp-9)}
.rpt-total strong{color:var(--c-lime);font-size:var(--fs-xl);margin-left:var(--sp-4)}

/* ============================================================================
   the directory — customers and suppliers
   ----------------------------------------------------------------------------
   One block, because the legacy is one screen written twice (core/parties.py).
   Read thirteen times for every save, so the search box is the screen and the
   form is a second page — the reverse of the legacy, which is a form with 345
   names in a scrolling <select> and no search at all.
   ========================================================================== */
.dir-page{max-width:var(--max-w);margin:0 auto;padding:var(--sp-5) var(--sp-7)}

.dir-find{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-4);
          background:var(--c-panel);border:1px solid var(--c-line);
          border-radius:var(--radius);padding:var(--sp-4) var(--sp-5)}
.dir-find input[type=search]{flex:1 1 320px;min-width:0;
          background:var(--c-raise);color:var(--c-ink);
          border:1px solid var(--c-line);border-radius:var(--radius-sm);
          padding:var(--sp-3) var(--sp-4);font-size:var(--fs-sm)}
.dir-toggle{display:flex;align-items:center;gap:var(--sp-2);
            color:var(--c-ink-3);font-size:var(--fs-sm);white-space:nowrap}
.dir-new{margin-left:auto;text-decoration:none}

.dir-count{color:var(--c-ink-3);font-size:var(--fs-sm);margin:var(--sp-5) 0 var(--sp-3)}
.dir-empty{color:var(--c-ink-3);padding:var(--sp-7) 0;text-align:center}

/* Four or five columns, and an address book is read on a phone as often as at
   a desk. Its own scrollport, so the page never scrolls sideways. */
.dir-wrap{overflow-x:auto}
.dir{width:100%;border-collapse:collapse;font-size:var(--fs-sm);
     background:var(--c-panel);border:1px solid var(--c-line);
     border-radius:var(--radius)}
.dir th{background:var(--c-raise);color:var(--c-ink-3);font-size:var(--fs-xs);
        font-weight:600;text-align:left;padding:var(--sp-3) var(--sp-4);
        border-bottom:1px solid var(--c-line)}
.dir td{padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--c-line-soft)}
.dir td.num,.dir th.num{text-align:right;font-variant-numeric:tabular-nums}
.dir tr:hover td{background:var(--c-hover)}
.dir a{color:var(--c-lime-2);text-decoration:none}
.dir a:hover{text-decoration:underline}

/* Unlisted is a state, not a deletion — the whole point of the change. It has
   to be legible on the row and not merely absent from it. */
.dir-off td{color:var(--c-ink-4)}
.dir-off a{color:var(--c-ink-3)}
.dir-tag{margin-left:var(--sp-3);padding:0 var(--sp-2);border-radius:var(--radius-pill);
         border:1px solid var(--c-line);color:var(--c-ink-4);font-size:var(--fs-xs)}

/* Read-only rendering of a field, for staff who may look but not change. */
.ro{color:var(--c-ink-2);font-size:var(--fs-sm);white-space:pre-wrap}
/* The address is a textarea and the e-mail an <input type=email>; neither is
   covered by the .field rules above, which name input[type=text] and
   input[type=date] explicitly. Left alone the textarea renders in the
   browser default — a light grey box in the middle of a dark form. */
.form-page .field textarea,.form-page .field input[type=email],
.form-page .field input[type=search]{
  background:var(--c-bg);color:var(--c-ink);border:1px solid var(--c-line);
  border-radius:var(--radius-sm);padding:var(--sp-3) var(--sp-4);
  font:var(--fs-md)/var(--lh) var(--font);width:100%;box-sizing:border-box}
.form-page .field textarea:focus,.form-page .field input[type=email]:focus{
  outline:none;border-color:var(--c-lime-edge);box-shadow:var(--ring)}

/* Label and box on one line; the explanation of what "unlisted" means is the
   point of the field, so it gets its own. */
.field-check{flex-direction:row;flex-wrap:wrap;align-items:center;gap:var(--sp-3)}
.field-check .hint{flex-basis:100%}
/* The stretching this used to correct is now handled on the element itself,
   next to the `input{width:100%}` rule that causes it. What is still needed
   here is only the flex instruction: a checkbox must not grow into the row. */
.field-check input[type=checkbox]{flex:0 0 auto}
.hist{list-style:none;margin:0;padding:0;color:var(--c-ink-3);font-size:var(--fs-sm)}
.hist li{padding:var(--sp-2) 0;border-bottom:1px solid var(--c-line-soft)}
.hist time{color:var(--c-ink-4);margin-right:var(--sp-3);
           font-variant-numeric:tabular-nums}

/* A statement the user must read before acting — a stale price, a job that has
   been put aside, a reason an action is refused. Not an error: these pages are
   working correctly and saying something true about the data. */
.warn-box{background:var(--c-warn-bg);color:var(--c-warn);
          border:1px solid var(--c-warn);border-radius:var(--radius-sm);
          padding:var(--sp-4) var(--sp-5);margin:0 0 var(--sp-5);
          font-size:var(--fs-sm);line-height:var(--lh)}
.warn-box strong{color:var(--c-warn)}
.form-page .warn-box{margin-bottom:var(--sp-5)}

/* A <select> inside a form panel. The .field rules name input[type=text] and
   input[type=date]; a bare select falls through to the browser default, which
   on this dark ground is a white box. */
.form-page .field select{background:var(--c-raise);color:var(--c-ink);
  border:1px solid var(--c-line);border-radius:var(--radius-sm);
  padding:var(--sp-3) var(--sp-4);font:var(--fs-md)/var(--lh) var(--font);
  width:100%;box-sizing:border-box}
.form-page .field select:focus{outline:none;border-color:var(--c-lime-edge);
  box-shadow:var(--ring)}

/* The one thing in the header that makes something rather than finds it. The
   legacy gives it a permanent nav slot ("Quote", top.inc:72) and it is used
   1,539 times in nine years — uncommon, but the system is unusable without it. */
.rpt-link.new-quote{color:var(--c-lime-2);border-color:var(--c-lime-edge)}
.rpt-link.new-quote:hover{background:var(--c-lime-dim);color:var(--c-lime)}

/* "Add a line" sits under the table, not in it: a control that creates a row
   is not itself a row. */
.grid-add{display:flex;align-items:center;gap:var(--sp-4);
          margin:var(--sp-4) 0 0;flex-wrap:wrap}
.grid-add .hint{margin:0}

/* ---- files on a job ------------------------------------------------------
   The replacement for the folder icon at default.asp:170, which opened
   \\VANMECHFILESRV\...\{job_no}\ in Windows Explorer and told the application
   nothing.

   Redesigned 16 Sep 2026, from the first job with real files on it. Three
   things were wrong and all three were the same mistake — a panel in the
   five-up grid holding content that grows without bound. Seven files wrapped
   to three lines each in a 290px column, a permanent dashed drop box cost
   four files' worth of height, and six of the seven were photographs shown as
   nothing but a filename. */
.detail-wide{grid-template-columns:1fr}

.files-head{display:flex;align-items:baseline;justify-content:space-between;
            gap:var(--sp-4);flex-wrap:wrap;margin:0 0 var(--sp-4)}
.files-head h2{margin:0}
.files-add{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap}
.files-add .hint{margin:0}
/* The real control is the <label> beside it, styled as a button. The input
   keeps a place in the layout rather than display:none, so it keeps its place
   in the tab order and stays operable from the keyboard. */
.files-add input[type=file]{width:1px;height:1px;opacity:0;flex:0 0 auto}

/* The whole panel is the drop target, so there is no box to draw. */
.drop-over{border-color:var(--c-lime-edge);background:var(--c-lime-dim)}

/* One row per file: preview, name, facts, remove. A grid rather than flex,
   because the names are ragged and the sizes and dates have to line up
   underneath each other to be worth showing at all. */
.file{display:grid;grid-template-columns:var(--thumb) minmax(0,1fr) auto auto;
      align-items:center;gap:var(--sp-2) var(--sp-4);
      padding:var(--sp-2) 0;border-top:1px solid var(--c-line-soft)}
.file:first-of-type{border-top:none}
.file-thumb{grid-row:span 2;display:flex;align-items:center;justify-content:center;
            width:var(--thumb);height:var(--thumb);overflow:hidden;
            background:var(--c-raise);border:1px solid var(--c-line);
            border-radius:var(--radius-sm);text-decoration:none}
.file-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.file-glyph{font-size:var(--fs-xs);font-weight:600;letter-spacing:.04em;
            color:var(--c-ink-3)}
.file-name{grid-column:2;color:var(--c-ink);text-decoration:none;
           overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.file-name:hover{color:var(--c-lime-2);text-decoration:underline}
.file-meta{grid-column:2;grid-row:2;color:var(--c-ink-3);font-size:var(--fs-xs)}
/* Spans both rows so it sits against the middle of the row, not the first
   line of it. */
.file-remove{grid-column:4;grid-row:span 2;background:none;border:none;
             padding:var(--sp-1) var(--sp-3);color:var(--c-ink-4);
             font-size:var(--fs-lg);line-height:1;cursor:pointer;
             border-radius:var(--radius-sm)}
.file-remove:hover{color:var(--c-warn);background:var(--c-hover)}

/* One line per file in flight. A sibling of the panel, because the panel
   replaces itself when the files change and would take the failures with it. */
#file-progress:empty{display:none}
.upload{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;
        margin:var(--sp-2) 0 0;font-size:var(--fs-sm)}
.upload-name{color:var(--c-ink-2)}
.upload-bar{flex:1 1 var(--jump-w);height:var(--sp-1);background:var(--c-raise);
            border-radius:var(--radius-pill);overflow:hidden}
.upload-fill{display:block;width:0;height:100%;background:var(--c-lime-2)}
.upload-failed .upload-say{color:var(--c-warn)}
