/* Industry — design-system tokens and component classes. This file is the source of truth for the system's look; retune it here and see readme.md. */
@import url('https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;700&family=Barlow+Condensed:wght@400;600&display=swap');

:root {
  /* This design system has no dark theme — without this, a browser/OS in
     dark mode applies its own dark UA styling to native form controls and,
     more visibly, to Google's Material-based web components (like the
     address autocomplete), which pick their surface colors off this
     property rather than off our --gmpx-* overrides. */
  color-scheme: light;

  /* Brand palette (2026-09-13): #122e50 / #294867 / #5f7c99 / #ffa405 / #f6f1e9.
     --color-accent is the ever-present navy — buttons, links, focus, active
     nav state, all of it. --color-highlight is the orange itself, kept
     around for a bold isolated moment (a swatch, a future logo mark) but
     NOT used for buttons/active chrome or run at small sizes — at full
     brightness it has too little contrast against the cream ground to
     read as a thin icon stroke or small text. --color-weather is that
     same orange darkened for exactly that job: the weather-warning icons
     on job cards/lists and the weather notice/banner, where it needs to
     actually stand out, not just match the brand hue. */
  --color-bg: #f6f1e9;
  --color-surface: #e4e1dd;
  /* The system's "white" — never a literal #fff, which reads cold/sterile
     against the warm cream ground. For a card that should pop as a
     distinctly brighter surface than --color-bg/--color-surface (the
     Today page's job rows, its clock-in card), not a general replacement
     for either of those. */
  --color-card: #fffdf9;
  --color-text: #122e50;
  --color-accent: #294867;
  --color-accent-2: #5f7c99;
  --color-highlight: #ffa405;
  --color-highlight-600: #e69404;
  --color-highlight-700: #cc8304;
  --color-highlight-100: #fff1db;
  --color-weather: #cc8304;
  --color-divider: color-mix(in srgb, #122e50 16%, transparent);
  /* There is no red or green in this palette (2026-10-08 UI audit) —
     --color-warn is what "this needs attention" (a past-due amount,
     negative profit, low stock, a failed sync) reads as instead of red;
     --color-warn-tint is its matching background for a tag/banner. Good/
     neutral states stay plain navy or mid navy, not a separate green.
     Same hue as --color-highlight-700 (already proven readable as small
     text elsewhere — past-due/unscheduled labels, aging pills) — brighter
     than the original #7a4f00, but still well short of the full brand
     --color-highlight (#ffa405), which this file's own tokens note is too
     low-contrast against the cream background to read as text at all. */
  --color-warn: #cc8304;
  --color-warn-tint: rgba(255, 164, 5, .16);

  /* Neutral ramp is left as true (achromatic) grey on purpose — it reads
     fine against the warm cream ground and this way nothing here has to
     guess at a warm-grey tint by hand. */
  --color-neutral-100: #f5f5f8;
  --color-neutral-200: #e7e7ea;
  --color-neutral-300: #d4d4d7;
  --color-neutral-400: #b7b7ba;
  --color-neutral-500: #98989b;
  --color-neutral-600: #7a7a7d;
  --color-neutral-700: #5d5d60;
  --color-neutral-800: #424244;
  --color-neutral-900: #2b2b2d;

  /* Tonal ramps, hand-tuned around the three brand navy anchors (500/700/900
     below are the palette's #5f7c99 / #294867 / #122e50 exactly; the rest
     interpolate toward white for the light end of the scale). */
  --color-accent-100: #eff2f5;
  --color-accent-200: #dce2e9;
  --color-accent-300: #bfcbd6;
  --color-accent-400: #9fb0c2;
  --color-accent-500: #5f7c99;
  --color-accent-600: #446280;
  --color-accent-700: #294867;
  --color-accent-800: #1e3b5c;
  --color-accent-900: #122e50;

  --color-accent-2-100: #f1f4f6;
  --color-accent-2-200: #dfe5eb;
  --color-accent-2-300: #c5d0dc;
  --color-accent-2-400: #a8b9cb;
  --color-accent-2-500: #6f8ba6;
  --color-accent-2-600: #4c6f8c;
  --color-accent-2-700: #325678;
  --color-accent-2-800: #23446b;
  --color-accent-2-900: #1a3559;

  --font-heading: "Barlow Condensed", system-ui, sans-serif;
  --font-heading-weight: 600;
  --font-body: "Barlow", system-ui, sans-serif;

  --space-1: 3.4px;
  --space-2: 6.8px;
  --space-3: 10.2px;
  --space-4: 13.6px;
  --space-6: 20.4px;
  --space-8: 27.2px;

  --radius-sm: 2px;
  --radius-md: 4px;
  --radius-lg: 7px;

  /* Elevation — derived from the ground: soft ink-tinted shadows on a
     light theme, a hairline edge + ambient darkness on a dark one. */
  --shadow-sm: 0 1px 2px color-mix(in srgb, #122e50 14%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #122e50 16%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #122e50 22%, transparent);
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }

/* ══════════════════════════════════════════════════════════════════════════
   Components — built with the tokens above. Plain CSS
   on plain HTML: no JavaScript, no build step. Each class is documented in
   readme.md and demonstrated in foundations/ and components/.
   ══════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400; }
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.12; letter-spacing: -0.015em; margin: 0;
}
h1 { font-size: 42px; }
h2 { font-size: 32px; }
h3 { font-size: 25px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
h6 { font-size: 13px; }
h6 { letter-spacing: 0.08em; text-transform: uppercase; }
p { margin: 0; }
a { color: var(--color-accent); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }
figure { margin: 0; }
figcaption {
  font-size: 12px; margin-top: var(--space-1);
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* — rules — */
.hr {
  height: 1px; border: 0; margin: var(--space-4) 0;
  background: var(--color-divider);
}

/* Native radios/checkboxes/range sliders that aren't the custom
   .check-box-row box — brand navy, never the browser's default blue. */
input[type="radio"], input[type="checkbox"], input[type="range"], progress { accent-color: #122e50; }

/* — buttons — */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text); /* matches the .input's 14px —
     the pair sits side by side in sign-up rows */
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
  /* Button text is condensed caps everywhere (brand rule; 2026-10-28 pass). */
  text-transform: uppercase; letter-spacing: .06em;
}
.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
/* .btn sets display, which would otherwise beat the [hidden] attribute — a
   "hidden" button stayed visible (Account showed Enable and Turn off together). */
.btn[hidden] { display: none; }
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
.btn-primary:hover { background: var(--color-accent-600); }
.btn-primary:active { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
/* A small, both-sides-padded secondary action next to a label or value
   (Clear, Retry, Copy link, Regenerate, Show all...) — these had six
   slightly different ad hoc inline paddings across pages for the same
   visual role; one shared size here instead. Not for the "+ Add X" links
   (those stay flush via padding-left:0, a different, already-consistent
   convention) or a toggle-style chip like the route board's Pin button
   (deliberately a larger tap target in that context). */
.btn-ghost-sm { font-size: 12px; padding: 4px 8px; }
.btn-icon { width: 36px; height: 36px; padding: 0; }
/* Same strength as the row's other icons (edit, pause…) — a faded trash can read as disabled. */
.btn-icon-danger { color: var(--color-accent); }
/* Destructive actions are mid navy, never amber — amber means "needs
   attention", not "dangerous" (2026-10-16 audit, pattern L). Every one of
   these sits behind a confirm(). */
.btn-icon-danger:hover { color: #294867; background: rgba(41, 72, 103, .10); }
.btn-icon-danger:active { background: rgba(41, 72, 103, .18); }
.btn-destructive, .btn-danger-text { color: #294867 !important; text-decoration: underline; border-color: transparent !important; background: none; }
.btn-destructive:hover, .btn-danger-text:hover { color: #122e50 !important; }
.btn-block { width: 100%; margin-top: var(--space-2); font-size: 18px; padding-top: 12px; padding-bottom: 12px; }
/* Full-width on mobile (where a primary action spanning the screen is the
   norm here), sized to its own label on desktop — .btn-block itself stays
   unconditionally full-width since it's also the auth/portal pages' own
   convention at any width, not just this app's forms. */
.btn-block-responsive { width: 100%; margin-top: var(--space-2); }
@media (min-width: 860px) {
  .btn-block-responsive { width: auto; font-size: 14px; }
}

/* List-table row actions — a text button (e.g. "View") sits next to 36px
   icon buttons; match its height and align all three on the same centre
   line. Left as a normal table cell (not flex) so its row divider still
   lines up with the other columns. */
.row-actions { white-space: nowrap; }
.row-actions .btn { height: 36px; vertical-align: middle; }
/* Hidden until the row is hovered/focused — desktop only (min-width
   matches field.css's own sidebar/tab-bar breakpoint): a touch device has
   no hover state to reveal them with, so they stay visible there, same as
   the collapsible mobile card layout already keeps its own actions cell
   visible unconditionally. :focus-within (not :focus) so tabbing onto any
   button in the row reveals the whole group, not just the one focused. */
@media (min-width: 860px) {
  /* The cell's direct children (a link, or the little form around a
     button), never the .btn inside a form — hiding both levels made a
     form-wrapped trash/pause icon vanish instantly on mouse-out while a
     bare pencil link faded, so the pencil looked laggy. */
  .table .row-actions > * { transition: opacity .12s ease; }
  .table tbody tr:not(:hover):not(:focus-within) .row-actions > * {
    opacity: 0;
    pointer-events: none;
  }
}

/* — forms — */
/* Every form label: small condensed caps (2026-10-28 sitewide pass — forms
   had three label styles). Checkbox rows and toggle options aren't labels. */
.field > label:not(.check-box-row):not(.seg-opt):not(.radio) {
  display: block; margin-bottom: 5px;
  font-family: var(--font-heading); font-weight: 600; font-size: 12px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--color-text);
}
/* "(optional)" and similar asides inside a label stay plain. */
.field > label .text-muted-field { font-family: var(--font-body); font-weight: 400; letter-spacing: 0; text-transform: none; }
/* A checkbox row inside a field is an option, not a field label — full
   size and full-strength text (unticked options used to read as small
   grey text, i.e. disabled). */
.field > label.check-box-row { font-size: 15px; color: var(--color-text); margin-bottom: 0; }
/* Required fields are the minority on most forms here — a '*' marks the
   few that are, rather than captioning every other field "(optional)". */
.required-asterisk { color: var(--color-accent-900); margin-left: 2px; }
.input {
  width: 100%; min-height: 36px; padding: 6px 10px; font: inherit;
  font-size: 14px; color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-card);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
input.input:is([type=date], [type=time], [type=datetime-local]) { height: 36px; }
/* The native select arrow isn't part of the padding box in most browsers
   — it sits flush to the edge no matter what padding-right is set to, so
   there's no way to nudge it in with CSS alone. Swapping it for a custom
   chevron (appearance:none + a background-image) is the only way to
   actually position it — placed at the same 10px offset as the input's
   own left padding, so it lines up symmetrically instead of crowding the
   edge. padding-right leaves room so selected text never runs under it. */
select.input {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%235d5d60' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 16px;
}
/* A text <input list="..."> (the Category autocomplete) gets its
   "has suggestions" arrow from the browser natively, not from appearance
   — that trick only applies to <select>. The pseudo-element browsers use
   for it is (non-standardly, but consistently in Chrome/Safari/Edge) the
   same one used for date/time pickers, so it can be hidden the same way
   and replaced with the identical custom chevron above for a matching
   look. Firefox has no equivalent hook and keeps its own native arrow —
   there's no CSS-only way to reach it there. */
input.input[list] {
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%235d5d60' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 16px;
}
input.input[list]::-webkit-calendar-picker-indicator {
  opacity: 0;
  -webkit-appearance: none;
}

.input-invalid, .input-invalid:hover, .input-invalid:focus-visible { border-color: var(--color-warn); }
.field-error { font-size: 12px; color: var(--color-warn); margin-top: 4px; display: none; }
.field-error.is-visible { display: block; }
.field-search { position: relative; }
/* White, not .input's own default grayish --color-surface — a search box
   sitting right next to other white controls (the type dropdown, cards)
   reads as a mismatched fill otherwise. */
.field-search .input { height: 44px; padding: 0 14px 0 34px; background: var(--color-card); }
.field-search svg {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  color: color-mix(in srgb, var(--color-text) 45%, transparent); pointer-events: none;
}
.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.radio input, .seg-opt input {
  position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--color-divider);
}
.radio:hover .dot { border-color: var(--color-accent); }
.radio input:checked + .dot {
  border-color: var(--color-accent); background: var(--color-accent);
  box-shadow: inset 0 0 0 4px var(--color-bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* :not([hidden]), not a bare class rule — any author-origin rule beats the
   browser's own default [hidden]{display:none} regardless of specificity
   (that's a cascade-origin rule, not a specificity one), so an unconditional
   display here would make [hidden] on the element a no-op either way. This
   way the rule simply doesn't apply at all while hidden is present, and the
   browser default takes over uncontested. */
.deposit-fields-row:not([hidden]) { display: flex; gap: var(--space-3); margin-top: var(--space-2); align-items: flex-end; }
.push-enabled-row:not([hidden]) { display: flex; align-items: center; gap: 8px; }
.seg {
  display: inline-flex; flex-wrap: wrap; gap: 6px;
  /* A flex item defaults to a min-width that can't shrink past its own
     content — harmless for a 2-3 option toggle, but a 6-option row (the
     jobs status filter) is then forced to overflow the page or get
     clipped by whatever container it's in. min-width:0 lets this box
     shrink to the space actually available, and flex-wrap above lets it
     spill onto a second row there instead — every option stays reachable
     and, being its own flex item (white-space:nowrap below), never
     breaks mid-label the way a plain shrinking block would. */
  min-width: 0; max-width: 100%;
}
.seg-opt {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 7px 12px; font-size: 13px; cursor: pointer; white-space: nowrap;
  color: inherit; text-decoration: none; background: var(--color-card);
  /* Each option is its own fully-bordered chip rather than one shared
     outline around the whole group — a joined "segmented control" border
     only reads cleanly across a single row; once a wide option list
     wraps to a second line (6 statuses, 5 roles, a narrow screen), a
     shared border makes the wrapped row look like a stray fragment
     trapped in a box that belongs to the row above it. Independent chips
     wrap exactly as gracefully as any other row of buttons. */
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
/* A "joined" segmented control — one shared outer border with internal
   dividers instead of each option being its own separate chip. Opt-in
   (see .seg-opt's own comment on why independent chips are the default):
   only makes sense for a short, fixed option count that never wraps, like
   the jobs list's List/Map/Calendar view toggle. */
.seg-joined { gap: 0; border: 1px solid var(--color-divider); }
.seg-joined .seg-opt {
  border: none; border-radius: 0;
  padding: 12px 18px; font-size: 15px;
}

/* seg-filter still claims the full row width when it shares a flex row
   with something else (jobs' Type filter alongside the List/Map toggle)
   — unrelated to the border style above, just layout. */
@media (max-width: 859.98px) {
  .seg-filter { flex-basis: 100%; }
}
/* jobs/index swaps its status seg-filter for a dropdown below tablet
   width (see .status-dropdown) — scoped to .list-toolbar rather than
   .seg-filter itself so clients/index's own status filter, which has no
   dropdown built, keeps wrapping as normal chips instead of disappearing. */
@media (max-width: 859.98px) {
  .list-toolbar .seg-filter { display: none; }
}

/* — status dropdown (jobs/index, mobile only) — */
.status-dropdown { display: none; }
@media (max-width: 859.98px) {
  .status-dropdown { display: block; position: relative; }
}
.status-dropdown-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 12px; color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.status-dropdown-current { color: var(--color-accent); font-size: 13px; }
.status-dropdown-menu {
  /* position:fixed, placed by JS from the trigger's own bounding rect
     (see jobs/index.blade.php) — a fixed left/right CSS anchor assumed
     the trigger always sits at one particular end of the toolbar row,
     which broke (menu clipped by the viewport edge) as soon as the
     trigger actually landed somewhere else, e.g. at the row's start on
     narrow screens once the search field wraps below it. */
  position: fixed; z-index: 20;
  min-width: 190px; max-height: 60vh; overflow-y: auto;
  background: #fdfcfb; border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.status-dropdown-item {
  display: block;
  padding: 9px 12px; font-size: 13px; color: var(--color-text); text-decoration: none;
  border-bottom: 1px solid var(--color-divider);
}
.status-dropdown-item:last-child { border-bottom: none; }
.status-dropdown-item:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.status-dropdown-item.is-active { background: var(--color-accent-900); color: var(--color-bg); }

/* — type dropdown (jobs/index) — */
/* Unlike .status-dropdown above (a mobile-only fallback for the status
   chips, hidden on desktop), this is the jobs list's only "type" control
   at any width — styled like a bordered select box, not a caption link,
   since it sits directly next to the search field on the same row. */
.type-dropdown { position: relative; }
.type-dropdown-trigger {
  display: inline-flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--color-card); border: 1px solid var(--color-divider);
  height: 44px; padding: 0 14px; font-size: 15px; color: var(--color-accent-900, #122e50);
  min-width: 150px; cursor: pointer;
}
.type-dropdown-menu {
  /* position:fixed, placed by JS — see the identical note on
     .status-dropdown-menu above. */
  position: fixed; z-index: 20;
  min-width: 190px; max-height: 60vh; overflow-y: auto;
  background: #fdfcfb; border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.type-dropdown-item {
  display: block;
  padding: 9px 12px; font-size: 13px; color: var(--color-text); text-decoration: none;
  border-bottom: 1px solid var(--color-divider);
}
.type-dropdown-item:last-child { border-bottom: none; }
.type-dropdown-item:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.type-dropdown-item.is-active { background: var(--color-accent-900); color: var(--color-bg); }

/* — multi-select picker (resources/views/components/multi-select-picker.blade.php) —
   Tool sets / Crew on the job form: a bordered control showing removable
   chips for what's picked, opening a searchable-in-spirit (currently just
   scrollable) checklist panel below it with a Clear/Done footer. */
.ms-picker { position: relative; }
.ms-control {
  display: flex; align-items: center; gap: 8px;
  /* 36px, matching .input's own min-height — every plain field on this
     app's forms is 36px tall; this was picked without checking against
     that when the picker family was first built, making it the outlier. */
  min-height: 36px; padding: 6px 10px; background: var(--color-card);
  border: 1px solid var(--color-divider); cursor: text;
}
.ms-picker:has([data-ms-panel]:not([hidden])) .ms-control,
.ms-picker:has([data-ss-panel]:not([hidden])) .ms-control { border-color: var(--color-accent); }
.ms-search-icon { flex: none; color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.ms-chips { flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.ms-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--color-accent-900); color: var(--color-bg);
  font-size: 13px; padding: 5px 6px 5px 10px; white-space: nowrap;
}
.ms-chip-remove {
  background: none; border: none; padding: 0; margin: 0; line-height: 1;
  color: inherit; opacity: .75; font-size: 15px; cursor: pointer;
}
.ms-chip-remove:hover { opacity: 1; }
.ms-search {
  flex: 1 1 80px; min-width: 80px; border: none; background: none; outline: none;
  font: inherit; font-size: 14px; color: var(--color-text); padding: 0;
}
.ms-search::placeholder { color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.ms-caret { flex: none; color: color-mix(in srgb, var(--color-text) 50%, transparent); margin-left: auto; }
.ms-panel {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 20;
  background: #fdfcfb; border: 1px solid var(--color-divider); box-shadow: var(--shadow-lg);
}
.ms-options { max-height: 320px; overflow-y: auto; }
/* .ms-options ahead of the element+class+class .is-compact rule below it
   in the file — this needs the extra ancestor class for its padding to
   actually win at equal specificity, not just source order. */
.ms-options label.ms-option.check-box-row { padding: 10px 14px; gap: 12px; }
/* A plain [hidden] attribute would lose to label.check-box-row's own
   unconditional `display: flex` at equal author-origin priority — two
   classes beats element+class, so this wins regardless of source order. */
.ms-option.is-filtered-out { display: none; }
.ms-no-results { padding: 14px; margin: 0; font-size: 14px; }
/* Materials picker's "Add '<typed text>' as a one-off pickup" row — sits
   above the catalog checklist, only shown when the typed text doesn't
   match a real catalog item. Not .ms-option: it must stay outside that
   class's filter-by-text and checkbox-state selectors, since it's neither
   filterable nor a real, persistent choice. */
.ms-add-custom {
  display: flex; width: 100%; align-items: center; gap: 8px;
  padding: 10px 14px; border: none; border-bottom: 1px solid var(--color-divider);
  background: none; font: inherit; font-size: 14px; color: var(--color-accent);
  text-align: left; cursor: pointer;
}
.ms-add-custom:hover { background: color-mix(in srgb, var(--color-accent) 6%, transparent); }
/* A plain [hidden] loses to this element's own unconditional `display:
   flex` above at equal author-origin priority (same trap as .check-box-row
   elsewhere in this file) — without this, the row shows up empty even
   before anything's been typed. */
.ms-add-custom[hidden] { display: none; }
/* Single-select picker (Equipment): same row as .check-box-row/.box, just
   rounded into the app's existing .radio "circular dot" shape — one
   choice, not several, so it reads as a radio despite reusing the square
   checkbox's CSS machinery (:has(input:checked), the checkmark icon). */
.ss-option .box { border-radius: 50%; }
.ms-option-label { flex: 1 1 auto; min-width: 0; align-self: center; }
.ms-option-meta { flex: none; margin-left: auto; font-size: 13px; white-space: nowrap; align-self: center; }
/* A crew running 3+ jobs the day being scheduled — a heads-up while
   picking, not an error; same amber as every other "worth a second look"
   signal in the app. */
.ms-option-meta.is-busy { color: var(--color-highlight-700); }
.ms-footer {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: 10px 14px; border-top: 1px solid var(--color-divider); font-size: 13px;
}
.ms-footer-actions { display: flex; align-items: center; gap: var(--space-3); }

/* A filter chip's own count — dimmer than the label so "11" reads as a
   detail, not a second label of equal weight. The active/attention state
   rules below both override this via higher specificity when they apply. */
.seg-opt-count { font-weight: 600; opacity: .7; }
/* The currently-selected filter chip (jobs status row) — a class instead
   of the inline style it used to be, so its count can get its own color
   below rather than just inheriting the pill's white text. */
.seg-opt-selected { background: var(--color-accent-900); border-color: var(--color-accent-900); color: var(--color-bg); }
/* A chip that wants attention even while it isn't the active filter — e.g.
   Unscheduled, once anything's actually sitting in that bucket. Only the
   border and the count itself pick up the warm color; the label stays the
   same navy (or white, once selected) as every other chip. */
.seg-opt-attention { border-color: var(--color-highlight); }
/* The count is the one thing that always pops in the accent color — on
   the selected chip and on one flagged for attention alike — while every
   other chip's count stays a muted, secondary tone (the opacity above). */
.seg-opt-selected .seg-opt-count, .seg-opt-attention .seg-opt-count { color: var(--color-highlight); opacity: 1; }
/* A filter with nothing in it — still clickable, but recedes so the
   buckets that actually hold something are what the eye lands on. */
.seg-opt-empty { background: transparent; border-color: rgba(18, 46, 80, .12); color: color-mix(in srgb, var(--color-text) 45%, transparent); }
/* Bigger than the base .seg-opt chip (13px/7px-12px) — the jobs status
   row sits directly under the search bar + type dropdown, both sized much
   larger, and reads as cramped next to them at the default chip scale. */
.seg-filter-lg .seg-opt { padding: 12px 18px; font-size: 15px; }
.seg-opt:has(input:checked) { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-bg); }
.seg-opt:not(:has(input:checked)):not(.is-active):not(.seg-opt-selected):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }
button.seg-opt { font: inherit; font-size: 13px; }
/* A tab row sitting inside a section heading (Financial's "Break down by")
   is still a normal tab, not part of the heading's caps. */
.scope-label .seg-opt, .form-section-title .seg-opt { font-family: var(--font-body); font-weight: 400; text-transform: none; letter-spacing: 0; }
button.seg-opt:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.seg-opt.is-active { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-bg); }
.seg-opt.is-active:hover { background: var(--color-accent); }
.seg-opt-selected:hover { background: var(--color-accent-900); }

/* — underline tabs (page-section switcher, e.g. Settings) — distinct from
   .seg/.seg-opt above: that's a segmented control/filter-pill component
   reused across list pages, this is a heavier one-per-page section
   switcher and reads better as underlined text than bordered chips. */
/* nowrap + horizontal scroll rather than letting a wide tab count (e.g.
   Settings' 7) wrap into a ragged multi-line block on a narrow screen —
   one row, swipe for the rest. Scrollbar hidden since the underline/active
   state is already enough of a position cue; -webkit-overflow-scrolling
   gives it inertial scroll on iOS. */
.tab-strip {
  display: flex; flex-wrap: nowrap; gap: 0 var(--space-6);
  border-bottom: 1px solid var(--color-divider);
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.tab-strip::-webkit-scrollbar { display: none; }
.tab-strip-link {
  display: inline-flex; align-items: center; flex: none;
  padding: var(--space-3) 0; margin-bottom: -1px;
  font-size: 14px; color: color-mix(in srgb, var(--color-text) 55%, transparent);
  text-decoration: none; white-space: nowrap;
  border-bottom: 2px solid transparent;
}
.tab-strip-link:hover { color: var(--color-text); }
.tab-strip-link.is-active { color: var(--color-text); border-bottom-color: var(--color-accent); font-weight: 600; }
/* The global box outline (:focus-visible further up) reads as stray
   vertical bars here — these links sit edge-to-edge with no horizontal
   padding, so the outline's left/right sides land right against the
   text with nothing to visually contain them. The same underline
   treatment .is-active already uses reads as focus just as clearly. */
.tab-strip-link:focus-visible { outline: none; color: var(--color-text); border-bottom-color: var(--color-accent); }

/* — cards — */
.card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
/* Same as every other section heading (.scope-label, .form-section-title). */
.card-kicker { font-family: var(--font-heading); font-weight: 600; font-size: 16px; letter-spacing: .07em; text-transform: uppercase; color: var(--color-text); }
.card-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 17px; line-height: 1.2;
}
.card-body { margin: 0; font-size: 13px; opacity: 0.8; flex: 1; }

/* — tags — */
.tag {
  display: inline-flex; align-items: center; font-size: 12px;
  letter-spacing: 0.02em; padding: 3px 10px;
  border-radius: calc(var(--radius-md) * 0.75);
}
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* — status pills (2026-10-08 UI audit, pattern C) — one shared component
   (<x-status-pill variant="...">) for every status badge app-wide (jobs,
   invoices/estimates, clients, team membership, ...), four fill weights so
   e.g. Paid and Sent can be told apart at a glance instead of all reading
   as the same thin outline. Square corners, same as every other tag. */
.status-pill {
  display: inline-flex; align-items: center; font-size: 12px;
  letter-spacing: 0.02em; padding: 3px 10px;
}
.status-pill-done { background: var(--color-accent-900); color: var(--color-bg); border: none; }
.status-pill-waiting { border: 1px solid var(--color-accent-900); color: var(--color-accent-900); }
.status-pill-attention { background: var(--color-highlight); color: var(--color-accent-900); border: none; }
.status-pill-closed { background: rgba(18, 46, 80, .08); color: var(--color-accent-2); border: none; }
/* In a list, the settled "everything's fine" state (Active client, Paid
   invoice, Complete job, Approved estimate) is plain text — a column
   where almost every row is that state used to be a wall of solid navy,
   and the few rows that need a look (Lead, Draft, Sent, Past due,
   Unscheduled, Missed) got lost in it. Detail pages keep the solid pill:
   there it's the one status on screen. */
.table .status-pill-done,
.list-card .status-pill-done,
.jlc-card .status-pill-done,
.job-row .status-pill-done {
  background: none; color: var(--color-accent); padding-left: 0; padding-right: 0;
}

/* — navigation — */
.nav {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: none;
}
.nav-brand {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; margin-right: auto;
}
.nav a { color: inherit; text-decoration: none; font-size: 14px; }
.nav a:hover, .nav a[aria-current='page'] { color: var(--color-accent); }
.nav a.btn-primary, .nav a.btn-primary:hover { color: var(--color-bg); }

/* — tables — */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: #5f7c99; white-space: nowrap;
  padding: var(--space-2); border-bottom: 1px solid var(--color-divider);
}
.table td {
  padding: var(--space-2);
  border-bottom: 1px solid rgba(18, 46, 80, .12);
}
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }

/* A list page's table/results area as a distinct white card sitting on
   the cream page background, rather than the table just floating
   directly on it. Works as a wrapper around a table + its footer (most
   list pages), or applied straight onto a <table> itself for pages with
   no separate footer element (job-series, timesheets) — padding on a
   table element is respected the same as on any block box. */
.list-card { background: var(--color-card); border: 1px solid var(--color-divider); padding: var(--space-6); padding-top: var(--space-4); }
/* No border-top here — the table's own last row already draws a
   border-bottom right above this (same as every other row), and a second
   border directly under it with no gap between them doubles up into one
   thicker line instead of reading as a single hairline. */
.list-card-footer {
  display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap;
  gap: var(--space-3); padding: var(--space-4) 0;
}
.col-sort-link {
  display: inline-flex; align-items: center; gap: 2px;
  color: inherit; text-decoration: none;
}
.col-sort-link:hover { color: var(--color-accent); }
.col-sort-link.is-active { color: var(--color-accent-900); }
.col-sort-arrow { font-size: 13px; margin-left: 3px; line-height: 1; }
/* A cell that must not wrap (phone numbers). */
.row-nowrap { white-space: nowrap; }
.col-sort-link svg { flex: none; }
/* Muted until hovered/active — signals "clickable" without competing with
   the header text the rest of the time. */
.col-sort-icon-idle { color: color-mix(in srgb, var(--color-text) 35%, transparent); }
.col-sort-link:hover .col-sort-icon-idle { color: var(--color-accent); }

/* — pagination (resources/views/vendor/pagination/custom.blade.php) — */
.pagination-nav { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; font-size: 13px; }
.pagination-pages { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.pagination-link {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; height: 28px; padding: 0 8px;
  color: var(--color-accent); text-decoration: none;
}
.pagination-link:hover { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.pagination-link.is-current { background: var(--color-accent-900); color: var(--color-bg); font-weight: 600; }
.pagination-link.is-disabled { color: color-mix(in srgb, var(--color-text) 30%, transparent); }
.pagination-ellipsis {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; height: 28px; color: color-mix(in srgb, var(--color-text) 40%, transparent);
}
/* "Not scheduled" / "Unassigned" both flag something the office still
   needs to act on, so both read in the same warm color as the
   Unscheduled status pill rather than blending into the row's plain text. */
.job-row-when-unset, .job-row-crew-unassigned { color: var(--color-highlight-700); }
.job-row-crew-dot {
  display: inline-block; width: 7px; height: 7px; margin-right: 5px;
  background: var(--color-highlight-700); vertical-align: 1px;
}
.job-title-text { color: inherit; text-decoration: none; font-weight: 700; }
.job-title-text:hover { text-decoration: underline; }
/* A table row's own name cell (client, team member, job's client column) —
   condensed-caps (.k) reads fine for a short label but turns a person's
   actual name into shouted, hard-to-scan letterforms; the row itself is
   already the link (or, on jobs/index, a plain cell next to one), so no
   underline either. */
.row-name { font-weight: 600; text-transform: none; letter-spacing: normal; font-family: var(--font-body); }
.row-name a { color: inherit; text-decoration: none; }
.row-name a:hover { text-decoration: underline; }
/* Tools: "Last on a job" link (list column + tool page). */
.tool-last-job { color: var(--color-accent-900, #122e50); font-weight: 600; text-decoration: none; }
.tool-last-job:hover { text-decoration: underline; }
.row-time { white-space: nowrap; font-size: 13px; }
/* Ran well past its estimate (ServiceJob::isOverrun) — the one number in this column worth a look. */
.row-time-overrun { color: var(--color-highlight-700, #cc8304); font-weight: 600; }
.row-time-flag {
  display: block; margin-top: 3px; font-size: 12px; font-weight: 700;
  color: var(--color-highlight-700); white-space: nowrap;
}
.row-time-flag-dot {
  display: inline-block; width: 7px; height: 7px; margin-right: 5px;
  background: var(--color-highlight-700); vertical-align: 1px;
}
/* A canceled job stays in the list (it's still a record), but reads as
   settled/inactive — struck-through title, everything else faded. */
.row-canceled { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.row-canceled .job-title-text { text-decoration: line-through; }

/* — status in any table: a small square marker + text, not a filled pill
   (the Jobs table look). Hollow navy = waiting, solid navy = done, amber =
   needs a look, hollow slate = closed. Detail pages and phone cards keep
   the solid pill: there it's the one status on screen. */
.table .status-pill { background: none; border: 0; padding: 0; gap: 9px; font-size: inherit; letter-spacing: 0; color: var(--color-accent-900); white-space: nowrap; }
.table .status-pill::before { content: ''; flex: none; width: 11px; height: 11px; box-sizing: border-box; }
.table .status-pill-waiting::before { border: 1.5px solid var(--color-accent-900); }
.table .status-pill-done::before { background: var(--color-accent-900); }
.table .status-pill-attention { color: #7a4f00; font-weight: 600; }
.table .status-pill-attention::before { background: var(--color-highlight); }
.table .status-pill-closed { color: #5f7c99; }
.table .status-pill-closed::before { border: 1.5px solid #5f7c99; }

/* — main record lists (.list-table): Jobs, Clients, Invoices, Tools, … —
   one flat white card with rows edge to edge, roomy rows, the first
   column as the row's title, Edit/Delete only on hover. Desktop only:
   below 1180px these tables become phone cards (see .table above). */
@media (min-width: 1180px) {
  .list-table.list-card, .list-card:has(> .list-table) { padding: 0; }
  /* The "N results for …" line and similar still sit inside the card. */
  .list-card:has(> .list-table) > :not(table):not([hidden]) { margin: 0; padding: 16px 20px; }
  .list-table { font-size: 15px; }
  .list-table th { padding: 18px 16px; }
  .list-table td { padding: 20px 16px; vertical-align: middle; }
  .list-table th:first-child, .list-table td:first-child { padding-left: 20px; }
  .list-table th:last-child, .list-table td:last-child { padding-right: 20px; }
  .list-table tbody tr:last-child td { border-bottom: 0; }
  /* First real column (after the select box) is the row's title. */
  .list-table tbody td:nth-child(1 of :not(.col-select)):not([colspan]) { font-size: 16px; font-weight: 600; }
  /* Sub-lines under the title (address, frequency, unit, vendor…) stay plain. */
  /* Direct children only, so a span inside the title link itself (e.g. the
     jobs list's last-word + repeat icon) stays bold. */
  .list-table tbody td:nth-child(1 of :not(.col-select)):not([colspan]) > :is(span, p, small, div):not(.tag) { font-weight: 400; }
  .list-table tbody td:nth-child(1 of :not(.col-select)):not([colspan]) .text-muted-field { font-size: 13px; font-weight: 400; }
  /* A title that's a link reads as the row's name, not an underlined link. */
  .list-table tbody td:nth-child(1 of :not(.col-select)):not([colspan]) > a:not(.btn) { color: inherit; text-decoration: none; }
  .list-table tbody td:nth-child(1 of :not(.col-select)):not([colspan]) > a:not(.btn):hover { text-decoration: underline; }
  /* Any other name column (e.g. a job's client) is plain. */
  .list-table tbody td.row-name:not(:nth-child(1 of :not(.col-select))) { font-weight: 400; }
  /* Fade the icons, not the cell — fading the cell also hid its background,
     so a selected row's highlight stopped short of the right edge. */
  .list-table .row-actions { white-space: nowrap; }
  .list-table .row-actions > * { opacity: 0; transition: opacity .12s ease; }
  .list-table tr:hover .row-actions > *, .list-table tr:focus-within .row-actions > * { opacity: 1; }
}
@media (min-width: 1180px) and (hover: none) { .list-table .row-actions > * { opacity: 1; } }

/* A wider cutover than field.css's 860px sidebar swap — a 5-6 column table
   (name, status, phone, email, address, actions) runs out of room well
   before the sidebar does, so each row becomes its own bordered card here
   instead, with th/td turned into labeled stacked rows via the
   td[data-label] attribute the table views set. */
@media (max-width: 1179.98px) {
  /* Once a table's rows are cards, the list's own white card goes away —
     a card of cards looked boxed-in on tablets (it used to drop only below
     640px, while rows became cards below 1180px). */
  .list-card:has(> .table), .table.list-card { border: 0; padding: 0; background: none; }
  .table, .table thead, .table tbody, .table th, .table tr, .table td { display: block; }
  .table thead { display: none; }
  .table tbody tr:hover { background: var(--color-card); }
  .table tr {
    border: 1px solid var(--color-divider); background: var(--color-card);
    padding: var(--space-3);
    margin-bottom: var(--space-3);
  }
  .table td { border: none; padding: var(--space-1) 0; }
  /* Timesheets: the seven per-day columns only make sense on the wide
     desktop grid — a phone card shows just Name / Total / Cost. */
  .timesheet-table .ts-day { display: none; }
  .table td[data-label]::before {
    content: attr(data-label);
    display: block; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
    color: color-mix(in srgb, var(--color-text) 60%, transparent);
    margin-bottom: 2px;
  }
  .table td:last-child {
    display: flex; align-items: center; gap: var(--space-1);
    padding-top: var(--space-2); margin-top: var(--space-1);
    border-top: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
  }
  /* An empty-state row's single <td colspan> is both the first and last
     child at once, so it otherwise inherits the divider/spacing above
     meant to set an actions cell apart from the row's other fields.
     Deliberately a same-specificity sibling rule (not :not([colspan])
     tacked onto the selector above) — stacking it on would out-rank
     .table-collapsible's own td:not([data-summary]) hide rule below and
     make every row's real actions cell always visible, expanded or not. */
  .table td[colspan] {
    display: block; border-top: none; margin-top: 0; padding-top: 0;
  }

  /* Opt-in: rows start collapsed to just their data-summary cell(s) — a
     tap (toggled via .is-expanded, see field.layout's script) reveals the
     rest. Lets a long list be scanned at a glance on a phone instead of
     scrolling past every field of every row. */
  /* Flex, not block: the collapsed jobs-list summary is three short rows
     (name+status, service, when+crew) that used to each claim a full
     block-level line apiece. Hidden (non-summary) cells take no space in
     a flex row, so the visible ones naturally flow together without any
     JS reordering; the flex-basis:100% cells below each force a fresh
     line, and everything after one wraps onto the next. */
  .table-collapsible tr { position: relative; cursor: pointer; padding-right: 32px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--space-2); }
  .table-collapsible td:not([data-summary]) { display: none; }
  /* order keeps the summary rows together as one visual block even once
     expanded — without it, the newly-revealed cells (type, detail,
     actions) stay at their DOM position (name, type, service, ...) and
     wedge themselves between the summary rows instead of landing below
     all of them. */
  .table-collapsible tr.is-expanded td:not([data-summary]) { display: block; flex-basis: 100%; order: 6; }
  .table-collapsible td.row-name {
    flex: 1 1 auto; min-width: 0; order: 0;
    font-size: 15px;
  }
  /* Bulk-select rows: pin the checkbox to the card's top-left corner and
     indent the whole card, so the title lines up with the body text below
     instead of being shoved in by the box sitting in the flex flow. */
  .table-collapsible tr:has(> td.col-select) { padding-left: calc(var(--space-3) + 28px); }
  .table-collapsible td.col-select { position: absolute; left: var(--space-3); top: var(--space-3); padding: 0; }
  .table-collapsible td.row-status { flex: 0 0 auto; padding: 0; order: 1; }
  .table-collapsible td.job-row-service { flex: 1 1 100%; order: 2; }
  .table-collapsible td.job-row-when { flex: 1 1 auto; order: 3; }
  .table-collapsible td.job-row-crew { flex: 0 0 auto; order: 4; }
  .table-collapsible td.job-row-crew-unassigned { color: var(--color-accent); }
  .table-collapsible tr::after {
    content: ''; position: absolute; top: var(--space-3); right: var(--space-3);
    width: 7px; height: 7px;
    border-right: 1.5px solid var(--color-text); border-bottom: 1.5px solid var(--color-text);
    transform: rotate(45deg); transition: transform .15s ease;
  }
  .table-collapsible tr.is-expanded::after { transform: rotate(225deg); }

  /* An empty-state row (single colspan message, no other cells to reveal)
     isn't actually expandable — it just needs to stay visible, not gain a
     chevron implying there's more underneath. */
  .table-collapsible tr:has(td[colspan]) { cursor: default; padding-right: var(--space-3); }
  .table-collapsible tr:has(td[colspan])::after { display: none; }
}

/* — dialog — */
.dialog-backdrop {
  /* display:none by default, NOT display:flex here — a class rule beats
     the browser's built-in [hidden]{display:none} at equal specificity,
     so setting a display directly on this class would make the [hidden]
     attribute (used to open/close it) do nothing at all. */
  position: fixed; inset: 0; display: none;
  /* Without this, any other positioned-but-z-index:auto element later in
     the DOM (e.g. .field-search, which is position:relative for its icon)
     paints above this backdrop despite it being position:fixed — auto
     z-index among positioned elements stacks in DOM order, not by
     position type, so a fixed overlay can genuinely lose to a plain
     relative element that just happens to come after it in the markup. */
  z-index: 100;
  /* Anchored near the top, not dead-center — true vertical centering
     means the exact landing spot depends on both viewport height and how
     much page content sits above it, and on a short browser window (or a
     short page, like a 3-row table) that math can genuinely place the
     dialog right on top of existing content instead of reading as a
     clean overlay. A fixed offset from the top is predictable regardless
     of either. overflow-y so a tall dialog on a short viewport can still
     scroll instead of getting clipped off-screen. */
  justify-content: center; align-items: flex-start; overflow-y: auto;
  padding: var(--space-4);
  /* Darker than a typical 50% dim — the page behind needs to visibly
     recede, not just tint, or the dialog reads as one more thing on the
     page rather than a modal in front of it. */
  background: color-mix(in srgb, var(--color-neutral-900) 72%, transparent);
}
.dialog-backdrop:not([hidden]) { display: flex; }
/* Any open dialog locks the page behind it — otherwise the backdrop's own
   overflow-y:auto scrolls the modal while the mouse wheel/touch drag also
   scrolls the page underneath it at the same time. :has() means every
   dialog gets this for free, including ones added later, with no JS. Same
   reasoning for the mobile slide-out menu — a different class, but the
   same open-backdrop-over-the-page shape. */
body:has(.dialog-backdrop:not([hidden])),
body:has(.mobile-menu-backdrop:not([hidden])) { overflow: hidden; }
.dialog {
  width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  margin-top: max(var(--space-8), 8vh);
  padding: var(--space-4); border-radius: var(--radius-lg);
  /* --color-surface is only a shade off from the page's own --color-bg —
     against the dimmed backdrop that barely reads as a distinct card. A
     near-white background plus a real border is what actually makes it
     pop as a floating element instead of blending into the dimmed page
     behind it. */
  background: #fdfcfb; border: 1px solid var(--color-divider); box-shadow: var(--shadow-lg);
}
.dialog-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 20px;
}
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }

/* — wireframe design language: every component reads as a flat, square,
     hairline-bordered object — */
.card, .btn, .input, .color-input, .tag, .seg, .seg-opt, .dialog { border-radius: 0; }
/* .dialog deliberately excluded here — unlike a .card sitting in normal
   page flow (fine to read as transparent + just a hairline border), a
   modal needs an opaque surface to actually separate from the dimmed
   backdrop behind it; this rule was overriding .dialog's own background
   further up (equal specificity, later in the cascade wins). */
.card { background: transparent; border: 1px solid var(--color-divider); }
.btn { border: 1px solid var(--color-divider); }
.btn-primary { border-color: var(--color-accent); }
.btn-ghost { border-color: transparent; }

/* — numbered form sections (job create/edit) — */
/* Section gap is deliberately well past the --space-4 used between fields
   within a section — otherwise section boundaries read the same as field
   boundaries and the whole form blurs into one dense list. */
.form-section { border-top: 1px solid var(--color-divider); padding-top: var(--space-8); margin-top: var(--space-8); }
/* Explicit class rather than :first-child — the job form's first section
   is preceded by a hidden confirm_conflicts input (and conditionally an
   error/conflict block), so it's never actually the DOM's first child
   even though it's the first *visible* one. */
.form-section-first { border-top: none; padding-top: 0; margin-top: 0; }
.form-section-title {
  display: flex; align-items: baseline; gap: 8px;
  font-family: var(--font-heading); font-weight: 600;
  /* Section headings: one size app-wide (16px since 2026-10-29). */
  font-size: 16px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--color-text); margin-bottom: var(--space-4);
}
.form-section-title .num { color: var(--color-accent-900); }
.form-section-hint { font-size: 12px; color: color-mix(in srgb, var(--color-text) 60%, transparent); margin: -6px 0 var(--space-4); }

/* A sub-section label inside a longer form section (e.g. "Tools &
   equipment" on the job form) — no surrounding box (2026-10-08 UI audit,
   pattern K: the picker fields below it already read as their own group). */
.field-cluster-title { margin-bottom: var(--space-3); }

/* — task rows (add/remove) — */
.task-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.task-row .task-num { flex: none; width: 20px; font-size: 12px; color: color-mix(in srgb, var(--color-text) 50%, transparent); text-align: right; }
.task-row .input { flex: 1; }
.task-row .task-remove { flex: none; }
.tasks-count { font-size: 12px; color: color-mix(in srgb, var(--color-text) 60%, transparent); margin: 4px 0 var(--space-2); }

/* — materials list (added via the picker above, not typed in place —
   name is a fixed label once a row exists; only quantity/unit are live) — */
.material-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 0; border-bottom: 1px solid var(--color-divider);
}
.material-row-info { min-width: 0; }
.material-row-name { font-size: 16px; font-weight: 700; color: var(--color-text); margin: 0; }
.material-row-meta {
  display: flex; align-items: center; gap: 7px; margin: 4px 0 0;
  font-size: 13px; color: color-mix(in srgb, var(--color-text) 60%, transparent);
}
/* Filled square = coming out of tracked shop stock; outline square = a
   one-off pickup with nothing to track — same distinction as the picker's
   own checklist, just carried onto the row once it's added. */
.material-row-dot { width: 8px; height: 8px; flex: none; }
.material-row-dot.is-tracked { background: var(--color-accent-900); }
.material-row-dot.is-custom { border: 1px solid color-mix(in srgb, var(--color-text) 40%, transparent); }
.material-row-right { display: flex; align-items: center; gap: 10px; flex: none; }
.material-qty-group { display: flex; align-items: stretch; border: 1px solid var(--color-divider); background: var(--color-card); }
.material-qty-step {
  width: 30px; border: none; background: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; color: var(--color-text);
}
.material-qty-step:hover { background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
.material-qty-input {
  width: 44px; border: none; border-left: 1px solid var(--color-divider); border-right: 1px solid var(--color-divider);
  text-align: center; font: inherit; font-size: 14px; color: var(--color-text); background: none;
}
.material-qty-input::-webkit-outer-spin-button, .material-qty-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.material-qty-input { -moz-appearance: textfield; }
.material-unit { border: none; text-align: center; font: inherit; font-size: 14px; color: var(--color-text); background: none; }
.material-unit-label { display: flex; align-items: center; justify-content: center; width: 44px; color: color-mix(in srgb, var(--color-text) 60%, transparent); }
.material-unit-input { width: 56px; }
.material-unit-input::placeholder { color: color-mix(in srgb, var(--color-text) 40%, transparent); }

/* — media dropzone (site walkthrough) — */
.media-dropzone {
  border: 1px dashed var(--color-divider); padding: var(--space-4); text-align: center;
  cursor: pointer; transition: border-color .15s, background .15s;
}
.media-dropzone.is-dragover { border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 6%, transparent); }
.media-dropzone p { margin: 0; font-size: 13px; }
.media-dropzone .hint { font-size: 12px; color: color-mix(in srgb, var(--color-text) 55%, transparent); margin-top: 4px; }
.media-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: var(--space-3); }
.media-thumb { position: relative; width: 84px; height: 84px; border: 1px solid var(--color-divider); overflow: hidden; background: #f6f1e9; }
.media-thumb img, .media-thumb video { width: 100%; height: 100%; object-fit: cover; }
.media-thumb .media-remove {
  position: absolute; top: 2px; right: 2px; width: 20px; height: 20px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(20,20,22,.65); color: #fff; border: none; cursor: pointer; font-size: 13px; line-height: 1;
}
.media-thumb .media-kind { position: absolute; bottom: 2px; left: 2px; font-size: 12px; padding: 1px 4px; background: rgba(20,20,22,.65); color: #fff; }
.media-play-overlay {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.85); color: #122e50;
}

/* — photo requirement rows ("photos to bring back") — */
.photo-req-row { border: 1px solid var(--color-divider); padding: var(--space-3); margin-bottom: 8px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* Job category template: plain rows, like its checklist above — not boxes inside the section. */
#req-rows .photo-req-row { border: 0; border-bottom: 1px solid var(--color-divider); padding: 8px 0; margin-bottom: 0; }
.photo-req-row .input { flex: 1; min-width: 140px; }
.photo-req-row .seg { flex: none; }

/* — live status badge next to a job form's title — */
.status-badge-live { font-size: 12px; }

/* — tool set builder (flowing divided rows, not boxed like .photo-req-row —
     these represent one continuous list, not independent cards) — */
.toolset-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 0; border-bottom: 1px solid var(--color-divider);
}
.toolset-row:first-child { padding-top: 0; }
.toolset-row-name { font-size: 16px; }
.toolset-row-inventory {
  font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 55%, transparent); margin-top: 2px;
}
.toolset-stepper { display: flex; align-items: center; border: 1px solid var(--color-divider); flex: none; }
.toolset-stepper button {
  width: 34px; height: 34px; border: none; background: transparent; cursor: pointer;
  font-size: 16px; color: var(--color-text); font-family: var(--font-heading);
}
.toolset-stepper button:first-child { border-right: 1px solid var(--color-divider); }
.toolset-stepper button:last-child { border-left: 1px solid var(--color-divider); }
.toolset-stepper .qty-value { width: 38px; text-align: center; font-weight: 600; font-family: var(--font-heading); }
.toolset-row-remove {
  background: none; border: none; cursor: pointer; flex: none; width: 26px;
  color: color-mix(in srgb, var(--color-text) 40%, transparent); font-size: 18px; line-height: 1; padding: 4px;
}
.toolset-row-remove:hover { color: var(--color-warn); }

/* Square checkbox (vs. .radio's circular dot) — matches the crew app's
   own .scope-row task checkboxes, reused here since a checked/unchecked
   category list reads the same way. The checkmark itself is CSS-only
   (background-image, shown via :has()) rather than a JS-toggled icon, so
   it needs no script to reflect a freshly-clicked checkbox. */
/* label.check-box-row (not just .check-box-row) so this beats .field >
   label's display:block — same specificity, and this rule comes later. */
label.check-box-row { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; padding: 14px 0; border-bottom: 1px solid var(--color-divider); }
.check-box-row input { position: absolute; opacity: 0; width: 0; height: 0; }
.check-box-row .box {
  display: block; width: 24px; height: 24px; flex: none; margin-top: 1px;
  border: 1px solid var(--color-divider); background-color: var(--color-card);
  background-repeat: no-repeat; background-position: center; background-size: 14px;
}
.check-box-row:has(input:checked) .box {
  background-color: var(--color-accent); border-color: var(--color-accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='4 12.5 9 17.5 20 6.5'/%3E%3C/svg%3E");
}
.check-box-row-name { font-size: 16px; margin-bottom: 4px; }
.check-box-row-label { align-self: center; }
/* Bulk-select checkbox in a list table (th/td.col-select) — just the box,
   none of the form-row padding/divider/top-align that label.check-box-row
   carries for settings forms. Was leaking a stray underline and pushing
   the box off-centre, most visibly in the mobile card layout. */
.col-select { vertical-align: middle; }
.col-select label.check-box-row { display: inline-flex; align-items: center; padding: 0; border-bottom: none; }
.col-select .check-box-row .box { margin-top: 0; }
/* Compact list-row variant (<x-check-box compact>) — tighter, no divider,
   smaller box. Top-aligned with its label: for a single-line label this
   renders identically to centering (the row's only as tall as that one
   line), but for a title+description pair (Settings toggles) the box now
   lines up with the title instead of floating at the vertical midpoint
   of both lines. */
label.check-box-row.is-compact { padding: 7px 0; border-bottom: none; align-items: flex-start; gap: 10px; }
.check-box-row.is-compact .box { width: 20px; height: 20px; margin-top: 1px; background-size: 12px; }
/* Compact bordered-chip variant of the same checkbox mechanics above —
   combine with check-box-row (class="check-box-row toggle-chip") rather
   than duplicating the input/.box/checkmark rules; this just overrides
   the layout (inline, its own border, no bottom divider) and adds a
   light accent fill once checked, distinct from the full-width list-row
   look check-box-row normally has (e.g. tool-set default job types). */
label.check-box-row.toggle-chip {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 9px 14px; border: 1px solid var(--color-divider);
  width: auto;
}
.check-box-row.toggle-chip:has(input:checked) {
  background: var(--color-accent-100); border-color: var(--color-accent);
}
.default-badge {
  display: inline-block; font-size: 12px; padding: 3px 8px;
  border: 1px solid var(--color-divider); border-radius: 4px;
  font-family: var(--font-heading); font-weight: 600; text-transform: uppercase; letter-spacing: .06em;
  color: var(--color-text);
}

/* — inventory shortfall dialog rows (tool-sets save-with-more-than-you-own warning) — */
.inventory-shortfall-row {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--color-divider); font-size: 15px;
}
.inventory-shortfall-meta {
  font-size: 12px; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap;
  font-family: var(--font-heading); font-weight: 600; color: var(--color-accent);
}

/* — small inline loading spinner (e.g. "Scanning receipt…") — toggle via
   style.display directly from JS rather than the hidden attribute: this
   class sets its own display, which (same specificity, later in the
   cascade) would beat [hidden]'s display:none and leave it visible. */
.spinner {
  display: inline-block; width: 12px; height: 12px; margin-right: 6px; vertical-align: -2px;
  border: 2px solid color-mix(in srgb, var(--color-accent) 25%, transparent);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  animation: spinner-rotate 0.7s linear infinite;
}
@keyframes spinner-rotate { to { transform: rotate(360deg); } }

/* — tool picker (custom type-ahead combobox) — same look as
   .status-dropdown-menu, just anchored to a text input instead of a
   trigger button, and full-width instead of a fixed min-width. */
.tool-picker { position: relative; }
.tool-picker-menu {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 20;
  max-height: 240px; overflow-y: auto;
  background: #fdfcfb; border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.tool-picker-item {
  display: flex; width: 100%; justify-content: space-between; align-items: baseline; gap: 12px;
  background: none; border: none; border-bottom: 1px solid var(--color-divider); cursor: pointer;
  padding: 9px 12px; font: inherit; font-size: 14px; color: var(--color-text); text-align: left;
}
.tool-picker-item:last-child { border-bottom: none; }
.tool-picker-item:hover, .tool-picker-item.is-active { background: color-mix(in srgb, var(--color-text) 6%, transparent); }
.tool-picker-item-units { font-size: 12px; flex: none; color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.tool-picker-empty { padding: 9px 12px; font-size: 13px; color: color-mix(in srgb, var(--color-text) 45%, transparent); }

/* .tool-picker-flush — job form's Category and "Additional tools" pickers.
   Unlike the plain .tool-picker above, which floats as a detached popover
   with a gap, these butt flush against the input below them. While the
   menu is open the input swaps its 2px focus outline (which sits *outside*
   the border box, so a menu could never line up with it) for a 1px accent
   border, and the menu carries the same 1px accent border on its other
   three sides — same width as the input, no gap, one continuous frame. */
.tool-picker-flush:has(.tool-picker-menu:not([hidden])) .input {
  outline: none; border-color: var(--color-accent);
}
.tool-picker-flush .tool-picker-menu {
  top: 100%; left: 0; right: 0;
  border: 1px solid var(--color-accent); border-top: none; border-radius: 0;
}


/* ===== Settings kit (2026-10-29 restyle) =====================================
   Reusable form patterns introduced on Settings: switch rows that hide their
   dependent fields while off, list cards with hover row actions, an inline
   confirm step, a sticky save bar, and a square status marker. Labels and
   buttons keep the app-wide condensed caps (style-system rule). */

.settings-col { max-width: 920px; }
.settings-col > .form-section:first-child,
.settings-col > form:first-of-type > .form-section:first-of-type,
.settings-col .tab-panel > .form-section:first-child { border-top: none; padding-top: 0; margin-top: 0; }
.settings-col .form-section { padding-top: var(--space-6); margin-top: var(--space-6); }
.settings-col .form-section-hint { margin-top: -10px; font-size: 13px; color: var(--color-accent); }

/* Big group heading inside one long tab (General → Invoicing / Field app). */
.settings-group-title {
  font-family: var(--font-heading); font-weight: 700; font-size: 26px; line-height: 1.1;
  color: var(--color-text); margin: var(--space-8) 0 0; padding-top: var(--space-6);
  border-top: 1px solid var(--color-divider);
}
.settings-group-title + .form-section { border-top: none; padding-top: var(--space-6); margin-top: 0; }
/* A tab that opens with a group heading (Notifications): the tab bar's own
   underline already separates it, so no second rule right below it. */
.settings-group-title-first { border-top: none; padding-top: 0; margin-top: 0; }

/* Equal-width field rows: three short fields to a row, stacking on phones. */
.field-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); }
.field-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.field-grid > .field { margin-bottom: 0; min-width: 0; }
@media (max-width: 720px) { .field-grid, .field-grid-2 { grid-template-columns: 1fr; } }
.field-help { font-size: 13px; color: var(--color-accent-2); margin: 5px 0 0; }
.field-unit { display: flex; align-items: center; gap: 8px; }
.field-unit > span { font-size: 14px; color: var(--color-accent); white-space: nowrap; }
.field-money { position: relative; }
.field-money > span { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--color-accent-2); font-size: 14px; pointer-events: none; }
.field-money > .input { padding-left: 22px; }

/* — card that holds rows (switch rows, list rows) — */
.set-card { background: var(--color-card); border: 1px solid var(--color-divider); }
.set-card > * + * { border-top: 1px solid rgba(18, 46, 80, .1); }
.set-card > [hidden] + *, .set-card > .switch-body + * { border-top: 1px solid rgba(18, 46, 80, .1); }

/* — switch row — */
.switch-row {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 14px 16px; cursor: pointer; margin: 0;
}
.switch-row:hover { background: rgba(18, 46, 80, .03); }
.switch-row > input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.switch-row-text { flex: 1; min-width: 0; }
.switch-row-label { display: block; font-size: 16px; font-weight: 600; color: var(--color-text); }
.switch-row-hint { display: block; font-size: 14px; color: var(--color-accent-2); margin-top: 1px; }
.switch-row .hint-off { display: none; }
.switch-row:has(> input[type=checkbox]:not(:checked)) .hint-on { display: none; }
.switch-row:has(> input[type=checkbox]:not(:checked)) .hint-off { display: block; }
.switch {
  flex: none; display: flex; justify-content: flex-start; width: 40px; height: 22px; padding: 2px;
  box-sizing: border-box; border: 1.5px solid rgba(18, 46, 80, .35); background: transparent;
}
.switch::after { content: ''; display: block; width: 15px; height: 15px; background: rgba(18, 46, 80, .45); }
.switch-row > input[type=checkbox]:checked ~ .switch { justify-content: flex-end; background: #122e50; border-color: #122e50; }
.switch-row > input[type=checkbox]:checked ~ .switch::after { background: #f6f1e9; }
.switch-row > input[type=checkbox]:focus-visible ~ .switch { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* Fields that only matter while the switch is on sit under it, hidden when off. */
/* Top padding so the row's hover tint above stops short of these fields
   instead of butting right up against them. */
.switch-body { padding: 12px 16px 16px; }
.set-card > .switch-body { border-top: none; }
.switch-row:has(> input[type=checkbox]:not(:checked)) + .switch-body { display: none; }
.switch-sentence { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 15px; color: var(--color-accent); }
.switch-sentence .input { width: 96px; }
.switch-sentence select.input { width: auto; }
/* A compact on/off chip next to an input (job type add row). */
.switch-chip { width: auto; padding: 0 12px; height: 36px; border: 1px solid var(--color-divider); background: var(--color-card); font-size: 15px; gap: 10px; }

/* — list rows with actions on hover — */
.set-row { display: flex; align-items: center; gap: 16px; padding: 13px 16px; }
.set-row-main { flex: 1; min-width: 0; color: inherit; text-decoration: none; }
a.set-row-main:hover .set-row-title { text-decoration: underline; text-underline-offset: 2px; }
.set-row-title { display: block; font-size: 16px; font-weight: 600; color: var(--color-text); }
.set-row-meta { display: block; font-size: 14px; color: var(--color-accent-2); margin-top: 1px; }
.set-row-actions { flex: none; display: flex; gap: 6px; justify-content: flex-end; }
@media (hover: hover) {
  .set-row .set-row-actions { opacity: 0; transition: opacity .12s; }
  .set-row:hover .set-row-actions, .set-row:focus-within .set-row-actions { opacity: 1; }
}
.set-row:hover { background: rgba(18, 46, 80, .03); }
.btn-sm { min-height: 34px; padding: 0 12px; font-size: 13px; }
.set-empty { padding: 16px; border: 1px dashed rgba(18, 46, 80, .25); font-size: 15px; color: var(--color-accent); }
.set-add { width: 100%; text-align: left; justify-content: flex-start; padding: 12px 16px; border-radius: 0; }

/* — confirm step: the row (or link) swaps for one sentence + Keep it / action — */
.confirm-panel { display: none; align-items: center; gap: 12px; flex: 1; flex-wrap: wrap; }
.confirm-panel > p { flex: 1; min-width: 200px; margin: 0; font-size: 15px; color: var(--color-text); }
.is-confirming > .confirm-hide, .is-confirming .confirm-hide { display: none !important; }
.is-confirming > .confirm-panel, .is-confirming .confirm-panel { display: flex; }
.link-action {
  border: 0; background: transparent; padding: 0; cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 500; color: var(--color-accent);
  text-decoration: underline; text-underline-offset: 2px;
}
.link-action:hover { color: var(--color-text); }

/* — status: a small square + word — */
.status-sq { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; color: var(--color-accent-2); white-space: nowrap; }
.status-sq::before { content: ''; flex: none; width: 9px; height: 9px; box-sizing: border-box; border: 1.5px solid currentColor; }
.status-sq.is-on { color: var(--color-text); }
.status-sq.is-on::before { background: #122e50; border-color: #122e50; }
.status-sq.is-warn { color: #7a4f00; }
.status-sq.is-warn::before { background: #ffa405; border-color: #ffa405; }

/* — sticky save bar: only while the form has unsaved changes — */
.save-bar {
  position: sticky; bottom: 0; z-index: 30; margin-top: var(--space-6);
  display: flex; align-items: center; gap: 14px; padding: 12px 16px; background: #122e50;
}
.save-bar[hidden] { display: none; }
.save-bar-text { flex: 1; font-size: 15px; color: #f6f1e9; }
.save-bar .btn-ghost { color: #f6f1e9; }
.save-bar .btn-ghost:hover { background: rgba(246, 241, 233, .12); }
.btn-amber { background: #ffa405; color: #122e50; }
.btn-amber:hover { background: var(--color-highlight-600); }
.saved-note { margin-top: var(--space-6); font-size: 13px; color: var(--color-accent-2); }
@media (max-width: 1099px) {
  /* Clear the phone tab bar. */
  .save-bar { bottom: calc(var(--tab-bar-height, 89px) + env(safe-area-inset-bottom)); }
}

/* Integration mark tile + numbered steps card. */
.mark-tile { flex: none; width: 40px; height: 40px; border: 1px solid var(--color-divider); display: flex; align-items: center; justify-content: center; overflow: hidden; font-family: var(--font-heading); font-weight: 700; font-size: 15px; color: var(--color-text); background: #fff; }
.mark-tile img { width: 100%; height: 100%; object-fit: cover; }
.steps-card { background: var(--color-card); border: 1px solid var(--color-divider); padding: 14px 16px; }
.steps-card ol { margin: 0; padding-left: 22px; font-size: 15px; color: var(--color-accent); }
.steps-card li { padding: 3px 0; }
.steps-card li::marker { color: var(--color-accent-2); }
.fact-row { display: flex; gap: 16px; padding: 11px 16px; font-size: 15px; }
.fact-row > span:first-child { flex: 1; color: var(--color-accent); }
.fact-row > span:last-child { font-weight: 600; color: var(--color-text); }

/* Inline editable rows (checklist, required photos) — borderless inputs in a card. */
.inline-row { display: flex; align-items: center; gap: 12px; padding: 4px 16px; }
.inline-row .row-num { flex: none; width: 22px; font-size: 14px; color: var(--color-accent-2); }
.inline-row .input-bare {
  flex: 1; min-width: 0; height: 38px; border: 0; background: transparent; padding: 0;
  font: inherit; font-size: 16px; color: var(--color-text);
}
.inline-row .input-bare::placeholder { color: var(--color-accent-2); }
.inline-row .input-bare:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.inline-row select.input { flex: 0 0 180px; width: 180px; }
@media (hover: hover) {
  .inline-row .row-remove { opacity: 0; }
  .inline-row:hover .row-remove, .inline-row:focus-within .row-remove { opacity: 1; }
}
@media (max-width: 640px) { .inline-row select.input { flex-basis: 130px; width: 130px; } }

/* Price book / tax rows: read-only text, edit swaps in inputs. */
.pb-row { display: grid; grid-template-columns: minmax(0, 1fr) 140px 130px 170px; gap: 14px; align-items: center; padding: 8px 16px; min-height: 52px; }
.pb-head { font-size: 12px; font-family: var(--font-heading); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--color-accent-2); min-height: 0; padding-top: 10px; padding-bottom: 10px; }
.pb-row .num { text-align: right; font-variant-numeric: tabular-nums; }
.pb-row .pb-edit { display: none; }
.pb-row.is-editing .pb-view { display: none; }
.pb-row.is-editing .pb-edit { display: block; }
.pb-row.is-editing .pb-edit.set-row-actions { display: flex; opacity: 1; }
.pb-row .confirm-panel { grid-column: 1 / -1; }
.pb-row.is-confirming > :not(.confirm-panel) { display: none !important; }
.pb-row.tax { grid-template-columns: minmax(0, 1fr) 130px 170px; }
@media (hover: hover) { .pb-row:hover .set-row-actions { opacity: 1; } .pb-row .pb-view.set-row-actions { opacity: 0; } .pb-row:hover .pb-view.set-row-actions, .pb-row:focus-within .pb-view.set-row-actions { opacity: 1; } }
.pb-row:hover { background: rgba(18, 46, 80, .03); }
@media (max-width: 640px) {
  .pb-head { display: none; }
  .pb-row, .pb-row.tax { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px; }
  .pb-row .pb-unit.pb-view { display: none; }
  .pb-row .num.pb-view { text-align: right; }
  .pb-row.is-editing .pb-edit:not(.set-row-actions) { grid-column: 1 / -1; }
  .pb-row .set-row-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

/* Settings tabs: wrap-free on desktop at the column width; scroll (no bar) on phone. */
@media (min-width: 1100px) { #settings-tabs { overflow-x: visible; } }
/* Add-an-item row above a settings list. */
.add-grid { display: grid; grid-template-columns: minmax(0, 1fr) 160px 160px auto; gap: 10px; align-items: end; }
.add-grid.tax { grid-template-columns: minmax(0, 1fr) 160px auto; }
.add-grid > .field { margin: 0; min-width: 0; }
.add-grid > .btn { min-height: 36px; }
@media (max-width: 640px) {
  .add-grid, .add-grid.tax { grid-template-columns: 1fr 1fr; }
  .add-grid > .field:first-child, .add-grid > .btn { grid-column: 1 / -1; }
  .add-grid.tax > .field:nth-child(2) { grid-column: 1 / -1; }
}
.pb-per { display: none; color: var(--color-accent-2); font-weight: 400; }
@media (max-width: 640px) { .pb-per { display: inline; } }
.inline-rows > * + * { border-top: 1px solid rgba(18, 46, 80, .1); }
.inline-rows:empty + .set-add { border-top: 0; }
.settings-group-title + .form-section { padding-top: var(--space-4); }
/* In-app confirm / alert / prompt (public/js/dialog.js). */
.app-dialog-body { margin: 0; font-size: 15px; line-height: 1.45; color: var(--color-text); white-space: pre-line; }
.app-dialog .dialog-actions .btn { min-height: 40px; }
/* Restyled form pages (Settings, client edit, team member, job type edit):
   roomier fields to match the 2026-10-29 mockups. */
.settings-col .input { min-height: 42px; font-size: 15px; padding-left: 12px; padding-right: 12px; }
.settings-col select.input { padding-right: 34px; }
.settings-col textarea.input { padding-top: 10px; padding-bottom: 10px; }
.settings-col .field > label:not(.check-box-row):not(.seg-opt):not(.radio) { margin-bottom: 8px; }
.settings-col .field-money > .input { padding-left: 24px; }
.settings-col .switch-sentence .input { min-height: 38px; }
.settings-col { --address-field-height: 42px; --address-field-font: 15px; }
.settings-col textarea.input { display: block; }
/* Search fields keep room for their magnifier on the roomier form pages. */
.settings-col .field-search .input { padding-left: 34px; }
/* Outlined buttons are filled white, like the back buttons (2026-10-29). */
.btn-secondary { background: var(--color-card); }
.btn-secondary:hover { background: color-mix(in srgb, #122e50 6%, var(--color-card)); }
.btn-secondary:active { background: color-mix(in srgb, #122e50 12%, var(--color-card)); }

/* Airy form layout (client edit, team member edit, 2026-10-29 mockups):
   sections separated by whitespace, not rule lines; one rule above the
   trailing related-records / integration sections (.form-airy-after). */
.form-airy .form-section { border-top: 0; padding-top: 0; margin-top: 44px; }
.form-airy .form-section:first-of-type { margin-top: 0; }
.form-airy .form-section-title { margin-bottom: 20px; }
.form-airy .form-section-hint { margin: -12px 0 16px; }
.form-airy .field { margin: 0 0 22px; }
.form-airy .field-grid { gap: 14px; margin-bottom: 22px; }
.form-airy .field-grid > .field { margin: 0; }
.form-airy .field-help { margin-top: 8px; }
.form-airy .form-section > .field:last-child, .form-airy .form-section > .field-grid:last-child { margin-bottom: 0; }
.form-airy .switch-row { padding: 12px 16px; }
.form-airy .switch-row-label { font-size: 15px; }
.form-airy .switch-row-hint { font-size: 13px; }
.settings-col > .form-section.form-airy-after { margin-top: 36px; padding-top: 32px; border-top: 1px solid var(--color-divider); }
.form-airy-after .form-section-title { margin-bottom: 10px; }
.form-airy-after .form-section-title { margin-bottom: 14px; }
.form-airy-after .form-section-hint { margin: 0 0 16px; }
/* Delete-this-record button at the foot of an edit page, below a rule. */
.page-danger { margin-top: 36px; padding-top: 24px; border-top: 1px solid var(--color-divider); }
.page-danger .btn svg { margin-right: 4px; }
.form-airy .field-grid + .field-help { margin-top: -12px; }

/* — Toasts (site-wide) — <x-toast-stack> + public/js/toast.js.
   Two looks: a navy success card that fades on its own, and a cream
   warning/error card with an orange border that waits to be dismissed.
   Both carry a title, an optional detail line and an optional action. */
.toast-stack {
  position: fixed; z-index: 1200; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--tab-bar-height, 0px) + env(safe-area-inset-bottom) + 16px);
  width: calc(100% - 32px); max-width: 520px;
  display: flex; flex-direction: column; gap: 12px;
  pointer-events: none;
}
.toast-stack:empty { display: none; }
.toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: 14px;
  padding: 18px 16px 18px 22px;
  box-shadow: 0 12px 32px rgba(18, 46, 80, .22), 0 2px 6px rgba(18, 46, 80, .12);
  animation: toast-in .22s ease-out both;
}
.toast.is-leaving { animation: toast-out .2s ease-in both; }
.toast-icon { flex: none; display: flex; padding-top: 1px; }
.toast-body { flex: 1 1 auto; min-width: 0; }
.toast-title { margin: 0; font-weight: 700; font-size: 17px; line-height: 1.35; }
.toast-detail { margin: 4px 0 0; font-size: 15px; line-height: 1.4; }
.toast-action-form { margin: 0; flex: none; }
.toast-action {
  flex: none; align-self: center; cursor: pointer; font: inherit; font-weight: 700; font-size: 16px;
  text-decoration: none; white-space: nowrap;
}
.toast-close {
  flex: none; align-self: flex-start; display: flex; padding: 2px; margin: -2px -4px 0 0;
  background: none; border: 0; cursor: pointer; opacity: .55;
}
.toast-close:hover { opacity: 1; }

.toast-success { background: var(--color-accent-900, #122e50); color: #f6f1e9; }
.toast-success .toast-icon { color: var(--color-highlight, #ffa405); }
.toast-success .toast-detail { color: #c5d0dc; }
.toast-success .toast-action { background: none; border: 0; padding: 4px 6px; color: var(--color-highlight, #ffa405); }
.toast-success .toast-action:hover { text-decoration: underline; }
.toast-success .toast-close { color: #f6f1e9; }

.toast-warning, .toast-error {
  background: var(--color-card, #fffdf9); color: var(--color-text, #122e50);
  border: 1.5px solid var(--color-highlight, #ffa405);
}
.toast-warning .toast-icon, .toast-error .toast-icon { color: #7a4f00; }
.toast-warning .toast-detail, .toast-error .toast-detail { color: var(--color-accent, #294867); }
.toast-warning .toast-action, .toast-error .toast-action {
  background: var(--color-card, #fffdf9); color: var(--color-text, #122e50);
  border: 1px solid var(--color-divider, #d9d4c7); padding: 8px 16px;
}
.toast-warning .toast-action:hover, .toast-error .toast-action:hover { border-color: currentColor; }
.toast-warning .toast-close, .toast-error .toast-close { color: var(--color-text, #122e50); }

@keyframes toast-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .toast, .toast.is-leaving { animation: none; } }
@media (max-width: 560px) {
  .toast { padding: 14px 12px 14px 16px; gap: 12px; }
  .toast-title { font-size: 16px; }
  .toast-detail { font-size: 14px; }
}
