/* ===========================================================================
   ShopApp — visual system
   Organised as: 1) tokens  2) base  3) shell  4) components  5) page sections
   Every accent use goes through --accent so the palette can be retuned in one
   place (the customer plans to tune it later).
   =========================================================================== */

/* ------------------------------------------------------------------ 1. tokens

   THEMING — every colour in this file resolves through a token declared here,
   so a theme is a re-assignment of this block and nothing else. Three states:

     html[data-theme="light"]  → :root as written below (explicit light wins)
     html[data-theme="dark"]   → the dark block in §1b
     no attribute / "system"   → the @media mirror in §1c follows the OS

   Colour audit (2026-08 dark-mode pass). Every literal that used to live
   OUTSIDE :root was promoted to a token; the list is the promotion record, and
   the rule is that nothing below §1c may name a colour again:

     1.  a:hover                       mixed toward #000   → --link-hover-mix
     2.  .nav-icon .badge              color: #fff         → --danger-ink
     3.  .btn-void border              mixed with #fff     → --mix-base
     4.  .btn-void:hover background    mixed with #fff     → --mix-base
     5.  .chip background              12% flag on #fff    → --chip-bg-pct / --mix-base
     6.  .chip color                   72% flag on --text  → --chip-ink-pct / --chip-ink-base
     7.  .chip border                  22% flag on #fff    → --chip-border-pct / --mix-base
     8.  .errors border                mixed with #fff     → --mix-base
     9.  .view-switch-link.is-current   mixed with #fff    → --mix-base
     10. .block border (week view)     mixed with #fff     → --mix-base
     11. .month-cell.is-today ring     mixed with #fff     → --mix-base
     12. .day-block border (day view)  mixed with #fff     → --mix-base

   `--mix-base` is the key one: every "tint this hue down toward the page" mix
   used to hard-code #fff, which inverts wrongly in dark. It now names the
   surface the tint sits on, so the same recipe produces a pale wash on white
   and a deep wash on charcoal. Schedule/board/WO templates were NOT edited for
   dark mode — they inherit it entirely through these tokens, which is the test
   of whether the tokenisation is real.

   Left deliberately un-themed: billing/invoice_print.html — a print stylesheet
   is ink on paper and must stay light regardless of screen theme.             */
:root {
  color-scheme: light;

  /* surfaces */
  --canvas: #f4f6f8;
  --surface: #ffffff;
  --surface-subtle: #f8fafc;
  --surface-hover: #fcfcfd;

  /* lines */
  --border: #eaecf0;          /* card + table outline */
  --border-shell: #e6e9ee;    /* sidebar / topbar */
  --border-control: #e0e4ea;  /* inputs, secondary buttons */
  --border-strong: #d0d5dd;
  --border-dashed: #c9d2e0;
  --row-line: #f2f4f7;        /* row separators inside a card */

  /* ink */
  --text: #101828;
  --text-2: #475467;
  --text-3: #667085;
  --muted: #98a2b3;
  --faint: #c2c9d2;
  --heading-2: #344054;

  /* accent (single source of truth) */
  --accent: #2563eb;
  --accent-ink: #ffffff;
  --accent-tint: #eef3ff;
  --accent-tint-ink: #2a4fc7;

  /* tinted pills */
  --green-bg: #ecfdf3;  --green-ink: #1a7f4b;
  --amber-bg: #fff6e6;  --amber-ink: #b25e09;
  --red-bg:   #fef0f0;  --red-ink:   #c2352b;
  --blue-bg:  #eef3ff;  --blue-ink:  #2a4fc7;
  --gray-bg:  #f2f4f7;  --gray-ink:  #667085;
  --danger: #ef4444;
  --danger-ink: #ffffff;      /* text on a solid --danger badge */

  /* per-technician palette — the calendar's overlay scopes assign these by
     technician pk (views._tech_palette) and cycle at 8. Eight hues that stay
     apart at 6px legend-dot size AND at 16% dilution behind text; the ninth
     tech repeats the first, which is why the legend is mandatory. */
  --tech-1: #2563eb;  --tech-2: #0d9488;
  --tech-3: #16a34a;  --tech-4: #ca8a04;
  --tech-5: #ea580c;  --tech-6: #e11d48;
  --tech-7: #7c3aed;  --tech-8: #64748b;

  /* mixing bases — see the audit note above */
  --mix-base: #ffffff;        /* what a colour tint dilutes toward */
  --link-hover-mix: #000000;  /* a:hover walks the accent this way */

  /* StatusFlag chip recipe (the flag hue arrives inline as --flag) */
  --chip-bg-pct: 12%;
  --chip-border-pct: 22%;
  --chip-ink-pct: 72%;
  --chip-ink-base: var(--text);

  /* shape */
  --radius-card: 12px;
  --radius-ctl: 8px;
  --radius-btn: 9px;
  --radius-sm: 6px;
  --radius-pill: 999px;

  /* depth */
  --shadow-card: 0 1px 2px rgba(16, 24, 40, 0.05);
  --shadow-menu: 0 8px 24px rgba(16, 24, 40, 0.12);
  --shadow-accent: 0 1px 2px color-mix(in srgb, var(--accent) 38%, transparent);
  /* the floating chat window — a real elevation, above --shadow-menu */
  --shadow-float: 0 12px 32px rgba(16, 24, 40, 0.16), 0 2px 6px rgba(16, 24, 40, 0.08);

  /* legacy aliases still referenced by a few rules */
  --bg: var(--surface);
  --column-bg: transparent;

  /* --------------------------------------------------- 1a. dark values
     The dark palette lives here, ONCE, under --dk- names. It is inert: no
     rule reads a --dk- token directly. §1b and §1c below are the only
     consumers, and they are two byte-identical lists of `--x: var(--dk-x)`
     re-assignments — one for the explicit choice, one for "follow the OS".
     Writing the values once is what stops the two blocks drifting apart;
     core/tests/test_theme.py asserts the two lists stay identical.          */

  /* surfaces — a near-black canvas with genuinely raised cards above it, not
     a uniform grey. Cards must read as ON something. */
  --dk-canvas: #0f1520;
  --dk-surface: #161d2b;
  --dk-surface-subtle: #1b2333;
  --dk-surface-hover: #1e2637;

  /* lines — in dark, borders carry the separation that shadows carry in light */
  --dk-border: #28323f;
  --dk-border-shell: #232c3a;
  --dk-border-control: #313d4e;
  --dk-border-strong: #3f4c5e;
  --dk-border-dashed: #3f4c5e;
  --dk-row-line: #202937;

  /* ink */
  --dk-text: #e7ecf3;
  --dk-text-2: #b6c1d1;
  --dk-text-3: #98a6ba;
  --dk-muted: #7a889e;
  --dk-faint: #556274;
  --dk-heading-2: #ccd6e4;

  /* accent — lifted, because #2563eb on #0f1520 is a hole rather than a link */
  --dk-accent: #6d9bff;
  --dk-accent-ink: #0c1322;
  --dk-accent-tint: #1b2742;
  --dk-accent-tint-ink: #a8c1ff;

  /* tinted pills — SOFT backgrounds (a saturated block glares at night), with
     the ink lifted well past the bg so 11.5px bold stays legible */
  --dk-green-bg: #14311f;  --dk-green-ink: #74dda4;
  --dk-amber-bg: #33250e;  --dk-amber-ink: #f0c073;
  --dk-red-bg:   #3a1c1b;  --dk-red-ink:   #f6968f;
  --dk-blue-bg:  #1b2742;  --dk-blue-ink:  #a8c1ff;
  --dk-gray-bg:  #232c3a;  --dk-gray-ink:  #9dabbf;
  --dk-danger: #f4726c;
  --dk-danger-ink: #2a0f0e;

  /* per-technician palette, lifted: the light hues are holes on charcoal, and
     a 16% tint of one is indistinguishable from the card underneath */
  --dk-tech-1: #6d9bff;  --dk-tech-2: #45c6b8;
  --dk-tech-3: #57cc7a;  --dk-tech-4: #e0b64a;
  --dk-tech-5: #f79055;  --dk-tech-6: #f8768e;
  --dk-tech-7: #b18cff;  --dk-tech-8: #9aabc0;

  /* depth — a black drop shadow is invisible on charcoal, so shadows shrink to
     a hint and the --border tokens above do the real work */
  --dk-shadow-card: 0 1px 2px rgba(0, 0, 0, 0.45);
  --dk-shadow-menu: 0 10px 28px rgba(0, 0, 0, 0.6);
  --dk-shadow-float: 0 12px 32px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.35);

  /* mixing bases */
  --dk-mix-base: #161d2b;      /* tints now dilute toward the CARD, not paper */
  --dk-link-hover-mix: #ffffff;

  /* chip recipe: a 12% wash vanishes on charcoal, and darkening the flag hue
     toward --text is backwards when --text is near-white. Dark tints harder
     and lifts the ink toward white instead. */
  --dk-chip-bg-pct: 24%;
  --dk-chip-border-pct: 40%;
  --dk-chip-ink-pct: 50%;
  --dk-chip-ink-base: #ffffff;
}

/* ----------------------------------------------- 1b. dark, chosen explicitly
   KEEP IDENTICAL TO §1c. Add a token here and you must add it there.        */
[data-theme="dark"] {
  color-scheme: dark;
  --canvas: var(--dk-canvas);
  --surface: var(--dk-surface);
  --surface-subtle: var(--dk-surface-subtle);
  --surface-hover: var(--dk-surface-hover);
  --border: var(--dk-border);
  --border-shell: var(--dk-border-shell);
  --border-control: var(--dk-border-control);
  --border-strong: var(--dk-border-strong);
  --border-dashed: var(--dk-border-dashed);
  --row-line: var(--dk-row-line);
  --text: var(--dk-text);
  --text-2: var(--dk-text-2);
  --text-3: var(--dk-text-3);
  --muted: var(--dk-muted);
  --faint: var(--dk-faint);
  --heading-2: var(--dk-heading-2);
  --accent: var(--dk-accent);
  --accent-ink: var(--dk-accent-ink);
  --accent-tint: var(--dk-accent-tint);
  --accent-tint-ink: var(--dk-accent-tint-ink);
  --green-bg: var(--dk-green-bg);
  --green-ink: var(--dk-green-ink);
  --amber-bg: var(--dk-amber-bg);
  --amber-ink: var(--dk-amber-ink);
  --red-bg: var(--dk-red-bg);
  --red-ink: var(--dk-red-ink);
  --blue-bg: var(--dk-blue-bg);
  --blue-ink: var(--dk-blue-ink);
  --gray-bg: var(--dk-gray-bg);
  --gray-ink: var(--dk-gray-ink);
  --danger: var(--dk-danger);
  --danger-ink: var(--dk-danger-ink);
  --tech-1: var(--dk-tech-1);
  --tech-2: var(--dk-tech-2);
  --tech-3: var(--dk-tech-3);
  --tech-4: var(--dk-tech-4);
  --tech-5: var(--dk-tech-5);
  --tech-6: var(--dk-tech-6);
  --tech-7: var(--dk-tech-7);
  --tech-8: var(--dk-tech-8);
  --shadow-card: var(--dk-shadow-card);
  --shadow-menu: var(--dk-shadow-menu);
  --shadow-float: var(--dk-shadow-float);
  --mix-base: var(--dk-mix-base);
  --link-hover-mix: var(--dk-link-hover-mix);
  --chip-bg-pct: var(--dk-chip-bg-pct);
  --chip-border-pct: var(--dk-chip-border-pct);
  --chip-ink-pct: var(--dk-chip-ink-pct);
  --chip-ink-base: var(--dk-chip-ink-base);
}

/* ---------------------------------------------------- 1c. dark, from the OS
   KEEP IDENTICAL TO §1b. The guard is what makes the three states work: an
   explicit `light` opts out of the OS preference, an explicit `dark` is
   already handled above, and everything else (no attribute at all, which is
   what "system" and every anonymous page render) follows prefers-color-scheme. */
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) {
    color-scheme: dark;
    --canvas: var(--dk-canvas);
    --surface: var(--dk-surface);
    --surface-subtle: var(--dk-surface-subtle);
    --surface-hover: var(--dk-surface-hover);
    --border: var(--dk-border);
    --border-shell: var(--dk-border-shell);
    --border-control: var(--dk-border-control);
    --border-strong: var(--dk-border-strong);
    --border-dashed: var(--dk-border-dashed);
    --row-line: var(--dk-row-line);
    --text: var(--dk-text);
    --text-2: var(--dk-text-2);
    --text-3: var(--dk-text-3);
    --muted: var(--dk-muted);
    --faint: var(--dk-faint);
    --heading-2: var(--dk-heading-2);
    --accent: var(--dk-accent);
    --accent-ink: var(--dk-accent-ink);
    --accent-tint: var(--dk-accent-tint);
    --accent-tint-ink: var(--dk-accent-tint-ink);
    --green-bg: var(--dk-green-bg);
    --green-ink: var(--dk-green-ink);
    --amber-bg: var(--dk-amber-bg);
    --amber-ink: var(--dk-amber-ink);
    --red-bg: var(--dk-red-bg);
    --red-ink: var(--dk-red-ink);
    --blue-bg: var(--dk-blue-bg);
    --blue-ink: var(--dk-blue-ink);
    --gray-bg: var(--dk-gray-bg);
    --gray-ink: var(--dk-gray-ink);
    --danger: var(--dk-danger);
    --danger-ink: var(--dk-danger-ink);
    --tech-1: var(--dk-tech-1);
    --tech-2: var(--dk-tech-2);
    --tech-3: var(--dk-tech-3);
    --tech-4: var(--dk-tech-4);
    --tech-5: var(--dk-tech-5);
    --tech-6: var(--dk-tech-6);
    --tech-7: var(--dk-tech-7);
    --tech-8: var(--dk-tech-8);
    --shadow-card: var(--dk-shadow-card);
    --shadow-menu: var(--dk-shadow-menu);
    --shadow-float: var(--dk-shadow-float);
    --mix-base: var(--dk-mix-base);
    --link-hover-mix: var(--dk-link-hover-mix);
    --chip-bg-pct: var(--dk-chip-bg-pct);
    --chip-border-pct: var(--dk-chip-border-pct);
    --chip-ink-pct: var(--dk-chip-ink-pct);
    --chip-ink-base: var(--dk-chip-ink-base);
  }
}

/* -------------------------------------------------------------------- 2. base */
* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
  color: var(--text);
  background: var(--canvas);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: color-mix(in srgb, var(--accent) 80%, var(--link-hover-mix)); }

h1 { font-size: 1.6rem; font-weight: 750; letter-spacing: -0.4px; margin: 0 0 0.9rem; }
h2 { font-size: 0.95rem; font-weight: 700; margin: 0 0 0.7rem; }
h3 { font-size: 0.9rem; font-weight: 700; margin: 0 0 0.5rem; }

.muted { color: var(--text-3); }
.empty, .empty-state, .board-empty { color: var(--text-3); padding: 0.75rem 0; }
.spacer { flex: 1 1 auto; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-ctl);
}
.skip-link:focus { left: 0.5rem; top: 0.5rem; }

/* Alpine cloak — hide toggled forms before Alpine initializes */
[x-cloak] { display: none !important; }

/* ------------------------------------------------------------------- 3. shell */
.app-shell {
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
  background: var(--canvas);
}

/* --- icon sidebar --- */
.app-sidebar {
  position: sticky;
  top: 0;
  flex: 0 0 64px;
  width: 64px;
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 14px 0;
  background: var(--surface);
  border-right: 1px solid var(--border-shell);
  overflow-y: auto;          /* short viewports: the rail scrolls, never clips */
  z-index: 40;
}

.brand {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  margin-bottom: 8px;
  border-radius: 10px;
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 800;
  font-size: 15px;
  letter-spacing: -0.5px;
}
.brand:hover { color: var(--accent-ink); }

.nav-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;              /* 44px touch target */
  height: 44px;
  border-radius: 10px;
  color: var(--text-3);
}
.nav-icon:hover { background: var(--gray-bg); color: var(--text-2); }
.nav-icon.is-active { background: var(--accent-tint); color: var(--accent); }

.nav-icon .badge {
  position: absolute;
  top: 6px;
  right: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--danger);
  color: var(--danger-ink);
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1;
}

.nav-spacer { flex: 1 1 auto; }

/* --- topbar --- */
.app-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-self: stretch;
}

.app-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 60px;
  padding: 0 24px;
  background: var(--surface);
  border-bottom: 1px solid var(--border-shell);
}

.greeting { font-size: 14px; color: var(--text-2); }
.greeting .tenant { color: var(--muted); }

.user-cluster { display: flex; align-items: center; gap: 10px; }

.menu-link {
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
  background: transparent;
  border: 0;
  padding: 8px 6px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.menu-link:hover { background: var(--gray-bg); color: var(--text); }

.logout-form { display: inline; margin: 0; }

/* Theme cycle button — sun / moon / auto, in the topbar user cluster.
   Ghost-styled so it reads as chrome, not as an action. Every authenticated
   user gets it (see the gate note in core/settings_views.appearance). */
.theme-form { display: inline-flex; margin: 0; }
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  min-height: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-3);
  border-radius: var(--radius-ctl);
  cursor: pointer;
}
.theme-toggle:hover { filter: none; background: var(--gray-bg); color: var(--text); }

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-pill);
  background: var(--accent-tint);
  color: var(--accent-tint-ink);
  font-size: 12.5px;
  font-weight: 700;
}

/* --- content canvas --- */
main {
  width: 100%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 22px 24px 32px;
}

/* Data-dense pages (board, schedule, grid, inventory) use the FULL viewport —
   a 2440px monitor should buy the shop extra columns, not side gutters. Text
   pages keep the 1400px reading measure. */
main.main--full { max-width: none; }

/* anonymous pages (login, etc.) — centred card, no chrome */
.app-shell--anon { justify-content: center; }
.app-shell--anon main { max-width: 420px; padding-top: 8vh; }
.app-shell--anon main form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 22px;
}
.app-shell--anon main form p { margin: 0 0 0.9rem; }
.app-shell--anon main form input { width: 100%; }

/* --- responsive: phones get a horizontal icon rail pinned to the top --- */
@media (max-width: 900px) {
  .app-shell { flex-direction: column; align-items: stretch; }

  /* Nav row + topbar stack above the content on phones — give lanes the rest. */
  .board { height: calc(100vh - 230px); height: calc(100dvh - 230px); }

  .app-sidebar {
    position: sticky;
    top: 0;
    flex: 0 0 auto;
    width: 100%;
    height: auto;
    flex-direction: row;
    justify-content: flex-start;
    gap: 2px;
    padding: 6px 8px;
    border-right: 0;
    border-bottom: 1px solid var(--border-shell);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .brand { margin-bottom: 0; margin-right: 6px; flex: 0 0 auto; }
  .nav-icon { flex: 0 0 auto; }
  .nav-spacer { display: none; }

  .app-topbar {
    position: static;
    flex-wrap: wrap;
    gap: 8px;
    padding: 10px 16px;
  }
  .greeting { flex: 1 1 100%; font-size: 13px; }
  .app-topbar .spacer { display: none; }

  main { padding: 16px 14px 28px; }
}

/* -------------------------------------------------------------- 4. components */

/* --- buttons --- */
button,
.btn,
.view-toggle,
.pager a,
.print-link,
.godaddy-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  min-height: 36px;
  padding: 0 14px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--radius-btn);
  cursor: pointer;
  white-space: nowrap;
}
button:hover,
.btn:hover { filter: brightness(1.06); color: var(--accent-ink); }
.view-toggle, .pager a, .print-link, .godaddy-link { text-decoration: none; }

.btn-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-accent);
}

.btn-secondary,
.view-toggle,
.pager a,
.print-link,
.godaddy-link,
.copy-btn,
.btn-edit {
  background: var(--surface);
  border: 1px solid var(--border-control);
  color: var(--text-2);
  font-weight: 550;
  border-radius: var(--radius-ctl);
}
.btn-secondary:hover,
.view-toggle:hover,
.pager a:hover,
.print-link:hover,
.godaddy-link:hover,
.copy-btn:hover,
.btn-edit:hover {
  filter: none;
  background: var(--surface-subtle);
  color: var(--text);
}

/* dashed "add" affordances */
.btn-dashed,
.sku-find-btn,
.add-block-toggle {
  background: transparent;
  border: 1px dashed var(--border-dashed);
  color: var(--accent);
  font-weight: 600;
  font-size: 12.5px;
  min-height: 32px;
  padding: 0 12px;
  border-radius: var(--radius-ctl);
}
.btn-dashed:hover,
.sku-find-btn:hover,
.add-block-toggle:hover {
  filter: none;
  background: var(--accent-tint);
  color: var(--accent);
}

/* ghost icon buttons (complete / reopen / delete row actions) */
.inline button,
.inline-delete button,
.block-delete button {
  min-height: 28px;
  padding: 0 6px;
  background: transparent;
  border: 0;
  color: var(--text-3);
  font-size: 14px;
  line-height: 1;
}
.inline button:hover { filter: none; background: var(--gray-bg); color: var(--text); }
.inline-delete button,
.block-delete button { color: var(--red-ink); }
.inline-delete button:hover,
.block-delete button:hover { filter: none; background: var(--red-bg); }

.inline, .inline-delete, .block-delete { display: inline; margin: 0; }

.btn-void {
  background: var(--red-bg);
  border-color: color-mix(in srgb, var(--red-ink) 25%, var(--mix-base));
  color: var(--red-ink);
}
.btn-void:hover { filter: none; background: color-mix(in srgb, var(--red-ink) 16%, var(--mix-base)); }

.btn-new svg { flex: 0 0 auto; }

/* --- inputs --- */
input,
select,
textarea {
  font: inherit;
  font-size: 13.5px;
  color: var(--text);
  padding: 0.45rem 0.65rem;
  min-height: 36px;
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-ctl);
}
textarea { min-height: auto; }
input:hover, select:hover, textarea:hover { border-color: var(--border-strong); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
}
input[type="checkbox"], input[type="radio"] {
  min-height: 0;
  accent-color: var(--accent);
  width: 16px;
  height: 16px;
  /* Undo the generic input surface/border above. A tick box is painted by the
     UA, and handing it an opaque background muddies the control — visibly so
     in dark, where the box came out a dirty olive. Cleared here, `color-scheme`
     on :root is what makes the native control dark. */
  background: transparent;
  border: 0;
  padding: 0;
}
input[type="search"]::placeholder, input::placeholder { color: var(--muted); }

label { font-size: 13px; color: var(--text-2); }

fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  padding: 14px 18px 18px;
  margin: 0 0 16px;
  box-shadow: var(--shadow-card);
}
legend { font-size: 13px; font-weight: 700; color: var(--heading-2); padding: 0 4px; }

/* --- cards --- */
.card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 12px;
  color: inherit;
  text-decoration: none;
}
.card:hover { color: inherit; }
.card + .card { margin-top: 12px; }
.cards .card + .card { margin-top: 0; }
.card--big { padding: 16px 18px; }

/* --- pills / chips -------------------------------------------------------
   StatusFlag rows carry a per-flag hex colour that the templates pass in as an
   inline `--flag` custom property. The pill derives a tinted background and a
   darkened ink from that single value, so each flag keeps its identity while
   rendering in the modern tinted style (no more solid saturated blocks).

   The three percentages and the two bases are tokens, not literals, because
   the recipe itself has to flip: on white, tint the flag down 12% and darken
   the ink; on charcoal, tint harder (24%) and LIGHTEN the ink toward white,
   or a mid-saturation flag colour disappears into the card.                   */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 21px;
  padding: 0 9px;
  border-radius: var(--radius-pill);
  font-size: 11.5px;
  font-weight: 650;
  line-height: 1;
  white-space: nowrap;
  background: color-mix(in srgb, var(--flag, var(--gray-ink)) var(--chip-bg-pct), var(--mix-base));
  color: color-mix(in srgb, var(--flag, var(--gray-ink)) var(--chip-ink-pct), var(--chip-ink-base));
  border: 1px solid color-mix(in srgb, var(--flag, var(--gray-ink)) var(--chip-border-pct), var(--mix-base));
}
.chip-sm { height: 19px; font-size: 11px; padding: 0 8px; }

/* toggle chips on the WO detail: "off" is a neutral pill with a colour dot */
button.chip { min-height: 24px; height: 24px; cursor: pointer; }
button.chip:hover { filter: brightness(0.98); }
.chip-off {
  background: var(--surface);
  border-color: var(--border-control);
  color: var(--text-3);
}
.chip-off::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--flag, var(--muted));
}

.flag-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 0.5rem; }

/* semantic pills */
.pill {
  display: inline-flex;
  align-items: center;
  height: 21px;
  padding: 0 9px;
  border-radius: var(--radius-pill);
  font-size: 11.5px;
  font-weight: 650;
  background: var(--gray-bg);
  color: var(--gray-ink);
}
.pill-green { background: var(--green-bg); color: var(--green-ink); }
.pill-amber { background: var(--amber-bg); color: var(--amber-ink); }
.pill-red { background: var(--red-bg); color: var(--red-ink); }
.pill-blue { background: var(--blue-bg); color: var(--blue-ink); }

.status-badge {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  font-size: 11.5px;
  font-weight: 650;
  background: var(--gray-bg);
  color: var(--gray-ink);
}
.status-badge.status-issued { background: var(--blue-bg); color: var(--blue-ink); }
.status-badge.status-paid { background: var(--green-bg); color: var(--green-ink); }
.status-badge.status-draft { background: var(--gray-bg); color: var(--gray-ink); }
.status-badge.status-void { background: var(--red-bg); color: var(--red-ink); }

/* QBO sync chip (invoice detail) */
.qbo-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.qbo-chip button { height: 22px; padding: 0 10px; font-size: 11.5px; }
.qbo-chip-error { font-size: 11.5px; }

/* initials badge (schedule blocks, advisor) */
.advisor-badge,
.card-advisor {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 10.5px;
  font-weight: 750;
  letter-spacing: 0.02em;
  line-height: 1;
}
.advisor-badge { min-width: 20px; height: 20px; font-size: 9.5px; }

/* --- errors --- */
.errors {
  list-style: none;
  margin: 0 0 14px;
  padding: 10px 14px;
  background: var(--red-bg);
  border: 1px solid color-mix(in srgb, var(--red-ink) 20%, var(--mix-base));
  border-radius: var(--radius-ctl);
  color: var(--red-ink);
  font-size: 13px;
}
.errors li + li { margin-top: 4px; }

/* --- tables --- */
.grid-table,
.wo-grid,
.invoice-lines,
#wo-lines table,
.payments table,
.credits table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  font-size: 13.5px;
}

.grid-table thead th,
.wo-grid thead th,
.invoice-lines thead th,
#wo-lines table thead th,
.payments table thead th,
.credits table thead th {
  text-align: left;
  padding: 9px 14px;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}
.grid-table thead th a,
.wo-grid thead th a { color: inherit; }

.grid-table tbody td,
.wo-grid tbody td,
.invoice-lines tbody td,
#wo-lines table tbody td,
.payments table tbody td,
.credits table tbody td {
  padding: 11px 14px;
  text-align: left;
  border-top: 1px solid var(--row-line);
  vertical-align: middle;
}
.grid-table tbody tr:first-child td,
.wo-grid tbody tr:first-child td,
.invoice-lines tbody tr:first-child td,
#wo-lines table tbody tr:first-child td { border-top: 0; }

.grid-table tbody tr:hover,
.wo-grid tbody tr:hover,
#wo-lines table tbody tr:hover { background: var(--surface-hover); }

.grid-table thead th:first-child,
.wo-grid thead th:first-child,
.invoice-lines thead th:first-child,
#wo-lines table thead th:first-child { border-top-left-radius: var(--radius-card); }
.grid-table thead th:last-child,
.wo-grid thead th:last-child,
.invoice-lines thead th:last-child,
#wo-lines table thead th:last-child { border-top-right-radius: var(--radius-card); }

.invoice-lines tfoot th,
#wo-lines table tfoot th,
.invoice-lines tfoot td {
  padding: 12px 14px;
  border-top: 1px solid var(--border);
  background: var(--surface-subtle);
  font-size: 13.5px;
  text-align: left;
}
.invoice-lines tfoot tr:last-child th:first-child { border-bottom-left-radius: var(--radius-card); }
.invoice-lines tfoot tr:last-child th:last-child { border-bottom-right-radius: var(--radius-card); }

/* Money columns. These need the same specificity as the grouped cell rules
   above (which set text-align: left), or the alignment never lands. */
.num,
.grid-table tbody td.num,
.wo-grid tbody td.num,
.invoice-lines tbody td.num,
#wo-lines table tbody td.num,
.payments table tbody td.num,
.credits table tbody td.num,
.grid-table thead th.num,
.wo-grid thead th.num,
.payments table thead th.num,
.invoice-lines thead th.num,
#wo-lines table thead th.num,
.invoice-lines tfoot th.num,
.invoice-lines tfoot td.num,
#wo-lines table tfoot th.num { text-align: right; }

/* row actions inside a table cell: small, secondary, never overlapping */
.grid-table tbody td form,
.wo-grid tbody td form { display: inline-block; margin: 0 0 0 8px; vertical-align: middle; }
.grid-table tbody td button,
.grid-table tbody td select,
.wo-grid tbody td button {
  min-height: 30px;
  font-size: 12.5px;
  padding: 0 10px;
  background: var(--surface);
  border: 1px solid var(--border-control);
  color: var(--text-2);
  font-weight: 550;
  border-radius: var(--radius-ctl);
}
.grid-table tbody td button:hover,
.wo-grid tbody td button:hover {
  filter: none;
  background: var(--surface-subtle);
  color: var(--text);
}

.wo-grid { display: block; overflow-x: auto; white-space: nowrap; }
.wo-grid tbody td { font-size: 13px; }
.grid-flags { display: flex; flex-wrap: wrap; gap: 4px; }

/* declined line */
tr.declined td { color: var(--muted); background: var(--surface-hover); }

/* --- description lists (WO header, invoice head) --- */
dl {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px 16px;
  margin: 0 0 12px;
  font-size: 13.5px;
}
dt { color: var(--text-3); font-size: 12.5px; }
dd { margin: 0; color: var(--text); }

/* --- dropdown menus --- */
.col-picker { position: relative; display: inline-block; }

.col-picker-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  min-width: 13rem;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-menu);
}
.col-picker-menu label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 13px;
  color: var(--text-2);
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  cursor: pointer;
}
.col-picker-menu label:hover { background: var(--gray-bg); }

.hidden-note,
.count {
  display: inline-flex;
  align-items: center;
  height: 19px;
  padding: 0 8px;
  border-radius: var(--radius-pill);
  background: var(--gray-bg);
  color: var(--text-2);
  font-size: 11.5px;
  font-weight: 650;
}

/* ----------------------------------------------------------- 5. page sections */

/* --- page toolbars --- */
.grid-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.grid-toolbar h1 { margin: 0; font-size: 1.55rem; }

/* --- board search --- */
.board-search {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
}
.board-search input[type="search"] {
  width: 200px;
  max-width: 45vw;
  height: 30px;
  padding: 0 9px;
  font-size: 13px;
}
.board-search button {
  height: 30px;
  padding: 0 11px;
  font-size: 13px;
}
.search-note {
  font-size: 12.5px;
  color: var(--text-2);
  white-space: nowrap;
}
.search-note a { margin-left: 6px; }

/* --- board --- */
.board {
  display: flex;
  align-items: stretch;
  gap: 14px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 8px;
  /* Pin to the viewport so the horizontal scrollbar is always on screen:
     with hundreds of real jobs, a lane must never grow the page taller than
     the window — each lane scrolls its own cards instead (see .cards). */
  height: calc(100vh - 170px);
  height: calc(100dvh - 170px);  /* dvh: mobile URL bars shrink vh lies */
  min-height: 320px;
}

.column {
  flex: 0 0 264px;
  width: 264px;
  background: transparent;   /* cards float on the canvas */
  border-radius: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;             /* let .cards shrink + scroll inside the lane */
}

.column h2 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 2px 4px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--heading-2);
}
.cards {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 3rem;
  flex: 1 1 auto;
  overflow-y: auto;          /* the lane, not the page, absorbs deep columns */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding-right: 2px;        /* keep card shadows clear of the scrollbar */
}

.board .card { display: flex; flex-direction: column; gap: 8px; }
.board .card:hover { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }

.card-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.card-contact { font-size: 14.5px; font-weight: 650; line-height: 1.3; }
.card-id {
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: 0.4px;
}
.card-vehicle { font-size: 12.5px; color: var(--text-3); }
.card-note { font-size: 12.5px; color: var(--text-2); }

.card-meta {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  font-size: 11.5px;
  color: var(--muted);
}

.card-flags { display: flex; gap: 6px; flex-wrap: wrap; }

.card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 2px;
}
.card-total { font-size: 14px; font-weight: 750; }

/* --- board drag polish -------------------------------------------------------
   The class names below are pinned in board.html's Sortable config; tests
   assert both sides. Scoped to .board .cards so the scheduler tray's own drag
   styling is untouched. Constraint: NOTHING in this section (or on .card) may
   declare a CSS transition — Sortable animates reflow with inline transforms,
   and a competing stylesheet transition makes every move animate twice, which
   is the laggy board this section fixed. Tokens only: the accent box must
   read in light and dark (dark mode is a palette swap, not a rewrite).

   Native-drag subtlety that dictates the rule ORDER here: while dragging,
   ghost + chosen sit on the SAME element (the browser's snapshot is what
   follows the cursor), so the ghost block comes last to win the tie and strip
   the chosen lift off the in-lane placeholder. */

/* Pickup: applied on mousedown, BEFORE the browser snapshots its native drag
   image, so the lift rides along under the cursor. Border + shadow only — no
   scale here: the chosen card still sits inside .cards { overflow-y: auto },
   and a transform poking 2px past the lane edge conjures a horizontal
   scrollbar (a lane-height jump on every pickup where scrollbars take space). */
.board .cards .sortable-chosen {
  cursor: grabbing;
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: var(--shadow-menu);
}

/* Fallback-mode clone (touch / forced-fallback): fixed-positioned by Sortable,
   so the scale lift is safe HERE — it can't overflow the lane. */
.board .cards .sortable-drag {
  opacity: 0.95;
  transform: scale(1.02);
  box-shadow: var(--shadow-menu);
}

/* Insertion preview: the in-lane placeholder keeps the card's exact box (its
   children still occupy layout, just invisible) and renders as a tinted
   dashed accent outline — the "blue box where the card will land". Works the
   same in an empty lane (emptyInsertThreshold in board.html). */
.board .cards .sortable-ghost {
  background: var(--accent-tint);
  border: 1px dashed var(--accent);
  box-shadow: none;
  transform: none;   /* stylesheet lift off; Sortable's inline FLIP still wins */
}
.board .cards .sortable-ghost > * { visibility: hidden; }

/* --- work order detail --- */
.wo-header,
#wo-lines,
#wo-invoices,
#wo-chat,
#wo-tasks,
#wo-schedule,
#shop-chat-summary,
#tasks,
#invoice {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 16px 18px;
  margin-bottom: 18px;
}

.wo-header h1 { font-size: 22px; margin-bottom: 12px; }
.wo-header .btn-edit { margin-top: 4px; }
.wo-header form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  max-width: 520px;
}
.wo-header form label { display: flex; flex-direction: column; gap: 4px; width: 100%; }
.wo-header form textarea { width: 100%; }

#wo-lines > h2,
#wo-invoices > h2,
#wo-chat > h2,
#wo-tasks > h2,
#wo-schedule > h2,
#tasks > h2,
#invoice > h2,
.payments > h2,
.credits > h2,
.paylink > h2 {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 12px;
}

#wo-lines table select { min-height: 32px; font-size: 12.5px; }
#wo-lines table button { min-height: 30px; font-size: 12.5px; padding: 0 10px; }
#wo-lines table td button {
  background: var(--surface);
  border: 1px solid var(--border-control);
  color: var(--text-2);
  font-weight: 550;
  border-radius: var(--radius-ctl);
}
#wo-lines table td button:hover { filter: none; background: var(--surface-subtle); color: var(--text); }

.add-line {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.add-line select { min-width: 260px; }

/* --- work-order breakdown (packages -> parts/labor lines -> money stack) ---
   The table is wider than a phone, so it scrolls inside its own container
   rather than pushing the page sideways.                                     */
.breakdown-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
#wo-lines table.breakdown { min-width: 720px; }

/* Each package is its own <tbody>, so the group reads as a block. */
#wo-lines table.breakdown tbody.pkg + tbody.pkg tr:first-child td {
  border-top: 1px solid var(--border);
}
#wo-lines table.breakdown tr.pkg-head td { background: var(--surface-subtle); }
#wo-lines table.breakdown tr.pkg-head:hover td { background: var(--surface-subtle); }
.pkg-name { font-weight: 650; }
.pkg-desc { display: block; font-size: 12px; margin-top: 2px; max-width: 46ch; }
.pkg-sub { font-variant-numeric: tabular-nums; }
tbody.pkg.declined tr.pkg-head .pkg-name,
tbody.pkg.declined tr.wo-line .line-desc { text-decoration: line-through; }
tbody.pkg.declined { opacity: 0.62; }

/* The lines themselves sit one step in from their package. */
#wo-lines table.breakdown tr.wo-line td.line-item { padding-left: 34px; }
.line-desc { margin: 0 6px; }
.line-part { font-size: 12px; }
.line-extended { font-variant-numeric: tabular-nums; }
tr.wo-line.returned .line-desc,
tr.wo-line.returned .line-part,
tr.wo-line.returned .num { text-decoration: line-through; color: var(--text-3); }
.returned-chip {
  background: var(--amber-bg);
  color: var(--amber-ink);
  border-color: color-mix(in srgb, var(--amber-ink) 22%, var(--mix-base));
}
.returned-by { font-size: 11.5px; margin-left: 6px; }
.kind-labor { --flag: var(--blue-ink); }
.kind-part { --flag: var(--green-ink); }
.kind-sublet { --flag: var(--amber-ink); }
.kind-fee { --flag: var(--gray-ink); }
.row-actions { white-space: nowrap; }
#wo-lines table.breakdown td.row-actions button + button { margin-left: 6px; }

/* Ad-hoc entry: a "+ line" affordance per package, opening an inline form. */
#wo-lines table.breakdown tr.pkg-add td { padding: 6px 14px 10px 34px; }
.add-line-btn {
  background: transparent;
  border: 1px dashed var(--border-dashed);
  color: var(--text-3);
  font-size: 12.5px;
  font-weight: 600;
  border-radius: var(--radius-ctl);
  padding: 0 12px;
}
.add-line-btn:hover { background: var(--surface-subtle); color: var(--text-2); }
.adhoc-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
.adhoc-form label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-3);
}
.adhoc-form label.grow { flex: 1 1 220px; }
.adhoc-form input, .adhoc-form select { font-size: 13px; }
.adhoc-form input[type="number"] { max-width: 110px; }
.link-btn {
  background: transparent;
  border: 0;
  color: var(--text-3);
  font-size: 12.5px;
  padding: 0 6px;
}

/* The money stack. Right-aligned block under the table, like the ticket. */
.money-stack { display: flex; flex-direction: column; align-items: flex-end; margin-top: 14px; }
.money-stack dl { margin: 0; min-width: 280px; }
.stack-row {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding: 4px 0;
  font-size: 13.5px;
}
.stack-row dt { color: var(--text-2); }
.stack-row dd { margin: 0; font-variant-numeric: tabular-nums; }
.stack-subtotal { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 8px; }
.stack-subtotal dt, .stack-subtotal dd { font-weight: 650; color: var(--text); }
.stack-total {
  border-top: 2px solid var(--border-strong);
  margin-top: 4px;
  padding-top: 8px;
  font-size: 15px;
}
.stack-total dt, .stack-total dd { font-weight: 750; color: var(--text); }
.stack-outstanding dt, .stack-outstanding dd { font-weight: 650; color: var(--text); }
.returned-note { font-size: 12px; margin: 8px 0 0; }

.tray-card-total { font-weight: 650; font-variant-numeric: tabular-nums; }

/* Touch targets. A returned part and an ad-hoc price both get typed on an iPad
   at the parts counter, so the controls inside the breakdown hold 44px there. */
@media (pointer: coarse) {
  #wo-lines table.breakdown button,
  #wo-lines table.breakdown select,
  #wo-lines table.breakdown input { min-height: 44px; }
  .add-line-btn { min-height: 44px; }
}

.wo-invoices,
.wo-blocks,
.reminder-rows,
.task-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}
.wo-invoices li,
.wo-blocks li,
.reminder-rows li {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 0;
  border-top: 1px solid var(--row-line);
  font-size: 13.5px;
}
.wo-invoices li:first-child,
.wo-blocks li:first-child,
.reminder-rows li:first-child { border-top: 0; }

.reminder-rows .offset { font-weight: 600; }
.reminder-rows .send-on,
.reminder-rows .tmpl { color: var(--text-3); font-size: 12.5px; }

/* Delivery outcome: the chip tint comes from --flag, same as StatusFlag pills. */
.reminder-sent { --flag: var(--green-ink); }
.reminder-cancelled { --flag: var(--gray-ink); }
.reminder-rows .status-note { color: var(--text-3); font-size: 12px; font-style: italic; }

.hours-summary {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  margin: 0 0 12px;
  font-size: 12.5px;
  color: var(--text-3);
}
.hours-summary strong { color: var(--text); }

.day-n {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: var(--radius-pill);
  background: var(--amber-bg);
  color: var(--amber-ink);
  font-size: 11px;
  font-weight: 650;
}

.schedule-modes { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }

#wo-reminders {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--row-line);
}
#wo-reminders .note { font-size: 12.5px; margin-top: 0; }

.add-task-form,
.add-reminder-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
  margin-top: 10px;
  padding: 12px;
  background: var(--surface-subtle);
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  max-width: 520px;
}
.add-task-form label,
.add-reminder-form label { display: flex; flex-direction: column; gap: 4px; width: 100%; }
.add-reminder-form textarea { width: 100%; }

.day-rows { width: 100%; margin: 10px 0; }
.day-rows th {
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 4px 6px 4px 0;
}
.day-rows td { padding: 3px 6px 3px 0; }

/* --- tasks page --- */
.task-rows li {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 0;
  border-top: 1px solid var(--row-line);
  font-size: 13.5px;
}
.task-rows li:first-child { border-top: 0; }
.task-rows .title { flex: 1 1 auto; min-width: 12rem; }
.task-rows .title.done { color: var(--muted); text-decoration: line-through; }
.task-rows .due { color: var(--text-3); font-size: 12.5px; font-weight: 600; }
.task-rows li.overdue .due { color: var(--red-ink); }
.task-rows .assignee {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: var(--radius-pill);
  background: var(--gray-bg);
  color: var(--gray-ink);
  font-size: 11px;
  font-weight: 650;
}

#tasks .filters { display: flex; gap: 6px; margin: 0 0 14px; }
#tasks .filters a {
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  background: var(--gray-bg);
}
#tasks .filters a.active { background: var(--accent-tint); color: var(--accent); }

.quick-add {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  margin: 0 0 16px;
  padding: 12px;
  background: var(--surface-subtle);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.quick-add input[type="text"] { flex: 1 1 16rem; }

/* --- schedule: week view --- */
.schedule-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}
.schedule-header h1 { margin: 0; }
.schedule-header .week-nav,
.schedule-month .month-nav,
.schedule-day .day-nav { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.schedule-header .tech-filter { margin-left: auto; }

/* "Today" jump: a link while browsed away, an inert chip on the current period */
.today-jump {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-control);
  background: var(--surface);
  font-size: 11.5px;
  font-weight: 650;
  text-decoration: none;
}
a.today-jump:hover { background: var(--surface-subtle); text-decoration: none; }
.today-jump.is-current { color: var(--text-3); opacity: 0.55; cursor: default; }

/* Week | Day | Month switch */
.view-switch { display: inline-flex; gap: 4px; }
.view-switch-link {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 11px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  color: var(--text-2);
  font-size: 12px;
  font-weight: 650;
  text-decoration: none;
}
a.view-switch-link:hover { background: var(--surface-subtle); text-decoration: none; }
.view-switch-link.is-current {
  background: var(--blue-bg);
  border-color: color-mix(in srgb, var(--accent) 22%, var(--mix-base));
  color: var(--accent);
  cursor: default;
}
.schedule-header .tech-filter label,
.schedule-day .day-picker label { display: inline-flex; align-items: center; gap: 6px; }

.schedule-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.schedule-table th,
.schedule-table td {
  border-right: 1px solid var(--row-line);
  border-bottom: 1px solid var(--row-line);
  padding: 0.4rem 0.5rem;
  vertical-align: top;
  font-size: 0.82rem;
}
.schedule-table tr > *:last-child { border-right: 0; }
.schedule-table tbody tr:last-child > * { border-bottom: 0; }
.schedule-table thead th {
  text-align: left;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border);
  color: var(--text-2);
  font-size: 12px;
  font-weight: 700;
}
.schedule-table .tech-name {
  width: 8rem;
  background: var(--surface-subtle);
  color: var(--text);
  font-weight: 650;
}
.schedule-table .closed { background: var(--gray-bg); color: var(--muted); }
.schedule-table .day-cell { height: 4rem; }
.schedule-table .day-cap {
  display: block;
  font-weight: 500;
  font-size: 0.72rem;
  color: var(--text-3);
}
.schedule-table .day-cap.overbooked { color: var(--red-ink); font-weight: 700; }

.block {
  background: var(--blue-bg);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--mix-base));
  border-radius: var(--radius-sm);
  padding: 0.25rem 0.4rem;
  margin-bottom: 0.3rem;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem;
  font-size: 0.78rem;
}
.block-time { font-weight: 650; color: var(--text); }
.block-note { color: var(--text-3); }

/* drop target: the blocks container inside a day cell. Needs a floor height so
   an empty cell is still a target you can hit with a dragged card. */
.cell-drop { min-height: 1.6rem; }
.schedule-table .day-cell.closed .cell-drop { min-height: 0; }
.cell-drop[data-drop] .block { cursor: grab; }

/* --- schedule: unscheduled-work tray --- */
.tray {
  margin-top: 1rem;
  padding: 0.8rem 1rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.tray h2 { margin: 0 0 0.6rem; font-size: 14px; }
.tray-hint { font-weight: 500; color: var(--text-3); font-size: 12.5px; }
/* Not a warning — an instruction. It replaces the "drag onto a day" hint on the
   views where a drop cannot name a technician on its own. */
.tray-hint-gate { color: var(--text-2); }
.tray-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  min-height: 3.4rem;         /* stays droppable once the last card leaves */
  align-content: flex-start;
}
.tray-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 12.5rem;
  padding: 0.45rem 0.6rem;
  background: var(--surface-subtle);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 12.5px;
  color: inherit;
  text-decoration: none;
}
/* Only a card that can actually be placed offers the grab cursor. On the
   date-only views (month, calendar overlay/week) with no ?tech= filter the tray
   still lists and links its work — it just cannot be dragged, and must not
   pretend otherwise. */
.tray-card-drag { cursor: grab; }
.tray-card-drag:active { cursor: grabbing; }
.tray-card:hover { color: inherit; border-color: var(--border-strong); }
.tray-card-top { display: flex; justify-content: space-between; gap: 6px; }
.tray-card-vehicle { color: var(--text-3); }
.tray-card-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.tray-promised { color: var(--text-3); font-size: 11.5px; }
/* An overdue promise is the one thing on a tray card that should stop the eye:
   it is the reason this job jumped the queue. */
.tray-promised.overdue {
  color: var(--red-ink);
  background: var(--red-bg);
  border-radius: var(--radius-pill);
  padding: 1px 7px;
  font-weight: 650;
}
.tray-more { margin: 0.6rem 0 0; font-size: 12px; }

/* search / column / promised / sort row above the cards */
.tray-filters {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0 0 0.7rem;
  font-size: 12.5px;
}
.tray-filters input[type="search"] {
  width: 180px;
  max-width: 45vw;
  height: 28px;
  padding: 0 9px;
  font-size: 12.5px;
}
.tray-filters select { height: 28px; font-size: 12.5px; }
.tray-filters label { display: inline-flex; align-items: center; gap: 5px; }
.tray-filters button { height: 28px; padding: 0 11px; font-size: 12.5px; }
.tray-clear { font-size: 12.5px; }

.add-block-form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-top: 0.4rem;
  padding: 0.5rem;
  background: var(--surface-subtle);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-ctl);
}
.add-block-form label { display: flex; flex-direction: column; gap: 2px; font-size: 11.5px; }
.add-block-form input,
.add-block-form select { min-height: 30px; font-size: 12.5px; padding: 0.25rem 0.45rem; }

/* --- schedule: month view --- */
.month-grid {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.month-grid th,
.month-grid td {
  border-right: 1px solid var(--row-line);
  border-bottom: 1px solid var(--row-line);
  padding: 0.35rem 0.45rem;
  vertical-align: top;
  font-size: 0.8rem;
}
.month-grid tr > *:last-child { border-right: 0; }
.month-grid tbody tr:last-child > * { border-bottom: 0; }
.month-grid thead th {
  text-align: left;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border);
  color: var(--text-2);
  font-size: 12px;
  font-weight: 700;
}
.month-cell { height: 6.5rem; }
.month-cell.closed { background: var(--gray-bg); color: var(--muted); }
.month-cell.out-of-month { background: var(--gray-bg); opacity: 0.45; }
.month-cell.is-today { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 45%, var(--mix-base)); }
.month-day-link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.35rem;
  text-decoration: none;
  color: inherit;
}
.month-day-link:hover { text-decoration: none; }
.month-day-link:hover .month-day-num { color: var(--accent); }
.month-day-num { font-weight: 700; font-size: 12.5px; }
.month-closed { font-size: 0.7rem; }
.month-grid .day-cap { font-size: 0.7rem; font-weight: 500; color: var(--text-3); }
.month-grid .day-cap.overbooked { color: var(--red-ink); font-weight: 700; }
/* Drop target inside a month cell. A <td> cannot be a SortableJS list, so the
   entries sit in this container instead — stretched past the entries it holds
   so a card dropped on the empty half of a square still lands on the day. */
.month-drop { min-height: 4.2rem; }
.month-drop[data-drop] { cursor: copy; }
.month-entry {
  display: flex;
  gap: 0.25rem;
  margin-top: 0.2rem;
  font-size: 0.72rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.month-entry .block-time { font-weight: 650; }
.month-more { display: inline-block; margin-top: 0.2rem; font-size: 0.7rem; color: var(--text-3); }

/* --- schedule: day view --- */
.schedule-day .day-picker { margin-left: auto; }
.day-closed-panel,
.day-empty {
  padding: 2rem;
  text-align: center;
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-card);
}

.day-board {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;          /* mobile: columns scroll horizontally */
  -webkit-overflow-scrolling: touch;
  padding-bottom: 0.5rem;
}
.day-col,
.day-axis {
  flex: 0 0 auto;
  min-width: 12rem;          /* keeps six techs readable; scroll past on phones */
}
.day-axis { min-width: 4.5rem; }
.day-col-head {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.45rem 0.55rem;
  background: var(--surface-subtle);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  min-height: 44px;          /* touch target */
}
.day-col-head .tech-name { font-weight: 650; font-size: 13px; }
.day-col-head .day-cap { font-size: 0.72rem; color: var(--text-3); }
.day-col-head .day-cap.overbooked { color: var(--red-ink); font-weight: 700; }
.axis-head { background: transparent; border-color: transparent; }

.day-grid {
  position: relative;
  display: grid;
  grid-template-rows: repeat(var(--slot-count), 1.75rem);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0 0 var(--radius-card) var(--radius-card);
}
.axis-grid { border: none; background: transparent; }
.slot-line { border-top: 1px solid var(--row-line); grid-column: 1; }
.slot-label {
  grid-column: 1;
  font-size: 0.68rem;
  color: var(--muted);
  padding-right: 0.3rem;
  text-align: right;
}

.day-block {
  grid-column: 1;
  margin: 1px 3px;
  padding: 0.25rem 0.4rem;
  background: var(--blue-bg);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--mix-base));
  border-radius: var(--radius-sm);
  overflow: hidden;
  font-size: 0.75rem;
  /* No min-height: the block's height IS its duration (grid-row span over
     1.75rem slots). Forcing 44px made a 30-minute block overflow its rows and
     cover the next one. The column header carries the touch target instead. */
}
.day-grid[data-drop] .day-block { cursor: grab; }
.day-grid[data-drop] .day-block:active { cursor: grabbing; }
.day-block-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.3rem;
}
.day-block .block-time { font-weight: 650; }
.day-block-wo { display: block; color: var(--text); }
.day-block-wo .wo-tag { color: var(--text-3); font-weight: 650; }
.day-block-note { color: var(--text-3); }
.day-continuity {
  display: inline-block;
  margin-top: 0.15rem;
  font-size: 0.65rem;
  font-weight: 700;
  color: var(--amber-ink);
}

/* --- schedule: calendar view (proportional day sheet) ---
   Sibling of the day view, not a replacement: there a block occupies whole
   30-minute rows, here its height IS its duration. Every top/height/left/width
   percentage arrives from views.schedule_calendar as an inline style; this
   file only supplies the frame, the tints and the type. */
.schedule-calendar .tech-filter { margin-left: auto; }
.cal-summary { margin: 0 0 0.5rem; font-size: 0.8rem; }
.cal-summary .day-cap.overbooked { color: var(--red-ink); font-weight: 700; }
.cal-closed-flag { color: var(--amber-ink); font-weight: 700; }

.cal-scroll {
  overflow-x: auto;              /* phones: columns scroll, ruler stays put */
  -webkit-overflow-scrolling: touch;
  padding-bottom: 0.5rem;
}
.cal-sheet {
  display: grid;
  grid-template-columns: 4.5rem repeat(var(--cols), minmax(12rem, 1fr));
  /* Row 2 is the whole time span. Its exact height is arbitrary — the blocks
     are sized in percent — but ~3.25rem per hour keeps a 30-minute job legible
     without a 9-hour day needing a scroll. */
  grid-template-rows: auto calc(var(--hours) * 3.25rem);
  min-width: min-content;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  /* clip, NOT hidden: hidden makes the sheet a scroll container, which steals
     the sticky ruler's anchor (measured: ruler at -397px under scroll). clip
     rounds the corners without creating a scroller. */
  overflow: clip;
}
.cal-head {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.45rem 0.55rem;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border);
  border-left: 1px solid var(--border);
  min-height: 44px;              /* touch target */
}
.cal-head .tech-name { font-weight: 650; font-size: 13px; }
.cal-head .day-cap { font-size: 0.72rem; color: var(--text-3); }
.cal-head .day-cap.overbooked { color: var(--red-ink); font-weight: 700; }
.cal-ruler-head,
.cal-ruler {
  position: sticky;
  left: 0;
  z-index: 3;
  border-left: none;
}
.cal-ruler-head { font-size: 0.72rem; color: var(--text-3); }
.cal-ruler {
  position: relative;
  position: sticky;
  background: var(--surface);
  border-right: 1px solid var(--border);
}
.cal-hour-label {
  position: absolute;
  right: 0.35rem;
  transform: translateY(-0.55em);
  font-size: 0.68rem;
  color: var(--muted);
  white-space: nowrap;
}
.cal-col {
  position: relative;
  border-left: 1px solid var(--border);
  overflow: hidden;
}
.cal-hour-line {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px solid var(--row-line);
}
.cal-closed-zone {
  position: absolute;
  left: 0;
  right: 0;
  background: var(--surface-subtle);
  opacity: 0.75;
}
.cal-block {
  position: absolute;
  display: block;
  box-sizing: border-box;
  padding: 0.15rem 0.35rem;
  min-height: 1.1rem;            /* a 15-minute block still shows its time */
  background: var(--blue-bg);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--mix-base));
  border-radius: var(--radius-sm);
  font-size: 0.72rem;
  line-height: 1.25;
  color: var(--text);
  overflow: hidden;
  text-decoration: none;
  z-index: 2;
}
.cal-block:hover { text-decoration: none; border-color: var(--accent); }
.cal-block-time { font-weight: 650; display: block; }
.cal-block-main {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-block .wo-tag { color: var(--text-3); font-weight: 650; }
.cal-block-plain { background: var(--surface-subtle); }
.cal-now {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 2px solid var(--red-ink);
  z-index: 4;
}
.cal-now-label {
  position: absolute;
  right: 0;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: -0.25rem;
  border-radius: 50%;
  background: var(--red-ink);
}

/* --- calendar: overlay + week scopes ---
   In an overlay the column stops answering "whose block is this?", so the
   technician moves into the block's colour. The hue comes from the --tech-N
   token the view picked; .cal-tint is the one rule that consumes it, so the
   tint recipe lives in exactly one place for all eight. */
.cal-t1 { --tech: var(--tech-1); }
.cal-t2 { --tech: var(--tech-2); }
.cal-t3 { --tech: var(--tech-3); }
.cal-t4 { --tech: var(--tech-4); }
.cal-t5 { --tech: var(--tech-5); }
.cal-t6 { --tech: var(--tech-6); }
.cal-t7 { --tech: var(--tech-7); }
.cal-t8 { --tech: var(--tech-8); }
.cal-tint {
  background: color-mix(in srgb, var(--tech) 16%, var(--mix-base));
  border-color: color-mix(in srgb, var(--tech) 45%, var(--mix-base));
  border-left: 3px solid var(--tech);   /* survives an ellipsis; a 12px sliver
                                           is still legibly "Dave's" */
}
.cal-tint:hover { border-color: var(--tech); }
.cal-block-tech {
  display: block;
  font-weight: 650;
  color: color-mix(in srgb, var(--tech) 72%, var(--chip-ink-base));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.9rem;
  margin: 0 0 0.5rem;
  padding: 0;
  list-style: none;
  font-size: 0.75rem;
}
.cal-legend-item { display: inline-flex; align-items: center; gap: 0.3rem; }
.cal-legend-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: var(--tech);
  flex: none;
}
.cal-legend-name { font-weight: 650; }
.cal-legend .day-cap { color: var(--text-3); }
.cal-legend .day-cap.overbooked { color: var(--red-ink); font-weight: 700; }

/* Week overlay: seven of these side by side, so the columns give up half their
   width and the type gives up 0.07rem. Density is the honest cost of the view —
   min-height keeps a 15-minute block clickable and the title= attribute on
   every block carries what the ellipsis eats. */
.cal-sheet-week {
  grid-template-columns: 3.4rem repeat(var(--cols), minmax(7.5rem, 1fr));
  grid-template-rows: auto calc(var(--hours) * 3rem);
}
.cal-sheet-week .cal-head { padding: 0.4rem 0.4rem; }
.cal-head-today { background: var(--accent-tint); }
.cal-day-link { font-weight: 650; font-size: 13px; }
.cal-block-sm {
  padding: 0.1rem 0.3rem;
  min-height: 0.95rem;           /* a 15-minute block stays a touch target */
  font-size: 0.66rem;
  line-height: 1.2;
}
/* Use the block's HEIGHT, not one clipped strip: an 8h job is ~300px tall —
   time on its own line, then the customer/WO wrapping to as many lines as fit.
   overflow:hidden on .cal-block clips short blocks naturally, and time renders
   FIRST so whatever survives clipping always includes it. */
.cal-block-sm .cal-block-time { display: block; }
.cal-block-sm .cal-block-main {
  display: block;
  white-space: normal;
  overflow-wrap: break-word;
  text-overflow: clip;
}
.cal-block-sm .wo-tag { font-size: 0.62rem; }
/* Week overlay only: this block can be dragged onto another day column. */
.cal-block-drag { cursor: grab; }
.cal-block-drag:active { cursor: grabbing; }

/* --- inventory + SKU picker --- */
.inventory-panel { margin-top: 14px; }

.inventory-levels {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
}
.inventory-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  background: var(--green-bg);
  color: var(--green-ink);
  font-size: 11px;
  font-weight: 650;
}
.inventory-chip dt { color: inherit; font-size: 11px; font-weight: 700; }
.inventory-chip dd { color: inherit; }
.inventory-truncated,
.inventory-limited,
.inventory-unavailable { font-size: 12.5px; }
.inventory-unavailable,
.sku-results-unavailable { color: var(--amber-ink); }

.sku-picker { position: relative; display: inline-block; }
.sku-picker-search { min-width: 14rem; margin-top: 4px; }
.sku-results {
  display: block;
  list-style: none;
  margin: 4px 0 0;
  padding: 6px;
  max-height: 16rem;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-menu);
  font-size: 12.5px;
}
.sku-results li {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.sku-results li:hover { background: var(--accent-tint); }
.sku-results__sku { font-weight: 700; color: var(--text); }
.sku-results__name { color: var(--text-2); }
.sku-results__cost,
.sku-results__retail { color: var(--muted); }
.sku-results__empty { cursor: default; color: var(--text-3); }
.sku-results__empty:hover { background: transparent; }

/* --- invoices --- */
.invoice-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.invoice-head h1 { margin: 0; font-size: 22px; }
.invoice-head dl { flex: 1 1 100%; margin: 0; }
.invoice-head .print-link { margin-left: auto; }

.payments,
.credits,
.paylink {
  display: block;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--row-line);
}

.paylink-fields {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
  padding: 12px;
  background: var(--surface-subtle);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.paylink-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 13px;
}
.paylink-label { min-width: 6.5rem; color: var(--text-3); font-size: 12.5px; }
.paylink-row code {
  padding: 3px 8px;
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  font-size: 12.5px;
}
.copy-btn { min-height: 30px; font-size: 12.5px; padding: 0 10px; }
.godaddy-link { font-size: 13px; font-weight: 600; }

.void-form { margin-top: 18px; }

/* --- customer (contact) detail + edit -------------------------------------
   Same card treatment as the work order detail: one <section> per subject,
   headings at 15px, rows separated by --row-line. The page is reached
   contextually (inbox thread, WO header) rather than from the nav, so it opens
   with a breadcrumb instead of a tab.                                        */
#customer-header,
#customer-methods,
#customer-vehicles,
#customer-workorders,
#customer-conversations,
#customer-notes,
#customer-edit {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 16px 18px;
  margin-bottom: 18px;
}

#customer-methods > h2,
#customer-vehicles > h2,
#customer-workorders > h2,
#customer-conversations > h2,
#customer-notes > h2 {
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 12px;
}

.crumbs { font-size: 12.5px; color: var(--text-3); margin-bottom: 12px; }
.crumbs a { color: var(--text-2); text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }

/* The name-as-link idiom shared by the inbox rows, the thread header and the
   WO header: reads as text, behaves as a link on hover. */
.contact-link { color: inherit; text-decoration: none; }
.contact-link:hover { text-decoration: underline; }

.customer-header h1 { font-size: 22px; margin-bottom: 10px; }
.customer-header .btn-edit { margin-top: 4px; }
.customer-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--text-3);
  margin-bottom: 4px;
}

/* Pipeline stage, tinted like every other pill on the board. */
.pipeline-new_lead { --flag: var(--blue-ink); }
.pipeline-engaged  { --flag: var(--amber-ink); }
.pipeline-customer { --flag: var(--green-ink); }
.pipeline-lapsed   { --flag: var(--gray-ink); }

/* Consent, the reason this page exists: green means an automated follow-up can
   legally go out, gray means it will be blocked and the cadence step wasted. */
.consent-yes { --flag: var(--green-ink); }
.consent-no  { --flag: var(--muted); }

.method-rows,
.vehicle-rows,
.wo-rows,
.convo-rows {
  list-style: none;
  margin: 0;
  padding: 0;
}
.method-rows li,
.vehicle-rows li,
.wo-rows li,
.convo-rows li {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 0;
  border-top: 1px solid var(--row-line);
  font-size: 13.5px;
}
.method-rows li:first-child,
.vehicle-rows li:first-child,
.wo-rows li:first-child,
.convo-rows li:first-child { border-top: 0; }

.method-rows .kind { color: var(--text-3); }
.method-rows .value { font-weight: 600; min-width: 12rem; }
.method-rows .value a { color: inherit; text-decoration: none; }
.method-rows .value a:hover { text-decoration: underline; }
.method-rows .primary-star { color: var(--amber-ink); }
.vehicle-rows .vin,
.wo-rows .column,
.convo-rows .when { color: var(--text-3); font-size: 12.5px; }
.wo-rows .total { font-weight: 700; margin-left: auto; }

/* A form button that has to sit inline in a row without looking like a button. */
.link-btn {
  background: none;
  border: 0;
  padding: 0;
  min-height: 0;
  color: var(--text-3);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.link-btn:hover { filter: none; background: none; color: var(--accent); text-decoration: underline; }

.add-method {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.add-method label { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; }
.add-method input[type="text"] { min-width: 220px; }

.notes-body { font-size: 13.5px; color: var(--text-2); margin: 0; }

#customer-edit h1 { font-size: 20px; margin-bottom: 12px; }
#customer-edit form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 520px;
}
#customer-edit label { display: flex; flex-direction: column; gap: 4px; width: 100%; }
#customer-edit textarea { width: 100%; }
.form-actions { display: flex; align-items: center; gap: 12px; }

/* --------------------------------------------------------- shop settings
   Owner/manager self-service (/settings/). Every surface here is a list of
   rows where each row is one form, so the layout primitive is a flex card
   rather than a table — see the comment in settings/board.html.              */
.settings-lede { max-width: 62ch; margin: -4px 0 18px; }

.settings-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 0 18px;
  border-bottom: 1px solid var(--border);
}
.settings-tabs a {
  padding: 7px 12px;
  font-size: 13px;
  font-weight: 650;
  color: var(--text-3);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.settings-tabs a:hover { color: var(--text); }
.settings-tabs a.is-active { color: var(--accent); border-bottom-color: var(--accent); }

.settings-hub {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px;
}
.settings-hub-card h2 { margin-bottom: 4px; font-size: 15px; }
.settings-hub-card p { margin: 0; font-size: 13px; }
.settings-hub-card:hover { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }

.settings-note { font-size: 13px; max-width: 68ch; margin: 0 0 12px; }

/* Appearance card. The only card in the hub that IS a form rather than a link
   to a page — the whole setting is three radios, so a subpage would be a
   click with nothing behind it. */
.settings-hub-card--form:hover { border-color: var(--border); }
.theme-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0 0;
}
.theme-choice {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  border: 1px solid var(--border-control);
  border-radius: var(--radius-pill);
  background: var(--surface);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
}
.theme-choice:hover { background: var(--surface-subtle); color: var(--text); }
.theme-choice:has(input:checked) {
  background: var(--accent-tint);
  border-color: color-mix(in srgb, var(--accent) 30%, var(--mix-base));
  color: var(--accent-tint-ink);
}
.theme-choices + button { margin-top: 12px; }

#settings-columns, #settings-flags,
#settings-message-templates, #settings-reminder-templates,
#settings-team { margin-bottom: 28px; }

.settings-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }

.settings-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
}
.settings-row-name { min-width: 200px; flex: 0 1 260px; }
.settings-row-count { font-size: 12.5px; white-space: nowrap; }
.settings-row button[disabled] { opacity: 0.4; cursor: default; }

.settings-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.settings-inline-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
}
.settings-inline-form label { display: flex; flex-direction: column; gap: 4px; }
.settings-inline-form .settings-check { flex-direction: row; align-items: center; height: 36px; }

.settings-add { margin-top: 4px; }
.settings-add .settings-note { margin: 8px 0 10px; }

.settings-template { display: flex; flex-direction: column; gap: 8px; }
.settings-template label { display: flex; flex-direction: column; gap: 4px; }
.settings-template textarea { width: 100%; font-size: 13.5px; }

.settings-table { margin-bottom: 12px; }
.settings-table input[type="time"], .settings-table input[type="number"] { min-width: 108px; }

.settings-editor-row td { background: var(--surface-subtle); }
.settings-editor {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
  padding: 6px 0 10px;
}
.settings-password-form { border-left: 1px solid var(--border); padding-left: 24px; }
.settings-password-form .settings-note { flex: 1 0 100%; margin: 0 0 2px; }

/* Inline show/hide toggle inside password boxes */
.pw-box { position: relative; display: inline-flex; align-items: center; width: 100%; }
.pw-box input { width: 100%; padding-right: 40px; }
.pw-toggle {
  position: absolute;
  right: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.pw-toggle:hover { background: var(--gray-bg); color: var(--text); }

/* Change-password page: stacked fields, capped width */
.pw-change-form { display: flex; flex-direction: column; gap: 12px; max-width: 420px; }
.pw-help { font-size: 12px; }
.pw-help ul { margin: 4px 0 0; padding-left: 18px; }

/* --- shop chat: internal tech <-> writer messaging, pinned to a work order ---
   Sized for the phone first. The pilot's techs will read this in a bay with
   greasy hands, so every tap target here clears 44px and the composer stacks
   rather than shrinking the textarea to a slot.                              */
.shop-chat .note { font-size: 12.5px; margin-top: -6px; }

.chat-chip { --flag: var(--accent); }
h1 > .chat-chip, h2 > .chat-chip { margin-left: 8px; vertical-align: middle; }

.shop-chat-thread {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Long threads scroll inside the card instead of pushing Tasks and Schedule
     off the bottom of the job. */
  max-height: 420px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 2px;
}

.shopmsg {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface-subtle);
  padding: 8px 11px;
}
.shopmsg-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 3px;
  font-size: 12px;
  color: var(--text-3);
}
.shopmsg-author { font-size: 13px; font-weight: 700; color: var(--text); }
.shopmsg-ro { font-weight: 650; color: var(--text-2); }
.shopmsg-body { margin: 0; font-size: 13.5px; color: var(--text); overflow-wrap: anywhere; }

.shop-chat-composer {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  margin-top: 12px;
}
.shop-chat-composer textarea {
  flex: 1 1 auto;
  min-height: 44px;
  font-size: 16px;        /* 16px stops iOS Safari zooming the page on focus */
  resize: vertical;
}
.shop-chat-composer button { min-height: 44px; padding: 0 20px; }

/* --- shop chat: the /shop-chat/ summary board --- */
.shop-chat-compose {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  padding-bottom: 14px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.shop-chat-compose label { display: flex; flex-direction: column; gap: 4px; }
.shop-chat-compose label:first-of-type { flex: 0 1 240px; }
.shop-chat-compose label:last-of-type { flex: 1 1 320px; }
.shop-chat-compose input,
.shop-chat-compose textarea { min-height: 44px; font-size: 16px; width: 100%; }
.shop-chat-compose button { min-height: 44px; padding: 0 20px; }

.shop-chat-rows { list-style: none; margin: 0; padding: 0; }
.shop-chat-rows li + li { border-top: 1px solid var(--border); }
.shop-chat-rows a {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-height: 44px;
  justify-content: center;
  padding: 10px 6px;
  color: inherit;
}
.shop-chat-rows a:hover { background: var(--surface-subtle); }
.row-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}
.row-ro { font-weight: 700; }
.row-contact { color: var(--text-2); }
.row-at { margin-left: auto; font-size: 12px; color: var(--text-3); }
.row-snippet { font-size: 13px; color: var(--text-2); overflow-wrap: anywhere; }

@media (max-width: 640px) {
  .shop-chat-composer { flex-direction: column; align-items: stretch; }
  .shop-chat-composer button { width: 100%; }
  .shop-chat-compose label:first-of-type,
  .shop-chat-compose label:last-of-type { flex: 1 1 100%; }
  .shop-chat-compose button { width: 100%; }
  .row-at { margin-left: 0; }
}

/* Hide-weekends checkbox in the scheduler headers */
.weekend-toggle { display: inline-flex; align-items: center; }
.weekend-toggle label { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-2); white-space: nowrap; cursor: pointer; }

/* --- my day: the technician tablet board ----------------------------------
   Designed for an iPad clamped to a toolbox, read from a metre away by someone
   who is not going to squint. Three rules run the whole section:

     1. 18px base on the lanes. The rest of the app is a 13-14px desk product;
        this one is not read at a desk.
     2. Every tap target clears 44px, including the chat chip, which is the
        ONLY interactive control here (this board is deliberately read-only —
        no status buttons, no clock-in — pending the owner's input).
     3. Tap anywhere on a card. The tap is a stretched overlay link so the chat
        chip can sit above it without nesting anchors.

   Everything resolves through the theme tokens: a bay is bright at 10am and a
   tablet on night shift is not, so this screen has to survive both palettes
   without a second set of colours.                                           */
.tech-head { margin-bottom: 18px; }
.tech-head h1 { margin: 0 0 4px; }
.tech-head-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  margin: 0;
  font-size: 15px;
  color: var(--text-2);
}
.tech-head-date { font-weight: 700; color: var(--text); }
.tech-head-who { color: var(--text-3); }
.tech-head-count { margin-left: auto; font-weight: 650; }

.tech-lane-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 10px;
  font-size: 17px;
}
.tech-lane-count {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-3);
}

.tech-today { margin-bottom: 26px; }
.tech-upnext, .tech-queue { margin-bottom: 26px; }
.tech-queue-group + .tech-queue-group { margin-top: 18px; }
.tech-queue-column {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 750;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* Today stacks full width — one job per row, nothing competing with it. The
   strips (up next, queue) tile, because those are scanned, not worked from. */
.tech-lane { display: flex; flex-direction: column; gap: 14px; }
.tech-strip {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
}

.tech-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 12px 14px;
  font-size: 15px;
}
.tech-card--big {
  padding: 18px 20px;
  font-size: 18px;
  border-left: 5px solid var(--accent);
}
.tech-card:hover { background: var(--surface-hover); }

/* The stretched tap target. z-index 0 puts it over the card's static content
   (which is what makes the whole card tappable) and under .tech-chat, which
   lifts itself to 1. */
.tech-tap {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: var(--radius-card);
}
.tech-tap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tech-card-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  margin-bottom: 4px;
}
.tech-when { font-weight: 750; color: var(--text); }
.tech-card--big .tech-when { font-size: 21px; letter-spacing: -0.01em; }
.tech-ro { font-weight: 700; color: var(--accent); }
.tech-card--big .tech-ro { font-size: 19px; }

/* The one control on the board. Sized past 44px in both directions because it
   is tapped with a work glove on, and lifted above the card's tap overlay. */
.tech-chat {
  position: relative;
  z-index: 1;
  margin-left: auto;
  height: 44px;
  min-width: 66px;
  justify-content: center;
  font-size: 16px;
  text-decoration: none;
}

.tech-contact { font-weight: 650; color: var(--text); }
.tech-card--big .tech-contact { font-size: 20px; }
.tech-vehicle { color: var(--text-2); margin-top: 1px; }

/* What the tech is actually on the hook for: the union of their package-level
   and line-level assignments. Indented under the job because it answers a
   different question from "whose truck is this". */
.tech-work {
  list-style: none;
  margin: 10px 0 0;
  padding: 0 0 0 12px;
  border-left: 2px solid var(--border-strong);
  color: var(--text-2);
}
.tech-work li { padding: 3px 0; }
.tech-card--big .tech-work { font-size: 17px; color: var(--text); }

/* Long parts lists collapse past five lines. The summary must lift above
   .tech-tap (same trick as .tech-chat) or tapping "+ N more" opens the WO
   instead of expanding; glove-sized like every other control on this board. */
.tech-work-more summary {
  position: relative;
  z-index: 1;
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  margin-left: 2px;
  color: var(--accent);
  font-weight: 650;
}
.tech-work-more summary::-webkit-details-marker { display: none; }
.tech-work-more[open] summary { color: var(--text-3); }
.tech-work-more .tech-work { margin-top: 0; }

.tech-card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  margin-top: 10px;
  font-size: 13px;
  color: var(--text-3);
}
.tech-card--big .tech-card-foot { font-size: 15px; }
.tech-column { font-weight: 700; color: var(--text-2); }

.tech-flags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.tech-card--big .tech-flags .chip { height: 26px; font-size: 13px; padding: 0 11px; }

.tech-empty {
  margin: 0;
  padding: 18px 2px;
  font-size: 16px;
  color: var(--text-3);
}

/* --- My Day embedded calendar (the "Schedule" side panel) -------------------
   Two columns on a wide screen: the work list keeps the width it always had
   and the schedule panel takes a fixed lane on the right, sticky below the
   topbar so the day's shape stays in view while the queue scrolls. Under
   ~1000px the panel stacks BELOW the list — the list is what a tech grabs the
   tablet for. The sheet inside reuses the calendar view's pieces (.cal-sheet,
   .cal-col, .cal-block…) so it inherits both palettes for free. */
.tech-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 330px;
  gap: 26px;
  align-items: start;
}
.myday-cal {
  position: sticky;
  top: 74px;                 /* clears the 60px sticky topbar */
  min-width: 0;
}
.myday-cal-switch { margin: 0 0 10px; }
/* Columns may shrink to fit the 330px lane (minmax 0): at week density a
   block keeps its start time and the title= carries what the ellipsis eats,
   exactly like the calendar's week overlay. */
.myday-cal-sheet {
  grid-template-columns: 3.4rem repeat(var(--cols), minmax(0, 1fr));
  grid-template-rows: auto calc(var(--hours) * 3rem);
  min-width: 0;
}
.myday-cal-sheet .cal-head {
  font-size: 12px;
  font-weight: 650;
  padding: 0.35rem 0.4rem;
  min-height: 0;             /* a label, not a touch target */
}

@media (max-width: 1000px) {
  .tech-layout { grid-template-columns: minmax(0, 1fr); }
  .myday-cal { position: static; }
}

@media (max-width: 640px) {
  .tech-head-count { margin-left: 0; flex: 1 1 100%; }
  .tech-card--big { padding: 15px 16px; }
  .tech-strip { grid-template-columns: 1fr; }
}

/* --- shop chat widget on the WO page (Slack-huddle style) -------------------
   Three states, one per User.chat_widget_mode, one class each:

     .floating   fixed top-right, draggable by its header (chat-widget.js)
     .collapsed  the floating window shrunk to its title-bar pill
     .inline     a normal card in the page flow at the bottom of the WO

   The markup sits at the BOTTOM of the page in all three states —
   position:fixed floats it regardless of DOM order, so no re-parenting.
   Internals keep their .shop-chat-* classes: the thread, bubbles and composer
   are the same feature restyled, not a rewrite.                              */
.chat-widget {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  overflow: hidden;
}
.chat-widget.floating,
.chat-widget.collapsed {
  position: fixed;
  z-index: 50;
  top: 64px;
  right: 16px;
  width: min(340px, 78vw);
  border-radius: 14px;
  box-shadow: var(--shadow-float);
  max-height: min(560px, calc(100vh - 84px));
}
.chat-widget.collapsed { max-height: none; width: min(280px, 70vw); }
.chat-widget.collapsed .shop-chat-thread,
.chat-widget.collapsed .shop-chat-composer,
.chat-widget.collapsed .errors { display: none; }
.chat-widget.collapsed .chat-head { border-bottom: none; }
.chat-widget.inline {
  position: static;
  border-radius: var(--radius-card);
  width: 100%;
  max-width: 520px;
  box-shadow: none;
}

/* Corner-drag resize — the desktop floating window ONLY. Scoped to a
   min-width media block rather than fought back in the narrow one: under
   720px every mode renders as the static inline card and must never grow a
   handle. The pill and the inline card keep their own CSS sizes (no resize
   property = none). overflow:hidden on .chat-widget is what makes the native
   handle legal — resize needs non-visible overflow. */
@media (min-width: 720px) {
  .chat-widget.floating:not(.collapsed) {
    resize: both;
    min-width: 280px;
    min-height: 320px;
    max-width: 90vw;
    max-height: calc(100vh - 84px);
  }
  /* Without an explicit height — never resized, and not mid-drag (the handle
     writes inline width/height from its first tick) — the window keeps its
     original content-driven cap instead of ballooning a long thread to the
     full viewport. An explicit height answers only to the viewport ceiling
     above. */
  .chat-widget.floating:not(.collapsed):not([style*="height"]) {
    max-height: min(560px, calc(100vh - 84px));
  }
}

.chat-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--border);
  user-select: none;
  -webkit-user-select: none;
}
/* touch-action:none keeps the browser from claiming the pointer for scroll,
   which is what lets a finger drag the window on a tablet */
.chat-widget.floating .chat-head,
.chat-widget.collapsed .chat-head { cursor: grab; touch-action: none; }
.chat-widget.dragging .chat-head { cursor: grabbing; }
/* .resizing rides a corner drag (chat-widget.js sets it from the observer):
   a width/height transition fighting the native handle is rubber-banding. */
.chat-widget.dragging,
.chat-widget.resizing { transition: none; }

.chat-title {
  margin: 0;
  font-weight: 700;
  font-size: 13.5px;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.chat-ro {
  color: var(--accent-tint-ink);
  background: var(--accent-tint);
  border-radius: var(--radius-sm);
  padding: 1px 7px;
  font-size: 12px;
  white-space: nowrap;
}
.chat-who {
  color: var(--text-3);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-actions { margin-left: auto; display: flex; gap: 2px; }
.chat-actions button {
  border: none;
  background: none;
  color: var(--text-3);
  cursor: pointer;
  width: 28px;
  height: 28px;
  border-radius: 7px;
  font-size: 14px;
  line-height: 1;
  display: grid;
  place-items: center;
  padding: 0;
  min-height: 0;
}
.chat-actions button:hover { background: var(--border-control); color: var(--text); }
.chat-actions button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* .chat-badge, not .badge — the rail's notification badge is a different thing.
   Hidden while the thread is on screen (the messages ARE the notification);
   the collapsed pill is where the count earns its keep. */
.chat-badge {
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 11px;
  font-weight: 800;
  border-radius: var(--radius-pill);
  padding: 1px 7px;
  display: none;
}
.chat-widget.collapsed .chat-badge { display: inline-block; }

/* min-height: 0 lets flex shrink the thread to the widget's explicit height
   (a resized window's height governs; the thread scrolls, the composer stays
   pinned) — and a TALLER window shows more thread, because flex: 1 fills it. */
.chat-widget .shop-chat-thread { flex: 1 1 auto; min-height: 0; overflow-y: auto; max-height: none; padding: 12px; }
.chat-widget.inline .shop-chat-thread { max-height: 320px; }
.chat-widget .errors { margin: 10px 12px 0; }
.chat-widget .shop-chat-composer {
  margin-top: 0;
  padding: 10px 12px;
  border-top: 1px solid var(--border);
}
.chat-widget .shop-chat-composer textarea { min-height: 40px; max-height: 90px; }

@media (prefers-reduced-motion: no-preference) {
  .chat-widget { transition: border-radius 0.18s ease, box-shadow 0.18s ease, width 0.18s ease; }
}

/* Phones don't float well: under 720px every stored mode renders as the inline
   card (chat-widget.js also stands down — no drag, no pref writes — so the
   stored preference survives untouched for the desk browser). */
@media (max-width: 719.98px) {
  .chat-widget.floating,
  .chat-widget.collapsed {
    position: static;
    /* !important: a desktop-resized widget carries inline width/height on
       every render (the server cannot see the viewport), and inline style
       beats any plain declaration. Without these pins a phone renders the
       desk's 800×600 as a fixed-size card and the page scrolls sideways. */
    width: 100% !important;
    height: auto !important;
    max-width: none;
    border-radius: var(--radius-card);
    box-shadow: none;
    max-height: none;
  }
  .chat-widget.collapsed .shop-chat-thread { display: flex; }
  .chat-widget.collapsed .shop-chat-composer { display: flex; }
  .chat-widget.collapsed .errors { display: block; }
  .chat-widget.collapsed .chat-head { border-bottom: 1px solid var(--border); }
  /* the widget no longer bounds the thread once it is static, so the thread
     re-caps itself — same figure as the inline card */
  .chat-widget .shop-chat-thread { max-height: 320px; }
  /* a count next to a visible thread is noise — the badge is the pill's */
  .chat-widget.collapsed .chat-badge { display: none; }
  .chat-widget .chat-actions { display: none; }
  .chat-widget .chat-head { cursor: default; touch-action: auto; }
}

/* --- work-order photos ---------------------------------------------------
   Tile thumbnails are the ORIGINAL file CSS-cropped to a square — there is
   no server-side thumbnail pipeline yet, so a page of 15MB uploads is heavy
   on cell data (ledgered; loading=lazy softens it). Colors are all tokens,
   so light/dark ride the existing theme switch for free. */
#wo-photos .photo-count {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-3);
  background: var(--gray-bg);
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  vertical-align: 2px;
}
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 12px;
  margin: 8px 0 12px;
}
.photo-tile { margin: 0; min-width: 0; }
.photo-tile > a { display: block; }
.photo-tile img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-subtle);
}
.photo-tile figcaption {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-2);
}
.photo-comment {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.photo-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
  color: var(--text-3);
}
.photo-by {
  font-weight: 600;
  font-size: 10.5px;
  color: var(--text-2);
  background: var(--gray-bg);
  border-radius: var(--radius-pill);
  padding: 1px 6px;
}
.photo-visible-chip { background: var(--green-bg); color: var(--green-ink); }
.photo-delete {
  margin-left: auto;
  min-height: 28px;
  min-width: 28px;
  padding: 0 6px;
  background: transparent;
  border: 0;
  color: var(--red-ink);
  font-size: 13px;
  line-height: 1;
}
.photo-delete:hover { filter: none; background: var(--red-bg); }
.photo-visibility {
  margin-top: 4px;
  width: 100%;
  min-height: 30px;
  padding: 0 8px;
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-ctl);
  color: var(--text-2);
  font-size: 11.5px;
  font-weight: 550;
}
.photo-visibility:hover { filter: none; background: var(--surface-subtle); color: var(--text); }
.photo-visibility.on { background: var(--green-bg); border-color: color-mix(in srgb, var(--green-ink) 25%, var(--mix-base)); color: var(--green-ink); }
.photo-upload {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: stretch;
}
.photo-upload input[type="file"] { min-height: 44px; flex: 1 1 240px; }
.photo-upload textarea { flex: 1 1 100%; }
.photo-upload button[type="submit"] { min-height: 44px; padding: 0 16px; }
/* fingers, not cursors: every photo control clears 44px on touch screens */
@media (pointer: coarse) {
  .photo-delete { min-height: 44px; min-width: 44px; }
  .photo-visibility { min-height: 44px; }
}
