/* ==========================================================================
   Yorklift Service Portal — the whole design system, one file.

   A quiet dashboard. A white page, a warm putty sidebar down the left, and
   everything on the page separated by a 1px line or a wash of tone — never a
   shadow, never a change of ground.

   THE RULE THIS FILE FOLLOWS: separate things with space first, tone second,
   a line third, and never a shadow. Most of what used to be a floating box
   here is now white with a hairline around it, because that is quieter, reads
   the same in daylight, and prints correctly.

   One brand thing is deliberately loud, and only one: safety orange on the one
   action that matters on each screen. Everything else is warm greyscale. That
   contrast is the design — an orange button means something precisely because
   nothing else is orange.

   BRAND: the values worth changing all live in the --brand block below.
   ========================================================================== */

@import url("/fonts.css");

:root {
  /* ---- BRAND (edit these) ------------------------------------------------ */
  --brand: #e4622a;        /* the accent: active nav, primary actions   */
  --brand-press: #c9521f;
  --on-brand: #ffffff;
  --ink: #1a1614;           /* headings, neutral buttons                 */

  /* Status. Reserved — never reused as a chart series colour. */
  --crit: #c8342b;
  --warn: #b26b00;
  --good: #177d45;

  /* ---- Chart palette ----------------------------------------------------
     Validated for colour-vision deficiency against both surfaces. Assigned in
     this fixed order and never cycled; a sixth category folds into "other".
     Do not substitute a hue without re-running the validator. */
  --viz-1: #2a78d6;   /* blue    */
  --viz-2: #1baf7a;   /* aqua    */
  --viz-3: #eda100;   /* yellow  */
  --viz-4: #e87ba4;   /* magenta */
  --viz-5: #4a3aa7;   /* violet  */
  --viz-other: #b9b4c4;

  /* ---- The dashboard's own series ---------------------------------------
     A ramp of the brand colour, then a warm grey for whatever is left over.

     This is the one place in the app where orange carries data rather than
     "press me", and it earns it: on the owner's screen the money IS the brand,
     and a blue bar chart on an orange-accented page looks like a widget somebody
     pasted in. The ramp is monochrome so a reader compares lengths rather than
     hunting for which hue means which customer, and the legend beside it always
     names every slice.

     Red, amber and green never appear here. They mean down, owed and paid
     everywhere else in the app, and a series in one of them would spend that
     meaning for nothing. */
  --chart-1: #f4600a;
  --chart-2: #f8873e;
  --chart-3: #fba86f;
  --chart-4: #fdc9a4;
  --chart-5: #a89f99;
  --chart-6: #cdc6c1;

  /* The second data hue: a warm grey for "everything else" and for the
     comparison series behind the brand one. */
  --data-2: #8e8378;

  /* ---- Surfaces, light mode ---------------------------------------------
     One ground. The page is white and the cards on it are white; what
     separates them is a 1px line, not a change of tone. The washes are for
     table heads, hovers and the quiet insides of things.

     Warm, not cool. A blue-grey scale next to a safety-orange accent makes the
     orange look like it has been dropped in from another palette; a scale with
     a little red in it lets the brand colour sit on top of the page rather than
     argue with it. Nothing here is a brand or status colour — those are the
     block above and they do not move. Nothing is pure black either: #1a1614 on
     white is a slightly softer contrast and easier on the eye over a working
     day. */
  --page: #ffffff;
  --card: #ffffff;
  --wash: #fcfbfa;
  --wash-2: #f3f0ee;
  --sidebar: #f1edea;
  --line-2: #f3f0ee;
  --line: #e9e5e2;

  --ink-2: #5d544f;
  --ink-3: #93897f;

  --sheet-bg: #ffffff;
  --scrim: rgba(0, 0, 0, 0.32);

  /* Frosted chrome: a bar that lets the page move underneath it. */
  --glass: rgba(255, 255, 255, 0.86);
  --glass-blur: saturate(180%) blur(20px);

  --crit-wash: #fef3f2;
  /* The out-of-service calendar chip: ink accents on a very light warm grey,
     so a day with four of them reads as the heaviest thing on the grid
     without becoming a wall of dark rectangles. */
  --down-chip: #f2efed;
  /* A red line, for the one place a wash is not separation enough: a card
     that is finished but has no proof on it, an invoice past its terms. */
  --crit-line: #f7cfcb;
  /* The breath. One small step deeper than the wash and the line above, so a
     breathing banner reads as an inhale rather than a strobe, and a halo that
     starts at a third of the crit red and fades to nothing. */
  --pulse-wash: #fde7e5;
  --pulse-line: #efb5af;
  --pulse-halo: rgba(200, 52, 43, 0.32);
  --pulse-halo-0: rgba(200, 52, 43, 0);
  --warn-wash: #fef8ec;
  --good-wash: #eaf6ef;
  --brand-wash: #fdf0ea;
  --tint-info: #eaf2fd;
  --tint-violet: #f1effc;

  /* ---- Type --------------------------------------------------------------
     ONE family. There is no display face and no condensed cut — hierarchy comes
     from size and weight, never from width, which is why the variable file
     reaching 800 matters more here than a second family would.

     Body copy is 500, not 400. This face is round and open enough that 400
     reads thin sitting next to an 800 heading; half a step up puts them back in
     the same conversation.

     Tracking is optical: tight as the type gets bigger, loose as it gets
     smaller. That is what keeps a 26px heading from looking gappy and a 10.5px
     label from looking cramped.

     Serials are the one exception and stay monospace — they are compared digit
     by digit against a plate on a machine, and that is a job for a face where
     every digit is the same width. */
  --sans: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font: var(--sans);
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* ---- Shape -------------------------------------------------------------
     Three shapes and no more: 8px on anything with a border round it, 6px on
     a chip small enough that 8 looks like a bubble, and a pill on things that
     are one line of text and wider than they are tall. */
  --r: 8px;
  --r-chip: 6px;
  --r-pill: 999px;

  /* ---- Depth -------------------------------------------------------------
     None. There is not one box-shadow in this stylesheet and there should
     never be another.

     A shadow is a lie about a flat screen, and once a page has three of them
     at different depths nobody can say what any of them means. Separation here
     is a 1px line and a change of tone — which reads the same on a phone in
     daylight, prints correctly, and cannot drift. Anything that genuinely sits
     above the page — a sheet, the lightbox — is separated by a scrim instead. */

  /* ---- Urgency: a breath, not a blink -------------------------------------
     Two animations for the handful of things genuinely waiting on a human —
     a report nobody has picked up, a machine down for hours. Small elements
     (badges, dots) take the halo; banners take the breathing tint. Both are
     slow on purpose: roughly one cycle every two and a half seconds, because
     anything faster reads as an error state, and neither touches opacity,
     because blinking text is harsh and looks broken.

     The halo animates box-shadow — the one sanctioned appearance of that
     property in this file. It is not depth: it rests at zero spread and zero
     alpha, and what it draws is a ring of the crit red exhaling outward, not
     a surface floating. The no-shadow rule in the Depth block above stands.

     The pulse is never the message. Everything that pulses is already red and
     already worded as urgent, so when reduced motion switches the animation
     off (see the media query at the bottom of the file) nothing is lost. */
  --breathe: 2.6s;
  --halo: 2.4s;

  /* ---- Motion ------------------------------------------------------------
     One curve, everywhere: quick to leave, slow to settle. Nothing bounces —
     a dashboard that springs about is tiring by mid-afternoon. */
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --fast: 140ms;
  --med: 260ms;
  --slow: 400ms;

  /* ---- Metrics ----------------------------------------------------------- */
  --tap: 44px;
  --tap-lg: 52px;
  --pad: 20px;
  --sidebar-w: 250px;
  --topbar-h: 64px;
}

/* --------------------------------------------------------------- dark mode

   Three states, not two. The operating system decides unless somebody says
   otherwise, and saying otherwise stamps data-theme on <html>:

     no stamp        follow the operating system
     data-theme=dark dark, whatever the system says
     data-theme=light light, whatever the system says

   So the dark tokens are declared twice — once behind the media query (guarded
   so an explicit "light" wins) and once behind the attribute. Nothing else in
   the file cares: every rule reads the tokens.
   ------------------------------------------------------------------------ */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {

    --brand: #ff7a33;
    --brand-press: #ff9459;
    --on-brand: #14100c;
    --crit: #ff6961;
    --warn: #ff9f45;
    --good: #30d59a;

    --viz-1: #3987e5;
    --viz-2: #199e70;
    --viz-3: #c98500;
    --viz-4: #d55181;
    --viz-5: #9085e9;
    --viz-other: #6a6577;

    /* The same ramp, lifted for a black ground: on #000 the light end of the
       light-mode ramp glows and the dark end disappears, so both ends move. */
    --chart-1: #ff7a33;
    --chart-2: #e86a2a;
    --chart-3: #b8531f;
    --chart-4: #8a3d16;
    --chart-5: #8e857f;
    --chart-6: #5d5651;

    /* True black ground, and every surface above it a step lighter. Depth in
       the dark is built out of tone, never out of shadow — a shadow on black
       is invisible, so the steps have to be legible on their own. */
    --page: #131110;
    --card: #1e1c1b;
    --wash: #2d2a28;
    --wash-2: #3b3735;
    --sidebar: #161413;
    --line-2: #282524;
    --line: #332f2d;

    --ink: #f7f4f2;
    --ink-2: #9c9490;
    --ink-3: #807874;

    --sheet-bg: #1e1c1b;
    --scrim: rgba(0, 0, 0, 0.62);

    --glass: rgba(19, 17, 16, 0.86);

    --crit-wash: #3a1512;
    --down-chip: #2b2724;
    --crit-line: #5c2a26;
    --pulse-wash: #4a1c17;
    --pulse-line: #6f332d;
    --pulse-halo: rgba(255, 105, 97, 0.3);
    --pulse-halo-0: rgba(255, 105, 97, 0);
    --warn-wash: #3a2410;
    --good-wash: #0d3327;
    --brand-wash: #3a2410;
    --tint-info: #14243a;
    --tint-violet: #1f1b38;

    /* Kept faint. In the dark these barely register, which is correct — the
       tone steps above are what separate one surface from another. */
  }
}

:root[data-theme="dark"] {

    --brand: #ff7a33;
    --brand-press: #ff9459;
    --on-brand: #14100c;
    --crit: #ff6961;
    --warn: #ffa53d;
    --good: #30d59a;

    --viz-1: #3987e5;
    --viz-2: #199e70;
    --viz-3: #c98500;
    --viz-4: #d55181;
    --viz-5: #9085e9;
    --viz-other: #6a6577;

    /* The same ramp, lifted for a black ground: on #000 the light end of the
       light-mode ramp glows and the dark end disappears, so both ends move. */
    --chart-1: #ff7a33;
    --chart-2: #e86a2a;
    --chart-3: #b8531f;
    --chart-4: #8a3d16;
    --chart-5: #8e857f;
    --chart-6: #5d5651;
    --data-2: #9a918a;

    /* True black ground, and every surface above it a step lighter. Depth in
       the dark is built out of tone, never out of shadow — a shadow on black
       is invisible, so the steps have to be legible on their own. */
    --page: #131110;
    --card: #1c1a19;
    --wash: #232120;
    --wash-2: #2c2927;
    --sidebar: #191716;
    --line-2: #282524;
    --line: #332f2d;

    --ink: #f7f4f2;
    --ink-2: #a49b96;
    --ink-3: #8a817c;

    --sheet-bg: #1e1c1b;
    --scrim: rgba(0, 0, 0, 0.62);

    --glass: rgba(19, 17, 16, 0.86);

    --crit-wash: #3a1512;
    --down-chip: #2b2724;
    --crit-line: #5c2a26;
    --pulse-wash: #4a1c17;
    --pulse-line: #6f332d;
    --pulse-halo: rgba(255, 105, 97, 0.3);
    --pulse-halo-0: rgba(255, 105, 97, 0);
    --warn-wash: #3a2410;
    --good-wash: #0d3327;
    --brand-wash: #3a2410;
    --tint-info: #14243a;
    --tint-violet: #1f1b38;

    /* Kept faint. In the dark these barely register, which is correct — the
       tone steps above are what separate one surface from another. */
}

/* ==========================================================================
   Reset
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/* The UA rule for [hidden] is display:none, but any class that sets a display
   beats it on specificity. Without this, hiding an element in JS does nothing. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  /* 13px. The staff side is a screen somebody reads down a column of figures
     for an hour at a time, and half a point smaller fits one more row of a
     table on a laptop without costing legibility. The customer side overrides
     this to 17px — see the block at the end of this file. */
  font-size: 13px;
  font-weight: 500;
  line-height: 1.55;
  color: var(--ink);
  background: var(--page);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  min-height: 100dvh;
}

/* Optical tracking: the bigger the text, the tighter it wants to be set. Left
   at defaults a 26px heading looks gappy and a 13px label looks cramped, and
   the difference between the two is most of what makes type look considered. */
/* One family, so the whole hierarchy is weight and size. 800 for anything that
   is a heading or a number worth looking at, 700 for a card title, 500 for
   everything read. There is no 400 in the app: this face is open enough that
   400 body copy reads thin beside an 800 heading. */
h1, h2, h3, h4 {
  margin: 0;
  font-weight: 800;
  line-height: 1.2;
  color: var(--ink);
}
h1 { font-size: 26px; letter-spacing: -0.022em; }
h2 { font-size: 20px; letter-spacing: -0.018em; }
h3 { font-size: 15.5px; letter-spacing: -0.015em; }
h4 { font-size: 14px; letter-spacing: -0.012em; }

p { margin: 0 0 12px; }
p:last-child { margin-bottom: 0; }

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

img { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }

/* Tick boxes and radios are left as the browser draws them — they are the one
   control every operating system already draws well — but they take the brand
   colour when they are on, and a size a thumb can actually hit. */
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--brand);
  width: 17px;
  height: 17px;
  cursor: pointer;
}

/* A soft halo rather than a hard rule, drawn as an outline rather than a shadow.
   An outline follows whatever corner radius the element already has, sits
   outside the box without nudging the layout, and does not fight a card's own
   shadow the way a second box-shadow layer would. Nothing to list per-shape and
   nothing to keep in step. */
:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--brand) 50%, transparent);
  outline-offset: 2px;
}

::selection { background: color-mix(in srgb, var(--brand) 26%, transparent); color: var(--ink); }

/* The micro-label — and the ONLY thing in the app still set in capitals. Small,
   heavy and letter-spaced: at 10.5px capitals are a texture rather than
   shouting, and the spacing is what stops them closing up. Anything larger than
   this is sentence case, everywhere. */
.eyebrow {
  display: block;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 8px;
}

/* The unit beside a headline number: "7 WAITING", "$10,055.83 CAD", "5,240 h".
   Separate from the digits on purpose — it keeps the number itself scannable
   and stops "h" or "CAD" being read as part of the figure. */
.unit {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  white-space: nowrap;
  font-variant-numeric: normal;
}
/* A unit that is a real word is set in capitals like the micro-labels; one that
   is a symbol or an SI suffix keeps its own case, because "H" is not "h". */
.unit--word { text-transform: uppercase; }

/* ==========================================================================
   Icons
   ========================================================================== */

.icon { display: block; width: 1em; height: 1em; flex: 0 0 auto; overflow: visible; }
.btn .icon { width: 1.05em; height: 1.05em; }

.list__icon, .banner__icon, .alert-toast__icon, .pick__icon,
.severity__icon, .issue-icon, .camera-btn__icon, .empty__icon, .signal__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  color: var(--ink-2);
}

.list__icon--critical, .banner__icon--danger, .severity__icon--critical, .issue-icon--critical { color: var(--crit); }
.list__icon--medium, .banner__icon--warn, .severity__icon--medium, .issue-icon--medium { color: var(--warn); }
.list__icon--low, .banner__icon--ok, .severity__icon--low, .issue-icon--low { color: var(--good); }
.pick__icon--danger { color: var(--crit); }
.camera-btn__icon, .pick__icon { color: var(--brand); }

.sheet__close .icon, .thumb__remove .icon, .lightbox__close .icon { width: 17px; height: 17px; }
.lightbox__nav .icon { width: 24px; height: 24px; }
.lightbox__nav--prev .icon { transform: rotate(180deg); }
.track__dot .icon { width: 13px; height: 13px; }
.done__check .icon { width: 48px; height: 48px; }

/* ==========================================================================
   The shell: one rounded panel floating on the page, sidebar down the left
   ========================================================================== */

#app { min-height: 100dvh; padding: 0; }

.shell {
  display: flex;
  min-height: 100dvh;
  background: var(--page);
  overflow: hidden;
}

/* ---- desktop: a fixed-height shell with two independent scroll areas.

   The page itself never scrolls; the rail and the content each scroll on
   their own, so the sidebar stays put however far down a page you read.
   Deliberately a flex shell and not position:fixed with a margin — fixed
   rails collect z-index and width bugs, and a flex column reflows for free
   when the rail collapses. 100dvh and not 100vh, because on a phone 100vh
   lies about the browser chrome — and this block is ≥900px only anyway:
   below that the drawer takes over and the body scrolls normally again. */
@media (min-width: 900px) {
  html, body { height: 100dvh; overflow: hidden; }
  #app { height: 100dvh; }
  .shell { height: 100%; min-height: 0; }
  .sidebar { flex: none; width: var(--sidebar-w); height: 100%; overflow-y: auto; }
  /* min-width: 0 is load-bearing: without it one wide table blows the flex
     column out and puts a horizontal scrollbar on the whole app. */
  .content { flex: 1 1 auto; min-width: 0; height: 100%; overflow-y: auto; }
}

@media (min-width: 1100px) {
  #app { padding: 20px; }
  .shell {
    min-height: calc(100dvh - 40px);
    border-radius: var(--r);
    border: 1px solid var(--line);
  }
}

/* ---- sidebar ----

   The rail follows the room it is in: a very light warm grey in daylight, near
   black at night. It used to be near-black in both, which read as a slab of
   night stuck to the side of a white page — right on a phone at seven in the
   morning in a freezer aisle, wrong on a desk in an office with the blinds up.

   Everything on it reads from the eight --rail-* variables below, so switching
   the whole rail is switching these and nothing else. The values here are the
   daylight set; the two blocks underneath swap them for the dark.

   Where you are stays a filled orange row in both — the one place on the whole
   screen where a large area of colour is allowed. */

.sidebar {
  /* A neutral near-white, faintly deeper at the foot — cooler than the warm
     beige it replaced, so the rail reads as quiet chrome beside the page.
     The base stays a solid colour (things read it back); the fade is a
     separate layer on top, switched off at night. */
  --rail-bg: #f9f9fa;
  --rail-sheen: linear-gradient(180deg, rgba(24, 24, 27, 0) 0%, rgba(24, 24, 27, 0) 62%, rgba(24, 24, 27, 0.035) 100%);
  --rail-line: #e7e7ea;
  --rail-card: rgba(24, 24, 27, 0.05);
  --rail-text: #1a1a1e;
  --rail-text-2: #56565c;
  --rail-text-3: #8b8b92;
  --rail-hover: rgba(24, 24, 27, 0.05);
  /* The customer's "Report a problem" button: ink on the light rail, and the
     near-white it always was on the dark one. */
  --rail-cta-bg: var(--ink);
  --rail-cta-ink: #f7f4f2;

  display: flex;
  flex-direction: column;
  background-color: var(--rail-bg);
  background-image: var(--rail-sheen);
  border-right: 1px solid var(--rail-line);
  padding: 16px 12px 12px;
  overflow-y: auto;
  color: var(--rail-text);
  transition: width 180ms var(--ease), padding 180ms var(--ease);
}

/* The brand row carries the collapse toggle, right-aligned and quiet. */
.sidebar__head { display: flex; align-items: center; }
.sidebar__head .sidebar__brand { flex: 1 1 auto; min-width: 0; }
.rail-toggle {
  flex: 0 0 auto;
  border: 0;
  background: none;
  padding: 4px 6px;
  margin-top: -12px;
  border-radius: var(--r-chip);
  font-size: 15px;
  line-height: 1;
  color: var(--rail-text-3);
  cursor: pointer;
}
.rail-toggle:hover { color: var(--rail-text); background: var(--rail-hover); }
@media (max-width: 899px) { .rail-toggle { display: none; } }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .sidebar {
    --rail-bg: #17140f;
    --rail-sheen: none;
    --rail-line: #2a2621;
    --rail-card: rgba(255, 255, 255, 0.05);
    --rail-text: #f7f4f2;
    --rail-text-2: #a8a09b;
    --rail-text-3: #7a726d;
    --rail-hover: rgba(255, 255, 255, 0.06);
    --rail-cta-bg: #f7f4f2;
    --rail-cta-ink: #14100d;
  }
}
:root[data-theme='dark'] .sidebar {
  --rail-bg: #17140f;
  --rail-sheen: none;
  --rail-line: #2a2621;
  --rail-card: rgba(255, 255, 255, 0.05);
  --rail-text: #f7f4f2;
  --rail-text-2: #a8a09b;
  --rail-text-3: #7a726d;
  --rail-hover: rgba(255, 255, 255, 0.06);
  --rail-cta-bg: #f7f4f2;
  --rail-cta-ink: #14100d;
}

.sidebar__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 18px;
  text-decoration: none;
}
.sidebar__brand:hover { text-decoration: none; }
/* One mark for every theme: the black tile IS the logo — the white Y needs
   it, so it is never recoloured, never stripped, and never swapped for a
   light variant. Height only; the tile's own ratio sets the width. */
.sidebar__logo { height: 26px; width: auto; flex: 0 0 auto; }
.sidebar__word {
  font-size: 19px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--rail-text);
}

.sidebar__group { margin-bottom: 16px; }
.sidebar__label {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--rail-text-3);
  padding: 6px 12px;
}

.navlink {
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: 38px;
  padding: 0 12px;
  border-radius: var(--r);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.006em;
  color: var(--rail-text-2);
  white-space: nowrap;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.navlink:hover { background: var(--rail-hover); color: var(--rail-text); text-decoration: none; }
.navlink .icon { width: 17px; height: 17px; color: var(--rail-text-3); }
.navlink:hover .icon { color: var(--rail-text-2); }

/* Where you are. */
.navlink.is-active {
  background: var(--brand);
  color: #14100d;
  font-weight: 600;
}
.navlink.is-active .icon { color: #14100d; }
.navlink.is-active:hover { background: var(--brand-press); color: #14100d; }

.navlink__count {
  margin-left: auto;
  font-size: 11px;
  font-weight: 700;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--crit);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* On the orange row a red pill disappears; go dark instead. */
.navlink.is-active .navlink__count { background: #14100d; color: #fff; }

/* ---- the rail, collapsed to 64px of icons -----------------------------------

   Applied from the root attribute boot.js sets before first paint, desktop
   only. Labels and section headings go; the count badges STAY, moved to the
   icon's shoulder — those badges are the whole point of the rail at a glance.
   Grouping still reads as a 1px divider where the headings were. Tooltips are
   drawn by app.js into the body (a CSS tooltip would be clipped by the rail's
   own scroll box), after a 400ms hover. */
@media (min-width: 900px) {
  :root[data-rail='min'] .sidebar { width: 64px; padding-left: 8px; padding-right: 8px; }
  :root[data-rail='min'] .sidebar__word,
  :root[data-rail='min'] .sidebar__label,
  :root[data-rail='min'] .sidebar__who,
  :root[data-rail='min'] .navlink > span:not(.navlink__count),
  :root[data-rail='min'] .sidebar__cta span { display: none; }

  :root[data-rail='min'] .sidebar__head { flex-direction: column; gap: 2px; }
  :root[data-rail='min'] .sidebar__brand { padding: 4px 0 6px; justify-content: center; }
  :root[data-rail='min'] .rail-toggle { margin: 0 0 8px; }

  :root[data-rail='min'] .sidebar__group { margin-bottom: 10px; padding-bottom: 2px; }
  :root[data-rail='min'] .sidebar__group + .sidebar__group {
    border-top: 1px solid var(--rail-line);
    padding-top: 10px;
  }

  :root[data-rail='min'] .navlink {
    justify-content: center;
    padding: 0;
    min-height: 40px;
    position: relative;
  }
  :root[data-rail='min'] .navlink__count {
    position: absolute;
    top: 3px;
    right: 4px;
    margin: 0;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    font-size: 10px;
  }

  :root[data-rail='min'] .sidebar__cta { min-height: 40px; margin: 4px 0 12px; border-radius: var(--r); }
  :root[data-rail='min'] .sidebar__user { justify-content: center; padding: 6px 0; }
}

/* The instant chart readout: charts.js floats it over any hit band carrying
   data-plot-tip. In the body, fixed, so no card or scroll box clips it. */
/* The crosshair pieces must never eat the hover that reveals them. */
.plot__cross, .plot__cross-dot { pointer-events: none; }

.charttip {
  position: fixed;
  z-index: 200;
  padding: 5px 10px;
  border-radius: var(--r-chip);
  background: var(--ink);
  color: var(--card);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
}

/* Top companies inside the Jobs closed card: name, a count bar, the count. */
.kpi__list { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line-2); }
.kpi__listhead {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 6px;
}
.kpi__lrow {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  font-size: 12px;
  color: var(--ink-2);
  text-decoration: none;
}
.kpi__lrow:hover { color: var(--ink); text-decoration: none; }
.kpi__lname {
  flex: 0 1 46%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}
.kpi__lbar { flex: 1 1 auto; height: 5px; border-radius: var(--r-pill); background: var(--wash-2); overflow: hidden; }
.kpi__lbar i { display: block; height: 100%; border-radius: var(--r-pill); background: var(--brand); }
.kpi__lcount { flex: 0 0 auto; min-width: 18px; text-align: right; color: var(--ink); }

/* The tooltip app.js floats beside a collapsed rail icon. In the body, not the
   rail, so the rail's scroll box cannot clip it. */
.railtip {
  position: fixed;
  z-index: 200;
  padding: 5px 10px;
  border-radius: var(--r-chip);
  background: var(--ink);
  color: var(--card);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  animation: fade var(--fast) var(--ease);
}

.sidebar__spacer { flex: 1 1 auto; min-height: 12px; }

.sidebar__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 42px;
  margin: 4px 2px 14px;
  border-radius: var(--r-pill);
  background: var(--rail-cta-bg);
  color: var(--rail-cta-ink);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.008em;
  transition: filter var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.sidebar__cta:hover { filter: brightness(1.12); text-decoration: none; }
.sidebar__cta:active { transform: scale(0.98); }

/* The account row. A hover surface rather than a bordered card — one less
   line drawn on a rail that already has plenty to look at. */
.sidebar__user {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--r);
  border: none;
  background: transparent;
  width: 100%;
  cursor: pointer;
  text-align: left;
  transition: background var(--fast) var(--ease);
}
.sidebar__user:hover { background: var(--rail-card); text-decoration: none; }
.sidebar__avatar {
  width: 32px;
  height: 32px;
  border-radius: var(--r-pill);
  background: var(--brand);
  color: #14100d;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11.5px;
  font-weight: 700;
  flex: 0 0 auto;
}
.sidebar__who { min-width: 0; flex: 1 1 auto; }
.sidebar__name { display: block; font-size: 13.5px; font-weight: 600; color: var(--rail-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar__role { display: block; font-size: 11.5px; color: var(--rail-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- content column ---- */

.content { display: flex; flex-direction: column; min-width: 0; background: var(--page); }

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--topbar-h);
  padding: 12px max(var(--pad), env(safe-area-inset-right)) 12px max(var(--pad), env(safe-area-inset-left));
  border-bottom: 1px solid var(--line-2);
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}
.topbar__title { display: flex; align-items: center; gap: 9px; min-width: 0; font-size: 14.5px; font-weight: 700; letter-spacing: -0.014em; }
.topbar__title .icon { width: 17px; height: 17px; color: var(--ink-3); }
.topbar__spacer { flex: 1 1 auto; }

/* Toolbar controls: no borders. A toolbar with six outlined boxes in it reads
   as six decisions; the same six as plain glyphs that fill in on hover read as
   somewhere to look when you need them. */
.topbar__chip {
  display: none;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding: 0 15px;
  border-radius: var(--r-pill);
  border: none;
  background: var(--wash-2);
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -0.008em;
  white-space: nowrap;
  transition: background var(--fast) var(--ease);
}
.topbar__chip:hover { text-decoration: none; background: var(--line); }
.topbar__chip .icon { width: 15px; height: 15px; }

.iconbtn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: none;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  flex: 0 0 auto;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.iconbtn:hover { background: var(--wash-2); color: var(--ink); }
.iconbtn .icon { width: 18px; height: 18px; }
.iconbtn[data-count]::after {
  content: attr(data-count);
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--crit);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}
.iconbtn[data-count="0"]::after, .iconbtn:not([data-count])::after { display: none; }

.menu-btn { display: inline-flex; }

main {
  flex: 1 1 auto;
  width: 100%;
  padding: 28px max(var(--pad), env(safe-area-inset-right))
           calc(56px + env(safe-area-inset-bottom)) max(var(--pad), env(safe-area-inset-left));
}
@media (min-width: 1100px) {
  main { padding-left: 28px; padding-right: 28px; }
}

/* ---- mobile: the sidebar becomes a drawer ---- */

@media (max-width: 899px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(300px, 86vw);
    z-index: 95;
    transform: translateX(-102%);
    transition: transform var(--med) var(--ease);
    padding-top: calc(16px + env(safe-area-inset-top));
  }
  .sidebar.is-open { transform: none; }
  .sidebar__scrim {
    position: fixed;
    inset: 0;
    z-index: 94;
    background: var(--scrim);
    animation: fade var(--med) var(--ease);
  }
}
@media (min-width: 900px) {
  .menu-btn { display: none; }
  .topbar__chip { display: inline-flex; }
  .sidebar__scrim { display: none; }
}

/* ==========================================================================
   Page headers
   ========================================================================== */

.page-head { margin-bottom: 26px; }
/* The one loud thing on a page, and it is loud by WEIGHT now rather than by
   width or by shouting. Sentence case: a heading in capitals is harder to read
   at any size, and at 26px there is no need for the extra emphasis. */
.page-title {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.022em;
  line-height: 1.15;
  margin: 0;
}
.page-title em { font-style: normal; color: var(--brand); }
.page-sub { color: var(--ink-2); font-size: 13px; font-weight: 500; margin: 8px 0 0; max-width: 68ch; }

.section { margin: 0 0 30px; }
.section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 12px;
}
.section__title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.015em; }
.section__hint { font-size: 12.5px; font-weight: 500; color: var(--ink-3); }

/* A filter row with a control beside it — the stockroom's search box and New
   item, the bills list and Enter a bill — has room for both side by side on a
   desk and room for neither on a phone. Sharing 390px between them scrolled the
   chips down to a stub and squeezed the search box to about the width of the
   word inside it, with the button pushed under it half the width of the screen.
   Below 720px each part takes its own full-width row instead.

   Only the chips and the controls are given a row of their own: a heading with
   a "See all" link beside it is two short things and stays on one line. */
@media (max-width: 720px) {
  .section__head { flex-wrap: wrap; row-gap: 10px; }
  .section__head > .chips { flex: 1 0 100%; margin-bottom: 0; }
  .section__head > .btn-row,
  .section__head > .btn,
  .section__head > .input,
  .section__head > .searchbox { flex: 1 0 100%; }
  .section__head > .btn { justify-content: center; }
  .section__head > .btn-row > .btn { flex: 1 1 auto; justify-content: center; }
  .section__head > .btn-row > .input { flex: 1 1 100%; min-width: 0; }
}

/* ==========================================================================
   Buttons
   ========================================================================== */

/* Pills, all of them. A button is short, holds one line, and is wider than it
   is tall — which is exactly the shape that wants a fully round end. The size
   variants keep the pill; only the height and padding change. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 38px;
  padding: 0 18px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.008em;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  -webkit-user-select: none;
  user-select: none;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease),
    color var(--fast) var(--ease), transform var(--fast) var(--ease), opacity var(--fast) var(--ease);
}
.btn:hover { text-decoration: none; background: var(--line); }
/* Pressed, not just hovered. A control that gives way under the finger feels
   connected to it; 2% is enough to notice and not enough to distract. */
.btn:active { transform: scale(0.97); }
.btn[disabled], .btn.is-busy { opacity: 0.4; pointer-events: none; }

.btn--primary { background: var(--brand); color: var(--on-brand); }
.btn--primary:hover { background: var(--brand-press); }

.btn--dark { background: var(--ink); color: var(--card); }
.btn--dark:hover { background: var(--ink); opacity: 0.86; }

.btn--danger { background: var(--crit); color: #fff; }
.btn--danger:hover { background: var(--crit); filter: brightness(0.94); }

.btn--ok { background: var(--good); color: #fff; }
.btn--ok:hover { filter: brightness(0.94); }

.btn--ghost { background: transparent; color: var(--ink-2); padding: 0 12px; }
.btn--ghost:hover { background: var(--wash-2); color: var(--ink); }

/* The secondary action. A hairline rather than a fill, so the primary next to
   it is unmistakably the primary. */
.btn--outline { background: var(--card); color: var(--ink); border-color: var(--line); }
.btn--outline:hover { background: var(--wash); border-color: var(--ink-3); }

.btn--lg { min-height: 48px; font-size: 15px; padding: 0 26px; }
.btn--sm { min-height: 30px; font-size: 12.5px; padding: 0 13px; gap: 5px; }
.btn--block { display: flex; width: 100%; }

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

/* ==========================================================================
   Cards
   ========================================================================== */

/* White on grey, a soft corner, and a whisper of shadow. No border in light
   mode — the tone difference is the edge, and drawing a line as well makes it
   look twice. In the dark --line becomes a hairline, because there a
   shadow shows nothing. */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--pad);
}
.card + .card { margin-top: 16px; }

/* Every figure in the app lines up with the one above it.
   ---------------------------------------------------------------------------
   Set here, once, on the elements that hold numbers, rather than remembered in
   forty places — a column of prices where the digits are different widths is
   the single fastest way to make a page look homemade. */
.num,
.mono,
td,
th,
input[type='number'],
.tile__value,
.kpi__value,
.stat__value,
.badge {
  font-variant-numeric: tabular-nums;
}
.card--flush { padding: 0; overflow: hidden; }
.card__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 15px; }
.card__title { font-size: 15px; font-weight: 700; letter-spacing: -0.014em; }

/* ---- signal cards: the tinted row across the top of the dashboard ---- */

.signals {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(268px, 1fr);
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 6px;
  margin-bottom: 22px;
  scrollbar-width: thin;
}
@media (min-width: 1100px) {
  .signals { grid-auto-flow: row; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); overflow: visible; }
}

.signal {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  overflow: hidden;
  min-width: 0;
}
.signal__top {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  background: var(--wash);
  color: var(--ink-2);
  border-bottom: 1px solid transparent;
}
.signal__icon .icon { width: 15px; height: 15px; }
.signal__body { padding: 16px; flex: 1 1 auto; display: flex; flex-direction: column; }
.signal__value {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.signal__text { font-size: 13px; color: var(--ink-2); margin-top: 5px; line-height: 1.45; flex: 1 1 auto; }
.signal__foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 16px; }
.signal__link { font-size: 13px; font-weight: 600; color: var(--ink); }
.signal__link:hover { color: var(--brand); text-decoration: none; }

.signal--danger .signal__top { background: var(--crit-wash); color: var(--crit); border-bottom-color: transparent; }
.signal--warn .signal__top { background: var(--warn-wash); color: var(--warn); border-bottom-color: transparent; }
.signal--ok .signal__top { background: var(--good-wash); color: var(--good); border-bottom-color: transparent; }
.signal--info .signal__top { background: var(--tint-info); color: var(--viz-1); border-bottom-color: transparent; }
.signal--violet .signal__top { background: var(--tint-violet); color: var(--viz-5); border-bottom-color: transparent; }

/* ==========================================================================
   THE DASHBOARD

   A different page from the rest of the app, on purpose.

   Everywhere else the design separates things with space and tone: grey ground,
   white cards, a whisper of shadow, generous air. That is right for a screen
   somebody uses to do one task. It is wrong for a screen somebody reads — and
   this page is read, top to bottom, once a morning, for figures.

   So here: the ground is WHITE, cards are drawn with a hairline instead of a
   shadow, corners are barely rounded, and the type is a step smaller than the
   rest of the app. Nothing floats. Separation comes from borders and whitespace,
   never from a grey backdrop, and there is not one shadow on the page.

   Colour is rationed harder than anywhere else in the app:
     · ORANGE is the data — the primary chart series, job references, links
     · GREEN is money that arrived, and profit
     · AMBER is money that has not arrived yet
     · RED is only ever down, unassigned, or past terms
   Red, amber and green are never a chart series. That is the one rule that lets
   a red figure on this page still mean something.
   ========================================================================== */

.dash {
  /* The dashboard used to carry its own softer line and its own header wash.
     Both are the app's tokens now — one line colour, one wash, everywhere. */
  --dash-strip-line: var(--crit-line);

  font-size: 13px;
  /* Every figure on this page is compared with the one above it, so the digits
     have to be the same width. Set once, here, rather than remembered in forty
     places. */
  font-variant-numeric: tabular-nums;
}
.dash /* A figure in any table lines up on the right, whichever table it is in.
   `table.data` had its own rule and `.table` did not, so the shelf forecast on
   Foresight was reading its quantities left-aligned. One rule for both. */
.num { font-variant-numeric: tabular-nums; }
table .num,
table th.num,
table td.num { text-align: right; white-space: nowrap; }
.dash .right { text-align: right; }
.dash .good { color: var(--good); }
.dash .dim { color: var(--ink-3); font-weight: 500; }


/* The ground goes white while this screen is the one on it.

   Keyed off the presence of the dashboard rather than a class the router has to
   remember to add: the page cannot then get out of step with the route, and
   nothing else in the app has to know this screen is different. */
.content:has(.dash),
.content:has(.dash) main { background: var(--card); }

/* ---------------------------------------------------------------- the strips

   Three of them, and the only red on the page. Not cards: a heading and a run of
   links, because the point is to be read as a sentence rather than scanned as a
   grid. Each one is absent at zero — a dashboard that says "0 forklifts down"
   in red every morning teaches people to ignore red.
   ------------------------------------------------------------------------ */

/* The technician's own screens read at 15px with warehouse-sized targets:
   they are used standing up, on a phone, often in gloves. */
.dash--mine { font-size: 15px; }
.dash--mine .myjob__who { font-size: 16.5px; }
.dash--mine .myjob__what, .dash--mine .list__title { font-size: 15px; }
.dash--mine .btn--sm { min-height: 44px; font-size: 14px; }
.dash--mine .list__item { min-height: 60px; }

/* The pulse: see the Urgency note in the token block. Applied and removed by
   the views — never left on an element whose work has been done. */
@keyframes yl-halo {
  0%   { box-shadow: 0 0 0 0 var(--pulse-halo); }
  70%  { box-shadow: 0 0 0 9px var(--pulse-halo-0); }
  100% { box-shadow: 0 0 0 0 var(--pulse-halo-0); }
}

@keyframes yl-breathe {
  0%, 100% { background: var(--crit-wash); border-color: var(--crit-line); }
  50%      { background: var(--pulse-wash); border-color: var(--pulse-line); }
}

.pulse-halo    { animation: yl-halo var(--halo, 2.4s) ease-out infinite; }
.pulse-breathe { animation: yl-breathe var(--breathe, 2.6s) ease-in-out infinite; }

.strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  margin: 0 0 14px;
  padding: 10px 14px;
  background: var(--crit-wash);
  border: 1px solid var(--dash-strip-line, #f7cfcb);
  border-radius: var(--r);
  line-height: 1.5;
}
.strip--heavy { border-color: var(--crit); }

/* Booked work is good news, not an alarm: the visits strip sits in neutral
   grey, keeps its shape, and only "no tech" inside it is allowed a colour. */
.strip--quiet { background: var(--wash); border-color: var(--line); }
.strip--quiet .strip__head { color: var(--ink-2); }
.strip--quiet .dchip { border-color: var(--line); }
.strip--quiet .dchip:hover { border-color: var(--ink-3); }
.strip--quiet .dchip__loud { color: var(--ink-2); }
.strip__head {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--crit);
}
.strip__head .icon { width: 14px; height: 14px; flex: 0 0 auto; }
.strip__items { display: flex; flex-wrap: wrap; gap: 4px 16px; min-width: 0; }

.strip__link {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 2px;
}
.strip__link:hover { color: var(--crit); text-decoration-color: currentColor; }

/* A machine, or a visit: bold name, grey owner, red urgency. */
.dchip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 3px 9px;
  background: var(--card);
  border: 1px solid var(--dash-strip-line, #f7cfcb);
  border-radius: var(--r-chip);
  font-size: 12.5px;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.dchip:hover { text-decoration: none; border-color: var(--crit); }
.dchip b { font-weight: 700; }
.dchip__soft { color: var(--ink-3); }
.dchip__loud { color: var(--crit); font-weight: 700; }
.dchip__tick { display: inline-flex; color: var(--good); }
.dchip__tick .icon { width: 13px; height: 13px; }
.dchip__gap { color: var(--warn); font-weight: 700; font-size: 11.5px; }

/* --------------------------------------------------------------- the greeting */

.dash__head { margin: 18px 0 16px; }
.dash__greeting {
  margin: 0;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.022em;
  line-height: 1.15;
}
.dash__sub { margin: 5px 0 0; font-size: 12.5px; font-weight: 500; color: var(--ink-3); }

/* ----------------------------------------------------------------- a section

   A hairline box. No shadow, and 8px corners rather than the 13 the rest of the
   app uses — square-ish reads as a document, and this page is a document.
   ------------------------------------------------------------------------ */

.panel {
  margin: 0 0 14px;
  padding: 14px 16px 16px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.panel__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-bottom: 12px;
}
.panel__title { margin: 0; font-size: 14px; font-weight: 800; letter-spacing: -0.012em; }
.panel__hint { font-size: 12px; color: var(--ink-3); }
.panel__link { margin-left: auto; font-size: 12.5px; font-weight: 700; color: var(--brand); }
.panel__foot { margin: 12px 0 0; font-size: 12px; color: var(--ink-3); line-height: 1.5; }

/* ------------------------------------------------- a technician's own screen

   Read at arm's length, one-handed, in a van. So: bigger type than the owner's
   dashboard, the customer and the machine as the loudest line, the address and
   the phone number always visible rather than a tap away, and every action a
   full-height target. No figure on any of these cards is money, because none is
   ever sent to this screen. */

.panel--live { border-color: var(--warn); }
/* Finished, and nobody photographed it. Red, and at the top of the screen,
   because it is the one kind of unfinished business that looks finished. */
.panel--proof { border-color: var(--crit); }
.myproof__why { margin: -4px 0 12px; font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }
.myjob--proof { border-color: var(--crit); background: var(--crit-wash); }

/* The line at the top of the finish-the-job sheet: what proof is already on
   this job, or the camera that puts some there. */
.proofstrip {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 11px 13px;
  border: 1px solid var(--crit);
  border-radius: var(--r);
  background: var(--crit-wash);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.proofstrip .btn { margin-left: auto; }
.proofstrip--ok { border-color: var(--good); background: var(--good-wash); }

/* The record on the report itself. */
.proof--missing { border-color: var(--crit); }

/* ------------------------------------------------------------------- trips

   A site is the heading; the jobs are underneath it. The company and the
   address are said once, the times in a line, and the address is a link — the
   next thing that happens after reading it is driving there. */

.sites { display: grid; gap: 12px; }

.site {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  overflow: hidden;
}
.site__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 14px;
  padding: 12px 14px;
  background: var(--wash);
  border-bottom: 1px solid var(--line);
}
.site__who { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.site__name { font-size: 15.5px; font-weight: 800; color: var(--ink); }
.site__where {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  text-decoration: none;
}
.site__where:hover { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }
.site__meta { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; }
.site__count { font-size: 12.5px; font-weight: 700; color: var(--ink-2); }
/* The cards inside a site lose their own frame: the site is the frame. */
.site .myjobs { padding: 10px; gap: 8px; }
.site .myjob { border-color: var(--line); }

.weekday { padding: 4px 0 2px; }
.weekday__name {
  margin: 12px 0 8px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* --------------------------------------------- needs something from you

   Short by construction — a week of photographs, unconfirmed hours, and claims
   with the office. Not red: none of it is an emergency, all of it is one tap
   to clear, and a red panel that is always there stops being read. */
.panel--needs { border-color: var(--warn); }
.needs { display: grid; gap: 8px; }
.needs__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}
.needs__what { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.needs__title { font-size: 13px; font-weight: 800; color: var(--ink); }
.needs__who { font-size: 13px; font-weight: 700; color: var(--brand); text-decoration: none; }
.needs__who:hover { text-decoration: underline; text-underline-offset: 2px; }
.needs__when { font-size: 11.5px; font-weight: 600; color: var(--ink-3); }

/* --------------------------------------------------------------- open work

   What nobody has taken, on a technician's own schedule. Read standing up, one
   thumb, deciding whether to drive there: the customer loudest, then what is
   wrong with it, then where and how long. No figure anywhere on it. */

.openjobs { display: grid; gap: 10px; }

.openjob {
  padding: 13px 14px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}
.openjob--asked { border-color: var(--warn); background: var(--warn-wash); }
.openjob__head { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-bottom: 7px; }
.openjob__when {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-left: auto;
}
.openjob__who {
  display: block;
  font-size: 16px;
  font-weight: 800;
  color: var(--ink);
  text-decoration: none;
  line-height: 1.25;
}
.openjob__who:hover { color: var(--brand); }
.openjob__what { margin: 3px 0 0; font-size: 13.5px; font-weight: 700; color: var(--ink-2); }
.openjob__note { margin: 6px 0 0; font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.openjob__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin-top: 10px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-3);
}
.openjob__facts span { display: inline-flex; align-items: center; gap: 5px; }
.openjob__do { margin-top: 12px; }

/* The count and the link on the technician's own screen — the list itself is
   on the schedule. */
.openlink {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  text-decoration: none;
}
.openlink:hover { border-color: var(--brand); }
.openlink__count {
  min-width: 38px;
  text-align: center;
  font-size: 20px;
  font-weight: 800;
  color: var(--brand);
}
.openlink__body { display: flex; flex-direction: column; gap: 2px; }
.openlink__title { font-size: 14px; font-weight: 800; color: var(--ink); }
.openlink__sub { font-size: 12px; font-weight: 600; color: var(--ink-3); }
.openlink__chev { margin-left: auto; color: var(--ink-3); display: inline-flex; }

/* When the parts are due, on a job that is stopped until they arrive. */
.myjob__eta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 8px 0 0;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-2);
}

/* Somebody has said they cannot take this one. On the job, and on the diary
   entry, because the job has NOT moved and must not look as though it has. */
.handback {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px 13px;
  border: 1px solid var(--warn);
  border-radius: var(--r);
  background: var(--warn-wash);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink);
}
.calev--handback { outline: 1.5px solid var(--warn); outline-offset: -1.5px; }

/* ------------------------------------------------- photographs still to send

   Nothing is wrong. A photograph was taken and is waiting for a bar of signal,
   which in a cold store is most of the day. So: quiet, out of the way, and in
   no colour that means trouble. */
.pqueue {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  box-shadow: none;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-2);
}
.pqueue__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--brand);
  animation: pqueue-pulse 1.8s ease-in-out infinite;
}
@keyframes pqueue-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
  .pqueue__dot { animation: none; }
}


.myjobs { display: grid; gap: 10px; }

.myjob {
  padding: 13px 14px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}
.myjob--live { border-color: var(--warn); background: var(--warn-wash); }

.myjob__head { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-bottom: 7px; }
.myjob__when {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.myjob__who {
  display: block;
  font-size: 16.5px;
  font-weight: 800;
  letter-spacing: -0.014em;
  color: var(--ink);
  text-decoration: none;
}
.myjob__who:hover { color: var(--brand); }
.myjob__what { margin: 2px 0 0; font-size: 13.5px; font-weight: 600; color: var(--ink-2); }
.myjob__note {
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--ink-2);
  line-height: 1.45;
  /* Two lines of what the customer typed. The rest is on the report. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.myjob__where { display: grid; gap: 3px; margin-top: 9px; font-size: 12.5px; color: var(--ink-3); }
.myjob__where span { display: flex; align-items: center; gap: 6px; }
.myjob__where svg { flex: 0 0 auto; }
.myjob__do { margin-top: 11px; gap: 8px; }

@media (max-width: 560px) {
  /* Thumb-sized, and never two words wide: on a phone each action gets its own
     line rather than being squeezed three abreast. */
  .myjob__do { flex-direction: column; align-items: stretch; }
  .myjob__do .btn { width: 100%; justify-content: center; min-height: 44px; }
}

/* ---------------------------------------------------------- work completed */

.tally-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 10px;
}
.tally {
  display: grid;
  gap: 2px;
  padding: 12px 14px 13px;
  background: var(--brand-wash);
  border-radius: var(--r-chip);
}
.tally__label {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand);
}
.tally__value {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--brand);
}
.tally__money { font-size: 13px; font-weight: 700; color: var(--ink); }
.tally__note { font-size: 11.5px; font-weight: 600; color: var(--brand); opacity: 0.85; }

/* Who did it: one row each, no chart. Three names do not need a bar chart. */
.whodid {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 0 10px;
}
.whodid__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 2px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
}
.whodid__who { display: inline-flex; align-items: center; gap: 8px; font-weight: 700; min-width: 0; }
.whodid__jobs { margin-left: auto; color: var(--ink-3); font-weight: 600; }
.whodid__money { color: var(--good); font-weight: 700; }

.avatar {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  color: var(--ink-2);
  font-size: 8.5px;
  font-weight: 800;
  letter-spacing: 0.03em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* A labelled number field small enough to sit at the end of a person's row on
   Settings. Lives here because it belongs to the same feature as the dashboard
   figures it feeds. */
.minifield { display: inline-grid; gap: 3px; }
.minifield__label {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.minifield .input { width: 104px; font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------- quick actions */

.quick { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.quick__label {
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-3);
  margin-right: 4px;
}
.quick__btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
  cursor: pointer;
  text-decoration: none;
}
.quick__btn:hover { text-decoration: none; border-color: var(--line); background: var(--wash); }
.quick__btn .icon { width: 14px; height: 14px; color: var(--brand); }

/* ------------------------------------------------------------- the four KPIs */

.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: 12px;
  margin: 0 0 14px;
}
.kpi {
  display: flex;
  flex-direction: column;
  padding: 13px 15px 15px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  min-width: 0;
}
.kpi__top { display: flex; align-items: center; gap: 8px; }
.kpi__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--r-chip);
  flex: 0 0 auto;
}
.kpi__icon--ok { background: var(--good-wash); color: var(--good); }
.kpi__icon--warn { background: var(--warn-wash); color: var(--warn); }
.kpi__icon--info { background: var(--tint-info); color: var(--viz-1); }
.kpi__icon--accent { background: var(--brand-wash); color: var(--brand); }
.kpi__title { font-size: 13px; font-weight: 700; }
.kpi__period {
  margin-top: 7px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.kpi__value {
  margin-top: 3px;
  font-size: 25px;
  font-weight: 800;
  letter-spacing: -0.022em;
  line-height: 1.15;
}
.kpi__value--ok { color: var(--good); }
.kpi__value--warn { color: var(--warn); }
.kpi__note { margin: 7px 0 0; font-size: 11.5px; color: var(--ink-3); line-height: 1.45; }
.kpi__note--bad { color: var(--crit); font-weight: 600; }
.kpi__note--good { color: var(--good); font-weight: 600; }
.kpi__link { display: inline-block; margin-top: 9px; font-size: 12px; font-weight: 700; color: var(--brand); }
.kpi__plot { margin-top: 10px; height: 40px; }
.kpi__plot .plot { height: 40px; width: 100%; }

/* The three lines a month's revenue splits into. */
.split { list-style: none; margin: 9px 0 0; padding: 0; display: grid; gap: 5px; }
.split li { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--ink-2); }
.split__dot { width: 7px; height: 7px; border-radius: var(--r-pill); flex: 0 0 auto; }
.split__dot--1 { background: var(--chart-1); }
.split__dot--2 { background: var(--chart-2); }
.split__dot--3 { background: var(--chart-5); }
.split__value { margin-left: auto; font-weight: 700; color: var(--ink); }

/* What is left after the work, and what is left after the month. Ruled off from
   the revenue split above it, because it answers a different question. */
.bottom { margin-top: 11px; padding-top: 9px; border-top: 1px solid var(--line); display: grid; gap: 5px; }
.bottom__row { display: flex; align-items: baseline; gap: 7px; font-size: 12px; color: var(--ink-2); }
.bottom__label { font-weight: 600; }
.bottom__pct { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.bottom__value { margin-left: auto; font-weight: 700; color: var(--ink); }
.bottom__value--bad { color: var(--crit); }
.bottom__row--net .bottom__label,
.bottom__row--net .bottom__value { font-size: 13px; }

/* "Worth a look", squeezed into this page's rhythm: hairline instead of shadow,
   8px corners, and rows a little tighter than the ones on Foresight. */
.dash__worth { margin: 0 0 14px; }
.dash__worth .worth__title { font-size: 14px; }
.dash__worth .worth__more { font-size: 12.5px; }
.dash__worth .list {
  border-color: var(--line);
  border-radius: var(--r);
}
.dash__worth .worth__row { padding-top: 9px; padding-bottom: 9px; }
.dash__worth .worth__line { font-size: 12.5px; }

/* ----------------------------------------------------------------- the charts */

.dash__charts {
  display: grid;
  grid-template-columns: 1.75fr 1fr;
  gap: 12px;
  align-items: start;
}
@media (max-width: 1000px) {
  .dash__charts { grid-template-columns: minmax(0, 1fr); }
}

.gplot { width: 100%; height: auto; display: block; }
.gplot__grid { stroke: var(--line); stroke-width: 1; }
.gplot__grid--base { stroke: var(--line); }
.gplot__ytick,
.gplot__xtick {
  fill: var(--ink-3);
  font-size: 10px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.gplot__bar--a { fill: var(--chart-1); }
.gplot__bar--b { fill: var(--chart-3); }
.gplot__col:hover .gplot__bar { filter: brightness(0.94); }
/* The target is a line you are trying to cross, not a series — dashed, grey,
   and never a colour that could be mistaken for data. */
.gplot__target { stroke: var(--ink-3); stroke-width: 1.5; stroke-dasharray: 5 4; }

.chartkey {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 10px 0 0;
  padding: 0;
  font-size: 11.5px;
  color: var(--ink-2);
}
.chartkey li { display: flex; align-items: center; gap: 7px; }
.chartkey__dot { width: 9px; height: 9px; border-radius: var(--r-chip); }
.chartkey__dot--1 { background: var(--chart-1); }
.chartkey__dot--2 { background: var(--chart-3); }
.chartkey__dash {
  width: 14px;
  height: 0;
  border-top: 1.5px dashed var(--ink-3);
}

/* The donut and its legend: the ring says the shape, the legend says the money. */
.donut { display: grid; grid-template-columns: 1fr; gap: 14px; justify-items: center; }
.donut__ring { width: 100%; max-width: 180px; height: auto; }
.donut__slice { transition: opacity var(--fast) var(--ease); }
.donut:hover .donut__slice { opacity: 0.55; }
.donut__slice:hover { opacity: 1; }

.donut__legend { list-style: none; margin: 0; padding: 0; width: 100%; display: grid; gap: 7px; }
.donut__row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; min-width: 0; }
.donut__dot { width: 9px; height: 9px; border-radius: var(--r-chip); flex: 0 0 auto; }
.donut__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-2);
}
.donut__value { font-weight: 700; color: var(--ink); }

/* --------------------------------------------------------- the revenue target */

.goal__now { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; margin: 0 0 10px; }
.goal__figure { font-size: 26px; font-weight: 800; letter-spacing: -0.022em; line-height: 1.1; }
.goal__of { font-size: 12.5px; color: var(--ink-3); }

.goal__bar {
  display: block;
  height: 7px;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  overflow: hidden;
}
.goal__bar--sm { height: 5px; margin-top: 8px; }
.goal__fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--brand); }

.goal-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 10px;
  margin-top: 14px;
}
.goal {
  padding: 11px 13px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
}
.goal__head { display: flex; align-items: baseline; gap: 8px; }
.goal__label {
  flex: 1 1 auto;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.goal__pct { font-size: 12.5px; font-weight: 800; }
.goal__amount { margin-top: 4px; font-size: 17px; font-weight: 800; letter-spacing: -0.018em; }
.goal__perday { margin-top: 7px; font-size: 11.5px; color: var(--ink-3); }

/* The same four bands the Targets sheet grades against, on the same figures.
   A tile reading green where the sheet reads amber would make somebody trust
   neither of them, so both take their colour from src/targets.js band(). */
.goal--red { border-color: var(--crit-line, var(--line)); background: var(--crit-wash); }
.goal--red .goal__pct { color: var(--crit); }
.goal--red .goal__fill { background: var(--crit); }

.goal--amber { background: var(--warn-wash); }
.goal--amber .goal__pct { color: var(--warn); }
.goal--amber .goal__fill { background: var(--warn); }

.goal--green { background: var(--good-wash); }
.goal--green .goal__pct { color: var(--good); }
.goal--green .goal__fill { background: var(--good); }

/* Half as much again as the month was planned around. Its own colour, because
   it is either a very good month or a target set too low. */
.goal--over { background: var(--brand-wash); }
.goal--over .goal__pct { color: var(--brand); }
.goal--over .goal__fill { background: var(--brand); }

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

   Dense on purpose. A header that is quieter than the rows, hairline dividers,
   and every number right-aligned and tabular so a column can be read down.
   ------------------------------------------------------------------------ */

.tablewrap { overflow-x: auto; margin: 0 -16px -16px; padding: 0 16px 16px; }

.dtable { width: 100%; border-collapse: collapse; min-width: 620px; }
.dtable thead th {
  padding: 7px 10px;
  background: var(--wash);
  border-bottom: 1px solid var(--line);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: left;
  white-space: nowrap;
}
.dtable thead th.right { text-align: right; }
.dtable tbody td {
  padding: 9px 10px;
  border-bottom: 1px solid var(--line);
  font-size: 12.5px;
  font-weight: 600;
  vertical-align: middle;
  white-space: nowrap;
}
.dtable tbody tr:last-child td { border-bottom: 0; }
.dtable tbody tr:hover { background: var(--wash); }
.dtable tbody tr[data-action] { cursor: pointer; }
.dtable td .dim { font-size: 11.5px; margin-top: 1px; }
.dtable__rank { color: var(--ink-3); font-weight: 700; width: 28px; }
.dtable__ref { color: var(--brand); font-weight: 700; }

/* A bar and its percentage, small enough to live in a table cell. */
.minibar {
  display: inline-block;
  vertical-align: middle;
  width: 74px;
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  overflow: hidden;
}
.minibar__fill { display: block; height: 100%; background: var(--brand); }
.minibar__pct { margin-left: 8px; font-size: 11.5px; font-weight: 700; color: var(--ink-3); }

/* Status, as a word rather than a colour on its own. */
.pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.pill--ok { background: var(--good-wash); color: var(--good); }
.pill--warn { background: var(--warn-wash); color: var(--warn); }
.pill--info { background: var(--tint-info); color: var(--viz-1); }
.pill--danger { background: var(--crit-wash); color: var(--crit); }

/* The way out to the figures that do not belong on a morning read. */
.dash__more { margin: 22px 0 0; text-align: center; font-size: 12.5px; }
.dash__more a { color: var(--ink-3); }
.dash__more a:hover { color: var(--ink); text-decoration: none; }

/* ---- narrow ------------------------------------------------------------- */

@media (max-width: 620px) {
  .dash__greeting { font-size: 20px; }
  .tally__value { font-size: 23px; }
  .kpi__value { font-size: 22px; }
  .goal__figure { font-size: 22px; }
  .panel { padding: 12px 13px 14px; }
  .tablewrap { margin: 0 -13px -14px; padding: 0 13px 14px; }
  /* The strips get long on a phone; each link on its own line beats a wrapped
     run of them with no clear breaks. */
  .strip__items { flex-direction: column; gap: 5px; }
  .dchip { white-space: normal; }

  /* The chart scales down proportionally, text and all, so at 390px a 10px axis
     label renders at about five and a half. The type inside the SVG goes up to
     compensate — it is the only way to keep an axis readable on a phone without
     giving the chart its own layout. */
  .gplot__ytick, .gplot__xtick { font-size: 17px; }
}

.worth { margin: 0; }
.worth__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 12px;
}
.worth__title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.015em; }
.worth__more { font-size: 13px; color: var(--ink-3); white-space: nowrap; }
.worth__more:hover { color: var(--ink); text-decoration: none; }

.worth__row { gap: 10px; cursor: pointer; }
/* One line, and it stays one line: the sentence takes whatever is left after
   the buttons and gives up with an ellipsis rather than wrapping the row to
   two. A block whose height depends on how wordy a rule got is not a block. */
.worth__line {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13.5px;
  font-weight: 500;
}

/* Visible on hover, and on keyboard focus — otherwise there is no way to reach
   it without a mouse. Holds its space at all times so nothing shifts when it
   appears. */
.worth__x {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--fast) var(--ease), background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.worth__row:hover .worth__x,
.worth__x:focus-visible { opacity: 1; }
.worth__x:hover { background: var(--wash-2); color: var(--ink); }

.worth__go { flex: 0 0 auto; }

/* Nothing firing. One grey line, no card, no illustration — an empty state
   that draws a box around itself is louder than the thing it replaced. */
.worth__none {
  margin: 0;
  padding: 4px 0;
  font-size: 13.5px;
  color: var(--ink-3);
}

/* ---- the full list, on Foresight ---- */

.worth__toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
}
.worth__dismissedHead { margin: 20px 0 10px; font-size: 13px; color: var(--ink-3); }
.worth__dismissedList .worth__row { opacity: 0.62; cursor: default; }
.worth__row.is-dismissed:hover { background: var(--card); }

@media (max-width: 620px) {
  /* On a phone the sentence gets the room and the dismiss lives under a press
     rather than a hover, which a touch screen does not have. */
  .worth__x { opacity: 1; width: 24px; height: 24px; }
  .worth__row { gap: 6px; }
}

/* ---- KPI tiles with a plot underneath ---- */

.tiles {
  display: grid;
  /* Two across on a phone; the auto-fit only kicks in once there is room. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 22px;
}
@media (min-width: 620px) {
  .tiles { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }
  .tiles--3 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
}
@media (max-width: 619px) {
  .tile { padding: 13px 13px 11px; }
  .tile__value { font-size: 25px; }
  .tile__value--money { font-size: 18px; }
  .tile__plot { height: 40px; margin-top: 10px; }
  /* Two footnotes side by side wrap into a mess at this width; keep the first. */
  .tile__foot { display: block; }
  .tile__foot span + span { display: none; }
}

.tile {
  display: block;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 18px 18px 14px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
  transition: transform var(--fast) var(--ease);
}
/* A tile you can open lifts a millimetre on hover rather than growing a
   darker outline. Same information, less ink. */
a.tile:hover { text-decoration: none; transform: translateY(-1px); }
a.tile:active { transform: none; }

/* A headline number. The weight does the work a poster face used to do, and
   the unit rides beside it rather than being folded into the digits. */
.tile__value {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 7px;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.tile__value--money { font-size: 24px; }
.tile__label {
  margin-top: 7px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
  line-height: 1.4;
}
.tile__plot { margin-top: 12px; height: 56px; }
.tile__foot { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; font-size: 11px; color: var(--ink-3); }
.tile--danger .tile__value { color: var(--crit); }
.tile--warn .tile__value { color: var(--warn); }
.tile--ok .tile__value { color: var(--good); }
.tile--primary .tile__value { color: var(--brand); }

/* the ↑/↓ chip */
.delta {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-top: 8px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  font-size: 11.5px;
  font-weight: 600;
  background: var(--wash-2);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.delta--good { background: var(--good-wash); color: var(--good); }
.delta--bad { background: var(--crit-wash); color: var(--crit); }
.delta__since { font-weight: 500; opacity: 0.75; }

/* ==========================================================================
   Charts
   ========================================================================== */

.plot { width: 100%; height: 100%; display: block; overflow: visible; }
.plot--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 52px;
  border-radius: var(--r);
  background: var(--wash);
  color: var(--ink-3);
  font-size: 12px;
}

/* the crosshair layer */
.plot__cross { stroke: var(--line); stroke-width: 1; opacity: 0; }
.plot__cross-dot { fill: var(--viz-1); opacity: 0; }
.plot__hit:hover .plot__cross, .plot__hit:hover .plot__cross-dot { opacity: 1; }
.plot__bar rect { transition: opacity var(--fast) var(--ease); }
.plot--bars:hover .plot__bar rect { opacity: 0.45; }
.plot--bars .plot__bar:hover rect { opacity: 1; }

.chart-row { display: grid; gap: 14px; }
@media (min-width: 940px) { .chart-row--2 { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); } }


.plot__slice { transition: opacity var(--fast) var(--ease); }

.legend { list-style: none; margin: 0; padding: 0; flex: 1 1 180px; min-width: 0; display: grid; gap: 7px; }
.legend__item { display: flex; align-items: center; gap: 9px; font-size: 13px; min-width: 0; }
.legend__swatch { width: 9px; height: 9px; flex: 0 0 auto; border-radius: var(--r-pill); }
.legend__label { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; }
.legend__value { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink); }

.hbars { list-style: none; margin: 0; padding: 0; display: grid; gap: 11px; }
.hbars__row { display: grid; grid-template-columns: minmax(84px, auto) 1fr auto; align-items: center; gap: 12px; font-size: 13px; }
.hbars__label { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hbars__track { height: 8px; border-radius: var(--r-pill); background: var(--wash-2); overflow: hidden; }
.hbars__fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--viz-1); }
.hbars__value { font-weight: 600; font-variant-numeric: tabular-nums; }

.meter { display: block; height: 8px; border-radius: var(--r-pill); background: var(--wash-2); overflow: hidden; min-width: 60px; }
.meter__fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--good); transition: width var(--slow) var(--ease); }
.meter__fill--soon { background: var(--warn); }
.meter__fill--over { background: var(--crit); }

/* ==========================================================================
   Banners
   ========================================================================== */

.banner {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 15px 17px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  margin-bottom: 16px;
  font-size: 14px;
  text-decoration: none;
  color: inherit;
  background: var(--card);
}
a.banner:hover { text-decoration: none; }
.banner__icon { width: 22px; height: 22px; margin-top: 1px; }
.banner__body { flex: 1 1 auto; min-width: 0; }
.banner__title { display: block; font-weight: 600; }
.banner__text { display: block; font-size: 13px; color: var(--ink-2); margin-top: 2px; }
.banner__chev { color: var(--ink-3); flex: 0 0 auto; align-self: center; }
.banner__chev .icon { width: 17px; height: 17px; }

.banner--danger { background: var(--crit-wash); border-color: transparent; }
.banner--danger .banner__title { color: var(--crit); }
.banner--warn { background: var(--warn-wash); border-color: transparent; }
.banner--warn .banner__title { color: var(--warn); }
.banner--ok { background: var(--good-wash); border-color: transparent; }
.banner--ok .banner__title { color: var(--good); }
.banner--info { background: var(--tint-info); border-color: transparent; }

/* ==========================================================================
   Lists
   ========================================================================== */

/* One rounded card holding rows, the way a grouped list works: the container
   has the corner and the shadow, the rows inside have neither. */
.list {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  overflow: hidden;
}

/* The separator is drawn as a pseudo-element rather than a border, so it can
   start where the text starts instead of at the edge of the card. Lining the
   rule up with the first character is the detail that makes a stack of rows
   read as one list rather than as a pile of boxes — and it means the last row
   needs no special case, because the rule simply is not drawn on it. */
.list__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 13px 16px;
  min-height: var(--tap);
  border: none;
  color: inherit;
  text-decoration: none;
  background: var(--card);
  transition: background var(--fast) var(--ease);
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-size: 13.5px;
}
.list__item:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 16px;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--line-2);
}
/* A row that leads with an icon insets further, to the text beside it. */
.list__item:has(.list__icon):not(:last-child)::after { left: 63px; }
.list__item:hover { background: var(--wash); text-decoration: none; }
.list__icon { width: 34px; height: 34px; border-radius: var(--r); background: var(--wash); }
.list__icon .icon { width: 19px; height: 19px; }
.list__icon--critical { background: var(--crit-wash); }
.list__icon--medium { background: var(--warn-wash); }
.list__icon--low { background: var(--good-wash); }
.list__body { flex: 1 1 auto; min-width: 0; }
.list__title { font-weight: 700; letter-spacing: -0.01em; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.list__sub { display: block; font-size: 13px; font-weight: 500; color: var(--ink-2); margin-top: 2px; overflow-wrap: anywhere; }
.list__sub--faint { color: var(--ink-3); font-size: 12.5px; }
.list__meta { flex: 0 0 auto; text-align: right; font-size: 12px; color: var(--ink-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* A total in a row of totals: same width whatever the digits, so the column
   reads down cleanly instead of ragging. */
.list__money { flex: 0 0 auto; min-width: 88px; text-align: right; font-weight: 600; font-size: 13px; font-variant-numeric: tabular-nums; }
/* A row that is only there to be read. */
.list__item--static { cursor: default; }
.list__item--static:hover { background: var(--card); }
/* The empty state belongs inside the card, so the page keeps its shape whether
   there are forty rows or none. */
.list__empty {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  padding: 22px 16px;
  font-size: 14px;
  color: var(--ink-2);
}
.list__chev { color: var(--ink-3); flex: 0 0 auto; }
.list__chev .icon { width: 17px; height: 17px; }

.card.list__item { border: 1px solid var(--line); border-radius: var(--r); margin-bottom: 10px; }
/* Standing on its own as a card, it has an edge already and wants no rule. */
.card.list__item::after { display: none; }

/* ==========================================================================
   Badges, chips, selects
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: -0.004em;
  background: var(--wash-2);
  color: var(--ink-2);
  white-space: nowrap;
}
.badge--danger { background: var(--crit-wash); color: var(--crit); }
.badge--warn { background: var(--warn-wash); color: var(--warn); }
.badge--ok { background: var(--good-wash); color: var(--good); }
.badge--primary { background: var(--brand-wash); color: var(--brand); }
.badge--info { background: var(--tint-info); color: var(--viz-1); }
.badge--new { background: var(--crit); color: #fff; }

.chips { display: flex; gap: 7px; overflow-x: auto; margin: 0 0 16px; padding-bottom: 2px; scrollbar-width: none; }
.chips::-webkit-scrollbar { display: none; }
/* A chip row that wraps rather than scrolling. For a SHORT, complete set
   somebody has to see all of — four measures, say — where a chip off the
   right-hand edge is a chip nobody knows exists. The scrolling default stays
   for the long, scannable rows it was built for. */
.chips--wrap { flex-wrap: wrap; overflow-x: visible; }
.chip {
  flex: 0 0 auto;
  min-height: 32px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.006em;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.chip:hover { background: var(--line); color: var(--ink); }
.chip:active { transform: scale(0.97); }
.chip.is-active { background: var(--ink); border-color: transparent; color: var(--card); font-weight: 600; }
.chip.is-active:hover { background: var(--ink); color: var(--card); }

.pill-select { position: relative; display: inline-flex; align-items: center; }
.pill-select select {
  appearance: none;
  -webkit-appearance: none;
  min-height: 36px;
  padding: 0 34px 0 14px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.008em;
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.pill-select select:hover { background: var(--line); }
.pill-select::after {
  content: "";
  position: absolute;
  right: 13px;
  width: 7px;
  height: 7px;
  border-right: 1.8px solid var(--ink-3);
  border-bottom: 1.8px solid var(--ink-3);
  transform: translateY(-2px) rotate(45deg);
  pointer-events: none;
}
.pill-select--danger select { background: var(--crit-wash); color: var(--crit); border-color: transparent; }
.pill-select--warn select { background: var(--warn-wash); color: var(--warn); border-color: transparent; }
.pill-select--ok select { background: var(--good-wash); color: var(--good); border-color: transparent; }
.pill-select--primary select { background: var(--brand-wash); color: var(--brand); border-color: transparent; }

/* ==========================================================================
   Forms
   ========================================================================== */

.field { margin-bottom: 18px; }
.field__label { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink-2); margin-bottom: 7px; }
.field__hint { font-size: 12.5px; color: var(--ink-3); margin-top: 7px; line-height: 1.45; }

/* A soft-cornered field with a light line, and a halo on focus rather than a
   heavier border — the field lights up instead of thickening. */
.input, .textarea, .select {
  width: 100%;
  min-height: 40px;
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  color: var(--ink);
  font-size: 15px;
  transition: border-color var(--fast) var(--ease);
  -webkit-appearance: none;
  appearance: none;
}
.input:hover, .textarea:hover, .select:hover { border-color: var(--ink-3); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--brand);
}
/* A field that sits in a toolbar or at the end of a row rather than in a form:
   shorter, tighter, and set at the size of the text around it instead of the
   16px a form field needs to stop a phone zooming when it is focused. Only for
   fields somebody adjusts, never for one they fill in. */
.input--sm { min-height: 32px; padding: 5px 10px; font-size: 13px; border-radius: var(--r-chip); }

.textarea { min-height: 104px; resize: vertical; line-height: 1.5; padding-top: 11px; }
.select {
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: calc(100% - 19px) calc(50% + 1px), calc(100% - 14px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 40px;
}
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.field-row--3 { grid-template-columns: repeat(3, 1fr); }

.switch-row {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: var(--tap-lg);
  padding: 12px 16px;
  background: var(--wash);
  border: 1px solid transparent;
  border-radius: var(--r);
}
.switch-row__body { flex: 1 1 auto; min-width: 0; }
.switch-row__title { display: block; font-weight: 600; font-size: 14px; }
.switch-row__sub { display: block; font-size: 12.5px; color: var(--ink-2); margin-top: 2px; }

/* A switch, not a checkbox in disguise: no border, a green track when on, and
   a white knob that carries a real shadow so it reads as sitting on top. */
.switch { position: relative; flex: 0 0 auto; width: 50px; height: 30px; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; z-index: 2; }
.switch__track {
  position: absolute;
  inset: 0;
  border-radius: var(--r-pill);
  background: var(--line);
  border: none;
  transition: background var(--med) var(--ease);
}
.switch__track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #fff;
  transition: transform var(--med) var(--ease);
}
.switch input:checked ~ .switch__track { background: var(--good); }
.switch input:checked ~ .switch__track::after { transform: translateX(20px); }
.switch input:focus-visible ~ .switch__track { outline: 3px solid color-mix(in srgb, var(--brand) 50%, transparent); outline-offset: 2px; }

/* ==========================================================================
   The report wizard
   ========================================================================== */

.wizard { max-width: 760px; margin: 0 auto; }
.wizard__bar { height: 4px; border-radius: var(--r-pill); background: var(--wash-2); overflow: hidden; margin-bottom: 10px; }
.wizard__bar-fill { height: 100%; border-radius: var(--r-pill); background: var(--brand); transition: width var(--slow) var(--ease); }
.wizard__step { font-size: 10.5px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 20px; }
.wizard__q { font-size: clamp(24px, 4.4vw, 34px); font-weight: 600; letter-spacing: -0.028em; margin: 0 0 8px; }
.wizard__help { color: var(--ink-2); font-size: 15px; margin: 0 0 22px; max-width: 52ch; }

.pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 10px; }
.pick {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  min-height: 124px;
  padding: 17px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: transform var(--fast) var(--ease);
}
.pick:hover { transform: translateY(-1px); }
.pick:active { transform: scale(0.98); }
.pick.is-selected {
  transform: none;
}
.pick__icon { width: 36px; height: 36px; border-radius: var(--r); background: var(--brand-wash); margin-bottom: 6px; }
.pick__label { display: block; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.pick__hint { display: block; font-size: 12.5px; color: var(--ink-2); line-height: 1.4; }
.pick__tag { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }
.pick--wide { grid-column: 1 / -1; flex-direction: row; align-items: center; min-height: 66px; gap: 14px; }
.pick--wide .pick__icon { margin-bottom: 0; }

.severity-grid { display: grid; gap: 10px; }
.severity {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 84px;
  padding: 17px 19px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  width: 100%;
  transition: transform var(--fast) var(--ease);
}
.severity:hover { }
.severity:active { transform: scale(0.99); }
.severity__icon { width: 44px; height: 44px; border-radius: var(--r); background: var(--wash); }
.severity__label { display: block; font-size: 17px; font-weight: 600; letter-spacing: -0.016em; }
.severity__hint { display: block; font-size: 13.5px; color: var(--ink-2); margin-top: 3px; }
.severity.is-selected[data-value="critical"] { border-color: transparent; }
.severity.is-selected[data-value="critical"] .severity__icon { background: var(--crit-wash); }
.severity.is-selected[data-value="medium"] { border-color: transparent; }
.severity.is-selected[data-value="medium"] .severity__icon { background: var(--warn-wash); }
.severity.is-selected[data-value="low"] { border-color: transparent; }
.severity.is-selected[data-value="low"] .severity__icon { background: var(--good-wash); }

.camera-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  width: 100%;
  min-height: 220px;
  padding: 24px;
  border: 2px dashed var(--line);
  border-radius: var(--r);
  -webkit-tap-highlight-color: transparent;
  background: var(--wash);
  color: var(--ink);
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.camera-btn:hover { border-color: var(--brand); background: var(--brand-wash); }
/* The same control inside a sheet, where 220px of empty box would push the
   rest of the form off a phone screen. */
.camera-btn--sm { min-height: 132px; padding: 18px; gap: 8px; }
.camera-btn--sm .camera-btn__label { font-size: 16px; }
.camera-btn__icon { width: 60px; height: 60px; border-radius: var(--r-pill); background: var(--card); }
.camera-btn__icon .icon { width: 28px; height: 28px; }
.camera-btn__label { display: block; font-size: 19px; font-weight: 600; letter-spacing: -0.018em; }
.camera-btn__hint { display: block; font-size: 13.5px; color: var(--ink-2); text-align: center; }
.camera-btn input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; font-size: 0; }

.thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(102px, 1fr)); gap: 8px; margin-top: 16px; }
.thumb { position: relative; aspect-ratio: 1; overflow: hidden; border-radius: var(--r); background: var(--wash-2); border: 1px solid var(--line); }
.thumb img { width: 100%; height: 100%; object-fit: cover; }
.thumb__remove {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.done { text-align: center; padding: 40px 20px 20px; max-width: 520px; margin: 0 auto; }
.done__check {
  width: 92px;
  height: 92px;
  margin: 0 auto 24px;
  border-radius: var(--r-pill);
  background: var(--good);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: pop var(--slow) var(--ease) both;
}
@keyframes pop {
  0% { transform: scale(0.5); opacity: 0; }
  60% { transform: scale(1.05); opacity: 1; }
  100% { transform: scale(1); }
}
.done__ref {
  display: inline-block;
  margin: 16px 0 10px;
  padding: 11px 24px;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  font-family: var(--font-mono);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* ==========================================================================
   Progress track + updates feed
   ========================================================================== */

.track { position: relative; padding-left: 38px; }
.track::before { content: ""; position: absolute; left: 13px; top: 12px; bottom: 14px; width: 2px; background: var(--line-2); }
.track__step { position: relative; padding: 0 0 24px; }
.track__step:last-child { padding-bottom: 0; }
.track__dot {
  position: absolute;
  left: -38px;
  top: 1px;
  width: 28px;
  height: 28px;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  color: var(--ink-3);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11.5px;
  font-weight: 700;
  z-index: 1;
}
.track__step.is-done .track__dot { background: var(--good); color: #fff; }
.track__step.is-current .track__dot { background: var(--brand); color: var(--on-brand); }
.track__step.is-current .track__dot::after {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: var(--r-pill);
  border: 2px solid var(--brand);
  animation: pulse 1.9s var(--ease) infinite;
}
@keyframes pulse {
  0% { transform: scale(0.85); opacity: 0.9; }
  70% { transform: scale(1.3); opacity: 0; }
  100% { transform: scale(1.3); opacity: 0; }
}
.track__label { font-size: 15px; font-weight: 600; }
.track__step:not(.is-done):not(.is-current) .track__label { color: var(--ink-3); font-weight: 500; }
.track__meta { font-size: 13px; color: var(--ink-2); margin-top: 2px; }

.feed { display: flex; flex-direction: column; }
.feed__item { position: relative; display: flex; gap: 12px; padding: 14px 0; }
.feed__item:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 44px;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--line-2);
}
.feed__avatar {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-2);
}
.feed__body { flex: 1 1 auto; min-width: 0; }
.feed__who { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.feed__when { font-size: 12px; color: var(--ink-3); font-weight: 400; }
.feed__msg { margin-top: 2px; overflow-wrap: anywhere; font-size: 14px; }
.feed__item.is-internal { background: var(--warn-wash); padding: 14px; margin: 4px 0; border-radius: var(--r); }
.feed__item.is-internal::after { display: none; }

/* A triage note is written as lines — a headline, then what to check, then what
   to take. Collapsing that into one paragraph loses the whole point of it. */
.feed__msg--lines { white-space: pre-line; }
.feed__item.is-triage { border-left: 3px solid var(--brand); border-radius: var(--r-chip); padding-left: 12px; }

/* ==========================================================================
   Photos + lightbox
   ========================================================================== */

.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); gap: 8px; }
.photo-grid button {
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--wash-2);
  overflow: hidden;
  aspect-ratio: 1;
  cursor: pointer;
  transition: transform var(--fast) var(--ease);
}
.photo-grid button:hover { transform: scale(1.02); }
.photo-grid img { width: 100%; height: 100%; object-fit: cover; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(8, 8, 10, 0.95);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: env(safe-area-inset-top) 16px env(safe-area-inset-bottom);
  animation: fade var(--med) var(--ease);
}
.lightbox img { max-width: 100%; max-height: 86dvh; object-fit: contain; border-radius: var(--r); }
.lightbox__close, .lightbox__nav {
  position: absolute;
  border: none;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  width: 46px;
  height: 46px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.lightbox__close { top: calc(16px + env(safe-area-inset-top)); right: 16px; }
.lightbox__nav--prev { left: 16px; top: 50%; transform: translateY(-50%); }
.lightbox__nav--next { right: 16px; top: 50%; transform: translateY(-50%); }
.lightbox__count { position: absolute; bottom: calc(20px + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%); color: rgba(255, 255, 255, 0.72); font-size: 12px; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

/* ==========================================================================
   Sheets
   ========================================================================== */

.scrim {
  position: fixed;
  inset: 0;
  z-index: 150;
  background: var(--scrim);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  animation: fade var(--med) var(--ease);
}
/* Rises from the bottom edge with the two top corners rounded hard, the way a
   sheet does — the shape says "this came from below and will go back there". */
.sheet {
  width: 100%;
  max-height: 92dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--sheet-bg);
  border-radius: var(--r) var(--r) 0 0;
  padding: 12px var(--pad) calc(var(--pad) + env(safe-area-inset-bottom));
  animation: sheet-up var(--slow) var(--ease);
}
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }

/* On a laptop a full-width drawer across the bottom is a phone pattern wearing
   the wrong clothes. With room to spare the same sheet becomes a centred panel
   that settles into place, all four corners rounded, and the drag grip — which
   nothing on a laptop can drag — goes away. */
@media (min-width: 900px) {
  .scrim { align-items: center; padding: 24px; }
  .sheet {
    max-width: 660px;
    max-height: 86dvh;
    border-radius: var(--r);
    padding: 22px var(--pad) var(--pad);
    animation: sheet-in var(--med) var(--ease);
  }
  .sheet__grip { display: none; }
}
@keyframes sheet-in {
  from { transform: translateY(10px) scale(0.99); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.sheet__grip { width: 38px; height: 5px; border-radius: var(--r-pill); background: var(--line); margin: 0 auto 14px; }
.sheet__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 20px; }
.sheet__title { font-size: 18px; font-weight: 800; letter-spacing: -0.02em; }
/* A filled grey circle, as a close button is on every Apple sheet — it needs
   no outline to be understood. */
.sheet__close {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  color: var(--ink-2);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.sheet__close:hover { background: var(--line); color: var(--ink); }
.sheet__foot { display: flex; gap: 10px; margin-top: 22px; }
.sheet__foot .btn { flex: 1 1 0; min-height: 44px; }

/* ==========================================================================
   Toasts + live alerts
   ========================================================================== */

#toasts {
  position: fixed;
  left: 50%;
  bottom: calc(24px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  width: min(420px, calc(100vw - 32px));
  pointer-events: none;
}
.toast {
  width: 100%;
  padding: 13px 18px;
  border-radius: var(--r);
  background: var(--ink);
  color: var(--card);
  font-size: 14px;
  font-weight: 500;
  animation: toast-in var(--med) var(--ease);
  pointer-events: auto;
}
.toast--error { background: var(--crit); color: #fff; }
.toast--ok { background: var(--good); color: #fff; }
.toast.is-out { animation: toast-out var(--med) var(--ease) forwards; }
@keyframes toast-in { from { transform: translateY(14px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes toast-out { to { transform: translateY(10px); opacity: 0; } }

#alert-root {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 250;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: calc(14px + env(safe-area-inset-top)) 12px 0;
  pointer-events: none;
}
.alert-toast {
  width: min(540px, 100%);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border-radius: var(--r);
  color: #fff;
  cursor: pointer;
  pointer-events: auto;
  text-decoration: none;
  animation: alert-in var(--slow) var(--ease);
  border: none;
  text-align: left;
  font: inherit;
}
.alert-toast--critical { background: var(--crit); }
.alert-toast--medium { background: var(--warn); }
.alert-toast--low { background: var(--ink); color: var(--card); }
.alert-toast__icon { width: 24px; height: 24px; color: currentColor; }
.alert-toast__body { flex: 1 1 auto; min-width: 0; }
.alert-toast__title { display: block; font-weight: 600; }
.alert-toast__sub { display: block; font-size: 13px; opacity: 0.9; margin-top: 1px; }
@keyframes alert-in { from { transform: translateY(-120%); opacity: 0; } to { transform: none; opacity: 1; } }
.alert-toast.is-out { animation: alert-out var(--med) var(--ease) forwards; }
@keyframes alert-out { to { transform: translateY(-120%); opacity: 0; } }

/* ==========================================================================
   Skeletons + empty states
   ========================================================================== */

.skel {
  border-radius: var(--r);
  background: linear-gradient(90deg, var(--wash) 25%, var(--wash-2) 37%, var(--wash) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
.skel--row { height: 66px; margin-bottom: 10px; }
.skel--tile { height: 130px; }
.skel--title { height: 34px; width: 45%; margin-bottom: 18px; border-radius: var(--r); }

.empty {
  text-align: center;
  padding: 52px 24px;
  color: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}
.empty__icon { width: 52px; height: 52px; border-radius: var(--r-pill); background: var(--wash); margin: 0 auto 16px; color: var(--ink-3); }
.empty__title { font-size: 16px; font-weight: 600; color: var(--ink); margin-bottom: 6px; }
.empty__text { font-size: 13.5px; max-width: 340px; margin: 0 auto; }
/* The way out of a dead end. Sits under the sentence explaining it, with
   enough space that it reads as the next thing to do rather than as part of
   the apology. */
.empty__action { display: inline-flex; margin-top: 16px; }

/* ==========================================================================
   Tables
   ========================================================================== */

.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}
table.data { width: 100%; min-width: 660px; border-collapse: collapse; background: var(--card); font-size: 13.5px; }
table.data th, table.data td { padding: 13px 16px; text-align: left; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
table.data th {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: -0.002em;
  color: var(--ink-3);
  white-space: nowrap;
  background: var(--wash);
}
table.data tr:last-child td { border-bottom: none; }
table.data tbody tr { transition: background var(--fast) var(--ease); }
table.data tbody tr:hover { background: var(--wash); }
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.data th.num { text-align: right; }

/* ==========================================================================
   Sign in

   The one screen that ignores the viewer's theme. It is the first thing a new
   operator sees and the only page that is pure brand, so it is always the
   near-black and orange from yorklift.ca — light mode included.
   ========================================================================== */

.auth {
  --auth-bg: #100d0b;
  --auth-card: rgba(255, 255, 255, 0.05);
  --auth-line: rgba(255, 255, 255, 0.1);
  --auth-field: rgba(255, 255, 255, 0.07);
  --auth-field-line: rgba(255, 255, 255, 0.14);
  --auth-text: #f7f4f2;
  --auth-text-2: #9e968f;
  --auth-text-3: #7a726d;

  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 26px;
  padding: 40px max(20px, env(safe-area-inset-left)) calc(40px + env(safe-area-inset-bottom));
  background: var(--auth-bg);
  color: var(--auth-text);
}

/* The lockup: mark and wordmark side by side, as on the website header. */
.auth__brand { display: flex; align-items: center; gap: 10px; }
.auth__logo { height: 26px; width: auto; flex: 0 0 auto; }
.auth__name {
  font-size: 30px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.024em;
  color: var(--auth-text);
}

.auth__card {
  width: min(560px, 100%);
  background: var(--auth-card);
  border: 1px solid var(--auth-line);
  border-radius: var(--r);
  padding: clamp(26px, 5vw, 44px);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}

.auth__eyebrow {
  display: block;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--brand);
  margin-bottom: 14px;
}
.auth__title {
  font-size: clamp(26px, 4.2vw, 32px);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.024em;
  color: var(--auth-text);
  margin: 0 0 14px;
}
.auth__lede { color: var(--auth-text-2); font-size: 16px; margin: 0 0 28px; max-width: 42ch; }
.auth__lede a { color: var(--brand); }

/* Phone-first sign-in: 16px is the floor below which iOS zooms the page on
   focus, and 56px is a thumb-sized primary action. Scoped to the auth card
   so the dense in-app forms keep their own scale. */
.auth__card .input { font-size: 16px; min-height: 52px; }
.auth__card .btn--block { min-height: 56px; font-size: 16px; }

/* The one-time "want a password?" card after a first link sign-in. */
.pw-offer {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 90;
  display: flex;
  justify-content: center;
  padding: 16px;
  pointer-events: none;
}
.pw-offer__card {
  pointer-events: auto;
  width: min(480px, 100%);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
  padding: 18px 20px;
}
.pw-offer__card p { color: var(--ink-2); font-size: 14px; margin: 6px 0 14px; }
.pw-offer__row { display: flex; gap: 10px; flex-wrap: wrap; }
.pw-offer__row .btn { flex: 1 1 auto; }

.auth .field { margin-bottom: 20px; }
.auth .field__label {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--auth-text-2);
  margin-bottom: 8px;
}
.auth .input {
  min-height: 50px;
  background: var(--auth-field);
  border-color: var(--auth-field-line);
  border-radius: var(--r);
  color: var(--auth-text);
  font-size: 16px;
}
.auth .input:hover { border-color: rgba(255, 255, 255, 0.22); }
.auth .input::placeholder { color: var(--auth-text-3); }
.auth .input:focus {
  border-color: var(--brand);
}

.auth .btn--primary {
  /* 56 is the floor: a thumb-sized primary action on a phone. */
  min-height: 56px;
  border-radius: var(--r-pill);
  background: #f4600a;
  color: #14100d;
  font-size: 16px;
  font-weight: 600;
}
.auth .btn--primary:hover { background: #ff7326; }

.auth__error {
  background: rgba(217, 45, 32, 0.16);
  border: 1px solid rgba(217, 45, 32, 0.3);
  border-radius: var(--r);
  color: #ff8079;
  padding: 13px 16px;
  font-size: 13.5px;
  font-weight: 500;
  margin-bottom: 20px;
}

.auth__help { margin: 20px 0 0; font-size: 13.5px; color: var(--auth-text-3); line-height: 1.6; }
.auth__help a { color: var(--auth-text-2); }

.auth__back { font-size: 14px; color: var(--auth-text-3); }
.auth__back:hover { color: var(--brand); text-decoration: none; }

/* ==========================================================================
   Maintenance due
   ========================================================================== */

.due { display: flex; align-items: center; gap: 10px; min-width: 128px; }

/* A service row on a phone.
   ---------------------------------------------------------------------------
   The desktop row is one flex line: icon, what the machine is, the wear bar,
   then Mark done and Book. At 390px those two buttons and a 128px bar leave the
   middle column about a letter wide, and "Durham Steel Works · Mill 2" comes out
   as a vertical column of characters. Nothing is dropped to fix it — the row
   simply wraps into three bands: what it is, how worn it is, what to do about
   it, each on its own line and each still tappable. */
@media (max-width: 720px) {
  .list__item--stack { flex-wrap: wrap; align-items: flex-start; row-gap: 11px; }
  .list__item--stack .list__body { flex: 1 1 0; min-width: 0; }
  .list__item--stack .list__meta { flex: 1 1 100%; order: 3; text-align: left; white-space: normal; }
  .list__item--stack .due { width: 100%; min-width: 0; }
  .list__item--stack .btn-row { flex: 1 1 100%; order: 4; flex-wrap: nowrap; gap: 8px; }
  .list__item--stack .btn-row .btn { flex: 1 1 0; min-width: 0; justify-content: center; }
}
.due__bar { flex: 1 1 auto; height: 6px; border-radius: var(--r-pill); background: var(--wash-2); overflow: hidden; min-width: 54px; }
.due__fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--good); transition: width var(--slow) var(--ease); }
.due__fill--soon { background: var(--warn); }
.due__fill--over { background: var(--crit); }
.due__num { font-size: 11.5px; font-weight: 600; color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ==========================================================================
   Invoice document
   ========================================================================== */

.doc { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 36px; max-width: 840px; margin: 0 auto; }
.doc__head { display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-bottom: 30px; padding-bottom: 22px; border-bottom: 1px solid var(--line-2); }
.doc__from-name { font-size: 24px; font-weight: 800; line-height: 1.1; letter-spacing: -0.022em; }
.doc__meta { font-size: 12.5px; color: var(--ink-2); line-height: 1.7; }
.doc__number { font-size: 13px; font-weight: 600; text-align: right; }
.doc__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 26px; }
.doc__h { font-size: 10.5px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 7px; }
.doc__work { background: var(--wash); border-radius: var(--r); padding: 16px 18px; margin-bottom: 26px; }
.doc table { width: 100%; border-collapse: collapse; font-size: 14px; }
.doc table th { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.07em; font-weight: 800; color: var(--ink-3); text-align: left; padding: 0 10px 10px; border-bottom: 1px solid var(--line); }
.doc table td { padding: 12px 10px; border-bottom: 1px solid var(--line-2); vertical-align: top; }
.doc table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.doc__line-sub { font-size: 12px; color: var(--ink-3); margin-top: 3px; }
.doc__totals { margin-left: auto; margin-top: 20px; width: min(320px, 100%); }
.doc__totals-row { display: flex; justify-content: space-between; gap: 20px; padding: 6px 10px; font-size: 14px; }
.doc__totals-row--grand { border-top: 1px solid var(--line); margin-top: 10px; padding-top: 14px; font-size: 20px; font-weight: 600; letter-spacing: -0.022em; }
.doc__foot { margin-top: 32px; padding-top: 18px; border-top: 1px solid var(--line-2); font-size: 12px; color: var(--ink-3); line-height: 1.7; }

/* ==========================================================================
   Utilities
   ========================================================================== */

.stack > * + * { margin-top: 12px; }
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.row--between { justify-content: space-between; }
.grow { flex: 1 1 auto; min-width: 0; }
.muted { color: var(--ink-2); }
.tiny { font-size: 12px; }
.small { font-size: 12.5px; color: var(--ink-3); }
.strong { font-weight: 700; }
.dim { color: var(--ink-3); font-weight: 500; }
.mono { font-family: var(--font-mono); font-size: 0.94em; }
.nowrap { white-space: nowrap; }
.center { text-align: center; }
.mt0 { margin-top: 0; }
.mt8 { margin-top: 8px; }
.mt16 { margin-top: 16px; }
.mt24 { margin-top: 24px; }
.mb0 { margin-bottom: 0; }
.mb8 { margin-bottom: 8px; }
.mb16 { margin-bottom: 16px; }
.mb24 { margin-bottom: 24px; }
.hide { display: none !important; }

.kv { display: grid; grid-template-columns: minmax(92px, auto) 1fr; gap: 9px 16px; font-size: 14px; }
.kv dt { font-size: 12px; font-weight: 500; color: var(--ink-3); padding-top: 1px; }
.kv dd { margin: 0; overflow-wrap: anywhere; }

.dot-sep::before { content: "·"; margin: 0 7px; color: var(--ink-3); }

/* Anything read as a figure — a count, an hour meter, a date, a total. Tabular
   digits so a column of them lines up instead of ragging by a pixel a row. */
.num { font-variant-numeric: tabular-nums; }

.cols { display: grid; gap: 14px; }
.grid-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(264px, 1fr)); gap: 12px; }

/* ---- the Customers grid --------------------------------------------------

   Three across at 1440, not four: the width is capped and the grid sits left,
   because four skinny cards floating in a full-width page is what made this
   screen look sparse. Each card is the SHARED .card — the container comes
   from its border, not from a shadow this design does not use — with a fixed
   three-row structure so a long company name can never push one card's chips
   below its neighbours': the name ellipsizes on one line beside a pill slot
   that never wraps, the address ellipsizes on the next, and the chips are
   pinned to the bottom of an equal-height card. */
.ccards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 12px;
  max-width: 1080px;
  align-items: stretch;
}
/* .card + .card stacks with a 16px gap when cards sit in a column; in this
   grid that margin would drop every card but the first 16px inside its cell
   and quietly misalign the whole row. The grid's gap is the spacing here. */
.ccards > .card { margin-top: 0; }

.ccard {
  position: relative;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.ccard:hover { border-color: var(--ink-3); transform: translateY(-1px); }
.ccard:focus-within { outline: 2px solid var(--brand); outline-offset: 2px; }

.ccard__top { display: flex; align-items: center; gap: 8px; }
.ccard__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}
.ccard__name:hover { text-decoration: none; }
/* The whole card is the link: the name stretches itself over it. */
.ccard__name::after { content: ""; position: absolute; inset: 0; }
.ccard__name:focus { outline: none; }

.ccard__pill { flex: none; white-space: nowrap; }

.ccard__addr {
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--ink-3);
}

.ccard__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: auto; padding-top: 16px; }
/* The chips are their own links, so they ride above the stretched name. */
.ccard__chips .badge { position: relative; z-index: 1; }
.ccard__chips .badge:hover { text-decoration: none; }

@media (min-width: 1000px) {
  .cols--2 { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: start; gap: 20px; }
}

@media (max-width: 430px) {
  .field-row, .field-row--3 { grid-template-columns: 1fr; }
  .pick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .btn:active, .pick:active, .chip:active, .severity:active,
  a.tile:hover, .sidebar__cta:active { transform: none; }

  /* The pulse in particular: for some people flashing content causes migraines
     or seizures, so it is off entirely — not shortened. The items stay clearly
     urgent without it; the red and the wording carry the message, never the
     animation. The blanket rule above already stops these, but they are named
     here so nobody trims that rule and quietly brings the pulse back. */
  .pulse-halo, .pulse-breathe { animation: none; }
}

/* ==========================================================================
   Front office — ledger lines, figure pairs and editable cells

   The back-office screens (inventory, payables, HST, payroll, the invoice
   editor) are number-dense in a way the service screens are not. These are
   the few pieces they need on top of the shared kit.
   ========================================================================== */

.grid-2 { display: grid; gap: 14px; }
@media (min-width: 880px) { .grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* A row of a running total: label left, money right, hairline between. */
.line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-2);
  font-size: 14px;
}
.line:last-child { border-bottom: none; }
.line__label { color: var(--ink-2); min-width: 0; }
.line__value { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 600; }
.line__value--warn { color: var(--warn); }
.line__value--danger { color: var(--crit); }
.line--total { border-top: 1px solid var(--line); border-bottom: none; margin-top: 8px; padding-top: 13px; font-size: 16px; }
.line--total .line__label { color: var(--ink); font-weight: 600; }

/* Two headline numbers side by side, used inside sheets where a tile grid
   would be too heavy. */
.figs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.fig { border: 1px solid transparent; border-radius: var(--r); padding: 14px 16px; background: var(--wash); }
.fig__label {
  display: block;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fig__value {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 5px;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.018em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

/* An input that lives inside a table cell: no chrome until you touch it. */
.input--cell {
  width: 84px;
  padding: 6px 8px;
  font-size: 13.5px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  background: transparent;
  /* A dotted underline is the only hint it needs: this is a number you can
     change, without turning the table into a wall of boxes. */
  border-color: transparent;
  border-bottom: 1px dashed var(--line);
}
.input--cell:hover { border-color: var(--line); background: var(--wash); }
.input--cell:focus { background: var(--card); border-radius: var(--r-chip); }
.input--wide { width: 100%; min-width: 160px; text-align: left; }

.input--search { max-width: 220px; }
@media (max-width: 560px) { .input--search { max-width: none; } }

/* The count on the stockroom list. Loud when it is at or below the line. */
.stock__qty {
  display: block;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.018em;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.stock__qty--warn { color: var(--warn); }
.stock__qty--danger { color: var(--crit); }

.text-ok { color: var(--good); }
/* Spend going UP is the bad direction on the compare table, which is why this
   is amber rather than green — the colour follows the meaning, not the sign. */
.text-warn { color: var(--warn); }

/* Compare locations: a plain table of real figures, and the numbers have to
   line up column to column or the comparison is work to read. */
.table--compare td.num,
.table--compare th.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.table--compare tbody tr:first-child td { font-weight: 600; }

/* Payment terms: four presets in a dropdown, with a day box for anything else. */
.terms { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.terms select { flex: 1 1 160px; min-width: 0; }
.terms__custom { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--ink-2); }
.terms__custom input { width: 88px; }
.terms__custom.is-hidden { display: none; }

/* ==========================================================================
   The diary — a calendar somebody plans a week of work in

   ONE IDEA HOLDS THIS TOGETHER: a calendar is a single continuous surface, not
   a row of floating boxes. So the grid here is a ruled table — white cells,
   hairline rules, no gaps and no rounded corners on a cell — and the only
   things with any colour on them are the events themselves and the circle
   around today's date. A month with three jobs in it should look like a month
   with three jobs in it, so the cells stay tall and mostly empty.

   Layout is a rail and a grid: filters, a small month and the unbooked count
   down the left, the calendar taking everything else. Below 1000px the rail
   stops being a column and sits above the calendar, because squeezing both
   into a phone gives you two things that are each too narrow to use.
   ========================================================================== */

.cal-page {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}
.cal-main { min-width: 0; }

/* ------------------------------------------------------------------ the rail */

.cal-rail { display: grid; gap: 20px; align-content: start; }
.cal-rail__group { display: grid; gap: 2px; }
.cal-rail__title {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 6px;
}
.cal-rail__note { font-size: 12px; color: var(--ink-3); margin: 0; line-height: 1.45; }
.cal-rail__clear {
  justify-self: start;
  margin-top: 6px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--brand);
  cursor: pointer;
}
.cal-rail__clear:hover { text-decoration: underline; }

/* A tick box, its colour, what it is, and how much of it there is this month. */
.railcheck {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 5px 6px;
  margin: 0 -6px;
  border-radius: var(--r-chip);
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
}
.railcheck:hover { background: var(--wash); }
.railcheck input { width: 15px; height: 15px; flex: 0 0 auto; }
.railcheck__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}
.railcheck__count {
  flex: 0 0 auto;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.railcheck input:not(:checked) ~ .railcheck__label { color: var(--ink-3); }

.railcheck__swatch { width: 9px; height: 9px; border-radius: var(--r-pill); flex: 0 0 auto; }
.railcheck__swatch--down { background: var(--ink); width: 10px; height: 10px; }
.railcheck__swatch--breakdown { background: var(--warn); }
.railcheck__swatch--visit { background: var(--good); }
.railcheck__swatch--service { background: var(--viz-1); }

.railcheck__avatar {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: var(--r-pill);
  background: var(--wash-2);
  color: var(--ink-2);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.03em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* The jump down to the jobs with no date on them. */
.railjump {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  font: inherit;
  text-align: left;
  background: var(--card);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  color: var(--ink);
  cursor: pointer;
}
.railjump:hover { border-color: var(--line); }
.railjump__label { font-size: 13px; font-weight: 600; }
.railjump__count {
  font-size: 13px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}
.railjump__count.is-waiting { color: var(--warn); }

/* ---- the small month picker ---- */

.mini { user-select: none; }
.mini__head { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-bottom: 8px; }
.mini__month { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; }
.mini__nav { display: flex; gap: 2px; }
.iconbtn--sm { width: 26px; height: 26px; border-radius: var(--r-chip); }
.iconbtn--sm .icon { width: 15px; height: 15px; }

.mini__names,
.mini__grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.mini__names {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  text-align: center;
  margin-bottom: 3px;
}
.mini__day {
  position: relative;
  aspect-ratio: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  border-radius: var(--r-pill);
  cursor: pointer;
}
.mini__day:hover { background: var(--wash-2); }
.mini__day--outside { color: var(--ink-3); }
/* A day with work on it carries a dot under the number, which is the one job a
   dot is genuinely good at: saying "something is here" in four pixels. */
.mini__day.has-work::after {
  content: '';
  position: absolute;
  bottom: 2px;
  left: 50%;
  transform: translateX(-50%);
  width: 3px;
  height: 3px;
  border-radius: var(--r-pill);
  background: var(--ink-3);
}
/* Today is circled; the day you are looking at is filled. Two different
   questions — "what is today" and "what am I looking at" — and usually the same
   answer, which is why they cannot both be a filled circle. */
.mini__day.is-today {
  color: var(--brand);
  font-weight: 800;
}
.mini__day.is-open {
  background: var(--ink);
  color: var(--card);
  font-weight: 700;
}
.mini__day.is-open.has-work::after { background: currentColor; }
.mini__day.is-open.is-today {
  background: var(--brand);
  color: var(--on-brand);
}

/* --------------------------------------------------------------- the toolbar */

.cal-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.cal-bar__nav { display: flex; align-items: center; gap: 2px; flex: 1 1 auto; justify-content: center; }
.cal-bar__title {
  min-width: 0;
  padding: 0 6px;
  font-size: 15.5px;
  font-weight: 800;
  letter-spacing: -0.015em;
  white-space: nowrap;
}
.cal-bar__tools { display: flex; align-items: center; gap: 10px; }

.cal-search { position: relative; display: flex; align-items: center; }
.cal-search__icon {
  position: absolute;
  left: 9px;
  display: inline-flex;
  color: var(--ink-3);
  pointer-events: none;
}
.cal-search__icon .icon { width: 15px; height: 15px; }
.cal-search .input { width: 168px; padding-left: 30px; }
.cal-search .input::-webkit-search-cancel-button { cursor: pointer; }

/* A segmented control: one pill, the live one lifted out of it. */
.seg {
  display: inline-flex;
  padding: 2px;
  gap: 2px;
  background: var(--wash-2);
  border-radius: var(--r);
}
.seg__btn {
  padding: 5px 11px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  border-radius: var(--r-chip);
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.seg__btn:hover { color: var(--ink); }
.seg__btn.is-active {
  background: var(--card);
  color: var(--ink);
}

/* ------------------------------------------------------------- MONTH: the grid

   A ruled table. Cells draw their own right and bottom rules and the wrapper
   draws the outside, so there is exactly one line everywhere and none doubled.
   ------------------------------------------------------------------------- */

.cal {
  background: var(--card);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  overflow: hidden;
}

.cal__names {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  border-bottom: 1px solid var(--line-2);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: center;
}
.cal__names span { padding: 9px 4px; }

.cal__grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }

/* A FIXED height, not a minimum, and this matters: it is what guarantees that a
   Tuesday with nine jobs on it is exactly as tall as a Saturday with none. A
   minimum lets the busiest cell in a row stretch that whole row, and the month
   stops being a grid you can read at a glance and becomes a set of ragged
   bands. Three events and a "+6 more" is what fits in this height, which is
   why that is what gets drawn. */
.cal__cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  height: 126px;
  padding: 6px 5px 7px;
  overflow: hidden;
  background: var(--card);
  border-right: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
}
.cal__cell:nth-child(7n) { border-right: 0; }
.cal__cell:nth-last-child(-n + 7) { border-bottom: 0; }

/* A cell somebody can press anywhere in to book that day. It sits under the
   number and the events, and is out of the tab order on purpose: 42 identical
   stops between one week and the next is not navigation. The day number beside
   it is a real button and is the keyboard way in. */
.cal__blank {
  position: absolute;
  inset: 0;
  width: 100%;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
}
.cal__cell:hover .cal__blank { background: color-mix(in srgb, var(--ink) 2.5%, transparent); }

.cal__num {
  position: relative;
  align-self: center;
  min-width: 26px;
  height: 26px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  border-radius: var(--r-pill);
  cursor: pointer;
}
.cal__num:hover { background: var(--wash-2); }
/* Outside the month: grey the number, and only the number. The cell stays white
   because a whole column of grey boxes reads as "broken", not "next month". */
.cal__cell--outside .cal__num { color: var(--ink-3); font-weight: 500; }

/* Today is a circle on the number. Not the cell — flooding a whole cell with
   orange makes the eye go there and nowhere else on the page. */
.cal__cell.is-today .cal__num {
  background: var(--brand);
  color: var(--on-brand);
  font-weight: 700;
}
.cal__cell.is-today .cal__num:hover { background: var(--brand-press); }

.cal__events { position: relative; display: grid; gap: 3px; align-content: start; overflow: hidden; }

/* ---------------------------------------------------------- one event, one line */

.calev {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  min-width: 0;
  padding: 1px 5px;
  border: 0;
  background: none;
  border-radius: var(--r-chip);
  font: inherit;
  font-size: 12px;
  line-height: 1.33;
  text-align: left;
  color: var(--ink);
  cursor: pointer;
}
.calev:hover { background: var(--wash-2); text-decoration: none; }
.calev__dot { width: 6px; height: 6px; border-radius: var(--r-pill); flex: 0 0 auto; background: var(--ink-3); }
.calev__time { flex: 0 0 auto; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink-2); }
.calev__text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.calev--down .calev__dot { background: var(--ink); width: 7px; height: 7px; }
.calev--breakdown .calev__dot { background: var(--warn); }
.calev--visit .calev__dot { background: var(--good); }
.calev--service .calev__dot { background: var(--viz-1); }

.calev--more {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ink-3);
  padding-left: 16px;
}
.calev--more:hover { color: var(--ink); }

/* On an hour grid an event is a block, drawn as tall as the job is long, so it
   carries its own tint rather than sitting on the cell's. */
.calev--block {
  position: absolute;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 2px 5px;
  padding: 4px 6px;
  overflow: hidden;
  border-radius: var(--r-chip);
  border-left: 2.5px solid var(--ink-3);
  background: var(--wash);
}
.calev--block:hover { background: var(--wash-2); }
.calev--block .calev__dot { display: none; }
.calev--block .calev__text { flex: 1 0 100%; white-space: normal; overflow: hidden; }

.calev--block.calev--down { border-left: 3px solid var(--ink); background: var(--down-chip); color: var(--ink); }
/* On the diary's day and unbooked lists the it's-down icon is this same
   out-of-service status — ink, like the chips, so the screen has one
   "worst thing here" colour instead of a red and a black arguing. Red on
   these lists elsewhere in the app still means severity and stays. */
.list--diary .list__icon--critical { color: var(--ink); background: var(--down-chip); }
.calev--block.calev--breakdown { border-left-color: var(--warn); background: var(--warn-wash); }
.calev--block.calev--visit { border-left-color: var(--good); background: var(--good-wash); }
.calev--block.calev--service { border-left-color: var(--viz-1); background: var(--tint-info); }

/* ------------------------------------------------------- WEEK and DAY: the hours

   The view a dispatcher plans in. An hour is a fixed height and a job is drawn
   as tall as it lasts, which is the only way to see that Tuesday morning is
   already full.
   ------------------------------------------------------------------------- */

.cal--week,
.cal--day { --calh: 52px; }

.calh__heads,
.calh__body { display: grid; grid-template-columns: 58px repeat(7, minmax(0, 1fr)); }
.calh--single .calh__heads,
.calh--single .calh__body { grid-template-columns: 58px minmax(0, 1fr); }

.calh__heads { border-bottom: 1px solid var(--line-2); }
.calh__gutter-head { border-right: 1px solid var(--line-2); }

.calh__dayhead {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 8px 4px;
  border: 0;
  border-right: 1px solid var(--line-2);
  background: none;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
}
.calh__dayhead:last-child { border-right: 0; }
.calh__dayhead:hover { background: var(--wash); }
.calh__dayname {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.calh__daynum {
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  min-width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
}
.calh__dayhead.is-today .calh__daynum { background: var(--brand); color: var(--on-brand); }
.calh__dayhead.is-today .calh__dayname { color: var(--brand); }

.calh__gutter { border-right: 1px solid var(--line-2); }
.calh__col { position: relative; border-right: 1px solid var(--line-2); }
.calh__col:last-child { border-right: 0; }
/* Today's column is faintly warm, which is what picks it out of the other six.
   In Day view there are no other six — the tint would just be the card looking
   orange for no reason, and the circle on the date already says which day it
   is. */
.calh__col.is-today { background: color-mix(in srgb, var(--brand) 3%, transparent); }
.cal--day .calh__col.is-today { background: none; }

/* Anything with no time on it has nowhere to sit on an hour grid, so it gets a
   band above the hours — the same height in every column so the week lines up. */
.calh__band {
  min-height: calc(var(--band) * 22px + 8px);
  padding: 4px;
  display: grid;
  gap: 2px;
  align-content: start;
  border-bottom: 1px solid var(--line-2);
}
.calh__band--gutter {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 6px 8px;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  text-align: right;
}

.calh__hours { position: relative; }
.calh__gutter .calh__hours { display: grid; }
.calh__hour {
  height: var(--calh);
  padding: 0 8px;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  text-align: right;
  /* The label belongs to the line at the top of its hour, so it is nudged up to
     sit against it rather than floating in the middle of the band. */
  transform: translateY(-6px);
}
.calh__hour em { font-style: normal; font-size: 9px; font-weight: 700; margin-left: 1px; }

/* One real button per hour, so pressing an empty ten o'clock books ten o'clock.
   Out of the tab order for the same reason the month's cells are: ninety-eight
   identical stops is not navigation, and the toolbar's Book something is. */
.calh__slot {
  display: block;
  width: 100%;
  height: var(--calh);
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--line-2);
  background: none;
  cursor: pointer;
}
.calh__slot:hover { background: color-mix(in srgb, var(--ink) 3%, transparent); }
.calh__slot:last-child { border-bottom: 0; }

.calh__events { position: absolute; inset: 0; pointer-events: none; }
.calh__events .calev { pointer-events: auto; }

/* An event with the hour to itself has room for the whole line. Two sharing an
   hour have room for the time. Three or more have room for a colour and nothing
   else, and a clipped "9:3" is worse than no time at all — so they show the
   colour, and the detail is in the tooltip and on the row they open. In Day view
   a column is the width of the page, so everything fits whatever the crowd. */
.cal--week .calev--block:not([data-share='1']) .calev__text { display: none; }
.cal--week .calev--block:not([data-share='1']):not([data-share='2']) .calev__time { display: none; }

/* DAY: the hours, and beside them the jobs nobody is going to yet. */
.calday { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 20px; align-items: start; }
.calday__side {
  padding: 14px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
.calday__loose { display: grid; gap: 6px; margin-bottom: 10px; }
/* In the side panel a block event is in normal flow, not on an hour grid. */
.calday__loose .calev--block { position: static; width: 100%; }

/* --------------------------------------------------------------- the legend */

.cal__key {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-top: 12px;
  font-size: 11.5px;
  color: var(--ink-2);
}
.cal__key span { display: flex; align-items: center; gap: 6px; }
.cal__found { margin: 8px 0 0; font-size: 12.5px; color: var(--ink-3); }

/* ---- narrow ------------------------------------------------------------- */

@media (max-width: 1080px) {
  .calday { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1000px) {
  /* The rail stops being a column and sits above the calendar. Its groups go
     side by side while there is room for them to. */
  .cal-page { grid-template-columns: minmax(0, 1fr); }
  .cal-rail {
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 18px 24px;
    padding-bottom: 4px;
  }
  .cal-rail__note { grid-column: 1 / -1; }
  .mini { max-width: 300px; }
  /* In Month view the grid below IS the month picker, and a second copy of it
     stacked on top only pushes the real one off the screen. Week and Day keep
     theirs — there, it is the only way to jump to another month. */
  .cal-page--month .mini { display: none; }
}

/* On a phone the rail is above the calendar, so every pixel it takes is a pixel
   of calendar pushed off the screen. So it becomes two rows of chips: no month
   picker at all (the toolbar's arrows and Today do that job, and in Month view
   the grid itself is the picker), and the filters laid out sideways rather than
   as a column eight rows deep. */
@media (max-width: 720px) {
  .mini { display: none; }
  .cal-rail { display: flex; flex-wrap: wrap; gap: 10px 12px; }
  .cal-rail__group { display: flex; flex-wrap: wrap; gap: 6px; width: 100%; }
  .cal-rail__title { width: 100%; margin-bottom: 0; }
  .railcheck {
    width: auto;
    margin: 0;
    padding: 5px 10px;
    gap: 7px;
    background: var(--card);
    border: 1px solid var(--line-2);
    border-radius: var(--r-pill);
  }
  .railcheck__label { font-size: 12.5px; }
  .railcheck__avatar { width: 18px; height: 18px; font-size: 8.5px; }
  .railjump { width: auto; padding: 7px 12px; border-radius: var(--r-pill); }
  .cal-rail__clear { margin-top: 0; align-self: center; }
}

@media (max-width: 720px) {
  /* Two rows, not three: Today beside the month, then the search and the view
     switch under them. */
  .cal-bar { gap: 8px; }
  .cal-bar__nav { justify-content: flex-start; }
  .cal-bar__title { font-size: 14.5px; padding: 0 2px; }
  .cal-bar__tools { flex: 1 1 100%; justify-content: space-between; }
  .cal-search { flex: 1 1 auto; }
  .cal-search .input { width: 100%; }

  .cal__cell { height: 106px; padding: 4px 3px 5px; gap: 3px; }
  .cal__num { min-width: 22px; height: 22px; font-size: 12px; }
  .cal__events { gap: 2px; }
  /* At this width a customer's name plus a machine cannot both be read, so the
     time goes and the name stays — a dot and a colour still say what kind of
     job it is. */
  .calev { padding: 1px 3px; font-size: 11px; gap: 4px; }
  /* Month only. In Week and Day the hour is the whole point of the view. */
  .cal--month .calev__time { display: none; }
  .calev--more { padding-left: 10px; font-size: 10.5px; }
  .calh__heads, .calh__body { grid-template-columns: 40px repeat(7, minmax(0, 1fr)); }
  .calh--single .calh__heads, .calh--single .calh__body { grid-template-columns: 40px minmax(0, 1fr); }
  .cal--week, .cal--day { --calh: 46px; }
  .calh__hour { font-size: 10px; padding: 0 4px; }
  .calh__daynum { font-size: 13px; min-width: 22px; height: 22px; }
  .calev--block { padding: 3px 4px; font-size: 10.5px; }
}

/* The week view cannot shrink below about 60px a column and stay readable, so
   on a phone it scrolls sideways inside its own card instead. The month view
   fits, and the day view has only one column to fit. */
@media (max-width: 560px) {
  .cal--week { overflow-x: auto; }
  .cal--week .calh { min-width: 620px; }
  .cal__names span { padding: 7px 2px; font-size: 9.5px; letter-spacing: 0.05em; }
}

/* ---- editing an invoice in place ----------------------------------------

   The document does not change shape when it becomes editable: the fields
   simply turn into inputs where they stand. Somebody correcting a line is
   looking at the bill the customer is holding while they correct it, and a
   layout that jumped would break that.
   ------------------------------------------------------------------------ */

.doc--editing { outline: 2px solid color-mix(in srgb, var(--brand) 35%, transparent); outline-offset: 6px; }
.editnote { align-self: center; font-size: 12.5px; color: var(--ink-3); }

/* A labelled field small enough to sit in the invoice header. */
.docfield { display: grid; gap: 2px; margin-bottom: 8px; }
.docfield--right { justify-items: end; text-align: right; }
.docfield--right .input { text-align: right; }
.docfield__label {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.docfield .input { max-width: 170px; }
.docfield__warn:not(:empty) {
  display: block;
  max-width: 220px;
  margin-top: 3px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--warn);
  text-align: right;
}

/* The customer's own PO, under the number, because that is the field their
   accounts department matches on and the usual reason a bill sits unpaid. */
.doc__po { font-weight: 700; color: var(--ink); }

/* Changed after it went out. A fact on the record, not a warning. */
.doc__edited { margin-top: 6px; font-size: 11.5px; color: var(--ink-3); }

/* A line while it is being edited. */
.linerow.is-dragging { opacity: 0.4; }
.linerow.is-over td { }
.linerow__grip { width: 22px; color: var(--ink-3); cursor: grab; }
.linerow__grip .icon { width: 14px; height: 14px; }
.linerow__amount { font-variant-numeric: tabular-nums; font-weight: 600; }

/* The × on a line: visible when the row is under the pointer or has focus in
   it, so a row of buttons does not compete with the numbers for attention. */
.linerow__x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--r-chip);
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--fast) var(--ease);
}
.linerow:hover .linerow__x,
.linerow:focus-within .linerow__x,
.linerow__x:focus-visible { opacity: 1; }
.linerow__x:hover { background: var(--crit-wash); color: var(--crit); }

table.is-editing td { vertical-align: top; }
table.is-editing .input--cell { margin-bottom: 3px; }

/* The fields that have nowhere else to live: discount, tax, PO, terms, notes. */
.doc__editrow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px 18px;
  margin: 22px 0 4px;
  padding: 18px 0 4px;
  border-top: 1px solid var(--line-2);
}

/* A discount is money coming off, so it reads like money coming off. */
.doc__totals-row--off span:last-child { color: var(--good); }

/* ---- the history panel ---- */

.revlog { margin-bottom: 16px; }
.rev { padding: 11px 0; border-top: 1px solid var(--line-2); }
.rev:first-of-type { border-top: 0; padding-top: 0; }
.rev__who { font-size: 12.5px; font-weight: 700; margin-bottom: 5px; }
.rev__list { margin: 0; padding-left: 18px; display: grid; gap: 3px; }
.rev__list li { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; }

/* Payments on an invoice: the proof photo first, then what it was. */
.doc__payments { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line-2); }
.payment {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 0;
}
.payment:not(:last-of-type)::after {
  content: "";
  position: absolute;
  left: 68px;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--line-2);
}
.payment__proof { display: flex; gap: 6px; flex: 0 0 auto; }
.payment__shot {
  width: 54px;
  height: 54px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--wash);
  cursor: pointer;
  overflow: hidden;
  flex: 0 0 auto;
  transition: transform var(--fast) var(--ease);
}
.payment__shot:hover { transform: scale(1.04); }
.payment__shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.payment__body { flex: 1 1 auto; min-width: 0; }
.payment__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

/* A pay line somebody has typed over: the figure is theirs, not ours. */
.input--cell.is-manual {
  border-bottom-style: solid;
  border-bottom-color: var(--warn);
  color: var(--warn);
  font-weight: 600;
}

/* ==========================================================================
   Print — an invoice you would actually put in an envelope
   ========================================================================== */


/* ============================================================== foresight

   The prediction screen. Built out of the same soft cards as everything else;
   the only new idea is that a forecast never appears without the line
   underneath saying what produced it, so .fore__basis is styled to read as a
   footnote rather than as body text — present, quiet, there when you look.
   ------------------------------------------------------------------------- */

.fore__headlines { display: grid; gap: 10px; margin-bottom: 20px; }

.fore__headline {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 15px 17px;
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--r);
  color: var(--ink);
  font-weight: 600;
  font-size: 14px;
}
.fore__headline:hover { background: var(--wash); }
.fore__headline svg { flex: 0 0 auto; width: 20px; height: 20px; color: var(--ink-2); }
.fore__headline--high { border-left-color: var(--crit); }
.fore__headline--high svg { color: var(--crit); }
.fore__headline--medium { border-left-color: var(--warn); }
.fore__headline--medium svg { color: var(--warn); }
.fore__headline--low { border-left-color: var(--brand); }

.fore__quiet {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 20px;
  background: var(--good-wash);
  border: 1px solid transparent;
  border-radius: var(--r);
  color: var(--ink);
  font-weight: 600;
}
.fore__quiet svg { width: 22px; height: 22px; color: var(--good); flex: 0 0 auto; }

/* ---- the machine cards ---- */

.fore__grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }

/* The urgency colour is a bar across the top of the card. With rounded corners
   a plain top border would be clipped into two odd stubs, so it is drawn as a
   band inside the card instead and the corner survives. */
.fore__item {
  position: relative;
  padding: 19px 17px 17px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  overflow: hidden;
}
.fore__item::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--line);
}
.fore__item--high::before { background: var(--crit); }
.fore__item--watch::before { background: var(--warn); }
.fore__item--low::before { background: var(--line); }

.fore__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.fore__title { font-size: 16px; font-weight: 800; letter-spacing: -0.016em; color: var(--ink); }
.fore__title:hover { color: var(--brand); }
.fore__sub { font-size: 12px; color: var(--ink-2); margin: 2px 0 12px; }

.fore__scoreFill { position: absolute; inset: 0 auto 0 0; background: var(--line); }
.fore__score--high .fore__scoreFill { background: var(--crit); }
.fore__score--watch .fore__scoreFill { background: var(--warn); }
/* The figure sits beside the bar rather than on top of it. Printed over the
   fill it needed a blend mode to stay legible against three different
   colours, and a blend mode is exactly the thing that breaks on one browser
   and leaves a number nobody can read. */
.fore__score { display: flex; align-items: center; gap: 8px; width: auto; background: none; }
.fore__score b { font-size: 12px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.fore__scoreTrack { position: relative; display: block; width: 64px; height: 8px; border-radius: var(--r-pill); background: var(--wash-2); overflow: hidden; flex: 0 0 auto; }

.fore__reasons { list-style: none; margin: 0 0 12px; padding: 0; display: grid; gap: 5px; }
.fore__reasons li { position: relative; padding-left: 14px; font-size: 13px; color: var(--ink-2); line-height: 1.45; }
.fore__reasons li::before { content: ""; position: absolute; left: 0; top: 8px; width: 5px; height: 5px; border-radius: var(--r-pill); background: var(--line); }

.fore__forecast { font-size: 13px; line-height: 1.5; color: var(--ink); margin: 0 0 2px; }
.fore__forecast--none { color: var(--ink-2); }
.fore__forecast b { font-weight: 700; }

/* The provenance line. Quiet, but never absent. */
.fore__basis { font-size: 11.5px; line-height: 1.45; color: var(--ink-3); margin: 0 0 12px; }

.fore__do {
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
  padding-top: 12px;
  border-top: 1px solid var(--line-2);
}
.fore__do b { font-weight: 700; }

.fore__note { font-size: 12px; line-height: 1.55; color: var(--ink-3); margin: 12px 0 0; max-width: 70ch; }

/* The money line on a watch card: the sentence a replacement is decided on. */
.fore__cost {
  margin: 8px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--line-2);
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.fore__cost .badge { margin-left: 4px; }

/* Where the rest of the old page went. One quiet row of signposts. */
.fore__links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 10px;
  margin-top: 28px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  font-weight: 600;
}
.fore__links span { color: var(--ink-3); }

/* ---- the question box ---- */

.fore__ask { border-left: 3px solid var(--brand); }
.fore__ask, .fore__item { overflow: hidden; }
.fore__ask--off { border-left-color: var(--line); }
.fore__ask textarea { resize: vertical; min-height: 52px; }

/* The suggested questions are whole sentences, not filter words. The shared
   .chips row scrolls sideways with its scrollbar hidden, which on a card just
   clips the last question out of sight with nothing to say it is there — so
   here they wrap onto as many lines as they need, and a chip too long for a
   phone wraps inside itself rather than pushing off the edge. */
.fore__ask .chips { flex-wrap: wrap; overflow-x: visible; }
.fore__ask .chip {
  max-width: 100%;
  padding: 7px 13px;
  text-align: left;
  white-space: normal;
}

.fore__answer:empty { display: none; }
.fore__answer { margin-top: 16px; }

.fore__thinking { font-size: 13px; color: var(--ink-2); margin: 0; }

.fore__answerText {
  padding: 15px 17px;
  background: var(--wash);
  border: 1px solid transparent;
  border-radius: var(--r);
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
}
.fore__answerText p { margin: 0 0 10px; }
.fore__answerText p:last-child { margin-bottom: 0; }
.fore__answerText--muted { color: var(--ink-2); }

/* An answer arrives a few words at a time. This line says so, and goes away
   when the answer is finished — a paragraph that has stopped growing with no
   sign of whether it is complete is worse than a spinner. */
.fore__streaming {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--ink-3);
  font-style: italic;
}

/* What it looked up, folded away. Open by choice, never in the way, and there
   so that "where did that number come from" has an answer. */
.fore__trail {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--ink-2);
}
.fore__trail > summary {
  cursor: pointer;
  padding: 4px 0;
  color: var(--ink-2);
}
.fore__trail > summary:hover { color: var(--ink); }
.fore__trail ul { margin: 6px 0 0; padding-left: 18px; }
.fore__trail li { margin-bottom: 4px; line-height: 1.5; }
.fore__trail .fore__answerText { margin-top: 8px; }

.fore__cost { margin: 8px 0 0; font-size: 11.5px; color: var(--ink-3); }

/* A part the van needs and the shelf has not got. */
.fore__shortLine { color: var(--crit); font-weight: 500; }

/* ---- Likely to invoice ----------------------------------------------------

   Four facts a line, which a laptop can rule into columns and a phone cannot.
   The same markup does both: a four-column grid with a header above 560px,
   and two lines a row below it — the bucket and its value on the first, how
   many and how sure on the second, so a number is never further than a
   thumb's width from the words it belongs to.

   No horizontal scroll. A summary somebody reads once a morning should not
   ask to be dragged sideways to be finished. */

.likely {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  overflow: hidden;
}

.likely__head,
.likely__row {
  display: grid;
  grid-template-columns: minmax(120px, 1.1fr) 52px 104px minmax(150px, 1.5fr);
  gap: 4px 14px;
  align-items: baseline;
  padding: 11px 14px;
  font-size: 13.5px;
}

.likely__head {
  background: var(--wash);
  border-bottom: 1px solid var(--line-2);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: -0.002em;
  color: var(--ink-3);
  white-space: nowrap;
}

.likely__row + .likely__row { border-top: 1px solid var(--line-2); }

.likely__jobs,
.likely__value {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.likely__sure { font-size: 12.5px; color: var(--ink-3); }

@media (max-width: 560px) {
  /* The header goes because the layout stops needing one: on two lines the
     value sits beside its bucket and the count says "jobs" in words. A row
     of column names over a stack is a row of column names pointing nowhere. */
  .likely__head { display: none; }

  .likely__row {
    grid-template-columns: auto 1fr auto;
    gap: 3px 8px;
    padding: 12px 14px;
  }
  .likely__what { grid-column: 1 / 3; grid-row: 1; font-weight: 600; }
  .likely__value { grid-column: 3; grid-row: 1; }
  .likely__jobs { grid-column: 1; grid-row: 2; text-align: left; color: var(--ink-3); font-size: 12.5px; }
  .likely__jobs::after { content: " jobs"; }
  .likely__sure { grid-column: 2 / 4; grid-row: 2; }
  .likely__sure::before { content: "· "; }
}

/* ---- the shelf table ---- */

/* Seven columns will not fit a phone, and the wrapper already scrolls
   horizontally — but only if the table refuses to squeeze. Without a floor it
   compresses every part name into a one-word-per-line column instead. */
.fore__shelf { min-width: 720px; }

.fore__grid + .fore__note { margin-bottom: 4px; }
.table tr.is-warn td { background: var(--warn-wash); }
.table tr.is-bad td { background: var(--crit-wash); }

@media (max-width: 560px) {
  .fore__grid { grid-template-columns: 1fr; }
  .fore__scoreTrack { width: 44px; }
}


/* ==========================================================================
   The customer fleet registry
   ==========================================================================
   Tabs over one company, the machine list, and the four-step import. Nothing
   new invented: the cards, chips, badges and inputs are the app's own, and the
   only genuinely new things here are a monospace serial and the four colours
   the import buckets need. Both light and dark come from the palette tokens.
   -------------------------------------------------------------------------- */

/* A serial number, for reading down a column against a plate on a machine.
   Tabular figures so every digit is the same width — proportional digits make
   two similar serials genuinely hard to tell apart. */
.serial {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.92em;
  letter-spacing: .01em;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
input.serial { color: var(--ink); letter-spacing: .02em; }

/* ---- tabs across a company ---- */

.tabs {
  display: flex;
  gap: 0;
  margin: 0 0 22px;
  border-bottom: 1px solid var(--line-2);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs__tab {
  flex: 0 0 auto;
  padding: 11px 4px;
  margin-right: 24px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.008em;
  color: var(--ink-2);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
  transition: color var(--fast) var(--ease);
}
/* No hover fill. A tab is a word with a rule under it; painting a grey box
   behind it on hover puts a box back into a design that just lost them. */
.tabs__tab:hover { color: var(--ink); text-decoration: none; }
.tabs__tab.is-active { color: var(--ink); border-bottom-color: var(--brand); font-weight: 600; }
.tabs__count {
  display: inline-block;
  min-width: 19px;
  padding: 1px 6px;
  margin-left: 6px;
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  background: var(--wash-2);
  color: var(--ink-2);
  border-radius: var(--r-pill);
}
.tabs__tab.is-active .tabs__count { background: var(--brand-wash); color: var(--brand); }

/* A chip on a customer card that is really a link. */
.badge--link { cursor: pointer; }
.badge--link:hover { background: var(--brand-wash); color: var(--brand); }

/* ==========================================================================
   The company page shell
   ==========================================================================
   Breadcrumb, title row, address line, tab row, content. The first four are
   identical on every tab and must not move a pixel when the tab changes — so
   each of them has a fixed height rather than one that depends on what is in
   it, and only the content region below them is ever repainted.
   -------------------------------------------------------------------------- */

.page { max-width: 1240px; }

.page__crumb {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--ink-3);
}
.page__crumb:hover { color: var(--ink); text-decoration: none; }

.page__titleRow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 38px;
}
.page__titleRow .page-title { margin: 0; }

.page__address {
  margin: 9px 0 0;
  font-size: 13.5px;
  color: var(--ink-3);
}

/* ---- the row of actions above a list ---- */

.page__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px 12px;
  flex-wrap: wrap;
  min-height: 36px;
  margin: 0 0 14px;
}
.page__toolbarLeft,
.page__toolbarRight { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.page__toolbarRight { justify-content: flex-end; }

/* Selecting rows swaps this in for the toolbar rather than adding a bar above
   it, so the list underneath does not slide out from under the pointer. */
.page__toolbar--bulk {
  padding: 0 14px;
  background: var(--brand-wash);
  border-left: 3px solid var(--brand);
  border-radius: var(--r);
}
.page__bulkCount { font-size: 13px; color: var(--ink-2); white-space: nowrap; }
.page__bulkCount b { font-weight: 600; color: var(--ink); }

/* ---- the row of filters below it ---- */

.page__filters {
  display: grid;
  gap: 8px;
  align-items: center;
  margin: 0 0 14px;
}
.page__filters--1 { grid-template-columns: minmax(180px, 460px); }
.page__filters--2 { grid-template-columns: minmax(180px, 1fr) 200px; max-width: 680px; }
.page__filters--3 { grid-template-columns: minmax(180px, 1fr) 200px 190px; max-width: 890px; }
.page__filters--4 { grid-template-columns: minmax(180px, 1fr) 210px 190px auto; }

.page__filterToggle {
  display: inline-flex;
  align-items: center;
  justify-self: end;
  gap: 7px;
  font-size: 13px;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
}

/* Below 900px the two rows go to two lines and the tabs scroll sideways. The
   order never changes — the same things in the same sequence, stacked. */
@media (max-width: 900px) {
  .page__toolbar { flex-direction: column; align-items: stretch; }
  .page__toolbarRight { justify-content: flex-start; }
  .page__filters--2,
  .page__filters--3,
  .page__filters--4 { grid-template-columns: 1fr 1fr; max-width: none; }
  .page__filters--2 > :first-child,
  .page__filters--3 > :first-child,
  .page__filters--4 > :first-child { grid-column: 1 / -1; }
  .page__filterToggle { grid-column: 1 / -1; justify-self: start; }
}

/* ---- the machine list ---- */

.fleet__row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 16px 4px 6px;
  cursor: pointer;
  transition: background var(--fast) var(--ease);
}
.fleet__row:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 44px;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--line-2);
}
.fleet__row:hover { background: var(--wash); }
.fleet__row.is-archived { opacity: .62; }
.fleet__tick { display: flex; align-items: center; justify-content: center; width: 34px; flex: 0 0 34px; cursor: pointer; }
.fleet__main {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: 3px;
  padding: 10px 4px;
  text-align: left;
}
.fleet__head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.fleet__name { font-weight: 600; font-size: 14.5px; }
.fleet__spec { font-size: 13px; color: var(--ink-2); }
.fleet__meta { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--ink-3); }
.fleet__meta span { display: inline-flex; align-items: center; gap: 4px; }
.fleet__status { flex: 0 0 auto; }

.fleet__empty { text-align: center; padding: 40px 24px; width: 100%; }
.fleet__emptyIcon { display: block; color: var(--ink-3); margin-bottom: 12px; }
.fleet__empty h3 { margin: 0 0 8px; }
.fleet__empty p { max-width: 46ch; margin: 0 auto 20px; }

.fleet__audit {
  margin: 20px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line-2);
  font-size: 12px;
  color: var(--ink-3);
}

/* ---- what the serial turned out to say ----

   Sits under the serial field and fills in as somebody types. Quiet: this is a
   convenience, not a result, and it must not look like the form has done
   something clever that needs checking. The reason line is the important half —
   a field nobody typed and nobody can explain is a field nobody trusts. */

.decoded:empty { display: none; }
.decoded { margin-top: 9px; }
.decoded__none {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  margin: 0;
  font-size: 12.5px;
  color: var(--ink-2);
}
.decoded__none .icon { color: var(--ink-3); flex: 0 0 auto; margin-top: 2px; }

/* The dead end: nothing read the code, so the panel says what still works
   rather than shrugging. Two routes out and, for staff, a third — teach it. */
.decoded__dead {
  padding: 10px 12px;
  border: 1px dashed var(--line);
  border-radius: var(--r);
  background: var(--wash);
}
.decoded__next {
  margin: 6px 0 0;
  padding-left: 18px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-2);
}
.decoded__next li + li { margin-top: 3px; }
.decoded__dead .btn { margin-top: 9px; }

/* Teaching it one, in place. It cannot be a sheet: the add-a-forklift form is
   already one, and opening a second closes the first with everything in it. */
.decoded__teach {
  margin-top: 8px;
  padding: 12px 13px 13px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--r);
  background: var(--wash);
}
.decoded__teach .field { margin-top: 9px; }
.decoded__teachfoot { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.decoded__teachfoot .btn { margin: 0; }

/* The serial input with the seat kept warm for "photograph the plate". */
.serialrow { display: flex; gap: 8px; align-items: stretch; }
.serialrow .input { flex: 1 1 auto; min-width: 0; }
.serialrow__plate { flex: 0 0 auto; }
.serialrow__plate:disabled { opacity: 0.45; cursor: not-allowed; }

/* The suggestion panel. Rows are buttons — clicking one puts that one value in
   its field; nothing lands without a click. */
.decoded__panel {
  margin-top: 8px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 10px 12px 11px;
  background: var(--wash);
}
.decoded__head {
  margin: 0 0 6px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.decoded__row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 3px 4px;
  margin: 0;
  border: 0;
  border-radius: var(--r-chip);
  background: none;
  font: inherit;
  font-size: 12.5px;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
}
button.decoded__row:hover { background: var(--wash-2); }
.decoded__row--year { cursor: default; color: var(--ink-3); }
.decoded__row--guess { color: var(--ink-3); }
.decoded__row--guess .decoded__val { color: var(--ink-3); }
.decoded__bullet { flex: 0 0 auto; font-size: 9px; color: var(--brand); }
.decoded__bullet.is-placed { color: var(--good); }
.decoded__bullet--none { color: var(--ink-3); }
.decoded__key { flex: 0 0 74px; font-weight: 700; }
.decoded__val { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.decoded__val--none { font-weight: 500; }
.decoded__sure {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}
.decoded__sure--certain { color: var(--good); }
.decoded__sure--likely { color: var(--warn); }
.decoded__why { margin: 7px 0 9px; font-size: 11.5px; line-height: 1.45; color: var(--ink-3); }

/* A match against a machine already on file: a person confirmed that spec, so
   it outranks the rules and sits above them. */
.decoded__fleet {
  margin-top: 8px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--r);
  padding: 9px 12px 10px;
  background: var(--brand-wash);
}
.decoded__fleetlabel {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand);
}
.decoded__fleetsay { margin: 4px 0 8px; font-size: 12.5px; line-height: 1.5; color: var(--ink); }
.decoded__fleet .btn { margin: 0; }
.decoded__fleetsay + .btn { margin-top: 0; }
.decoded__fleet p:last-child { margin-bottom: 0; }

/* ------------------------------------------------ the photographed plate

   The serial as printed, every character on show. A doubtful character (8/B,
   0/O) is a button that swaps between its readings; the serial reaches the
   field only through "Use this serial". */
.plateserial {
  margin: 2px 0 10px;
  padding: 8px 10px 9px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}
.plateserial__top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.plateserial__top .decoded__key { flex: 0 0 auto; font-size: 12.5px; }
.plateserial__chars { display: inline-flex; align-items: flex-start; gap: 1px; flex-wrap: wrap; }
.platechar {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  min-width: 15px;
  padding: 1px 2px;
  border: 0;
  border-radius: 3px;
  background: none;
  font: inherit;
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.2;
}
button.platechar--doubt {
  cursor: pointer;
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  outline: 1px dashed var(--warn);
  outline-offset: 0;
}
button.platechar--doubt:hover { background: color-mix(in srgb, var(--warn) 24%, transparent); }
.platechar__alt { font-size: 8.5px; font-weight: 700; color: var(--warn); line-height: 1; }
.plateserial__hint { margin: 0 0 7px; font-size: 11.5px; line-height: 1.45; color: var(--ink-3); }
.plateserial .btn { margin: 0; }

/* A field where the plate and the model code disagree: both readings shown,
   a person picks, nothing picked for them. */
.decoded__conflict {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  padding: 5px 4px;
  font-size: 12.5px;
  border-top: 1px dashed var(--line);
}
.decoded__conflict .decoded__key { color: var(--bad); }
.decoded__conflictsay { flex: 1 1 100%; font-size: 11.5px; color: var(--ink-3); order: 3; margin: 0; }
.decoded__conflictpick { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.decoded__conflictpick .btn { margin: 0; }

/* The plate on the machine detail: the photo beside the scan button, and the
   read-against-the-record rows underneath. */
.plate__row { display: flex; gap: 14px; align-items: flex-start; }
.plate__thumb {
  display: block;
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: var(--r);
  border: 1px solid var(--line);
}
.plate__cmp { cursor: default; }
.plate__cmp--ok .decoded__bullet { color: var(--good); }
.plate__cmp--differ .decoded__bullet { color: var(--bad); }
.plate__cmp--blank .decoded__bullet, .plate__cmp--info .decoded__bullet { color: var(--ink-3); }
.plate__cmp--differ .decoded__val { color: var(--bad); }

/* -------------------------------------------------- the parts fitment index

   Ranked suggestions from our own fitting history. The proof line under each
   name — "fitted 5 times · fixed it 4 of 5" — is the feature; the row is a
   button wherever clicking it fills a form. */
.fitm { margin-bottom: 4px; }
.fitm__tier { margin-bottom: 10px; }
.fitm__tier:last-child { margin-bottom: 0; }
.fitm__head {
  margin: 0 0 4px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fitm__row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  margin: 0 0 2px;
  border: 0;
  border-radius: var(--r-chip);
  background: var(--wash);
  font: inherit;
  text-align: left;
  color: var(--ink);
}
button.fitm__row--pick { cursor: pointer; }
button.fitm__row--pick:hover { background: var(--wash-2); }
.fitm__row.is-picked { outline: 2px solid var(--brand); outline-offset: -2px; }
.fitm__dot { flex: 0 0 auto; font-size: 9px; line-height: 1.9; color: var(--ink-3); }
.fitm__dot--have { color: var(--good); }
.fitm__main { flex: 1 1 auto; min-width: 0; }
.fitm__top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.fitm__name { font-size: 13px; font-weight: 700; }
.fitm__id { font-size: 11.5px; color: var(--ink-3); }
.fitm__proof { display: block; margin-top: 1px; font-size: 11.5px; line-height: 1.45; color: var(--ink-2); }
.fitm--quiet .fitm__row { background: var(--wash); cursor: default; }

/* Folded shut on the machine page until asked. */
.fitm--fold { border: 1px solid var(--line); border-radius: var(--r); padding: 0 12px; background: var(--card); }
.fitm--fold[open] { padding-bottom: 10px; }
.fitm__summary {
  padding: 10px 0;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  list-style-position: inside;
}
.fitm__summary::marker { color: var(--ink-3); }

/* ------------------------------------------------------------ core returns

   The deposit ledger on the Parts page, and the one-line core notes that ride
   on part rows and the add-a-part sheet. */
.corechip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 3px;
  color: var(--ink-2);
}
.corechip svg { flex: 0 0 auto; }
.corechip--do { color: var(--warn); font-weight: 700; }
.corechip--ask {
  display: block;
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px dashed var(--line);
  border-radius: var(--r);
  background: var(--warn-wash);
  color: var(--ink);
}
p.corechip { margin: 0 0 12px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--r); background: var(--warn-wash); }

.cores__tiles { margin-bottom: 20px; }

.coregrp { border: 1px solid var(--line); border-radius: var(--r); margin-bottom: 10px; overflow: hidden; }
.coregrp:last-child { margin-bottom: 0; }
.coregrp__head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 12px;
  background: var(--wash);
  border-bottom: 1px solid var(--line);
}
.coregrp__name { font-weight: 800; font-size: 13.5px; }
.coregrp__meta { font-size: 12px; color: var(--ink-3); }
.coregrp__btns { margin-left: auto; display: inline-flex; gap: 6px; flex-wrap: wrap; }
.coregrp__btns .btn { margin: 0; }

.corerow {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.corerow:last-child { border-bottom: 0; }
.corerow__main { flex: 1 1 240px; min-width: 0; }
.corerow__val { font-weight: 700; white-space: nowrap; }
.corerow .btn { margin: 0; }
.corerow--settled { color: var(--ink-2); }
.cores__settled { margin-top: 16px; }
.cores__settled .corerow { border-bottom: 0; padding-left: 0; padding-right: 0; }

@media (max-width: 640px) {
  .corerow { align-items: flex-start; }
  .coregrp__btns { margin-left: 0; width: 100%; }
}

/* The orange dot: a field the panel filled, not yet touched by a person.
   Typing in the field takes it off. Nothing decoded sits in the form unmarked. */
.label--decoded::after {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 6px;
  border-radius: 50%;
  background: var(--brand);
  vertical-align: 1px;
}

/* ---- the import wizard ---- */

.sheet--wide { max-width: 1080px; }

.wiz__rail {
  display: flex;
  gap: 6px;
  margin: 0 0 20px;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
}
.wiz__rail::-webkit-scrollbar { display: none; }
.wiz__step {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  padding: 6px 14px 6px 7px;
  font-size: 13px;
  color: var(--ink-3);
  background: var(--wash);
  border-radius: var(--r-pill);
}
.wiz__step.is-now { color: var(--ink); background: var(--brand-wash); font-weight: 600; }
.wiz__step.is-done { color: var(--ink-2); }
.wiz__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
  font-size: 11.5px;
  font-weight: 600;
  border-radius: var(--r-pill);
  background: var(--card);
  border: 1px solid var(--line);
  color: inherit;
}
.wiz__step.is-now .wiz__num { background: var(--brand); border-color: var(--brand); color: #fff; }
.wiz__step.is-done .wiz__num { background: var(--good-wash); border-color: var(--good-wash); color: var(--good); }

.drop {
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 40px 22px;
  border: 2px dashed var(--line);
  border-radius: var(--r);
  background: var(--wash);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.drop:hover, .drop.is-over { border-color: var(--brand); background: var(--brand-wash); }
.drop__icon { color: var(--ink-3); }
.drop.is-over .drop__icon { color: var(--brand); }

/* ---- step two: the column mapping ---- */

.map { display: grid; gap: 6px; }
.map__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 10px 12px;
  background: var(--wash);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-chip);
}
.map__row.is-ignored { border-left-color: var(--line); opacity: .72; }
.map__from { min-width: 0; display: grid; gap: 2px; }
.map__from b { font-size: 13.5px; overflow-wrap: anywhere; }
.map__samples { font-size: 12px; color: var(--ink-3); overflow-wrap: anywhere; }
.map__arrow { color: var(--ink-3); display: flex; }
.map__to { font-size: 13px; }
@media (max-width: 700px) {
  .map__row { grid-template-columns: 1fr; }
  .map__arrow { display: none; }
}

/* ---- step three: review ---- */

.rev__summary { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.rev__chip {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 6px 13px;
  font-size: 12.5px;
  border-radius: var(--r-pill);
  background: var(--wash);
  color: var(--ink-2);
}
.rev__chip b { font-size: 15px; font-weight: 700; }
.rev__chip.is-zero { opacity: .55; }
.rev__chip--new { background: var(--good-wash); color: var(--good); }
.rev__chip--update { background: var(--warn-wash); color: var(--warn); }
.rev__chip--error { background: var(--crit-wash); color: var(--crit); }
.rev__chip--skipped { background: var(--wash); color: var(--ink-3); }

.rev { display: grid; gap: 6px; max-height: 52vh; overflow-y: auto; padding-right: 2px; }
.rev__row {
  padding: 11px 13px;
  background: var(--card);
  border: 1px solid var(--line-2);
  border-left-width: 3px;
  border-radius: var(--r-chip);
}
.rev__row--new { border-left-color: var(--good); }
.rev__row--update { border-left-color: var(--warn); }
.rev__row--error { border-left-color: var(--crit); background: var(--crit-wash); }
.rev__row--skipped { border-left-color: var(--line); opacity: .8; }

.rev__head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.rev__tick { width: 18px; flex: 0 0 18px; }
.rev__state {
  flex: 0 0 auto;
  padding: 2px 9px;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  border-radius: var(--r-pill);
}
.rev__state--new { background: var(--good-wash); color: var(--good); }
.rev__state--update { background: var(--warn-wash); color: var(--warn); }
.rev__state--error { background: var(--crit); color: #fff; }
.rev__state--skipped { background: var(--wash); color: var(--ink-3); }
.rev__line { font-size: 11.5px; color: var(--ink-3); flex: 0 0 auto; }
.rev__serial { flex: 0 0 auto; }
.rev__why { font-size: 12.5px; color: var(--ink-2); flex: 1 1 220px; min-width: 0; }

.rev__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(122px, 1fr));
  gap: 7px;
  margin-top: 9px;
}
.rev__field { display: grid; gap: 3px; font-size: 11px; color: var(--ink-3); }
.rev__field .input { padding: 5px 8px; font-size: 12.5px; min-height: 0; }

.rev__diff { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 8px; font-size: 12px; }
.rev__change { color: var(--ink-2); display: inline-flex; align-items: center; gap: 4px; }
.rev__change s { color: var(--ink-3); }
.rev__change b { color: var(--ink); }

.rev__elsewhere {
  margin-top: 9px;
  padding: 10px 12px;
  background: var(--warn-wash);
  border-left: 2px solid var(--warn);
  border-radius: var(--r-chip);
}
.rev__choice { display: flex; align-items: flex-start; gap: 8px; font-size: 12.5px; padding: 3px 0; cursor: pointer; }

.rev__notes { margin: 8px 0 0; padding-left: 17px; font-size: 12px; color: var(--ink-3); }
.rev__notes li { margin-bottom: 3px; }
.rev__notes li.is-warning { color: var(--warn); list-style: none; margin-left: -17px; display: flex; gap: 5px; align-items: baseline; }

/* ---- step four ---- */

.wiz__done { text-align: center; padding: 22px 12px 6px; }
.wiz__doneIcon { display: block; color: var(--good); margin-bottom: 10px; }
.wiz__done h3 { margin: 0 0 6px; }
.wiz__done p { margin: 0; }

/* Two columns of specs on a machine, one on a phone. */
.kv--two { grid-template-columns: minmax(92px, auto) 1fr; }
@media (min-width: 720px) {
  .kv--two { grid-template-columns: minmax(92px, auto) 1fr minmax(92px, auto) 1fr; }
}

.req {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--brand);
  margin-left: 6px;
}

/* ==========================================================================
   The printed report

   A screen built for paper. Everything a person presses is marked no-print and
   disappears; what is left is a header naming the company and the exact window,
   the figures, and a footer that repeats both — because a printed sheet gets
   photocopied, emailed and pinned to a wall, and it has to say what it is
   without the screen it came from.
   ========================================================================== */

.rephead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 14px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--line);
}
.rephead__name { font-size: 19px; font-weight: 800; letter-spacing: -0.02em; }
.rephead__meta { margin-top: 3px; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.rephead__right { text-align: right; }
.rephead__label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.rephead__value { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }

.repblock { margin-bottom: 26px; }
.repblock .section__head { margin-bottom: 12px; }
.repsub { margin: 8px 0 0; font-size: 12.5px; color: var(--ink-3); max-width: 68ch; }

.repnote {
  margin: 0 0 20px;
  padding: 10px 13px;
  border-left: 3px solid var(--brand);
  background: var(--wash);
  font-size: 13px;
  color: var(--ink-2);
}

.repline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 8px 0;
  font-size: 13.5px;
  border-bottom: 1px solid var(--line-2);
}
.repline:last-child { border-bottom: 0; }
.repline .num { font-weight: 700; white-space: nowrap; }

/* The date boxes, only there when "Choose dates" is the chosen period. */
.repbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin-bottom: 16px; }
.repbar .field { margin: 0; }

.repfoot {
  margin-top: 28px;
  padding-top: 12px;
  border-top: 1px solid var(--line-2);
  font-size: 11.5px;
  color: var(--ink-3);
}

@media (max-width: 720px) {
  .rephead { flex-direction: column; gap: 8px; }
  .rephead__right { text-align: left; }
  .repbar .field { flex: 1 1 46%; }

  /* Two money tiles across a 390px phone leaves about 150px for the figure, and
     $26,893.79 does not fit in it — it wrapped between the 7 and the 9, which
     reads as a different number entirely. One across, at full size. */
  .repblock .tiles { grid-template-columns: minmax(0, 1fr); }
  .repblock .tile__value { flex-wrap: nowrap; white-space: nowrap; }
}

/* ==========================================================================
   Parts — the buy list, what is coming, and the shelf

   Drawn on the dashboard's paper: white ground, hairline boxes, 8px corners,
   no shadows anywhere, and every figure tabular so a column reads down. The
   supplier blocks are shaped like the purchase order they are about to become
   — who it goes to and what it comes to on one line, then the parts under it.
   ========================================================================== */

.kpi__icon--danger { background: var(--crit-wash); color: var(--crit); }
.kpi__value--danger { color: var(--crit); }

.parts .panel { padding: 15px 16px 16px; }
.parts .panel__head { align-items: center; }

/* ---- one supplier's worth of parts ---- */

/* A group on the buy list: one row until it is asked for.
   ---------------------------------------------------------------------------
   Four suppliers used to draw four tables, each with its own header row, for
   eight lines of data between them — a page of column headings. Now it is four
   rows you can read at a glance and one open group underneath whichever you are
   about to ring. Only one opens at a time, which is what keeps the page to a
   screen. */

.pos { display: grid; gap: 6px; }

.po__row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  background: var(--card);
  border: 0;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  min-height: var(--tap);
}
.po__row:hover { background: var(--wash); }
.po__chev { margin-left: auto; color: var(--ink-3); display: flex; transition: transform var(--fast) var(--ease); }
.po.is-open .po__chev { transform: rotate(180deg); }
.po.is-open .po__row { border-bottom: 1px solid var(--line-2); }

.po__open { padding: 12px 14px 4px; }
.po__acts { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }

/* A line somebody typed rather than one the app worked out for itself. */
.byhand {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: var(--r-chip);
  background: var(--wash-2);
  color: var(--ink-3);
  font-size: 10.5px;
  font-weight: 700;
}

/* Tabs across the top of a page. Bigger than the segmented control inside a
   panel, because these change the whole screen. */
.seg--tabs { margin: 0 0 12px; }
.seg--tabs .seg__btn { padding: 9px 18px; font-size: 13.5px; }

.kpis--3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.kpi__short { color: var(--crit); font-weight: 700; }

.dash__head--row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }

/* ----------------------------------------------------- the demo strip

   Only ever on the copy anybody can be handed a link to (DEMO_MODE=true). The
   working instance paints nothing here at all, so there is no rule to switch
   off and no chance of it appearing over somebody's real shop. Sticky rather
   than fixed: it belongs to the page, not to the window. */
.demostrip {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0;
  padding: 9px 20px;
  background: var(--warn-wash);
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.45;
}
.demostrip__tag {
  flex: none;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--warn);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
}
@media (max-width: 620px) {
  .demostrip { padding: 8px 16px; font-size: 12px; }
}

/* ------------------------------------------------- the one search box

   What comes back is grouped — work orders, then reports, then machines, and
   so on down — because "six things" and "six kinds of thing" are read
   differently, and a flat list of eighteen rows makes a person hunt for the
   heading that isn't there.

   The whole lot scrolls inside the sheet rather than pushing the sheet taller:
   on a phone the field must stay where the thumb left it, so that what is
   typed next lands in the same place. */
.find { max-height: min(52vh, 420px); overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
.find__group + .find__group { margin-top: 16px; }
.find__label {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-3);
  letter-spacing: 0.01em;
  margin: 0 0 6px 2px;
}
/* Under the badge, not beside it: the meta column is already the narrow one. */
.find__meta { display: block; margin-top: 3px; font-size: 11.5px; color: var(--ink-3); }

/* The search inside "Add a part", and what it finds. */
.lookup { display: grid; gap: 4px; margin: 0 0 16px; max-height: 260px; overflow-y: auto; }
.lookup__row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.lookup__row:hover { background: var(--wash); border-color: var(--brand); }
.lookup__main { min-width: 0; }
.lookup__name { display: block; font-size: 13px; font-weight: 700; }
.lookup__sub { display: block; font-size: 11.5px; color: var(--ink-3); }
.lookup__stock { margin-left: auto; font-size: 11.5px; color: var(--ink-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
.lookup__stock--have { color: var(--good); font-weight: 700; }

.lookup__new {
  width: 100%;
  padding: 9px 11px;
  border: 1px dashed var(--line);
  border-radius: var(--r);
  background: transparent;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--brand);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 7px;
  justify-content: center;
}
.lookup__new:hover { background: var(--brand-wash); }

.lookup__chosen {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 12px;
  margin-bottom: 16px;
  border: 1px solid var(--brand);
  border-radius: var(--r);
  background: var(--brand-wash);
}
.lookup__chosen .btn { margin-left: auto; }

/* The "also stock this line" toggle. */
.switch { display: block; margin: 0 0 16px; cursor: pointer; }
.switch input { margin-right: 8px; }
.switch__hint { display: block; margin-top: 2px; font-size: 11.5px; color: var(--ink-3); }

.po { border: 1px solid var(--line); border-radius: var(--r); margin: 0 0 10px; overflow: hidden; }
.po:last-child { margin-bottom: 0; }
.po__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  padding: 9px 12px;
  background: var(--wash);
  border-bottom: 1px solid var(--line);
}
.po__name { font-size: 13.5px; font-weight: 800; letter-spacing: -0.012em; }
.po__meta { font-size: 12px; color: var(--ink-3); }
.po__meta strong { color: var(--ink); font-weight: 700; }
.po__actions { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }

.po .tablewrap { margin: 0; padding: 0; }
.dtable--po { min-width: 640px; }
.dtable--po thead th { background: var(--card); }
.dtable--po tbody td { padding: 9px 12px; }
.dtable--po thead th { padding: 7px 12px; }

/* Where a shelf line is headed, when it is not headed for a customer. */
.parts__shelf { font-size: 12.5px; color: var(--ink-2); }
.parts__none { margin: 4px 0 0; font-size: 12.5px; color: var(--ink-3); }

/* ---- the shelf ---- */

.parts__tools { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 14px; }
.parts__tools .input--search { flex: 1 1 240px; max-width: 320px; }
.parts__tools .chips { margin: 0; flex: 1 1 auto; }
.parts__tools .btn { flex: 0 0 auto; }

.shelfgroup { border: 1px solid var(--line); border-radius: var(--r); margin: 0 0 10px; overflow: hidden; }
.shelfgroup--low { border-color: var(--warn); }
.shelfgroup__head--btn {
  width: 100%;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  min-height: var(--tap);
}
.shelfgroup__head--btn:hover { background: var(--wash-2); }
.shelfgroup__chev { margin-left: auto; color: var(--ink-3); display: flex; transition: transform var(--fast) var(--ease); }
.shelfgroup.is-open .shelfgroup__chev { transform: rotate(180deg); }

.shelfgroup__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  background: var(--wash);
  border-bottom: 1px solid var(--line);
}
.shelfgroup--low .shelfgroup__head { background: var(--warn-wash); }
.shelfgroup__name { font-size: 12px; font-weight: 800; letter-spacing: 0.02em; }
.shelfgroup__meta { font-size: 11.5px; color: var(--ink-3); }

.shelf { border-bottom: 1px solid var(--line); }
.shelf:last-child { border-bottom: 0; }

/* The row is a button so the whole line opens it — a chevron you have to hit
   exactly is a chevron nobody presses on a phone. */
.shelf__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto 20px;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 10px 12px;
  background: none;
  border: 0;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.shelf__row:hover { background: var(--wash); }
.shelf__main { min-width: 0; }
.shelf__name {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.008em;
}
.shelf__sub {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.shelf__money { display: grid; gap: 2px; text-align: right; font-size: 11.5px; font-variant-numeric: tabular-nums; }
.shelf__pair .dim { color: var(--ink-3); margin-right: 5px; }
.shelf__count { text-align: right; min-width: 96px; }
.shelf__qty { display: block; font-size: 19px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; }
.shelf__qty--warn { color: var(--warn); }
.shelf__qty--bad { color: var(--crit); }
.shelf__min { display: block; margin-top: 1px; font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.shelf__chev { display: inline-flex; color: var(--ink-3); transition: transform 0.15s ease; }
.shelf__chev .icon { width: 16px; height: 16px; }
.shelf.is-open .shelf__chev { transform: rotate(180deg); }
.shelf.is-open .shelf__row { background: var(--wash); }

/* What the merge was for: where the part actually went. */
.shelf__more {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  padding: 2px 12px 13px;
  border-top: 1px dashed var(--line);
}
.shelf__movetitle {
  margin: 10px 0 6px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.shelf__acts { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 6px; padding-top: 10px; }

.moves { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.moves__row { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12px; }
.moves__date { color: var(--ink-3); font-variant-numeric: tabular-nums; min-width: 54px; }
.moves__what--out { color: var(--ink); font-weight: 600; }
.moves__what--in { color: var(--good); font-weight: 600; }
.moves__who { color: var(--ink-3); }

@media (max-width: 720px) {
  .parts .panel { padding: 13px 12px 14px; }
  .parts .panel__head { flex-wrap: wrap; }
  .parts .panel__head .seg { margin-left: 0 !important; width: 100%; }
  .po__actions { margin-left: 0; width: 100%; }
  .po__actions .btn { flex: 1 1 auto; justify-content: center; }
  .parts__tools .input--search { flex: 1 0 100%; max-width: none; }
  .parts__tools .chips { flex: 1 0 100%; }
  .parts__tools .btn { flex: 1 0 100%; justify-content: center; }

  /* Four columns of figures do not fit beside a part name on a phone: the
     money moves under the name and the count keeps the right-hand edge, which
     is the number somebody is actually scanning for. */
  .shelf__row { grid-template-columns: minmax(0, 1fr) auto 18px; gap: 10px; }
  .shelf__money {
    grid-column: 1 / 2;
    grid-row: 2;
    display: flex;
    gap: 12px;
    text-align: left;
    margin-top: 3px;
  }
  .shelf__count { grid-column: 2; grid-row: 1 / 3; }
  .shelf__more { grid-template-columns: minmax(0, 1fr); }
  .shelf__acts { padding-top: 0; }
}

/* ==========================================================================
   The machine record

   One forklift's whole life: who was here last, everyone who has touched it,
   every entry with a name and a time on it, and every invoice ever raised
   against it. Read on the phone while somebody waits, so the two things that
   have to be findable at a glance are the timestamp and the person.
   ========================================================================== */

.rec__last { padding: 15px 16px 16px; }
.rec__lasthead { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.rec__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--card);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
}
.rec__lastwho { display: block; font-size: 14.5px; font-weight: 800; letter-spacing: -0.014em; }
.rec__lastwhen { display: block; margin-top: 1px; font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.rec__lastref { margin-left: auto; font-size: 12.5px; font-weight: 700; color: var(--brand); }
.rec__lastwork { margin: 11px 0 0; font-size: 14px; line-height: 1.5; }
.rec__lastmeta { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 9px; font-size: 12px; color: var(--ink-3); }

.rec__cols { display: grid; gap: 12px; margin-top: 16px; }
@media (min-width: 760px) { .rec__cols { grid-template-columns: 1fr 1fr; } }

.rec__people { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.rec__person { display: flex; align-items: center; gap: 10px; }
.rec__personbody { min-width: 0; }
.rec__personname { display: block; font-size: 13.5px; font-weight: 700; }
.rec__personsub { display: block; margin-top: 1px; font-size: 11.5px; color: var(--ink-3); }
.rec__none { margin: 0; font-size: 13px; color: var(--ink-3); }
.rec__foot { margin: 10px 0 0; font-size: 11.5px; color: var(--ink-3); }

/* ---- the timeline ---- */

.rec__feed { list-style: none; margin: 0; padding: 0; }
.rec__event {
  display: grid;
  grid-template-columns: 92px 26px minmax(0, 1fr);
  align-items: start;
  gap: 10px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line-2);
}
.rec__event:last-child { border-bottom: 0; }
.rec__when { text-align: right; font-variant-numeric: tabular-nums; padding-top: 1px; }
.rec__date { display: block; font-size: 12.5px; font-weight: 700; }
.rec__time { display: block; font-size: 11px; color: var(--ink-3); }
.rec__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-pill);
  background: var(--wash);
  color: var(--ink-2);
}

/* The one distinction that matters on this list: did we do it, or did they. */
.rec__event--yours .rec__icon { background: var(--tint-info); color: var(--viz-1); }
.rec__icon--completed { background: var(--good-wash); color: var(--good); }
.rec__icon--reported { background: var(--crit-wash); color: var(--crit); }
.rec__icon--invoiced, .rec__icon--paid { background: var(--brand-wash); color: var(--brand); }

.rec__body { min-width: 0; }
.rec__title { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; font-size: 13.5px; font-weight: 700; }
.rec__amount { font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }
.rec__who { display: block; margin-top: 2px; font-size: 12px; color: var(--ink-3); }
.rec__detail { display: block; margin-top: 5px; font-size: 13px; color: var(--ink-2); line-height: 1.5; }

@media (max-width: 560px) {
  .rec__event { grid-template-columns: 26px minmax(0, 1fr); }
  .rec__when { grid-column: 2; grid-row: 1; text-align: left; display: flex; gap: 8px; align-items: baseline; }
  .rec__icon { grid-column: 1; grid-row: 1 / 3; }
  .rec__body { grid-column: 2; grid-row: 2; }
}

/* ==========================================================================
   No zooming when somebody types

   Safari on iOS zooms the whole page in when a focused field is set below 16px,
   and then leaves it zoomed — the page jumps sideways, the field you are typing
   in slides off the edge, and pinching back out is a job in itself. It is not a
   preference, it is a hard threshold, and the only fix is to be at or above it.

   So every control a person can type into is 16px on a touch screen, whatever
   size it takes on a desk. That includes the deliberately small ones — a
   toolbar search box, a quantity cell in the invoice editor — because a field
   is a field to Safari, and the phone layout gives them the width anyway.

   Coarse pointer rather than screen width: a small window on a laptop does not
   zoom, and a large tablet does.
   ========================================================================== */

@media (pointer: coarse) {
  .input,
  .textarea,
  .select,
  .input--sm,
  .input--cell,
  .pill-select select,
  .rev__field .input,
  input[type='text'],
  input[type='search'],
  input[type='email'],
  input[type='tel'],
  input[type='url'],
  input[type='number'],
  input[type='password'],
  input[type='date'],
  input[type='time'],
  input[type='datetime-local'],
  select,
  textarea {
    /* 16px exactly. Larger reads as a mistake next to the labels around it. */
    font-size: 16px;
  }

  /* The small ones grow a little to carry the bigger text without the glyphs
     touching the border. */
  .input--sm { min-height: 38px; padding: 7px 11px; }
  .input--cell { width: 92px; padding: 7px 8px; }
  .pill-select select { min-height: 40px; }
  .terms__custom input { width: 96px; }
}

/* ==========================================================================
   What this will take — the sizing on a report

   The assistant's reading of the photographs and the record on the left of the
   sentence, the portal's own arithmetic on the right. The screen has to make
   which is which obvious, because somebody quotes a customer from it.
   ========================================================================== */

.size__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.size__eyebrow .icon { width: 13px; height: 13px; }

.size__photos {
  padding: 11px 13px;
  margin: 0 0 14px;
  background: var(--wash);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-chip);
}
.size__photoline { margin: 5px 0 0; font-size: 13.5px; line-height: 1.5; }

.size__figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px;
  margin: 0 0 12px;
}
.size__fig {
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--card);
}
.size__fig--good { background: var(--good-wash); border-color: transparent; }
.size__label {
  display: block;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.size__value {
  display: block;
  margin-top: 4px;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.size__fig--good .size__value { color: var(--good); }
.size__sub { display: block; margin-top: 2px; font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

.size__why { margin: 0 0 14px; font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.size__block { margin: 0 0 14px; }
.size__line { margin: 5px 0 0; font-size: 13px; line-height: 1.5; }

.size__parts { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 6px; }
.size__part {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 7px 0;
  border-bottom: 1px solid var(--line-2);
  font-size: 13px;
}
.size__part:last-child { border-bottom: 0; }
.size__partname { font-weight: 600; }
.size__partmeta { display: inline-flex; align-items: center; gap: 8px; font-variant-numeric: tabular-nums; }

/* =============================================================================
   THE CUSTOMER SIDE — different rules, on purpose
   =============================================================================

   Everything above this line is the staff side: dense, 13px, tables, a screen
   of figures read at a desk for an hour at a time.

   A customer is not that. A customer is a forklift operator standing in an
   aisle with one glove off, reporting a machine that has just stopped. They use
   this app perhaps four times a year, on a phone, and they are already annoyed
   when they open it.

   So the customer side takes the new COLOURS, the new FACE and the new 8px
   CORNERS — and nothing else from the staff treatment. It keeps 17px body text,
   it keeps targets you can hit without looking, and it has no table anywhere in
   it. Making it match the staff density would be tidy on a designer's screen
   and worse in the aisle, which is the only place it matters.

   Scoped on one attribute set by the router, so a page cannot forget.
   ========================================================================== */

:root[data-side='customer'] body,
:root[data-side='customer'] #view {
  font-size: 17px;
  line-height: 1.5;
}

/* Anything that is a target rather than a label. 60px, not 44 — a gloved
   thumb on a phone in a cold store is the size to design for. */
:root[data-side='customer'] .btn--lg,
:root[data-side='customer'] .btn--block,
:root[data-side='customer'] .fab,
:root[data-side='customer'] .camera-btn,
:root[data-side='customer'] .severity-card {
  font-size: 17px;
}

:root[data-side='customer'] .list__item { min-height: 60px; font-size: 16px; }
:root[data-side='customer'] .list__title { font-size: 17px; }
:root[data-side='customer'] .list__sub { font-size: 15px; }
:root[data-side='customer'] .page-title { font-size: 27px; }
:root[data-side='customer'] .page-sub { font-size: 16px; }
:root[data-side='customer'] .card__title { font-size: 18px; }
:root[data-side='customer'] .badge { font-size: 13.5px; padding: 4px 12px; }
:root[data-side='customer'] .input,
:root[data-side='customer'] .select,
:root[data-side='customer'] .textarea { font-size: 17px; min-height: 52px; }
:root[data-side='customer'] .tile__value { font-size: 30px; }
:root[data-side='customer'] .tile__label { font-size: 12px; }
:root[data-side='customer'] .small { font-size: 15px; }
:root[data-side='customer'] .tiny { font-size: 13.5px; }

/* The wizard is left exactly as it is in shape and size — the four steps, the
   picture buttons, the huge camera, the three severity choices. Only its
   corners come down to the app's 8px, and its colours come from the tokens. */
:root[data-side='customer'] .pick,
:root[data-side='customer'] .severity-card,
:root[data-side='customer'] .camera-btn,
:root[data-side='customer'] .thumb,
:root[data-side='customer'] .wizard__step { border-radius: var(--r); }

/* The camera and the severity choices are the two biggest targets in the app
   and they stay that way. */
:root[data-side='customer'] .camera-btn { min-height: 132px; }
:root[data-side='customer'] .severity-card { min-height: 72px; }
:root[data-side='customer'] .pick { min-height: 96px; }

@media print {
  :root {
    --page: #fff;
    --card: #fff;
    --wash: #fff;
    --wash-2: #fff;
    --ink: #000;
    --ink-2: #333;
    --ink-3: #555;
    --line-2: #ddd;
    --line: #999;
    /* Paper has no corners and casts no shadow. Everything the screen design
       rounds and lifts goes flat here, in one place, by resetting the tokens
       the rest of the file reads. */
    --r: 0;
    --r-chip: 0;
  }
  @page { margin: 16mm; }

  body { background: #fff; font-size: 11.5pt; }
  #app { padding: 0; }
  .shell { display: block; border: none; border-radius: 0; }
  .sidebar, .topbar, #toasts, #alert-root, .scrim, .no-print, .btn, .chips, .sidebar__scrim { display: none !important; }
  main { padding: 0; }
  .doc { border: none; border-radius: 0; padding: 0; max-width: none; }
  .doc__work { background: var(--wash); }
  a { color: #000; text-decoration: none; }

  /* A printed month is the week to come pinned to a wall, so it gets the whole
     page: the rail, the toolbar and the search box are all things you press, and
     none of them survive the journey to paper. Cells stop being a fixed height
     so nothing is cut off — on paper there is no scrolling to a "+2 more". */
  .cal-page { display: block; }
  #cal-rail, .cal-bar, .calev--more { display: none !important; }
  .cal__cell { height: auto; min-height: 92px; break-inside: avoid; }
  .cal__blank { display: none; }
  .calev, .calev__text { white-space: normal; }
  .calday { display: block; }
  .calday__side { margin-top: 10mm; }

  /* The report goes on paper as a report: the header and footer that name the
     company and the window stay, the tiles flatten into plain figures, and a
     table is never allowed to break a row across a page fold. */
  .rephead { border-bottom: 1.5px solid #000; }
  .repblock { break-inside: avoid; margin-bottom: 8mm; }
  .repnote { border-left: 2px solid #666; background: none; padding-left: 8px; }
  .tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4mm; }
  .tile {
    border: 1px solid #ccc;
    background: none !important;
    padding: 3mm 3.5mm;
    break-inside: avoid;
  }
  .tile__value { font-size: 15pt; }
  .tile__label, .tile__foot { color: #333; }
  table.data { font-size: 9.5pt; }
  table.data tr { break-inside: avoid; }
  table.data thead { display: table-header-group; }
  .table-wrap { overflow: visible; }
  .repfoot { break-inside: avoid; }
}

/* ==========================================================================
   The employee file — the roster's rows and the drawer's documents.
   ========================================================================== */

.team-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.team-row:hover { border-color: var(--brand); background: var(--wash); }
.team-row__who { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.team-row__who .tiny { flex-basis: 100%; }
.team-row__files { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }

.doc-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--wash);
  border-radius: var(--r);
}
.doc-row--sin { background: var(--tint-info); }
.doc-row__icon { flex: 0 0 auto; color: var(--ink-3); display: inline-flex; }

/* The diary. One column that never fights the width: the event, then when
   and by whom in a single small line under it, with a rail of dots so the
   eye can walk down it. */
.diary { display: flex; flex-direction: column; }
.diary__row {
  display: flex;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-2);
}
.diary__row:last-child { border-bottom: none; padding-bottom: 2px; }
.diary__dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  margin-top: 6px;
  border-radius: 50%;
  background: var(--line);
}
.diary__body { min-width: 0; }
.diary__what { font-size: 14px; font-weight: 500; overflow-wrap: anywhere; }
.diary__meta { font-size: 12px; color: var(--ink-3); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 640px) {
  .team-row { flex-wrap: wrap; }
  .team-row__files { justify-content: flex-start; }
  /* The drawer's rows put their buttons on a second row rather than
     squeezing the filename into a sliver. */
  .doc-row { flex-wrap: wrap; }
  .doc-row .grow { flex-basis: calc(100% - 28px); }
  .doc-row .btn { margin-left: 28px; }
  .doc-row .btn ~ .btn { margin-left: 0; }
}

/* ==========================================================================
   The report centre — tabs over one window, and the growth sheet's parts.
   ========================================================================== */

.reptabs {
  display: flex;
  gap: 4px;
  margin: 14px 0 12px;
  padding: 4px;
  background: var(--wash);
  border-radius: var(--r);
  width: fit-content;
  max-width: 100%;
  overflow-x: auto;
}
.reptab {
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  font-size: 13.5px;
  color: var(--ink-2);
  padding: 8px 16px;
  border-radius: calc(var(--r) - 4px);
  cursor: pointer;
  white-space: nowrap;
}
.reptab:hover { color: var(--ink); }
.reptab.is-active { background: var(--surface); color: var(--ink); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }

/* Five sheets do not fit one line on a phone, and the sideways scroll they
   used to get hid the last one completely: a tab nobody can see is a tab
   nobody presses. They wrap instead, so every sheet on the page is on the
   page. */
@media (max-width: 620px) {
  .reptabs { width: 100%; flex-wrap: wrap; overflow-x: visible; }
  .reptab { flex: 1 1 auto; padding: 8px 12px; text-align: center; }
}

/* ---- Targets -----------------------------------------------------------

   Written narrow first, because that is where the reading happens: an owner
   checks whether the month is on course from a phone, standing in the shop,
   not from a desk. On a phone a row is three short lines — the measure and
   its score, then what that is out of, then the bar. Above 720px the same
   markup rules itself into columns.

   The three colours are the reserved status tokens and nothing else uses
   them near a number: under 85% red, 85–99 amber, 100 and over green. A row
   with no target set gets none of them at all — grey is a state, not a
   failing grade. */

.tgt { display: grid; gap: 2px; }

.tgt__row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 4px 12px;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--card);
}

.tgt__label { font-weight: 600; font-size: 13.5px; }
.tgt__est {
  font-weight: 500;
  font-size: 11px;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 4px);
  padding: 0 4px;
}

.tgt__pct {
  justify-self: end;
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.tgt__nums {
  grid-column: 1 / -1;
  font-size: 13px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.tgt__nums b { color: var(--ink); font-weight: 600; }

.tgt__bar {
  grid-column: 1 / -1;
  height: 7px;
  border-radius: var(--r-pill);
  background: var(--wash);
  overflow: hidden;
  margin-top: 3px;
}
.tgt__bar i { display: block; height: 100%; border-radius: var(--r-pill); background: var(--ink-3); }

/* What the same target says should have been done by today. Grey on purpose:
   it is context for the colour above it, not a second verdict. */
.tgt__pace {
  grid-column: 1 / -1;
  font-size: 12px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.tgt__row--red { border-color: var(--crit-line, var(--line)); background: var(--crit-wash); }
.tgt__row--red .tgt__pct { color: var(--crit); }
.tgt__row--red .tgt__bar i { background: var(--crit); }

.tgt__row--amber { background: var(--warn-wash); }
.tgt__row--amber .tgt__pct { color: var(--warn); }
.tgt__row--amber .tgt__bar i { background: var(--warn); }

.tgt__row--green { background: var(--good-wash); }
.tgt__row--green .tgt__pct { color: var(--good); }
.tgt__row--green .tgt__bar i { background: var(--good); }

.tgt__row--over { background: var(--brand-wash); }
.tgt__row--over .tgt__pct { color: var(--brand); }
.tgt__row--over .tgt__bar i { background: var(--brand); }

.tgt__row--none .tgt__pct { font-size: 12.5px; font-weight: 500; color: var(--ink-3); }

@media (min-width: 720px) {
  .tgt__row {
    /* The label takes what it needs and no more; the slack goes to the bar,
       which is the part that carries the meaning at a glance. */
    grid-template-columns: minmax(120px, 210px) minmax(150px, auto) minmax(120px, 1fr) 68px;
    align-items: center;
    gap: 6px 18px;
  }
  .tgt__nums { grid-column: 2; text-align: right; }
  .tgt__bar { grid-column: 3; margin-top: 0; }
  .tgt__pct { grid-column: 4; }
  .tgt__pace { grid-column: 1 / -1; margin-top: -2px; }
  .tgt__row--none .tgt__nums { grid-column: 2 / 4; }
}

/* The catch-up: what it takes a day, from here, to still get there. Said in
   the same grey as the pace line — it is advice, not a verdict. */
.tgt__catch {
  grid-column: 1 / -1;
  font-size: 12px;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.tgt__catch--done { color: var(--good); }

/* Revenue's three tiers, under the row they belong to. Each is graded, and
   each carries its own per-day figure — "we will make the commitment but not
   the goal" is exactly what a shop wants to know on the twentieth. */
.tgt__tiers {
  grid-column: 1 / -1;
  display: grid;
  gap: 6px;
  margin-top: 8px;
}

.tgt__tier {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: 2px 10px;
  padding: 8px 10px;
  border-radius: var(--r-sm, 6px);
  background: var(--surface);
  border: 1px solid var(--line-2);
  font-size: 12.5px;
}
.tgt__tierName { font-weight: 600; }
.tgt__tierAmount { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.tgt__tierPct { justify-self: end; font-weight: 700; font-variant-numeric: tabular-nums; }
.tgt__tierBar {
  grid-column: 1 / -1;
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--wash);
  overflow: hidden;
}
.tgt__tierBar i { display: block; height: 100%; background: var(--ink-3); border-radius: var(--r-pill); }
.tgt__tierCatch { grid-column: 1 / -1; color: var(--ink-3); font-variant-numeric: tabular-nums; }

.tgt__tier--red .tgt__tierPct { color: var(--crit); }
.tgt__tier--red .tgt__tierBar i { background: var(--crit); }
.tgt__tier--amber .tgt__tierPct { color: var(--warn); }
.tgt__tier--amber .tgt__tierBar i { background: var(--warn); }
.tgt__tier--green .tgt__tierPct { color: var(--good); }
.tgt__tier--green .tgt__tierBar i { background: var(--good); }
.tgt__tier--over .tgt__tierPct { color: var(--brand); }
.tgt__tier--over .tgt__tierBar i { background: var(--brand); }
.tgt__tier--none .tgt__tierPct { color: var(--ink-3); font-weight: 500; }

@media (min-width: 720px) {
  .tgt__tier {
    grid-template-columns: 90px minmax(90px, auto) minmax(110px, 1fr) 54px minmax(150px, auto);
    align-items: center;
  }
  .tgt__tierBar { grid-column: 3; }
  .tgt__tierPct { grid-column: 4; }
  .tgt__tierCatch { grid-column: 5; grid-row: 1; text-align: right; }
}

/* A second chip row under the first, for revenue's three tiers. */
.chips--sub { margin-top: -8px; }
.chips--sub .chip { font-size: 12.5px; min-height: 28px; padding: 0 11px; }

/* ---- setting them ---- */

/* The year being set, and the arrows either side of it. */
.tgtyear {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.tgtyear__label {
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  min-width: 4ch;
  text-align: center;
}

/* Twelve months, one measure at a time. Three across on a phone — Jan Feb Mar
   over Apr May Jun reads as a year in a way a single column never does — and
   as many as fit on anything wider. Forty-eight boxes at once would not be a
   form, it would be a wall, which is why the measure is a chip above them. */
.tgtset__boxes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
@media (min-width: 620px) { .tgtset__boxes { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 900px) { .tgtset__boxes { grid-template-columns: repeat(6, minmax(0, 1fr)); } }

.tgtset__box { display: grid; gap: 4px; }
.tgtset__month {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: -0.002em;
}
.tgtset__box .input { text-align: right; font-variant-numeric: tabular-nums; }

/* The year, which is the twelve added up and never a box. */
.tgtset__total {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line-2);
  font-size: 13px;
}
.tgtset__total b { font-size: 15px; font-variant-numeric: tabular-nums; }

/* The revenue mix, as one bar that always adds to the width. */
.mixbar {
  display: flex;
  height: 14px;
  border-radius: var(--r-pill);
  overflow: hidden;
  margin-bottom: 14px;
}
.mixbar__seg--labour { background: var(--brand); }
.mixbar__seg--parts { background: var(--viz-1, #4a90d9); }
.mixbar__seg--travel { background: var(--line); }

/* An opportunity: the sentence, then the arithmetic it came from. */
.opp {
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--r);
  margin-bottom: 10px;
}
.opp__line { font-size: 14.5px; font-weight: 500; overflow-wrap: anywhere; }
.opp__basis { font-size: 12px; color: var(--ink-3); margin-top: 4px; overflow-wrap: anywhere; }

.repperson .section__title { display: flex; align-items: center; gap: 8px; }
.repline__util { display: inline-flex; align-items: center; gap: 8px; }

@media print {
  .reptabs { display: none; }
}

/* The one-line explanation of a core, above the Cores tiles. */
.cores__note { margin: 14px 0 16px; max-width: 68ch; }

/* ==========================================================================
   The booking sheet — one sheet, opened from four places.
   ========================================================================== */

.bk-head { padding-bottom: 12px; border-bottom: 1px solid var(--line-2); }
.bk-head__unit { font-size: 17px; font-weight: 700; }
.bk-head__why { font-size: 13.5px; color: var(--ink-2); margin-top: 2px; }

.bk-section { margin-top: 18px; }
.bk-section__head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.bk-section__title { font-size: 13px; font-weight: 700; }
.bk-section__hint { font-size: 12px; color: var(--ink-3); }

/* One row per technician: the dot says free/room/full at a glance, the middle
   says who and why they might be the obvious pick, the right says their day. */
.bk-techs { display: flex; flex-direction: column; gap: 6px; }
.bk-tech {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  cursor: pointer;
  background: var(--surface);
}
.bk-tech:hover { border-color: var(--brand); }
.bk-tech.is-chosen { border-color: var(--brand); background: var(--tint-brand, var(--wash)); }
.bk-tech input { position: absolute; opacity: 0; width: 0; height: 0; }
.bk-tech__dot { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.bk-tech__dot--free { background: var(--good); }
.bk-tech__dot--room { background: var(--warn); }
.bk-tech__dot--full { background: var(--crit); }
.bk-tech__dot--later { background: var(--ink-3); }
.bk-tech__who { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.bk-tech__note { font-size: 12px; color: var(--brand); }
.bk-tech__load { font-size: 12.5px; color: var(--ink-2); white-space: nowrap; }
/* Already going there: the cheapest answer on the sheet, so it looks like one. */
.bk-tech--onsite { border-color: var(--brand); }

.bk-warn {
  margin: 10px 0 0;
  font-size: 12.5px;
  color: var(--ink-2);
  padding: 9px 11px;
  background: var(--tint-warn, var(--wash));
  border-radius: var(--r);
}

.bk-parts {
  margin: 12px 0 0;
  font-size: 12.5px;
  color: var(--ink-2);
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
}

/* The batching offer. The single most valuable thing on the sheet, so it is
   framed rather than tucked under a fold. */
.bk-batch {
  margin-top: 18px;
  padding: 14px;
  border: 1px solid var(--brand);
  border-radius: var(--r);
  background: var(--wash);
}
.bk-batch__head { font-size: 13px; font-weight: 700; margin-bottom: 10px; }
.bk-batch__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--line-2);
  cursor: pointer;
}
.bk-batch__row:first-of-type { border-top: none; }
.bk-batch__foot { margin: 10px 0 0; font-size: 12.5px; color: var(--ink-2); }

/* The dashboard's unassigned strip: link and a Book button side by side. */
.strip__pair { display: inline-flex; align-items: center; gap: 6px; }
.strip__book {
  border: 1px solid var(--line);
  background: var(--surface);
  border-radius: var(--r-chip);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  cursor: pointer;
  color: var(--ink);
}
.strip__book:hover { border-color: var(--brand); color: var(--brand); }

@media (max-width: 640px) {
  .bk-tech { flex-wrap: wrap; }
  .bk-tech__load { flex-basis: 100%; padding-left: 19px; }
}

/* ========================================================== where the punches happened

   Deliberately the same weight as everything else on the job. There is no
   warning colour anywhere in this block and there must not be one: a distance
   is a fact for the owner to weigh, not an accusation the software makes. */
.pins { border-top: 1px solid var(--line); padding-top: 12px; }
.pins__head {
  margin: 0 0 8px;
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.pins__row {
  display: grid;
  grid-template-columns: 78px 76px 1fr;
  gap: 8px;
  align-items: baseline;
  padding: 4px 0;
  font-size: 13px;
}
.pins__what { font-weight: 600; }
.pins__when { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.pins__where { color: var(--ink-2); }
/* A reading too coarse to state a distance from reads quieter, not louder —
   it is less information, not a problem. */
.pins__where--vague { color: var(--ink-3); }
.pins__total { margin: 8px 0 0; font-size: 13px; font-weight: 600; }
.pins__offer {
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--wash);
  font-size: 13px;
}
.pins__offer .btn { margin: 0; }

/* The plan view. Drawn from the numbers, so no tile server is told where our
   technicians were, and the accuracy ring is as prominent as the pin. */
.pinmap { margin: 12px 0 0; }
.pinmap svg { width: 190px; max-width: 100%; height: auto; display: block; }
.pinmap__ground { fill: var(--wash-2); stroke: var(--line); stroke-width: 1; rx: 8; }
.pinmap__halo { fill: var(--brand); opacity: 0.1; }
.pinmap__site { stroke: var(--ink-2); stroke-width: 1.5; }
.pinmap__pin { fill: var(--brand); }
.pinmap__pin--ended { fill: var(--ink); }
.pinmap__bar { stroke: var(--ink-3); stroke-width: 2; }
.pinmap__scale { font-size: 9px; fill: var(--ink-3); }
.pinmap__key {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-top: 7px;
  font-size: 11.5px;
  color: var(--ink-3);
}
.pinmap__legend { display: inline-flex; align-items: center; gap: 5px; }
.pinmap__legend::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--brand);
}
.pinmap__legend--site::before { background: var(--ink-2); border-radius: 0; height: 2px; width: 10px; }
.pinmap__legend--ended::before { background: var(--ink); }
.pinmap__note { flex: 1 1 100%; }

@media (max-width: 560px) {
  .pins__row { grid-template-columns: 1fr; gap: 0; padding: 7px 0; }
  .pins__row + .pins__row { border-top: 1px dashed var(--line); }
  .pins__when { font-size: 12px; }
}

/* ===================================================== cores: tiles that filter

   The four tiles were static numbers with the detail that explains them
   scrolled off underneath, which is exactly how the detail got missed. Each
   one is a button now: press it to see only what it counts, press it again to
   see everything. So they have to LOOK pressable — a cursor, a hover, and a
   focus ring a keyboard can find. */
.tile--pick {
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
}
.tile--pick:hover { transform: translateY(-1px); border-color: var(--ink-3); }
.tile--pick:active { transform: none; }
.tile--pick:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
/* Pressed: the brand edge and a tint, so which filter is on is readable from
   across the room rather than from the small print. */
.tile--pick.is-on {
  border-color: var(--brand);
  box-shadow: inset 0 0 0 1px var(--brand);
  background: var(--brand-wash);
}
.tile--pick.is-on .tile__label { color: var(--brand); }

.cores__filtered {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin: 14px 0 0;
  color: var(--ink-2);
}
.linkbtn {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--brand);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.linkbtn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* Where the old unit physically is — the field that actually stops a core
   expiring. Greyed on the row, and a button, because the answer changes. */
.corerow__where {
  display: inline-block;
  margin-top: 3px;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 11.5px;
  color: var(--ink-3);
  cursor: pointer;
  text-align: left;
}
.corerow__where:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.corerow__where:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 3px; }
/* Nobody has said where it is, which is the state worth a nudge — dashed
   rather than loud, because it is a missing note, not an error. */
.corerow__where--none {
  color: var(--ink-3);
  border-bottom: 1px dashed var(--line-strong, var(--ink-3));
  font-style: italic;
}
.corerow--expired .corerow__main .strong { color: var(--ink-2); }

/* What is going in the box, on the return sheet. */
.shiplist { border-top: 1px solid var(--line); margin-top: 4px; }
.shiplist__row {
  display: grid;
  grid-template-columns: 10px 1fr auto;
  gap: 10px;
  align-items: baseline;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.shiplist__dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--brand);
  transform: translateY(-1px);
}
.shiplist__off { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }
.shiplist__where { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; }

@media (max-width: 560px) {
  .shiplist__row { grid-template-columns: 10px 1fr; }
  .shiplist__where { grid-column: 2; }
}

/* ============================================================= the vendor book

   Built on .dtable rather than a second table style of its own — white, 8px
   radius, 1px hairlines, no shadows, every number right-aligned and tabular.
   What is added here is only what a vendor row needs that a data table does
   not already have. */
.vendpage .tablewrap { margin-top: 0; }
.vend { min-width: 1040px; }
.vend thead th { position: sticky; top: 0; z-index: 1; }
.vend__name a { font-weight: 700; color: var(--ink); text-decoration: none; }
.vend__name a:hover { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }
.vend__sub { display: block; font-size: 11px; font-weight: 600; color: var(--ink-3); margin-top: 2px; white-space: normal; }

/* Edited where they are read. A supplier's phone number changes every few
   months and a modal for one field is how a list stops being kept up. */
.vend__edit { min-width: 120px; }
/* A phone number is a fixed length and reads badly clipped; an email never
   fits whatever we give it, so the narrower column is the email's. */
.vend__edit--phone { min-width: 132px; }
.vend__edit .input--sm { width: 100%; }
.input--sm { padding: 5px 8px; font-size: 12.5px; height: auto; }
.vend__row.is-dirty .input--sm { border-color: var(--brand); }
.vend__row .vend__save:disabled { opacity: 0.4; }
/* The row's Save is quiet until there is something to save, then it is the
   obvious thing to press — twelve permanently-orange buttons down a list read
   as an instruction rather than an option. */
.vend__row.is-dirty .vend__save {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

/* Red only when there is something to say. A column that is red on every row
   is a column nobody reads. */
.vend__owed { color: var(--crit); font-weight: 800; text-decoration: none; }
a.vend__owed:hover { text-decoration: underline; text-underline-offset: 2px; }
.vend__zero,
.vend__none { color: var(--ink-3); font-weight: 600; }
.vend__lead { color: var(--ink-2); }
.vend__acts { white-space: nowrap; }
.vend__acts .btn { margin-right: 4px; }
.vend__acts .btn:last-child { margin-right: 0; }
.vend__empty { color: var(--ink-3); font-weight: 600; text-align: center; padding: 22px 10px; }

.vend__bar {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
  margin: 14px 0 12px;
}
.vend__bar .input { flex: 0 1 280px; }
.vend__totals { font-size: 12.5px; color: var(--ink-2); margin-left: auto; }

/* The pairs the migration would not guess at. Advisory: it merges nothing. */
.vend__review {
  margin-top: 14px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--warn);
  border-radius: var(--r);
  background: var(--wash);
}
.vend__reviewhead { margin: 0 0 8px; font-size: 13px; }
.vend__reviewrow {
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  padding: 5px 0;
  font-size: 13px;
}

/* ------------------------------------------------------- one vendor's page */
.vend__head { align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.vend__switch { display: flex; flex-direction: column; gap: 4px; min-width: 210px; }
.vend__switchlabel {
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.vend__summary { margin: 4px 0 18px; font-size: 14px; color: var(--ink-2); }
.vend__summary b { color: var(--ink); }

.vend .is-late td { background: var(--crit-wash); }
.vend__paid { white-space: nowrap; }
.tick { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: 12px; }
.tick input { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; }

@media (max-width: 720px) {
  .vend__totals { margin-left: 0; flex: 1 1 100%; }
  .vend__bar .input { flex: 1 1 100%; }
  .vend__switch { min-width: 0; flex: 1 1 100%; }
}


/* ==========================================================================
   The QR scan page — /m/<token>. Standing at the machine, phone in hand.
   Reuses the pick / severity / camera-btn / thumbs / done components; this
   block is only the page's own frame.
   ========================================================================== */

.scanpage { background: var(--wash); }
.scan {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  padding: 16px;
}
.scan__loading { color: var(--ink-2); padding-top: 40vh; }
.scan__card { width: min(560px, 100%); }
.scan__brand {
  font-weight: 800;
  letter-spacing: 0.02em;
  font-size: 15px;
  color: var(--ink-2);
  margin: 4px 0 14px;
}
.scan__brand b { color: var(--brand); }
.scan__machine {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 14px 16px;
  margin-bottom: 18px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.scan__unit { font-size: 20px; font-weight: 800; }
.scan__meta { color: var(--ink-2); font-size: 14px; }
.scan__q { font-size: 24px; font-weight: 800; letter-spacing: -0.02em; margin: 0 0 6px; }
.scan__help { color: var(--ink-2); font-size: 15px; margin: 0 0 16px; }
.scan .btn--lg { min-height: 56px; font-size: 16px; }
.scan .input, .scan .textarea { font-size: 16px; }


/* ==========================================================================
   The customer home — three blocks, phone first. Not a dashboard.
   ========================================================================== */

.chome__head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.chome__logo { width: 44px; height: 44px; object-fit: contain; border-radius: 8px; border: 1px solid var(--line); background: var(--card); }
.chome__company { font-size: 22px; font-weight: 800; letter-spacing: -0.02em; margin: 0; }
.chome__serviced { color: var(--ink-2); font-size: 13px; margin: 2px 0 0; }
.chome__serviced b { color: var(--brand); }

.chome__block {
  display: block;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
  margin-bottom: 10px;
  color: inherit;
  text-decoration: none;
}
.chome__block span, .chome__row span { text-decoration: none; }
a.chome__block, a.chome__block:hover, .chome__row, .chome__row:hover { text-decoration: none; }
.chome__block--down { border-color: var(--ink); }
.chome__block--live { border-color: var(--brand); }
.chome__label {
  display: block;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 6px;
}
.chome__big { display: block; font-size: 19px; font-weight: 800; letter-spacing: -0.01em; }
.chome__sub { display: block; color: var(--ink-2); font-size: 14px; margin-top: 2px; }
.chome__calm { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 600; }
.chome__calm svg { color: var(--ok, #2c7a4b); flex: none; }

.chome__row { display: flex; align-items: center; gap: 10px; padding: 8px 0; color: inherit; text-decoration: none; }
.chome__row + .chome__row { border-top: 1px solid var(--line); }
.chome__dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.chome__dot--down { background: var(--ink); width: 11px; height: 11px; }
.chome__dot--open { background: var(--warn, #b97d10); }
.chome__body { flex: 1; min-width: 0; }
.chome__unit { display: block; font-weight: 700; font-size: 15px; }
.chome__row .chome__sub { margin-top: 0; font-size: 13px; }

.chome__report { min-height: 60px; font-size: 17px; }

/* ------------------------------------------------------------- QR labels

   The print sheet for the stickers that go on the machines. On screen it is
   a preview grid in true label proportions; on paper it is one label per
   page on 4"x6" (or 2"x3") stock, selected by #qlabels[data-size]. The QR
   is generated in the page (js/qr.js) and printed at 25mm or larger with
   its quiet zone intact — that is what scans from arm's length. */
.qlabels {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 16px;
}
.qlabel {
  background: #fff;
  color: #000;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 18px 14px 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 6px;
  aspect-ratio: 2 / 3;
  justify-content: center;
}
.qlabel__pick {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--ink-2);
  cursor: pointer;
}
.qlabel__code { width: 62%; max-width: 260px; }
.qlabel__code svg { display: block; width: 100%; height: auto; }
.qlabel__unit { font-size: 26px; font-weight: 800; letter-spacing: -0.01em; line-height: 1.1; }
.qlabel__machine { font-size: 13px; color: #333; min-height: 1em; }
.qlabel__ask { font-size: 14px; line-height: 1.35; margin-top: 4px; }
.qlabel__brand {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: #333;
  margin-top: 6px;
}
.qlabel__brand img { width: 14px; height: 14px; }
.qlabel--skip { opacity: 0.35; }
.qlabel--skip .qlabel__pick { opacity: 1; }

/* The small stock: same card, tighter type, the QR still comfortably
   over the 25mm floor. */
#qlabels[data-size='s'] .qlabel__unit { font-size: 20px; }
#qlabels[data-size='s'] .qlabel__ask { font-size: 12px; }
#qlabels[data-size='s'] .qlabel__machine { font-size: 11px; }
#qlabels[data-size='s'] .qlabel__code { width: 56%; }

/* One label per page. Named pages carry the stock size, so the printer
   dialog defaults to the right paper without any fiddling. */
@page qlabel-l { size: 4in 6in; margin: 0; }
@page qlabel-s { size: 2in 3in; margin: 0; }

@media print {
  .qlabels { display: block; }
  .qlabel {
    page: qlabel-l;
    break-after: page;
    border: none;
    border-radius: 0;
    aspect-ratio: auto;
    width: 4in;
    height: 6in;
    padding: 0.28in 0.2in;
    margin: 0;
    gap: 4px;
  }
  .qlabel:last-child { break-after: auto; }
  .qlabel--skip { display: none; }
  /* The QR at 68mm on the big stock, 38mm on the small — both well past
     the 25mm minimum, quiet zone included in the SVG itself. */
  .qlabel__code { width: 68mm; max-width: none; }
  #qlabels[data-size='s'] .qlabel {
    page: qlabel-s;
    width: 2in;
    height: 3in;
    padding: 0.12in 0.1in;
  }
  #qlabels[data-size='s'] .qlabel__code { width: 38mm; }
  #qlabels[data-size='s'] .qlabel__unit { font-size: 15pt; }
  #qlabels[data-size='s'] .qlabel__ask { font-size: 8pt; }
  #qlabels[data-size='s'] .qlabel__machine { font-size: 7pt; }
  #qlabels[data-size='s'] .qlabel__brand { font-size: 6.5pt; }
  .qlabel__unit { font-size: 26pt; }
  .qlabel__machine { font-size: 11pt; }
  .qlabel__ask { font-size: 13pt; }
  .qlabel__brand { font-size: 9pt; }
}

/* When the label sheet is what's being printed, the app shell must not put
   a single box into the default page context — one stray border or padding
   box prints as a blank letter page before the first label. display:contents
   removes the wrappers' boxes and leaves only the labels themselves. */
@media print {
  body:has(#qlabels) #app,
  body:has(#qlabels) .shell,
  body:has(#qlabels) .content,
  body:has(#qlabels) main {
    display: contents;
  }
  body:has(#qlabels) #demo-strip,
  body:has(#qlabels) #auth-root,
  body:has(#qlabels) #nav-root,
  body:has(#qlabels) #sheet-root,
  body:has(#qlabels) #lightbox-root {
    display: none;
  }
  body:has(#qlabels) { margin: 0; padding: 0; background: #fff; }
}

/* The logo row on the company overview — the office's upload lever. */
.brandrow { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.brandrow__img {
  height: 48px;
  max-width: 200px;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
  background: #fff;
  padding: 4px;
}

/* Photographs that are links (the machine record's strip): same tiles as the
   lightbox grid, navigating instead of zooming. */
.photo-grid--links a {
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--wash-2);
  overflow: hidden;
  aspect-ratio: 1;
}
.photo-grid--links a:hover { transform: scale(1.02); }
