/* ════════════════════════════════════════════════════════════
   KONTOUR — Banavat India

   Beige canvas, pine structure, one scarce lime accent.

   Three rules this sheet holds to:
   1. Glass floats, it never carries text. Frosted surfaces are for
      chrome that passes over content — the top bar, the tab bar,
      the sticky day headings. Every block of readable detail sits
      on an opaque surface.
   2. Gradients give depth, never noise. They run light-to-dark
      along one axis so surfaces read as lit from above, and they
      never travel far enough to change the contrast under text.
   3. Motion explains, it does not perform. Things enter from where
      they came from, numbers count so you see them change, and the
      whole system stops dead under prefers-reduced-motion.
   ════════════════════════════════════════════════════════════ */

/* ── Kontour: the workspace ──────────────────────────────────
   Monochrome by design. Kontour is the shell every module sits
   in, so it carries no hue of its own — black, shades of black,
   white, shades of white, and a neutral pulled a few degrees
   cool so it reads as chosen rather than inherited.

   Colour in this app means "you are somewhere". Phynance is
   green; the next module will be its own. The shell stays
   quiet so the module you are in is the thing you notice.
   ─────────────────────────────────────────────────────────── */

:root {
  /* ── Shades of black ─────────────────────────────────────── */
  --k-950:#050506;
  --k-900:#0B0B0D;
  --k-850:#131316;   /* primary dark surface */
  --k-800:#1B1B1F;
  --k-700:#26262B;
  --k-600:#35353C;
  --k-500:#55555E;
  --k-400:#7C7C86;
  --k-300:#9A9AA2;

  /* ── Shades of white ─────────────────────────────────────── */
  --k-200:#C9C9CF;
  --k-150:#DFDFE3;
  --k-100:#E9E9EC;
  --k-075:#F1F1F3;
  --k-050:#F7F7F8;
  --k-000:#FFFFFF;

  /* The pine ramp keeps its names so every component below goes
     on addressing one ramp; only what the names resolve to
     changes when a module takes over. */
  --pine-900:var(--k-950);
  --pine-800:var(--k-900);
  --pine-700:var(--k-850);
  --pine-600:var(--k-800);
  --pine-500:var(--k-700);
  --pine-400:var(--k-500);
  --pine-300:var(--k-300);
  --pine-200:var(--k-200);
  --pine-100:var(--k-100);
  --pine-050:var(--k-050);

  /* Accent. In the shell the accent is simply black — the one
     black pill on a white page, exactly one per screen. */
  --lime:#FFFFFF;            /* accent as text, on a dark ground */
  --lime-lit:#FFFFFF;
  --lime-deep:var(--k-900);  /* accent as a small mark on a light ground */
  --on-accent:#FFFFFF;       /* text riding on an accent surface */
  --accent-glow:rgba(11,11,13,.30);
  --accent-glow-soft:rgba(11,11,13,.14);
  --tea:var(--k-100);
  --tea-soft:var(--k-075);

  /* Canvas. Warm, not grey: a workshop's paperwork sits on cream,
     and against cream a white card actually reads as a card. */
  --page:#FAF6EF;
  --paper:#FFFFFF;
  --line:#E7E1D6;
  --line-soft:#F0EBE1;

  /* ── The line's own colours ────────────────────────────────
     One soft tint per station, so a piece's place on the floor is
     legible before a word of it is read. Text always rides on the
     -ink pair, never on the tint itself. */
  --st-quote:#EDE4FA;    --st-quote-ink:#5B3FA6;
  --st-prod:#FBE7A6;     --st-prod-ink:#7A5B08;
  --st-sub:#FBDCE7;      --st-sub-ink:#9C3A62;
  --st-qc:#D8E7FB;       --st-qc-ink:#245287;
  --st-ship:#D5EEDF;     --st-ship-ink:#1C6B4A;
  --st-done:#E7E4DE;     --st-done-ink:#55555E;

  --ink:#0B0B0D;
  --ink-2:#55555E;
  --ink-3:#8A8A93;

  /* Semantic money. Kept as real colour even in the monochrome
     shell: an accounting app has to separate in from out at a
     glance, and two greys cannot do that. They are desaturated
     to sit quietly against black and white. */
  --in:#1C6B4A;
  --in-soft:#E7F1EB;
  --out:#9C4A2C;
  --out-soft:#F6E8E1;
  --clay-lit:#E5A98A;
  --warn:#8A6A22;
  --warn-soft:#F5EEDD;

  /* ── Gradients ─────────────────────────────────────────────
     Light-to-dark along one axis, never far enough to change the
     contrast under text. */
  --grad-pine:linear-gradient(163deg, #202026 0%, #131316 42%, #050506 100%);
  --grad-pine-deep:linear-gradient(168deg, #131316 0%, #08080A 55%, #030304 100%);
  --grad-lime:linear-gradient(145deg, #2A2A31 0%, #131316 52%, #050506 100%);
  --grad-card:linear-gradient(176deg, #FFFFFF 0%, #FAFAFB 100%);
  --grad-tile-in:linear-gradient(145deg, #2E8A63 0%, #1C6B4A 100%);
  --grad-tile-out:linear-gradient(145deg, #C4744F 0%, #9C4A2C 100%);
  --grad-tile-pine:linear-gradient(145deg, #2A2A31 0%, #131316 100%);
  --grad-tile-tea:linear-gradient(145deg, #F7F7F8 0%, #E9E9EC 100%);

  /* ── Glass — chrome only, never under body text ───────────── */
  --glass-light:rgba(255,255,255,.72);
  --glass-dark:rgba(8,8,10,.62);
  --glass-edge:rgba(11,11,13,.09);
  --blur:saturate(150%) blur(20px);

  /* ── Elevation — neutral, cool-tinted, never a flat grey box ── */
  --e1:0 1px 2px rgba(11,11,13,.05), 0 2px 8px -2px rgba(11,11,13,.06);
  --e2:0 2px 4px rgba(11,11,13,.05), 0 8px 20px -6px rgba(11,11,13,.10);
  --e3:0 4px 8px rgba(11,11,13,.06), 0 18px 38px -10px rgba(11,11,13,.20);
  --shadow:var(--e1);
  --shadow-lift:var(--e3);

  /* ── Shape ─────────────────────────────────────────────────── */
  --r-lg:26px;
  --r-md:20px;
  --r-sm:14px;

  /* ── Motion ────────────────────────────────────────────────── */
  --ease:cubic-bezier(.22,.8,.24,1);      /* decelerating, natural */
  --ease-back:cubic-bezier(.34,1.4,.5,1); /* slight overshoot */
  --t-fast:.16s;
  --t-mid:.32s;
  --t-slow:.52s;

  /* Channel tokens. Every translucent tint in the sheet is mixed
     from these, so a module swap carries the tints with it. */
  /* Text and marks riding on a dark surface. Kept under its
     original name because seventeen rules already ask for it. */
  --beige:#FFFFFF;
  --on-accent-rgb:255,255,255;
  --hero-glow-rgb:120,120,132;

  --accent-rgb:255,255,255;
  --accent-deep-rgb:11,11,13;
  --paper-rgb:255,255,255;
  --ink-rgb:11,11,13;
  --shade-rgb:5,5,6;

  /* ── The fluid scale ──────────────────────────────────────────
     One ramp, no breakpoints. Every step is a clamp() between the
     size the design wants on the narrowest phone and the size it
     wants on the widest desktop, interpolated on viewport width, so
     type and spacing move continuously with the window instead of
     jumping at four chosen numbers and needing a hand-written
     correction at each one.

     The middle term is always `<rem> + <vw>` and never plain vw:
     the rem half keeps the ramp honest when the reader raises their
     browser's font size, which a pure-vw ramp silently ignores. */
  --step--2:clamp(.5625rem, .53rem + .16vw, .6875rem);
  --step--1:clamp(.6875rem, .65rem + .19vw, .8125rem);
  --step-0:clamp(.78125rem, .74rem + .21vw, .9375rem);
  --step-1:clamp(.875rem, .82rem + .27vw, 1.0625rem);
  --step-2:clamp(.9375rem, .87rem + .33vw, 1.1875rem);
  --step-3:clamp(1.0625rem, .96rem + .5vw, 1.4375rem);
  --step-4:clamp(1.25rem, 1.06rem + .93vw, 1.9375rem);
  --step-5:clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem);

  /* Spacing off the same ramp, so a gap and the type beside it
     grow together rather than drifting apart. */
  --sp-3xs:clamp(.25rem, .23rem + .1vw, .3125rem);
  --sp-2xs:clamp(.375rem, .34rem + .16vw, .5rem);
  --sp-xs:clamp(.5rem, .45rem + .24vw, .6875rem);
  --sp-sm:clamp(.6875rem, .62rem + .31vw, .875rem);
  --sp-md:clamp(.875rem, .78rem + .44vw, 1.25rem);
  --sp-lg:clamp(1.125rem, .97rem + .69vw, 1.75rem);
  --sp-xl:clamp(1.5rem, 1.24rem + 1.13vw, 2.5rem);

  /* The smallest a finger can reliably hit. Every control below
     uses it as a *floor* via min-height, never as a fixed height,
     so a control whose label wraps grows instead of clipping. */
  --tap:2.75rem;
  --fab-size:clamp(3.25rem, 3rem + 1.2vw, 4rem);

  /* Chrome. These are the size the bars *want* to be; because the
     shell is a grid, each one is a row that takes what its content
     actually needs, and nothing else has to subtract them. */
  --tabbar-h:clamp(3.25rem, 3rem + 1vw, 4rem);
  --topbar-h:clamp(2.75rem, 2.5rem + 1vw, 3.5rem);
  --brandbar-h:clamp(2.25rem, 2rem + 1vw, 3rem);
  --rail-w:clamp(13rem, 11rem + 8vw, 17rem);
  --safe-b:env(safe-area-inset-bottom, 0px);
  --safe-t:env(safe-area-inset-top, 0px);
}

/* ── Phynance: the money module ──────────────────────────────
   The one place the original brand lives on. Nothing below the
   token layer changes — every component already reads the ramp,
   so entering Phynance re-points the ramp and the whole module
   turns green in one step.

   Pine #00311F · Emerald Pine #084734 · Lime Glow #CEF17B
   ─────────────────────────────────────────────────────────── */

:root[data-section="phynance"] {
  --pine-900:#001A10;
  --pine-800:#00311F;
  --pine-700:#084734;
  --pine-600:#0D5B42;
  --pine-500:#1E7357;
  --pine-400:#4E9578;
  --pine-300:#8ABBA1;
  --pine-200:#B9D8C4;
  --pine-100:#DDEDE2;
  --pine-050:#EFF7F1;

  --lime:#CEF17B;
  --lime-lit:#E2FAA6;
  --lime-deep:#A8D442;
  --on-accent:#00311F;
  --accent-glow:rgba(168,212,66,.70);
  --accent-glow-soft:rgba(206,241,123,.35);
  --beige:#FFF7E6;
  --on-accent-rgb:0,49,31;
  --hero-glow-rgb:30,115,87;

  --accent-rgb:206,241,123;
  --accent-deep-rgb:168,212,66;
  --paper-rgb:255,247,230;
  --ink-rgb:0,49,31;
  --shade-rgb:0,26,16;

  --tea:#CDEDB3;
  --tea-soft:#EAF6DC;

  /* The canvas warms a few degrees. Beige is the module's ground,
     but Kontour's chrome frames it, so it stops short of the
     original #FFF7E6 — enough to feel like somewhere else, not
     enough to fight the shell around it. */
  --page:#FBF6EC;
  --paper:#FFFFFF;
  --line:#EAE1D0;
  --line-soft:#F2EBDC;

  --ink:#00311F;
  --ink-2:#3E5A4B;
  --ink-3:#5F7568;

  --in:#0B6B45;
  --in-soft:#E4F3DC;
  --out:#A8502C;
  --out-soft:#F8E7DB;
  --clay-lit:#EBAC84;
  --warn:#A97A28;
  --warn-soft:#F8EEDA;

  --grad-pine:linear-gradient(163deg, #0B5741 0%, #084734 42%, #00281A 100%);
  --grad-pine-deep:linear-gradient(168deg, #084734 0%, #00281A 55%, #001A10 100%);
  --grad-lime:linear-gradient(145deg, #E2FAA6 0%, #CEF17B 52%, #B2DC52 100%);
  --grad-card:linear-gradient(176deg, #FFFFFF 0%, #FFFBF2 100%);
  --grad-tile-in:linear-gradient(145deg, #23A874 0%, #0B6B45 100%);
  --grad-tile-out:linear-gradient(145deg, #DA7E4C 0%, #A8502C 100%);
  --grad-tile-pine:linear-gradient(145deg, #14785A 0%, #084734 100%);
  --grad-tile-tea:linear-gradient(145deg, #F2FBE6 0%, #DCEFC4 100%);

  --glass-light:rgba(255,247,230,.74);
  --glass-dark:rgba(0,32,20,.62);
  --glass-edge:rgba(0,49,31,.09);

  --e1:0 1px 2px rgba(0,49,31,.05), 0 2px 8px -2px rgba(0,49,31,.06);
  --e2:0 2px 4px rgba(0,49,31,.05), 0 8px 20px -6px rgba(0,49,31,.10);
  --e3:0 4px 8px rgba(0,49,31,.06), 0 18px 38px -10px rgba(0,49,31,.18);
}

* { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }

html, body {
  margin:0; padding:0;
  /* Plain 100% until dvh is known to be supported (below) or, on a
     browser that lacks it, until app.js measures the real visible
     height into --app-h. Never the other way around: a JS height
     that overrides a dvh-capable browser is what caused the dead
     strip under the tab bar on iOS Safari — its dvh already tracks
     the safe area and the disappearing toolbar correctly, and the
     one time this measured it independently it measured shorter than
     the real screen and pinned that undersized height permanently. */
  height:var(--app-h, 100%);
  color:var(--ink);
  font-family:"Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Arial, sans-serif;
  /* 100%, not a px size: this is the one place a hard number would
     override the reader's own browser setting, and every rem in the
     sheet is measured from here. */
  font-size:100%;
  line-height:1.45;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overscroll-behavior-y:none;
}
/* The canvas is not flat beige — two very wide, very faint washes give
   it depth without ever getting strong enough to sit under text. */
body {
  overflow:hidden;
  background:
    radial-gradient(120% 60% at 88% 0%, rgba(var(--accent-rgb), .13), transparent 60%),
    radial-gradient(110% 55% at 0% 100%, rgba(8,71,52,.055), transparent 62%),
    var(--page);
}

button { font:inherit; color:inherit; border:0; background:none; cursor:pointer; }
input, select, textarea { font:inherit; color:inherit; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }

.num { font-variant-numeric:tabular-nums; font-feature-settings:"tnum"; }
[hidden] { display:none !important; }

/* ── Motion primitives ────────────────────────────────────────
   Blocks enter from just below where they belong, staggered by
   position, so a screen assembles in reading order.             */
[data-rise] {
  opacity:0;
  transform:translate3d(0,16px,0);
  will-change:opacity, transform;   /* released by motion.js once shown */
}
[data-rise].shown {
  opacity:1;
  transform:none;
  transition:opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
}

@keyframes fade { from{opacity:0} to{opacity:1} }
@keyframes rise { from{transform:translateY(100%)} to{transform:translateY(0)} }
@keyframes pop  { 0%{transform:scale(.86)} 60%{transform:scale(1.06)} 100%{transform:scale(1)} }
@keyframes shimmer { from{transform:translateX(-120%)} to{transform:translateX(240%)} }
@keyframes breathe { 0%,100%{opacity:.45; transform:scale(1)} 50%{opacity:.85; transform:scale(1.1)} }
@keyframes drawCheck { to{stroke-dashoffset:0} }
@keyframes ringOut { from{transform:scale(.55);opacity:.8} to{transform:scale(1.9);opacity:0} }
@keyframes rippleOut { to{transform:scale(1);opacity:0} }
@keyframes shake {
  0%,100%{transform:translateX(0)} 20%{transform:translateX(-9px)}
  40%{transform:translateX(9px)} 60%{transform:translateX(-5px)} 80%{transform:translateX(5px)}
}

/* One switch turns the whole system off. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
  [data-rise] { opacity:1 !important; transform:none !important; }
}

/* ── PIN gate ─────────────────────────────────────────────── */
.gate {
  position:fixed; inset:0; z-index:200;
  background:var(--grad-pine-deep);
  display:flex; align-items:center; justify-content:center;
  padding:var(--sp-lg); overflow:hidden;
}
.gate::before {
  content:""; position:absolute; inset:-30% -10% auto -10%; height:90%;
  background:radial-gradient(50% 50% at 50% 0%, rgba(var(--accent-rgb), .20), transparent 70%);
  pointer-events:none;
}
.gate-inner { width:100%; max-width:21.25rem; text-align:center; position:relative; z-index:1; }
/* The `vh` term is what used to be the short-landscape rule: in a
   window with no height to spare the mark shrinks on its own, and
   there is nothing to keep in step by hand. Same idea for the
   title, the gaps below, and the keys. */
.gate-mark {
  inline-size:clamp(3rem, 2rem + 5vh, 4rem); aspect-ratio:1;
  margin:0 auto var(--sp-md);
  border-radius:22px;
  background:linear-gradient(150deg, rgba(var(--accent-rgb), .22), rgba(var(--accent-rgb), .07));
  border:1px solid rgba(var(--accent-rgb), .32);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18), 0 10px 26px -8px var(--accent-glow-soft);
  display:grid; place-items:center;
  font-size:clamp(1.4375rem, 1rem + 2.2vh, 1.8125rem); color:var(--lime);
  animation:pop .6s var(--ease-back) both;
}
.gate-title {
  margin:0; font-weight:600; color:var(--beige); letter-spacing:.2px;
  font-size:clamp(1.375rem, 1rem + 2.4vh, 1.6875rem);
}
.gate-sub {
  margin:var(--sp-2xs) 0 clamp(1rem, .4rem + 3vh, 1.625rem);
  font-size:var(--step-1); color:var(--pine-200);
}
.gate-sub.err { color:var(--lime); }

.pin-dots {
  display:flex; gap:var(--sp-sm); justify-content:center;
  margin-bottom:clamp(1.25rem, .4rem + 4vh, 2.125rem);
}
.pin-dots i {
  width:13px; height:13px; border-radius:50%;
  background:transparent; border:1.5px solid rgba(var(--accent-rgb), .42);
  transition:background var(--t-fast), transform var(--t-fast) var(--ease-back), box-shadow var(--t-fast);
}
.pin-dots i.on {
  background:var(--lime); border-color:var(--lime); transform:scale(1.18);
  box-shadow:0 0 14px var(--accent-glow-soft);
}
.pin-dots.shake { animation:shake .4s; }

.numpad { display:grid; grid-template-columns:repeat(3,1fr); gap:var(--sp-sm); }
.numpad button {
  /* min-height, so a key is never shorter than a finger even when
     the `vh` term bottoms out in a short window. */
  min-height:clamp(2.75rem, 1.5rem + 6vh, 4.125rem);
  border-radius:20px;
  font-size:clamp(1.1875rem, .9rem + 1.4vh, 1.4375rem); font-weight:500;
  background:linear-gradient(160deg, rgba(var(--paper-rgb), .14), rgba(var(--paper-rgb), .06));
  border:1px solid rgba(var(--paper-rgb), .10);
  color:var(--beige);
  transition:background var(--t-fast), transform .08s var(--ease);
}
.numpad button:active { background:rgba(var(--accent-rgb), .24); transform:scale(.94); }
.numpad button.ghost { background:none; border:0; font-size:var(--step-0); color:var(--pine-200); }

.bio-btn {
  display:flex; align-items:center; justify-content:center; gap:9px;
  width:100%; margin-top:18px; padding:13px;
  border-radius:16px;
  background:linear-gradient(160deg, rgba(var(--accent-rgb), .16), rgba(var(--accent-rgb), .06));
  border:1px solid rgba(var(--accent-rgb), .28);
  color:var(--lime); font-size:var(--step-0); font-weight:600;
  transition:background var(--t-fast), transform .1s var(--ease);
}
.bio-btn:active { transform:scale(.97); background:rgba(var(--accent-rgb), .22); }

/* ── Sign in ──────────────────────────────────────────────────
   The same door as the PIN gate, so the two screens that stand in
   front of the books read as one thing rather than two. */
.gate-inner.auth { max-width:380px; text-align:left; }
.gate-inner.auth .gate-mark,
.gate-inner.auth .gate-title { margin-inline:auto; text-align:center; }
.gate-inner.auth .gate-sub { text-align:center; }

/* The one line of explanation on the door: who signs in with what.
   Quieter than the sub-heading, and it reads as prose, not a label. */
.gate-note {
  margin:18px 0 0; text-align:center;
  font-size:var(--step--1); line-height:1.5; color:var(--pine-200);
}

.auth-form { display:flex; flex-direction:column; gap:13px; }
.auth-f { display:block; }
.auth-f span {
  display:block; margin-bottom:6px;
  font-size:var(--step--1); font-weight:600; letter-spacing:.2px;
  color:var(--pine-200);
}
/* Fields on pine. The light .control would sit on this ground as a
   white slab; this keeps the surface dark and the focus ring lime. */
.control.dark {
  background:rgba(var(--paper-rgb), .09);
  border:1px solid rgba(var(--paper-rgb), .14);
  color:var(--beige);
}
.control.dark::placeholder { color:rgba(var(--paper-rgb), .38); }
.control.dark:focus {
  border-color:var(--lime);
  box-shadow:0 0 0 3px rgba(var(--accent-rgb), .18);
}
.auth-form .btn { margin-top:4px; }

.auth-err {
  font-size:var(--step--1); line-height:1.45;
  color:var(--pine-800);
  background:var(--clay-lit);
  border-radius:12px; padding:10px 13px;
  animation:pop .3s var(--ease-back);
}

.auth-alt {
  display:flex; justify-content:center; gap:18px; flex-wrap:wrap;
  margin-top:18px;
}
.auth-alt button {
  font-size:var(--step-0); font-weight:600; color:var(--lime);
  padding:6px 2px;
}

.auth-orgs { display:flex; flex-direction:column; gap:9px; margin-top:4px; }
.auth-org {
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:15px; border-radius:17px; text-align:left;
  background:linear-gradient(160deg, rgba(var(--paper-rgb), .12), rgba(var(--paper-rgb), .05));
  border:1px solid rgba(var(--paper-rgb), .1);
  color:var(--beige);
  transition:background var(--t-fast), transform .12s var(--ease);
}
.auth-org:active { transform:scale(.97); }
.auth-org-n { font-size:var(--step-0); font-weight:600; }
.auth-org-r {
  font-size:var(--step--2); font-weight:700; letter-spacing:.7px;
  text-transform:uppercase; color:var(--lime);
}

@media (hover:hover) and (pointer:fine) {
  .auth-org:hover { background:rgba(var(--accent-rgb), .16); }
  .auth-alt button:hover { text-decoration:underline; }
}

/* ── App shell ────────────────────────────────────────────────
   The whole shell is one grid, and that is the only thing holding
   the pieces apart. A rail column on the left, and three rows down
   the right of it: brand bar, the work, tab bar. The glass bar and
   the entry button take no row of their own — they share the
   work's cell, because both are meant to lie over it.

   Nothing here is positioned. That matters more than it sounds:
   while the bars were `fixed` and the scroller was `absolute`,
   every one of them had to be told the height of the others —
   `padding-top:var(--brandbar-h)`, `left:var(--rail-w)`,
   `padding-bottom:calc(var(--tabbar-h) + var(--safe-b) + 92px)` —
   and each of those numbers was a promise that had to be re-made
   by hand at every breakpoint. A grid track measures its own
   content, so a bar that grows pushes the work down instead of
   silently sitting on top of it, and there is no number to keep.

   The rail track is 0 until there is room for a rail, so the same
   three lines describe the phone and the desktop. */
.app {
  height:var(--app-h, 100%);
  display:grid;
  grid-template-columns:var(--rail-track, 0px) minmax(0, 1fr);
  grid-template-rows:auto minmax(0, 1fr) auto;
  grid-template-areas:
    "rail brandbar"
    "rail screen"
    "rail tabbar";
  overflow:hidden;
}
/* The work itself: the one thing in the shell that scrolls.
   `minmax(0,1fr)` above is what lets it — a bare `1fr` floors at
   the content's own height, and the row would grow past the
   viewport instead of the pane scrolling inside it. */
.screen {
  grid-area:screen;
  min-height:0;
  overflow-y:auto; overflow-x:hidden;
  -webkit-overflow-scrolling:touch;
  /* The only reserve left, and it is the button's own size rather
     than a sum of everything below it: the entry button floats over
     this corner, so the last row has to be able to clear it. The
     tab bar is a row of its own now and no longer part of this. */
  padding-bottom:calc(var(--fab-size) + var(--sp-xl));
}

/* ── Sticky brand header ──────────────────────────────────────
   The one piece of Kontour that never scrolls, is never a screen's
   own hero, and never changes: the mark and the name of the app
   itself. On a phone, where the rail's own wordmark has no home,
   this is that home instead. */
.brandbar {
  grid-area:brandbar; z-index:30;
  display:flex; align-items:center; gap:var(--sp-xs);
  /* min-height, not height: the bar is as tall as the safe area plus
     its own row, and if the mark or the word ever grows it takes the
     room rather than cropping itself. */
  min-height:calc(var(--safe-t) + var(--brandbar-h));
  padding:var(--safe-t) var(--sp-md) 0;
  background:var(--pine-800);
  border-bottom:1px solid rgba(var(--accent-rgb), .14);
}
.brandbar-logo {
  width:clamp(1.25rem, 1.1rem + .5vw, 1.75rem); height:auto; aspect-ratio:1;
  border-radius:6px; flex:none; object-fit:cover;
}
.brandbar-word { font-size:var(--step-1); font-weight:800; letter-spacing:.6px; color:var(--beige); }
.screen::-webkit-scrollbar { width:0; height:0; }

/* ── Floating glass top bar ───────────────────────────────────
   Appears only once the hero has scrolled away, so the screen
   title and its headline figure are never lost. Because it passes
   over content, it is the right place for glass. */
.topbar {
  /* Shares the work's cell rather than owning a row of its own, and
     sits at the top of it. A row would hold its height open even
     while the bar is hidden, and the screen would open on a gap; in
     the same cell it lies over the work exactly as glass should,
     and the rail column insets it for free — no `left` to keep in
     step with the rail's width. */
  grid-area:screen; align-self:start; z-index:20;
  padding:var(--sp-sm) var(--sp-md);
  background:var(--glass-dark);
  -webkit-backdrop-filter:var(--blur);
  backdrop-filter:var(--blur);
  border-bottom:1px solid rgba(var(--accent-rgb), .14);
  transform:translate3d(0,-102%,0);
  opacity:0;
  transition:transform var(--t-mid) var(--ease), opacity var(--t-mid) var(--ease);
  pointer-events:none;
}
.topbar.on { transform:none; opacity:1; pointer-events:auto; }
.topbar-in {
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-sm);
  max-width:var(--content-w); margin-inline:auto;
}
.topbar-t {
  font-size:var(--step-1); font-weight:600; color:var(--beige);
  letter-spacing:.2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.topbar-v { font-size:var(--step-2); font-weight:700; color:var(--lime); white-space:nowrap; }
.topbar-v .cur { font-size:var(--step--1); font-weight:500; opacity:.8; margin-right:1px; }
.topbar-v small { display:block; font-size:var(--step--2); font-weight:600; letter-spacing:.7px; color:var(--pine-300); text-align:right; }

@supports not ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))) {
  .topbar { background:var(--pine-800); }
}

/* ── Hero ─────────────────────────────────────────────────────
   The deep pine band every screen opens with. Two soft radial
   washes keep it from reading as a flat rectangle. */
.hero {
  --p:0;                                   /* scroll progress, set by app.js */
  position:relative; overflow:hidden;
  background:var(--grad-pine);
  color:var(--beige);
  border-radius:0 0 clamp(2rem, 1.6rem + 1.6vw, 2.375rem) clamp(2rem, 1.6rem + 1.6vw, 2.375rem);
  /* The top term carries a `vh` so the band gives ground in a short
     window without a query, and the sides ride the space ramp so the
     hero stays in step with the panel and section below it. */
  padding:calc(var(--safe-t) + clamp(.5rem, .1rem + 1.4vh, 1.625rem)) var(--sp-lg) clamp(.75rem, .4rem + 1.2vh, 1.875rem);
}
.hero::before {
  content:""; position:absolute; inset:-55% -25% auto -25%; height:170%;
  background:radial-gradient(52% 48% at 20% 2%, rgba(var(--accent-rgb), .22), transparent 68%);
  pointer-events:none;
}
.hero::after {
  content:""; position:absolute; right:-14%; bottom:-58%; width:76%; height:150%;
  background:radial-gradient(closest-side, rgba(var(--hero-glow-rgb), .5), transparent 72%);
  pointer-events:none;
}
.hero.with-panel { border-radius:0; padding-bottom:clamp(1rem, .6rem + 1.4vh, 2.625rem); }
/* A screen that is mostly a list wants its band to name itself and
   then get out of the way: the same hero, set on a tighter rhythm,
   so the rows start higher up the window. */
.hero.compact {
  border-radius:0 0 var(--r-md) var(--r-md);
  padding-top:calc(var(--safe-t) + var(--sp-xs));
  padding-bottom:var(--sp-xs);
}
.hero.compact .hero-bar { margin-bottom:0; }
.hero.compact .hero-title { font-size:var(--step-1); }
.hero.compact .hero-title small { font-size:var(--step--2); }
.hero > * { position:relative; z-index:1; }

/* Hero content lifts and fades as the glass bar takes over — one
   crossfade, rather than two headers competing. */
.hero-bar, .stat-row {
  transform:translate3d(0, calc(var(--p) * -14px), 0);
  opacity:calc(1 - var(--p) * .92);
}

.hero-bar {
  display:flex; align-items:center; justify-content:space-between; gap:var(--sp-sm);
  flex-wrap:wrap;
  margin-bottom:clamp(.625rem, .3rem + 1.2vh, 1.125rem);
}
.hero-title { font-size:var(--step-2); font-weight:600; letter-spacing:.2px; min-width:0; }
.hero-title small {
  display:block; font-size:var(--step--1); font-weight:400;
  color:var(--pine-200); letter-spacing:.3px; margin-top:1px;
}
.icon-btn {
  inline-size:clamp(2rem, 1.8rem + .8vw, 2.5rem); aspect-ratio:1;
  border-radius:12px; flex:none;
  display:grid; place-items:center; color:var(--beige);
  background:rgba(var(--paper-rgb), .07);
  border:1px solid rgba(var(--paper-rgb), .09);
  transition:background var(--t-fast), transform .1s var(--ease);
}
.icon-btn:active { background:rgba(var(--accent-rgb), .2); transform:scale(.92); }
.icon-btn.plain { color:var(--ink-2); background:var(--tea-soft); border-color:transparent; }
/* A sheet header offering more than one action — the document view's
   print and edit buttons — needs the same row layout .sheet-head
   itself already uses for its three slots. */
.sheet-head-acts { display:flex; align-items:center; gap:8px; }

/* three-up stat row. Grid rather than flex, and every track
   `minmax(0,1fr)`: three equal columns that a long figure narrows
   the type inside rather than bursting out of. `flex:1` floors at
   the content's width and the third stat used to push past the
   hero's edge on a compact phone. */
.stat-row {
  display:grid; grid-template-columns:repeat(3, minmax(0, 1fr));
  align-items:start;
}
.stat { text-align:center; padding:0 var(--sp-3xs); position:relative; min-width:0; }
.stat + .stat::before {
  content:""; position:absolute; left:0; top:6px; bottom:6px;
  width:1px; background:linear-gradient(180deg, transparent, rgba(var(--accent-rgb), .26), transparent);
}
.stat-ico { color:var(--pine-200); margin-bottom:2px; display:block; }
/* One ramp instead of the four sizes this used to be given — 18px
   on a phone, 26 at 600, 28 at 900, 20 in a short window — each of
   which had to be re-judged whenever the hero changed. */
.stat-val {
  font-size:clamp(1.125rem, .85rem + 1.35vw, 1.75rem);
  font-weight:700; letter-spacing:-.5px;
}
.stat-val .cur { font-size:var(--step--1); font-weight:500; opacity:.72; margin-right:1px; }
.stat-val.pos { color:var(--lime); text-shadow:0 0 22px var(--accent-glow-soft); }
.stat-val.neg { color:var(--clay-lit); }
.stat-lbl {
  font-size:var(--step--2); color:var(--pine-300);
  letter-spacing:.8px; margin-top:1px; font-weight:600;
}

/* ── Panel ────────────────────────────────────────────────────
   The tray rises over the pine band, full width, so no corner of
   the band is left poking out beside it. */
.panel {
  background:var(--page);
  border-radius:30px 30px 0 0;
  margin:-24px 0 0;
  padding:var(--sp-lg) var(--sp-md) var(--sp-3xs);
  position:relative;
  box-shadow:0 -14px 30px -18px rgba(var(--shade-rgb), .35);
}
.seg { display:flex; justify-content:center; margin-bottom:var(--sp-lg); }
.seg button {
  flex:1 1 0; min-width:0; max-width:9.375rem;
  min-height:var(--tap);
  padding:var(--sp-2xs) var(--sp-3xs);
  font-size:var(--step-1); color:var(--ink-3); font-weight:500;
  position:relative; text-align:center;
  transition:color var(--t-fast);
}
.seg button.on { color:var(--ink); font-weight:700; }
/* An underline under the word it belongs to, rather than a dot
   marooned below it — at this size a dot reads as a stray mark. */
.seg button.on::after {
  content:""; position:absolute; left:50%; bottom:0;
  width:26px; height:3px; margin-left:-13px;
  border-radius:2px; background:var(--lime-deep);
  box-shadow:0 0 10px var(--accent-glow);
  animation:pop .34s var(--ease-back);
}

/* The card grids ask for a comfortable card width and let the
   browser work out how many fit. That one line replaces the ladder
   these used to climb — two columns, then 168px tracks at 600, 190
   at 1280, 210 at 1600, 240 somewhere else — where every rung was a
   guess about a window nobody had measured.

   `min(<track>, 100%)` is what keeps it safe below the track width:
   without it a 12rem minimum on a 320px phone overflows the row. */
.grid2 {
  display:grid; gap:var(--sp-sm);
  /* 8.5rem is chosen, not guessed: it is the widest minimum that
     still leaves two cards side by side inside a 320px phone, which
     is the density the pair was drawn at. Above that width the row
     simply takes more cards. */
  grid-template-columns:repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr));
}
.grid3 {
  display:grid; gap:var(--sp-xs);
  grid-template-columns:repeat(auto-fit, minmax(min(5rem, 100%), 1fr));
}
.grid3 .card { padding:13px 10px; }
.grid3 .card-ico { width:32px; height:32px; margin-bottom:9px; border-radius:11px; }
.grid3 .card-name { font-size:var(--step--1); }
.grid3 .card-val { font-size:var(--step-1); margin-top:6px; }

/* ── Cards ────────────────────────────────────────────────── */
.card {
  background:var(--grad-card);
  border-radius:22px;
  padding:var(--sp-md) var(--sp-sm);
  border:1px solid var(--line-soft);
  box-shadow:var(--e2);
  text-align:left;
  display:block; width:100%; min-width:0;
  position:relative; overflow:hidden;
  transition:transform .12s var(--ease), box-shadow var(--t-fast);
}
.card:active { transform:scale(.975); box-shadow:var(--e1); }

/* ── 3D icon tiles ────────────────────────────────────────────
   A gradient lit from the top-left, a hairline highlight along the
   top edge, a shade along the bottom, and a cast shadow tinted with
   the tile's own colour. That combination is what reads as a solid
   object — a bevel alone never does. */
.card-ico {
  inline-size:clamp(2.375rem, 2.2rem + .7vw, 2.625rem); aspect-ratio:1;
  margin-bottom:var(--sp-sm);
  display:grid; place-items:center; border-radius:14px;
  background:var(--grad-tile-pine); color:#fff;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.34),
    inset 0 -1px 0 rgba(0,0,0,.16),
    0 3px 8px -2px rgba(8,71,52,.42);
}
.card-ico svg { filter:drop-shadow(0 1px 1px rgba(0,0,0,.22)); }
.card-name { font-size:var(--step-2); font-weight:600; letter-spacing:-.1px; }
.card-sub { font-size:var(--step-0); color:var(--ink-3); margin-top:2px; }
.card-val { font-size:clamp(1.1875rem, 1rem + .9vw, 1.3125rem); font-weight:700; margin-top:var(--sp-2xs); letter-spacing:-.5px; }
.card-val.neg { color:var(--out); }

/* wide info strip */
.strip {
  display:flex; align-items:center; gap:13px;
  background:var(--grad-pine); color:var(--beige);
  border-radius:22px; padding:14px;
  margin-top:14px; width:100%; text-align:left;
  box-shadow:var(--e2);
  position:relative; overflow:hidden;
  transition:transform .12s var(--ease);
}
.strip:active { transform:scale(.98); }
.strip.accent {
  background:var(--grad-lime); color:var(--on-accent);
  box-shadow:0 6px 20px -6px var(--accent-glow);
}
/* A slow light sweep. The accent strip only ever appears when
   something actually needs doing, so it is allowed to ask. */
.strip.accent::after {
  content:""; position:absolute; top:0; bottom:0; width:34%;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.55), transparent);
  animation:shimmer 3.6s var(--ease) infinite;
  pointer-events:none;
}
.strip-ico {
  width:42px; height:42px; border-radius:15px; flex:none;
  background:rgba(var(--accent-rgb), .15);
  border:1px solid rgba(var(--accent-rgb), .16);
  display:grid; place-items:center;
}
.strip.accent .strip-ico {
  background:rgba(var(--on-accent-rgb), .15);
  border-color:rgba(var(--on-accent-rgb), .22);
}
.strip-txt { flex:1; min-width:0; }
.strip-t { font-size:var(--step-0); font-weight:600; }
.strip-s { font-size:var(--step--1); opacity:.82; margin-top:1px; }
.strip-go { opacity:.7; flex:none; }

/* ── Sections ─────────────────────────────────────────────── */
.sec { padding:var(--sp-lg) var(--sp-md) 0; }
.sec-head { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:11px; }
.sec-title {
  font-size:var(--step--1); font-weight:700; letter-spacing:1px;
  text-transform:uppercase; color:var(--ink-3);
}
.sec-link { font-size:var(--step--1); color:var(--pine-700); font-weight:600; }

.list {
  background:var(--paper); border-radius:var(--r-md);
  border:1px solid var(--line-soft); box-shadow:var(--e2); overflow:hidden;
}
.row {
  display:flex; align-items:center; gap:var(--sp-sm);
  padding:var(--sp-sm); width:100%; text-align:left;
  min-height:var(--tap);
  border-bottom:1px solid var(--line-soft);
  transition:background var(--t-fast);
  position:relative; overflow:hidden;
}
.row:last-child { border-bottom:0; }
.row:active { background:var(--tea-soft); }
.row-ico {
  inline-size:clamp(2.25rem, 2.1rem + .6vw, 2.5rem); aspect-ratio:1;
  border-radius:14px; flex:none;
  display:grid; place-items:center;
  background:var(--grad-tile-tea); color:var(--pine-700);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9), inset 0 -1px 0 rgba(8,71,52,.10), 0 2px 6px -2px rgba(8,71,52,.20);
}
.row-ico.in {
  background:var(--grad-tile-in); color:#fff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.36), inset 0 -1px 0 rgba(0,0,0,.16), 0 3px 8px -2px rgba(11,107,69,.42);
}
.row-ico.out {
  background:var(--grad-tile-out); color:#fff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.36), inset 0 -1px 0 rgba(0,0,0,.16), 0 3px 8px -2px rgba(168,80,44,.42);
}
.row-ico.tr {
  background:var(--grad-tile-pine); color:#fff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.30), inset 0 -1px 0 rgba(0,0,0,.16), 0 3px 8px -2px rgba(8,71,52,.42);
}
.row-ico.in svg, .row-ico.out svg, .row-ico.tr svg { filter:drop-shadow(0 1px 1px rgba(0,0,0,.22)); }
/* Both lines are spans, and an inline box cannot show an ellipsis —
   it just runs on until it hits the amount, or the edge of the phone.
   The column makes them blocks, which is what lets the truncation
   below actually happen. */
.row-txt { flex:1; min-width:0; display:flex; flex-direction:column; }
.row-t {
  display:block;
  font-size:var(--step-2); font-weight:600; letter-spacing:-.1px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* The second line carries the job code and the particulars — the
   detail most often squinted at. Raised from 11.5px to 12.5px and
   from the faintest ink to the mid tone. */
.row-s {
  display:block;
  font-size:var(--step-0); color:var(--ink-2); margin-top:2px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* The figure is the one thing that must never be shortened, so it
   keeps its width and the text beside it gives way instead. */
.row-amt {
  font-size:var(--step-2); font-weight:700; flex:none; letter-spacing:-.3px;
  white-space:nowrap; text-align:right;
}
.row-amt.in { color:var(--in); }
.row-amt.out { color:var(--out); }
.row-amt.tr { color:var(--ink-2); }
.row-go { color:var(--ink-3); flex:none; }

/* Day headings hold their place while their own entries scroll past, so
   you never lose track of which day you are reading. Each heading is
   bounded by its own .daygroup — without that, every heading in the month
   would stick at the same point and pile up on top of the last one.
   The offset clears the glass bar, which sits above them — and only
   that. The safe area used to be added here too, back when this
   stuck to a scroller that started at the top of the window; the
   brand bar is a grid row above it now and already holds that
   space, so counting it again pushed every heading a notch low. */
.daygroup { position:relative; }
.daybar {
  position:sticky; top:calc(var(--topbar-h) + var(--sp-2xs)); z-index:6;
  display:flex; justify-content:space-between; align-items:baseline;
  margin:var(--sp-lg) 0 var(--sp-2xs);
  padding:var(--sp-2xs) var(--sp-sm);
  border-radius:13px;
  font-size:var(--step--1); color:var(--ink-3);
  background:var(--glass-light);
  -webkit-backdrop-filter:var(--blur);
  backdrop-filter:var(--blur);
  border:1px solid var(--glass-edge);
}
.daybar b { font-size:var(--step--1); color:var(--ink); font-weight:700; }
@supports not ((backdrop-filter:blur(2px)) or (-webkit-backdrop-filter:blur(2px))) {
  .daybar { background:var(--page); }
}

.empty { text-align:center; padding:46px 24px; color:var(--ink-3); }
.empty-ico {
  width:64px; height:64px; margin:0 auto 14px;
  display:grid; place-items:center; border-radius:22px;
  background:var(--grad-tile-tea); color:var(--pine-600);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9), 0 6px 16px -6px rgba(8,71,52,.28);
}
.empty p { margin:0; font-size:var(--step-0); color:var(--ink-2); font-weight:600; }
.empty small { display:block; margin-top:6px; font-size:var(--step--1); color:var(--ink-3); }

/* A filter row longer than the screen keeps scrolling past the edge,
   so it fades out there rather than ending on a chip sliced flat —
   a hard cut reads as a bug, a fade reads as more to come. */
.chipbar {
  display:flex; gap:8px; overflow-x:auto; padding:2px 0 4px; scrollbar-width:none;
  scroll-padding:0 4px;
  overscroll-behavior-x:contain;
}
/* Set by app.js, and only on a row that really does run past its box —
   a row that fits has nothing to fade. */
.chipbar.overflows {
  -webkit-mask-image:linear-gradient(90deg, #000 calc(100% - 26px), transparent 100%);
  mask-image:linear-gradient(90deg, #000 calc(100% - 26px), transparent 100%);
}
.chipbar::-webkit-scrollbar { display:none; }
.chip {
  flex:none; display:inline-flex; align-items:center;
  min-height:2.25rem;
  padding:var(--sp-2xs) var(--sp-md); border-radius:999px;
  background:var(--paper); border:1px solid var(--line);
  font-size:var(--step-1); color:var(--ink-2); white-space:nowrap;
  transition:transform .1s var(--ease), background var(--t-fast), color var(--t-fast);
}
.chip:active { transform:scale(.94); }
.chip.on {
  background:var(--grad-tile-pine); border-color:transparent; color:var(--beige);
  font-weight:600; box-shadow:0 3px 10px -3px rgba(8,71,52,.5);
}

/* ── Tab bar + FAB ────────────────────────────────────────────
   Solid, not frosted. The sticky bars that share the glass token
   pass over content for a moment; this one is always there, and a
   row of the list showing through the two labels that carry the
   whole app does not read as glass, it reads as a bug. */
.tabbar {
  grid-area:tabbar; z-index:15;
  min-height:calc(var(--tabbar-h) + var(--safe-b));
  padding-bottom:var(--safe-b);
  background:var(--page);
  border-top:1px solid var(--glass-edge);
  display:flex; align-items:stretch; justify-content:space-around;
}
.tab {
  flex:1 1 0; min-width:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:var(--sp-3xs);
  padding:var(--sp-2xs) var(--sp-3xs);
  color:var(--ink-3); position:relative;
  transition:color var(--t-fast);
}
.tab svg { transition:transform var(--t-mid) var(--ease-back); }
.tab.on { color:var(--pine-700); }
.tab.on svg { transform:translateY(-2px) scale(1.08); }
.tab.on::after {
  content:""; position:absolute; bottom:11px;
  width:5px; height:5px; border-radius:50%; background:var(--lime-deep);
  box-shadow:0 0 8px var(--accent-glow);
  animation:pop .34s var(--ease-back);
}
.tab span { font-size:var(--step--2); letter-spacing:.2px; font-weight:500; }
/* The entry button is an action, not a destination, so it does not
   sit in the row of destinations — it floats clear of the bar at the
   thumb's corner, the way every list app puts its compose button. */
.fab {
  /* Placed in the work's cell and pinned to its far corner by the
     grid, not by an offset. The tab bar is a row below this cell, so
     the button cannot land on top of it however tall that bar grows
     — which is what the old `bottom:calc(tabbar + safe + 16px)` was
     trying to guarantee by arithmetic, and had to restate three
     times. */
  grid-area:screen; z-index:16;
  justify-self:end; align-self:end;
  margin:var(--sp-md);
  /* Not for the button's own placement — the grid does that — but so
     the halo below stays attached to it. An absolutely positioned
     ::before with no positioned ancestor resolves against the
     viewport, and `inset:-10px` then drew the glow 10px outside the
     window rather than 10px outside the button. */
  position:relative;
  flex:none;
  inline-size:var(--fab-size); block-size:var(--fab-size);
  border-radius:20px;
  background:var(--grad-lime); color:var(--on-accent);
  display:grid; place-items:center;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.7),
    inset 0 -2px 0 rgba(0,0,0,.08),
    0 10px 26px -8px var(--accent-glow),
    0 3px 8px -3px rgba(var(--shade-rgb), .3);
  transform:none;
  transition:transform .16s var(--ease-back), opacity .18s var(--ease),
             visibility .18s var(--ease);
}
.fab::before {
  content:""; position:absolute; inset:-10px; border-radius:30px;
  background:radial-gradient(closest-side, rgba(var(--accent-rgb), .32), transparent 72%);
  pointer-events:none; z-index:-1;
}
/* The label exists on every screen for the accessible name; only the
   rail has the width to draw it. */
.fab-t { display:none; }
.fab:active { transform:scale(.9); }
.fab svg { transition:transform var(--t-mid) var(--ease-back); }
.fab:active svg { transform:rotate(90deg); }

/* ── Sheets ───────────────────────────────────────────────── */
.scrim {
  position:fixed; inset:0; z-index:100;
  background:rgba(0,20,12,.5);
  -webkit-backdrop-filter:blur(4px);
  backdrop-filter:blur(4px);
  animation:fade var(--t-mid) ease;
}

.sheet {
  position:fixed; left:0; right:0; bottom:0; z-index:101;
  max-height:94%; display:flex; flex-direction:column;
  background:var(--page);
  border-radius:30px 30px 0 0;
  box-shadow:0 -18px 50px -18px rgba(var(--shade-rgb), .5);
  animation:rise .38s var(--ease);
  padding-bottom:var(--safe-b);
}
.sheet.full { top:0; max-height:100%; border-radius:0; box-shadow:none; }
.sheet-head { display:flex; align-items:center; gap:10px; padding:14px 16px 12px; flex:none; }
.sheet-head h2 { flex:1; margin:0; font-size:var(--step-1); font-weight:600; text-align:center; }
.sheet-body { overflow-y:auto; padding:0 16px 18px; }
.grab { width:40px; height:4px; border-radius:2px; background:var(--pine-200); margin:10px auto 0; flex:none; }

/* dark entry sheet.
   No `position` here — the base .sheet is already position:fixed, which is
   itself a containing block for the ::before glow. Setting relative would
   drop the sheet out of fixed positioning and park it below the fold. */
.sheet.dark {
  background:var(--grad-pine-deep); color:var(--beige);
  overflow:hidden;
}
.sheet.dark::before {
  content:""; position:absolute; inset:-30% -20% auto -20%; height:70%;
  background:radial-gradient(50% 60% at 50% 0%, rgba(var(--accent-rgb), .17), transparent 70%);
  pointer-events:none;
}
.sheet.dark > * { position:relative; z-index:1; }
.sheet.dark .sheet-head h2 { color:var(--beige); }
.sheet.dark .grab { background:rgba(var(--accent-rgb), .34); }
.sheet.dark .icon-btn { background:rgba(var(--paper-rgb), .08); border-color:rgba(var(--paper-rgb), .1); }

.dirchips { display:flex; gap:10px; padding:2px 16px 16px; flex:none; }
.dirchip {
  flex:1; padding:12px 6px 11px; border-radius:20px;
  background:linear-gradient(160deg, rgba(var(--paper-rgb), .11), rgba(var(--paper-rgb), .05));
  display:flex; flex-direction:column; align-items:center; gap:6px;
  font-size:var(--step--1); color:rgba(var(--paper-rgb), .72);
  border:1px solid rgba(var(--paper-rgb), .08);
  transition:background var(--t-fast), border-color var(--t-fast), color var(--t-fast), transform .12s var(--ease);
}
.dirchip:active { transform:scale(.95); }
.dirchip.on {
  background:linear-gradient(160deg, rgba(var(--accent-rgb), .24), rgba(var(--accent-rgb), .10));
  border-color:rgba(var(--accent-rgb), .5); color:var(--lime); font-weight:600;
  box-shadow:0 6px 18px -8px var(--accent-glow-soft);
}
.dirchip.on.in {
  background:linear-gradient(160deg, rgba(var(--accent-rgb), .26), rgba(var(--accent-rgb), .10));
  border-color:var(--lime); color:var(--lime);
}
.dirchip.on.out {
  background:linear-gradient(160deg, rgba(235,172,132,.26), rgba(235,172,132,.1));
  border-color:var(--clay-lit); color:var(--clay-lit);
  box-shadow:0 6px 18px -8px rgba(235,172,132,.55);
}

.amount-wrap { text-align:center; padding:var(--sp-2xs) var(--sp-md) var(--sp-lg); flex:none; }
/* The figure being typed. Both terms carry `vh` because this sits
   in a sheet whose height is the scarce thing — it gave ground in
   two separate queries before, at 360px wide and in landscape. */
.amount {
  font-size:clamp(2rem, 1.2rem + 5.5vh, 3.125rem); font-weight:700; letter-spacing:-1.8px;
  color:var(--beige); min-height:clamp(2.5rem, 1.6rem + 5vh, 3.75rem); line-height:1.12;
  display:flex; align-items:center; justify-content:center; gap:5px;
  text-shadow:0 4px 24px rgba(0,0,0,.28);
}
.amount .cur { font-size:clamp(1.125rem, .8rem + 2vh, 1.5rem); font-weight:400; opacity:.55; }
.amount.zero { color:rgba(var(--paper-rgb), .28); text-shadow:none; }
.amount-rule {
  width:min(8.75rem, 40%); height:3px; border-radius:2px;
  background:var(--grad-lime); margin:var(--sp-2xs) auto 0;
  box-shadow:0 0 16px rgba(var(--accent-rgb), .55);
}
.amount-note { font-size:var(--step-0); color:var(--pine-200); margin-top:var(--sp-sm); min-height:1.0625rem; }

.tray {
  background:var(--page); color:var(--ink);
  border-radius:28px 28px 0 0;
  padding:18px 16px 18px;
  flex:1; overflow-y:auto;
  box-shadow:0 -12px 30px -16px rgba(var(--shade-rgb), .5);
}
.tray-lbl {
  font-size:var(--step--2); font-weight:700; letter-spacing:.9px;
  text-transform:uppercase; color:var(--ink-3); margin:0 0 9px;
}
.tray-lbl.sp { margin-top:20px; }

/* account picker */
.acct-row { display:flex; gap:10px; overflow-x:auto; padding:2px 2px 5px; scrollbar-width:none; }
.acct-row::-webkit-scrollbar { display:none; }
.acct {
  flex:none; min-width:110px;
  background:var(--grad-card); border-radius:19px; padding:12px 13px;
  box-shadow:var(--e1); border:1.5px solid var(--line-soft);
  text-align:left;
  transition:transform .12s var(--ease), box-shadow var(--t-fast), border-color var(--t-fast);
}
.acct:active { transform:scale(.95); }
.acct.on {
  border-color:transparent; background:var(--grad-tile-pine); color:var(--beige);
  box-shadow:0 8px 20px -8px rgba(8,71,52,.7);
}
.acct-top { display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; }
.acct-dot {
  width:28px; height:16px; border-radius:8px; background:var(--pine-200);
  position:relative; transition:background var(--t-fast);
}
.acct-dot::after {
  content:""; position:absolute; top:2.5px; left:3px;
  width:11px; height:11px; border-radius:50%; background:#fff;
  box-shadow:0 1px 2px rgba(0,0,0,.25);
  transition:transform var(--t-mid) var(--ease-back);
}
.acct.on .acct-dot { background:var(--lime); box-shadow:0 0 10px var(--accent-glow-soft); }
.acct.on .acct-dot::after { transform:translateX(11px); }
.acct-n { font-size:var(--step-0); font-weight:600; }
.acct-b { font-size:var(--step--1); opacity:.66; margin-top:1px; }

.numgrid { display:grid; grid-template-columns:repeat(3,1fr); gap:var(--sp-xs); margin-top:var(--sp-sm); }
.numgrid button {
  min-height:clamp(2.625rem, 1.8rem + 4.4vh, 3.375rem); border-radius:17px;
  background:var(--grad-card); border:1px solid var(--line-soft);
  font-size:clamp(1.1875rem, 1rem + 1vh, 1.375rem); font-weight:500; box-shadow:var(--e1);
  transition:transform .09s var(--ease), background var(--t-fast);
}
.numgrid button:active { background:var(--tea-soft); transform:scale(.93); }
.numgrid button.act { background:var(--tea-soft); box-shadow:none; color:var(--ink-2); }

/* disclosure */
.disclose { margin-top:18px; }
.disclose-btn {
  display:flex; align-items:center; justify-content:center; gap:7px;
  width:100%; padding:12px; border-radius:16px;
  font-size:var(--step-0); font-weight:600; color:var(--pine-700);
  background:var(--grad-tile-tea);
  border:1px solid rgba(8,71,52,.07);
  transition:background var(--t-fast);
}
.disclose-btn .caret { transition:transform var(--t-mid) var(--ease-back); display:grid; }
.disclose.open .disclose-btn .caret { transform:rotate(180deg); }
.disclose-body { display:none; padding-top:15px; }
.disclose.open .disclose-body { display:block; animation:fade var(--t-mid) var(--ease); }

/* fields */
.field { margin-bottom:13px; }
.field > label { display:block; font-size:var(--step--1); font-weight:600; color:var(--ink-2); margin-bottom:6px; }
.control {
  width:100%; background:var(--paper); border:1px solid var(--line);
  border-radius:15px;
  /* A floor, not a height: a select showing a long option, or a
     field whose value wraps, grows rather than clipping its own
     text — and it is never below the size of a fingertip. */
  min-height:var(--tap);
  padding:var(--sp-sm); font-size:var(--step-2);
  appearance:none;
  transition:border-color var(--t-fast), box-shadow var(--t-fast);
}
.control:focus { outline:none; border-color:var(--pine-500); box-shadow:0 0 0 3px var(--tea-soft); }
textarea.control { resize:vertical; min-height:4rem; }
select.control { padding-right:34px; background-position:right 13px center; background-repeat:no-repeat; }
.field-2 {
  display:grid; gap:var(--sp-xs);
  grid-template-columns:repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
}
.hint { font-size:var(--step--1); color:var(--ink-3); margin-top:6px; line-height:1.5; }
.hint.warn { color:var(--warn); }

.switchrow {
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  background:var(--grad-card); border:1px solid var(--line);
  border-radius:15px; padding:13px; margin-bottom:12px;
}
.switchrow .sw-t { font-size:var(--step-0); font-weight:600; }
.switchrow .sw-s { font-size:var(--step--1); color:var(--ink-3); margin-top:1px; }
.switch {
  width:46px; height:27px; border-radius:14px; background:var(--pine-200);
  position:relative; flex:none; transition:background var(--t-mid) var(--ease);
}
.switch::after {
  content:""; position:absolute; top:3px; left:3px; width:21px; height:21px;
  border-radius:50%; background:#fff; box-shadow:0 1px 3px rgba(0,0,0,.25);
  transition:transform var(--t-mid) var(--ease-back);
}
.switch.on { background:var(--grad-tile-pine); }
.switch.on::after { transform:translateX(19px); }

.toggle2 { display:flex; background:var(--tea-soft); border-radius:14px; padding:3px; }
.toggle2 button {
  flex:1; padding:9px 4px; border-radius:11px; font-size:var(--step--1); color:var(--ink-2);
  transition:background var(--t-fast), color var(--t-fast);
}
.toggle2 button.on { background:var(--paper); font-weight:600; color:var(--ink); box-shadow:var(--e1); }

.catgrid { display:flex; flex-wrap:wrap; gap:8px; }
.cat {
  padding:10px 14px; border-radius:14px;
  background:var(--paper); border:1px solid var(--line);
  font-size:var(--step-0); color:var(--ink-2);
  transition:transform .1s var(--ease), background var(--t-fast), color var(--t-fast);
}
.cat:active { transform:scale(.94); }
.cat.on {
  background:var(--grad-tile-pine); border-color:transparent; color:var(--beige);
  font-weight:600; box-shadow:0 4px 12px -4px rgba(8,71,52,.6);
}

.btn {
  width:100%;
  display:flex; align-items:center; justify-content:center; gap:var(--sp-2xs);
  min-height:var(--tap);
  padding:var(--sp-md); border-radius:18px;
  font-size:var(--step-2); font-weight:700; text-align:center;
  background:var(--grad-lime); color:var(--on-accent);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.6),
    0 6px 20px -6px var(--accent-glow);
  margin-top:var(--sp-md);
  position:relative; overflow:hidden;
  transition:transform .12s var(--ease), opacity var(--t-fast);
}
.btn:active { transform:scale(.975); }
.btn[disabled] { opacity:.38; box-shadow:none; }
.btn.sec {
  background:var(--grad-tile-tea); color:var(--pine-700);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.8);
}
.btn.danger { background:var(--out-soft); color:var(--out); box-shadow:none; }
.btn.ghost {
  background:transparent; color:var(--pine-700); box-shadow:none;
  border:1.5px solid var(--line);
}
.btn.ok { background:#0b6b45; color:#fff; box-shadow:0 6px 20px -6px rgba(11,107,69,.5); }
.btn.sm { padding:var(--sp-sm); font-size:var(--step-1); margin-top:var(--sp-xs); }

/* photo */
.photo-add {
  display:flex; align-items:center; gap:11px;
  width:100%; padding:14px; border-radius:16px;
  background:var(--paper); border:1.5px dashed var(--pine-300);
  color:var(--pine-700); font-size:var(--step-0); font-weight:600;
  transition:background var(--t-fast), border-color var(--t-fast);
}
.photo-add:active { background:var(--tea-soft); border-color:var(--pine-500); }
.photo-thumbs { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
.thumb {
  position:relative; width:76px; height:76px; border-radius:15px;
  overflow:hidden; background:var(--pine-100);
  box-shadow:var(--e2);
  animation:pop .34s var(--ease-back);
}
.thumb img { width:100%; height:100%; object-fit:cover; display:block; }
.thumb .x {
  position:absolute; top:4px; right:4px; width:22px; height:22px;
  border-radius:50%; background:rgba(var(--shade-rgb), .66); color:var(--beige);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  display:grid; place-items:center;
}
.thumb .badge {
  position:absolute; left:0; right:0; bottom:0;
  font-size:var(--step--2); text-align:center; padding:3px;
  background:rgba(var(--shade-rgb), .72); color:var(--beige); letter-spacing:.3px;
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
}
.thumb .badge.up { background:rgba(11,107,69,.9); }

/* meters (reports) */
.meter { margin-bottom:15px; }
.meter-top { display:flex; justify-content:space-between; align-items:baseline; font-size:var(--step-0); margin-bottom:6px; }
.meter-top b { font-weight:600; }
.meter-top span { color:var(--ink-2); font-size:var(--step--1); font-weight:600; }
.meter-bar {
  height:10px; border-radius:5px; background:var(--tea-soft);
  overflow:hidden; box-shadow:inset 0 1px 2px rgba(8,71,52,.10);
}
.meter-fill {
  height:100%; border-radius:5px; width:0;
  background:var(--grad-tile-pine);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.25);
  transition:width .85s var(--ease);
}
.meter-fill.out { background:var(--grad-tile-out); }
.meter-fill.in { background:var(--grad-tile-in); }

.kpis {
  display:grid; gap:var(--sp-sm);
  grid-template-columns:repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr));
}
.kpi {
  background:var(--grad-card); border-radius:20px; padding:15px;
  border:1px solid var(--line-soft); box-shadow:var(--e2);
}
.kpi-l { font-size:var(--step--2); color:var(--ink-3); letter-spacing:.8px; font-weight:700; }
.kpi-v { font-size:var(--step-3); font-weight:700; margin-top:4px; letter-spacing:-.6px; }
.kpi-v.in { color:var(--in); } .kpi-v.out { color:var(--out); }
.kpi-s { font-size:var(--step--1); color:var(--ink-3); margin-top:3px; }

.pill { display:inline-block; padding:4px 11px; border-radius:999px; font-size:var(--step--2); font-weight:700; letter-spacing:.4px; }
.pill.in { background:var(--in-soft); color:var(--in); }
.pill.out { background:var(--out-soft); color:var(--out); }
.pill.warn { background:var(--warn-soft); color:var(--warn); }
.pill.mut { background:var(--tea-soft); color:var(--pine-700); }
.pill.sm { padding:2px 8px; font-size:var(--step--2); letter-spacing:.2px; vertical-align:1px; }

.kv {
  display:flex; justify-content:space-between; gap:14px;
  padding:12px 0; border-bottom:1px solid var(--line-soft); font-size:var(--step-0);
}
.kv:last-child { border-bottom:0; }
.kv span { color:var(--ink-2); font-size:var(--step-0); }
.kv b { font-weight:700; text-align:right; }

/* ── Save confirmation ────────────────────────────────────────
   A drawn tick rather than a spinner: it says the thing is done,
   and it is gone before it can become annoying. */
.burst {
  position:fixed; inset:0; z-index:310;
  display:grid; place-items:center; pointer-events:none;
}
.burst-disc {
  width:104px; height:104px; border-radius:50%;
  background:var(--grad-lime);
  box-shadow:0 14px 40px -10px var(--accent-glow);
  display:grid; place-items:center;
  animation:pop .42s var(--ease-back) both;
  position:relative;
}
.burst-disc::after {
  content:""; position:absolute; inset:0; border-radius:50%;
  border:2px solid var(--lime);
  animation:ringOut .75s var(--ease) both;
}
.burst svg { width:48px; height:48px; }
.burst path {
  stroke:var(--on-accent); stroke-width:5.5; fill:none;
  stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:44; stroke-dashoffset:44;
  animation:drawCheck .4s var(--ease) .16s both;
}

/* ripple */
.ripple {
  position:absolute; border-radius:50%; pointer-events:none;
  background:radial-gradient(closest-side, rgba(8,71,52,.16), transparent);
  transform:scale(0); opacity:.85;
  animation:rippleOut .6s var(--ease) both;
}

/* Ripple hosts must clip, or the expanding circle escapes the rounded
   corner it was launched inside. */
.cat, .chip, .acct, .numgrid button, .disclose-btn {
  position:relative; overflow:hidden;
}

/* toast */
.toast-root {
  position:fixed; left:0; right:0; bottom:calc(var(--tabbar-h) + var(--safe-b) + 16px);
  z-index:300; display:flex; flex-direction:column; align-items:center; gap:8px; pointer-events:none;
}
/* Clear the floating entry button rather than sliding in behind it. */
[data-section="phynance"] .toast-root {
  bottom:calc(var(--tabbar-h) + var(--safe-b) + 90px);
}
.toast {
  background:var(--glass-dark); color:var(--beige);
  -webkit-backdrop-filter:var(--blur); backdrop-filter:var(--blur);
  border:1px solid rgba(var(--accent-rgb), .18);
  padding:12px 18px; border-radius:16px; font-size:var(--step-0); font-weight:500;
  box-shadow:var(--e3); animation:pop .34s var(--ease-back);
  max-width:88%; text-align:center;
}
.toast.warn { background:var(--warn); border-color:transparent; }
.toast.err { background:var(--out); border-color:transparent; }

/* ════════════════════════════════════════════════════════════
   RESPONSIVE

   Far less of one than there used to be. Size is the ramp's job
   now — type, spacing, the chrome's own height and every card
   track are clamp()ed between a phone and a wide desktop, so a
   window between two of the widths below is not a gap anybody has
   to fill in by hand.

   What is left here are the three things a ramp cannot express,
   because each is a change of *arrangement* rather than of size:

     large / tablet  ≥600px   the app becomes a centred column
     desktop         ≥900px   the drawer becomes a standing rail,
                              and sheets become centred dialogs
     wide desktop    ≥1280px  the spine is allowed to be wider

   Plus one orthogonal case: a short landscape viewport, where
   height is the scarce thing and the PIN gate starts to scroll.

   --content-w is the spine. Every direct child of .screen is
   centred inside it, so a wider window gives the app more room
   without ever stretching a line of text past reading width.
   ════════════════════════════════════════════════════════════ */

/* The spine is the full width until there is room to centre it.
   --rail-w is NOT reset here: it is the drawer's width on a phone
   as much as the rail's on a desktop, and zeroing it collapsed the
   drawer to its own padding. What switches at 900px is the grid
   *track* the rail sits in, which starts at 0 on .app. */
:root { --content-w:100%; }

/* dvh tracks the *dynamic* viewport on its own — a mobile browser
   collapsing its toolbar, or the safe area around a home indicator,
   is exactly what it was built to account for — so a dvh-capable
   browser gets it unconditionally here, later in the cascade than
   the var() above and at the same specificity, so it always wins
   where it applies. app.js's own measurement is left to the browsers
   this rule does not match. */
@supports (height:100dvh) {
  html, body, .app { height:100dvh; }
}


/* ── Large phones and up: a centred column ──────────────────── */
@media (min-width:600px) {
  :root { --content-w:640px; }

  body {
    background:
      radial-gradient(70% 55% at 50% 0%, rgba(var(--hero-glow-rgb), .30), transparent 72%),
      var(--pine-900);
  }
  /* Until the rail arrives, the app stays one column with the pine
     washes showing in the gutters — the phone design, given room.
     Constraining .app rather than only its contents keeps the hero
     full-bleed and the tab bar the width of the thing it belongs to. */
  .app {
    max-width:var(--content-w); margin-inline:auto;
    background:var(--page); box-shadow:0 0 90px rgba(0,0,0,.45);
  }

  /* The spine. Everything a view renders sits inside it.
     margin-inline only — .panel's negative margin-top, which
     laps it over the hero, has to survive. */
  .screen > * {
    max-width:var(--content-w);
    margin-inline:auto;
  }
  /* The hero, panel, section and glass bar all take their padding
     from the same space ramp and have already widened by the time
     this matches, so none of them is re-stated here. Nor is the
     headline figure, nor the card grids — those size themselves off
     their own content now (see .grid2 and .kpis). */

  /* Sheets stay bottom-anchored here, just narrower. Centred with
     auto margins, never translateX — the open and close animations
     own `transform`, and the two would fight. */
  .sheet, .toast-root {
    max-width:35rem; left:0; right:0;
    margin-left:auto; margin-right:auto;
  }
  .gate-inner { max-width:23.75rem; }
}

/* ── Desktop: the rail arrives ────────────────────────────────
   The sidebar takes the rail track (see THE SIDEBAR SHELL below,
   which is where the switch is actually made). Everything that
   used to be pushed clear of a rail by hand — the screen, the
   glass bar, the toast, the sheet, each carrying its own copy of
   `left:var(--rail-w)` — is inside a grid cell that already
   starts where the rail ends, so none of it appears here. */
@media (min-width:900px) {
  :root { --content-w:720px; }

  .app { max-width:none; box-shadow:none; }

  /* Filters have room to lay themselves out rather than scroll away. */
  .chipbar { flex-wrap:wrap; overflow-x:visible; }

  /* The toast is a viewport-level layer, like the sheet and the
     lightbox, so it is one of the few things that still has to be
     told where the rail ends. There is no tab bar at this width
     either, so it comes back down to the foot of the window. */
  .toast-root,
  [data-section="phynance"] .toast-root {
    left:var(--rail-w); bottom:var(--sp-lg);
  }

  /* Sheets become centred dialogs over the content area. */
  .sheet {
    left:var(--rail-w); right:0;
    bottom:26px;
    max-width:520px; max-height:min(86vh, 820px);
    border-radius:28px;
    /* Floating now, so scrolling content has to be clipped to the
       radius — on a phone the sheet's bottom was the screen's. */
    overflow:hidden;
  }
  .sheet-body { padding-bottom:26px; }
  .sheet.full {
    top:auto; height:min(86vh, 820px); max-height:none;
    border-radius:28px; box-shadow:0 24px 60px -20px rgba(var(--shade-rgb), .55);
  }
  .sheet.dark { overflow:hidden; }
  .tray { border-radius:24px 24px 0 0; }
}

/* ── Wide desktop: more column ────────────────────────────────
   Only the spine widens. The grids inside it gain their columns
   from the extra room on their own — that is what auto-fit is for
   — and the padding and radii are already on the ramp. */
@media (min-width:1280px) {
  :root { --content-w:1040px; }
}

/* ── Short viewports (a phone turned sideways) ────────────────
   Height, not width, is what has run out — the one thing a ramp
   measured on width cannot see, so it is the one case still worth
   a query.

   It is a short block now, and stays short: everything that used
   to be re-stated here — the gate's mark and title, the numpad and
   keypad keys, the hero's padding, the headline figure, the entry
   button, the tab labels — is sized by a clamp() carrying a `vh`
   term, so it has already given ground by the time this matches.
   What is left is the two decisions a ramp cannot express: the
   gate stops centring and starts scrolling, and a sheet may use
   the full height. */
@media (max-height:560px) and (orientation:landscape) {
  .gate { align-items:flex-start; overflow-y:auto; }
  .sheet { max-height:100%; }
}

/* ── Pointers that hover ──────────────────────────────────────
   A finger only ever gets :active. A mouse should be told what
   is live before it commits. */
@media (hover:hover) and (pointer:fine) {
  .card:hover      { transform:translateY(-2px); box-shadow:var(--e3); }
  .strip:hover     { transform:translateY(-2px); }
  .row:hover       { background:var(--tea-soft); }
  .chip:hover,
  .cat:hover       { background:var(--tea-soft); }
  .acct:hover      { box-shadow:var(--e2); }
  .btn:hover       { filter:brightness(1.03); }
  .icon-btn:hover  { background:rgba(var(--paper-rgb), .14); }
  .icon-btn.plain:hover { background:var(--tea); }
  .tab:not(.on):hover   { color:var(--pine-600); }
  .numpad button:hover  { background:rgba(var(--accent-rgb), .16); }
  .numgrid button:hover { background:var(--tea-soft); }
  .sec-link:hover  { text-decoration:underline; }
}

/* Keyboard users get the same affordance a pointer gets. */
:focus-visible {
  outline:2.5px solid var(--pine-500);
  outline-offset:2px;
  border-radius:6px;
}
.gate :focus-visible,
.hero :focus-visible,
.sheet.dark :focus-visible,
.strip:not([style]) :focus-visible { outline-color:var(--lime); }

/* Touch keyboards zoom a field whose text is under 16px. Only
   coarse pointers pay the extra pixel; the drawn design keeps 15. */
@media (pointer:coarse) {
  .control { font-size:16px; }
}

/* ════════════════════════════════════════════════════════════
   KONTOUR SHELL — the workspace frame

   Kontour is a web application first now, so the rail is a real
   sidebar and the module's screens sit in a panel beside it.
   Everything here reads the token ramp, so the shell is
   monochrome and Phynance turns the same components green.
   ════════════════════════════════════════════════════════════ */

/* ── Module sub-nav ──────────────────────────────────────────
   Phynance's three screens. A row of pills that sticks under the
   hero as you scroll, so the module's own navigation is always
   within reach without competing with the workspace rail. */
.subnav {
  position:sticky; top:8px; z-index:6;
  display:flex; gap:6px;
  width:max-content; max-width:calc(100% - 32px);
  margin:16px auto 4px;
  padding:5px;
  border-radius:999px;
  background:var(--glass-light);
  -webkit-backdrop-filter:var(--blur); backdrop-filter:var(--blur);
  border:1px solid var(--glass-edge);
  box-shadow:var(--e2);
  overflow-x:auto; scrollbar-width:none;
  scroll-behavior:smooth; scroll-padding:0 5px;
  overscroll-behavior-x:contain;
}
.subnav::-webkit-scrollbar { width:0; height:0; }

/* The pills ride the ramp so four of them fit inside a 320px phone
   without being walked down at 430 and again at 360 — the padding
   is what gives, and it gives continuously. */
.subtab {
  flex:none;
  padding:var(--sp-2xs) clamp(.625rem, .1rem + 2.6vw, 1.25rem);
  border-radius:999px;
  font-size:var(--step-1); font-weight:600; letter-spacing:-.1px;
  color:var(--ink-3);
  white-space:nowrap;
  transition:color var(--t-fast), background var(--t-fast);
}
.subtab:focus-visible {
  outline:2px solid var(--pine-700); outline-offset:2px;
}
.subtab.on {
  background:var(--pine-700);
  color:var(--beige);
  box-shadow:var(--e1);
}
@media (hover:hover) and (pointer:fine) {
  .subtab:not(.on):hover { color:var(--ink); background:var(--tea-soft); }
}

/* The ≥900px block that used to sit here styled `.tab` as a rail
   row — a solid pill, a hover tint, a shadowed entry button. None
   of it ever painted: the sidebar took over as the rail and hides
   `.tabbar` outright at that width, so every rule in it lost to
   `display:none`. Removed rather than re-pointed; the sidebar has
   its own .side-item styling and does not want a second copy. */

@media (prefers-reduced-motion: reduce) {
  .subtab, .tab { transition:none; }
}

/* ── Full desktop ────────────────────────────────────────────
   The spine is allowed to be wider. That is the whole of it now:
   what used to be here besides — a wider section padding, a wider
   card track — is on the ramp and arrived gradually instead of all
   at once on the 1600th pixel. */
@media (min-width:1600px) {
  :root { --content-w:1320px; }
}

/* ── The rail never changes colour ───────────────────────────
   data-section re-points the ramp for the whole document, which
   is what turns the panel green. The rail is not the module
   though — it is the workspace holding it — so it re-asserts the
   shell's own ramp and stays monochrome whatever you are in.
   That is what makes the colour mean "you are here": one side of
   the screen moves, the other stays put. */
.tabbar {
  --pine-900:var(--k-950);
  --pine-800:var(--k-900);
  --pine-700:var(--k-850);
  --pine-600:var(--k-800);
  --pine-500:var(--k-700);
  --pine-400:var(--k-500);
  --pine-300:var(--k-300);
  --pine-200:var(--k-200);
  --pine-100:var(--k-100);
  --pine-050:var(--k-050);

  --lime:#FFFFFF;
  --lime-lit:#FFFFFF;
  --lime-deep:var(--k-900);
  --on-accent:#FFFFFF;
  --on-accent-rgb:255,255,255;
  --accent-glow:rgba(11,11,13,.30);
  --accent-rgb:255,255,255;
  --accent-deep-rgb:11,11,13;
  --beige:#FFFFFF;
  --tea:var(--k-100);
  --tea-soft:var(--k-075);

  --page:#F4F4F6;
  --paper:#FFFFFF;
  --line:#E4E4E8;
  --line-soft:#EFEFF2;
  --glass-edge:rgba(11,11,13,.09);
  --glass-light:rgba(255,255,255,.72);
  --glass-dark:rgba(8,8,10,.62);
  --paper-rgb:255,255,255;
  --ink-rgb:11,11,13;
  --shade-rgb:5,5,6;
  --ink:#0B0B0D;
  --ink-2:#55555E;
  --ink-3:#8A8A93;

  --grad-lime:linear-gradient(145deg, #2A2A31 0%, #131316 52%, #050506 100%);
  --grad-tile-tea:linear-gradient(145deg, #F7F7F8 0%, #E9E9EC 100%);
}

/* ════════════════════════════════════════════════════════════
   DASHBOARD — snippets, not a fifth ledger

   Every block is a heading, a way through, and at most a handful
   of rows. The cap is the design: a snippet that grows into a
   list is just the other screen, badly.
   ════════════════════════════════════════════════════════════ */

.snip { margin-bottom:22px; }
.snip-head {
  display:flex; align-items:baseline; justify-content:space-between;
  gap:12px; margin:0 0 10px;
}
.snip-t {
  margin:0; font-size:var(--step--1); font-weight:700;
  letter-spacing:.9px; text-transform:uppercase; color:var(--ink-3);
}
.snip-go {
  display:inline-flex; align-items:center; gap:3px;
  font-size:var(--step--1); font-weight:600; color:var(--ink-2);
  padding:4px 2px; border-radius:8px;
}
.snip-go svg { transition:transform var(--t-fast) var(--ease); }
@media (hover:hover) and (pointer:fine) {
  .snip-go:hover { color:var(--ink); }
  .snip-go:hover svg { transform:translateX(2px); }
}

.snip-rows { display:flex; flex-direction:column; gap:8px; }

.snip-none {
  display:flex; align-items:center; gap:10px;
  padding:18px 16px; border-radius:var(--r-md);
  background:var(--paper); border:1px dashed var(--line);
  color:var(--ink-3); font-size:var(--step-0);
}

.qrow {
  display:flex; align-items:center; gap:12px;
  width:100%; text-align:left;
  padding:13px 15px; border-radius:var(--r-md);
  background:var(--grad-card); border:1px solid var(--line-soft);
  box-shadow:var(--e1);
  transition:transform .12s var(--ease), box-shadow var(--t-fast);
}
.qrow:active { transform:scale(.99); }
@media (hover:hover) and (pointer:fine) {
  .qrow:hover { box-shadow:var(--e2); }
}
.qrow-main { flex:1; min-width:0; }
.qrow-t {
  font-size:var(--step-0); font-weight:600; color:var(--ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.qrow-s { font-size:var(--step--1); color:var(--ink-3); margin-top:2px; }
.qrow-side { flex:none; text-align:right; display:flex; flex-direction:column; gap:4px; align-items:flex-end; }
.qrow-amt { font-size:var(--step-0); font-weight:700; color:var(--ink); }
.qrow-amt.in { color:var(--in); }
.qrow-amt.out { color:var(--out); }
.qrow-cap { font-size:var(--step--2); color:var(--ink-3); letter-spacing:.2px; }

/* ════════════════════════════════════════════════════════════
   QUOTATION — the list
   ════════════════════════════════════════════════════════════ */

.searchbar { margin-bottom:12px; }

.qlist {
  display:grid; gap:var(--sp-sm);
  grid-template-columns:repeat(auto-fill, minmax(min(20rem, 100%), 1fr));
}

.qcard {
  display:flex; flex-direction:column; gap:9px;
  padding:16px; border-radius:var(--r-lg);
  background:var(--grad-card); border:1px solid var(--line-soft);
  box-shadow:var(--e1); text-align:left; width:100%; min-width:0;
  cursor:pointer;
  transition:transform .14s var(--ease), box-shadow var(--t-fast);
}
.qcard:active { transform:scale(.99); }
.qcard:focus-visible { outline:2px solid var(--pine-700); outline-offset:2px; }
@media (hover:hover) and (pointer:fine) {
  .qcard:hover { box-shadow:var(--e2); transform:translateY(-1px); }
}
/* One cluster of facts: who, then the identifiers under it, with the
   figure and its status opposite. Four separate rows of label-and-
   value took a whole phone screen per card; this takes a third of it
   and answers the same questions. */
.qcard-top { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
.qcard-id { min-width:0; }
.qcard-client {
  font-size:var(--step-1); font-weight:700; color:var(--ink); letter-spacing:-.2px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.qcard-line {
  display:flex; align-items:center; flex-wrap:wrap; gap:5px;
  margin-top:3px; font-size:var(--step--1); color:var(--ink-3);
}
.qcard-line .qcard-mr { font-weight:700; color:var(--ink-2); letter-spacing:.2px; }
.qcard-dot {
  width:3px; height:3px; border-radius:50%; background:var(--ink-3);
  opacity:.55; flex:none;
}
.qcard-money { display:flex; flex-direction:column; align-items:flex-end; gap:5px; flex:none; }
.qcard-title {
  margin:0; font-size:var(--step-0); color:var(--ink-2); line-height:1.45;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.qcard-amt { font-size:var(--step-2); font-weight:700; color:var(--ink); letter-spacing:-.4px; }

/* The actions a quotation is actually used for, as a row of equal
   targets across the foot of the card. Everything rarer is one tap
   further in, behind the last one. */
.qcard-acts {
  display:flex; align-items:stretch; flex-wrap:wrap; gap:7px; width:100%;
  padding-top:11px; border-top:1px solid var(--line-soft);
}
.act {
  /* min-width:0 is load-bearing: a flex item's default min-width is
     the width of its own content, so without this a row that is a
     little too narrow does not compress the buttons — it overflows
     the card, and everything past the screen edge is simply gone,
     with no scrollbar or visual sign anything is missing. That is
     the bug this once was; flex-wrap above is the second line of
     defence, so a row that still cannot fit drops to two rows,
     visibly, rather than losing a button off the edge again. */
  flex:1 1 72px; min-width:0; display:flex; align-items:center; justify-content:center; gap:6px;
  min-height:40px; padding:8px 8px; border-radius:13px;
  background:var(--tea-soft); color:var(--ink-2);
  font-size:var(--step--1); font-weight:600;
  transition:background var(--t-fast), color var(--t-fast), transform .12s var(--ease);
}
.act span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.act:active { transform:scale(.96); }
.act.ghost { flex:none; width:44px; background:transparent; border:1px solid var(--line); }
.act.grow { flex:2 1 120px; }
.act:focus-visible { outline:2px solid var(--pine-700); outline-offset:2px; }
@media (hover:hover) and (pointer:fine) {
  .act:hover { background:var(--tea); color:var(--ink); }
}
@media (max-width:400px) {
  .act span { display:none; }
  .act { gap:0; flex-basis:44px; }
}

/* ── The action sheet ──────────────────────────────────────────
   Row targets, not a menu: 52px of tappable height each, the icon
   carrying the meaning and the sub-line saying what it costs. */
.actlist { display:flex; flex-direction:column; gap:2px; margin:2px 0 6px; }
.actrow {
  display:flex; align-items:center; gap:12px; width:100%;
  padding:12px 6px; border-radius:14px; text-align:left;
  color:var(--ink); transition:background var(--t-fast);
}
.actrow-ico {
  flex:none; width:36px; height:36px; border-radius:12px;
  display:grid; place-items:center;
  background:var(--tea-soft); color:var(--ink-2);
}
.actrow-t { flex:1; min-width:0; font-size:var(--step-0); font-weight:600; }
.actrow-t small {
  display:block; font-size:var(--step--1); font-weight:400; color:var(--ink-3);
  margin-top:1px; line-height:1.35;
}
.actrow svg:last-child { color:var(--ink-3); flex:none; }
.actrow.danger { color:var(--out); }
.actrow.danger .actrow-ico { background:var(--out-soft); color:var(--out); }
.actrow:active { background:var(--tea-soft); }
@media (hover:hover) and (pointer:fine) {
  .actrow:hover { background:var(--tea-soft); }
}

/* A search field reads as one the moment it carries the glyph. */
.searchbar { position:relative; }
.searchbar-ico {
  position:absolute; left:13px; top:50%; transform:translateY(-50%);
  color:var(--ink-3); pointer-events:none; display:flex;
}
.searchbar .control { padding-left:38px; }
.chipbar-sep {
  flex:none; width:1px; align-self:stretch; margin:4px 3px;
  background:var(--line);
}

.mini {
  display:inline-flex; align-items:center; gap:5px;
  padding:6px 12px; border-radius:999px;
  font-size:var(--step--1); font-weight:600;
  background:var(--tea-soft); color:var(--ink-2);
  border:1px solid transparent;
  transition:background var(--t-fast), color var(--t-fast);
}
@media (hover:hover) and (pointer:fine) {
  .mini:hover { background:var(--tea); color:var(--ink); }
}
.mini.ok { background:var(--in-soft); color:var(--in); }
.mini.danger { background:transparent; color:var(--ink-3); padding:6px 9px; }
@media (hover:hover) and (pointer:fine) {
  .mini.danger:hover { background:var(--out-soft); color:var(--out); }
}
.mini.flat { background:transparent; color:var(--ink-3); border-color:var(--line); }

/* ════════════════════════════════════════════════════════════
   QUOTATION — the builder

   Laid out in the order the document is read. The body scrolls;
   the total does not, because it is the figure being negotiated.
   ════════════════════════════════════════════════════════════ */

.qb { display:flex; flex-direction:column; min-height:0; flex:1; }
.qb-scroll { flex:1; min-height:0; overflow-y:auto; padding:4px 18px 20px; }

.qb-sec { padding-top:14px; }
.qb-sec + .qb-sec { margin-top:6px; padding-top:18px; border-top:1px solid var(--line-soft); }
.qb-sec-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }
.qb-sec-acts { display:flex; gap:6px; }
.qb-h {
  margin:0 0 10px; font-size:var(--step--1); font-weight:700;
  letter-spacing:.9px; text-transform:uppercase; color:var(--ink-3);
}
.qb-sec-head .qb-h { margin-bottom:0; }
.qb-hint { margin:6px 0 12px; font-size:var(--step--1); color:var(--ink-3); line-height:1.5; }

.qb-grid {
  display:grid; gap:0 var(--sp-sm);
  grid-template-columns:repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}

.qb-count {
  display:inline-grid; place-items:center; min-width:19px; height:19px;
  padding:0 5px; margin-left:5px; border-radius:999px;
  background:var(--tea); color:var(--ink-2);
  font-size:var(--step--2); font-weight:700; letter-spacing:0;
}

/* ── Clusters ──────────────────────────────────────────────────
   A label above every input turns six facts into six blocks and a
   screen and a half of scrolling. Paired on one line inside a single
   card, the same six read as one thing — which is what they are:
   the header of the document. */
.control.lead {
  font-size:var(--step-1); font-weight:700; letter-spacing:-.2px;
  padding-top:13px; padding-bottom:13px;
}
.qcluster {
  margin-top:10px; border-radius:var(--r-md);
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1); overflow:hidden;
}
.qpair {
  display:flex; align-items:center; gap:10px;
  padding:2px 12px 2px 14px; min-height:46px;
}
.qpair + .qpair { border-top:1px solid var(--line-soft); }
.qpair-l {
  flex:none; width:82px; font-size:var(--step--1); font-weight:600; color:var(--ink-3);
}
.qpair .control { flex:1; min-width:0; text-align:right; padding-right:2px; }
.qpair .control:focus { text-align:left; }
.qpair input[type="date"] { text-align:right; }

/* ── Disclosure ────────────────────────────────────────────────
   Everything with a default that is usually right: shut, with the
   current value on the summary so a closed panel still answers the
   question it is hiding. */
.qdisc { margin-top:10px; }
.qdisc > summary {
  display:flex; align-items:center; gap:8px;
  padding:11px 12px; border-radius:13px; cursor:pointer;
  font-size:var(--step--1); font-weight:600; color:var(--ink-2);
  background:var(--tea-soft);
  list-style:none;
}
.qdisc > summary::-webkit-details-marker { display:none; }
.qdisc > summary svg { flex:none; color:var(--ink-3); transition:transform var(--t-fast); }
.qdisc[open] > summary svg { transform:rotate(90deg); }
.qdisc[open] > summary { border-radius:13px 13px 0 0; }
.qdisc-t { flex:1; }
.qdisc-v { font-size:var(--step--1); font-weight:600; color:var(--ink-3); }
.qdisc-peek {
  flex:1; min-width:0; font-weight:500; color:var(--ink-3); font-size:var(--step--1);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.qdisc.panel-disc > div,
.qdisc > .fin-row:first-of-type { margin-top:8px; }
.qdisc.tight > summary { background:transparent; padding:9px 2px; }
.qdisc.tight[open] > summary { border-bottom:1px solid var(--line-soft); border-radius:0; }
.mini.wide {
  width:100%; justify-content:center; margin-top:9px; padding:10px 12px;
}
.qmoney { display:flex; align-items:center; gap:7px; }
.qmoney span { font-size:var(--step--1); font-weight:600; color:var(--ink-3); }

/* The builder's own action row, on the same shape as the card's. */
.qb-acts { display:flex; align-items:stretch; flex-wrap:wrap; gap:8px; margin-top:11px; }
.qb-acts .act { min-height:46px; }
.qb-acts .btn.sm.grow { flex:2 1 140px; min-width:0; margin:0; }
.qb-acts .btn.sm.ghost { flex:1 1 90px; min-width:0; margin:0; }

.qb-none {
  text-align:center; padding:26px 18px; color:var(--ink-3);
  border:1px dashed var(--line); border-radius:var(--r-md);
}
.qb-none p { margin:8px 0 2px; font-weight:600; color:var(--ink-2); font-size:var(--step-0); }
.qb-none small { font-size:var(--step--1); }

/* ── One line of the quotation ─────────────────────────────── */
.qline {
  margin-top:12px; padding:13px;
  border-radius:var(--r-md);
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1);
}
.qline-head { display:flex; align-items:center; gap:9px; }
.qline-n {
  flex:none; width:22px; height:22px; border-radius:8px;
  display:grid; place-items:center;
  background:var(--tea-soft); color:var(--ink-3);
  font-size:var(--step--2); font-weight:700;
}
.qline-img {
  flex:none; width:38px; height:38px; border-radius:11px;
  object-fit:cover; background:var(--tea-soft);
}
.qline-img.ph { display:grid; place-items:center; color:var(--ink-3); }
.qline-title { flex:1; min-width:0; font-weight:600; }
.control.flush { border-color:transparent; background:transparent; padding-left:6px; }
.control.flush:focus { border-color:var(--pine-500); background:var(--paper); }

.qline-kind { display:flex; align-items:center; gap:6px; margin-top:10px; flex-wrap:wrap; }
/* A row of seg-mini pills. Deliberately not named with "seg" — sharing
   that class with the underline tab-switcher above let its rules (the
   glowing underline, the 14.5px font, the 150px max-width) bleed onto
   these pill buttons, since both are plain <button> elements inside a
   same-named ancestor. */
.segbar { display:flex; gap:6px; margin-top:2px; }
.segbar .seg-mini { flex:1; text-align:center; }
/* A floor under the small controls. Their type is on the ramp, so on
   the narrowest phone the box would otherwise follow it down to under
   28px — readable, but below what a finger reliably hits. 2rem is the
   smallest these are allowed to get; the label still scales. */
.seg-mini {
  display:inline-flex; align-items:center; justify-content:center;
  min-height:2rem;
  padding:var(--sp-3xs) var(--sp-sm); border-radius:999px;
  font-size:var(--step--1); font-weight:600;
  background:var(--tea-soft); color:var(--ink-3);
  transition:background var(--t-fast), color var(--t-fast);
}
.seg-mini.on { background:var(--pine-700); color:var(--beige); }
.qline-code {
  margin-left:auto; font-size:var(--step--2); font-weight:600;
  color:var(--ink-3); letter-spacing:.4px;
}

.qline-dims { display:flex; gap:8px; margin-top:10px; flex-wrap:wrap; }
.qline-dims label,
.qline-money label {
  display:flex; align-items:center; gap:6px;
  font-size:var(--step--1); font-weight:600; color:var(--ink-3);
}
.qline-dims label.grow, .qline-money label.grow { flex:1; min-width:130px; }
.mini-in { width:88px; padding:8px 10px; font-size:var(--step-0); }

/* A rupee amount field: the ₹ overlays the input's own left padding,
   so it reads as money at a glance — distinct from a plain qty or
   rate-% field next to it — without doubling up the control's own
   border and background inside a second box. */
.money-in { position:relative; display:inline-flex; }
.money-in .cur {
  position:absolute; left:9px; top:50%; transform:translateY(-50%);
  font-size:var(--step--1); font-weight:600; color:var(--ink-3); pointer-events:none;
}
.money-in .control { padding-left:20px; }
.qline-dims label.grow .mini-in,
.qline-money label.grow .mini-in { width:100%; }

.qline-spec { margin-top:10px; font-size:var(--step--1); }

/* Rate, quantity and what the line is charged as — the three things
   that move the total — on one row under the name. The kind toggle
   pushes to the right so the two numbers stay side by side, which is
   how they are checked. */
.qline-money {
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  margin-top:11px; padding-top:11px; border-top:1px solid var(--line-soft);
}
.qline-money .qline-kind { margin:0 0 0 auto; }
.qline-amt {
  flex:none; font-size:var(--step-1); font-weight:700; color:var(--ink); letter-spacing:-.3px;
}
.qline-money .mini-in { width:82px; }

/* ── The foot that stays put ───────────────────────────────── */
.qb-foot {
  flex:none;
  padding:13px 18px calc(var(--safe-b) + 14px);
  border-top:1px solid var(--line);
  background:var(--glass-light);
  -webkit-backdrop-filter:var(--blur); backdrop-filter:var(--blur);
}
.qb-sums { display:flex; flex-direction:column; gap:6px; margin-bottom:11px; }
.qb-sum {
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  font-size:var(--step--1); color:var(--ink-2);
}
.qb-sum b { font-weight:600; color:var(--ink); }
.qb-sum .mini-in { width:110px; }
.qb-gst { display:flex; align-items:center; gap:7px; font-size:var(--step--1); color:var(--ink-3); }
.qb-gst .mini-in { width:64px; }
.qb-sum.tot {
  padding-top:9px; border-top:1px solid var(--line);
  font-size:var(--step-0); font-weight:700; color:var(--ink);
}
.qb-sum.tot b { font-size:var(--step-3); letter-spacing:-.4px; }

/* ════════════════════════════════════════════════════════════
   DESIGN LIBRARY

   Browsing is visual on purpose — nobody remembers what BD-14 is,
   but everybody recognises the photograph.
   ════════════════════════════════════════════════════════════ */

.dgrid {
  display:grid; gap:var(--sp-sm);
  /* The track itself is on the ramp, so the tiles grow with the
     window instead of stepping up once at 900px. */
  grid-template-columns:repeat(auto-fill, minmax(min(clamp(8.5rem, 7.5rem + 5vw, 11.875rem), 100%), 1fr));
}

.dcard {
  display:flex; flex-direction:column; text-align:left;
  border-radius:var(--r-md); overflow:hidden;
  background:var(--grad-card); border:1px solid var(--line-soft);
  box-shadow:var(--e1);
  transition:transform .14s var(--ease), box-shadow var(--t-fast);
}
.dcard:active { transform:scale(.98); }
@media (hover:hover) and (pointer:fine) {
  .dcard:hover { box-shadow:var(--e2); transform:translateY(-2px); }
}
.dcard-img {
  width:100%; aspect-ratio:4/3; object-fit:cover;
  background:var(--tea-soft); display:block;
}
.dcard-img.ph { display:grid; place-items:center; color:var(--ink-3); }
.dcard-body { padding:11px 12px 12px; display:flex; flex-direction:column; gap:3px; }
.dcard-code { font-size:var(--step--2); font-weight:700; letter-spacing:.5px; color:var(--ink-3); }
.dcard-name {
  font-size:var(--step-0); font-weight:600; color:var(--ink); line-height:1.3;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.dcard-dims { font-size:var(--step--2); color:var(--ink-3); }
.dcard-foot { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:5px; }
.dcard-rate { font-size:var(--step-0); font-weight:700; color:var(--ink); }
.dcard-fin { font-size:var(--step--2); color:var(--ink-3); margin-top:2px; }

/* The photograph is the first thing a design needs, so it is the
   first thing the form asks for. */
.dphoto {
  width:100%; aspect-ratio:16/10; margin-bottom:14px;
  border-radius:var(--r-md); overflow:hidden;
  background:var(--tea-soft); border:1px dashed var(--line);
  display:grid; place-items:center; color:var(--ink-3);
}
.dphoto.has { border-style:solid; border-color:var(--line-soft); }
.dphoto img { width:100%; height:100%; object-fit:cover; }
.dphoto span { display:grid; justify-items:center; gap:7px; font-size:var(--step--1); font-weight:600; }

.fin-row { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
.fin-row .control:first-child { flex:1; min-width:0; }

.sheet-lede {
  margin:2px 0 14px; font-size:var(--step-0); line-height:1.55; color:var(--ink-2);
}

@media (prefers-reduced-motion: reduce) {
  .qrow, .qcard, .dcard, .snip-go svg, .mini, .seg-mini { transition:none; }
}

/* Money labels sit beside their field, so they must not wrap — a
   two-word label like "Lump sum" was breaking and clipping. */
.qline-money label, .qline-dims label { white-space:nowrap; }

/* A quotation is a document, and a document needs width. The
   builder and the library picker open wider than an ordinary
   sheet once there is room for it. */
@media (min-width:900px) {
  .sheet.full.wide, .sheet.wide { max-width:760px; }
}
@media (min-width:1280px) {
  .sheet.full.wide, .sheet.wide { max-width:880px; }
}

/* ════════════════════════════════════════════════════════════
   THE QUOTATION DOCUMENT

   Not a screen — a preview of the file, drawn to the same grid
   the PDF is drawn to. Everything below is in points, through
   --pt, because the printed document is specified in points:
   Letter paper, a 0.7in margin on all four sides, Montserrat at
   6.4pt, and eight columns whose edges every table snaps to. If
   a number here disagrees with one in js/quotepdf.js, the two
   documents have drifted apart and this is the copy that is
   wrong.

   The page never scrolls sideways and no table inside it does
   either: the columns are percentages of a fixed-layout table,
   so a long description wraps rather than pushing the page out.
   On a narrow screen the whole sheet is scaled down to fit — see
   fitPages() in views/quotedoc.js — which is the only honest way
   to show a Letter page on a phone.
   ════════════════════════════════════════════════════════════ */

.doc-scroll {
  background:var(--k-100); padding:16px;
  overflow-y:auto; overflow-x:hidden; -webkit-overflow-scrolling:touch;
}

/* The sheets, and the scale that fits them to the screen. The outer
   box is the size the document is *painted* at and clips to it; the
   inner one is a full-size 816px page that happens to be drawn
   small. Keeping them apart is what stops a phone from finding a
   page's worth of empty room off the right-hand edge. */
.doc-fit {
  box-sizing:content-box; margin:0 auto; padding:6px; overflow:hidden;
}
.doc-fit-in { width:816px; transform-origin:top left; }
/* Measured, cut into pages and scaled before anyone sees it — hidden
   rather than absent, so it holds its space instead of reflowing in
   front of the reader. */
.doc-fit-in.laying-out { visibility:hidden; }
.doc-sheets { display:flex; flex-direction:column; gap:16px; }

.doc-page {
  --pt:1.3333px;
  /* The gap between a rule and the text inside it — PAD in
     js/quotepdf.js. Text that runs the width of the page is inset by
     it too, or the file and the preview wrap in different places. */
  --pad:calc(1.8 * var(--pt));
  --ink:#000;
  --rule:#434343;              /* the letterhead rule, and the payment box */
  --hair:#B7B7B7;              /* the client panel and the shipping rows */
  --edge:#666666;              /* the heavier edge on the totals */
  --band:#EFEFEF;              /* behind a shaded row */

  box-sizing:border-box;
  width:calc(612 * var(--pt));
  min-height:calc(792 * var(--pt));
  padding:calc(54 * var(--pt)) calc(50.4 * var(--pt)) calc(50.4 * var(--pt));
  background:#FFFFFF;
  color:var(--ink);
  font-family:'Kontour Montserrat', ui-sans-serif, system-ui, sans-serif;
  font-size:calc(6.4 * var(--pt));
  line-height:1.219;
  box-shadow:var(--e2);
}
/* A block that starts a page has no gap above it — paginate() does its
   arithmetic on the same assumption. */
.doc-page > :first-child { margin-top:0 !important; }

/* ── Letterhead ── */
.doc-head {
  display:flex; align-items:flex-start; justify-content:space-between;
  box-sizing:border-box;
  height:calc(45 * var(--pt));
  border-bottom:calc(1.5 * var(--pt)) solid var(--rule);
}
.doc-title {
  margin:0; padding-left:var(--pad);
  font-size:calc(23 * var(--pt)); font-weight:700;
  letter-spacing:-.01em; text-transform:uppercase;
}
.doc-mark { flex:none; width:calc(44.3 * var(--pt)); height:calc(44.3 * var(--pt)); object-fit:contain; }

/* ── Who it is for, and when ── */
.doc-panels { display:flex; justify-content:space-between; margin-top:calc(10.35 * var(--pt)); }
.doc-panel { border-collapse:collapse; table-layout:fixed; }
.doc-panel.who  { width:51.5856%; }
.doc-panel.when { width:33.4615%; }
.doc-panel th, .doc-panel td {
  height:calc(10.33 * var(--pt));
  padding:calc(1.35 * var(--pt)) var(--pad);
  border:calc(.5 * var(--pt)) solid var(--hair);
  font-weight:400; text-align:left; vertical-align:middle;
  overflow-wrap:anywhere;
}
.doc-panel th { font-weight:700; }
.doc-panel.who  th { width:32.53%; }
.doc-panel.when th { width:63.65%; }
.doc-panel.when th, .doc-panel.when td { text-align:right; }
/* The address is the one field that runs long, so its cell is
   three rows deep whatever it holds. */
.doc-panel .addr th, .doc-panel .addr td { height:calc(30.99 * var(--pt)); vertical-align:top; }

.doc-stamp {
  margin:calc(6 * var(--pt)) 0 0; padding:0 var(--pad);
  font-weight:700;
}

/* ── The items ── */
.doc-items { width:100%; table-layout:fixed; border-collapse:collapse; margin-top:calc(10.1 * var(--pt)); }
.doc-items th {
  height:calc(10.82 * var(--pt));
  padding:0 var(--pad);
  background:var(--ink); color:#FFFFFF; font-weight:700;
  text-align:left; vertical-align:middle;
}
.doc-items td {
  height:calc(80.7 * var(--pt));
  padding:calc(8 * var(--pt)) var(--pad);
  vertical-align:middle; overflow-wrap:anywhere;
}
.doc-items .c-sr   { width:6.7795%;  text-align:center; }
.doc-items .c-img  { width:10.0001%; padding-left:0; padding-right:0; }
.doc-items .c-name { width:12.9319%; }
.doc-items .c-desc { width:21.8741%; }
.doc-items .c-dim  { width:14.9529%; text-align:center; font-weight:700; }
.doc-items .c-rate { width:9.7674%;  text-align:right; }
.doc-items .c-qty  { width:11.5297%; text-align:center; }
.doc-items .c-amt  { width:12.1644%; text-align:right; }
.doc-items thead .c-sr   { text-align:left; }
.doc-items thead .c-img  { text-align:center; padding:0 var(--pad); }
.doc-items thead .c-dim,
.doc-items thead .c-rate,
.doc-items thead .c-qty  { text-align:center; }
.doc-items thead .c-amt  { text-align:right; }
.doc-items thead .c-dim  { font-weight:700; }

/* The photograph is what a furniture quotation is actually looked
   at for, so it takes the whole of its column — and the same
   square every other one down the page gets, whatever shape the
   original was. */
.doc-img-btn {
  display:block; width:100%; height:calc(51.2 * var(--pt));
  padding:0; border:0; background:none; cursor:zoom-in;
}
/* A fixed box rather than one the photograph's own shape decides:
   the row is the same height either way, and a slot that does not
   change when the image finally decodes is one paginate() can measure
   before it has. */
.doc-img-btn img { display:block; width:100%; height:100%; object-fit:contain; }
.doc-line-gst { display:block; color:#6A6A72; }
.doc-empty { text-align:center; color:#8A8A93; }

/* ── Payment terms, and the ladder beside them ── */
.doc-band { display:flex; align-items:flex-start; }
.doc-pay {
  flex:0 0 66.5385%; box-sizing:border-box;
  padding:calc(4.2 * var(--pt)) var(--pad) calc(4.9 * var(--pt));
  border-top:calc(.5 * var(--pt)) solid var(--rule);
  border-bottom:calc(.5 * var(--pt)) solid var(--rule);
}
.doc-pay h2 { margin:0; font-size:1em; font-weight:700; }
.doc-ladder { flex:0 0 33.4615%; border-collapse:collapse; table-layout:fixed; }
.doc-ladder th, .doc-ladder td {
  height:calc(10.82 * var(--pt));
  padding:0 var(--pad);
  border:calc(1 * var(--pt)) solid var(--edge);
  font-weight:400; vertical-align:middle;
}
.doc-ladder th { width:63.65%; text-align:left; }
.doc-ladder td { text-align:right; }
.doc-ladder .a th, .doc-ladder .a td { background:var(--band); font-weight:700; }

/* ── Shipping, and what it all comes to ── */
.doc-ship  { width:100%; table-layout:fixed; border-collapse:collapse; margin-top:calc(20.2 * var(--pt)); }
.doc-grand { width:100%; table-layout:fixed; border-collapse:collapse; margin-top:calc(20.4 * var(--pt)); }

.doc-ship th {
  height:calc(11.32 * var(--pt)); padding:0 var(--pad);
  background:var(--band); border:calc(1 * var(--pt)) solid var(--edge);
  font-weight:700; text-align:center; vertical-align:middle;
}
.doc-ship td {
  height:calc(13.53 * var(--pt)); padding:0 var(--pad);
  border:calc(.5 * var(--pt)) solid var(--hair);
  text-align:center; vertical-align:middle; overflow-wrap:anywhere;
}
.doc-ship .c-sr { width:6.7795%; }
.doc-ship thead .c-sr { text-align:left; }
.doc-ship .c-amt { width:33.4615%; }

.doc-grand th {
  height:calc(12.3 * var(--pt));
  background:var(--ink); color:#FFFFFF; font-weight:700;
  font-size:calc(7.7 * var(--pt)); text-align:center; vertical-align:middle;
  border:calc(1 * var(--pt)) solid var(--ink);
}
.doc-grand td {
  height:calc(25.09 * var(--pt));
  border:calc(1 * var(--pt)) solid var(--ink);
  text-align:center; vertical-align:middle;
}
.doc-grand .c-a { width:29.7115%; }
.doc-grand .c-b { width:36.827%; }
.doc-total { font-size:calc(9 * var(--pt)); font-weight:700; }

/* ── Where to pay, who to ask, and the standing terms ── */
.doc-details { display:flex; justify-content:space-between; margin-top:calc(53.1 * var(--pt)); }
.doc-details section { flex:0 0 51.5856%; padding-left:var(--pad); }
.doc-details section + section { flex-basis:33.4615%; }
.doc-details h2, .doc-terms h2, .doc-note h2 { margin:0; font-size:1em; font-weight:700; }
.doc-details p { margin:calc(7.8 * var(--pt)) 0 0; }

.doc-terms { margin-top:calc(25.7 * var(--pt)); padding:0 var(--pad); }
.doc-terms h2 { margin-bottom:calc(2.6 * var(--pt)); }
.doc-clause {
  margin:0;
  padding-left:calc(4.122 * var(--pt));
  text-indent:calc(-4.122 * var(--pt));
}
.doc-aster { margin:calc(7.8 * var(--pt)) 0 0; }

/* ── The note ──
   Under the standing terms on the closing page paginate() gives the
   two of them, away from the figures. */
.doc-note { margin-top:calc(25.7 * var(--pt)); padding:0 var(--pad); }
.doc-note h2 { margin-bottom:calc(2.2 * var(--pt)); }
.doc-note p { margin:calc(7.8 * var(--pt)) 0 0; }
.doc-note p:first-of-type { margin-top:0; }

/* ── Print ───────────────────────────────────────────────────
   Everything the app puts around the document gets out of the
   way and the fit-to-screen scale is dropped. The margins move
   from the sheet onto @page, because only the printer knows
   where its own page breaks fall: a quotation long enough to run
   over gets the same 0.7in on every page that way, where a fixed
   sheet would give it to the first one and run the rest to the
   paper's edge. The note still starts a page of its own. */
@media print {
  body.printing { background:#FFFFFF !important; }
  body.printing .app,
  body.printing .gate,
  body.printing .scrim,
  body.printing .toast-root,
  body.printing .lightbox,
  body.printing .sheet-head,
  body.printing .qb-foot { display:none !important; }

  /* Only the sheet on top is the thing being printed. Reaching the
     document through the builder's Preview leaves the builder open
     underneath — sheets stack, they do not replace each other — and
     `position:static` below then laid that form out in the print
     flow ahead of the quotation, so a two-page quotation printed as
     three with the form as page one. Opening the same quotation from
     the list never showed it, because there was no second sheet.
     Sheets are <section> siblings in #sheet-root, so the last one is
     the top of the stack. */
  body.printing .sheet:not(:last-of-type) { display:none !important; }

  body.printing .sheet,
  body.printing .sheet.full {
    position:static !important;
    max-width:none !important; max-height:none !important;
    height:auto !important; box-shadow:none !important;
    border-radius:0 !important; background:#fff !important;
    overflow:visible !important; animation:none !important;
  }
  body.printing .qb,
  body.printing .qb-scroll,
  body.printing .doc-scroll {
    overflow:visible !important; height:auto !important;
    padding:0 !important; background:#fff !important;
  }
  body.printing .doc-fit,
  body.printing .doc-fit-in {
    transform:none !important; overflow:visible !important; padding:0 !important;
    width:auto !important; height:auto !important;
  }
  body.printing .doc-sheets { display:block; gap:0; }
  body.printing .doc-page {
    width:auto; min-height:0; padding:0; box-shadow:none;
  }
  body.printing .doc-page + .doc-page { break-before:page; }
  .doc-items thead { display:table-header-group; }
  .doc-items tr, .doc-ship tr, .doc-band, .doc-grand,
  .doc-terms, .doc-details { break-inside:avoid; }
  /* 54pt over, 50.4pt round the rest — the PDF's own margins. */
  @page { size:letter; margin:0.75in 0.7in 0.7in; }
}

.qline-lump {
  font-size:var(--step--1); font-weight:600; color:var(--ink-3);
  padding:var(--sp-2xs) 0;
}

/* ── Revision families, brand mark, GST switch ───────────────── */

.qcard-hist { margin-top:2px; }
.qcard-hist summary {
  cursor:pointer; list-style:none;
  font-size:var(--step--1); font-weight:600; color:var(--ink-3);
  padding:5px 0;
}
.qcard-hist summary::-webkit-details-marker { display:none; }
.qcard-hist summary::before { content:"▸ "; }
.qcard-hist[open] summary::before { content:"▾ "; }
@media (hover:hover) and (pointer:fine) {
  .qcard-hist summary:hover { color:var(--ink); }
}
.qhist {
  display:flex; align-items:center; gap:9px; width:100%;
  padding:7px 9px; margin-top:5px;
  border-radius:12px; background:var(--tea-soft);
  font-size:var(--step--1); text-align:left;
}
@media (hover:hover) and (pointer:fine) { .qhist:hover { background:var(--tea); } }
.qhist-n { font-weight:700; color:var(--ink); }
.qhist-d { color:var(--ink-3); }
.qhist-a { margin-left:auto; font-weight:600; color:var(--ink); }

.seg-mini.gst-t { margin-left:8px; padding:3px 9px; font-size:var(--step--2); }

/* The mark, wherever it appears. */
.brand-mark { object-fit:contain; border-radius:6px; display:block; }


/* A photograph, life-size in the room it opens in — everything else
   on the document is fine at a glance, this is the one thing worth a
   second look. */
.lightbox { display:flex; align-items:center; justify-content:center; padding:4px 16px 20px; }
.lightbox img { max-width:100%; max-height:72vh; border-radius:10px; display:block; }

/* The rail wears the mark beside the name. */
.tabbar .rail-brand {
  display:none;
  align-items:center; gap:10px;
  padding:0 15px 20px;
}
@media (min-width:900px) {
  .tabbar.has-logo::before { display:none; }
  .tabbar.has-logo .rail-brand { display:flex; order:-2; }
  .rail-brand-t { font-size:var(--step-2); font-weight:700; letter-spacing:-.3px; color:var(--pine-700); }
}

.gate-logo { width:64px; height:64px; object-fit:contain; margin:0 auto 4px; display:block; }
.logo-drop { aspect-ratio:auto; min-height:150px; }
.logo-drop img { object-fit:contain; padding:14px; }

/* A line's image is picked in place, so the slot is a button. */
.qline-imgwrap { position:relative; flex:none; display:block; }
button.qline-img {
  width:38px; height:38px; border-radius:11px; padding:0;
  display:grid; place-items:center; overflow:hidden;
  background:var(--tea-soft); color:var(--ink-3);
  border:1px dashed var(--line);
  transition:border-color var(--t-fast), background var(--t-fast);
}
button.qline-img.has { border-style:solid; border-color:var(--line-soft); }
button.qline-img img { width:100%; height:100%; object-fit:cover; }
@media (hover:hover) and (pointer:fine) {
  button.qline-img:hover { border-color:var(--pine-500); background:var(--tea); }
}
.qline-img-x {
  position:absolute; top:-6px; right:-6px;
  width:18px; height:18px; border-radius:50%;
  background:var(--pine-700); color:var(--beige);
  font-size:var(--step--1); line-height:1; display:grid; place-items:center;
  box-shadow:var(--e1);
}

/* The entry button appears only inside Phynance. It sits outside the
   bar now, so leaving Phynance drops nothing out of the row of
   destinations — the three tabs hold their places and the button
   scales away on its own. */
.fab.off {
  opacity:0; visibility:hidden; pointer-events:none;
  transform:scale(.6);
}

/* ── The builder's foot, compacted ───────────────────────────
   One line: what it comes to, and the way out. The ladder opens
   above it only when you ask for it. */
.qb-bar {
  display:flex; align-items:center; gap:12px;
  flex-wrap:wrap;
}
.qb-brk {
  display:inline-flex; align-items:center; gap:6px;
  flex:1; min-width:0;
  min-height:2rem;
  padding:var(--sp-3xs) 2px; border-radius:9px;
  font-size:var(--step--1); font-weight:500; color:var(--ink-3);
  text-align:left;
}
.qb-brk span {
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.qb-brk svg { flex:none; transition:transform var(--t-fast) var(--ease); }
.qb-brk[aria-expanded="true"] svg { transform:rotate(180deg); }
@media (hover:hover) and (pointer:fine) {
  .qb-brk:hover { color:var(--ink); }
}

.qb-tot { display:flex; align-items:baseline; gap:8px; flex:none; }
.qb-tot span { font-size:var(--step--1); font-weight:600; color:var(--ink-3); }
.qb-tot b { font-size:var(--step-3); font-weight:700; color:var(--ink); letter-spacing:-.4px; }

.btn.sm { width:auto; flex:none; padding:11px 26px; margin:0; }

/* The ladder, when opened, is a panel above the bar rather than the
   bar itself — so opening it never moves the Total or the button. */
.qb-foot .qb-sums {
  margin-bottom:10px; padding-bottom:10px;
  border-bottom:1px solid var(--line);
}

@media (max-width:420px) {
  .qb-brk { flex-basis:100%; order:3; }
  .qb-tot { flex:1; }
}

/* The card's long tail of actions moved into a sheet — see .actlist.
   A dropdown anchored to a card in a scrolling list had to be closed
   as carefully as it was opened, and on a phone it opened under the
   thumb that had just tapped it. */

/* ═══════════════════════════════════════════════════════════════
   THE SIDEBAR SHELL
   ═══════════════════════════════════════════════════════════════
   Kontour is a production unit before it is an app, and a
   production unit has one order to it. The sidebar is that order,
   written down once: quotation, production, sub-contractor, QC,
   shipping, archive, top to bottom, in the sequence work actually
   moves. Nothing about the screen you are on rearranges it.

   It is a real column on a desktop and a drawer on a phone —
   the same markup either way, because a second navigation is a
   second thing to keep true. */

/* On a phone this is the one thing in the shell that genuinely has
   to leave the flow: a drawer is a layer over the work by
   definition, and a grid track for it would push the work aside
   instead of covering it. Above 900px it stops being a layer, and
   the rule below hands it back to the grid as an ordinary column. */
.sidebar {
  position:fixed; top:0; bottom:0; left:0; z-index:60;
  width:var(--rail-w); max-width:86vw;
  display:flex; flex-direction:column;
  padding:calc(var(--safe-t) + var(--sp-lg)) var(--sp-sm) calc(var(--safe-b) + var(--sp-md));
  background:var(--grad-pine-deep);
  color:var(--beige);
  border-radius:0 26px 26px 0;
  /* Off-canvas until asked for. A phone gets the work, not the map. */
  transform:translate3d(-102%,0,0);
  transition:transform var(--t-mid) var(--ease);
  overflow-y:auto; overscroll-behavior:contain;
}
.sidebar::-webkit-scrollbar { width:0; }
body.nav-open .sidebar { transform:none; }

.nav-scrim {
  position:fixed; inset:0; z-index:55;
  background:rgba(var(--shade-rgb), .5);
  -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px);
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity var(--t-mid) var(--ease), visibility var(--t-mid);
}
body.nav-open .nav-scrim { opacity:1; visibility:visible; pointer-events:auto; }

/* ── Brand ──────────────────────────────────────────────────── */
.side-brand {
  display:flex; align-items:center; gap:10px;
  padding:6px 10px 22px;
}
.side-mark { width:30px; height:30px; border-radius:9px; object-fit:cover; flex:none; }
.side-word {
  font-size:var(--step-1); font-weight:800; letter-spacing:1.4px;
  color:var(--beige); flex:1; min-width:0;
}
.side-shut {
  width:32px; height:32px; border-radius:11px; flex:none;
  display:grid; place-items:center; color:var(--k-300);
  background:rgba(var(--paper-rgb), .07);
}
.side-shut:active { transform:scale(.92); }

/* ── The stations ───────────────────────────────────────────── */
.side-nav { flex:1; display:flex; flex-direction:column; gap:2px; }
.side-lbl {
  margin:16px 0 7px; padding:0 12px;
  font-size:var(--step--2); font-weight:600; letter-spacing:1.1px;
  text-transform:uppercase; color:var(--k-500);
}
.side-lbl:first-child { margin-top:0; }

.side-item {
  display:flex; align-items:center; gap:13px;
  width:100%; padding:11px 12px; border-radius:14px;
  font-size:var(--step-0); font-weight:500; letter-spacing:.1px;
  color:var(--k-300); text-align:left;
  position:relative;
  transition:background var(--t-fast), color var(--t-fast);
}
.side-item svg { flex:none; }
.side-item span { flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.side-item:active { transform:scale(.985); }

/* The live station: the one lit row, with the accent bar the
   printed schedule would have put a tick against. */
.side-item.on {
  background:rgba(var(--paper-rgb), .10);
  color:var(--beige); font-weight:600;
}
.side-item.on::before {
  content:""; position:absolute; left:-14px; top:50%;
  width:3px; height:20px; margin-top:-10px;
  border-radius:0 3px 3px 0; background:var(--beige);
}

.side-foot {
  padding-top:12px; margin-top:10px;
  border-top:1px solid rgba(var(--paper-rgb), .08);
}

@media (hover:hover) and (pointer:fine) {
  .side-item:not(.on):hover { background:rgba(var(--paper-rgb), .06); color:var(--k-150); }
}

/* ── The phone's own header ─────────────────────────────────── */
.brandbar-menu {
  width:30px; height:30px; flex:none; margin-right:2px;
  display:flex; flex-direction:column; justify-content:center; gap:4px;
  padding:0 5px;
}
.brandbar-menu span {
  display:block; height:1.6px; border-radius:2px;
  background:var(--beige); opacity:.85;
}
.brandbar-menu span:last-child { width:65%; }
.brandbar-menu:active { transform:scale(.9); }

/* ── Desktop: the drawer stops being a drawer ─────────────────
   One declaration does the whole switch: the rail track opens to
   the rail's width and the sidebar becomes the column that fills
   it. Nothing else needs telling. The screen, the glass bar and
   the entry button are all placed in cells to the right of that
   track, so they inset themselves — where each of them used to
   carry its own `left:var(--rail-w)`, and each was one edit away
   from disagreeing with the others. */
@media (min-width:900px) {
  .app { --rail-track:var(--rail-w); }

  .sidebar {
    position:static;
    grid-area:rail;
    transform:none; border-radius:0; width:auto; max-width:none;
  }
  .nav-scrim { display:none !important; }
  .side-shut { display:none; }
  .brandbar { display:none; }
  .tabbar { display:none; }
}

/* The floating button never stands in the rail: the rail is a list
   of destinations and this is an action. It keeps the same corner of
   the work at every size, and because that corner is a grid cell's
   corner it needs no offsets of its own — the three sets of
   `right/bottom/width/height` that used to restate it at each
   breakpoint are gone with them. */
.fab-t { display:none; }

/* ═══════════════════════════════════════════════════════════════
   THE PRODUCTION FLOOR
   ═══════════════════════════════════════════════════════════════
   Phynance keeps the dark hero band — it is about money and wants
   the weight. The floor is paperwork, and paperwork sits on cream
   under a plain heading, the way the job sheet on the bench does.
   See js/views/chrome.js for the pieces these dress. */

.floor {
  padding:22px 18px calc(var(--safe-b) + 96px);
  max-width:var(--content-w); margin-inline:auto;
}

/* ── Page heading ───────────────────────────────────────────── */
.pagehead {
  display:flex; align-items:flex-start; justify-content:space-between;
  flex-wrap:wrap;
  gap:var(--sp-md); margin-bottom:var(--sp-lg);
}
.pagehead-txt { min-width:0; flex:1 1 12rem; }
.pagehead h1 {
  margin:0; font-size:var(--step-5); font-weight:700;
  letter-spacing:-1px; line-height:1.08; color:var(--ink);
  /* The title and its button used to compete for the width and the
     query at 430px settled it by shrinking both. They wrap now. */
  overflow-wrap:break-word;
}
.pagehead p {
  margin:var(--sp-2xs) 0 0; font-size:var(--step-1); color:var(--ink-3); font-weight:500;
}
.pagehead-acts { display:flex; align-items:center; gap:var(--sp-2xs); flex:none; }

/* The one black pill per screen — the reference's own rule, and a
   good one: if everything is an action, nothing is. */
.pill-btn {
  display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-2xs);
  min-height:var(--tap);
  padding:var(--sp-2xs) var(--sp-md); border-radius:999px;
  background:var(--k-900); color:#fff;
  font-size:var(--step-1); font-weight:600; white-space:nowrap;
  box-shadow:var(--e1);
  transition:transform .12s var(--ease), opacity var(--t-fast);
}
.pill-btn:active { transform:scale(.96); }
.pill-btn.ghost {
  background:var(--paper); color:var(--ink);
  border:1px solid var(--line);
}

/* ── The figures across the top ─────────────────────────────── */
.statcards {
  display:grid; gap:var(--sp-sm); margin-bottom:var(--sp-lg);
  /* The track is itself on a ramp, so the tiles get steadily bigger
     as the window does instead of the count jumping at 430 and
     1280. The 8.5rem floor is what keeps two tiles on a 320px
     phone, which is the density the board was drawn at. */
  grid-template-columns:repeat(auto-fit, minmax(min(clamp(8.5rem, 6.5rem + 10vw, 16.25rem), 100%), 1fr));
}
.statcard {
  position:relative; text-align:left;
  display:flex; flex-direction:column; gap:3px; min-width:0;
  padding:var(--sp-md); border-radius:20px;
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1);
  transition:transform .14s var(--ease), box-shadow var(--t-fast);
}
.statcard.go { cursor:pointer; }
.statcard.go:active { transform:scale(.985); }
@media (hover:hover) and (pointer:fine) {
  .statcard.go:hover { transform:translateY(-2px); box-shadow:var(--e2); }
}
.statcard-l {
  font-size:var(--step--1); font-weight:600; letter-spacing:.9px;
  text-transform:uppercase; color:var(--ink-3);
}
/* The chevron floats over the corner, so a long label (ACTIVE ORDER
   VALUE) has to be told to stop short of it rather than run underneath. */
.statcard.go .statcard-l { padding-right:var(--sp-md); }
.statcard-v {
  font-size:clamp(1.3125rem, 1.05rem + 1.3vw, 1.6875rem);
  font-weight:700; letter-spacing:-1px; color:var(--ink);
  line-height:1.1; margin-top:2px;
}
.statcard-v .cur { font-size:var(--step-2); font-weight:500; opacity:.5; margin-right:1px; }
.statcard-h { font-size:var(--step-0); color:var(--ink-3); font-weight:500; }
.statcard-go {
  position:absolute; top:14px; right:13px; color:var(--ink-3); opacity:.5;
}

/* Each station's own tint, used for its figure and its cards alike,
   so one colour means one place all the way down the line. */
.t-quote { background:var(--st-quote); border-color:transparent; }
.t-prod  { background:var(--st-prod);  border-color:transparent; }
.t-sub   { background:var(--st-sub);   border-color:transparent; }
.t-qc    { background:var(--st-qc);    border-color:transparent; }
.t-ship  { background:var(--st-ship);  border-color:transparent; }
.t-done  { background:var(--st-done);  border-color:transparent; }
.t-quote .statcard-l, .t-quote .statcard-h { color:var(--st-quote-ink); opacity:.8; }
.t-prod  .statcard-l, .t-prod  .statcard-h { color:var(--st-prod-ink);  opacity:.8; }
.t-sub   .statcard-l, .t-sub   .statcard-h { color:var(--st-sub-ink);   opacity:.8; }
.t-qc    .statcard-l, .t-qc    .statcard-h { color:var(--st-qc-ink);    opacity:.8; }
.t-ship  .statcard-l, .t-ship  .statcard-h { color:var(--st-ship-ink);  opacity:.8; }
.t-done  .statcard-l, .t-done  .statcard-h { color:var(--st-done-ink);  opacity:.8; }

/* ── Section heading ────────────────────────────────────────── */
/* Heading on one side, the sort row on the other — until there is
   no longer room for both, at which point they wrap and the sorts
   take their own line. `flex-wrap` decides that from the content's
   real width, which is what the 430px query was guessing at. */
.secthead {
  display:flex; align-items:baseline; justify-content:space-between;
  flex-wrap:wrap;
  gap:var(--sp-xs) var(--sp-sm); margin:var(--sp-lg) 0 var(--sp-sm);
}
.secthead:first-child { margin-top:0; }
.secthead h2 {
  margin:0; font-size:var(--step-2); font-weight:700; letter-spacing:-.2px; color:var(--ink);
}
.secthead .sec-link { font-size:var(--step-0); font-weight:600; color:var(--ink-3); }

/* ── One order, at whatever station it is standing in ───────── */
.olist {
  display:grid; gap:var(--sp-xs);
  grid-template-columns:repeat(auto-fill, minmax(min(21.25rem, 100%), 1fr));
}

.ocard {
  position:relative; overflow:hidden;
  display:flex; align-items:center; gap:12px; width:100%; min-width:0;
  padding:15px 15px 15px 20px; border-radius:19px;
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1); text-align:left; cursor:pointer;
  transition:transform .14s var(--ease), box-shadow var(--t-fast);
}
/* The stripe is the station. It is the only thing that changes as a
   card moves down the line, so the same order stays recognisable. */
.ocard::before {
  content:""; position:absolute; left:0; top:0; bottom:0; width:5px;
  background:var(--k-200);
}
.ocard.t-quote::before { background:var(--st-quote-ink); }
.ocard.t-prod::before  { background:var(--st-prod-ink); }
.ocard.t-sub::before   { background:var(--st-sub-ink); }
.ocard.t-qc::before    { background:var(--st-qc-ink); }
.ocard.t-ship::before  { background:var(--st-ship-ink); }
.ocard.t-done::before  { background:var(--st-done-ink); }
/* The tint classes double as card backgrounds elsewhere; on a card
   the stripe carries the colour and the ground stays paper. */
.ocard.t-quote, .ocard.t-prod, .ocard.t-sub,
.ocard.t-qc, .ocard.t-ship, .ocard.t-done {
  background:var(--paper); border-color:var(--line-soft);
}
.ocard:active { transform:scale(.99); }
.ocard:focus-visible { outline:2px solid var(--ink); outline-offset:2px; }
@media (hover:hover) and (pointer:fine) {
  .ocard:hover { box-shadow:var(--e2); transform:translateY(-1px); }
}
.ocard-main { flex:1; min-width:0; }
.ocard-t {
  font-size:var(--step-1); font-weight:700; letter-spacing:-.2px; color:var(--ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.ocard-m {
  display:flex; align-items:center; flex-wrap:wrap; gap:5px;
  margin-top:3px; font-size:var(--step--1); color:var(--ink-3);
}
.ocard-mr { font-weight:700; color:var(--ink-2); letter-spacing:.2px; }
.ocard-dot {
  width:3px; height:3px; border-radius:50%; background:var(--ink-3);
  opacity:.55; flex:none;
}
.ocard-flag { margin-top:7px; display:flex; flex-wrap:wrap; gap:5px; }
.ocard-side { display:flex; align-items:center; gap:9px; flex:none; }
.ocard-go { color:var(--ink-3); opacity:.45; display:flex; }

/* ── A station still being specified ────────────────────────── */
.comingup {
  padding:18px 19px; border-radius:19px;
  background:var(--paper); border:1px dashed var(--line);
  margin-bottom:20px;
}
.comingup p {
  margin:0 0 7px; font-size:var(--step-0); line-height:1.55; color:var(--ink-2);
}
.comingup p:last-child { margin-bottom:0; }
.comingup p:first-child { font-weight:600; color:var(--ink); }

/* ── Sub-contractor ─────────────────────────────────────────── */
.vgrid {
  display:grid; gap:var(--sp-xs);
  grid-template-columns:repeat(auto-fill, minmax(min(16.25rem, 100%), 1fr));
}
.vcard {
  display:flex; align-items:center; gap:13px; width:100%; text-align:left;
  padding:15px; border-radius:19px;
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1); cursor:pointer;
  transition:transform .14s var(--ease), box-shadow var(--t-fast);
}
.vcard:active { transform:scale(.99); }
@media (hover:hover) and (pointer:fine) {
  .vcard:hover { box-shadow:var(--e2); transform:translateY(-1px); }
}
.vcard-ini {
  width:42px; height:42px; flex:none; border-radius:14px;
  display:grid; place-items:center;
  background:var(--st-sub); color:var(--st-sub-ink);
  font-size:var(--step-0); font-weight:700; letter-spacing:.4px;
}
.vcard-txt { flex:1; min-width:0; }
.vcard-n {
  font-size:var(--step-0); font-weight:700; color:var(--ink); letter-spacing:-.2px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.vcard-s { font-size:var(--step--1); color:var(--ink-3); margin-top:2px; }
.vcard-c { font-size:var(--step-2); font-weight:700; color:var(--ink); letter-spacing:-.5px; flex:none; }

/* ── Trade tags ─────────────────────────────────────────────── */
.tags { display:flex; flex-wrap:wrap; gap:5px; }
.tag {
  font-size:var(--step--2); font-weight:600; letter-spacing:.3px;
  padding:4px 9px; border-radius:999px;
  background:var(--tea-soft); color:var(--ink-2);
  text-transform:capitalize;
}

/* A page of pieces rather than orders — QC and Shipping both work
   at that grain, so they share the row. */
.plist { display:grid; gap:9px; }
.prow {
  display:flex; align-items:center; gap:12px; width:100%; text-align:left;
  padding:13px 15px; border-radius:17px;
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1); cursor:pointer;
}
.prow:active { transform:scale(.99); }
.prow-txt { flex:1; min-width:0; }
.prow-t {
  font-size:var(--step-0); font-weight:600; color:var(--ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.prow-s { font-size:var(--step--1); color:var(--ink-3); margin-top:2px; }
.prow-qty {
  flex:none; font-size:var(--step--1); font-weight:600; color:var(--ink-2);
  padding:4px 9px; border-radius:999px; background:var(--tea-soft);
}

/* ── One piece, inside an order ─────────────────────────────── */
.odetail-head { margin-bottom:16px; }
.odetail-dates {
  display:grid; grid-template-columns:repeat(3, 1fr); gap:10px;
  padding:14px 16px; border-radius:17px;
  background:var(--paper); border:1px solid var(--line-soft);
}
.odetail-dates dt {
  font-size:var(--step--2); font-weight:600; letter-spacing:.8px;
  text-transform:uppercase; color:var(--ink-3); margin-bottom:3px;
}
.odetail-dates dd { margin:0; font-size:var(--step-0); font-weight:600; color:var(--ink); }

.piece {
  position:relative; overflow:hidden;
  padding:15px 16px 12px 18px; border-radius:19px; margin-bottom:11px;
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1);
}
.piece::before {
  content:""; position:absolute; left:0; top:0; bottom:0; width:5px;
  background:var(--k-200);
}
.piece.t-prod::before { background:var(--st-prod-ink); }
.piece.t-qc::before   { background:var(--st-qc-ink); }
.piece.t-ship::before { background:var(--st-ship-ink); }
.piece.t-done::before { background:var(--st-done-ink); }

.piece-top { display:flex; align-items:flex-start; gap:10px; }
.piece-id { flex:1; min-width:0; }
.piece-n { font-size:var(--step-0); font-weight:700; letter-spacing:-.2px; color:var(--ink); }
.piece-spec {
  margin:4px 0 0; font-size:var(--step--1); line-height:1.5; color:var(--ink-3);
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}

.piece-stage {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  margin-top:11px; padding:9px 0 3px;
  border-top:1px solid var(--line-soft);
}
.piece-stage > span {
  font-size:var(--step--1); font-weight:600; letter-spacing:.4px;
  text-transform:uppercase; color:var(--ink-3); flex:none;
}
.piece-stage select {
  flex:1; max-width:190px; text-align:right;
  font-size:var(--step-0); font-weight:600; color:var(--ink);
}

.piece-specs { margin:6px 0 2px; }
.piece-specs > div {
  display:flex; justify-content:space-between; gap:12px;
  padding:7px 0; border-bottom:1px solid var(--line-soft);
}
.piece-specs > div:last-child { border-bottom:0; }
.piece-specs dt { font-size:var(--step--1); color:var(--ink-3); flex:none; }
.piece-specs dd {
  margin:0; font-size:var(--step--1); font-weight:600; color:var(--ink-2);
  text-align:right; min-width:0;
}
.piece-vendors { padding-top:4px; }

.mini.ok { color:var(--in); }
.mini.danger-txt { color:var(--out); margin-top:8px; }

/* ── QC's own row: two decisions, one tap each ──────────────── */
.qcrow { align-items:center; }
.qcrow-acts { display:flex; align-items:center; gap:7px; flex:none; }
@media (max-width:520px) {
  .qcrow { flex-wrap:wrap; }
  .qcrow-acts { width:100%; justify-content:flex-end; }
}

/* On cream, the chip row and the search field both need their own
   ground back — they were drawn for a white panel. */
.floor .searchbar {
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1); margin-bottom:14px;
}
.floor .chipbar { margin-bottom:4px; }
.floor .dgrid { margin-top:4px; }
.floor .empty {
  background:var(--paper); border-radius:20px; border:1px solid var(--line-soft);
}

/* Both of these stack a title over a caption, and both are built
   from spans so the whole row can be one button. Spans run inline
   unless told otherwise, which put the caption alongside the title. */
.vcard-txt, .prow-txt { display:flex; flex-direction:column; }
.vcard-n, .vcard-s, .prow-t, .prow-s { display:block; }

/* ── Quotation list: line items, not cards ──────────────────────
   One row per job: who and what on the left, the total and the one
   action a row earns on the right. Earlier rounds still fold under
   it, the way they did as a card. */
.qrows { display:flex; flex-direction:column; gap:8px; }
.qrow2 {
  display:flex; align-items:center; gap:12px; width:100%; min-width:0;
  padding:13px 14px; border-radius:16px; text-align:left; cursor:pointer;
  background:var(--paper); border:1px solid var(--line-soft);
  box-shadow:var(--e1);
  transition:transform .12s var(--ease), box-shadow var(--t-fast);
}
.qrow2:active { transform:scale(.99); }
.qrow2:focus-visible { outline:2px solid var(--ink); outline-offset:2px; }
@media (hover:hover) and (pointer:fine) {
  .qrow2:hover { box-shadow:var(--e2); }
}
.qrow2-main { flex:1; min-width:0; }
.qrow2-top { display:flex; align-items:center; gap:8px; }
.qrow2-client {
  font-size:var(--step-0); font-weight:700; color:var(--ink); letter-spacing:-.1px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0;
}
.qrow2-meta {
  display:flex; align-items:center; flex-wrap:wrap; gap:5px;
  margin-top:3px; font-size:var(--step--1); color:var(--ink-3);
}
.qrow2-side { display:flex; flex-direction:column; align-items:flex-end; gap:6px; flex:none; }
.qrow2-amt { font-size:var(--step-0); font-weight:700; color:var(--ink); letter-spacing:-.2px; white-space:nowrap; }
.qrow2-acts { display:flex; align-items:center; gap:6px; }
.icon-btn.sm { width:32px; height:32px; border-radius:10px; }

/* ── The sort control in the section's own headline ─────────── */
.sortbar { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
.sortbtn {
  display:inline-flex; align-items:center; gap:3px;
  font-size:var(--step--1); font-weight:600; color:var(--ink-3);
  padding:5px 9px; border-radius:999px;
  transition:background var(--t-fast), color var(--t-fast);
}
.sortbtn.on { background:var(--tea-soft); color:var(--ink); }
.sortbtn svg { flex:none; }

/* ── Quotation list: the table, for a screen with columns ────────
   The same list the cards below hold, laid out in columns whose
   headings are the sorter. Cards and table are both in the page and
   the width decides which one is on screen — one list, one sort, one
   selection, drawn twice rather than reasoned about twice. */
.qtable-wrap { display:none; }
.qrows .ckbox, .olist .ckbox { display:flex; }

@media (min-width:760px) {
  .qtable-wrap {
    display:block; overflow-x:auto;
    background:var(--paper); border:1px solid var(--line-soft);
    border-radius:18px; box-shadow:var(--e1);
  }
  /* The cards are the same list; only one of the two is ever on
     screen. On the Archive the completed-orders tab keeps its own
     .olist, which has no table beside it to hide it. */
  .qrows, .qtable-wrap + .olist, .sortbar.only-narrow { display:none; }
}

.qtable { width:100%; border-collapse:collapse; font-size:var(--step-0); }
.qtable th, .qtable td {
  padding:12px 14px; text-align:left; vertical-align:middle;
  border-bottom:1px solid var(--line-soft);
}
.qtable thead th { padding-top:14px; padding-bottom:10px; }
.qtable tbody tr:last-child td { border-bottom:0; }

/* The heading is the control. It reads as a label until it is the
   one sorting, so the column in charge of the order is obvious
   without a second row of chips saying so. */
.qt-sort {
  display:inline-flex; align-items:center; gap:5px;
  font-size:var(--step--1); font-weight:600; letter-spacing:.02em;
  color:var(--ink-3); background:none; border:0; padding:0; cursor:pointer;
  transition:color var(--t-fast);
}
.qt-sort:hover { color:var(--ink-2); }
.qt-sort.on { color:var(--ink); }
.qt-sortmark { display:inline-flex; opacity:.35; }
.qt-sortmark.on { opacity:1; }

.qt-row { cursor:pointer; transition:background var(--t-fast); }
.qt-row:focus-visible { outline:2px solid var(--ink); outline-offset:-2px; }
@media (hover:hover) and (pointer:fine) {
  .qt-row:hover { background:var(--k-050); }
}
.qt-row.picked { background:var(--tea-soft); }

.qt-no { font-weight:700; color:var(--ink); letter-spacing:-.1px; }
.qt-sub { margin-top:2px; font-size:var(--step--1); color:var(--ink-3); }
.qt-client { color:var(--ink-2); }
.qt-date { color:var(--ink-3); white-space:nowrap; }
.qtable td.qt-amt { font-weight:700; color:var(--ink); white-space:nowrap; text-align:right; }
.qtable th.qt-amt { text-align:right; }
.qtable th.qt-amt .qt-sort { flex-direction:row-reverse; }
.qt-st { white-space:nowrap; }
.qt-check { width:1%; padding-right:0; }
.qt-acts { width:1%; white-space:nowrap; }
.qt-acts .icon-btn { display:inline-flex; }

/* ── The tick ────────────────────────────────────────────────── */
.ckbox { display:inline-flex; align-items:center; flex:none; cursor:pointer; }
.ckbox input { position:absolute; opacity:0; width:0; height:0; }
.ckbox-box {
  display:inline-flex; align-items:center; justify-content:center;
  width:18px; height:18px; border-radius:6px;
  border:1.5px solid var(--line); background:var(--paper);
  color:transparent; transition:background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.ckbox-box svg { stroke-width:3; }
.ckbox input:checked + .ckbox-box,
.ckbox input:indeterminate + .ckbox-box {
  background:var(--ink); border-color:var(--ink); color:var(--on-accent);
}
/* Some but not all: a tick would claim the whole list is selected,
   so the partial state gets a bar of its own instead. */
.ckbox input:indeterminate + .ckbox-box svg { display:none; }
.ckbox input:indeterminate + .ckbox-box::after {
  content:''; width:9px; height:2px; border-radius:1px; background:var(--on-accent);
}
.ckbox input:focus-visible + .ckbox-box { outline:2px solid var(--ink); outline-offset:2px; }
.qrow2.picked, .ocard.picked { border-color:var(--ink); }

/* ── The bulk bar ────────────────────────────────────────────────
   In the page from the start and hidden while nothing is ticked, so
   the first tick does not push the list down under the finger that
   made it. */
.bulkbar {
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  margin:0 0 10px; padding:9px 12px; border-radius:14px;
  background:var(--ink); color:var(--k-050);
  box-shadow:var(--e2);
}
.bulkbar[hidden] { display:none; }
.bulkbar-n { font-size:var(--step--1); font-weight:700; letter-spacing:.01em; white-space:nowrap; }
.bulkbar-acts { display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-left:auto; }
.bulkbtn {
  display:inline-flex; align-items:center; gap:6px;
  font-size:var(--step--1); font-weight:600; color:var(--k-050);
  padding:6px 10px; border-radius:999px; border:0; cursor:pointer;
  background:rgba(255,255,255,.12);
  transition:background var(--t-fast);
}
.bulkbtn:hover { background:rgba(255,255,255,.22); }
.bulkbtn.danger { background:rgba(220,80,80,.30); }
.bulkbtn.danger:hover { background:rgba(220,80,80,.45); }
.bulkbtn.ghost { background:none; padding:6px; }
@media (max-width:520px) {
  .bulkbtn span { display:none; }
  .bulkbtn { padding:8px; }
  .bulkbtn.ghost { padding:8px; }
}


/* ═══════════════════════════════════════════════════════════════
   PHONES — nothing left to say
   ═══════════════════════════════════════════════════════════════
   There were two blocks here, at ≤430px and ≤360px, and between
   them they re-stated about seventy sizes: the page head, the
   station tiles, the cards, the rows, the fields, the chips, the
   keypads, the entry button. They existed because the base scale
   was drawn for the widest window and every narrow one had to be
   walked back down by hand — and because the sheet had grown in
   layers, they had to sit last in the file to win on specificity
   at all.

   The ramp starts at the phone and grows from there, so the phone
   size is now the *lower bound* of each clamp() rather than a
   correction applied after the fact. Nothing needs restating, and
   what used to be decided by a width — the page head's button
   dropping below its title, the sort row leaving the heading's
   line — is decided by flex-wrap from the content's own width,
   which is right on a 320px phone and a 280px foldable alike.

   One thing genuinely is a phone decision and it is kept: a row of
   sort buttons scrolls sideways rather than wrapping into a block,
   because wrapping it pushes the list itself off the screen.
   ═══════════════════════════════════════════════════════════════ */
.sortbar {
  flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
  overscroll-behavior-x:contain;
}
.sortbar::-webkit-scrollbar { display:none; }
@media (min-width:900px) {
  .sortbar { flex-wrap:wrap; overflow-x:visible; }
}

/* ── Sub-contractor ───────────────────────────────────────────
   The work order screens. A piece is recognised by its photograph
   here more than by its name, so the row carries a real thumbnail
   rather than an icon, and the pick list is built around it. */

.prow .thumb {
  width:46px; height:46px; border-radius:11px;
  flex:none; box-shadow:none; animation:none;
  background:var(--pine-100);
}
.prow .thumb:empty::after {
  content:''; display:block; width:100%; height:100%;
  background:
    linear-gradient(135deg, transparent 46%, rgba(var(--shade-rgb),.13) 46%, rgba(var(--shade-rgb),.13) 54%, transparent 54%);
}

/* What is still owed, on the person's card. Nothing owed shows an
   em dash rather than a zero — a settled account should read as
   settled, not as a number to check. */
.vcard-c.due { color:var(--out); }

/* Ticking pieces to send out. The whole row is the target, because
   a checkbox on a phone in a workshop is a missed tap. */
.prow.pick { cursor:pointer; }
.prow.pick .tick {
  flex:none; width:22px; height:22px; border-radius:50%;
  border:2px solid var(--line); background:transparent;
  transition:background .16s var(--ease), border-color .16s var(--ease);
}
.prow.pick.on { border-color:var(--pine-500); background:var(--tea-soft); }
.prow.pick.on .tick {
  border-color:var(--pine-500); background:var(--pine-500);
  box-shadow:inset 0 0 0 3px var(--paper);
}

/* A rate typed against a piece. The row is a form, not a button, so
   it does not press when the number field is tapped. */
.prow.rate-row { cursor:default; }
.prow.rate-row:active { transform:none; }
.prow.rate-row .inp { margin:0; padding:9px 10px; text-align:right; }

/* A piece's photograph, shown big enough to actually check it. */
.photo-wide {
  border-radius:16px; overflow:hidden; margin-bottom:16px;
  background:var(--pine-100); box-shadow:var(--e1);
}
.photo-wide img { width:100%; display:block; max-height:260px; object-fit:cover; }

/* Something the screen has to say before the numbers make sense —
   a blacklisting, an archived record, a figure the import could not
   state cleanly. */
.notice {
  border-radius:14px; padding:12px 14px; margin-bottom:14px;
  background:var(--tea-soft); border:1px solid var(--line);
}
.notice strong { display:block; font-size:var(--step--1); font-weight:700; }
.notice p { margin:4px 0 0; font-size:var(--step--1); color:var(--ink-2); }
.notice .hint { margin-top:6px; }
.notice.bad { background:var(--out-soft); border-color:transparent; }
.notice.bad strong { color:var(--out); }
.notice.warn { background:var(--warn-soft); border-color:transparent; }
.notice.warn strong { color:var(--warn); }

/* Work already commissioned against a piece, shown under the names
   on the order sheet: who holds it, on which work order, at what
   rate. A row, not a field — it opens the work order. */
.piece-commissioned { margin-top:8px; border-top:1px solid var(--line-soft); }
.qpair.as-row {
  width:100%; background:transparent; border:0; cursor:pointer;
  text-align:left; font:inherit;
}
.qpair.as-row:active { background:var(--tea-soft); }
.qpair-v { flex:1; min-width:0; text-align:right; font-size:var(--step--1); color:var(--ink-2); font-weight:600; }

/* Three figures that belong together — ordered, paid, remaining —
   read as one row on a screen with the room for it and stack on a
   phone, rather than two side by side and a third stranded below. */
.kpis.three { grid-template-columns:1fr; }
@media (min-width:520px) {
  .kpis.three { grid-template-columns:repeat(3, 1fr); }
}

/* A sheet on a desktop screen was staying phone-width: 560px in the
   middle of a 2000px monitor, which left the sub-contractor board
   and the work order — both of them tables — squeezed into a column
   with everything wrapping. Anything marked `wide` already opened
   wider once the quotation builder needed it; these use the same
   rule, and below it the plain sheets are unchanged. */
@media (min-width:900px) {
  .sheet.full.wide .sheet-body,
  .sheet.wide .sheet-body { padding-left:22px; padding-right:22px; }
}

/* ── The floor: one piece, and what has been said about it ──────
   The piece board (views/piecework.js) is the same shape wherever
   it opens from — the sub-contractor's tab or QC's rework list —
   so it is styled once here rather than twice there. */

.piece-head { display:flex; gap:14px; align-items:flex-start; }
.piece-img {
  width:96px; height:96px; flex:none; border-radius:14px; overflow:hidden;
  object-fit:cover; background:var(--tea-soft);
  display:flex; align-items:center; justify-content:center; color:var(--ink-3);
}
.piece-meta { flex:1; min-width:0; }
.piece-t { font-size:var(--step-1); font-weight:700; color:var(--ink); }
.piece-s { font-size:var(--step--1); color:var(--ink-3); margin-top:3px; }

/* Four verdicts, always in the same order and always all four
   visible: the one that is missing is the one nobody records. */
.verdicts {
  display:grid; gap:var(--sp-2xs);
  grid-template-columns:repeat(auto-fit, minmax(min(7.5rem, 100%), 1fr));
}
.verdicts .btn { margin:0; }

.logfeed { display:flex; flex-direction:column; gap:10px; }
.logrow {
  border:1px solid var(--line-soft); border-radius:14px; padding:11px 13px;
  background:var(--card, #fff);
}
.logrow-h { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.logrow-t { font-size:var(--step--1); font-weight:700; color:var(--ink); }
.logrow-s { font-size:var(--step--1); color:var(--ink-3); }
.logrow-x { margin:6px 0 0; font-size:var(--step--1); color:var(--ink-2); }
.logshots, .shotstrip { display:flex; flex-wrap:wrap; gap:7px; margin-top:8px; }
.logshot {
  width:74px; height:74px; border-radius:10px; object-fit:cover;
  border:1px solid var(--line-soft);
}

/* A row that carries a state and a figure at its end. */
.prow-end { display:flex; align-items:center; gap:9px; flex:none; }

/* ── QC's photograph library, project by project ──────────────── */

.galgroup { margin-bottom:18px; }
.galhead {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  margin-bottom:8px;
}
.galhead-txt { min-width:0; }
.galhead-t { display:block; font-size:var(--step-0); font-weight:700; color:var(--ink); }
.galhead-s { display:block; font-size:var(--step--1); color:var(--ink-3); margin-top:1px; }
.galgrid { display:grid; grid-template-columns:repeat(auto-fill, minmax(96px, 1fr)); gap:8px; }
.galgrid:empty { display:none; }
.galshot {
  width:100%; aspect-ratio:1; border-radius:12px; object-fit:cover;
  border:1px solid var(--line-soft);
}

/* ── Shipping: the two things you do to an order that is out ──── */

.shipcard { display:flex; flex-direction:column; }
.shipcard .ocard { flex:1; }
.shipacts { display:flex; gap:8px; padding:8px 4px 0; }

/* The check list. One tap per thing looked at, in a column rather
   than a wrapped row: it is read down while holding the piece, and
   a wrapped grid makes it easy to skip a line. */
.checklist { display:flex; flex-direction:column; gap:2px; margin-bottom:10px; }
.checkrow {
  display:flex; align-items:center; gap:11px; padding:9px 11px;
  border-radius:11px; cursor:pointer; font-size:var(--step-0); color:var(--ink);
}
.checkrow:active { background:var(--tea-soft); }
.checkrow .box, .tickrow .box {
  flex:none; width:19px; height:19px; accent-color:var(--pine-500); cursor:pointer;
}
/* A row whose whole width is the label, so the tap target is the
   row and not the box. */
.tickrow { cursor:pointer; }

/* ════════════════════════════════════════════════════════════
   THE FOUR READINGS — board, timeline, cards, table
   (js/views/viewkit.js)

   One list, four shapes. Everything below shares one idea: the
   colour is the stage, and it is the same colour in all four, so
   moving between them never asks you to relearn the floor.

   Each shape resolves the stage into a `--tone` / `--tone-ink`
   pair and then only ever refers to those, which is why a new
   station is two lines in the map below rather than an edit
   everywhere.
   ════════════════════════════════════════════════════════════ */

:root { --st-wait:#F0EBE1; --st-wait-ink:#6B6357; }

:is(.bcol, .bcard, .bigcard, .gantt-bar, .legend li, .meters li,
    .bar, .ch-seg, .bigcard-lines li, .cgrid .bigcard) {
  --tone:var(--st-wait); --tone-ink:var(--st-wait-ink);
}
:is(.bcol, .bcard, .bigcard, .gantt-bar, .legend li, .meters li,
    .bar, .ch-seg, .bigcard-lines li).t-quote { --tone:var(--st-quote); --tone-ink:var(--st-quote-ink); }
:is(.bcol, .bcard, .bigcard, .gantt-bar, .legend li, .meters li,
    .bar, .ch-seg, .bigcard-lines li).t-prod  { --tone:var(--st-prod);  --tone-ink:var(--st-prod-ink); }
:is(.bcol, .bcard, .bigcard, .gantt-bar, .legend li, .meters li,
    .bar, .ch-seg, .bigcard-lines li).t-sub   { --tone:var(--st-sub);   --tone-ink:var(--st-sub-ink); }
:is(.bcol, .bcard, .bigcard, .gantt-bar, .legend li, .meters li,
    .bar, .ch-seg, .bigcard-lines li).t-qc    { --tone:var(--st-qc);    --tone-ink:var(--st-qc-ink); }
:is(.bcol, .bcard, .bigcard, .gantt-bar, .legend li, .meters li,
    .bar, .ch-seg, .bigcard-lines li).t-ship  { --tone:var(--st-ship);  --tone-ink:var(--st-ship-ink); }
:is(.bcol, .bcard, .bigcard, .gantt-bar, .legend li, .meters li,
    .bar, .ch-seg, .bigcard-lines li).t-done  { --tone:var(--st-done);  --tone-ink:var(--st-done-ink); }

/* ── The view switch ───────────────────────────────────────── */
.viewtoggle {
  display:inline-flex; gap:2px; padding:3px;
  background:var(--tea-soft); border:1px solid var(--line-soft);
  border-radius:999px; flex:none;
}
.viewbtn {
  display:inline-flex; align-items:center; gap:6px;
  padding:6px 12px; border:0; border-radius:999px;
  background:transparent; color:var(--ink-2);
  font:inherit; font-size:var(--step--1); font-weight:600; cursor:pointer;
  transition:background var(--t-fast) ease, color var(--t-fast) ease;
}
.viewbtn.on { background:var(--paper); color:var(--ink); box-shadow:0 1px 3px rgba(0,0,0,.08); }
.viewbtn span { display:none; }
@media (min-width:560px) { .viewbtn span { display:inline; } }

/* ── The board ─────────────────────────────────────────────── */
.board {
  display:grid; gap:12px; margin-bottom:22px;
  grid-auto-flow:column; grid-auto-columns:minmax(230px, 1fr);
  overflow-x:auto; padding-bottom:6px;
  scroll-snap-type:x proximity;
}
.board::-webkit-scrollbar { height:7px; }
.board::-webkit-scrollbar-thumb { background:var(--line); border-radius:4px; }
.bcol {
  scroll-snap-align:start;
  background:var(--paper); border:1px solid var(--line-soft);
  border-radius:18px; overflow:hidden; display:flex; flex-direction:column;
  min-height:120px;
}
.bcol-h {
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  padding:11px 13px; background:var(--tone); color:var(--tone-ink);
}
.bcol-t { font-size:var(--step--1); font-weight:700; letter-spacing:.2px; }
.bcol-n {
  min-width:22px; text-align:center; padding:1px 7px; border-radius:999px;
  background:rgba(255,255,255,.6); font-size:var(--step--1); font-weight:700;
}
/* A column of forty pieces should scroll inside its column rather
   than make the whole page as tall as the worst pile-up. */
.bcol-b { padding:10px; display:flex; flex-direction:column; gap:9px;
  max-height:min(62vh, 620px); overflow-y:auto; }
.bcol-empty { margin:6px 2px; font-size:var(--step--1); color:var(--ink-3); }

.bcard {
  text-align:left; background:var(--grad-card);
  border:1px solid var(--line-soft); border-left:4px solid var(--tone-ink);
  border-radius:13px; padding:10px 11px; cursor:pointer;
  transition:transform var(--t-fast) ease, box-shadow var(--t-fast) ease;
}
.bcard:active { transform:scale(.985); }
.bcard-t { font-size:var(--step-0); font-weight:650; color:var(--ink); line-height:1.3; }
.bcard-s { margin-top:3px; font-size:var(--step--1); color:var(--ink-2); }
.bcard-m { margin-top:7px; display:flex; flex-wrap:wrap; gap:4px 10px; font-size:var(--step--2); color:var(--ink-3); }
.bcard-f:empty { display:none; }
.bcard-f { margin-top:8px; }
@media (hover:hover) and (pointer:fine) {
  .bcard:hover { box-shadow:0 6px 18px -10px rgba(0,0,0,.35); transform:translateY(-1px); }
}

/* ── The timeline ──────────────────────────────────────────── */
.gantt { margin-bottom:22px; background:var(--paper); border:1px solid var(--line-soft); border-radius:18px; padding:14px 14px 10px; }
.gantt-scale {
  position:relative; height:20px; margin-left:38%; margin-bottom:8px;
  border-bottom:1px dashed var(--line);
}
@media (min-width:720px) { .gantt-scale { margin-left:30%; } }
.gantt-tick {
  position:absolute; top:0; transform:translateX(-50%);
  font-size:var(--step--2); color:var(--ink-3); white-space:nowrap;
}
.gantt-now {
  position:absolute; top:-2px; transform:translateX(-50%);
  font-size:var(--step--2); font-weight:700; color:var(--out); white-space:nowrap;
}
.gantt-now i { display:block; }
.gantt-rows { display:flex; flex-direction:column; gap:7px; }
.gantt-row {
  display:grid; grid-template-columns:38% 1fr; gap:10px; align-items:center;
  padding:4px 2px; border-radius:10px; cursor:pointer;
}
@media (min-width:720px) { .gantt-row { grid-template-columns:30% 1fr; } }
.gantt-row:active { background:var(--tea-soft); }
@media (hover:hover) and (pointer:fine) { .gantt-row:hover { background:var(--tea-soft); } }
.gantt-lbl { min-width:0; }
.gantt-lbl-t { display:block; font-size:var(--step--1); font-weight:640; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.gantt-lbl-s { display:block; font-size:var(--step--2); color:var(--ink-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.gantt-track { position:relative; height:26px; background:var(--tea-soft); border-radius:8px; overflow:hidden; }
.gantt-rule { position:absolute; top:0; bottom:0; width:1px; background:var(--out); opacity:.45; }
.gantt-bar {
  position:absolute; top:3px; bottom:3px; min-width:24px;
  background:var(--tone); border-radius:6px;
  display:flex; align-items:center; overflow:hidden;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.05);
}
.gantt-bar.late { box-shadow:inset 0 0 0 1.5px var(--out); }
.gantt-fill { position:absolute; left:0; top:0; bottom:0; background:rgba(255,255,255,.45); }
.gantt-bar-t {
  position:relative; padding:0 8px; font-size:var(--step--2); font-weight:700;
  color:var(--tone-ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.gantt-foot, .gantt-none { margin:10px 2px 2px; font-size:var(--step--1); color:var(--ink-3); }

/* ── Card grid ─────────────────────────────────────────────── */
.cgrid {
  display:grid; gap:var(--sp-md); margin-bottom:var(--sp-lg);
  grid-template-columns:repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
}

.bigcard {
  text-align:left; background:var(--grad-card);
  border:1px solid var(--line-soft); border-top:4px solid var(--tone-ink);
  border-radius:20px; padding:14px 15px 12px; cursor:pointer;
  display:flex; flex-direction:column; gap:10px;
  transition:transform var(--t-fast) ease, box-shadow var(--t-fast) ease;
}
@media (hover:hover) and (pointer:fine) {
  .bigcard:hover { transform:translateY(-2px); box-shadow:0 14px 30px -18px rgba(0,0,0,.5); }
}
.bigcard-id { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.bigcard-mr { font-size:var(--step--1); font-weight:750; letter-spacing:.6px; color:var(--tone-ink); }
.bigcard-t { margin:4px 0 0; font-size:var(--step-1); font-weight:650; color:var(--ink); }
.bigcard-stats { display:grid; grid-template-columns:repeat(3, 1fr); gap:8px; background:var(--tone); border-radius:13px; padding:9px 10px; }
.bigcard-stat { display:flex; flex-direction:column; gap:2px; min-width:0; }
.bigcard-sv { font-size:var(--step-0); font-weight:700; color:var(--tone-ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.bigcard-sl { font-size:var(--step--2); font-weight:700; letter-spacing:.5px; text-transform:uppercase; color:var(--tone-ink); opacity:.75; }
.bigcard-lines { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:5px; }
.bigcard-lines li {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:6px 9px; border-radius:9px; background:var(--tea-soft);
  border-left:3px solid var(--tone-ink); font-size:var(--step--1);
}
.bigcard-line-t { color:var(--ink); font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.bigcard-line-r { color:var(--ink-3); font-size:var(--step--2); white-space:nowrap; }
.bigcard-foot { display:flex; flex-direction:column; gap:6px; }
.bigcard-act { display:flex; flex-wrap:wrap; gap:6px; }
.bigcard-note { font-size:var(--step--1); color:var(--ink-3); }

/* ── Sortable table ────────────────────────────────────────── */
.dtable-wrap {
  overflow-x:auto; background:var(--paper);
  border:1px solid var(--line-soft); border-radius:16px; margin-bottom:20px;
}
.dtable { width:100%; border-collapse:collapse; font-size:var(--step--1); }
.dtable th { text-align:left; background:var(--tea-soft); border-bottom:1px solid var(--line); white-space:nowrap; }
.dtable th.sorted { background:var(--k-100); }
.dt-h {
  display:inline-flex; align-items:center; gap:5px; width:100%;
  padding:10px 12px; border:0; background:transparent; cursor:pointer;
  font:inherit; font-size:var(--step--2); font-weight:750; letter-spacing:.5px;
  text-transform:uppercase; color:var(--ink-2);
}
.dt-h.flat { cursor:default; }
.dt-mark { display:inline-flex; opacity:.35; }
.dt-mark.on { opacity:1; color:var(--ink); }
.dtable td { padding:11px 12px; border-bottom:1px solid var(--line-soft); vertical-align:middle; }
.dtable tbody tr:last-child td { border-bottom:0; }
.dtable tbody tr { cursor:pointer; }
.dtable tbody tr:active { background:var(--tea-soft); }
@media (hover:hover) and (pointer:fine) { .dtable tbody tr:hover { background:var(--tea-soft); } }
.dt-strong { font-weight:640; color:var(--ink); }
.dt-mut { color:var(--ink-3); }
.dt-date { white-space:nowrap; color:var(--ink-2); }
.dt-amt { white-space:nowrap; font-weight:700; }
.dt-amt .in { color:var(--in); }
.dt-amt .out { color:var(--out); }
.dt-tag {
  display:inline-block; padding:2px 8px; border-radius:999px;
  background:var(--tea-soft); font-size:var(--step--2); font-weight:700; color:var(--ink-2);
}
.ta-right, th.ta-right .dt-h { text-align:right; justify-content:flex-end; }
.ta-center { text-align:center; }
/* On a phone the headings are too narrow to tap, so the same sort
   is offered as the chip row every other list already uses. */
@media (max-width:719px) { .dtable-wrap { border-radius:14px; } }

/* ── Dashboard panels and charts ───────────────────────────── */
.panels {
  display:grid; gap:var(--sp-md); margin-bottom:var(--sp-lg);
  grid-template-columns:repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
}
/* A wide panel takes the whole row whatever that row turned out to
   be — `1 / -1` needs no count, where `span 2` had to be told how
   many columns there were and was wrong at every width but one. */
.dpanel.wide { grid-column:1 / -1; }

.dpanel { background:var(--paper); border:1px solid var(--line-soft); border-radius:20px; padding:14px 16px 16px; }
.dpanel-h { display:flex; align-items:flex-start; justify-content:space-between; gap:10px; margin-bottom:12px; }
.dpanel-h h3 { margin:0; font-size:var(--step-0); font-weight:680; color:var(--ink); }
.dpanel-h p { margin:2px 0 0; font-size:var(--step--1); color:var(--ink-3); }
.dpanel-b { min-height:60px; }
.chart-none { padding:18px 2px; font-size:var(--step--1); color:var(--ink-3); }

.chart { width:100%; height:auto; display:block; color:var(--ink); }
.ch-grid { stroke:var(--line-soft); stroke-width:1; }
.ch-ax { fill:var(--ink-3); font-size:var(--step--2); }
.ch-val { fill:var(--ink-2); font-size:var(--step--2); font-weight:700; }
.ch-line { fill:none; stroke:var(--ink); stroke-width:2.2; stroke-linejoin:round; stroke-linecap:round; }
.ch-dot { fill:var(--paper); stroke:var(--ink); stroke-width:2; }
.chart-donut { max-width:150px; }
.ch-track { fill:none; stroke:var(--tea-soft); stroke-width:16; }
.ch-seg { fill:none; stroke:var(--tone-ink); stroke-width:16; }
.ch-cap { fill:var(--ink-3); font-size:var(--step--2); }
.ch-tot { fill:var(--ink); font-size:var(--step-1); font-weight:700; }

.donut { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
.legend { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; flex:1; min-width:130px; }
.legend.flat { flex-direction:row; flex-wrap:wrap; gap:6px 14px; margin-top:10px; }
.legend li { display:flex; align-items:center; gap:8px; font-size:var(--step--1); color:var(--ink-2);
  background:transparent; border:0; }
.legend li i { width:10px; height:10px; border-radius:3px; background:var(--tone-ink); flex:none; }
.legend li b { margin-left:auto; color:var(--ink); }
.legend li em { font-style:normal; color:var(--ink-3); min-width:34px; text-align:right; }
.legend.flat li b, .legend.flat li em { display:none; }

.barchart { display:flex; align-items:flex-end; gap:10px; }
.barcol { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:flex-end; height:100%; gap:4px; }
.barval { font-size:var(--step--2); font-weight:700; color:var(--ink-2); }
.bar { width:100%; max-width:38px; background:var(--tone-ink); border-radius:6px 6px 3px 3px; }
.barlbl { font-size:var(--step--2); color:var(--ink-3); }

.meters { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:11px; }
.meters li { display:flex; flex-direction:column; gap:4px; background:transparent; border:0; }
.meter-h { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.meter-l { font-size:var(--step--1); font-weight:600; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.meter-v { font-size:var(--step--1); font-weight:700; color:var(--ink-2); white-space:nowrap; }
.meter-track { height:8px; border-radius:999px; background:var(--tea-soft); overflow:hidden; }
.meter-track i { display:block; height:100%; border-radius:999px; background:var(--tone-ink); }
.meter-n { font-size:var(--step--2); color:var(--ink-3); }

/* ── The centred window ────────────────────────────────────────
   A card or a row opens into the middle of the screen rather than
   up from its bottom edge: three quarters of the viewport, the same
   margin on all four sides, and the floor still visible around it
   so you can see what you came from. Tapping that floor closes it —
   the scrim already carried that behaviour, this only makes the
   target the whole of the space around the window.

   A phone keeps the sheet it always had: three quarters of a
   540-pixel screen is not a window, it is a worse full screen. */
@media (min-width:900px) {
  .sheet, .sheet.full {
    left:50%; right:auto; top:50%; bottom:auto;
    width:75vw; max-width:75vw;
    height:75vh; max-height:75vh;
    transform:translate(-50%, -50%);
    border-radius:24px;
    box-shadow:0 30px 80px -30px rgba(var(--shade-rgb), .6);
    animation:centre-in .3s var(--ease);
    overflow:hidden;
  }
  /* Narrow, form-shaped sheets do not want to be three quarters of a
     wide monitor — a single column of fields stretched to 1400px is
     unreadable. They cap, and stay centred. */
  .sheet:not(.wide):not(.full) { max-width:min(75vw, 640px); }
  .sheet.wide, .sheet.full.wide { max-width:min(75vw, 1100px); }
  .sheet-content, .sheet-body { max-height:100%; }
}

@keyframes centre-in {
  from { opacity:0; transform:translate(-50%, -46%) scale(.97); }
  to   { opacity:1; transform:translate(-50%, -50%) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .sheet, .sheet.full { animation:none; }
}

/* Leaving, in the direction it arrived from. */
.sheet.closing { transition:transform .2s ease-in, opacity .2s ease-in; transform:translateY(100%); }
@media (min-width:900px) {
  .sheet.closing, .sheet.full.closing {
    transform:translate(-50%, -48%) scale(.98); opacity:0;
  }
}

/* ════════════════════════════════════════════════════════════
   THE PICTURE AGAINST THE LINE
   (js/views/thumbs.js, ui.openLightbox)

   Nothing here is recognised by its description. Every list of
   pieces therefore carries the photograph beside the words, at one
   of three sizes — dense row, standard, card — and every one of
   them opens full screen when it is tapped.
   ════════════════════════════════════════════════════════════ */

.pthumb {
  flex:none; display:block; position:relative; padding:0;
  width:62px; height:62px; border-radius:13px; overflow:hidden;
  border:1px solid var(--line); background:var(--tea-soft);
  cursor:zoom-in; transition:transform var(--t-fast) ease, box-shadow var(--t-fast) ease;
}
.pthumb img { width:100%; height:100%; object-fit:cover; display:block; }
.pthumb.sm { width:46px; height:46px; border-radius:11px; }
.pthumb.lg { width:100%; height:104px; border-radius:11px; }
.pthumb.empty {
  display:grid; place-items:center; color:var(--ink-3);
  cursor:default; border-style:dashed;
}
/* The magnifier only appears where there is something to magnify,
   and only on a pointer that can hover — on a phone it would just
   be sitting on top of the picture. */
.pthumb-z {
  position:absolute; right:4px; bottom:4px; width:20px; height:20px;
  border-radius:50%; display:grid; place-items:center;
  background:rgba(20,19,15,.62); color:#fff; opacity:0;
  transition:opacity var(--t-fast) ease;
}
@media (hover:hover) and (pointer:fine) {
  .pthumb:hover { transform:scale(1.03); box-shadow:0 8px 20px -12px rgba(0,0,0,.5); }
  .pthumb:hover .pthumb-z { opacity:1; }
}
.pthumb:focus-visible { outline:2px solid var(--ink); outline-offset:2px; }

/* An order's pieces, across the top of its card. */
.cover { display:grid; grid-template-columns:repeat(auto-fit, minmax(0, 1fr)); gap:6px; position:relative; }
.cover-more {
  position:absolute; right:6px; bottom:6px; padding:2px 8px; border-radius:999px;
  background:rgba(20,19,15,.7); color:#fff; font-size:var(--step--2); font-weight:700;
}

/* A board card leads with its picture. */
.bcard-img { margin:-2px 0 8px; }

/* The rows are already flex with a gap of their own, so a thumbnail
   only has to be the first child — nothing else about them moves. */

/* Images that were already on screen before this — a work order's
   row, a piece board's header — become zoomable rather than being
   redrawn. */
.zoom { cursor:zoom-in; }

/* ── Full screen ───────────────────────────────────────────────
   Above everything, including a centred sheet, because it is opened
   from inside one. A tap anywhere on the ground closes it. */
.lightbox {
  position:fixed; inset:0; z-index:300;
  background:rgba(8,7,5,.92);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  display:grid; place-items:center; padding:24px;
  animation:fade .18s ease;
  cursor:zoom-out;
}
.lightbox.closing { opacity:0; transition:opacity .18s ease; }
.lightbox-fig { margin:0; display:flex; flex-direction:column; align-items:center; gap:12px; max-width:100%; max-height:100%; }
.lightbox-fig img {
  max-width:min(96vw, 1400px); max-height:82vh;
  width:auto; height:auto; object-fit:contain;
  border-radius:8px; background:#fff;
  box-shadow:0 30px 80px -30px rgba(0,0,0,.9);
}
.lightbox-fig figcaption {
  color:rgba(255,255,255,.82); font-size:var(--step-0); text-align:center;
  max-width:60ch;
}
.lightbox-x {
  position:absolute; top:16px; right:16px; width:42px; height:42px;
  border-radius:50%; border:0; display:grid; place-items:center;
  background:rgba(255,255,255,.14); color:#fff; cursor:pointer;
}
.lightbox-x:hover { background:rgba(255,255,255,.24); }
@media (prefers-reduced-motion: reduce) { .lightbox { animation:none; } }

/* ════════════════════════════════════════════════════════════
   THE DESKTOP TAKES THE WHOLE WINDOW

   Kontour grew out of a phone, and the desktop layout it grew
   into kept the phone's habit: one centred column with the page
   showing down both sides. On a workshop PC that is a monitor
   two thirds wasted — the board can only show four columns, the
   tables truncate, and the cards sit three abreast on a screen
   that would take five.

   So from the rail's width up, the content is the window. The
   spine becomes the full width beside the rail and the gutters
   grow with it — a margin, not a column — so nothing is ever
   glued to the edge of the glass.
   ════════════════════════════════════════════════════════════ */

@media (min-width:900px) {
  :root { --content-w:100%; --gutter:26px; }

  .screen > *, .floor, .topbar-in, .sec { max-width:none; }
  .floor { padding:22px var(--gutter) calc(var(--safe-b) + 96px); }
  .sec { padding:26px var(--gutter) 0; }
  .hero { padding-left:var(--gutter); padding-right:var(--gutter); }
  .topbar { padding-left:var(--gutter); padding-right:var(--gutter); }

  /* Given the width, the grids stop being two or three fixed
     columns and simply fill the row. */
  .grid2 { grid-template-columns:repeat(auto-fill, minmax(210px, 1fr)); }
  .kpis  { grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); }
  .vgrid { grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); }

  /* The figure cards spread to fill the row rather than multiply
     into a strip of eight nobody reads the end of: auto-fit makes
     a track per card and lets each one take its share. */
  .statcards { grid-template-columns:repeat(auto-fit, minmax(260px, 1fr)); }

  /* The dark hero band goes full bleed, but what is written on it
     does not: three figures spread across a metre of glass are
     three figures nobody reads together. */
  .hero-bar, .stat-row { max-width:1040px; margin-inline:auto; }

  /* A button is the size of what it says, not the size of the
     window. Full-width is a phone idiom — there it is the thumb's
     target; here it is a metre of empty pill. */
  .floor > .btn, .sec > .btn { max-width:420px; }
}

@media (min-width:1280px) {
  :root { --content-w:100%; --gutter:32px; }
  .sec { padding:30px var(--gutter) 0; }
  /* The board shows every stage at once rather than scrolling. */
  .board { grid-auto-columns:minmax(240px, 1fr); overflow-x:visible; }
  .cgrid { grid-template-columns:repeat(auto-fill, minmax(320px, 1fr)); }
  .panels { grid-template-columns:repeat(3, 1fr); }
}

@media (min-width:1600px) {
  :root { --content-w:100%; --gutter:38px; }
  .sec { padding:34px var(--gutter) 0; }
  .floor { padding-left:var(--gutter); padding-right:var(--gutter); }
  /* Only the reading matter stays a column: a paragraph set 1500
     pixels wide is a paragraph nobody finishes the line of. Lists,
     boards, cards and tables take everything. */
  .comingup, .sheet-lede { max-width:78ch; }
}

/* A day book at full width would set its amount a metre from its
   party — a row you have to track across with a finger. The money
   screens therefore keep a working column, and sit at the left of
   it rather than centred, so the eye starts in the same place
   whatever the window is doing. The stations do not: a card grid
   and a board genuinely want the room. */
@media (min-width:1280px) {
  [data-section="phynance"] .sec > .list,
  [data-section="phynance"] .daygroup,
  [data-section="phynance"] .sec > .empty,
  [data-section="commission"] .sec > .list { max-width:1120px; }
}

/* ── Filling the room, top to bottom ──────────────────────────
   Widening the page exposed the other half of the same problem:
   the board stopped at a fixed height with the bottom third of the
   monitor empty under it, and a short analytics panel was stretched
   into a tall box with a hole in it.

   Two rules, and they pull in opposite directions on purpose. The
   board is a workspace: it should reach the bottom of the screen
   and scroll inside its own columns, so the pile-up at one stage is
   read against the height of the window. A panel is a statement:
   it should be exactly as tall as what it says, and sit at the top
   of its row rather than being stretched to match its neighbour. */
@media (min-width:900px) {
  /* Only a screen that is holding a board becomes a full-height
     column; every other screen keeps flowing as it did. */
  .floor:has(.board) {
    display:flex; flex-direction:column; height:100%;
    /* Every other screen reserves a strip at the bottom so its last
       row can be scrolled out from under the floating button. A
       board does not scroll — its columns do — so that strip is
       just a bare inch of page. */
    padding-bottom:calc(var(--safe-b) + 26px);
  }
  /* Takes the room left under the headings, and never less than a
     usable column — on a short window the page scrolls instead. */
  .floor > .board { flex:1 1 auto; min-height:380px; }
  .bcol { height:100%; }
  /* The cap moves off the body and onto the column: the body now
     takes whatever height the window gives the board. */
  .bcol-b { max-height:none; flex:1 1 auto; min-height:0; overflow-y:auto; }

  /* A panel is as tall as its contents. */
  .panels { align-items:start; }

  /* Five bars on a metre of panel should be bars, not pencils. */
  .bar { max-width:58px; }
}

/* ════════════════════════════════════════════════════════════
   THE PIECE CARD, AND THE ONE THING THAT HAPPENS NEXT
   (js/views/orderdetail.js, js/views/nextstep.js)

   A card says where the piece is, who is making it, and what to do
   with it now. The stage is a statement — a pill — and the corner
   button is the only thing that moves it, so there is never a
   choice of seven where the floor is waiting on one.
   ════════════════════════════════════════════════════════════ */

/* The stripe down the left is the piece's own stage, using the same
   tints as the board and the timeline. */
.piece.t-wait::before  { background:var(--st-wait-ink); }
.piece.t-quote::before { background:var(--st-quote-ink); }
.piece.t-sub::before   { background:var(--st-sub-ink); }

.piece-mark { display:flex; flex-direction:column; align-items:flex-end; gap:6px; flex:none; }

/* ── Who is making it ─────────────────────────────────────── */
.makers { margin-top:10px; padding-top:10px; border-top:1px solid var(--line-soft); }
.makers-h { display:flex; align-items:center; justify-content:space-between; gap:10px; }
.makers-t {
  font-size:var(--step--2); font-weight:750; letter-spacing:.7px;
  text-transform:uppercase; color:var(--ink-3);
}
.makers-list { display:flex; flex-direction:column; gap:6px; margin-top:8px; }
.maker {
  display:grid; gap:4px 10px; align-items:center; width:100%; text-align:left;
  grid-template-columns:1fr auto;
  padding:9px 11px; border-radius:12px; cursor:pointer;
  background:var(--tea-soft); border:1px solid var(--line-soft);
}
@media (min-width:560px) {
  .maker { grid-template-columns:minmax(0,1.2fr) minmax(0,1fr) auto auto; }
}
.maker:active { transform:scale(.995); }
@media (hover:hover) and (pointer:fine) { .maker:hover { background:var(--k-075); } }
.maker-n { font-size:var(--step-0); font-weight:650; color:var(--ink); min-width:0; }
.maker-tr { font-size:var(--step--1); color:var(--ink-2); min-width:0; }
.maker-st { justify-self:start; }
.maker-v { font-size:var(--step--1); font-weight:700; color:var(--ink-2); white-space:nowrap; justify-self:end; }
.makers-none { margin:8px 0 0; font-size:var(--step--1); color:var(--ink-3); }
.makers-old { margin:8px 0 0; font-size:var(--step--1); color:var(--ink-3); line-height:1.5; }

/* ── The next step ────────────────────────────────────────── */
.piece-foot {
  display:flex; align-items:center; justify-content:space-between;
  gap:10px; margin-top:12px; flex-wrap:wrap;
}
.nextbtn {
  display:inline-flex; align-items:center; gap:6px; margin-left:auto;
  padding:10px 15px; border:0; border-radius:999px; cursor:pointer;
  background:var(--ink); color:var(--page);
  font:inherit; font-size:var(--step--1); font-weight:650; white-space:nowrap;
  transition:transform var(--t-fast) ease, opacity var(--t-fast) ease;
}
.nextbtn:active { transform:scale(.97); }
@media (hover:hover) and (pointer:fine) { .nextbtn:hover { opacity:.88; } }
.nextbtn.sm { padding:7px 11px; font-size:var(--step--1); gap:4px; }
/* The end of the line is a mark, not a control: something has to
   stand in the corner or it reads as a button that failed to draw. */
.nextbtn.done {
  background:var(--in-soft); color:var(--in); cursor:default;
  font-weight:700;
}

/* On a board card the step sits in the same corner as everywhere
   else, with the overdue pill on the left of it. */
.bcard-f { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.bcard-f:has(.nextbtn) { margin-top:9px; }

/* ── Handing one piece to one person ──────────────────────── */
.qa-piece {
  display:flex; align-items:center; gap:12px; margin-bottom:14px;
  padding:10px 12px; border-radius:15px;
  background:var(--tea-soft); border:1px solid var(--line-soft);
}
.qa-img { width:54px; height:54px; border-radius:12px; object-fit:cover; flex:none; background:var(--k-100); }
.qa-img.ph { display:grid; place-items:center; color:var(--ink-3); }
.qa-name { font-size:var(--step-0); font-weight:700; color:var(--ink); }
.qa-sub { font-size:var(--step--1); color:var(--ink-3); margin-top:2px; }
.qa-row { margin-bottom:0; }
.qa-row.on { border-color:var(--ink); box-shadow:0 0 0 1px var(--ink); }
.qa-tick { color:var(--in); display:grid; place-items:center; flex:none; }
.plist[data-people] { display:flex; flex-direction:column; gap:7px; max-height:44vh; overflow-y:auto; margin-bottom:14px; }

/* The corner button on a station row: the exception on the left,
   the next step on the right, as on every other card. */
.qcrow-acts { display:flex; align-items:center; gap:8px; flex:none; flex-wrap:wrap; justify-content:flex-end; }
.bigcard-act {
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  padding:7px 0; border-top:1px solid var(--line-soft);
}
.bigcard-act:first-child { border-top:0; }
.bigcard-act-n {
  flex:1; min-width:0; font-size:var(--step--1); font-weight:600; color:var(--ink-2);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.bigcard-act .nextbtn { margin-left:0; }

/* A quotation row's corner button sits under its amount, and the
   table's under the icons, so the eye finds the next step in the
   same place on every screen. */
.qrow2-side { display:flex; flex-direction:column; align-items:flex-end; gap:8px; }
.qrow2-side .nextbtn { margin-left:0; }
/* The table's action column is a cell — it stays a cell, and the
   button simply sits inline in it beside the icons. */
.qt-acts .nextbtn { vertical-align:middle; margin:0 6px 0 0; }
.prow-end { display:flex; align-items:center; gap:9px; flex:none; }
.prow-end .nextbtn { margin-left:0; }

/* ═══════════════════════════════════════════════════════════════
   CATALOG — the product book, as rows
   ═══════════════════════════════════════════════════════════════
   The screen is a search box over a table: a toolbar carrying how
   many rows, the filters and the way out; the table itself, which
   is the shared .dtable with a photograph in its first column; and
   a pager, because a catalog is hundreds of pieces and a phone
   should not be asked to hold them all at once.

   Everything reads from the token layer, so the whole screen turns
   green with the rest of Phynance without naming a colour here. */

/* ── The screen holds still, the rows move ───────────────────
   A catalog is read by looking things up, and looking a thing up
   means the search box, the filters and the column headings have to
   be where you left them when you get to the bottom of the list.
   The whole screen used to be the scroller, so all three had gone by
   the time the eightieth row was in view. Here the hero, the toolbar
   and the pager are the frame and only the rows move inside it — one
   scrollbar on the screen, and it belongs to the list.

   Below 34rem of height there is not enough window left to divide,
   so the screen goes back to being one plain scroller. */
@media (min-height:34rem) {
  .screen.pinned {
    overflow:hidden;
    padding-bottom:0;
    display:flex; flex-direction:column;
  }
  /* A flex child with `width:auto` and the spine's `margin-inline:
     auto` shrinks to its own content instead of taking the column.
     The cap and the centring still apply — this only says the child
     is as wide as it is allowed to be. */
  .screen.pinned > * { width:100%; }
  .screen.pinned > .hero { flex:none; }

  .screen.pinned > .sec {
    flex:1 1 auto; min-height:0;
    display:flex; flex-direction:column;
    padding-top:var(--sp-md);
    padding-bottom:var(--sp-sm);
  }
  .cat-scroll {
    flex:1 1 auto; min-height:0;
    display:flex; flex-direction:column;
  }
  /* Whichever shape the list is in — the table, the card grid, or
     the empty state — it is the one child, and it is what scrolls. */
  .cat-scroll > * {
    flex:1 1 auto; min-height:0;
    overflow:auto; -webkit-overflow-scrolling:touch;
    margin-bottom:0;
  }
  /* The headings ride along at the top of their own scroller, so the
     column a figure belongs to is never a scroll away. */
  .cat-scroll .dtable thead th { position:sticky; top:0; z-index:2; }
  /* The grid is the scroller now, so its rows must keep their own
     height rather than being handed the pane's: without this a short
     list stretches its cards and a long one squeezes them. */
  /* The grid is the scroller now, so its rows have to keep their own
     height. `auto` tracks inside a box of definite height are
     flexible and get squeezed to fit it; `min-content` tracks are
     the cards themselves, and the pane scrolls past them. */
  .cat-scroll .dgrid {
    align-content:start; grid-auto-rows:min-content;
    /* A card's shadow would be shaved off by the pane's own edge. */
    padding:3px 3px 8px;
  }

  .screen.pinned .cat-pager {
    flex:none; margin:10px 0 0;
    padding-bottom:var(--sp-2xs);
  }
}

.cat-bar {
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  margin-bottom:12px;
}
.cat-search { flex:1 1 260px; margin-bottom:0; }
.cat-tools { display:flex; flex-wrap:wrap; align-items:center; gap:8px; }

.cat-showing {
  display:inline-flex; align-items:center; gap:7px;
  font-size:var(--step--1); color:var(--ink-3); white-space:nowrap;
}
.cat-per { width:auto; padding:8px 30px 8px 11px; font-size:var(--step--1); }

.cat-btn {
  display:inline-flex; align-items:center; gap:7px;
  padding:9px 14px; border-radius:12px; cursor:pointer;
  border:1px solid var(--line); background:var(--paper);
  font:inherit; font-size:var(--step--1); font-weight:640; color:var(--ink-2);
  transition:background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.cat-btn:active { transform:scale(.97); }
.cat-btn.on { border-color:var(--ink); color:var(--ink); background:var(--tea-soft); }
.cat-btn.primary {
  /* The module's own accent, the way every other primary action in
     the app is drawn — `--ink` is the text colour in Phynance, where
     ink and on-accent are both the same deep green. */
  background:var(--grad-lime); color:var(--on-accent); border-color:transparent;
  box-shadow:0 6px 18px -8px var(--accent-glow);
}
@media (hover:hover) and (pointer:fine) {
  .cat-btn:hover { background:var(--tea-soft); color:var(--ink); }
  .cat-btn.primary:hover { filter:brightness(1.04); }
}

.cat-headrow {
  display:flex; align-items:center; justify-content:space-between;
  gap:10px; flex-wrap:wrap; margin-bottom:8px;
}
.cat-count { margin:0; font-size:var(--step--1); color:var(--ink-3); }
.cat-clear {
  margin-left:8px; border:0; background:none; cursor:pointer; font:inherit;
  font-size:var(--step--1); font-weight:640; color:var(--ink); text-decoration:underline;
}

/* ── The row ─────────────────────────────────────────────────── */
.cat-name { display:flex; align-items:center; gap:10px; min-width:0; }
.cat-name > span { min-width:0; display:flex; flex-direction:column; gap:1px; }
.cat-thumb {
  width:38px; height:38px; flex:none; border-radius:10px; object-fit:cover;
  background:var(--tea-soft); border:1px solid var(--line-soft);
}
.cat-thumb.ph {
  display:inline-flex; align-items:center; justify-content:center; color:var(--ink-3);
}
.cat-sub, .cat-dims {
  font-size:var(--step--2); color:var(--ink-3);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:24ch;
  display:block;
}
.cat-alt { font-weight:600; color:var(--ink-3); }
.cat-acts { width:1%; white-space:nowrap; }

/* ── The pager ───────────────────────────────────────────────── */
.cat-pager {
  display:flex; align-items:center; justify-content:space-between;
  gap:10px; flex-wrap:wrap; margin:2px 0 22px;
}
.cat-pgs { display:flex; align-items:center; gap:4px; flex-wrap:wrap; }
.cat-pg, .cat-step {
  display:inline-flex; align-items:center; gap:5px; cursor:pointer;
  min-width:32px; height:32px; padding:0 10px; justify-content:center;
  border:0; background:none; border-radius:10px; font:inherit;
  font-size:var(--step--1); font-weight:640; color:var(--ink-3);
  transition:background var(--t-fast), color var(--t-fast);
}
.cat-pg.on { background:var(--grad-lime); color:var(--on-accent); font-weight:750; }
.cat-step[disabled] { opacity:.35; cursor:default; }
.cat-gap { color:var(--ink-3); padding:0 2px; }
@media (hover:hover) and (pointer:fine) {
  .cat-pg:hover, .cat-step:not([disabled]):hover { background:var(--tea-soft); color:var(--ink); }
  .cat-pg.on:hover { filter:brightness(1.04); }
}
@media (max-width:420px) { .cat-step span { display:none; } }

/* ── One piece, full screen ──────────────────────────────────── */
.catview { display:flex; flex-direction:column; gap:14px; }
.catview-img {
  /* Contained, not cropped: a catalog photograph is shot on white
     with the piece centred, and cropping it to a band cuts the legs
     off the thing being looked at. Capped in height so the specs are
     on screen with it rather than a scroll below it. */
  width:100%; aspect-ratio:3/2; max-height:min(44vh, 360px);
  object-fit:contain; border-radius:var(--r-md);
  background:var(--tea-soft); cursor:zoom-in;
}
.catview-img.ph {
  display:flex; align-items:center; justify-content:center; color:var(--ink-3); cursor:default;
}
/* On a wide sheet the picture and the numbers sit side by side —
   the piece is judged by eye and quoted by figure, and both should
   be in view at once. */
@media (min-width:780px) {
  .catview {
    display:grid; gap:16px 22px; align-items:start;
    grid-template-columns:minmax(0, 1.05fr) minmax(0, 1fr);
  }
  .catview-img { grid-row:1 / span 4; max-height:none; position:sticky; top:0; }
  .catview-note, .catview-acts { grid-column:2; }
}

.catview-head { display:flex; align-items:flex-start; justify-content:space-between; gap:10px; }
.catview-head h3 { margin:0; font-size:var(--step-2); font-weight:700; color:var(--ink); }
.catview-code { margin:2px 0 0; font-size:var(--step--1); font-weight:700; letter-spacing:.6px; color:var(--ink-3); }

.catview-sizes { display:flex; flex-wrap:wrap; gap:8px; }
.catview-size {
  flex:1 1 160px; padding:10px 12px; border-radius:14px;
  background:var(--tea-soft); display:flex; flex-direction:column; gap:2px;
}
.catview-size-d { font-size:var(--step--1); color:var(--ink-2); }
.catview-size-p { font-size:var(--step-1); font-weight:750; color:var(--ink); }
.catview-size small { font-size:var(--step--2); font-weight:700; letter-spacing:.4px; text-transform:uppercase; color:var(--ink-3); }

.catview-specs { margin:0; display:flex; flex-direction:column; gap:0; }
.catview-specs > div {
  display:flex; gap:12px; padding:9px 2px; border-bottom:1px solid var(--line-soft);
}
.catview-specs > div:last-child { border-bottom:0; }
.catview-specs dt {
  flex:0 0 34%; font-size:var(--step--1); font-weight:700; color:var(--ink-3);
}
.catview-specs dd { margin:0; flex:1; font-size:var(--step--1); color:var(--ink); }
.catview-note { margin:0; font-size:var(--step--2); color:var(--ink-3); line-height:1.5; }
.catview-acts { display:flex; gap:8px; flex-wrap:wrap; }
.catview-acts .btn { margin:0; flex:1 1 140px; }

/* ── Adding one ──────────────────────────────────────────────── */
.catpick { display:flex; align-items:center; gap:12px; margin-bottom:6px; }
.catpick-img {
  width:96px; height:96px; flex:none; border-radius:var(--r-sm); overflow:hidden;
  background:var(--tea-soft); border:1px dashed var(--line); cursor:pointer;
  display:flex; align-items:center; justify-content:center; color:var(--ink-3);
}
.catpick-img img { width:100%; height:100%; object-fit:cover; }
.catpick-acts { display:flex; flex-direction:column; gap:6px; }
.catpick-acts .btn { margin:0; }

.catbooks { display:flex; flex-direction:column; gap:6px; margin:8px 0 10px; }
.catbook {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:9px 12px; border-radius:12px; background:var(--tea-soft);
}
.catbook span { display:flex; flex-direction:column; gap:1px; font-size:var(--step--1); color:var(--ink); }
.catbook small { font-size:var(--step--2); color:var(--ink-3); }
.form-sep {
  margin:18px 0 0; font-size:var(--step--1); font-weight:750;
  letter-spacing:.5px; text-transform:uppercase; color:var(--ink-3);
}

/* ── The row's own menu ──────────────────────────────────────── */
.menu-list { display:flex; flex-direction:column; padding:0 var(--sp-md) var(--sp-md); }
.menu-item {
  display:flex; align-items:center; gap:12px; width:100%; cursor:pointer;
  padding:14px 4px; border:0; border-bottom:1px solid var(--line-soft);
  background:none; font:inherit; font-size:var(--step-0); color:var(--ink); text-align:left;
}
.menu-item:last-child { border-bottom:0; }
.menu-item.danger { color:var(--out); }
.menu-item:active { background:var(--tea-soft); }

/* A fourth pill tone: a piece that is in the book at a reduced
   price is neither in nor out, and reading it as either is wrong. */
.pill.info { background:rgba(43,108,176,.12); color:#2B6CB0; }

/* ── The share card ──────────────────────────────────────────── */
.catshare { display:flex; flex-direction:column; gap:10px; }
.catshare-pv {
  border-radius:var(--r-md); overflow:hidden; background:var(--tea-soft);
  display:flex; align-items:center; justify-content:center;
  min-height:280px; aspect-ratio:1080/1500; max-height:56vh; margin:0 auto;
}
.catshare-pv img { width:100%; height:100%; object-fit:contain; display:block; }
.catshare-wait {
  display:flex; align-items:center; gap:8px; color:var(--ink-3);
  font-size:var(--step--1);
}
.catview-size-tax { font-size:var(--step--2); color:var(--ink-3); }
.catview-acts .btn.sm.ghost { flex:1 1 100px; }

/* ── The tax note ────────────────────────────────────────────────
   A price with no tax stated is the one figure on these screens
   somebody can act on wrongly, so every shape that shows one says
   which it is — the column heading, the card, the product screen
   and the shared image alike. */
.cat-gstnote { margin-left:8px; color:var(--ink-3); font-size:var(--step--2); }
.cat-exgst {
  display:block; font-style:normal; font-weight:600;
  font-size:var(--step--2); color:var(--ink-3); letter-spacing:.1px;
}
.catview-size-p em {
  display:block; font-style:normal; font-weight:600;
  font-size:var(--step--2); color:var(--ink-3); margin-top:1px;
}

/* ── The catalog toolbar, on a phone ─────────────────────────────
   Four controls and a search box wrapped onto two rows and pushed
   the first product below the fold. Narrow, the buttons keep their
   icons and drop their words — Filter carries a count as a dot
   rather than a "· 2", the rows-per-page select loses the word
   "Showing", and Add New Product keeps its place but not its label —
   it is the screen's only way to add a product, so it is the one
   control that may not stand down. The GST switch keeps its label at
   every width: it is the one control here whose state changes what a
   number means. */
.only-wide { display:inline; }

@media (max-width:560px) {
  .cat-bar { gap:8px; margin-bottom:10px; }
  .cat-tools { gap:6px; width:100%; }
  .cat-btn span, .cat-showing .only-wide { display:none; }
  /* Narrower, never shorter: a wordless button still has to be a
     fingertip tall, which is what --tap is for. */
  .cat-btn { padding:0 13px; gap:0; min-height:var(--tap); }
  /* …except the one that says which figure the list is showing. */
  .cat-btn[data-gst] { padding:0 14px; gap:6px; }
  .cat-btn[data-gst] span { display:inline; font-size:var(--step--2); }
  .cat-per { padding:7px 26px 7px 10px; font-size:var(--step--2); }
  .cat-showing { gap:0; }
  .cat-headrow { margin-bottom:6px; }
  .cat-search .control { font-size:var(--step--1); }
}

.cat-badge {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:16px; height:16px; padding:0 4px; margin-left:6px;
  border-radius:999px; background:var(--ink); color:var(--paper);
  /* --paper, not --on-accent: inside Phynance the accent's own text
     colour is the same deep green as --ink, and the count vanished
     into its own dot. */
  font-size:10px; font-weight:750; font-style:normal; line-height:1;
}
@media (max-width:560px) {
  /* No room beside a wordless button, so it rides the corner. */
  .cat-btn { position:relative; }
  .cat-badge { position:absolute; top:-5px; right:-5px; margin:0; }
}
