/* ----------------------------------------------------------------------
 * TallerPro Design System tokens (locked — do not change hex)
 *
 * Primary #2563eb · Inter · sidebar 230px · cards 12px
 * Icons stay Bootstrap Icons in this chrome pass (no Lucide swap).
 *
 * Light is the default. `[data-theme="dark"]` on <html> flips page
 * colors. Status/priority badges keep their own colors so the 16 live
 * work-order statuses stay readable on either background.
 * ---------------------------------------------------------------------- */
:root {
  --sw: 230px;
  --pr: #2563eb;
  --pr-hover: #1d4ed8;
  /* PR-15: the vans' colours — the SAME hexes as ApplicationHelper::TRANSIT_UNIT_COLORS
     (a test pins them together). Consumed through .van-N { --van } everywhere. */
  --van-color-1: #2563eb;
  --van-color-2: #115e59;
  --van-color-3: #92400e;
  --van-color-none: #64748b;
  --radius-card: 12px;

  /* Page surfaces */
  --bg-page:        #f1f5f9;
  --bg-card:        #fff;
  --bg-card-header: #f8fafc;
  --bg-input:       #fff;

  /* Text. Muted was #64748b — 4.34:1 on the page bg, under the 4.5:1 AA
     bar, and we set it at 10px uppercase labels (UI audit 2026-08-17).
     #475569 reads 7:1. The "locked" note above covers the PRIMARY. */
  --text-strong: #0f172a;
  --text-muted:  #475569;
  --bs-secondary-color: var(--text-muted); /* .text-muted hereda el token */

  /* Borders / dividers */
  --border-soft: #f1f5f9;

  /* Shadows */
  --shadow-card: 0 1px 3px rgba(0, 0, 0, .07);

  /* Bootstrap's compiled primary is still #0d6efd. Map it onto the
     locked DS blue so brand-pill, active nav, and .btn-primary agree. */
  --bs-primary: #2563eb;
  --bs-primary-rgb: 37, 99, 235;
  --bs-primary-text-emphasis: #1e40af;
  --bs-primary-bg-subtle: #dbeafe;
  --bs-primary-border-subtle: #93c5fd;
  --bs-link-color: #2563eb;
  --bs-link-hover-color: #1d4ed8;
}

[data-theme="dark"] {
  --bg-page:        #0b1220;
  --bg-card:        #1e293b;
  --bg-card-header: #0f172a;
  --bg-input:       #0f172a;

  --text-strong: #f1f5f9;
  --text-muted:  #94a3b8;

  --border-soft: #334155;

  --shadow-card: 0 1px 3px rgba(0, 0, 0, .5);

  --bs-primary: #2563eb;
  --bs-primary-rgb: 37, 99, 235;
  --bs-primary-text-emphasis: #93c5fd;
  --bs-primary-bg-subtle: #172554;
  --bs-primary-border-subtle: #1e40af;
  --bs-link-color: #60a5fa;
  --bs-link-hover-color: #93c5fd;
}

body {
  margin: 0;
  font-family: Inter, "Segoe UI", system-ui, -apple-system, sans-serif;
  background: var(--bg-page);
  color: var(--text-strong);
}

/* ── Staff session screens (login / password / authenticator 2FA) ──
   Dedicated layout — no sidebar. Public landing stays on layouts/public. */
.session-layout {
  min-height: 100vh;
  background: var(--bg-page);
  color: var(--text-strong);
}

.auth-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
}

.auth-card {
  width: 100%;
  max-width: 400px;
}

.auth-brand {
  text-align: center;
  margin-bottom: 24px;
}

.auth-brand-pill {
  width: 40px;
  height: 40px;
  margin: 0 auto 12px;
  border-radius: 10px;
  background: var(--pr);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.1rem;
}

.auth-brand-overline {
  text-transform: uppercase;
  font-size: .65rem;
  letter-spacing: .16em;
  color: var(--text-muted);
  margin-bottom: 4px;
}

.auth-brand-name {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-strong);
  margin: 0 0 8px;
}

.auth-heading {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-strong);
  margin: 12px 0 4px;
}

.auth-subtitle {
  margin: 0;
  color: var(--text-muted);
  font-size: .9rem;
}

.auth-panel {
  background: var(--bg-card);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  border: 1px solid var(--border-soft);
  padding: 28px 24px;
}

.auth-panel .form-label {
  font-size: .82rem;
  font-weight: 500;
  color: var(--text-strong);
}

.auth-panel .form-control {
  border-radius: 8px;
  padding: 10px 12px;
}

.auth-otp-input {
  letter-spacing: 0.3em;
  font-size: 1.4rem;
  font-weight: 600;
}

.auth-footer {
  text-align: center;
  margin-top: 16px;
}

.auth-footer a {
  font-size: .82rem;
  color: var(--text-muted);
  text-decoration: none;
}

.auth-footer a:hover {
  color: var(--pr);
}

/* ── Sidebar (proto lockup — DS tokens, not leftover Bootstrap admin) ── */
.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--sw);
  height: 100vh;
  background: var(--bg-card);
  color: var(--text-strong);
  border-right: 1px solid var(--border-soft);
  overflow-y: auto;
  z-index: 100;
  display: flex;
  flex-direction: column;
}

.sidebar .brand {
  padding: 18px 16px 16px;
  border-bottom: 1px solid var(--border-soft);
  display: flex;
  align-items: center;
  gap: 10px;
}

.sidebar .brand-pill {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: var(--pr);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: .95rem;
  flex-shrink: 0;
}

.sidebar .brand-text {
  min-width: 0;
}

.sidebar .brand-overline {
  text-transform: uppercase;
  font-size: .62rem;
  letter-spacing: .14em;
  font-weight: 500;
  color: var(--text-muted);
  line-height: 1.15;
  margin-bottom: 1px;
}

.sidebar .brand-name {
  font-weight: 700;
  font-size: .95rem;
  letter-spacing: -0.02em;
  color: var(--text-strong);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-section {
  padding: 16px 16px 6px;
  font-size: .62rem;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: .14em;
  text-transform: uppercase;
}

.sidebar a {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 1px 8px;
  padding: 7px 10px;
  color: var(--text-strong);
  text-decoration: none;
  font-size: .84rem;
  font-weight: 500;
  border-radius: 8px;
  border-left: 3px solid transparent;
  transition: background-color .12s, color .12s;
}

.sidebar a:hover {
  background: var(--bg-page);
  color: var(--text-strong);
}

.sidebar a.active {
  background: var(--bs-primary-bg-subtle);
  color: var(--pr);
  border-left-color: var(--pr);
  font-weight: 600;
}

.sidebar a i {
  font-size: 1rem;
  width: 18px;
  flex-shrink: 0;
  color: inherit;
}

/* Main content */
.main {
  margin-left: var(--sw);
  padding: 24px;
  min-height: 100vh;
}

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin: -24px -24px 24px;
  padding: 12px 24px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border-soft);
  box-shadow: var(--shadow-card);
}

.topbar-title-wrap {
  min-width: 0;
  flex: 0 1 auto;
}

.topbar-title {
  margin: 0;
  font-weight: 600;
  font-size: 1.02rem;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--text-strong);
}

.topbar-search {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 220px;
  min-width: 0;
  max-width: 420px;
  height: 36px;
  padding: 0 12px;
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  background: var(--bg-page);
  color: var(--text-muted);
  font-size: .85rem;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.topbar-search:hover,
.topbar-search:focus-visible {
  border-color: color-mix(in srgb, var(--pr) 40%, var(--border-soft));
  outline: none;
}

.topbar-search .bi-search {
  font-size: .9rem;
  flex-shrink: 0;
}

.topbar-search-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar-search-kbd {
  font-size: .68rem;
  font-weight: 500;
  padding: 1px 6px;
  border-radius: 4px;
  border: 1px solid var(--border-soft);
  background: var(--bg-card);
  color: var(--text-muted);
}

.topbar-actions {
  flex-shrink: 0;
  gap: 12px;
}

.topbar-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--text-muted);
  text-decoration: none;
  border-radius: 8px;
  /* The bell is a <button> now (it opens the popover) — kill the
     native chrome so it keeps looking like the icon it always was. */
  background: none;
  border: 0;
  padding: 0;
}

/* The bell's peek popover: last 6 rows without leaving the page. */
.notif-pop {
  min-width: 320px;
  max-width: 380px;
}
.notif-pop .notif-pop-item {
  white-space: normal;
  padding-top: 8px;
  padding-bottom: 8px;
}

.topbar-bell:hover {
  color: var(--text-strong);
  background: var(--bg-page);
}

.topbar-bell .bi-bell {
  font-size: 1.05rem;
}

.topbar-bell .badge,
.topbar-bell-count {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 999px;
  background: #dc2626;
  color: #fff;
  font-size: .58rem;
  font-weight: 600;
  line-height: 14px;
  text-align: center;
}

.topbar .date {
  color: var(--text-muted);
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}

/* Cards — shared surface so inside pages inherit proto weight */
.card {
  border-color: var(--border-soft);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}

.stat-card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-card);
  padding: 20px;
  box-shadow: var(--shadow-card);
}

.stat-card .icon {
  width: 44px;
  height: 44px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
}

.stat-card h3 {
  font-size: 1.6rem;
  font-weight: 700;
  margin: 8px 0 2px;
  color: var(--text-strong);
}

.stat-card p {
  margin: 0;
  color: var(--text-muted);
  font-size: .8rem;
}

/* Tables */
.table-card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  margin-top: 20px;
}

/* .table-card clips (overflow:hidden), so a wide table loses its right-hand
   columns with no scrollbar — including action buttons. Wrap such tables in
   .table-responsive; the inner scrollport works fine inside the clip. */

.table-card .card-header {
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-soft);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.table-card .card-header h6 {
  margin: 0;
  font-weight: 700;
  color: var(--text-strong);
}

.table th {
  font-size: .78rem;
  color: var(--text-muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  border-color: var(--border-soft) !important;
  background: var(--bg-card-header);
}

.table td {
  font-size: .87rem;
  vertical-align: middle;
  border-color: var(--border-soft) !important;
}

/* Money reads as a column, not as prose: Segoe UI ships proportional digits,
   so $1,234.56 and $99.00 never lined up their decimal points anywhere in the
   app. tabular-nums fixes the glyph widths wherever a figure is right-aligned
   or displayed large. */
.table th.text-end,
.table td.text-end,
.stat-card h3,
.display-6 {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Status chips — same 16 live statuses, proto-style pill */
.badge-status,
.badge-priority {
  padding: 2px 10px;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .01em;
  line-height: 1.45;
  display: inline-flex;
  align-items: center;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
}

.status-new-order { background: #f0fdf4; color: #166534; }
/* The three statuses nobody had painted (they rendered as bare text).
   Call-to-* share their stage family's tones; Warranty Return gets its
   own fuchsia so a comeback never blends in with normal work. */
.status-warranty-return { background: #fae8ff; color: #86198f; }
.status-call-to-estimate { background: #fef3c7; color: #78350f; }
.status-call-to-collect { background: #ccfbf1; color: #115e59; }
.status-give-estimate { background: #fef3c7; color: #78350f; }
.status-waiting-for-approval { background: #fef9c3; color: #713f12; }
.status-order-part { background: #ede9fe; color: #6d28d9; }
.status-waiting-for-part { background: #fce7f3; color: #be185d; }
.status-waiting-for-repair { background: #ffedd5; color: #7c2d12; }
.status-pending { background: #fef3c7; color: #92400e; }
.status-in-progress { background: #dbeafe; color: #1e40af; }
.status-waiting-for-delivery { background: #e0f2fe; color: #0369a1; }
.status-ready { background: #d1fae5; color: #065f46; }
.status-waiting-for-pick-up { background: #ccfbf1; color: #115e59; }
.status-complete { background: #bbf7d0; color: #14532d; }
.status-delivered { background: #e0e7ff; color: #3730a3; }
.status-cancelled { background: #fee2e2; color: #991b1b; }
.status-abandoned { background: #fef3c7; color: #92400e; }
.status-for-sale { background: #cffafe; color: #155e75; }
.status-sold { background: #bbf7d0; color: #14532d; }
.status-draft { background: #f1f5f9; color: #475569; }
.status-sent { background: #dbeafe; color: #1e40af; }
.status-approved { background: #d1fae5; color: #065f46; }
.status-rejected { background: #fee2e2; color: #991b1b; }
.status-expired { background: #fef3c7; color: #92400e; }
.status-converted { background: #e0e7ff; color: #3730a3; }
.status-received { background: #d1fae5; color: #065f46; }
.status-billed { background: #e0e7ff; color: #3730a3; }   /* PR-20: PO Billed badge had no rule */

/* Priority badges */
.priority-high { background: #fee2e2; color: #991b1b; }
.priority-normal { background: #f0fdf4; color: #166534; }
.priority-low { background: #f8fafc; color: #475569; }

/* Forms */
.btn-primary { background: var(--pr); border-color: var(--pr); }
.btn-primary:hover { background: var(--pr-hover); border-color: var(--pr-hover); }
/* PR-26: Bootstrap 5.3 leaves --bs-secondary at #6c757d in dark; on --bg-card
   (#1e293b) that reads 3.16:1 — under AA for the ~84 outline buttons. */
[data-theme="dark"] .btn-outline-secondary {
  --bs-btn-color: #cbd5e1;              /* 9.5:1 on #1e293b */
  --bs-btn-border-color: #94a3b8;       /* 5.5:1 — clears the 3:1 non-text minimum */
  --bs-btn-hover-bg: #334155;
  --bs-btn-hover-border-color: #cbd5e1;
  --bs-btn-hover-color: #f1f5f9;
  --bs-btn-active-bg: #334155;
  --bs-btn-active-color: #f1f5f9;
}
.form-control:focus,
.form-select:focus { border-color: var(--pr); box-shadow: 0 0 0 3px rgba(37, 99, 235, .15); }

/* Flash messages */
.flash-container {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 9999;
  min-width: 280px;
}

/* ── Technician bottom tab bar (phone widths — Ivan, 2026-08-17:
   "usan celular y tablet"; the tablet keeps the sidebar). Tokens on
   both surfaces so dark mode comes free. */
.tech-tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 200;
  display: flex;
  background: var(--bg-card);
  border-top: 1px solid var(--border-soft);
  box-shadow: 0 -1px 3px rgba(0, 0, 0, .07);
  padding-bottom: env(safe-area-inset-bottom);
}

.tech-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 0 6px;
  color: var(--text-muted);
  text-decoration: none;
  font-size: .62rem;
  font-weight: 500;
}

.tech-tab i { font-size: 1.15rem; line-height: 1; }
.tech-tab.is-on { color: var(--pr); }

@media (max-width: 767.98px) {
  .has-tech-tabbar .main { padding-bottom: 84px; }
}

/* ── Topbar user corner (moved from the sidebar bottom — Ivan,
   2026-08-17, proto): avatar + name opens the menu with Security and
   Sign out; theme/language pills sit loose on lg+, inside the menu
   below that. The .sidebar-pills classes are the shared pill styles. */
.topbar-user {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: none;
  border: 0;
  padding: 2px 6px;
  border-radius: 8px;
}

.topbar-user:hover { background: var(--bg-page); }

.topbar-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 700;
  font-size: .8rem;
  flex-shrink: 0;
}

.topbar-user-meta { line-height: 1.15; }

.topbar-user-meta .name {
  color: var(--text-strong);
  font-size: .78rem;
  font-weight: 600;
  max-width: 140px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topbar-user-meta .role {
  color: var(--text-muted);
  font-size: .66rem;
}

.topbar-pills-wrap .sidebar-pills,
.dropdown-item-text .sidebar-pills { margin: 0 4px 0 0; }

.sidebar-pills {
  display: inline-flex;
  align-items: center;
  margin: 0 4px 8px 0;
  padding: 2px;
  border: 1px solid var(--border-soft);
  border-radius: 999px;
  background: var(--bg-page);
}

.sidebar-pills form {
  display: contents;
}

.sidebar-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 26px;
  padding: 0 6px;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font-size: .72rem;
  font-weight: 600;
  font-family: inherit;
  line-height: 1;
  text-decoration: none;
  border-radius: 999px;
  cursor: pointer;
}

.sidebar-pill.text-primary {
  color: var(--pr);
}

/* Print */
@media print {
  .sidebar, .topbar, .no-print, .flash-container { display: none !important; }
  .main { margin-left: 0 !important; padding: 0 !important; }
}

/* Mobile responsive.
   The .show class is driven by the mobile-nav Stimulus controller, opened by
   the hamburger in the topbar. Before that button existed nothing added .show,
   so every phone — and an iPad in portrait, exactly 768px — had no reachable
   navigation at all.
   767.98px, not 768: Bootstrap's .d-md-none on the hamburger is
   min-width:768px, so at EXACTLY 768 the drawer was hidden AND the button that
   opens it was hidden — iPad portrait, the tablet the crew is getting. */
@media (max-width: 767.98px) {
  .sidebar { transform: translateX(-100%); transition: transform .3s; }
  .sidebar.show { transform: translateX(0); box-shadow: 0 0 40px rgba(0,0,0,.35); }
  .main { margin-left: 0; padding: 16px; }
  .topbar { margin: -16px -16px 16px; padding: 10px 16px; gap: 10px; }
  .topbar-search { flex: 0 0 36px; min-width: 36px; max-width: 36px; padding: 0; justify-content: center; }
  .topbar-search-label,
  .topbar-search-kbd { display: none; }
  .topbar .date { display: none; }
  /* Dim + trap taps behind the open drawer. z-index sits just under .sidebar
     (100) so the drawer stays on top, and above page content. */
  .sidebar-backdrop.show {
    position: fixed;
    inset: 0;
    z-index: 99;
    background: rgba(0, 0, 0, .45);
  }
}

/* Never render the backdrop on desktop, even if a stale .show survives a
   resize — the sidebar is permanently visible there. */
.sidebar-backdrop { display: none; }
@media (max-width: 767.98px) {
  .sidebar-backdrop.show { display: block; }
}

/* The drawer is the ONLY navigation on phones and tablets now, tapped by a
   tech with gloves on. 8px padding gave ~36px rows; 44px is the floor. */
@media (max-width: 767.98px) {
  .sidebar a { padding: 12px 16px; font-size: .95rem; }
}

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

/* Turbo progress bar */
.turbo-progress-bar {
  background: var(--pr);
  height: 3px;
}

/* Calendar live-van dot: gentle pulse when the van is on the road */
@keyframes pulse-dot {
  0%, 100% { transform: scale(1);   opacity: 1;   }
  50%      { transform: scale(1.4); opacity: 0.65; }
}

/* WO form "Advanced fields" disclosure block — native <details> styled
   as a Bootstrap button. Hides the default browser triangle marker
   (varies by engine) and replaces it with a chevron that rotates on
   open. Purely cosmetic; the form-disclosure Stimulus controller
   drives the open/close logic. */
.wo-advanced__summary { list-style: none; cursor: pointer; }
.wo-advanced__summary::-webkit-details-marker { display: none; }
.wo-advanced__summary::marker { display: none; content: ""; }
.wo-advanced__chevron { transition: transform .15s ease; }
.wo-advanced[open] .wo-advanced__chevron { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .wo-advanced__chevron { transition: none; }
}

/* PR-13 — the list's fine-status disclosure (same chevron idiom as .wo-advanced) */
.wo-fine-statuses__summary { list-style: none; cursor: pointer; }
.wo-fine-statuses__summary::-webkit-details-marker { display: none; }
.wo-fine-statuses__summary::marker { display: none; content: ""; }
.wo-fine-statuses__chevron { transition: transform .15s ease; }
.wo-fine-statuses[open] .wo-fine-statuses__chevron { transform: rotate(90deg); }
.wo-stage-chips__closed .cal-chip { opacity: .85; }
.bulk-cell { width: 40px; }
/* PR-13 — on a phone the list comes first; the filters hide behind "Filtros (N)" */
.wo-filters__toggle { display: none; }
@media (max-width: 767.98px) {
  .wo-filters__toggle { display: inline-flex; }
  .wo-filters__state:not(:checked) ~ .wo-filters { display: none; }
  .wo-filters__state:checked ~ .wo-filters__toggle { border-color: var(--pr); box-shadow: inset 0 0 0 1px var(--pr); }
}

/* ─── Command palette (Cmd+K) ───────────────────────────────────────
   Mounted once in the layout. Hidden by default; .is-open flips it.
   Sits above Bootstrap modals (z-index 1055) at 1080. Uses Bootstrap
   CSS variables so dark mode tracks automatically. */
.cmdk { position: fixed; inset: 0; z-index: 1080; display: none; }
.cmdk.is-open { display: block; }
.cmdk-backdrop {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(2px);
}
.cmdk-dialog {
  position: relative;
  width: min(640px, calc(100vw - 32px));
  max-height: min(70vh, 560px);
  margin: 10vh auto 0;
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
  border: 1px solid var(--bs-border-color);
  border-radius: 12px;
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.35);
  display: flex; flex-direction: column;
  overflow: hidden;
  outline: none;
}
.cmdk-input-row {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--bs-border-color);
}
.cmdk-icon { font-size: 1.1rem; color: var(--bs-secondary-color); }
.cmdk-input {
  flex: 1; border: none; outline: none; background: transparent;
  color: var(--bs-body-color); font-size: 1rem;
}
.cmdk-kbd {
  font-size: 0.7rem; padding: 2px 6px;
  border-radius: 4px;
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  color: var(--bs-secondary-color);
}
.cmdk-list {
  list-style: none; margin: 0; padding: 4px 0;
  overflow-y: auto; flex: 1; min-height: 0;
}
.cmdk-group {
  padding: 10px 16px 4px;
  font-size: 0.7rem; letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--bs-secondary-color);
}
.cmdk-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 16px;
  cursor: pointer;
  border-left: 2px solid transparent;
  min-height: 40px;
}
.cmdk-item.is-active {
  background: var(--bs-tertiary-bg);
  border-left-color: var(--bs-primary);
}
.cmdk-item .bi { color: var(--bs-secondary-color); }
.cmdk-item.is-active .bi { color: var(--bs-primary); }
.cmdk-title { flex: 0 1 auto; font-weight: 500; }
.cmdk-meta {
  flex: 1; margin-left: 8px;
  color: var(--bs-secondary-color); font-size: 0.85rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cmdk-go {
  font-size: 0.7rem; padding: 2px 6px;
  border-radius: 4px;
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  color: var(--bs-secondary-color);
  opacity: 0; transition: opacity .1s ease;
}
.cmdk-item.is-active .cmdk-go { opacity: 1; }
.cmdk-empty {
  padding: 20px 16px;
  color: var(--bs-secondary-color);
  text-align: center;
}
.cmdk-hint {
  display: flex; gap: 14px; align-items: center;
  padding: 8px 16px;
  border-top: 1px solid var(--bs-border-color);
  font-size: 0.75rem; color: var(--bs-secondary-color);
  background: var(--bs-tertiary-bg);
}
.cmdk-hint kbd {
  display: inline-block; padding: 0 5px; margin-right: 3px;
  border-radius: 3px;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  color: var(--bs-body-color);
}
.cmdk-status { margin-left: auto; }
body.cmdk-lock { overflow: hidden; }

/* Recent-orders tables on the dashboard: keep the row-as-shortcut
   affordance now that the first cell carries a real <a> instead of
   a JS onclick. Cursor pointer mirrors the prior UX without trapping
   keyboard users in an inaccessible <tr onclick>. */
.table-card tr:has(td:first-child a) { cursor: pointer; }

@media (max-width: 640px) {
  .cmdk-dialog { width: calc(100vw - 16px); margin: 6vh auto 0; }
  .cmdk-meta { display: none; }  /* keep titles readable on narrow screens */
}

/* ── Week time grid ───────────────────────────────────────────────────
   Cards sit at the minute they start and are as tall as they are long, in
   lanes when they overlap. Colour = the van; the tint is mixed against
   --cal-surface so the today-column and dark mode stay seamless. */

/* The sticky day header needs a scrollport it can stick inside; .table-card's
   overflow:hidden makes the card a non-scrolling one, which silently kills
   sticky. The calendar card opts out. */
.table-card--calendar { overflow: visible; }

/* The header is its OWN sticky grid above .cal-week. As a row INSIDE the grid
   its sticky rectangle was its own 40px cell — it could barely travel — and
   the now-line (z5) and drag guide (z6) painted over it. */
/* PR-25 — the manual's floating index. The topbar is not fixed, so the
   offset is just breathing room; no overflow ancestor here, so sticky works. */
.help-toc { position: sticky; top: 1rem; max-height: calc(100vh - 2rem); overflow-y: auto; padding: 12px 14px;
            border: 1px solid var(--border-soft); border-radius: 12px; background: var(--bg-card); font-size: .85rem; }
.help-toc__group-label { font-size: .65rem; letter-spacing: .1em; text-transform: uppercase; margin: 8px 0 4px; }
.help-toc__link { display: block; padding: 3px 8px; border-radius: 6px; color: var(--text-muted); text-decoration: none; }
.help-toc__link:hover { color: var(--pr); background: var(--ds-muted); }
.help-toc__link.is-active { color: var(--pr); font-weight: 600; background: var(--ds-muted); }
.help-toc-mobile summary::-webkit-details-marker { display: none; }
.help-toc-mobile summary::marker { display: none; content: ""; }
.help-toc-mobile summary::before { content: "▸ "; }
.help-toc-mobile[open] summary::before { content: "▾ "; }
[id^="s"][class*="col-"], [id="lifecycle"], [id="payment"], [id="appointments"], [id="inventory"], [id="field-tech"], [id="vendors"], [id="po-flow"], [id="expenses"], [id="reports"], [id="admin"], [id="troubleshooting"], [id="rules"] { scroll-margin-top: 1rem; }

.cal-headrow {
  position: sticky;
  top: 0;
  z-index: 20;
  display: grid;
  /* Trailing 26px track is the closed-Sunday sliver. The body grid repeats it
     so the columns stay in step. */
  grid-template-columns: 44px repeat(var(--cal-cols, 6), minmax(0, 1fr)) 52px;
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 8px 8px 0 0;
}
/* The DAY board has no closed-Sunday sliver. Without these rules it inherits
   the week's trailing 26px track: a dead stripe down its right edge, and —
   with any off-hours job — the hour gutter auto-places into the sliver and
   every van column shifts one track left. Keep var(--cal-cols): the view
   sets it to the live van count, and a fourth van must keep working. */
.cal-headrow--day,
.cal-week--day {
  grid-template-columns: 44px repeat(var(--cal-cols, 4), minmax(0, 1fr));
}

.cal-week {
  display: grid;
  /* minmax(0,…): one long unbroken client name must not widen its column and
     skew the whole week. */
  grid-template-columns: 44px repeat(var(--cal-cols, 6), minmax(0, 1fr)) 52px;
  grid-auto-rows: auto;
  border: 1px solid var(--bs-border-color);
  border-top: 0;
  border-radius: 0 0 8px 8px;
  --cal-surface: var(--bg-card);   /* cards sit on .table-card, not on the body */
}

.cal-corner, .cal-head {
  background: var(--bs-tertiary-bg);
  border-bottom: 1px solid var(--bs-border-color);
}
.cal-head {
  display: block;
  text-align: center;
  padding: 5px 2px 6px;
  color: inherit;
  text-decoration: none;
  /* the six column rules used to die at the header, leaving one undivided bar
     sitting over a divided grid */
  border-left: 1px solid var(--bs-border-color);
}
/* Today's header: a soft brand wash (proto) — findable without shouting.
   The solid slab + white text competed with every card on the board. */
.cal-head.is-today { background: var(--bs-primary-bg-subtle); color: var(--pr); }
.cal-head__dow  { font-size: .7rem; text-transform: uppercase; letter-spacing: .1em; opacity: .85; }
.cal-head__num  { font-weight: 700; font-size: 1.15rem; line-height: 1.1; }

/* How full the day is, under the date (Ivan's proto). Muted on purpose — it
   answers a question you ask second, after "which day is this". */
.cal-head__count {
  font-size: .65rem;
  line-height: 1.2;
  margin-top: 1px;
  color: var(--bs-secondary-color);
}
.cal-head.is-today .cal-head__count { color: var(--pr); opacity: .75; }

/* Ausencia del técnico (16-sep). La columna de la van sin chofer va apagada y
   rayada: una columna VACÍA no puede leerse como una columna LIBRE. Hereda
   .cal-head__count (mismo tamaño, otro color) y usa --bs-warning-*, que sí
   invierte con el tema (auditoría de diseño del 12-ago). */
.cal-head__away { color: var(--bs-warning-text-emphasis, #997404); }
.cal-head--van.is-away, .fleet-van-card.is-away { opacity: .75; }
.cal-tl-col.is-away {
  background-image: repeating-linear-gradient(45deg,
    color-mix(in srgb, var(--bs-warning) 9%, transparent) 0 6px, transparent 6px 12px);
}

/* Sunday: a column that says "closed" rather than a day that isn't drawn.
   Omitting it left a normal week with no sign the day existed. Vertical text
   so the sliver stays narrow — the working days need the width more. */
.cal-head--closed { padding: 5px 0; opacity: .6; cursor: default; }
.cal-head--closed .cal-head__dow { font-size: .6rem; }
.cal-head__closed {
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--bs-secondary-color);
  writing-mode: vertical-rl;
  margin: 3px auto 0;
}
/* The closed column says WHY it's hatched, in readable vertical type down
   its body (proto: "Sunday · Closed" across the full height) — the 9px
   header micro-label alone read as a rendering glitch. */
.cal-tl-closed { position: relative; }
.cal-tl-closed__label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  writing-mode: vertical-rl;
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--bs-secondary-color);
  white-space: nowrap;
}
.cal-tl-closed,
.cal-cell--closed {
  border-left: 1px solid var(--bs-border-color);
  background: repeating-linear-gradient(
    45deg,
    transparent, transparent 5px,
    var(--bs-tertiary-bg) 5px, var(--bs-tertiary-bg) 10px
  );
}

/* iPad week (Ivan, 2026-08-16): keep the columns readable and let the week
   scroll sideways (the proto's call) instead of crushing six days into
   768px. The DAY board (4 columns) still fits and keeps its no-scroll
   layout. Known trade: inside a horizontal scroller the sticky header
   pins to the scroller, not the viewport — same trade the proto makes. */
@media (max-width: 1120px) {
  #calendar_grid { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .cal-headrow:not(.cal-headrow--day),
  .cal-week:not(.cal-week--day) { min-width: 1080px; }
}

/* ── Month grid — same skin as day/week, Sundays hatched closed ────── */
.cal-month-wrap { overflow-x: auto; }
.cal-month {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  border: 1px solid var(--bs-border-color);
  border-radius: 8px;
}
.cal-month th {
  padding: 6px 8px;
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  text-align: center;
  color: var(--bs-secondary-color);
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
}
.cal-month td {
  height: 92px;
  vertical-align: top;
  padding: 4px 6px;
  border: 1px solid var(--bs-border-color);
}
.cal-month td.is-out { opacity: .4; }
.cal-month td.is-today { background: var(--bs-primary-bg-subtle); }
.cal-month th.is-closed,
.cal-month td.is-closed {
  background: repeating-linear-gradient(
    45deg,
    transparent, transparent 5px,
    var(--bs-tertiary-bg) 5px, var(--bs-tertiary-bg) 10px
  );
}
.cal-month__num { text-align: right; margin-bottom: 2px; }
.cal-month__num a {
  font-size: .78rem;
  font-weight: 600;
  color: var(--bs-body-color);
  text-decoration: none;
}
.cal-month td.is-today .cal-month__num a { color: var(--pr); }
.cal-month__pill {
  display: block;
  font-size: .65rem;
  padding: 2px 5px;
  margin-bottom: 2px;
  border-radius: 4px;
  border-left: 3px solid var(--van);
  background: color-mix(in srgb, var(--van) 18%, var(--bg-card));
  color: var(--bs-body-color);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-month__pill i { margin-right: 2px; }
.cal-month__more {
  font-size: .6rem;
  color: var(--bs-secondary-color);
  text-decoration: none;
}

/* Day DISPATCH BOARD headers (Ivan's proto, 2026-08-15): the column is a van,
   the tech driving it is the label. The unassigned column reads dimmer — an
   unassigned job is a hole in the plan, and its column shouldn't compete with
   the real vans for attention. */
.cal-head--van { text-align: left; padding: 6px 10px 7px; }
.cal-head__van {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: .8rem;
  line-height: 1.2;
}
.cal-head__tech {
  font-size: .68rem;
  color: var(--bs-secondary-color);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-head--van.is-empty .cal-head__van { color: var(--bs-secondary-color); font-weight: 600; }
.cal-col-empty-hint {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: .7rem;
  color: var(--bs-secondary-color);
  opacity: .6;
  pointer-events: none;   /* the hint must not eat click-to-book */
}

/* The fold-out map panel (Ivan's proto): map left, per-van stops right,
   stacking to one column on narrow screens. */
.cal-map-panel { margin-bottom: 12px; }
.cal-map-hint  { font-size: .74rem; color: var(--bs-secondary-color); margin: 0 0 6px; }
.cal-map-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 12px;
}
@media (max-width: 1023px) {
  .cal-map-split { grid-template-columns: 1fr; }
}
.cal-map {
  height: 44vh;
  min-height: 300px;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--bs-border-color);
}
.cal-map-side {
  max-height: 44vh;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.cal-map-van {
  border: 1px solid var(--bs-border-color);
  border-radius: 10px;
  padding: 8px 10px;
  cursor: pointer;
  background: var(--bg-card);
}
.cal-map-van:hover { background: var(--bs-tertiary-bg); }
.cal-map-van-head {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: .82rem;
  margin-bottom: 2px;
}
.cal-map-van-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}

/* The route in order (Ivan's proto): numbered badge · who · time · street.
   Shared by the calendar's map panel and the Fleet van cards. */
.cal-map-stops {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.cal-map-stops li a,
.cal-map-stops li {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  text-decoration: none;
  color: var(--bs-body-color);
  font-size: .76rem;
  line-height: 1.3;
}
.cal-map-stops li a:hover .cal-map-stop-who { text-decoration: underline; }
.cal-map-stop-n {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  color: #fff;
  font-size: .6rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
}
.cal-map-stop-who {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-map-stop-meta {
  color: var(--bs-secondary-color);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cal-gutter {
  border-top: 1px solid var(--bs-border-color);
  padding: 4px 5px 0 0;
  text-align: right;
  font-size: .7rem;
  color: var(--bs-secondary-color);
  white-space: nowrap;
}
.cal-gutter--off { font-size: .6rem; text-transform: uppercase; letter-spacing: .05em; }

.cal-cell {
  position: relative;
  min-width: 0;
  padding: 4px;
  border-top: 1px solid var(--bs-border-color);
  border-left: 1px solid var(--bs-border-color);
}
.cal-cell.is-today { background: var(--bs-primary-bg-subtle);
                     --cal-surface: var(--bs-primary-bg-subtle); }
.cal-cell--off     { background: var(--bs-secondary-bg); }

/* ── the timeline: one tall row, cards positioned by the minute ── */
.cal-tl-gutter { position: relative; }
/* Chip filter dims the unselected vans — jobs stay visible, the eye knows
   where it was told to look, and nobody books over an invisible column. */
.cal-head--van.is-dimmed,
.cal-tl-col.is-dimmed { opacity: .45; }
.cal-tl-col.is-dimmed { filter: saturate(.35); }
/* The unassigned column reads as "not a real van" from across the room
   (proto): faint stripe over the body, not just a dimmed header. */
.cal-tl-col[data-unit=""] {
  background: repeating-linear-gradient(45deg,
    transparent, transparent 7px,
    rgba(100, 116, 139, .07) 7px, rgba(100, 116, 139, .07) 14px);
}
/* The now-line's name tag, once, in the hour gutter (proto). */
.cal-tl-now-label {
  position: absolute;
  right: 2px;
  z-index: 6;
  background: #dc2626;
  color: #fff;
  font-size: .55rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 999px;
}
.cal-tl-gutter__label {
  position: absolute;
  right: 4px;
  font-size: .65rem;
  color: var(--bs-secondary-color);
  white-space: nowrap;
}
.cal-tl-col {
  position: relative;
  min-width: 0;
  border-left: 1px solid var(--bs-border-color);
  cursor: copy;                     /* click an empty stretch to book */
}
.cal-tl-col.is-today       { background: var(--bs-primary-bg-subtle);
                             --cal-surface: var(--bs-primary-bg-subtle); }
.cal-tl-col.is-drop-target { box-shadow: inset 0 0 0 2px var(--pr); }

/* Hour rules, the now-line and the drag guide are decoration: if they can be
   hit-tested they swallow click-to-book on ~13 stripes per column, because the
   handler only fires when the column ITSELF is the event target. */
.cal-tl-line, .cal-tl-now, .cal-tl-now span, .cal-tl-ghost { pointer-events: none; }
.cal-tl-line {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 1px solid var(--bs-border-color);
  opacity: .55;
}
.cal-tl-now {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 2px solid #ef4444;
  z-index: 5;
}
.cal-tl-now span {
  position: absolute;
  left: -5px;
  top: -5px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ef4444;
}
/* The drop line that follows the pointer, snapped to 15 minutes. */
.cal-tl-ghost {
  position: absolute;
  left: 2px;
  right: 2px;
  height: 2px;
  background: var(--pr);
  z-index: 6;
}

/* Sunday is closed and has no column; a hand-entered job still surfaces. */
.cal-closed-strip { display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin-top: 8px; font-size: .75rem; }
.cal-closed-strip__label { text-transform: uppercase; letter-spacing: .05em;
  font-size: .6rem; color: var(--bs-secondary-color); }
.cal-closed-strip__item { border-left: 3px solid var(--van); border-radius: 6px;
  padding: 2px 8px; text-decoration: none; color: var(--bs-body-color);
  background: color-mix(in srgb, var(--van) 12%, var(--bg-card)); }

/* ── the card ── */
.cal-card {
  display: block;
  position: relative;
  overflow: hidden;
  text-decoration: none;
  color: var(--bs-body-color);
  border-left: 4px solid var(--van);
  background: color-mix(in srgb, var(--van) 14%, var(--cal-surface));
  --van-ink: color-mix(in srgb, var(--van) 80%, var(--text-strong));
  --cal-amber-ink: #b45309;
}
.cal-card--noai { border-left-style: dashed; }   /* the AI cannot see this one */
.cal-card--tl {
  position: absolute;
  padding: 3px 5px;
  border-radius: 6px;
  line-height: 1.22;
  /* a 1px ring in the van's own colour, so two back-to-back jobs on the SAME
     van never melt into one block */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--van) 30%, transparent),
              0 1px 3px rgba(0, 0, 0, .16);
  transition: box-shadow .12s, filter .12s;
}
.cal-card--tl:hover { z-index: 4; filter: brightness(.97);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--van) 45%, transparent),
              0 2px 8px rgba(0, 0, 0, .20); }
[data-theme="dark"] .cal-card--tl:hover { filter: brightness(1.12); }

.cal-card__l1 {
  font-size: .72rem;
  font-weight: 700;
  padding-right: 14px;   /* clears the live-van dot */
  /* clamp, not nowrap+ellipsis: nowrap left the ellipsis inert and long names
     clipped mid-word */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.cal-card--tl.is-tall .cal-card__l1 { -webkit-line-clamp: 2; }
.cal-card__l2,
.cal-card__l3 {
  font-size: .68rem;
  line-height: 1.22;
  color: var(--bs-secondary-color);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-card__glyph     { font-size: 13px; color: var(--van-ink); }
.cal-card__phoneicon { font-size: .7rem; color: var(--cal-amber-ink); }

/* Proto reading order (Ivan's design, 2026-08-15): time + type lead the card,
   the name under them, WO + status at the foot. Tabular numerals so 8:00 and
   11:30 line up down a column. */
.cal-card__top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px;
  font-size: .66rem;
  line-height: 1.2;
  padding-right: 14px;   /* clears the live-van dot */
}
.cal-card__time {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--van-ink);
  white-space: nowrap;
}
.cal-card__time-inline {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: .66rem;
  color: var(--bs-secondary-color);
  white-space: nowrap;
}
.cal-card__type {
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: .58rem;
  font-weight: 600;
  color: var(--bs-secondary-color);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-card__foot {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;      /* pins to the card's bottom edge */
  min-height: 0;
  overflow: hidden;
}
.cal-card__wo {
  font-size: .62rem;
  font-weight: 700;
  color: var(--van-ink);
  white-space: nowrap;
}
/* The stock badge is sized for tables; inside a card it shrinks to fit. */
.cal-card__foot .badge-status {
  font-size: .56rem;
  padding: 1px 6px;
  height: auto;
  line-height: 1.4;
}
/* The card flexes so margin-top:auto on the foot can work. */
.cal-card--tl { display: flex; flex-direction: column; }

/* The proto's day pulse: a finished job steps back, the one happening right
   now leads with the van's full colour. Both stay readable — the fade is on
   the card, never below AA on the text it contains. */
.cal-card--tl.is-past { opacity: .55; }
.cal-card--tl.is-past:hover { opacity: 1; }   /* lean in and it wakes up */
.cal-card--tl.is-live {
  box-shadow: 0 0 0 2px var(--van),
              0 2px 8px rgba(0, 0, 0, .18);
}

/* Van-state dot. Theme-aware fills — the old greys and ambers sat at 1.1–2.2:1
   on a pastel tint. The double ring lifts it off whatever colour is behind. */
.cal-dot {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  box-shadow: 0 0 0 1.5px var(--cal-surface, var(--bg-card)),
              0 0 0 2.5px color-mix(in srgb, var(--text-strong) 55%, transparent);
}
.cal-dot--go   { background: #15803d; }
.cal-dot--idle { background: #6b7280; }
.cal-dot--old  { background: #b45309; }
[data-theme="dark"] .cal-dot--go   { background: #4ade80; }
[data-theme="dark"] .cal-dot--idle { background: #cbd5e1; }
[data-theme="dark"] .cal-dot--old  { background: #fbbf24; }

[data-theme="dark"] .cal-card { --van-ink: color-mix(in srgb, var(--van) 45%, #fff);
                                --cal-amber-ink: #fcd34d; }

/* Keyboard users had no visible focus anywhere on this screen. */
.cal-card:focus-visible,
.cal-head:focus-visible,
.cal-chip:focus-visible,
.cal-closed-strip__item:focus-visible { outline: 2px solid var(--pr); outline-offset: 1px; }

/* ── toolbar chips ──
   btn-light and btn-outline-dark do not invert in BS 5.3 — in dark mode those
   labels sat at 1.06:1. These ride the theme tokens instead. Inactive chips are
   dimmed by COLOUR, never opacity: opacity also faded the little colour dot,
   which is the legend itself. */
.cal-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 16px;
  font-size: .75rem;
  line-height: 1.5;
  padding: 2px 10px;
  border: 1px solid var(--bs-border-color);
  background: var(--bg-card);
  color: var(--text-strong);
  text-decoration: none;
}
.cal-chip:hover  { background: var(--bs-tertiary-bg); color: var(--text-strong); }
.cal-chip.is-off { color: var(--text-muted); }
.cal-chip.is-on  { border-color: var(--pr); box-shadow: inset 0 0 0 1px var(--pr); font-weight: 600; }
.cal-chip__dot   { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.cal-pending     { color: #9a3412; font-weight: 700; }
[data-theme="dark"] .cal-pending { color: #fdba74; }

/* Proto's pending banner: confirmations waiting read as an amber to-do chip,
   not a footnote. Amber-100 on amber-800 = 6.4:1; dark inverts to amber-900
   surface with amber-200 ink, same ratios the proto documents. */
.cal-pending-banner {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px;
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 600;
  background: #fef3c7;
  color: #92400e;
  border: 1px solid #fcd34d;
}
.cal-pending-banner .bi { font-size: .72rem; }

/* "All vans out" — green, because unlike the amber pending chip this is not a
   to-do, it's the good state: everything that could be dispatched is on the
   road. Same pill shape so the toolbar keeps one rhythm. */
.cal-live-vans {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 12px;
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 600;
  background: #dcfce7;
  color: #166534;
  border: 1px solid #86efac;
}
.cal-live-vans .bi { font-size: .8rem; }

/* Unassigned jobs, at the top of the map's side panel. The map itself can't
   show them — no van means no route to draw — so they'd be invisible on the
   one screen whose job is spotting exactly this. */
.cal-map-warn {
  background: #fef3c7;
  color: #92400e;
  border: 1px solid #fcd34d;
  border-radius: 8px;
  padding: 8px 10px;
  margin-bottom: 10px;
  font-size: .78rem;
  font-weight: 600;
}
.cal-map-warn .cal-map-stops { margin: 6px 0 0; font-weight: 500; }
.cal-map-warn .cal-map-stops a { color: inherit; }
[data-theme="dark"] .cal-map-warn {
  background: #78350f;
  color: #fde68a;
  border-color: #b45309;
}
[data-theme="dark"] .cal-live-vans {
  background: #14532d;
  color: #bbf7d0;
  border-color: #15803d;
}
[data-theme="dark"] .cal-pending-banner {
  background: #78350f;
  color: #fde68a;
  border-color: #b45309;
}

/* Active stop: a pulsing GREEN RING, not a green left border. The border is the
   van colour — the one thing Ivan dispatches by — and overwriting it on the
   very card he most needs to identify was backwards. */
@keyframes pulse-ring {
  0%, 100% { box-shadow: 0 1px 3px rgba(0, 0, 0, .16), 0 0 0 2px #22c55e; }
  50%      { box-shadow: 0 1px 3px rgba(0, 0, 0, .16), 0 0 0 2px rgba(34, 197, 94, .25); }
}
.cal-active-stop { animation: pulse-ring 1.5s ease-in-out infinite; }

/* The old reduced-motion block only silenced the sidebar transition; both
   calendar pulses kept running. */
@media (prefers-reduced-motion: reduce) {
  .cal-active-stop { animation: none; box-shadow: 0 1px 3px rgba(0, 0, 0, .16), 0 0 0 2px #22c55e; }
  .cal-dot { animation: none !important; }
}

/* ── Dashboard shop board (page 1 — proto pulse, live 16 statuses) ── */
/* Flat on the page like the proto (Ivan's red circle, 2026-08-17) —
   the card frame made the queue read as a widget instead of THE board. */
.shop-board {
  padding: 0;
}

.shop-board-closed { border-top: 1px solid var(--border-soft); }

.shop-board-groups {
  /* Content-width groups flowing left to right — the proto packs NEW +
     ESTIMATED + APPROVED on one line. A 1fr grid forced equal columns
     and half a screen of air. */
  display: flex;
  flex-wrap: wrap;
  gap: 14px 28px;
}

.shop-board-group { flex: 0 1 auto; }

/* The "All N" door above the groups — same pill, a firmer outline. */
.shop-chip--all {
  font-weight: 600;
  border-width: 1.5px;
}

/* Small-caps muted group header, proto-style — the chips carry the
   counts, the label just names the stage (and still opens its queue). */
.shop-board-group-label {
  display: inline-flex;
  align-items: center;
  margin-bottom: 6px;
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: .66rem;
  text-transform: uppercase;
  letter-spacing: .12em;
}

.shop-board-group-label:hover { color: var(--pr); }

.shop-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.shop-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .01em;
  line-height: 1.45;
  text-decoration: none;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
}

.shop-chip:hover { color: inherit; filter: brightness(0.97); }

.shop-chip.is-zero { opacity: .55; }

/* PR-10 — the queue's four accordions (same chevron idiom as .wo-fine-statuses).
   Collapsed, the four summaries sit on one row and the whole queue is ~40px. */
.shop-board-group__summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border-soft);
  font-size: .68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em;
  color: var(--text-muted);
}
.shop-board-group__summary::-webkit-details-marker { display: none; }
.shop-board-group__summary::marker { display: none; content: ""; }
.shop-board-group__summary:hover { color: var(--pr); }
.shop-board-group__sum { font-variant-numeric: tabular-nums; font-weight: 800; color: var(--text-strong); }
.shop-board-group__chevron { transition: transform .15s ease; }
.shop-board-group[open] .shop-board-group__chevron { transform: rotate(90deg); }
.shop-board-group[open] .shop-board-group__summary { margin-bottom: 8px; }
.shop-board-group[open] { flex-basis: 100%; }
@media (prefers-reduced-motion: reduce) { .shop-board-group__chevron { transition: none; } }

.shop-chip-count {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-weight: 700;
}

.pulse-card-head { display: block; }

.pulse-list {
  list-style: none;
  padding: 0;
  margin: 12px 0 0;
}

.pulse-list li + li a { border-top: 1px solid var(--border-soft); }

.pulse-list a {
  display: flex;
  justify-content: space-between;
  /* Two-line proto rows (client · device under the number) — top-align
     so the badge sits level with the WO number, not floating mid-row. */
  align-items: flex-start;
  gap: 8px;
  padding: 8px 0;
  text-decoration: none;
  color: var(--text-strong);
  font-size: .85rem;
}

.pulse-list a:hover { color: var(--pr); }

.pulse-list .badge-status { flex-shrink: 0; }

/* ── Work-order list on a phone (Ivan's proto) ──────────────────────
   Nine columns don't fit a phone: it becomes a horizontal scroll hunt, or six
   columns of ellipsis. Both layouts are server-rendered and CSS picks between
   them, so the page works with JS off and never flashes the wrong one. */
.wo-cards { display: none; }

@media (max-width: 767.98px) {
  .wo-cards { display: grid; gap: 8px; }
  .table-card--wo { display: none; }   /* scoped: other pages keep their tables */
}

.wo-card {
  display: block;
  padding: 12px 14px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-card);
  background: var(--bg-card);
  box-shadow: var(--shadow-card);
  color: inherit;
  text-decoration: none;
}
.wo-card:active { background: var(--bg-page); }

.wo-card__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
  flex-wrap: wrap;   /* badges drop under the number on a narrow phone */
}
.wo-card__badges {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* Each badge shows its full label — no "Give…" / "N…" clipping. */
.wo-card__badges .badge { white-space: nowrap; }
.wo-card__number { font-size: .9rem; }
.wo-card__client { font-weight: 600; line-height: 1.3; }
.wo-card__meta {
  font-size: .8rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wo-card__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
}

/* ── "Este taller" shop memory + Chat técnico drawer (2026-08-17) ────── */
.shop-mem__head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.shop-mem__head .bi-stars { color: var(--brand-600, #2563eb); }
.shop-mem__label {
  font-size: .78rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--text-muted); margin-bottom: 4px;
}
.shop-mem__section { margin-bottom: 12px; }
/* "Esta máquina volvió" owns the eye: brand ring, never buried. */
.shop-mem__section--machine {
  border: 1px solid var(--brand-600, #2563eb);
  border-radius: var(--radius-card, 12px);
  padding: 8px 10px;
  background: color-mix(in srgb, var(--brand-600, #2563eb) 6%, transparent);
}
.shop-mem__section--machine .shop-mem__label { color: var(--brand-600, #2563eb); }
.shop-mem__row {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 6px 0; border-top: 1px solid var(--border-soft);
  font-size: .875rem;
}
.shop-mem__row:first-of-type { border-top: 0; }
.shop-mem__id { font-weight: 700; text-decoration: none; white-space: nowrap; }
.shop-mem__who { color: var(--text-muted); white-space: nowrap; font-size: .8rem; }
.shop-mem__line { flex: 1 1 12ch; min-width: 0; }
.shop-mem__foot { margin-top: 8px; text-align: end; }

.shop-mem__row .badge { font-variant-numeric: tabular-nums; } /* totales alinean */

/* JOYA-7: la fila que acaba de nacer del "Repetir" parpadea 2 s y vuelve
   a la normalidad — CSS puro, ningún JS que limpiar. */
@keyframes row-flash {
  0%   { background-color: color-mix(in srgb, var(--brand-600, #2563eb) 22%, transparent); }
  100% { background-color: transparent; }
}
tr.row-flash > td { animation: row-flash 2s ease-out 1; }

.tech-chat-scrim {
  position: fixed; inset: 0; background: rgba(0, 0, 0, .35);
  opacity: 0; pointer-events: none; transition: opacity .15s ease; z-index: 1049;
}
.tech-chat-scrim.is-open { opacity: 1; pointer-events: auto; }
.tech-chat {
  position: fixed; top: 0; right: 0; bottom: 0; width: 360px; max-width: 100vw;
  background: var(--bg-card); border-left: 1px solid var(--border-soft);
  box-shadow: -8px 0 24px rgba(0, 0, 0, .12);
  display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform .18s ease; z-index: 1050;
}
.tech-chat.is-open { transform: translateX(0); }
.tech-chat__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border-soft);
}
.tech-chat__head .bi-stars { color: var(--brand-600, #2563eb); }
.tech-chat__body { flex: 1 1 auto; overflow-y: auto; padding: 12px 14px; }
.tech-chat__compose { padding: 10px 14px; border-top: 1px solid var(--border-soft); }
.tech-chat__compose .input-ds { width: 100%; min-height: 40px; }

@media (max-width: 767.98px) {
  /* Bottom sheet: 70vh, rounded top, gloves-sized targets. */
  .tech-chat {
    top: auto; left: 0; right: 0; height: 70vh; width: 100%;
    border-left: 0; border-top: 1px solid var(--border-soft);
    border-radius: 16px 16px 0 0;
    transform: translateY(100%);
  }
  .tech-chat.is-open { transform: translateY(0); }
}

/* Fase 2 (LLM chat): sus burbujas/typing se escriben cuando exista la UI —
   el CSS anticipado vivió aquí huérfano un mes (censo 9a) y salió. */
.tech-chat__compose { display: flex; gap: 6px; }

/* PR-14 — "+N more" on a crowded hour (week/day timelines) and the agenda list */
.cal-more { position: absolute; right: 4px; bottom: 4px; z-index: 5; font-size: .7rem; font-weight: 600; padding: 1px 8px; border-radius: 999px; background: var(--bg-card); border: 1px solid var(--bs-border-color); color: var(--text-strong); text-decoration: none; }
.cal-more:hover { background: var(--bs-tertiary-bg); color: var(--text-strong); }
.cal-agenda td { vertical-align: top; }
.cal-agenda__active td { background: var(--bs-success-bg-subtle, #d1fae5); }

/* PR-27: the bell is red only while something actionable is unread; news is grey. */
.topbar-bell-count.is-quiet { background: var(--text-muted) !important; color: #fff; }

/* PR-15: van identity by class — chips, dots, cards and pins read --van from here. */
.van-1 { --van: var(--van-color-1); } .van-2 { --van: var(--van-color-2); } .van-3 { --van: var(--van-color-3); } .van-none { --van: var(--van-color-none); }
.van-dot { background: var(--van); }
.fleet-van-card.is-off { opacity: .45; }
.fleet-layer-chip { cursor: pointer; }
