/* ═════════════════════════════════════════════════════════════════════════════
   js/jobs-list-redesign.css — WIP-UI-JOBS-LIST-01
   Compact header + consistent KPIs + phase mini-bar + quick actions
   for the Jobs list page. Lift target: spartan-jobs-list-redesign-v1.html.

   All new classes are `jl-*` prefixed to avoid collisions with existing
   .kpi (spartan-tokens.css line 383) and .spill (polish.css line 297).
   Overdue pulse classes (.is-overdue-row, .pill-overdue, .badge-overdue)
   stay generic — they're shared with sidebar badges and other pages.

   Loads AFTER styles.css / portal-theme.css / spartan-tokens.css /
   polish.css / job-detail-redesign.css so the section overrides win
   cleanly inside `.jl-page`.
   ═════════════════════════════════════════════════════════════════════════════ */

/* ── OVERDUE PULSE (mandatory per design constitution) ────────────────────── */
/* A-806 — ONE calm signal per row, not five competing ones.
   Graham (2026-09-14, video): "way too much pulsing ... it needs to be cleaner".
   The row-background pulse is gone outright: it animated `background` on a whole
   <tr>, so every overdue row repainted in full every frame, and with 20+ rows on
   screen at three unsynchronised periods (1.4s / 1.8s / 2.4s) the list shimmered.
   The overdue state is now carried STATICALLY — a soft tint + the inset accent
   bar below — which is legible at a glance and costs nothing per frame.
   The single surviving motion is pulse-overdue-dot, slowed and softened, and it
   is allowed on exactly one element per row (.pill-overdue .dot). */
@keyframes pulse-overdue-dot {
  0%, 100% { box-shadow: 0 0 0 0 rgba(161,98,7,.38); }
  70%      { box-shadow: 0 0 0 6px rgba(161,98,7,0); }
}
.is-overdue-row {
  background: rgba(250,204,21,.05);
  position: relative;
}
.is-overdue-row > td:first-child {
  box-shadow: inset 3px 0 0 var(--red, #a16207);
}
.pill-overdue {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--pale-red, #fefce8);
  color: var(--red, #a16207);
  padding: 2px 7px 2px 6px;
  border-radius: 5px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  box-shadow: inset 0 0 0 1px rgba(161,98,7,.25);
  white-space: nowrap;
}
.pill-overdue .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--red, #a16207);
}
/* A-806 — motion only during the boot window (see _spBootPulseWindow in
   modules/99-init.ts). No class, no motion: the safe direction is the quiet one. */
body.sp-boot-pulse .pill-overdue .dot {
  animation: pulse-overdue-dot 2.5s ease-in-out infinite;
}
.badge-overdue {
  background: var(--fos-plate);
  color: var(--fos-on-plate);
  font-size: 10px;
  font-weight: 800;
  border-radius: 10px;
  padding: 1px 7px;
}
@media (prefers-reduced-motion: reduce) {
  .pill-overdue .dot {
    animation: none !important;
  }
}

/* ── JOBS LIST PAGE CONTAINER ─────────────────────────────────────────────── */
.jl-page {
  padding: 4px 0 48px;
}

/* ── COMPACT PAGE HEADER ─────────────────────────────────────────────────── */
.jl-ph {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--s200, #E7E5E4);
}
.jl-ph-l {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}
.jl-ph-eyebrow {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--s400, #A8A29E);
  line-height: 1;
}
.jl-ph-h1-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}
.jl-ph-h1 {
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.025em;
  color: var(--ink, #0A0A0A);
  line-height: 1;
  margin: 0;
  font-family: var(--ff-sans, 'Archivo', system-ui, sans-serif);
}
.jl-ph-h1-meta {
  font-size: 14px;
  color: var(--s500, #78716C);
  font-weight: 600;
}
.jl-ph-h1-meta b {
  color: var(--ink, #0A0A0A);
  font-weight: 800;
}
.jl-ph-r {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* View switcher (Table / Kanban / Map) */
.jl-view-sw {
  display: flex;
  background: var(--s100, #F5F5F4);
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
}
.jl-view-sw button {
  padding: 6px 11px;
  font-size: 12px;
  font-weight: 700;
  color: var(--s500, #78716C);
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}
.jl-view-sw button:hover { color: var(--ink, #0A0A0A); }
.jl-view-sw button.jl-on {
  background: #fff;
  color: var(--ink, #0A0A0A);
  box-shadow: 0 1px 2px rgba(10,10,10,.03), 0 1px 1px rgba(10,10,10,.02);
}

/* ── KPI STRIP (consistent, compact) ──────────────────────────────────────── */
.jl-kpi-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
  margin-bottom: 20px;
}
.jl-kpi {
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 12px;
  padding: 14px 16px;
  cursor: pointer;
  transition: box-shadow 180ms cubic-bezier(.4,0,.2,1),
              transform 180ms cubic-bezier(.4,0,.2,1),
              border-color 180ms cubic-bezier(.4,0,.2,1);
  box-shadow: var(--shadow-rest, 0 1px 2px rgba(10,10,10,.03), 0 1px 1px rgba(10,10,10,.02));
  position: relative;
  overflow: hidden;
}
.jl-kpi:hover {
  border-color: var(--s300, #D6D3D1);
  box-shadow: var(--shadow-hover, 0 1px 2px rgba(10,10,10,.04), 0 12px 28px -12px rgba(10,10,10,.18));
  transform: translateY(-2px);
}
.jl-kpi-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.jl-kpi-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--s500, #78716C);
}
.jl-kpi-ico {
  width: 24px;
  height: 24px;
  border-radius: 7px;
  background: var(--s100, #F5F5F4);
  color: var(--s500, #78716C);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
}
.jl-kpi:hover .jl-kpi-ico {
  background: var(--ink, #0A0A0A);
  color: #fff;
}
.jl-kpi-val {
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.025em;
  color: var(--ink, #0A0A0A);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
  line-height: 1.1;
}
.jl-kpi-val.jl-red { color: var(--red, #a16207); }
.jl-kpi-sub {
  font-size: 11px;
  color: var(--s500, #78716C);
  margin-top: 4px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 5px;
}
.jl-kpi-sub.jl-up   { color: var(--ok-fg, #15803D); }
.jl-kpi-sub.jl-down { color: var(--red, #a16207); }
.jl-kpi-sub.jl-red  { color: var(--red, #a16207); }
.jl-kpi-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--s100, #F5F5F4);
}
.jl-kpi-bar-fill {
  height: 100%;
  background: var(--ink, #0A0A0A);
  transition: width 400ms cubic-bezier(.4,0,.2,1);
}
.jl-kpi.jl-alert .jl-kpi-bar-fill { background: var(--red, #a16207); }
.jl-kpi.jl-alert .jl-kpi-val      { color: var(--red, #a16207); }
.jl-kpi-sub .jl-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--red, #a16207);
}
@media (max-width: 1180px) { .jl-kpi-row { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 740px)  { .jl-kpi-row { grid-template-columns: repeat(2, 1fr); } }

/* ── FILTER BAR ───────────────────────────────────────────────────────────── */
.jl-fbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 0 16px;
  flex-wrap: wrap;
}
.jl-seg {
  display: flex;
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
  box-shadow: var(--shadow-rest, 0 1px 2px rgba(10,10,10,.03));
}
.jl-seg button {
  padding: 7px 12px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--s500, #78716C);
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  letter-spacing: -0.005em;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}
.jl-seg button:hover { color: var(--ink, #0A0A0A); }
.jl-seg button.jl-on {
  background: var(--ink, #0A0A0A);
  color: #fff;
}
.jl-seg-count {
  font-size: 10px;
  font-weight: 800;
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--s100, #F5F5F4);
  color: var(--s600, #57534E);
  letter-spacing: 0;
}
.jl-seg button.jl-on .jl-seg-count {
  background: rgba(255,255,255,.18);
  color: #fff;
}
.jl-seg button:hover .jl-seg-count {
  background: var(--s200, #E7E5E4);
  color: var(--ink, #0A0A0A);
}

/* Filter chips */
.jl-chips {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.jl-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--s700, #44403C);
  cursor: pointer;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  box-shadow: var(--shadow-rest, 0 1px 2px rgba(10,10,10,.03));
  font-family: inherit;
}
.jl-chip:hover {
  border-color: var(--ink, #0A0A0A);
  color: var(--ink, #0A0A0A);
  transform: translateY(-1px);
  box-shadow: var(--shadow-hover, 0 1px 2px rgba(10,10,10,.04), 0 12px 28px -12px rgba(10,10,10,.18));
}
.jl-chip.jl-on {
  background: var(--ink, #0A0A0A);
  color: #fff;
  border-color: var(--ink, #0A0A0A);
}
.jl-chip-c {
  font-size: 10px;
  font-weight: 800;
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--s100, #F5F5F4);
  color: var(--s600, #57534E);
}
.jl-chip.jl-on .jl-chip-c {
  background: rgba(255,255,255,.2);
  color: #fff;
}
.jl-chip.jl-alert {
  background: var(--pale-red, #fefce8);
  border-color: rgba(161,98,7,.25);
  color: var(--red, #a16207);
}
.jl-chip.jl-alert:hover {
  background: var(--fos-plate);
  color: var(--fos-on-plate);
}
.jl-chip.jl-alert .jl-chip-c {
  background: rgba(161,98,7,.15);
  color: var(--red, #a16207);
}
.jl-chip.jl-alert:hover .jl-chip-c {
  background: rgba(0,0,0,.12);
  color: var(--fos-on-plate);
  animation: none;
}

.jl-fbar-r {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 10px;
}
.jl-fbar-search {
  position: relative;
}
.jl-fbar-search input {
  padding: 8px 12px 8px 34px;
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 10px;
  font-size: 12.5px;
  min-width: 240px;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  box-shadow: var(--shadow-rest, 0 1px 2px rgba(10,10,10,.03));
  font-family: inherit;
  color: var(--ink, #0A0A0A);
}
.jl-fbar-search input:focus {
  outline: none;
  border-color: var(--ink, #0A0A0A);
  box-shadow: 0 0 0 3px rgba(10,10,10,.05);
}
.jl-fbar-search .ico {
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--s400, #A8A29E);
  pointer-events: none;
}

/* ── TABLE ───────────────────────────────────────────────────────────────── */
.jl-tbl-wrap {
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: var(--shadow-rest, 0 1px 2px rgba(10,10,10,.03));
}
.jl-tbl {
  /* A-806 — was width:100%. That made the table's width depend on the card and
     the card's fit-content width depend on the table, so the card could only
     ever resolve to the full page width and 11 columns stretched across a
     2560px monitor. Content-sized here; the card hugs it (.jl-tbl-wrap above)
     and clamps to the page, which is what "resize to suit how many columns"
     means in both directions. */
  width: auto;
  border-collapse: collapse;
  table-layout: auto;
}
/* The phone/card layout is not a table and must still span the page. */
.jl-tbl.jl-cards { width: 100%; }
.jl-tbl thead {
  background: var(--s50, #FAFAF9);
  border-bottom: 1px solid var(--s200, #E7E5E4);
}
.jl-tbl th {
  padding: 12px 14px;
  text-align: left;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.20em;
  text-transform: uppercase;
  color: var(--s500, #78716C);
  white-space: nowrap;
}
.jl-tbl th.jl-r { text-align: right; }
.jl-tbl th.jl-c { text-align: center; }
.jl-tbl td {
  padding: 14px;
  font-size: 13px;
  border-bottom: 1px solid var(--s100, #F5F5F4);
  vertical-align: middle;
}
.jl-tbl tbody tr {
  transition: background 180ms cubic-bezier(.4,0,.2,1);
  cursor: pointer;
  position: relative;
}
.jl-tbl tbody tr:hover { background: var(--s50, #FAFAF9); }
.jl-tbl tbody tr:last-child td { border-bottom: 0; }
.jl-tbl tbody tr td:first-child { padding-left: 18px; }
.jl-tbl tbody tr td:last-child  { padding-right: 18px; }

/* Compact density */
.jl-tbl.jl-dense td { padding: 10px 14px; }
.jl-tbl.jl-dense tbody tr td:first-child { padding-left: 14px; }
.jl-tbl.jl-dense tbody tr td:last-child  { padding-right: 14px; }

/* Checkbox */
.jl-cb {
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--s300, #D6D3D1);
  border-radius: 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  background: #fff;
  vertical-align: middle;
}
.jl-cb:hover { border-color: var(--ink, #0A0A0A); }
.jl-cb.jl-on {
  background: var(--ink, #0A0A0A);
  border-color: var(--ink, #0A0A0A);
  color: #fff;
}

/* Job ref */
.jl-jref {
  font-family: 'Archivo Narrow', var(--ff-sans, 'Archivo', sans-serif);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  background: var(--s100, #F5F5F4);
  color: var(--s700, #44403C);
  padding: 3px 8px;
  border-radius: 6px;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  white-space: nowrap;
  display: inline-block;
}
.jl-tbl tbody tr:hover .jl-jref {
  background: var(--ink, #0A0A0A);
  color: #fff;
}

/* Client cell */
.jl-client-cell { min-width: 0; }
/* A-806 — the row box is fixed so the virtual scroller's `start * rowH`
   reservation is EXACT rather than a mean. Content that would exceed it is
   clipped by the cell above, not allowed to push the row taller. `height` on a
   <tr> is a minimum in table layout, so the cap lives on the cell content. */
.jl-tbl tbody tr > td { overflow: hidden; }
.jl-client-name {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ink, #0A0A0A);
  letter-spacing: -0.005em;
  line-height: 1.2;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.jl-client-meta {
  font-size: 11px;
  color: var(--s500, #78716C);
  margin-top: 3px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 6px;
  /* A-575: this line carries the SALES REP as well as the phone now. Without
     wrapping, the two fight for a narrow cell and the phone number breaks
     mid-value ("0412 887 / 340"). Each child therefore stays intact
     (`.jl-client-meta > * { white-space: nowrap }` below) — that is what stops
     the mid-value break, and it still holds.
     A-806 — but the SECOND LINE has to go. This is the only cell in the row
     whose height varies, and it made row heights bimodal: measured on the
     harness at 300 jobs, 83px when the meta fits on one line and 97px when it
     wraps. The virtual scroller reserves `start * rowH` from a SINGLE mean row
     height (88px), so every slice whose mix of short/tall rows differed from
     that mean re-scaled the document underneath the reader — 27 layout shifts
     and CLS 0.468 over one scroll-through. That is the "jitters and bumps up
     and down" report, and a uniform row makes the reservation exact instead of
     approximate.
     Clipping rather than wrapping loses nothing important: each child is still
     whole, the rep leads the line, and the same columns removed from the
     default set (see _JL_OPTIONAL_COLS) give this cell far more width than it
     had, so at the default layout the line fits without clipping at all. */
  flex-wrap: nowrap;
  overflow: hidden;
  min-width: 0;
}
.jl-client-meta > * { white-space: nowrap; }
.jl-client-meta .ico { color: var(--s400, #A8A29E); }

/* Branch (with suburb merged in) */
.jl-branch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--s700, #44403C);
  letter-spacing: 0.04em;
}
.jl-branch-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
}
.jl-branch-vic .jl-branch-dot { background: #1D4ED8; }
.jl-branch-act .jl-branch-dot { background: #15803D; }
.jl-branch-sa  .jl-branch-dot { background: #D97706; }
.jl-branch-tas .jl-branch-dot { background: #7C3AED; }
.jl-branch-sub {
  font-size: 11px;
  color: var(--s400, #A8A29E);
  font-weight: 500;
  display: block;
  margin-top: 2px;
  letter-spacing: 0;
}

/* A-600 — dispatch bay, on its own line under the status pill. Cyan, matching
   the Production Kanban's IN DISPATCH column + bay plates, so the same job
   reads the same on the board and in the list. Only rendered while the job
   actually holds a bay. */
.jl-bay {
  /* block-level flex + shrink-to-fit width = its own line, hugging its text
     (the status pill above is inline-flex and would otherwise sit beside it) */
  display: flex;
  width: max-content;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  background: #ECFEFF;
  color: #0E7490;
  border: 1px solid #A5F3FC;
  white-space: nowrap;
}
.jl-bay::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 2px;
  background: #06B6D4;
}

/* A-611 — SUPPLY ONLY vs INSTALL, on every row.
   Phoenix: "on the jobs list it should clearly say if its supply only or
   install". BOTH kinds are badged, not just the supply-only ones — an unbadged
   row would read as "nobody has said", which is the exact ambiguity being
   removed. Its own line under the job ref so it never competes with the ref for
   width, and shrink-to-fit so it hugs its text (the ref above is inline-block
   and would otherwise sit beside it). */
.jl-kind {
  display: flex;
  width: max-content;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  padding: 2px 7px;
  border-radius: 6px;
  font-family: 'Archivo Narrow', var(--ff-sans, 'Archivo', sans-serif);
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  white-space: nowrap;
}
.jl-kind::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 2px;
}
/* Supply only — cyan, matching the supply-only job page's accent so the two
   surfaces read as the same thing. */
.jl-kind-supply {
  color: #0E7490;
  background: #CFFAFE;
  border: 1px solid #A5F3FC;
}
.jl-kind-supply::before { background: #0891B2; }
/* Install — deliberately quiet. It is the overwhelming majority, so it must be
   readable without turning the list into a wall of colour. */
.jl-kind-install {
  color: var(--s700, #44403C);
  background: var(--s100, #F5F5F4);
  border: 1px solid var(--s200, #E7E5E4);
}
.jl-kind-install::before { background: var(--s400, #A8A29E); }
/* Missing an opening depth. Supply-only only, and a warning rather than a
   blocker: Phoenix chose warn-never-block, so this never stops the job — it just
   means a depth nobody has agreed to. */
.jl-kind-warn {
  color: #92400E;
  background: #FEF3C7;
  border: 1px solid #FCD34D;
}
.jl-kind-warn::before { background: #D97706; }

/* Phase mini-bar — the 3-phase indicator */
.jl-phx {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 88px;
}
.jl-phx-seg {
  height: 4px;
  flex: 1;
  border-radius: 2px;
  background: var(--s200, #E7E5E4);
  transition: all 180ms cubic-bezier(.4,0,.2,1);
}
.jl-phx-seg.jl-done { background: var(--ink, #0A0A0A); }
.jl-phx-seg.jl-now {
  background: var(--red, #a16207);
  box-shadow: 0 0 8px rgba(250,204,21,.4);
}
.jl-phx-meta {
  font-size: 10.5px;
  color: var(--s500, #78716C);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 5px;
  display: block;
}
.jl-phx-meta b { color: var(--ink, #0A0A0A); }

/* Status pill (lifted from mockup's `.spill` — namespaced to avoid the
   polish.css `.spill` grid-cell collision) */
.jl-spill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.06);
}
.jl-spill .jl-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
}
.jl-spill.jl-info {
  background: var(--info-bg, #DBEAFE);
  color: var(--info-fg, #1D4ED8);
  box-shadow: inset 0 0 0 1px rgba(29,78,216,.15);
}
.jl-spill.jl-info .jl-dot { background: var(--info-fg, #1D4ED8); }
.jl-spill.jl-warn {
  background: var(--warn-bg, #FEF3C7);
  color: var(--warn-fg, #92400E);
  box-shadow: inset 0 0 0 1px rgba(146,64,14,.18);
}
.jl-spill.jl-warn .jl-dot { background: var(--warn-fg, #92400E); }
.jl-spill.jl-ok {
  background: var(--ok-bg, #DCFCE7);
  color: var(--ok-fg, #15803D);
  box-shadow: inset 0 0 0 1px rgba(21,128,61,.18);
}
.jl-spill.jl-ok .jl-dot { background: var(--ok-fg, #15803D); }
.jl-spill.jl-purple {
  background: #F3E8FF;
  color: #6D28D9;
  box-shadow: inset 0 0 0 1px rgba(109,40,217,.15);
}
.jl-spill.jl-purple .jl-dot { background: #6D28D9; }
.jl-spill.jl-grey {
  background: var(--s100, #F5F5F4);
  color: var(--s600, #57534E);
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.08);
}
.jl-spill.jl-grey .jl-dot { background: var(--s500, #78716C); }

/* Money */
.jl-money {
  font-size: 14px;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: var(--ink, #0A0A0A);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
  text-align: right;
  display: block;
}
.jl-money-sub {
  font-size: 10.5px;
  color: var(--s400, #A8A29E);
  font-weight: 600;
  margin-top: 2px;
  text-align: right;
}

/* Next milestone */
.jl-next {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.jl-next-label {
  font-size: 9.5px;
  font-weight: 700;
  color: var(--s400, #A8A29E);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.jl-next-val {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink, #0A0A0A);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.005em;
}
.jl-next-val.jl-muted {
  color: var(--s400, #A8A29E);
  font-weight: 500;
}
.jl-next-val.jl-red { color: var(--red, #a16207); }

/* Age */
.jl-age-cell { text-align: center; }
.jl-age-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  background: var(--s100, #F5F5F4);
  color: var(--s700, #44403C);
  border-radius: 5px;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.jl-age-pill.jl-warn {
  background: var(--warn-bg, #FEF3C7);
  color: var(--warn-fg, #92400E);
}
.jl-age-pill.jl-red {
  background: var(--pale-red, #fefce8);
  color: var(--red, #a16207);
  box-shadow: inset 0 0 0 1px rgba(161,98,7,.25);
}
.jl-age-pill .jl-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}
.jl-age-pill.jl-red .jl-dot { /* A-806 — static; .pill-overdue on the same row already carries the one pulse. */ }

/* Quick-action cluster. A-450: was opacity:0 by default, only fading in on
   tbody tr:hover — nothing to hover on touch/tablet, and easy to miss even
   with a mouse (same bug already fixed on the Leads list, A-439). Always
   visible now; sized to match every other redone page's quick-action buttons. */
.jl-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  justify-content: flex-end;
}
.jl-qa-btn {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  color: var(--s600, #57534E);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  cursor: pointer;
  padding: 0;
  font-family: inherit;
}
.jl-qa-btn .ico { width: 15px; height: 15px; }
.jl-qa-btn:hover {
  background: var(--ink, #0A0A0A);
  color: #fff;
  border-color: var(--ink, #0A0A0A);
  transform: translateY(-1px);
}
.jl-qa-btn.jl-red:hover {
  background: var(--fos-plate);
  border-color: var(--fos-plate);
  color: var(--fos-on-plate);
}

/* Material flag pill */
.jl-matflag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px;
  background: var(--pale-red, #fefce8);
  color: var(--red, #a16207);
  border-radius: 5px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  box-shadow: inset 0 0 0 1px rgba(161,98,7,.25);
  cursor: help;
}
.jl-matflag .ico {
  width: 11px;
  height: 11px;
}
/* SC-G7 — UNKNOWN, not "not ordered".
   A job with no factory order has nothing the CRM can confirm from, and
   MaterialTruth deliberately refuses to claim 'not_ordered' there. Painting
   that the same red as a resolved fact would cry wolf across hundreds of
   Ascora imports and train everyone to ignore the flag — the way the
   comms-breach lights went. Slate says "somebody has to check this" without
   claiming to know the answer; red stays for the ones we do know. */
.jl-matflag-unknown {
  background: var(--s100, #F5F5F4);
  color: var(--s600, #57534E);
  box-shadow: inset 0 0 0 1px rgba(87,83,78,.22);
}

/* Table footer (density toggle + counts) */
.jl-tbl-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  background: var(--s50, #FAFAF9);
  border-top: 1px solid var(--s200, #E7E5E4);
  font-size: 12px;
  color: var(--s500, #78716C);
  font-weight: 600;
}
.jl-tbl-foot-l {
  display: flex;
  align-items: center;
  gap: 14px;
}
.jl-tbl-foot-r {
  display: flex;
  align-items: center;
  gap: 6px;
}
.jl-tbl-foot-r button {
  padding: 5px 10px;
  border-radius: 7px;
  border: 1px solid var(--s200, #E7E5E4);
  background: #fff;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--s600, #57534E);
  transition: all 180ms cubic-bezier(.4,0,.2,1);
  cursor: pointer;
  font-family: inherit;
}
.jl-tbl-foot-r button:hover {
  border-color: var(--ink, #0A0A0A);
  color: var(--ink, #0A0A0A);
}
.jl-tbl-foot-r button.jl-on {
  background: var(--ink, #0A0A0A);
  color: #fff;
  border-color: var(--ink, #0A0A0A);
}

/* "Coming soon" stub for Kanban / Map views */
.jl-stub {
  background: #fff;
  border: 1px dashed var(--s300, #D6D3D1);
  border-radius: 14px;
  padding: 64px 24px;
  text-align: center;
  color: var(--s500, #78716C);
}
.jl-stub-title {
  font-size: 18px;
  font-weight: 800;
  color: var(--ink, #0A0A0A);
  margin-bottom: 6px;
  letter-spacing: -0.015em;
}
.jl-stub-desc {
  font-size: 13px;
  font-weight: 500;
  max-width: 420px;
  margin: 0 auto;
  line-height: 1.5;
}

/* Bulk-action bar (preserved from legacy renderer's design) */
.jl-bulk-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
  padding: 10px 16px;
  background: var(--pale-red, #fefce8);
  border: 1px solid rgba(161,98,7,.18);
  border-radius: 10px;
  font-size: 13px;
  font-weight: 700;
  color: var(--red, #a16207);
}

/* ── Sortable header cells (Phoenix request 2026-05-21) ───────────────────── */
.jl-tbl thead th.jl-th-sortable {
  cursor: pointer;
  user-select: none;
  transition: background 180ms cubic-bezier(.4,0,.2,1),
              color 180ms cubic-bezier(.4,0,.2,1);
}
.jl-tbl thead th.jl-th-sortable:hover {
  background: var(--s100, #F5F5F4);
  color: var(--ink, #0A0A0A);
}
.jl-tbl thead th.jl-th-sortable.jl-sorted {
  color: var(--ink, #0A0A0A);
  background: var(--s100, #F5F5F4);
}
/* S-261 — drag-to-reorder columns: drop-target indicator while a header is
   dragged over another. The dragged header keeps the pointer cursor (click
   still sorts); the target shows a red insertion edge. */
.jl-tbl thead th.jl-th-dragover {
  box-shadow: inset 3px 0 0 var(--red, #a16207);
  background: rgba(250,204,21,.06);
}
.jl-sort-arrow {
  color: var(--red, #a16207);
  font-size: 8px;
  letter-spacing: 0;
  margin-left: 3px;
  display: inline-block;
  vertical-align: middle;
}

/* ── Date column cells (Created / CM / Aluplast / Glass / Production /
       Install) — Phoenix request 2026-05-21 ─────────────────────────────── */
.jl-tbl td.jl-date-cell {
  padding: 14px 10px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
}
.jl-tbl.jl-dense td.jl-date-cell { padding: 10px 10px; }
.jl-date-val {
  font-size: 12px;
  font-weight: 700;
  color: var(--ink, #0A0A0A);
  letter-spacing: -0.005em;
}
.jl-date-val.jl-muted {
  color: var(--s400, #A8A29E);
  font-weight: 500;
}
.jl-date-val.jl-red {
  color: var(--red, #a16207);
}
.jl-date-val.jl-bold {
  color: var(--ink, #0A0A0A);
  font-weight: 800;
}

/* ── S-298: click-to-edit date cells ─────────────────────────────────────────
   The six key-date columns are editable in place. Hover shows a pencil + a
   soft outline so the affordance is discoverable without cluttering every
   cell; the swapped-in picker is styled to sit flush in the cell. */
.jl-tbl td.jl-editable { cursor: pointer; position: relative; }
.jl-tbl td.jl-editable:hover {
  background: #fff7f8;
  box-shadow: inset 0 0 0 1px #f3c1c9;
}
.jl-tbl td.jl-editable:hover::after {
  content: '✎';
  position: absolute;
  top: 4px;
  right: 6px;
  font-size: 10px;
  color: #a16207;
  opacity: .75;
}
.jl-date-inline-input {
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 4px 6px;
  border: 1.5px solid var(--red, #a16207);
  border-radius: 6px;
  width: 132px;
  max-width: 100%;
  background: #fff;
  color: var(--ink, #0A0A0A);
}

/* Allow horizontal scroll on small viewports — the extended layout has
   15 columns and won't fit on narrow screens. */
/* A-806 — "The page should resize to suit how many columns we have."
   The card is sized by its CONTENT, then clamped to the page. Turn columns off
   and it narrows to fit them instead of stretching a handful of columns across
   a 2560px monitor; turn columns on and it grows, up to the full page width
   (the 1400px shell cap is lifted for this page — see modules/99-init.ts), and
   only past that does overflow-x scrolling start.
   max-width, not width, so the flex/normal flow above it is unaffected. */
.jl-tbl-wrap {
  width: fit-content;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
}
.jl-tbl-wrap::-webkit-scrollbar { height: 8px; }
.jl-tbl-wrap::-webkit-scrollbar-thumb {
  background: var(--s300, #D6D3D1);
  border-radius: 4px;
}
.jl-tbl-wrap::-webkit-scrollbar-thumb:hover { background: var(--s400, #A8A29E); }

/* A-630 — the left/right scrollbar ABOVE the table.
   The bar the browser puts on .jl-tbl-wrap is at the BOTTOM of that element,
   and that element is the whole list — measured on prod: 4956px tall in a
   1138px viewport, so the bar sat ~3800px below the fold and nobody could
   reach it. The right-hand columns (Production, Expected, Install …) just read
   as cut off. This bar sits where the columns are.
   The inner spacer is sized to the table's real width in _jlHScrollSync
   (22-jobs-page.js) — that is what gives this empty div a scrollbar at all. */
.jl-hbar { display: none; }
.jl-hbar.jl-hbar-on {
  display: block;
  height: 14px;
  margin: 0 0 6px;
  overflow-x: auto;
  overflow-y: hidden;
  border-radius: 8px;
  background: var(--s100, #F5F5F4);
  /* Never a vertical scroll target, and never a swipe-back gesture. */
  overscroll-behavior-x: contain;
}
.jl-hbar-in { height: 1px; }
/* Always-visible thumb — the whole point of this bar is the affordance, so it
   must not be an overlay scrollbar that only appears once you already scrolled. */
.jl-hbar::-webkit-scrollbar { height: 14px; }
.jl-hbar::-webkit-scrollbar-track { background: transparent; }
.jl-hbar::-webkit-scrollbar-thumb {
  background: var(--s400, #A8A29E);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: 8px;
}
.jl-hbar::-webkit-scrollbar-thumb:hover {
  background: var(--s500, #78716C);
  background-clip: content-box;
}
/* Firefox has no ::-webkit pseudo-elements; its bar is always drawn anyway. */
@supports (scrollbar-width: auto) {
  .jl-hbar.jl-hbar-on { scrollbar-width: auto; scrollbar-color: var(--s400, #A8A29E) transparent; }
}

/* A-630 — the footer lives INSIDE the horizontal scroller, so without this it
   slides off to the left as soon as you scroll right. */
.jl-tbl-foot { position: sticky; left: 0; }

/* A-600b: the "no windows uploaded" red pulse lives ONLY on the Production
   Queue (.pqx-nowin in modules/56-production-queue.js). Removed from this
   list on Graham's instruction 2026-08-11 — do not re-add .jl-nowin-pulse. */

/* ═══════════════════════════════════════════════════════════════════════════
 * A-668 · PHONE / FOLDABLE LAYOUT  (Jobs list · /jobs)
 * ═══════════════════════════════════════════════════════════════════════════
 * The automated sweep found this page the worst non-map overflow in the app:
 * 35px sideways at 344×882 (Galaxy Z Fold cover screen), with the "Columns"
 * button's right edge 12px off the viewport. Two root causes, both structural:
 *
 *   1. `.jl-fbar-r` is a no-wrap flex row holding a status popover, a search
 *      input with `min-width:240px` and two buttons. At 344px that is ~350px of
 *      content in a ~312px content box, and because the row cannot wrap the
 *      overflow escapes to the document — so the BODY scrolled sideways and
 *      every position:fixed element juddered against it.
 *   2. `.jl-ph` is `justify-content:space-between`, so the five bulk-import
 *      buttons (labels of 200px+) squeezed the title column to ~100px and the
 *      "7 active · 2 in Pre-Install · …" line broke over six lines.
 *
 * Everything below is fenced behind `html[data-vp="xs"]` / `[data-vp="sm"]`
 * (stamped by modules/_lib/viewport.js before the first paint), so the desktop
 * and tablet layouts are byte-for-byte what they were. `sm` is included with
 * `xs` throughout because the renderer's own branch is `SpartanVP.phone()`,
 * which is xs|sm — the two must agree or the card markup lands in a table.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── Popover geometry, all tiers ──────────────────────────────────────────
 * The Status and Columns popovers used to carry their width, position and
 * shadow as an INLINE style. An inline width cannot be overridden by any
 * stylesheet, so a 270px panel stayed 270px on a 344px screen. The renderer
 * now emits `.jl-drop` and only keeps `display` inline (that is what the
 * toggle handlers write), which puts the geometry back under CSS control. */
.jl-drop-wrap { position: relative; }
.jl-drop {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 12px;
  box-shadow: 0 12px 36px rgba(17,24,39,.14);
  z-index: 80;
}
.jl-drop-status { width: 270px; max-height: 420px; overflow-y: auto; padding: 8px 10px; }
.jl-drop-cols   { width: 210px; padding: 10px 12px; }
.jl-colopt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 2px;
  font-size: 12.5px;
  color: #374151;
  cursor: pointer;
  font-weight: 600;
}
.jl-colopt input[type="checkbox"] { width: 15px; height: 15px; flex: 0 0 auto; }

/* The phone sort control that stands in for the (hidden) sortable header row.
 * A native <select> on purpose: the OS gives it a full-height picker wheel,
 * which beats anything we could draw for a thumb. */
.jl-sort-sel {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: #fff;
  border: 1px solid var(--s200, #E7E5E4);
  border-radius: 10px;
  padding: 0 4px 0 10px;
  box-shadow: var(--shadow-rest, 0 1px 2px rgba(10,10,10,.03));
}
.jl-sort-sel > span {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--s400, #A8A29E);
}
.jl-sort-sel select {
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink, #0A0A0A);
  padding: 8px 4px;
  min-height: 40px;
}

/* ── 1 · Page header ─────────────────────────────────────────────────────── */
html[data-vp="xs"] .jl-ph,
html[data-vp="sm"] .jl-ph {
  /* Stack instead of space-between: the title column gets the full width back,
     so the eyebrow stops breaking into three lines and the active-jobs summary
     reads as one sentence. */
  flex-direction: column;
  align-items: stretch;
  gap: 14px;
  margin-bottom: 16px;
  padding-bottom: 14px;
}
html[data-vp="xs"] .jl-ph-h1 { font-size: 24px; }
html[data-vp="xs"] .jl-ph-h1-meta,
html[data-vp="sm"] .jl-ph-h1-meta { font-size: 12.5px; }
html[data-vp="xs"] .jl-ph-eyebrow,
html[data-vp="sm"] .jl-ph-eyebrow { letter-spacing: 0.16em; }
html[data-vp="xs"] .jl-ph-r,
html[data-vp="sm"] .jl-ph-r { gap: 8px; }
/* New Job is the one action worth a full-width tap target on a phone. */
html[data-vp="xs"] .jl-ph-r > .btn-r { flex: 1 1 100%; justify-content: center; }

/* The "More actions" disclosure that now holds Export + the Ascora/CM imports.
   Closed it is one 44px button; open it is a stack of full-width buttons, which
   is the only way five 200px labels fit a 344px screen without clipping. */
.jl-ph-more { flex: 0 0 auto; }
.jl-ph-more > summary {
  list-style: none;
  cursor: pointer;
  justify-content: center;
}
.jl-ph-more > summary::-webkit-details-marker { display: none; }
.jl-ph-more > summary::after { content: ' ▾'; }
.jl-ph-more[open] > summary::after { content: ' ▴'; }
/* Wraps rather than stacks by default, so the wide-but-SHORT case (a Fold cover
   screen in landscape, 882×344) spends its scarce height on two rows instead of
   five. Only a genuine phone gets the one-per-line column below. */
.jl-ph-more-body {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
.jl-ph-more-body > .btn {
  /* These labels are long by design ("Update Ascora Status and Jobs"). Let them
     wrap rather than clip — a truncated action name is a guess. */
  white-space: normal;
  text-align: left;
}
html[data-vp="xs"] .jl-ph-more,
html[data-vp="sm"] .jl-ph-more { flex: 1 1 100%; }
html[data-vp="xs"] .jl-ph-more > summary,
html[data-vp="sm"] .jl-ph-more > summary { width: 100%; }
html[data-vp="xs"] .jl-ph-more-body,
html[data-vp="sm"] .jl-ph-more-body { flex-direction: column; }
html[data-vp="xs"] .jl-ph-more-body > .btn,
html[data-vp="sm"] .jl-ph-more-body > .btn { width: 100%; justify-content: flex-start; }

/* ── 2 · KPI strip ───────────────────────────────────────────────────────── */
html[data-vp="xs"] .jl-kpi-row { gap: 8px; margin-bottom: 14px; }
html[data-vp="xs"] .jl-kpi { padding: 11px 12px; }
html[data-vp="xs"] .jl-kpi-val { font-size: 22px; }
html[data-vp="xs"] .jl-kpi-label { letter-spacing: 0.1em; }
/* Deliberately NOT spanning the odd fifth tile across both columns: every tile
   centres its figure under a left-aligned label (the desktop design), and at
   full width that reads as a number floating away from its own caption. Five
   identical half-width tiles with one gap is the lesser evil. */

/* ── 3 · Filter bar ──────────────────────────────────────────────────────── */
/* Each segmented control becomes its own horizontal scroll strip. Squashing
   them (the old behaviour) turned "Pre-Install" into "Pre- Insta ll" across
   three lines; wrapping them would put a phase count on a line of its own.
   Scrolling keeps every label intact and is the same idiom the module bar
   already uses at these tiers. */
html[data-vp="xs"] .jl-fbar,
html[data-vp="sm"] .jl-fbar { gap: 8px; padding-bottom: 12px; }
html[data-vp="xs"] .jl-fbar > .jl-seg,
html[data-vp="sm"] .jl-fbar > .jl-seg {
  flex: 1 1 100%;
  margin-right: 0 !important;   /* beats the inline margin-right on the type seg */
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
}
html[data-vp="xs"] .jl-fbar > .jl-seg::-webkit-scrollbar,
html[data-vp="sm"] .jl-fbar > .jl-seg::-webkit-scrollbar { display: none; }
html[data-vp="xs"] .jl-seg button,
html[data-vp="sm"] .jl-seg button {
  flex: 0 0 auto;
  white-space: nowrap;
  scroll-snap-align: start;
  min-height: 40px;
}
html[data-vp="xs"] .jl-chips,
html[data-vp="sm"] .jl-chips { flex: 1 1 100%; }
html[data-vp="xs"] .jl-chip,
html[data-vp="sm"] .jl-chip { min-height: 40px; }

/* THE OVERFLOW ITSELF. `margin-left:auto` + no wrap is what pushed "Columns"
   off the screen. Full width and wrapping at phone tiers, with the search box
   taking a line of its own — it is the control people actually reach for. */
html[data-vp="xs"] .jl-fbar-r,
html[data-vp="sm"] .jl-fbar-r {
  flex: 1 1 100%;
  margin-left: 0;
  flex-wrap: wrap;
  gap: 8px;
}
html[data-vp="xs"] .jl-fbar-search,
html[data-vp="sm"] .jl-fbar-search { flex: 1 1 100%; order: -1; }
html[data-vp="xs"] .jl-fbar-search input,
html[data-vp="sm"] .jl-fbar-search input {
  width: 100%;
  min-width: 0;
  /* 16px stops iOS Safari zooming the page on focus — which, on a page that
     was already overflowing, left the user parked off-screen. */
  font-size: 16px;
  min-height: 44px;
}
/* The status popover is 270px wide — wider than the 312px content box has room
   for once it is anchored to a control near the right edge, and its 420px
   max-height ran off a 344px-tall Fold cover screen in landscape. At phone
   tiers it becomes a bottom sheet: pinned to the viewport, not to the filter
   row, so it lands in the same place whether the page is scrolled or not. */
html[data-vp="xs"] .jl-fbar-r .jl-drop,
html[data-vp="sm"] .jl-fbar-r .jl-drop {
  position: fixed;
  left: 8px;
  right: 8px;
  bottom: max(8px, env(safe-area-inset-bottom));
  top: auto;
  width: auto;
  max-height: min(60vh, 420px);
  overflow-y: auto;
  border-radius: 14px;
  box-shadow: 0 -8px 40px rgba(17,24,39,.22);
  z-index: 200;
}
html[data-vp="xs"] .jl-colopt,
html[data-vp="sm"] .jl-colopt { min-height: 44px; padding: 6px 4px; font-size: 14px; }
html[data-vp="xs"] .jl-colopt input[type="checkbox"],
html[data-vp="sm"] .jl-colopt input[type="checkbox"] { width: 20px; height: 20px; }

/* ── 4 · The list as cards ───────────────────────────────────────────────── */
/* Pairs with the `_jlPhone` branch in _rowOf (modules/22-jobs-page.js), which
   emits ONE full-width cell per row. It is still a <table>/<tbody>/<tr> so the
   window cap, the IntersectionObserver, the virtual-scroll spacers and the
   scroll restore all keep working untouched — only the painting changes.
   These selectors are more specific than js/responsive.css's global
   `table { display:block; overflow-x:auto }` (which is the right default for
   every other table and the wrong one here, because .jl-tbl-wrap already owns
   the horizontal scroll), so they win on specificity without !important. */
html[data-vp="xs"] .app-main .jl-tbl.jl-cards,
html[data-vp="sm"] .app-main .jl-tbl.jl-cards {
  display: block;
  width: 100%;
  overflow-x: hidden;
}
html[data-vp="xs"] .app-main .jl-tbl.jl-cards > thead,
html[data-vp="sm"] .app-main .jl-tbl.jl-cards > thead { display: none; }
html[data-vp="xs"] .app-main .jl-tbl.jl-cards > tbody,
html[data-vp="sm"] .app-main .jl-tbl.jl-cards > tbody {
  display: block;
  width: auto;
  min-width: 0;
}
html[data-vp="xs"] .app-main .jl-tbl.jl-cards > tbody > tr,
html[data-vp="sm"] .app-main .jl-tbl.jl-cards > tbody > tr { display: block; }
html[data-vp="xs"] .app-main .jl-tbl.jl-cards td.jl-card,
html[data-vp="sm"] .app-main .jl-tbl.jl-cards td.jl-card {
  display: block;
  /* !important only to beat js/responsive.css's global
     `:where(th, td) { padding: 9px 10px !important }`, which is a sensible floor
     for a data cell and too tight for a whole card. Nothing else here needs it. */
  padding: 13px 14px !important;
  font-size: 13px !important;
  border-bottom: 1px solid var(--s100, #F5F5F4);
}
html[data-vp="xs"] .app-main .jl-tbl.jl-cards > tbody > tr:last-child td.jl-card,
html[data-vp="sm"] .app-main .jl-tbl.jl-cards > tbody > tr:last-child td.jl-card { border-bottom: 0; }
/* The overdue / behind-on-payment row treatments are backgrounds on the <tr>;
   a display:block <tr> still paints them, so nothing is lost. */

.jl-card-top {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.jl-card-stage {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 9px;
}
/* The phase bar takes whatever the status pill leaves — it is a progress cue,
   so it should read as "how far along", not as a fixed-width chip. */
.jl-card-stage > .jl-phx { flex: 1 1 auto; min-width: 48px; margin: 0; }
.jl-card-stage > .jl-spill { flex: 0 0 auto; }
.jl-card-name {
  font-size: 15.5px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--ink, #0A0A0A);
  line-height: 1.25;
  margin-top: 9px;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.jl-card .jl-client-meta { margin-top: 4px; }
.jl-card-facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 12px;
  margin-top: 11px;
  padding-top: 10px;
  border-top: 1px dashed var(--s200, #E7E5E4);
}
.jl-card-fact { min-width: 0; }
.jl-card-k {
  display: block;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--s400, #A8A29E);
}
.jl-card-v { display: block; margin-top: 2px; }
/* .jl-money / .jl-money-sub are right-aligned for the table's right-aligned
   Value column. In a card the label sits above the figure, so right-aligning it
   floats the number away from its own label. */
.jl-card-fact .jl-money,
.jl-card-fact .jl-money-sub { text-align: left; }
.jl-card .jl-actions {
  justify-content: flex-start;
  gap: 8px;
  margin-top: 11px;
}
/* 34px is a mis-tap on glass, and these are the row's only buttons now that the
   whole card is the tap target for "open job". */
html[data-vp="xs"] .jl-qa-btn,
html[data-vp="sm"] .jl-qa-btn { width: 40px; height: 40px; }

/* ── 5 · Footer ──────────────────────────────────────────────────────────── */
/* The density toggle is gone at these tiers (it only moved table cell padding),
   so the true-total count gets the whole row — it is the only place the real
   list size is stated, and it must never be the thing that gets squashed. */
html[data-vp="xs"] .jl-tbl-foot,
html[data-vp="sm"] .jl-tbl-foot { flex-wrap: wrap; gap: 6px; }
html[data-vp="xs"] .jl-tbl-foot-l,
html[data-vp="sm"] .jl-tbl-foot-l { flex: 1 1 100%; }

/* ── 6 · Short viewports (Fold cover screen in landscape, 882×344) ───────── */
/* 344px of height with ~96px of chrome on top: the KPI strip alone would take
   half of what is left before a single job appears. Tighten it rather than
   remove it — the counts are the reason people open this page. */
html[data-vp-h="short"] .jl-kpi { padding: 8px 12px; }
html[data-vp-h="short"] .jl-kpi-val { font-size: 19px; }
html[data-vp-h="short"] .jl-kpi-bar { margin-top: 6px; }
html[data-vp-h="short"] .jl-ph { margin-bottom: 12px; padding-bottom: 10px; }
