/* ═══════════════════════════════════════════════════════════════════════════
   West Penn Vehicle Specialist — Application Stylesheet
   ═══════════════════════════════════════════════════════════════════════════
   Editorial × tech aesthetic, dark, cyan accent, Rajdhani display + DM Sans body.
   Matches ClientBridgeUSA's design system so the brand reads consistently.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Polish pass, 8 Aug 2026 ────────────────────────────────────────────
   The original was built overnight for a demo and read like it: 14px body,
   11px buttons, 9px labels, flat surfaces, and tap targets around 26px on a
   phone. This pass keeps the identity — dark, cyan, Rajdhani display — and
   fixes the execution.

   Rules that hold everywhere below:
     • Nothing interactive is smaller than 44px on a touch screen.
     • Body text has a floor of 13px; anything smaller is a diagram label,
       not typography.
     • Form controls are 16px on mobile. Anything less and iOS Safari zooms
       the page on focus, which feels broken.
     • Every focusable thing gets a visible ring. Colour alone was the only
       focus signal before, which fails for anyone who tabs.
     • Surfaces get real elevation. Flat-on-flat was the single biggest
       reason it read as cheap.
   ────────────────────────────────────────────────────────────────────── */

:root {
  /* Color tokens.
     Neutrals carry a faint blue undertone now (#0b0c0e rather than #0a0a0a).
     A pure-grey dark UI reads as unfinished; the cast is barely perceptible
     on its own but it is what makes the cyan sit in the palette instead of
     floating on top of it. */
  --black: #0b0c0e;
  --deep: #070809;
  --card: #141619;
  --card-light: #1a1d21;
  --card-lighter: #22262b;
  --border: #24282e;
  --border-light: #2f343b;
  --border-lighter: #3c424a;
  --white: #f4f6f8;
  --muted: #9aa3ad;            /* lifted from #888 — 4.5:1 on --card */
  --muted-light: #c2c9d1;      /* lifted from #aaa */
  --muted-dark: #6b747e;       /* lifted from #555, which was unreadable */
  --accent: #22e0f5;           /* cyan, a touch deeper so white text can sit on it */
  --accent-dim: #0e8ea6;
  --accent-soft: rgba(34, 224, 245, 0.12);

  /* ── Primary button finish ────────────────────────────────────────────
     Kian, 2 Sep 2026: "Make the button highlights/gradient an option. I
     personally dont like it, but some may."

     ⚠ THE DEFAULT HERE IS THE GRADIENT, because that is what this
     stylesheet has always drawn. Every setting in this app ships at the
     value the code already used, so a shop that never touches it sees no
     change. brand.button_gradient = false stamps data-buttons="flat" on
     the root and these three tokens swap.

     The fallbacks inside the gradient ARE the previous literals, so an
     unbranded shop renders identically — --accent-bright is only set once
     a brand colour is configured. */
  --btn-bg: linear-gradient(180deg, var(--accent-bright, #3ae8fb), var(--accent));
  --btn-bg-hover: linear-gradient(180deg, var(--accent-bright, #5cedfc), var(--accent));
  --btn-sheen: inset 0 1px 0 rgba(255, 255, 255, 0.25);
  --accent2: #8b45ff;          /* purple */
  --green: #22e08a;
  --warn: #ffb224;
  --danger: #ff5a72;

  /* Elevation. Three steps, used consistently: resting cards, raised
     surfaces (dropdowns, sticky bars), and overlays (modals, drawers). */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);
  --shadow-xl: 0 24px 64px rgba(0, 0, 0, 0.65), 0 4px 12px rgba(0, 0, 0, 0.5);
  --ring: 0 0 0 3px rgba(34, 224, 245, 0.35);

  /* ── The section mark ──────────────────────────────────────────────────
     A Pennsylvania keystone — the shape in West Penn's own logo — drawn as a
     mask so it takes the colour of whatever it sits beside.

     ⚠ A BRAND TOKEN, NOT A HARD-CODED SHAPE, and that is the whole reason it
     lives up here. CONFIG-LAYER.md exists to get West Penn out of a second
     shop's software; a keystone welded into shared chrome would be that same
     problem wearing a nicer hat. A shop with its own mark overrides this one
     declaration and every page follows it. A shop with none sets it to
     `none` and the rule below draws nothing at all.

     Deliberately NOT the logo file: at 8px a raster is mush, and a mask
     inherits the text colour instead of fighting it. */
  --brand-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8.6 2.8h6.8l5.6 5.4-2.9 12.9H5.9L3 8.2z'/%3E%3C/svg%3E");

  /* Type scale. Named by role, not by size, so a future adjustment is one
     edit rather than a search for "12px". */
  --text-xs: 12px;   /* the floor for real text — badges, table meta */
  --text-sm: 13.5px; /* secondary text, table cells */
  --text-base: 15px; /* body */
  --text-md: 16px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 28px;
  --text-3xl: 34px;

  /* Spacing */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;

  /* The minimum any finger should have to hit. */
  --tap: 44px;

  /* Light-color markers for diagrams */
  --light-red: #ff3333;
  --light-blue: #3366ff;
  --light-amber: #ffaa00;
  --light-white: #ffffff;
  --light-red-blue: #9933ff;
  --light-green: #00cc66;

  /* Typography */
  --font-display: 'Rajdhani', 'Inter', sans-serif;
  --font-body: 'DM Sans', 'Inter', sans-serif;
  --font-serif: 'Instrument Serif', Georgia, serif;
  --font-mono: 'JetBrains Mono', 'Fira Code', Consolas, monospace;

  /* Sizing. Radii went up across the board — 6px on a 40px-tall button reads
     as an accident; 10px reads as a decision. */
  --header-height: 60px;
  --mobile-header-height: 58px;
  --sidebar-width: 252px;
  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 14px;
  --radius-xl: 20px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  background: var(--black);
  color: var(--white);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.55;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Stops a long doc number or VIN from pushing the whole page sideways on a
   phone — horizontal scroll was the most common mobile complaint. */
body { overflow-x: hidden; }

a { color: var(--accent); text-decoration: none; transition: color 0.15s; }
a:hover { color: var(--white); }

/* ⚠ Focus. Previously the only focus signal was a border-colour change on
   inputs, and nothing at all on buttons and links — unusable by keyboard.
   :focus-visible keeps the ring off mouse clicks. */
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--radius-sm);
}

/* Respect a user who has asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Grain overlay — subtle */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1000;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.4'/%3E%3C/svg%3E");
  opacity: 0.025;
  mix-blend-mode: overlay;
}

/* ─── Typography utilities ─────────────────────────────────────────────── */
h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.15;
}
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-xl); }
/* h3 was 16px UPPERCASE with letter-spacing in a condensed face — the least
   readable combination in the whole stylesheet, used for section headings.
   Now sentence case at a normal tracking. */
h3 {
  font-size: var(--text-lg);
  color: var(--muted-light);
  letter-spacing: -0.005em;
}
.serif-em { font-family: var(--font-serif); font-style: italic; font-weight: 400; }

.mono { font-family: var(--font-mono); }
.muted { color: var(--muted); }
.muted-light { color: var(--muted-light); }
.accent { color: var(--accent); }
.green { color: var(--green); }
.danger { color: var(--danger); }
.warn { color: var(--warn); }

/* ─── Layout shell ─────────────────────────────────────────────────────── */
.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-width) 1fr;
  min-height: 100vh;
}
/* ⚠ A grid item defaults to min-width:auto, which means it REFUSES to shrink
   below the widest thing inside it. With a 620px-min table in .main that made
   the whole page 620px wide on a 375px phone — the layout slid sideways and
   the table-wrap's own overflow-x never engaged, because the wrap was never
   the thing being squeezed. min-width:0 lets .main shrink so the scrolling
   happens INSIDE the table box, which is the whole point of .table-wrap.
   Measured: eight pages overflowed before this line. */
.app-shell > *, .main { min-width: 0; }

/* ⚠ THE NAV SCROLLS; THE FOOTER DOES NOT. The whole sidebar used to be the
   scroll region, and the admin nav alone is ~1,470px tall — taller than any
   monitor — so the footer holding Sign Out sat about a thousand pixels below
   the fold on every admin page and was reached only by scrolling the
   sidebar. Kian, 12 Sep 2026: "the sign out button disappears". Brand and
   footer are pinned; the nav is the one flex child that shrinks and scrolls. */
.sidebar {
  background: var(--deep);
  border-right: 1px solid var(--border);
  padding: 20px 0;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.sidebar-brand {
  flex-shrink: 0;
  padding: 0 24px 24px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}
.sidebar-brand .logo {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.02em;
  line-height: 1;
}
.sidebar-brand .logo .accent { color: var(--accent); }
.sidebar-brand .sub {
  font-size: 12px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  margin-top: 4px;
}

.sidebar-nav {
  flex: 1 1 auto;
  min-height: 0;               /* a flex child refuses to shrink below its content without this */
  overflow-y: auto;
  padding: 0 12px 8px;
  scrollbar-width: thin;
  scrollbar-color: var(--border-light) transparent;
}
.sidebar-nav .group-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 600;
  color: var(--muted-dark);
  padding: 18px 12px 8px;
}
.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 12px;
  min-height: var(--tap);
  margin-bottom: 2px;
  border-radius: var(--radius);
  color: var(--muted-light);
  font-size: var(--text-sm);
  font-weight: 500;
  transition: background 0.15s, color 0.15s;
}
.sidebar-nav a:hover {
  background: var(--card-light);
  color: var(--white);
}
/* The active item used to shift 2px left because the border was added
   without compensating the padding, so switching pages nudged the whole
   list. It is an inset pseudo-element now, which changes nothing in flow. */
/* Sub-views of Documents. The indent was 34px against a 12px parent, and
   that 22px step read as a ragged left edge rather than as hierarchy. Pulled
   in, with a hairline rail connecting the group so the offset looks deliberate.

   The rail is a background gradient rather than a pseudo-element because
   ::before is already the active-item accent bar, and an active sub-item needs
   both. Sized slightly over 100% so the 2px gap between rows does not dash it. */
.sidebar-nav a.nav-sub {
  padding-left: 30px;
  background-image: linear-gradient(var(--border-light), var(--border-light));
  background-size: 1px calc(100% + 2px);
  background-position: 15px 0;
  background-repeat: no-repeat;
}
/* The active state paints its own background, which would wipe the rail out —
   it has the accent bar instead, so that is fine and deliberate. */
.sidebar-nav a.nav-sub:hover { background-position: 15px 0; }

.sidebar-nav a.active {
  position: relative;
  background: linear-gradient(90deg, var(--accent-soft), transparent 70%);
  color: var(--white);
  font-weight: 600;
}
.sidebar-nav a.active::before {
  content: '';
  position: absolute;
  left: -12px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 22px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}
.sidebar-nav a.active svg { color: var(--accent); }
.sidebar-nav a.coming-soon {
  opacity: 0.4;
  pointer-events: none;
}
.sidebar-nav a.coming-soon::after {
  content: 'SOON';
  margin-left: auto;
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--muted-dark);
}
.sidebar-nav svg { width: 16px; height: 16px; stroke-width: 1.75; }

.nav-badge {
  margin-left: auto;
  background: var(--danger);
  color: var(--white);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  padding: 2px 7px;
  border-radius: 999px;
  min-width: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  letter-spacing: 0;
}

.sidebar-footer {
  flex-shrink: 0;
  padding: 16px 24px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}
.sidebar-footer .user-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.sidebar-footer .avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--card-lighter);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 13px;
  color: var(--black);
}
.sidebar-footer .who { flex: 1; min-width: 0; }
.sidebar-footer .who .name { color: var(--white); font-size: 13px; font-weight: 500; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-footer .who .role { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; }
.sidebar-footer .signout {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: 12px;
  padding: 11px 12px;
  min-height: var(--tap);
  background: var(--card-light);
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  color: var(--muted-light);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  text-align: left;
}
.sidebar-footer .signout svg { width: 16px; height: 16px; flex-shrink: 0; }
.sidebar-footer .signout:hover {
  color: var(--white);
  border-color: var(--accent);
  background: var(--card);
}

/* ─── Main content area ───────────────────────────────────────────────── */
.main {
  padding: 32px 40px;
  min-height: 100vh;
}
.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 28px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--border);
}
.page-header .crumb {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 8px;
}

/* The keystone ahead of every page's section label. Small, quiet, and the one
   piece of this interface that is a decision rather than a default.

   ⚠ MASKED, not an <img> and not a background-image. A mask takes
   `currentColor`, so the mark dims with the label it belongs to instead of
   sitting there at full strength next to grey text.

   ⚠ DECORATIVE ONLY. It is a ::before with empty content, so a screen reader
   passes over it — which is right. The label already says what the section
   is; a keystone read aloud is noise.

   ⚠ DEGRADES TO NOTHING. The colour is applied THROUGH the mask rather than
   painted behind it, so a browser without mask support shows the label alone
   rather than a solid rectangle. */
.page-header .crumb::before {
  /* ⚠ HIDDEN BY DEFAULT, shown only where masks work — see the @supports
     below. This is the safe direction round: the colour is painted as a
     BACKGROUND and cut to shape by the mask, so a browser that ignores the
     mask would otherwise render a solid cyan RECTANGLE next to every page
     title. Hiding after the fact cannot work, because there is no negative
     test that reliably detects it. */
  content: "";
  display: none;
  width: 13px;
  height: 16px;
  margin-right: 8px;
  vertical-align: -3px;
  /* ⚠ The accent, not currentColor. A mark in the same grey as the label it
     precedes reads as a bullet; in the brand colour it reads as a mark, which
     is the entire point of putting it there. Held slightly back at 85% so it
     sits with the label rather than shouting over it. */
  background-color: var(--accent);
  opacity: 0.85;
  -webkit-mask-image: var(--brand-mark);
  mask-image: var(--brand-mark);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

@supports (mask-image: var(--brand-mark)) or (-webkit-mask-image: var(--brand-mark)) {
  .page-header .crumb::before { display: inline-block; }
}

/* ⚠ A shop that sets --brand-mark: none gets no mark AND no gap, rather than
   an 8px hole where one used to be. Nobody should be able to tell that a
   marker was ever meant to be there. */
:root[style*="--brand-mark: none"] .page-header .crumb::before,
:root[style*="--brand-mark:none"] .page-header .crumb::before { display: none; }
.page-header h1 { font-size: var(--text-2xl); }
.page-header .meta { font-size: var(--text-sm); color: var(--muted); margin-top: 7px; }
/* Buttons in a page header wrap instead of overflowing on a narrow screen. */
/* The ACTIONS side of a page header, so buttons sit in a row and wrap on a
   narrow screen.
   ⚠ :not(:first-child) is load-bearing. Without it, a header with no buttons
   has exactly one child — the crumb/title/meta block — and :last-child
   matched THAT, laying the title out in a row beside its own breadcrumb.
   Reports, Account and Time Corrections all looked wrong; Documents did not,
   purely because it happens to have a New Estimate button. */
.page-header > div:last-child:not(:first-child) { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* ─── Cards & containers ──────────────────────────────────────────────── */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  box-shadow: var(--shadow);
}
/* A one-pixel highlight along the top edge. This is the cheapest trick in
   dark UI: it suggests a light source, so cards read as physical objects
   rather than as differently-coloured rectangles. */
.card {
  position: relative;
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 90px);
}
.card-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-md);
  letter-spacing: 0.005em;
  color: var(--white);
  margin-bottom: 16px;
}

/* ─── Tables ──────────────────────────────────────────────────────────── */
.table-wrap {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow);
  /* A wide table scrolls INSIDE its own box instead of widening the page.
     Combined with body{overflow-x:hidden} this is what stops the whole
     layout sliding sideways on a phone. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
table.t {
  width: 100%;
  border-collapse: collapse;
}
table.t th, table.t td {
  padding: 14px 18px;
  text-align: left;
  font-size: var(--text-sm);
  border-bottom: 1px solid var(--border);
}
table.t th {
  background: var(--card-light);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted-light);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}
table.t tbody tr { cursor: pointer; transition: background 0.12s; }
table.t tbody tr:hover { background: var(--card-light); }
table.t tbody tr:last-child td { border-bottom: none; }
/* Row actions sit in a flex row so three buttons (Edit / Disable / Delete)
   stay on one line and keep their spacing. */
table.t td .btn + .btn { margin-left: 6px; }
table.t .num { text-align: right; font-variant-numeric: tabular-nums; }
table.t .col-narrow { width: 1%; white-space: nowrap; }

/* ─── Buttons ─────────────────────────────────────────────────────────── */
/* ⚠ Buttons were 13px UPPERCASE at 9px vertical padding — about 33px tall,
   and .btn-sm was 26px. Both are well under the 44px a fingertip needs, and
   uppercase condensed text at 11px is the hardest thing here to read.
   Sentence case, real height, real weight. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 18px;
  min-height: 40px;
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-md);
  letter-spacing: 0.01em;
  cursor: pointer;
  border: 1px solid transparent;
  background: var(--card-light);
  color: var(--white);
  transition: background 0.15s, border-color 0.15s, transform 0.08s, box-shadow 0.15s;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}
.btn:hover { background: var(--card-lighter); border-color: var(--border-light); }
/* A 1px press. Small enough to feel mechanical rather than bouncy. */
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}
/* Flat: one solid colour, no top sheen. Hover lightens the whole button
   rather than shifting a gradient stop, which works whether or not a brand
   colour is set — there is no --accent-bright to reach for when a shop has
   configured nothing. */
:root[data-buttons="flat"] {
  --btn-bg: var(--accent);
  --btn-bg-hover: var(--accent);
  --btn-sheen: 0 0 transparent;
}
:root[data-buttons="flat"] .btn-primary:hover { filter: brightness(1.12); }

.btn-primary {
  /* ⚠ The fallbacks ARE the previous literals, so an unbranded shop renders
     identically. --accent-bright and --accent-text are set by applyBrand()
     only when a brand is configured. */
  background: var(--btn-bg);
  color: var(--accent-text, #04252b);
  font-weight: 700;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), var(--btn-sheen);
}
.btn-primary:hover { background: var(--btn-bg-hover); color: var(--accent-text, #04252b); border-color: transparent; }
.btn-ghost {
  background: transparent;
  border: 1px solid var(--border-light);
  color: var(--muted-light);
}
.btn-ghost:hover { color: var(--white); border-color: var(--border-lighter); background: var(--card-light); }
.btn-success { background: var(--green); color: var(--black); }
.btn-success:hover { background: #33ff99; color: var(--black); }
.btn-danger { background: transparent; border: 1px solid var(--danger); color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: var(--white); }
.btn-warn { background: var(--warn); color: var(--black); }
.btn-warn:hover { background: #ffbb33; color: var(--black); }
.btn-sm { padding: 8px 14px; min-height: 36px; font-size: var(--text-sm); }
.btn-lg { padding: 14px 26px; min-height: 52px; font-size: var(--text-lg); border-radius: var(--radius-lg); }
.btn svg { width: 16px; height: 16px; stroke-width: 2; flex-shrink: 0; }

/* ─── Forms ──────────────────────────────────────────────────────────── */
/* Labels keep the uppercase treatment — on a short word above a field it
   reads as structure rather than as body copy — but at a legible size. */
label {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted-light);
  margin-bottom: 7px;
}
input[type="text"], input[type="email"], input[type="password"], input[type="tel"],
input[type="number"], input[type="date"], input[type="search"], select, textarea {
  width: 100%;
  background: var(--card-light);
  border: 1px solid var(--border-light);
  color: var(--white);
  padding: 11px 14px;
  min-height: var(--tap);
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-size: var(--text-base);
  transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
input::placeholder, textarea::placeholder { color: var(--muted-dark); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--card-lighter);
  box-shadow: var(--ring);
}
textarea { resize: vertical; min-height: 96px; line-height: 1.55; }

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
}
.form-grid .full { grid-column: 1 / -1; }
.form-actions {
  margin-top: 20px;
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

/* ─── Status pills ────────────────────────────────────────────────────── */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid;
}
.pill::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}

/* Document type-specific colors */
.pill-estimate     { color: var(--accent);    border-color: rgba(0, 229, 255, 0.3); background: rgba(0, 229, 255, 0.06); }
.pill-sales_order  { color: var(--warn);      border-color: rgba(255, 170, 0, 0.3);  background: rgba(255, 170, 0, 0.06); }
.pill-work_order   { color: var(--accent2);   border-color: rgba(123, 47, 255, 0.3); background: rgba(123, 47, 255, 0.06); }
.pill-invoice      { color: var(--green);     border-color: rgba(0, 255, 136, 0.3);  background: rgba(0, 255, 136, 0.06); }
.pill-warranty_hold{ color: var(--danger);    border-color: rgba(255, 68, 102, 0.3); background: rgba(255, 68, 102, 0.06); }

.pill-status-draft, .pill-status-pending, .pill-status-assigned { color: var(--muted-light); border-color: var(--border-light); background: var(--card-light); }
.pill-status-sent, .pill-status-in_progress, .pill-status-scheduled { color: var(--warn); border-color: rgba(255, 170, 0, 0.3); background: rgba(255, 170, 0, 0.06); }
.pill-status-accepted, .pill-status-completed, .pill-status-pushed { color: var(--green); border-color: rgba(0, 255, 136, 0.3); background: rgba(0, 255, 136, 0.06); }
.pill-status-paid { color: var(--green); border-color: rgba(0, 255, 136, 0.5); background: rgba(0, 255, 136, 0.12); }
.pill-status-declined, .pill-status-cancelled, .pill-status-void { color: var(--danger); border-color: rgba(255, 68, 102, 0.3); background: rgba(255, 68, 102, 0.06); }
.pill-status-pending_qbo_push { color: var(--accent); border-color: rgba(0, 229, 255, 0.3); background: rgba(0, 229, 255, 0.06); }

/* Preset layout scope pills */
.pill-system-scope   { color: var(--accent2);   border-color: rgba(123, 47, 255, 0.3); background: rgba(123, 47, 255, 0.06); }
.pill-customer-scope { color: var(--accent);    border-color: rgba(0, 229, 255, 0.3);  background: rgba(0, 229, 255, 0.06); }
.pill-neutral        { color: var(--muted-light); border-color: var(--border-light);   background: var(--card-light); text-transform: capitalize; }

/* ─── Stat tiles (for dashboard) ──────────────────────────────────────── */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  margin-bottom: 28px;
}
.stat {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow);
  background-image: linear-gradient(180deg, rgba(255,255,255,0.035), transparent 70px);
}
.stat .label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--muted);
  margin-bottom: 10px;
}
.stat .val {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-3xl);
  letter-spacing: -0.025em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat .sub { font-size: var(--text-sm); color: var(--muted); margin-top: 6px; }
.stat.accent .val { color: var(--accent); }
.stat.warn .val { color: var(--warn); }
.stat.green .val { color: var(--green); }

/* ─── Modals ──────────────────────────────────────────────────────────── */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  animation: fade-in 0.15s ease-out;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
}
.modal {
  background: var(--card);
  border: 1px solid var(--border-light);
  border-radius: var(--radius-xl);
  width: 100%;
  max-width: 560px;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: var(--shadow-xl);
  animation: modal-in 0.18s cubic-bezier(0.2, 0.8, 0.3, 1);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes modal-in {
  from { transform: translateY(12px) scale(0.985); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 24px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--card);
  z-index: 1;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}
.modal-header h2 { font-size: var(--text-xl); }
/* The close X was an 8px hit area. Now a proper target. */
.modal-header .close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 26px;
  cursor: pointer;
  line-height: 1;
  width: var(--tap);
  height: var(--tap);
  margin: -10px -12px -10px 0;
  border-radius: var(--radius);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.modal-header .close:hover { color: var(--white); }
.modal-body { padding: 24px; }
.modal-header .close:hover { background: var(--card-lighter); }

/* ─── Empty states ────────────────────────────────────────────────────── */
.empty {
  padding: 72px 24px;
  text-align: center;
  color: var(--muted);
}
.empty .icon {
  width: 48px;
  height: 48px;
  margin: 0 auto 16px;
  opacity: 0.4;
}
.empty h3 { color: var(--white); margin-bottom: 8px; font-size: var(--text-lg); }
.empty p { font-size: var(--text-base); margin-bottom: 24px; }
.empty .icon { width: 56px; height: 56px; }

/* ─── Toast notifications ────────────────────────────────────────────── */
#toast-container {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.toast {
  background: var(--card-lighter);
  border: 1px solid var(--border-lighter);
  border-left: 3px solid var(--accent);
  padding: 14px 18px;
  border-radius: var(--radius);
  min-width: 280px;
  max-width: min(420px, calc(100vw - 32px));
  font-size: var(--text-sm);
  box-shadow: var(--shadow-lg);
  animation: toast-in 0.22s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.toast.success { border-left-color: var(--green); }
.toast.error { border-left-color: var(--danger); }
@keyframes toast-in {
  from { transform: translateX(20px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

/* ─── Document detail timeline ───────────────────────────────────────── */
.lifecycle {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 24px;
  padding: 16px 20px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow-x: auto;
}
.lifecycle-step {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-dark);
  white-space: nowrap;
}
.lifecycle-step.done { color: var(--muted-light); }
.lifecycle-step.current { color: var(--accent); background: rgba(0, 229, 255, 0.08); }
.lifecycle-arrow { color: var(--muted-dark); }

/* ─── Document detail two-column ─────────────────────────────────────── */
.detail-grid {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: 24px;
  align-items: start;
}
.detail-grid .sidebar-col { display: flex; flex-direction: column; gap: 16px; }

/* ─── Line items editor ──────────────────────────────────────────────── */
.line-items {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-bottom: 16px;
}
.line-items-header, .line-item-row {
  display: grid;
  grid-template-columns: 1fr 80px 110px 110px 40px;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
}
.line-items-header {
  background: var(--card-light);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted-light);
}
.line-item-row {
  border-top: 1px solid var(--border);
  font-size: 14px;
}
.line-item-row.breakage { background: rgba(255, 68, 102, 0.04); }
.line-item-row.breakage .item-name { color: var(--danger); }
.line-items-totals {
  border-top: 1px solid var(--border);
  padding: 16px;
  background: var(--card-light);
}
.totals-row {
  display: flex;
  justify-content: flex-end;
  gap: 30px;
  padding: 4px 0;
  font-size: 14px;
}
.totals-row.grand {
  border-top: 1px solid var(--border);
  margin-top: 8px;
  padding-top: 12px;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.totals-row.grand .val { color: var(--accent); }
.totals-row .lbl { color: var(--muted-light); }
.totals-row .val { color: var(--white); font-variant-numeric: tabular-nums; min-width: 80px; text-align: right; }

/* ─── Action bar (transition buttons) ─────────────────────────────────── */
.action-bar {
  background: var(--card);
  border: 1px solid var(--accent);
  border-radius: var(--radius-lg);
  padding: 16px 20px;
  margin-bottom: 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  background: linear-gradient(135deg, var(--card), rgba(0, 229, 255, 0.04));
}
.action-bar .text { flex: 1; }
.action-bar .text h3 { color: var(--accent); }
.action-bar .text p { font-size: 14px; color: var(--muted-light); margin-top: 4px; }
.action-bar .actions { display: flex; gap: 10px; }

/* ─── Customer detail / vehicle preview ──────────────────────────────── */
.kv-list { display: flex; flex-direction: column; gap: 10px; }
.kv-list .row { display: flex; justify-content: space-between; gap: 12px; font-size: 14px; }
.kv-list .row .k { color: var(--muted); }
.kv-list .row .v { color: var(--white); text-align: right; }

/* ─── Loading state ──────────────────────────────────────────────────── */
.spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--border-lighter);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

/* ─── Search bar ─────────────────────────────────────────────────────── */
.searchbar {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 12px;
  margin-bottom: 16px;
  max-width: 400px;
}
.searchbar input {
  background: none;
  border: none;
  flex: 1;
  padding: 2px 0;
}
.searchbar input:focus { background: none; }
.searchbar svg { width: 14px; height: 14px; color: var(--muted); }

/* ─── Responsive ──────────────────────────────────────────────────────── */
/* The mobile top bar and drawer. Hidden on desktop, where the sidebar is a
   normal grid column. Markup comes from renderShell() in app.js. */
.mobile-topbar { display: none; }
.nav-scrim { display: none; }

@media (max-width: 900px) {
  .app-shell { grid-template-columns: 1fr; }

  .mobile-topbar {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 12px;
    padding-top: env(safe-area-inset-top);
    height: calc(var(--mobile-header-height) + env(safe-area-inset-top));
    position: sticky;
    top: 0;
    z-index: 60;
    background: rgba(11, 12, 14, 0.88);
    backdrop-filter: saturate(160%) blur(14px);
    -webkit-backdrop-filter: saturate(160%) blur(14px);
    border-bottom: 1px solid var(--border);
  }
  .mobile-menu-btn {
    width: var(--tap);
    height: var(--tap);
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid var(--border-light);
    border-radius: var(--radius);
    color: var(--white);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .mobile-menu-btn:active { background: var(--card-light); }
  .mobile-menu-btn svg { width: 22px; height: 22px; }
  .mobile-topbar-logo { height: 26px; width: auto; }
  .mobile-topbar .avatar {
    margin-left: auto;
    width: 34px; height: 34px;
    border-radius: 50%;
    background: var(--card-lighter);
    color: var(--black);
    font-weight: 700;
    font-size: var(--text-sm);
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
  }

  /* Off-canvas via transform, not display or left — it animates on the GPU
     and the nav stays in the accessibility tree while closed. */
  .sidebar {
    display: flex;
    position: fixed;
    top: 0; bottom: 0; left: 0;
    width: min(310px, 86vw);
    height: 100%;
    z-index: 80;
    transform: translateX(-100%);
    transition: transform 0.26s cubic-bezier(0.2, 0.8, 0.3, 1);
    box-shadow: var(--shadow-xl);
    padding-top: env(safe-area-inset-top);
  }
  body.nav-open .sidebar { transform: none; }

  .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 70;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(2px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.26s;
  }
  body.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }

  /* Sub-items drop their indent on a phone — the group label already carries
     the hierarchy and horizontal space is scarce. */
  .sidebar-nav a.nav-sub { padding-left: 12px; background-image: none; }
  .sidebar-nav a { min-height: 48px; font-size: var(--text-base); }

  .main { padding: 20px 16px 40px; }
  .detail-grid { grid-template-columns: 1fr; }
  .form-grid { grid-template-columns: 1fr; }

  .page-header {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
    margin-bottom: 22px;
  }
  .page-header h1 { font-size: var(--text-xl); }
  .page-header > div:last-child:not(:first-child) .btn { flex: 1; min-height: var(--tap); }

  .card { padding: 18px 16px; }
  .stat-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
  .stat .val { font-size: var(--text-2xl); }

  /* ⚠ 16px minimum on form controls. Below that iOS Safari zooms the page
     when a field takes focus and never zooms back — it reads as a bug. */
  input[type="text"], input[type="email"], input[type="password"], input[type="tel"],
  input[type="number"], input[type="date"], input[type="search"], select, textarea {
    font-size: var(--text-md);
    min-height: 48px;
  }

  .btn { min-height: var(--tap); font-size: var(--text-md); }
  .btn-sm { min-height: 40px; padding: 9px 14px; }

  /* Sheet-style from the bottom: the native idiom on a phone, and it puts
     the controls in thumb reach instead of up under the notch. */
  .modal-backdrop { padding: 0; align-items: flex-end; }
  .modal {
    max-width: 100%;
    max-height: 92vh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    animation: sheet-in 0.24s cubic-bezier(0.2, 0.8, 0.3, 1);
    padding-bottom: env(safe-area-inset-bottom);
  }
  @keyframes sheet-in { from { transform: translateY(100%); } to { transform: none; } }
  .form-actions { flex-direction: column-reverse; }
  .form-actions .btn { width: 100%; }

  #toast-container { left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom)); }
  .toast { min-width: 0; max-width: none; }

  table.t th, table.t td { padding: 12px 14px; }
  /* Let a data table keep readable column widths and SCROLL inside its
     wrapper, rather than compressing "Johnstown Police Department" onto
     three lines. .table-wrap already has overflow-x: auto.
     ⚠ SCOPED TO .table-wrap ON PURPOSE. Applied to every table.t it also hit
     the BARE tables on app.html and customer.html, which have no wrapper and
     therefore no scroll container — those pages went 682px and 653px wide on
     a 375px screen. A bare table gets block-level scrolling of its own
     instead. */
  .table-wrap table.t { min-width: 620px; }
  .main > table.t, .card table.t {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  /* A hint that there is more to the right — without it the scroll is
     invisible and people assume the columns are just cut off. */
  .table-wrap { position: relative; }
  .table-wrap::after {
    content: '';
    position: absolute;
    top: 0; right: 0; bottom: 0;
    width: 28px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, rgba(11, 12, 14, 0.85));
  }

  /* Keep the last rows clear of the install banner. */
  body.has-install-banner .main { padding-bottom: 116px; }

  /* ── Overflow safety net ──────────────────────────────────────────────
     Measured at 375px: app.html came out 920px wide, customer.html 670,
     document.html 510. Three different causes, all the same shape — a
     container that cannot shrink.

     1. Pages define two-column layouts with an INLINE style
        (app.html: "grid-template-columns: 2fr 1fr"). A media query cannot
        reach inline CSS by specificity, so it is matched by attribute and
        overridden. Blunt, but in a codebase with no build step this is the
        only way to collapse them, and a nested two-column grid is never
        right on a phone anyway.
     2. Grid and flex children default to min-width:auto and refuse to
        shrink below their content.
     3. .lifecycle already had overflow-x:auto but its steps were being
        squeezed rather than scrolled. */
  .main [style*="grid-template-columns"] { grid-template-columns: 1fr !important; }
  /* min-width:auto is the default for grid and flex ITEMS, so every column
     wrapper needs releasing too — not just the shell. The unnamed <div>
     column inside .doc-page was the last thing holding document.html at
     493px. */
  .card, .stat, .table-wrap, .lifecycle, .qbo-doc,
  .detail-grid > *, .doc-page > * { min-width: 0; }
  .lifecycle-step, .lifecycle-arrow { flex-shrink: 0; }

  /* .doc-page kept a hard 300px sidebar at every width — document.html was
     the last page still sliding sideways at 375px. */
  .doc-page { grid-template-columns: 1fr; gap: 16px; }

  /* The transition buttons ran off the edge: two nested flex rows, neither
     allowed to wrap. Stacked and full-width, they are also a much better
     target for the actions that move a job through its lifecycle. */
  .action-bar {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
    padding: 16px;
  }
  .action-bar .actions { flex-wrap: wrap; }
  .action-bar .actions .btn { flex: 1 1 auto; min-width: 140px; }

  /* ── Tables become cards ──────────────────────────────────────────────
     A data table on a 375px screen is only readable by dragging it
     sideways — measured at 609px off-screen for a product row. Each row
     becomes a card instead, one field per line labelled with its column
     name, which app.js copies onto the cells from the <th>.

     ⚠ This overrides the min-width/scroll treatment above ON PURPOSE. The
     two cannot both apply: min-width forces the sideways scroll this
     replaces. Wide tables on a tablet still scroll, because the breakpoint
     for cards is the same 900px. */
  .table-wrap {
    background: transparent;
    border: none;
    box-shadow: none;
    overflow-x: visible;
  }
  .table-wrap::after { display: none; }        /* the scroll-edge fade */
  .table-wrap table.t,
  .main > table.t,
  .card table.t { min-width: 0; display: block; overflow: visible; }

  table.t thead { display: none; }             /* labels move onto the cells */
  table.t tbody, table.t tr, table.t td { display: block; width: 100%; }

  table.t tbody tr {
    background: var(--card);
    background-image: linear-gradient(180deg, rgba(255,255,255,0.035), transparent 70px);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: 4px 14px;
    margin-bottom: 12px;
  }
  table.t tbody tr:hover { background: var(--card); }   /* no row highlight on touch */

  table.t td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    text-align: left !important;
    padding: 9px 0;
    border-bottom: 1px solid var(--border);
  }
  table.t tbody tr td:last-child { border-bottom: none; }
  /* A long email or SKU has no spaces to break at and pushed the card wider
     than the screen — 27px of overflow on the customers list. The label side
     keeps its width; the value side gives way. */
  table.t td { min-width: 0; overflow-wrap: anywhere; }
  table.t td::before { max-width: 45%; overflow-wrap: normal; }

  /* The column name, from data-label. */
  table.t td::before {
    content: attr(data-label);
    flex-shrink: 0;
    color: var(--muted);
    font-family: var(--font-display);
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
  }
  /* A header with no text — the select-all checkbox — gets no label, and its
     cell sits on its own line rather than pretending to be a field. */
  table.t td[data-label=""]::before { content: none; }
  table.t td[data-label=""] { justify-content: flex-start; }

  /* Numbers keep their tabular alignment but sit on the right of the row. */
  table.t td.num { justify-content: space-between; }
  table.t td.num, table.t .mono { font-variant-numeric: tabular-nums; }

  /* Buttons are the reason several of these tables were so wide. Given a
     line of their own they get real tap targets instead. */
  table.t td:last-child { flex-wrap: wrap; padding-top: 12px; }
  table.t td:last-child .btn { flex: 1 1 auto; min-width: 96px; margin-left: 0 !important; }
  table.t td .btn + .btn { margin-left: 0; }
}

/* A coarse pointer gets full-size targets whatever the viewport width — a
   tablet in landscape is still a touch screen. */
@media (pointer: coarse) {
  .btn { min-height: var(--tap); }
  .cfg-view-chip { min-height: var(--tap); min-width: var(--tap); }
  .sidebar-nav a { min-height: 48px; }
}

/* ─── Logo light bars ────────────────────────────────────────────────────
   Emergency-light beams sweeping out from behind the keystone badge: red
   above, blue below, brightest where they pass behind the mark.

   Done in CSS rather than baked into the PNG for two reasons. The artwork
   stays a clean transparent badge that also works on the light customer-facing
   estimate page, where cop lights would be wrong. And the beams sit BEHIND the
   logo — only possible because the logo has a real alpha channel now, so the
   glow shows through around the keystone instead of being clipped to a box.

   ⚠ Not applied to the mobile top bar (26px tall — the blur becomes mud) or to
   view-estimate.html, which is a formal document a customer prints. */
.wp-logo {
  position: relative;
  display: block;
  isolation: isolate;      /* keeps the blur off anything behind the header */
}
.wp-logo img {
  position: relative;
  z-index: 1;
  display: block;
}
.wp-logo::before,
.wp-logo::after {
  content: '';
  position: absolute;
  z-index: 0;
  height: 11%;
  min-height: 5px;
  border-radius: 999px;
  pointer-events: none;
  filter: blur(8px);
}
/* Red, riding just above the wordmark band. */
.wp-logo::before {
  top: 40%;
  left: -42%;
  right: -42%;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(255, 48, 48, 0.55) 14%,
    rgba(255, 60, 60, 0.95) 34%,
    rgba(255, 240, 240, 0.98) 50%,
    rgba(255, 60, 60, 0.95) 66%,
    rgba(255, 48, 48, 0.55) 86%,
    transparent 100%);
}
/* Blue, just below it. */
.wp-logo::after {
  top: 56%;
  left: -36%;
  right: -36%;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(56, 104, 255, 0.55) 16%,
    rgba(70, 120, 255, 0.95) 35%,
    rgba(220, 232, 255, 0.98) 50%,
    rgba(70, 120, 255, 0.95) 65%,
    rgba(56, 104, 255, 0.55) 84%,
    transparent 100%);
}

/* The sidebar is a narrow panel that clips horizontally, so the full sweep
   gets sliced off square against its edge. Pulled in to land inside. */
.sidebar-brand .wp-logo::before { left: -13%; right: -13%; }
.sidebar-brand .wp-logo::after  { left: -9%;  right: -9%; }
.sidebar-brand .wp-logo::before,
.sidebar-brand .wp-logo::after { filter: blur(6px); }

/* A slow alternating pulse, ONLY on the sign-in screen. The sidebar logo is on
   screen all day and anything moving there becomes an irritation; the login
   page is looked at for a few seconds. */
.wp-logo.wp-logo-live::before { animation: bar-red 2.6s ease-in-out infinite; }
.wp-logo.wp-logo-live::after  { animation: bar-blue 2.6s ease-in-out infinite; }
@keyframes bar-red  { 0%, 44% { opacity: 1; } 50%, 94% { opacity: 0.28; } 100% { opacity: 1; } }
@keyframes bar-blue { 0%, 44% { opacity: 0.28; } 50%, 94% { opacity: 1; } 100% { opacity: 0.28; } }

/* The global prefers-reduced-motion rule near the top already collapses these
   to a stop; this pins them at full brightness rather than mid-fade. */
@media (prefers-reduced-motion: reduce) {
  .wp-logo.wp-logo-live::before,
  .wp-logo.wp-logo-live::after { animation: none; opacity: 1; }
}

/* ─── Add to home screen ─────────────────────────────────────────────────
   The banner is shown once to a tech on a phone and remembers dismissal.
   The sidebar link is the way back for anyone who dismissed it. */
.install-banner {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  z-index: 150;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border-radius: var(--radius-lg);
  background: rgba(26, 29, 33, 0.97);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid var(--border-lighter);
  box-shadow: var(--shadow-xl);
  animation: banner-in 0.32s cubic-bezier(0.2, 0.8, 0.3, 1);
}
@keyframes banner-in {
  from { transform: translateY(140%); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
/* 36px, not 40, and a 12px gap. Measured at 375px the title needed exactly
   the width it had — zero slack — so a 360px phone or a longer string would
   have clipped. This buys ~16px. */
.install-banner { gap: 12px; }
.install-banner-icon {
  width: 36px; height: 36px;
  border-radius: 8px;
  flex-shrink: 0;
}
/* ⚠ Everything here is sized to keep the banner ONE ROW at 375px. It first
   shipped at 127px tall because the copy wrapped to three lines, and it sat
   on top of the second job card. Keep the strings short and the nowrap. */
.install-banner-text { flex: 1; min-width: 0; line-height: 1.3; }
.install-banner-text strong {
  display: block;
  font-size: var(--text-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.install-banner-text span {
  display: block;
  font-size: var(--text-sm);
  color: var(--muted);
  margin-top: 1px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.install-banner .btn { flex-shrink: 0; }
.install-banner-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  width: 34px; height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
}
.install-banner-close:hover { color: var(--white); background: var(--card-lighter); }

.install-link {
  display: flex;               /* every device: a phone adds to its home screen, a PC installs and pins */
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: 10px;
  padding: 11px 12px;
  min-height: var(--tap);
  background: transparent;
  border: 1px dashed var(--border-lighter);
  border-radius: var(--radius);
  color: var(--muted-light);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  text-align: left;
}
.install-link:hover { color: var(--white); border-color: var(--accent); }
.install-link svg { width: 16px; height: 16px; flex-shrink: 0; }
/* Already installed — hide both routes, they would do nothing. */
@media (display-mode: standalone) {
  .install-link, .install-banner { display: none !important; }
}

/* ─── Tax exempt badge ───────────────────────────────────────────────── */
.tax-exempt-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(0, 229, 255, 0.08);
  border: 1px solid rgba(0, 229, 255, 0.3);
  color: var(--accent);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ─── Shortage warning card ──────────────────────────────────────────── */
.shortage-card {
  border-color: var(--danger) !important;
  background: linear-gradient(135deg, var(--card), rgba(255, 68, 102, 0.04));
}
.shortage-card .card-title { color: var(--danger); }
.shortage-line {
  display: grid;
  grid-template-columns: 1fr 90px 90px 90px 90px;
  gap: 10px;
  padding: 8px 16px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  align-items: center;
}
.shortage-line.short { background: rgba(255, 68, 102, 0.04); }
.shortage-line.ok { color: var(--muted); }
.shortage-line .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

/* ─── Toggle switch (for tax exempt etc) ─────────────────────────────── */
.toggle {
  position: relative;
  display: inline-block;
  width: 38px;
  height: 22px;
  vertical-align: middle;
}
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
  position: absolute;
  inset: 0;
  cursor: pointer;
  background: var(--card-lighter);
  border: 1px solid var(--border-light);
  border-radius: 22px;
  transition: 0.2s;
}
.toggle-slider::before {
  content: '';
  position: absolute;
  height: 14px;
  width: 14px;
  left: 3px;
  bottom: 3px;
  background: var(--muted-light);
  border-radius: 50%;
  transition: 0.2s;
}
.toggle input:checked + .toggle-slider {
  background: var(--accent);
  border-color: var(--accent);
}
.toggle input:checked + .toggle-slider::before {
  transform: translateX(16px);
  background: var(--black);
}
.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
}
.toggle-row .label-text {
  flex: 1;
  font-family: var(--font-body);
  text-transform: none;
  letter-spacing: 0;
  font-size: 14px;
  font-weight: 500;
  color: var(--white);
  margin: 0;
}
.toggle-row .label-text .hint {
  display: block;
  color: var(--muted);
  font-size: 13px;
  margin-top: 2px;
  font-weight: 400;
}

/* ─── Search-as-you-type for line item picker ────────────────────────── */
.item-search-wrap { position: relative; }
.item-search-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 320px;
  overflow-y: auto;
  background: var(--card-lighter);
  border: 1px solid var(--border-lighter);
  border-radius: var(--radius);
  margin-top: 4px;
  z-index: 20;
  display: none;
}
.item-search-results.open { display: block; }
.item-search-result {
  padding: 10px 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
  align-items: center;
  font-size: 14px;
}
.item-search-result:hover, .item-search-result.highlighted {
  background: var(--card-light);
}
.item-search-result:last-child { border-bottom: none; }
.item-search-result .name { font-weight: 500; }
.item-search-result .sku { font-family: var(--font-mono); font-size: 13px; color: var(--muted); }
.item-search-result .meta { font-size: 13px; color: var(--muted); margin-top: 2px; }
.item-search-result .right { text-align: right; }
.item-search-result .price { font-family: var(--font-mono); font-weight: 600; }
.item-search-result .avail { font-size: 12px; color: var(--muted); margin-top: 2px; }
.item-search-result .avail.short { color: var(--danger); }
.item-search-result .avail.ok { color: var(--green); }
.item-search-result .cat-tag {
  display: inline-block;
  background: rgba(0, 229, 255, 0.08);
  color: var(--accent);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 2px 6px;
  border-radius: 999px;
  margin-right: 6px;
}

/* ─── Category filter pills ──────────────────────────────────────────── */
.category-pills {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.cat-pill {
  padding: 4px 10px;
  border-radius: 999px;
  background: transparent;
  border: 1px solid var(--border-light);
  color: var(--muted-light);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  cursor: pointer;
  transition: all 0.15s;
}
.cat-pill:hover { color: var(--white); border-color: var(--border-lighter); }
.cat-pill.active { background: var(--accent); color: var(--black); border-color: var(--accent); }

/* ─── QBO-style document layout ──────────────────────────────────────── */

.qbo-doc {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-bottom: 24px;
}

/* Top section: BILL TO (left) + metadata (right) */
.qbo-doc-top {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 24px;
  padding: 24px 28px;
  border-bottom: 1px solid var(--border);
}

.qbo-bill-to {
  font-size: 14px;
}
.qbo-bill-to .section-label {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  margin-bottom: 8px;
}
.qbo-bill-to .company-name {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--white);
  margin-bottom: 2px;
}
.qbo-bill-to .contact { font-size: 13px; color: var(--muted-light); }
.qbo-bill-to .addr { font-size: 13px; color: var(--muted-light); margin-top: 6px; line-height: 1.5; }
.qbo-bill-to .badges { margin-top: 10px; display: flex; gap: 6px; }

.qbo-doc-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 8px 16px;
  align-content: start;
  font-size: 13px;
}
.qbo-doc-meta .k {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  white-space: nowrap;
}
.qbo-doc-meta .v {
  color: var(--white);
  font-variant-numeric: tabular-nums;
}
.qbo-doc-meta .v.editable {
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 3px;
  border: 1px dashed transparent;
}
.qbo-doc-meta .v.editable:hover {
  border-color: var(--border-light);
  background: var(--card-light);
}
.qbo-doc-meta input[type="date"], .qbo-doc-meta input[type="text"] {
  font-size: 13px;
  padding: 4px 8px;
  width: auto;
}

/* Line items table — QBO style */
.qbo-items {
  width: 100%;
}
.qbo-items-head {
  display: grid;
  grid-template-columns: 84px 2fr 2fr 70px 100px 110px 40px;
  gap: 12px;
  padding: 10px 28px;
  background: var(--deep);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
}
.qbo-items-head .num, .qbo-row .num { text-align: right; }
.qbo-row {
  display: grid;
  grid-template-columns: 84px 2fr 2fr 70px 100px 110px 40px;
  gap: 12px;
  padding: 10px 28px;
  align-items: center;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
  transition: background 0.1s;
}
.qbo-row:hover { background: var(--card-light); }
.qbo-row.breakage { background: rgba(255, 68, 102, 0.03); }
.qbo-row.breakage:hover { background: rgba(255, 68, 102, 0.06); }
.qbo-row .row-num {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted-dark);
}
.qbo-row .product-cell .name {
  font-weight: 500;
  color: var(--white);
}
.qbo-row .product-cell .sku {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted);
  margin-top: 2px;
}
.qbo-row .product-cell .breakage-tag {
  display: inline-block;
  margin-left: 8px;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--danger);
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(255, 68, 102, 0.1);
  border: 1px solid rgba(255, 68, 102, 0.3);
  vertical-align: middle;
}
.qbo-row .amount { font-family: var(--font-mono); font-weight: 600; }

/* Inline editing inputs */
.qbo-cell-editable {
  cursor: text;
  padding: 4px 8px;
  border-radius: 3px;
  border: 1px solid transparent;
  min-height: 28px;
  display: flex;
  align-items: center;
}
.qbo-cell-editable:hover { border-color: var(--border-light); background: var(--card-lighter); }
.qbo-cell-editable.editing { border-color: var(--accent); background: var(--card-lighter); }
.qbo-inline-input {
  width: 100%;
  background: transparent;
  border: none;
  color: var(--white);
  font-size: 14px;
  padding: 0;
  outline: none;
  font-family: var(--font-body);
}
.qbo-inline-input.num {
  text-align: right;
  font-family: var(--font-mono);
}
.qbo-row .delete-row {
  background: none;
  border: none;
  color: var(--muted-dark);
  cursor: pointer;
  padding: 4px;
  border-radius: 3px;
  font-size: 14px;
  line-height: 1;
}
.qbo-row .delete-row:hover {
  color: var(--danger);
  background: rgba(255, 68, 102, 0.08);
}

.qbo-add-row {
  padding: 12px 28px;
  border-bottom: 1px solid var(--border);
  display: flex;
  gap: 10px;
}

/* Totals stacked right */
.qbo-totals {
  padding: 16px 28px 20px;
  display: flex;
  justify-content: flex-end;
}
.qbo-totals-inner {
  width: 340px;
  font-size: 14px;
}
.qbo-totals-row {
  display: grid;
  grid-template-columns: 1fr 120px;
  padding: 6px 0;
  align-items: baseline;
}
.qbo-totals-row .lbl { color: var(--muted-light); text-align: right; padding-right: 16px; }
.qbo-totals-row .val { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
.qbo-totals-row.grand {
  border-top: 1px solid var(--border-light);
  margin-top: 8px;
  padding-top: 14px;
  font-size: 18px;
}
.qbo-totals-row.grand .lbl {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--white);
}
.qbo-totals-row.grand .val {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--accent);
}

/* Message + Terms section (QBO footer) */
.qbo-footer-section {
  padding: 20px 28px;
  border-top: 1px solid var(--border);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}
.qbo-footer-section h3 {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  margin-bottom: 8px;
}
.qbo-footer-section textarea {
  width: 100%;
  background: var(--card-light);
  border: 1px solid var(--border);
  color: var(--white);
  padding: 10px 12px;
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-size: 14px;
  min-height: 80px;
  resize: vertical;
}
.qbo-footer-section textarea:focus {
  outline: none;
  border-color: var(--accent);
}
.qbo-footer-section .readonly-note {
  font-size: 14px;
  color: var(--muted-light);
  white-space: pre-wrap;
  min-height: 40px;
  padding: 10px 12px;
  background: var(--card-light);
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

/* Document page two-column layout (main + right sidebar) */
.doc-page {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 24px;
  align-items: start;
}
.doc-sidebar {
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: sticky;
  top: 20px;
}

@media (max-width: 1100px) {
  .doc-page { grid-template-columns: 1fr; }
  .doc-sidebar { position: static; }
  .qbo-doc-top { grid-template-columns: 1fr; }
  .qbo-items-head, .qbo-row { grid-template-columns: 74px 1fr 1fr 60px 80px 90px 28px; gap: 8px; padding: 8px 14px; }
  .qbo-footer-section { grid-template-columns: 1fr; }
}

/* ═════════════════════════════════════════════════════════════════════
   VEHICLE CONFIGURATOR — three-column drag-and-drop editor
   ═════════════════════════════════════════════════════════════════════ */

.cfg-shell {
  display: grid;
  grid-template-columns: 260px 1fr 340px;
  gap: 16px;
  height: calc(100vh - 180px);
  min-height: 600px;
}

.cfg-panel {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.cfg-panel-header {
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--deep);
}
.cfg-panel-header h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-light);
  margin: 0;
}
.cfg-panel-body { flex: 1; overflow-y: auto; padding: 16px 18px; }
.cfg-panel-footer { padding: 14px 18px; border-top: 1px solid var(--border); background: var(--deep); }

.cfg-section-label {
  font-family: var(--font-display);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted-dark);
  margin: 12px 0 6px;
}
.cfg-section-label:first-child { margin-top: 0; }

/* Vehicle picker — large select-like cards */
.cfg-vehicle-list { display: flex; flex-direction: column; gap: 6px; }
.cfg-vehicle-item {
  padding: 10px 12px;
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 14px;
  color: var(--muted-light);
  transition: all 0.1s;
}
.cfg-vehicle-item:hover { border-color: var(--border-lighter); color: var(--white); background: var(--card-light); }
.cfg-vehicle-item.active {
  border-color: var(--accent);
  background: rgba(0, 229, 255, 0.06);
  color: var(--white);
}
.cfg-vehicle-item .vname { font-weight: 600; font-family: var(--font-display); }
.cfg-vehicle-item .vsub { font-size: 13px; color: var(--muted); margin-top: 2px; }

/* Layout picker — select element styled */
.cfg-layout-select {
  width: 100%;
  margin-bottom: 8px;
}

/* Color legend */
.cfg-legend { display: flex; flex-direction: column; gap: 6px; }
.cfg-legend-item {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--muted-light);
}
.cfg-legend-dot {
  width: 14px; height: 14px; border-radius: 50%;
  border: 1px solid var(--black);
}

/* Center canvas */
.cfg-canvas-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f5f5f7;  /* Light grey so the white vehicle artwork stands out */
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
}
.cfg-canvas {
  max-width: 100%;
  max-height: 100%;
  background: transparent;
  cursor: default;
  user-select: none;
}
.cfg-canvas .silhouette-host { pointer-events: none; }
.cfg-canvas .silhouette-host * { pointer-events: none; }

/* Region label in the right-panel lights list */
.cfg-region-label {
  display: inline-block;
  background: var(--card-light);
  border: 1px solid var(--border-light);
  color: var(--accent);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 4px;
}

/* Selected light row in the right panel */
.cfg-light-row {
  cursor: pointer;
  transition: background 0.1s, border-color 0.1s;
}
.cfg-light-row:hover { background: var(--card-light); }
.cfg-light-row.selected {
  background: rgba(0, 229, 255, 0.06);
  border-color: var(--accent);
}

.cfg-empty {
  text-align: center;
  color: var(--muted);
  font-size: 14px;
  padding: 40px 20px;
}

/* Light markers in the SVG */
.cfg-marker {
  cursor: grab;
  transition: filter 0.1s;
}
.cfg-marker:hover { filter: drop-shadow(0 0 6px var(--accent)); }
.cfg-marker.selected { filter: drop-shadow(0 0 8px var(--accent)) drop-shadow(0 0 12px rgba(0, 229, 255, 0.5)); }
.cfg-marker.dragging { cursor: grabbing; opacity: 0.85; }
.cfg-marker-circle { stroke: #0a0a0a; stroke-width: 1.5; }
.cfg-marker-label {
  fill: #ffffff; font-family: 'Rajdhani', sans-serif; font-size: 7px; font-weight: 700;
  text-anchor: middle; dominant-baseline: middle;
  pointer-events: none;
  letter-spacing: 0;
}

/* Anchor hints (when snap is on) */
.cfg-anchor-hint {
  fill: rgba(0, 229, 255, 0.15);
  stroke: rgba(0, 229, 255, 0.4);
  stroke-width: 0.5;
  stroke-dasharray: 2 2;
  pointer-events: none;
}

/* Toolbar above canvas */
.cfg-toolbar {
  padding: 10px 18px;
  background: var(--deep);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.cfg-toolbar .info { flex: 1; font-size: 14px; color: var(--muted-light); }
.cfg-toolbar .info strong { color: var(--white); font-family: var(--font-display); }

/* Right panel — lights list */
.cfg-light-row {
  display: grid;
  grid-template-columns: 28px 1fr 36px;
  gap: 8px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
  align-items: center;
  font-size: 13px;
}
.cfg-light-row:last-child { border-bottom: none; }
/* The disc grew with its label. The number inside went 9px → 12px in the
   readability pass, and a two-digit marker would have touched the edge of
   the old 22px circle. */
.cfg-light-row .marker-preview {
  width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid var(--black);
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 12px; font-weight: 700; color: #fff;
}
.cfg-light-row .name { font-weight: 500; color: var(--white); }
.cfg-light-row .sku { font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.cfg-light-row .qty { font-size: 13px; color: var(--muted-light); margin-top: 2px; }
.cfg-light-row .remove-btn {
  background: none; border: none; color: var(--muted-dark);
  cursor: pointer; font-size: 16px; line-height: 1; padding: 4px;
}
.cfg-light-row .remove-btn:hover { color: var(--danger); }

.cfg-totals-mini {
  padding: 12px 0;
  border-top: 1px solid var(--border);
  margin-top: 8px;
}
.cfg-totals-mini .row {
  display: flex; justify-content: space-between;
  padding: 3px 0; font-size: 13px; color: var(--muted-light);
}
.cfg-totals-mini .row.grand {
  border-top: 1px solid var(--border-light);
  padding-top: 8px; margin-top: 4px;
  font-family: var(--font-display); font-size: 15px; font-weight: 700;
  color: var(--white);
}
.cfg-totals-mini .row.grand .val { color: var(--accent); }

/* Add-light selector */
.cfg-add-light {
  background: var(--card-light);
  border: 1px solid var(--border-light);
  border-radius: var(--radius);
  padding: 10px;
  margin-bottom: 12px;
}
.cfg-add-light label {
  font-size: 12px; margin-bottom: 4px;
}

/* Toggle row in toolbar */
.cfg-toggle {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--muted-light);
  cursor: pointer;
}
.cfg-toggle input[type="checkbox"] { cursor: pointer; }

/* View tabs above the canvas */
.cfg-view-tabs {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--border);
  background: var(--deep);
  padding: 0 18px;
}
.cfg-view-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--muted-light);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 12px 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: all 0.15s;
}
.cfg-view-tab:hover { color: var(--white); }
.cfg-view-tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
.cfg-view-tab .view-count {
  background: var(--card-light);
  color: var(--muted-light);
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0;
}
.cfg-view-tab.active .view-count {
  background: rgba(0, 229, 255, 0.15);
  color: var(--accent);
}

/* Tiny tabs (used in the right-panel lights list) */
.cfg-tiny-tab {
  background: var(--card-light);
  border: 1px solid var(--border-light);
  color: var(--muted-light);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.1s;
}
.cfg-tiny-tab:hover { border-color: var(--border-lighter); color: var(--white); }
.cfg-tiny-tab.active {
  background: rgba(0, 229, 255, 0.1);
  border-color: var(--accent);
  color: var(--accent);
}

/* Solo / Duo / Trio mode toggle */
.cfg-lens-mode {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
  margin-bottom: 8px;
}
.cfg-lens-mode-btn {
  background: var(--card-light);
  border: 1px solid var(--border-light);
  color: var(--muted-light);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 7px 6px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.1s;
}
.cfg-lens-mode-btn:hover { color: var(--white); border-color: var(--border-lighter); }
.cfg-lens-mode-btn.active {
  background: rgba(0, 229, 255, 0.12);
  border-color: var(--accent);
  color: var(--accent);
}

/* Per-lens color picker rows */
.cfg-lens-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
}
.cfg-lens-row:last-child { border-bottom: none; }
.cfg-lens-label {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-light);
  min-width: 50px;
}
.cfg-lens-swatches {
  display: flex;
  gap: 4px;
  flex: 1;
}
.cfg-lens-swatch {
  width: 22px;
  height: 22px;
  border: 2px solid var(--border-light);
  border-radius: 50%;
  cursor: pointer;
  transition: all 0.1s;
  padding: 0;
}
.cfg-lens-swatch:hover {
  border-color: var(--accent);
  transform: scale(1.1);
}
.cfg-lens-swatch.active {
  border-color: var(--white);
  box-shadow: 0 0 0 2px var(--accent);
}

@media (max-width: 1200px) {
  .cfg-shell { grid-template-columns: 220px 1fr 300px; }
}
@media (max-width: 1000px) {
  .cfg-shell {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    height: auto;
  }
  .cfg-canvas-wrap { min-height: 480px; }
}

/* ─── Which views a light is drawn in ───────────────────────────────────
   One light is DRAWN in up to five panels (decision #10), and not every copy
   is real — an interior lightbar sits behind the windscreen and is not seen
   from the side. These chips switch a view off.

   ⚠ ON and OFF must be legible without reading the label, because the whole
   point is spotting at a glance that a light is drawn somewhere it is not
   fitted. Off is not merely dimmer: it loses its fill and goes dashed, so it
   reads as the outline of something absent rather than a disabled button.

   Only rendered on the SELECTED row — see renderViewChips. */
.cfg-view-chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 0 0 10px 36px;          /* lines up under the row, past the disc */
  border-bottom: 1px solid var(--border);
}
/* The row above owns a border of its own and two stacked read as a mistake.
   :has() is unsupported on older phones, where the only cost is that second
   line — the control still works. */
.cfg-light-row:has(+ .cfg-view-chips) { border-bottom: none; }
.cfg-view-chips-label {
  font-family: var(--font-display); font-size: 12px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted); margin-right: 2px;
}
.cfg-view-chip {
  font-family: var(--font-display); font-size: 12px; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 5px 10px; border-radius: 4px;
  cursor: pointer; line-height: 1; background: none;
}
.cfg-view-chip.is-on {
  border: 1px solid var(--accent); color: var(--black);
  background: var(--accent); font-weight: 700;
}
.cfg-view-chip.is-off {
  border: 1px dashed var(--muted-dark); color: var(--muted);
  text-decoration: line-through;
}
.cfg-view-chip.is-off:hover { color: var(--muted-light); border-color: var(--muted); }
/* The view it was PLACED in. Not a button — hiding it would leave nothing to
   grab and no way back; removing the light is the honest way to do that. */
.cfg-view-chip.is-placed {
  border: 1px solid var(--border); color: var(--muted-light);
  cursor: default; opacity: 0.85;
}

/* ─── Schedule calendar ──────────────────────────────────────────────────
   ⚠ A GRID, NOT A TABLE. `table.t` gets the responsive card treatment below
   900px — each row restacks as a labelled card — which is right for data and
   completely wrong for a calendar, where the seven-column shape IS the
   information. A grid opts out of that entirely.

   Below 640px the columns would be ~45px, too narrow for a chip. It scrolls
   sideways inside its own container rather than squeezing, which is the one
   place in this app horizontal scroll is correct: a month has seven columns
   and nothing can change that. */
.cal-head {
  display: grid; grid-template-columns: repeat(7, minmax(88px, 1fr));
  gap: 6px; margin-bottom: 6px; min-width: 620px;
}
.cal-head > div {
  font-family: var(--font-display); font-size: var(--text-xs);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
  text-align: center; padding: 4px 0;
}
.cal-grid {
  display: grid; grid-template-columns: repeat(7, minmax(88px, 1fr));
  gap: 6px; min-width: 620px;
}
/* ⚠ THE CONTAINER THE COMMENT ABOVE PROMISED, WHICH DID NOT EXIST.
   .cal-head and .cal-grid carry min-width: 620px and overflow-x: visible, and
   nothing wrapped them — so instead of scrolling inside something, they pushed
   the whole PAGE to 668px at a 375px viewport. Found by measuring every tech
   page at 375px, not by looking at it.

   This is the same trap the 8 Aug design pass recorded for .table-wrap: a grid
   or flex child defaults to min-width: auto and refuses to shrink below its
   content, so the wrapper is never the thing being squeezed unless you say so.
   Hence min-width: 0 here as well as the overflow.

   ⚠ ONE wrapper around BOTH, because they must scroll together — otherwise the
   weekday header slides off its own column. */
.cal-scroll { overflow-x: auto; min-width: 0; -webkit-overflow-scrolling: touch; }
.cal-head, .cal-grid { overflow-x: visible; }
@media (max-width: 660px) {
  .cal-head, .cal-grid { min-width: 620px; }
}

.cal-cell {
  min-height: 92px; padding: 6px; border-radius: var(--radius);
  background: var(--card); border: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 4px;
}
.cal-cell[role="button"] { cursor: pointer; }
.cal-cell[role="button"]:hover { border-color: var(--accent); }
.cal-cell[role="button"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cal-empty { background: none; border: none; min-height: 0; }
.cal-cell.is-today { border-color: var(--accent); }
/* ⚠ Not colour alone. A double-booking is flagged with a left bar as well, so
   it survives a colour-blind reader and a greyscale print. */
.cal-cell.has-conflict { border-left: 3px solid var(--warning, #e0a800); }

.cal-daynum {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--muted); text-align: right; line-height: 1;
}
.cal-cell.is-today .cal-daynum { color: var(--accent); font-weight: 700; }

.cal-chip {
  font-size: 12px; line-height: 1.3; padding: 3px 6px;
  background: var(--card-light); border-radius: 3px;
  border-left: 3px solid var(--accent);
  display: flex; gap: 5px; align-items: baseline;
  /* A long customer name must not stretch the column and push the week off
     the screen. */
  overflow: hidden;
}
.cal-chip-who { font-weight: 600; white-space: nowrap; }
.cal-chip-job { color: var(--muted-light); font-family: var(--font-mono); font-size: 11px;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-chip-hrs { margin-left: auto; color: var(--muted); font-family: var(--font-mono); font-size: 11px; }

/* ─── Hours remaining on a build ─────────────────────────────────────────
   Shown to a technician when they clock in. ⚠ Hours are not prices, so
   decision #6 permits this — but the labour RATE must never appear, since the
   budget and the rate together give the line total. */
.job-hours {
  display: flex; gap: 16px; flex-wrap: wrap; align-items: baseline;
  background: var(--card-light); padding: 10px 12px;
  border-radius: var(--radius); margin-top: 10px;
}
.job-hours .jh-num { font-family: var(--font-mono); font-size: 17px; }
.job-hours .jh-lab {
  font-family: var(--font-display); font-size: var(--text-xs);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
/* ⚠ Over budget is stated, never hidden. Clamping remaining at zero is the
   same mistake as clamping stock availability — it removes the one number
   somebody has to act on. */
.job-hours .jh-over { color: var(--danger); }

/* ⚠ TODAY MUST BE UNMISSABLE. A tinted border alone was too quiet to find at a
   glance in a 31-cell grid — and worse, it was on the WRONG DAY: the check used
   toISOString(), which is UTC, so from early evening onwards Eastern it marked
   tomorrow. Fixed to local components; this makes the right answer obvious. */
.cal-today-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: 10px; background: var(--accent); color: var(--black);
  font-weight: 700;
}
.cal-cell.is-today { border-color: var(--accent); background: var(--card-light); }

/* A shop that has not supplied artwork yet. Set in the display face at the
   badge's own size, so the header still reads as a header rather than as a
   missing image. Never another shop's logo. */
.wordmark-fallback {
  font-family: var(--font-display, "Rajdhani", sans-serif);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: 0.3px;
  color: var(--text, #e8ecf1);
  max-width: 160px;
}

/* ── Brand colour picker (Admin → Settings) ─────────────────────────────
   ⚠ Added because a shop should not have to know a hex code to choose its
   own colour. The native colour input is a small grey button by default, so
   it is sized to match the hex field beside it and the row reads as one
   control rather than three. */
.colour-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.colour-well {
  width: 52px; height: 40px; padding: 2px; flex: none; cursor: pointer;
  background: transparent; border: 1px solid var(--border-light);
  border-radius: 8px;
}
.colour-hex {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: lowercase; max-width: 130px; flex: none;
}
/* Shows the label colour the app will derive, so a pale accent looks like
   what it will become rather than being refused. */
.colour-preview {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 84px; height: 40px; padding: 0 14px; border-radius: 8px;
  font-weight: 700; font-size: var(--text-sm); color: #16202b;
  user-select: none;
}
.swatches { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
/* 30px because these are tapped on a phone as well as clicked. */
.swatch-btn {
  width: 30px; height: 30px; border-radius: 7px; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.18); padding: 0;
  transition: transform 0.12s;
}
.swatch-btn:hover { transform: scale(1.08); }
.swatch-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Logo fields in Settings ───────────────────────────────────────────────
   ⚠ THE PREVIEW SITS ON THE GROUND THE LOGO WILL ACTUALLY SIT ON — black for
   the dark-background file, white for the light-background one. That is the
   whole point of the control: the difference between the two files is which
   background they survive, and no amount of label text explains it as fast as
   seeing white lettering vanish on white paper. */
.logo-help { font-size: 13px; margin: -2px 0 10px; }
.logo-field {
  display: flex; gap: 16px; align-items: stretch; flex-wrap: wrap;
}
.logo-preview-box {
  flex: 0 0 260px; min-height: 104px; border-radius: 10px;
  border: 1px solid var(--border-light);
  display: flex; align-items: center; justify-content: center;
  padding: 12px; overflow: hidden;
}
.logo-preview-box img { max-width: 100%; max-height: 80px; display: block; }
.logo-empty {
  font-size: 12px; color: #8a9099; text-align: center; line-height: 1.4;
  display: flex; align-items: center; justify-content: center;
}
/* ⚠ min-width:0 or this refuses to shrink and the row scrolls sideways at
   375px — grid and flex items default to min-width:auto (§4). */
.logo-controls { flex: 1 1 240px; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.logo-wants { font-size: 12px; line-height: 1.5; }
.logo-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
/* The file input is hidden inside its label, so the label IS the button and
   needs to look and behave like one. */
.logo-buttons label.btn { cursor: pointer; display: inline-flex; align-items: center; }
.logo-status { font-size: 12px; min-height: 16px; }
.logo-status.is-ok { color: var(--green, #22e08a); }
.logo-status.is-error { color: var(--red, #ff4466); }

/* The raw cause under an error panel: present for a support screenshot,
   never the thing the eye lands on first. */
.empty .error-reason {
  font-size: var(--text-xs); color: var(--muted); opacity: 0.75;
  margin: 18px 0 0; word-break: break-word;
}

/* ⚠ THE ONE CONTROL THE 44px FLOOR MISSED. The 8 Aug design overhaul set
   "nothing interactive is under 44px on a touch screen"; the sidebar's Sign
   Out stayed at 29px because on desktop it is a mouse target and nobody
   re-measured it inside the mobile drawer, which is where a technician meets
   it — on a phone, in a shop, often with cold hands.
   Measured at 29px on my-jobs.html at 375px, and it was the ONLY element
   under the floor in the whole technician view. */
/* (12 Sep 2026: Sign Out is a full-width 44px row on every screen now, so the
   mobile-only rule that once lived here is gone.) */

/* A shift that has stopped being plausible. Amber, not red: the tech has done
   nothing wrong and the entry is not yet an error — it is a prompt. */
.clock-status-time.is-long { color: var(--warn, #f0a202); }
.clock-long-note {
  font-size: 12px; color: var(--warn, #f0a202); margin-top: 4px; line-height: 1.4;
}

/* ── Removing a document ───────────────────────────────────────────────────
   Its own card at the foot of the page, not a ghost button beside View PDF.
   Bordered rather than filled: this is a place you arrive at deliberately,
   not something that should shout at you while you read a quote. */
.card.doc-danger { border-color: rgba(255, 90, 90, 0.25); margin-top: 20px; }
.doc-danger-title { color: var(--danger, #ff4466); }
.doc-danger-body {
  font-size: 14px; line-height: 1.6; color: var(--muted-light); max-width: 62ch;
}
.doc-danger-btn {
  background: var(--danger, #ff4466); color: #fff; margin-top: 14px;
}
.doc-danger-btn:hover { filter: brightness(1.08); }

/* A line of text on a quote — prose, not a part. Spans the number columns
   because there are no numbers to show. */
.qbo-row.note-row { align-items: center; }
.qbo-row .note-text {
  font-size: 14px; line-height: 1.5; color: var(--muted-light);
  white-space: pre-wrap; padding: 4px 0;
}

/* An instruction line on a base quote: click it to swap it for a part.
   Looks like the text it replaced, not like a button, until you approach it —
   it IS the content, and a heavy control here would compete with the parts. */
.note-swap {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left;
  white-space: pre-wrap; cursor: pointer;
  border-bottom: 1px dashed var(--border-light);
}
.note-swap:hover { color: var(--accent); border-bottom-color: var(--accent); }
.note-swap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Display order by arrows (admin lists). Two ghost buttons side by side; the
   disabled one at each end tells you where the list stops. */
.order-arrows { display: inline-flex; gap: 2px; }
.order-arrows .btn { padding-left: 8px; padding-right: 8px; line-height: 1; }
.order-arrows .btn[disabled] { opacity: .25; cursor: default; }

/* ── Skeleton loading ──────────────────────────────────────────────────────
   Every page used to show one spinner and then pop the whole layout in at
   once. These grey blocks stand in the shape of the content so the page
   settles instead of jumping. app.js swaps them into every .loading-page at
   boot; a page that renders its own loader can call skeletonHTML(). The
   shimmer is decorative and switched off under prefers-reduced-motion. */
.skeleton { width: 100%; padding: 4px 0 40px; }
.skel {
  display: block;
  background: var(--card-light);
  border-radius: 8px;
  position: relative;
  overflow: hidden;
}
.skel::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.05) 50%, transparent 100%);
  transform: translateX(-100%);
  animation: skel-shimmer 1.4s ease-in-out infinite;
}
@keyframes skel-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; } }
.skel-title { height: 28px; width: 220px; margin-bottom: 8px; }
.skel-sub   { height: 14px; width: 340px; margin-bottom: 28px; }
.skel-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 28px; }
.skel-stat  { height: 76px; border-radius: 12px; }
.skel-row   { height: 44px; margin-bottom: 8px; }
.skel-row:nth-child(odd) { opacity: .85; }
@media (max-width: 900px) { .skel-stats { grid-template-columns: repeat(2, 1fr); } .skel-sub { width: 70%; } }

/* ── Three motions, all purposeful (3 Sep 2026) ────────────────────────────
   content-in: what replaces a skeleton fades up 4px over 200ms, so a page
   settles rather than snapping. Applied by app.js ONLY during the first
   seconds after load — a re-render after a save must not re-animate.
   marker-land: a light just added to the diagram scales in from 70%, so the
   eye finds where it dropped on a busy canvas. Animated on an INNER <g>:
   the outer one carries the translate attribute, and a CSS transform there
   would override it and jump the light to the origin.
   The dashboard count-up is JS (app.js countUp). The global reduced-motion
   rule near the top collapses all three. */
@keyframes content-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.content-in { animation: content-in 0.2s ease-out both; }
@keyframes marker-land { from { transform: scale(0.7); opacity: 0.4; } to { transform: scale(1); opacity: 1; } }
.cfg-marker-body.cfg-marker-land { animation: marker-land 0.14s ease-out both; transform-box: fill-box; transform-origin: center; }

/* ── Line items on a document: the drag grip, multi-line descriptions ──────
   West Penn, 9 Sep 2026: "three little lines on the left side of each product
   that can be clicked and dragged and dropped" — the ▲▼ arrows are gone. The
   first grid column widened from 32px to 60px to hold the grip beside the
   line number; the last shrank back to 40px for × alone. touch-action: none
   on the grip is what makes a finger on it drag the LINE and not the page.
   A description keeps its line breaks on screen exactly as it prints. */
.qbo-row .line-actions { display: flex; justify-content: flex-end; align-items: center; gap: 2px; }
.line-grip {
  flex: 0 0 auto; width: 24px; height: 32px; min-height: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: 6px;
  color: var(--muted); cursor: grab; touch-action: none;
}
.line-grip span {
  display: block; width: 14px; height: 12px; border-radius: 1px;
  background: repeating-linear-gradient(to bottom, currentColor 0 2px, transparent 2px 5px);
}
.line-grip:hover { color: var(--white); background: var(--card-light); }
.line-grip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
body.is-dragging-line, body.is-dragging-line .line-grip { cursor: grabbing; }
body.is-dragging-line { user-select: none; -webkit-user-select: none; }
.qbo-row.dragging { opacity: .45; }
.qbo-row.drop-before { box-shadow: inset 0 3px 0 0 var(--accent); }
.qbo-row.drop-after { box-shadow: inset 0 -3px 0 0 var(--accent); }
.qbo-row .desc-cell { white-space: pre-wrap; word-break: break-word; }
.qbo-inline-textarea {
  width: 100%; min-height: 48px; resize: vertical; display: block;
  font: inherit; font-size: var(--text-sm); line-height: 1.4; padding: 6px 8px;
  background: var(--deep); color: var(--white); border: 1px solid var(--accent); border-radius: 6px;
}

/* ── Technician app ────────────────────────────────────────────────────────
   Kian, 4 Sep 2026: "make the tech app look super professional and inviting."
   A tech lives on a phone. The four places they go get a bottom tab bar there
   (the drawer keeps Help and Sign Out); My Jobs greets them by name and shows
   an hours bar per job; requests are cards, not a table squeezed to 375px. */
.tab-bar { display: none; }
@media (max-width: 900px) {
  .tab-bar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    display: flex; justify-content: space-around; align-items: stretch;
    background: var(--deep); border-top: 1px solid var(--border);
    padding: 6px 0 calc(6px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -8px 24px rgba(0,0,0,.35);
  }
  .tab-bar a {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
    padding: 6px 0 4px; min-height: 52px; color: var(--muted); text-decoration: none;
    font-family: var(--font-display); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
    position: relative;
  }
  .tab-bar a svg { width: 22px; height: 22px; }
  .tab-bar a.active { color: var(--accent); }
  .tab-bar a.active::before { content: ''; position: absolute; top: -7px; left: 30%; right: 30%; height: 2px; background: var(--accent); border-radius: 2px; }
  body.has-tab-bar .main { padding-bottom: calc(92px + env(safe-area-inset-bottom, 0px)); }
  body.has-tab-bar .install-banner { bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }
}

.tech-hero { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 22px 18px 6px; }
.tech-hero .hi { font-family: var(--font-display); font-size: var(--text-2xl); font-weight: 700; line-height: 1.1; }
.tech-hero .date { color: var(--muted); font-size: var(--text-sm); margin-top: 4px; }
.job-section { display: flex; align-items: baseline; justify-content: space-between; padding: 18px 18px 8px; }
.job-section h2 { font-family: var(--font-display); font-size: var(--text-xs); letter-spacing: .12em; text-transform: uppercase; color: var(--muted-light); margin: 0; }
.job-section .count { color: var(--muted); font-size: var(--text-xs); font-family: var(--font-mono); }
.job-progress { margin-top: 12px; }
.job-progress-track { height: 6px; background: var(--card-lighter); border-radius: 3px; overflow: hidden; }
.job-progress-fill { height: 100%; width: 0; background: var(--accent); border-radius: 3px; transition: width .4s ease-out; }
.job-progress-fill.over { background: var(--danger); }
.job-progress-lab { margin-top: 6px; font-size: var(--text-xs); color: var(--muted); display: flex; justify-content: space-between; }
.job-progress-lab strong { color: var(--muted-light); font-weight: 500; }

.req-list { display: grid; gap: 12px; }
.req-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 14px 16px; }
.req-card-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 8px; }
.req-card-part { font-weight: 600; font-size: var(--text-md); }
.req-card-meta { color: var(--muted); font-size: var(--text-sm); display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 6px; }
.req-card-note { margin-top: 10px; padding: 10px 12px; background: var(--deep); border-radius: var(--radius); font-size: var(--text-sm); color: var(--muted-light); font-style: italic; }

/* ── Line items on a phone (added 7 Sep 2026) ─────────────────────────────
   The seven-column grid above needs ~400px of fixed columns, so on a 375px
   phone the two flexible columns (name, description) collapsed to almost
   nothing and a description wrapped one letter per line (Kian's screenshot,
   EB2SP3J). Under 700px a line stacks: name and description across the
   width, quantity and rate on their own labelled line, the amount on the
   right, the header row gone — the labels travel with each line instead.
   Cell order is fixed by document.html: number, product, description,
   quantity, rate, amount, actions. */
@media (max-width: 700px) {
  .qbo-items-head { display: none; }
  .qbo-row {
    grid-template-columns: 24px 1fr 1fr auto;
    grid-template-areas:
      "num product product amount"
      "num desc    desc    desc"
      "num qty     rate    actions";
    gap: 4px 10px;
    padding: 12px 14px;
    align-items: start;
  }
  .qbo-row > :nth-child(1) { grid-area: num; padding-top: 2px; flex-direction: column; align-items: flex-start; gap: 2px; }
  .qbo-row > .product-cell { grid-area: product; min-width: 0; }
  .qbo-row > .desc-cell { grid-area: desc; min-width: 0; font-size: 13px; color: var(--muted-light); overflow-wrap: anywhere; }
  .qbo-row > :nth-child(4) { grid-area: qty; text-align: left; }
  .qbo-row > :nth-child(5) { grid-area: rate; text-align: left; }
  .qbo-row > :nth-child(4)::before { content: 'Qty '; color: var(--muted); font-family: var(--font-display); font-size: 11px; letter-spacing: .08em; }
  .qbo-row > :nth-child(5)::before { content: '@ '; color: var(--muted); }
  .qbo-row > .amount { grid-area: amount; font-weight: 700; white-space: nowrap; }
  .qbo-row > .line-actions { grid-area: actions; justify-self: end; }
  .qbo-row .product-cell .name { overflow-wrap: anywhere; }
}


/* ── Vehicle diagram on the work order page (diagram.js, 7 Sep 2026) ────── */
.diagram-plate { background: #f3f5f7; border-radius: 10px; padding: 10px; border: 1px solid var(--border); }
.diagram-frame { position: relative; width: 100%; height: 0; }
.diagram-frame img, .diagram-frame svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.diagram-frame img { object-fit: contain; }
.diagram-viewtags { display: flex; justify-content: space-between; margin-top: 6px; font-family: var(--font-display); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.diagram-lights { list-style: none; margin: 14px 0 0; padding: 0; }
.diagram-lights li { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 14px; }
.diagram-lights .swatch { width: 12px; height: 12px; border-radius: 3px; border: 1px solid rgba(255,255,255,.15); flex: none; }
.diagram-lights .qty { color: var(--muted); font-size: 13px; margin-left: auto; white-space: nowrap; font-family: var(--font-mono); }
.diagram-tag { font-family: var(--font-mono); font-size: 12px; background: var(--card-light); border: 1px solid var(--border-light); border-radius: 4px; padding: 1px 6px; min-width: 26px; text-align: center; color: var(--accent); }


/* ── Schedule day picker (7 Sep 2026) ─────────────────────────────────────── */
.dp { background: var(--card-light); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px; }
.dp-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.dp-head span { text-align: center; font-family: var(--font-display); font-size: 11px; letter-spacing: .08em; color: var(--muted); padding: 2px 0; }
.dp-day { min-height: 36px; border-radius: 6px; border: 1px solid var(--border); background: transparent; color: var(--white); font-family: var(--font-mono); font-size: 13px; cursor: pointer; padding: 0; }
.dp-day.we { color: var(--muted); }
.dp-day:hover { border-color: var(--accent); }
.dp-day.on { background: var(--accent); color: #0c0e11; border-color: var(--accent); font-weight: 700; }
.dp-summary { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; font-size: 13px; color: var(--muted-light); }


/* ── Row menu: the three dots on a Documents row (10 Sep 2026) ───────────── */
.t td.row-actions, .t th.row-actions-th { width: 44px; padding-left: 0; padding-right: 8px; text-align: right; }
.kebab {
  width: 36px; height: 36px; min-height: 0; padding: 0; border-radius: 8px;
  background: transparent; border: 1px solid transparent; color: var(--muted);
  font-size: 20px; line-height: 1; letter-spacing: 1px; cursor: pointer;
}
.kebab:hover, .kebab:focus-visible { color: var(--white); border-color: var(--border-light); background: var(--card-light); }
.row-menu {
  position: absolute; z-index: 60; min-width: 220px; padding: 6px;
  background: var(--card); border: 1px solid var(--border-light); border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, .45);
}
.row-menu-title { padding: 6px 10px 8px; font-size: 12px; color: var(--muted); border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.row-menu button {
  display: block; width: 100%; text-align: left; padding: 10px 12px; min-height: 40px;
  background: transparent; border: 0; border-radius: 6px; color: var(--white); font: inherit; font-size: 14px; cursor: pointer;
}
.row-menu button:hover, .row-menu button:focus-visible { background: var(--card-light); outline: none; }


/* ── The document title, edited in place (10 Sep 2026) ───────────────────── */
h1 .doc-title.editable { cursor: text; border-bottom: 1px dashed transparent; border-radius: 4px; padding: 0 4px; margin: 0 -4px; }
h1 .doc-title.editable:hover, h1 .doc-title.editable:focus-visible { border-bottom-color: var(--accent); background: var(--card-light); outline: none; }
h1 .doc-title.editing { border-bottom-color: transparent; background: transparent; flex: 1 1 320px; min-width: 240px; }
h1 .title-input { font: inherit; font-size: inherit; letter-spacing: inherit; width: 100%; min-height: 0; padding: 2px 8px; background: var(--deep); color: var(--white); border: 1px solid var(--accent); border-radius: 6px; }


/* ── Configurator: snap points, guides, hidden cursor while dragging (11 Sep 2026) ── */
body.cfg-dragging, body.cfg-dragging * { cursor: none !important; }
.cfg-canvas.editing-snaps { cursor: crosshair; }
.cfg-anchor .cfg-anchor-hint { fill: rgba(0, 229, 255, .18); stroke: var(--accent); stroke-width: 1.5; }
.cfg-anchor line { stroke: var(--accent); stroke-width: 1; opacity: .8; }
.cfg-anchor text { fill: var(--accent); font-family: var(--font-display); font-size: 11px; letter-spacing: .06em; }
.cfg-anchor.editing { cursor: pointer; }
.cfg-anchor.editing .cfg-anchor-hint { fill: rgba(0, 229, 255, .28); stroke-width: 2; }
.cfg-anchor.editing:hover .cfg-anchor-hint { fill: rgba(255, 68, 102, .35); stroke: var(--danger); }
.cfg-guide { stroke: var(--accent); stroke-dasharray: 6 5; opacity: .9; pointer-events: none; }


/* ── Insert below (the plus beside the number) and replace-by-name (11 Sep 2026) ── */
.line-insert {
  flex: 0 0 auto; width: 22px; height: 22px; min-height: 0; padding: 0; margin-left: 2px;
  border-radius: 50%; border: 1px solid var(--border-light); background: var(--card-light);
  color: var(--muted-light); font-size: 16px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity .12s;
}
.qbo-row:hover .line-insert, .line-insert:focus-visible, .line-insert[data-popmenu-anchor] { opacity: 1; }
.line-insert:hover, .line-insert:focus-visible { color: var(--accent); border-color: var(--accent); outline: none; }
@media (hover: none) { .qbo-row .line-insert { opacity: 1; } }
.qbo-row .product-cell .name-swap {
  background: none; border: 0; padding: 0; min-height: 0; color: inherit; font: inherit; font-weight: inherit;
  text-align: left; cursor: pointer; border-bottom: 1px dashed transparent; overflow-wrap: anywhere;
}
.qbo-row .product-cell .name-swap:hover, .qbo-row .product-cell .name-swap:focus-visible { border-bottom-color: var(--accent); color: var(--accent); outline: none; }
