/*
 * Vantage Facelift — canonical refined visual layer.
 *
 * Scoped to body.v-face and loaded by opted-in application pages.
 * This layer restyles the existing v-ui vocabulary (modals, buttons, tables,
 * forms) toward the approved 2026-07 concept: single primary action, 8-pt
 * spacing, header fades, chips for state, Title Case, calm density.
 * Consumes tenant brand vars (--gold, --bg, etc.) — no hardcoded tenant color.
 * This is the single opt-in layer for incremental page standardization.
 *
 * COMPONENT CONTRACT (2026-07-25): a component rule may style the element
 * carrying its class plus NAMED sub-parts (.v-modal-head/-body/-foot,
 * .v-drawer-header/-body/-footer, .v-field-label). It may NOT style generic
 * descendants (`.modal label`, `.modal div`) or page/module element IDs —
 * that is how standardizing a frame mangles page content. Existing generic
 * `.modal label` typography reaches are grandfathered ONLY until each page's
 * conversion stamps .v-field-label; remove them with the last consumer.
 */

body.v-ui.v-face {
  /*
   * Four palette inputs. During the POC these adapt the legacy brand compiler;
   * the facelift itself consumes only the semantic roles derived below.
   * Changing the brand input changes every accent without recoloring the
   * canvas, surfaces, text, or hairlines independently.
   */
  --vf-brand-source: var(--v-theme-accent, var(--gold));
  --vf-canvas-source: var(--v-theme-canvas, var(--bg));
  --vf-surface-source: var(--v-theme-surface, var(--bg-mid));
  --vf-text-source: var(--v-theme-text, var(--gold-light));

  --vf-accent: var(--vf-brand-source);
  --vf-canvas: var(--vf-canvas-source);
  --vf-surface: var(--vf-surface-source);
  --vf-surface-raised: var(--v-theme-surface-raised, var(--bg-light));
  --vf-text: var(--vf-text-source);
  --vf-text-muted: var(--v-theme-text-muted, color-mix(in srgb, var(--vf-text) 62%, var(--vf-surface)));
  --vf-text-subtle: var(--v-theme-text-subtle, color-mix(in srgb, var(--vf-text) 46%, var(--vf-surface)));
  --vf-line: var(--v-theme-border, color-mix(in srgb, var(--vf-text) 13%, var(--vf-surface)));
  --vf-line-soft: var(--v-theme-border-soft, color-mix(in srgb, var(--vf-text) 8%, var(--vf-surface)));
  --vf-panel: var(--v-theme-panel, color-mix(in srgb, var(--vf-text) 3%, var(--vf-surface)));
  --vf-chrome: var(--v-theme-chrome, color-mix(in srgb, var(--vf-surface) 88%, var(--vf-text)));
  --vf-chrome-active: var(--v-theme-chrome-active, color-mix(in srgb, var(--vf-chrome) 88%, var(--vf-accent)));
  --vf-accent-soft: color-mix(in srgb, var(--vf-accent) 10%, var(--vf-surface));
  --vf-accent-line: color-mix(in srgb, var(--vf-accent) 28%, var(--vf-line));
  --vf-on-accent: var(--v-theme-on-accent, color-mix(in srgb, var(--vf-canvas) 92%, var(--vf-accent)));
  --vf-focus: color-mix(in srgb, var(--vf-accent) 22%, transparent);
  --vf-positive: var(--green, var(--vf-accent));
  /* Ends at --v-danger, not at the accent. A tenant may still override via
     --red; without one, danger must stay danger rather than becoming whatever
     the brand accent happens to be. */
  --vf-danger: var(--red, var(--v-danger));

  /* ── Entity status colour chart ────────────────────────────────────────
     THE colours for a lifecycle status, anywhere in the product. Before this
     the same three ticket statuses were coloured in five different places:
     admin-home.js twice, admin-service-finance.js twice (one of them solid
     dark fills, the odd one out), and admin-projects.js. Nate picked the
     translucent set as canonical on 2026-08-12.

     Semantic, not brand: a status means the same thing whatever a tenant
     rebrands to, so these are fixed hues rather than accent derivatives. Add a
     status here and every surface gets it. */
  --vf-status-open:      #58d68d;   /* live, nothing blocking          */
  --vf-status-scheduled: #58d68d;   /* booked, same "healthy" reading  */
  --vf-status-invoicing: #bb8fce;   /* work done, money outstanding    */
  --vf-status-resolved:  #5dade2;   /* closed out                      */
  --vf-status-hold:      #f39c12;   /* paused, needs attention         */
  --vf-status-canceled:  var(--vf-danger);
  /* Project lifecycle. Live values confirmed against the projects table
     2026-08-12: Active, Bidding, Complete, On Hold, Cancelled. Active reuses the
     open hue and Complete the resolved hue, because they mean the same thing on
     a project that Open and Resolved mean on a ticket. */
  --vf-status-active:    var(--vf-status-open);
  --vf-status-bidding:   #a29bfe;
  --vf-status-complete:  var(--vf-status-resolved);
  --vf-r: 12px;
  --vf-r-sm: 8px;
  --v-border: var(--vf-line);
}

/* ── Page: one flat canvas; emphasis belongs to components, not the page ─ */
body.v-ui.v-face .main {
  background: transparent;
}

/* ── Typography: COLOUR ONLY ────────────────────────────────────────────
   Size, weight and tracking for every text role belong to the type scale in
   vantage-ui.css (--v-fs-*, Nate ruling 2026-08-27). This layer restates none
   of them: a second owner here is exactly how a modal title shipped at 19px
   on converted pages and 22/24px everywhere else. Add a colour here; never a
   font-size. */
body.v-ui.v-face .hero-title,
body.v-ui.v-face .v-page-title,
body.v-ui.v-face .section-title,
body.v-ui.v-face .v-section-title,
body.v-ui.v-face .card-title {
  color: var(--vf-text);
}
body.v-ui.v-face .form-group > label,
body.v-ui.v-face .modal label,
body.v-ui.v-face .v-field-label,
body.v-ui.v-face .form-label,
body.v-ui.v-face .field-label,
body.v-ui.v-face .label-hint {
  color: var(--vf-text-muted);
}


/* ══ THE TYPE SCALE ═══════════════════════════════════════════════════════
   Moved here from vantage-ui.css on Nate's direction, 2026-08-27: "we should
   be putting everything into facelift anyway except for the excluded things."

   Coverage checked before the move: 73 pages load vantage-ui.css, 71 load this
   file, and all 73 pages that carry a body class carry v-face. The three that
   load the canon without this file (gc-proposal, sign, subscription-setup)
   have NO body class at all, so `body.v-ui` rules never reached them either —
   nothing loses typography by this move. sign and subscription-setup are
   queued for a facelift opt-in.

   COUPLING, stated plainly because it is the one real cost: a handful of
   component rules that still live in vantage-ui.css read these tokens (table
   th and td, inputs, .tab-btn, the KPI label, the app-shell hero). Those rules
   are scoped `body.v-ui`, and this file is where the values now live, so a
   page that carries v-ui MUST also load this sheet or those components fall
   back to inherited sizing. That is enforced by the v-face body-class contract
   in scripts/test-shared-app-shell.js — do not create a v-ui page without it.

   Nine steps, one job each. Vantage had tokens for space, radius, control
   height and color but NONE for type, so every size in the app was a raw pixel
   literal: 3,644 declarations in CSS and 6,131 in markup/JS across 43 distinct
   px values, with `.hero-title` alone redefined in 28 page stylesheets at
   24/26/28/32/36/38px. Forty-three sizes is not a scale.

   Nothing lives between two steps. 12.5px, 11.5px, 10.5px, 9.5px, 9px, 8.5px,
   8px and 7px are not values — they are drift, and a page that reaches for one
   is answering a question the scale already answers.

   Weight has exactly two values, 400 and 500 (the C22 no-bold ruling). Every
   role below states its own weight, because the C22 reset sets h1 through h6
   to 500 and a role that stays silent renders at 500 on a heading tag and 400
   on a plain div — the same header, two weights, decided by whichever tag the
   author happened to type.

   Letter-spacing belongs to exactly two steps: the optical correction on the
   page title, and the tracking on a table header. Everywhere else it is 0.

   Brief: docs/briefs/typography-standardization.md. Enforcement: C38. */
body.v-ui {
  --v-fs-page: clamp(28px, 3vw, 38px);  /* page hero — ONE per page        */
  --v-fs-page-sub: 13px;                /* the line under the hero         */
  --v-fs-section: 16px;                 /* EVERY section/card/panel header */
  --v-fs-section-sub: 12px;             /* a section's description         */
  --v-fs-dialog: 19px;                  /* modal title AND drawer title    */
  --v-fs-body: 13px;                    /* body, cells, values, CONTROLS   */
  --v-fs-label: 12px;                   /* the label that names a field    */
  --v-fs-micro: 11px;                   /* hints, captions, counts — floor */
  --v-fs-th: 10px;                      /* <th> only; the one uppercase    */
  /* A DISPLAYED NUMBER (Nate ruling 2026-08-27: "22"). The tenth step, and the
     one the scale was missing: a figure with a label over it is not a heading,
     and it was shipping at SEVEN sizes across five class names — 26px
     .v-kpi-value (29 files), 20px .stat-val (14 files), and page-local 17px
     (three GC pages), 18px, 22px, 28px and 32px. The canon already warned
     "do not reintroduce a second KPI family" after deleting a third in
     August; two survived it, plus three page-local spellings. 22px is the
     middle that moves every consumer the least. */
  --v-fs-figure: 22px;
}
/* ── Canonical typography roles ──────────────────────────────────────────
   Eight roles, and they are the whole vocabulary. Every one reads a step of
   the type scale defined on the shell block above — none of them states a
   pixel value, so the scale can be retuned in one place.

   Eyebrow roles are retired (2026-07-25): a section opens with its title;
   former eyebrow-only headers promote to .v-section-title. `.hero-title`/
   `.hero-sub` are temporary aliases of the .v-page-* roles and are removed
   when the last page stops using them.

   Every role states its own font-weight. Silence is not neutral here: the
   C22 reset sets h1 through h6 to 500, so a role that omits weight renders at
   500 on a heading tag and 400 on a plain div. `.section-title` was shipping
   both — 500 across its 123 heading sites and 400 across its 162 div, span and
   p sites (measured 2026-08-27).

   If a piece of text does not fit one of these roles, that is the signal to
   stop and ask, not to invent a ninth size. */
body.v-ui .v-page-title,
body.v-ui .hero-title {
  margin: 0;
  color: var(--v-text, var(--gold));
  font-family: var(--v-font-display);
  font-size: var(--v-fs-page);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

body.v-ui .v-page-sub,
body.v-ui .hero-sub {
  margin: var(--v-space-1) 0 0;
  color: var(--v-text-muted, var(--muted));
  font-size: var(--v-fs-page-sub);
  font-weight: 400;
}

/* THE section heading — one size for every section, card and panel header in
   the product (Nate ruling 2026-08-27: "Agreed. one."). `.section-title` was
   26px for a page-level section while `.v-section-title` was 16px for one
   inside a panel; two sizes for one idea is how a page grew three tiers of
   header. `.card-title` — six unrelated definitions across six page
   stylesheets, and the difference Nate saw between the proposal builder and
   Settings — is the same role and resolves here too. Page stylesheets do not
   redefine any of these; if a header needs to differ, that is a new named
   role, not a local override. */
body.v-ui .v-section-title,
body.v-ui .section-title,
body.v-ui .card-title {
  margin: 0 0 14px;
  color: var(--v-text, var(--gold-light));
  font-family: var(--font);
  font-size: var(--v-fs-section);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: 0;
  text-transform: none;
}

/* The description under a section header. Typography only — the two aliases
   keep their own bottom margin because they sit in different containers: a
   `.section-sub` opens a page section and needs air before the content, a
   `.card-title-sub` sits inside a card head and must not push it open. */
body.v-ui .v-section-sub,
body.v-ui .section-sub,
body.v-ui .card-title-sub {
  color: var(--v-text-muted, var(--muted));
  font-family: var(--font-body);
  font-size: var(--v-fs-section-sub);
  font-weight: 400;
  line-height: 1.6;
}
body.v-ui .section-sub { margin: 0 0 var(--v-space-8); }

/* Body-copy roles. Pages use these instead of restating size/color per class. */
body.v-ui .v-body {
  color: var(--v-text, var(--gold-light));
  font-size: var(--v-fs-body);
  font-weight: 400;
  line-height: 1.6;
}

body.v-ui .v-muted {
  color: var(--v-text-muted, var(--muted));
  font-size: var(--v-fs-body);
  font-weight: 400;
  line-height: 1.6;
}

body.v-ui .v-small {
  color: var(--v-text-muted, var(--muted));
  font-size: var(--v-fs-micro);
  font-weight: 400;
  line-height: 1.5;
}

/* THE dialog title — modal AND drawer, one size (Nate ruling 2026-08-27,
   confirming the 2026-08-26 "one canonical header, and it should match the
   modal"). This is the single typography owner for the role, and it lives
   here rather than in the facelift because the facelift is opt-in while this
   sheet reaches every v-ui page; a title that only sized itself on converted
   pages is how 19px / 22px / 24px all shipped at once. `.modal-header h2` is
   the legacy alias for a title that has not been stamped .modal-title yet —
   it takes the same token, and drains as C23 converts each page. */
body.v-ui .modal-title,
body.v-ui .modal-header h2 {
  margin-bottom: var(--v-space-2);
  color: var(--v-text, var(--gold));
  font-size: var(--v-fs-dialog);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-transform: none;
}

/* THE field label (Nate ruling 2026-08-27: 12px). Field labels were shipping
   at 9, 9.5, 10, 11 and 12px across four class names — `.v-field-label`,
   `.form-label`, `.field-label` and a bare `.modal label` — over 18 page
   stylesheets and 597 markup sites. `.v-field-label` is the canonical name;
   the other three are aliases that drain as pages convert. The label is
   muted, not accent: it names the control, the value is what matters. */
body.v-ui .form-group > label,
body.v-ui .modal label,
body.v-ui .v-field-label,
body.v-ui .form-label,
body.v-ui .field-label {
  display: block;
  margin-bottom: var(--v-space-2);
  color: var(--v-text-muted, var(--gold));
  font-size: var(--v-fs-label);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}

/* A label's trailing hint — the parenthetical that qualifies the label, not a
   second label. It is the micro step, and it is the FLOOR: nothing in the
   product is smaller than this except a table header. */
body.v-ui .label-hint {
  margin-left: 6px;
  color: var(--v-text-muted, var(--muted));
  font-size: var(--v-fs-micro);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

/* ── Controls: softer surfaces, clear focus ring ───────────────────────── */
body.v-ui.v-face input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="range"]),
body.v-ui.v-face select,
body.v-ui.v-face textarea {
  border-color: var(--vf-line);
  border-radius: var(--vf-r-sm);
  /* background-COLOR, not the shorthand — the shorthand would wipe the
     canonical select caret (background-image) from vantage-ui.css. */
  background-color: var(--vf-panel);
}
body.v-ui.v-face input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="range"]):hover,
body.v-ui.v-face select:hover,
body.v-ui.v-face textarea:hover {
  border-color: var(--vf-accent-line);
}

/* ── Buttons: one structure, three semantic treatments.
   Primary advances the surface, Secondary covers every ordinary supporting
   action, and Danger is destructive. Compact and icon-only are modifiers.
   Shared hover glow, no gradients or compatibility aliases, and no outline or
   ghost family. */
body.v-ui.v-face .v-btn-primary,
body.v-ui.v-face .v-btn-secondary,
body.v-ui.v-face .v-btn-danger,
body.v-ui.v-face .v-btn-compact {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--v-control-height, 36px);
  height: var(--v-control-height, 36px);
  padding: 0 14px;
  border-radius: var(--vf-r-sm);
  font-family: var(--font);
  /* A button is a control, so it rides the body step (--v-fs-body) like the
     tabs beside it — never a literal. */
  font-size: var(--v-fs-body) !important;
  font-weight: 500 !important;
  letter-spacing: 0;
  text-transform: none;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.13s, border-color 0.13s, color 0.13s,
    transform 0.06s, box-shadow 0.13s;
}
body.v-ui.v-face :is(.v-btn-primary, .v-btn-secondary, .v-btn-danger):active {
  transform: translateY(1px);
}
body.v-ui.v-face :is(.v-btn-primary, .v-btn-secondary, .v-btn-danger):disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Primary — flat gold, near-ink text, hover brightens + soft lift */
body.v-ui.v-face .v-btn-primary {
  border: 1px solid var(--vf-accent);
  background: var(--vf-accent);
  color: var(--vf-on-accent);
}
body.v-ui.v-face .v-btn-primary:hover {
  background: color-mix(in srgb, var(--vf-accent) 88%, var(--vf-text));
  border-color: color-mix(in srgb, var(--vf-accent) 88%, var(--vf-text));
  color: var(--vf-on-accent);
  box-shadow: 0 2px 12px var(--vf-focus);
}

/* Secondary — the single neutral treatment for every supporting action. */
body.v-ui.v-face .v-btn-secondary {
  border: 1px solid var(--vf-line);
  background: transparent;
  color: var(--vf-text);
}
body.v-ui.v-face .v-btn-secondary:hover {
  border-color: var(--vf-accent-line);
  background: var(--vf-accent-soft);
  color: var(--vf-text);
  box-shadow: 0 2px 12px var(--vf-focus);
}

body.v-ui.v-face .v-btn-danger {
  border: 1px solid color-mix(in srgb, var(--vf-danger) 42%, transparent);
  background: color-mix(in srgb, var(--vf-danger) 6%, transparent);
  color: var(--vf-danger);
}
body.v-ui.v-face .v-btn-danger:hover {
  border-color: color-mix(in srgb, var(--vf-danger) 65%, transparent);
  background: color-mix(in srgb, var(--vf-danger) 12%, transparent);
  box-shadow: 0 2px 12px color-mix(in srgb, var(--vf-danger) 24%, transparent);
}
body.v-ui.v-face :is(.v-btn-primary, .v-btn-secondary, .v-btn-danger):focus-visible {
  outline: 2px solid var(--vf-accent);
  outline-offset: 2px;
}
body.v-ui.v-face .v-btn-compact {
  min-height: 28px;
  height: 28px;
  padding: 0 10px;
  font-size: 12px !important;
}
/* Chip size. Mirrors the ungated .v-btn-chip in vantage-buttons.css and carries
   the same geometry as body.v-ui.v-face .v-chip below, because it IS a chip that
   does something. See the note there. */
body.v-ui.v-face .v-btn-chip {
  min-height: 22px;
  height: 22px;
  padding: 0 10px;
  border-radius: 20px;
  font-size: 11.5px !important;
  letter-spacing: 0.02em;
}
body.v-ui.v-face .v-btn-icon {
  width: var(--v-control-height, 36px);
  padding: 0;
}
body.v-ui.v-face .v-btn-compact.v-btn-icon {
  width: 28px;
}

/* Segmented filter control — "pick one of these"
   NARROW VIEWPORTS (2026-07-26, admin-selections lane): the control had no
   narrow-width strategy at all, so a seg wider than its container either ran
   off the right edge of the screen (admin-projects' 6-segment filter measured
   425px inside a 390px viewport) or had its tail segments clipped away with no
   way to reach them (admin-selections' 7-segment Status Filter: 449px of
   content in a 356px box — "Archived" was unreachable on a phone). The pill
   must stay JOINED, so it does not wrap and the segments do not squash: the
   container caps at its parent and scrolls horizontally instead. Purely
   additive — every other consumer measured 321px or less at 390px and one row,
   so none of them reflow. */
body.v-ui.v-face .v-seg {
  display: inline-flex;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;              /* the pill keeps its shape — no scrollbar chrome */
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--vf-line);
  border-radius: var(--vf-r-sm);
  background: var(--vf-panel);
}
body.v-ui.v-face .v-seg::-webkit-scrollbar { display: none; }
body.v-ui.v-face .v-seg button {
  flex: 0 0 auto;                     /* segments never shrink — the container scrolls */
  height: calc(var(--v-control-height, 36px) - 2px);
  padding: 0 14px;
  border: 0;
  background: transparent;
  color: var(--vf-text-muted);
  font-family: var(--font);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
  text-transform: none;
  cursor: pointer;
}
body.v-ui.v-face .v-seg button + button { border-left: 1px solid var(--vf-line-soft); }
body.v-ui.v-face .v-seg button.on,
body.v-ui.v-face .v-seg button[aria-pressed="true"] {
  background: var(--vf-accent-soft);
  color: var(--vf-text);
  
}
body.v-ui.v-face .v-seg button:hover:not(.on):not([aria-pressed="true"]) { color: var(--vf-text); }
/* Facet count. A segmented control used as a status filter above a list carries
   the row count for that status ("Draft 4"), so the number reads as a quieter
   trailing figure and lights up with its segment. A MODIFIER on .v-seg, not a
   new family: the retired .stat-pills strip is what happens when counts get
   their own component. Added 2026-08-02 with .v-list-toolbar (vantage-ui.css). */
body.v-ui.v-face .v-seg button .v-seg-count {
  margin-left: 7px;
  color: var(--vf-text-subtle);
  font-variant-numeric: tabular-nums;
}
body.v-ui.v-face .v-seg button.on .v-seg-count,
body.v-ui.v-face .v-seg button[aria-pressed="true"] .v-seg-count { color: var(--vf-accent); }

/* ── Tabs: Title Case, accent underline ────────────────────────────────── */
/* ── Tables: gradient header, even rows, quiet hairlines ───────────────── */
body.v-ui.v-face .table-wrap {
  border-color: var(--vf-line);
  border-radius: var(--vf-r);
}
body.v-ui.v-face .table-wrap > table > thead {
  background: linear-gradient(180deg,
    var(--v-theme-table-header, var(--vf-surface-raised)),
    var(--vf-surface));
}
body.v-ui.v-face .table-wrap > table th {
  padding: 11px 16px;
  border-bottom: 1px solid var(--vf-line);
}
body.v-ui.v-face .table-wrap > table td {
  padding: var(--v-row-pad, 13px 16px);
  border-top: 1px solid var(--vf-line-soft);
  vertical-align: middle;
}
body.v-ui.v-face .table-wrap > table tbody tr:hover > td {
  background: var(--v-theme-table-hover, var(--vf-chrome-active));
}
body.v-ui.v-face .comms-cell-primary { font-weight: 500; color: var(--vf-text); }
body.v-ui.v-face .comms-cell-secondary,
body.v-ui.v-face .col-sub {
  margin-top: 2px;
  color: var(--vf-text-muted);
  font-size: 12px;
  max-width: 280px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Modals: flat glassy chrome, footer discipline (frame ruling 2026-07-25:
   no gradient band — enforced by design-contract C18) ─────────────────── */
body.v-ui.v-face .modal-overlay {
  background: color-mix(in srgb, var(--vf-canvas) 68%, transparent);
  backdrop-filter: blur(6px);
}
body.v-ui.v-face .modal {
  border: 1px solid var(--vf-line);
  border-radius: 16px;
  background: var(--vf-surface);
  box-shadow: 0 32px 80px rgba(0, 0, 0, 0.5);
}
body.v-ui.v-face .modal-title,
body.v-ui.v-face .modal-header h2 {
  color: var(--vf-text);
}
body.v-ui.v-face .modal-sub { color: var(--vf-text-muted); }
body.v-ui.v-face .modal-close {
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: var(--vf-r-sm);
  background: transparent;
  color: var(--vf-text-muted);
}
body.v-ui.v-face .modal-close:hover {
  background: var(--vf-accent-soft);
  color: var(--vf-text);
}

/* ── Customer editor POC: concept structure, not a legacy modal reskin ── */
body.v-ui.v-face #edit-customer-modal {
  align-items: flex-start;
  padding: 24px;
  overflow: hidden;
}
body.v-ui.v-face .vf-customer-editor {
  width: min(680px, calc(100vw - 48px));
  max-width: 680px !important;
  max-height: calc(100dvh - 48px);
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.v-ui.v-face .vf-customer-editor .modal-header {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) auto 32px;
  align-items: center;
  gap: 14px;
  min-height: 88px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--vf-line-soft);
}
/* THE canonical modal-header gradient — one rule, both families (frame ruling
   2026-07-25 rev 2, Nate: the gradient belongs on the HEADER band, bleeding
   edge to edge on the padding-0 frame — never painted on the frame itself,
   which rendered as an inset square). Enforced: C18 bans frame gradients but
   exempts header selectors; this rule is the only place it may live. The band
   is a SURFACE, so neutral light palettes source it from the raised surface
   role instead of leaking the brand/text hue back into the dialog. */
body.v-ui.v-face .vf-customer-editor .modal-header,
body.v-ui.v-face .v-service-modal .modal-header,
body.v-ui.v-face .v-drawer-header {
  background: linear-gradient(180deg, var(--vf-surface-raised), transparent) !important;
}
/* THE canonical modal close — one rule. REVERSED 2026-07-26 (Nate: "the x
   close out doesnt have the border around it so i know its not canonical"),
   superseding the 2026-07-25 bare-ghost ruling: the close is the standard
   compact icon-button — 28px box, 1px hairline, muted → text on hover. Any
   close button inside a modal header converges here regardless of which
   button classes the page used. */
body.v-ui.v-face .modal-header [aria-label="Close"],
body.v-ui.v-face .v-drawer-header [aria-label="Close"],
body.v-ui.v-face .modal-header .modal-close {
  box-sizing: border-box !important;
  width: 28px !important;
  height: 28px !important;
  min-height: 28px !important;
  padding: 0 !important;
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  background: transparent !important;
  border: 1px solid var(--vf-line) !important;
  border-radius: var(--vf-r-sm) !important;
  box-shadow: none !important;
  color: var(--vf-text-muted) !important;
}
body.v-ui.v-face .modal-header [aria-label="Close"]:hover,
body.v-ui.v-face .v-drawer-header [aria-label="Close"]:hover,
body.v-ui.v-face .modal-header .modal-close:hover {
  color: var(--vf-text) !important;
  border-color: color-mix(in srgb, var(--vf-text) 32%, transparent) !important;
  background: color-mix(in srgb, var(--vf-text) 6%, transparent) !important;
}
body.v-ui.v-face .vf-customer-avatar {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--vf-line);
  border-radius: 50%;
  background: var(--vf-panel);
  color: var(--vf-text);
  font-size: 16px;
  
}
body.v-ui.v-face .vf-customer-identity { min-width: 0; }
body.v-ui.v-face .vf-customer-identity .modal-title,
body.v-ui.v-face .vf-customer-identity .modal-sub {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.v-ui.v-face .vf-customer-identity .modal-sub { margin-top: 3px; }
body.v-ui.v-face .vf-customer-identity .modal-sub,
body.v-ui.v-face .vf-customer-editor .label-hint,
body.v-ui.v-face .vf-section-count,
body.v-ui.v-face .vf-address-meta,
body.v-ui.v-face .vf-portal-copy p,
body.v-ui.v-face .vf-empty-state,
body.v-ui.v-face .vf-customer-editor #household-contacts-list > p {
  color: var(--vf-text-muted);
}
body.v-ui.v-face .vf-customer-editor .modal-header .modal-close {
  position: static;
  inset: auto;
}
body.v-ui.v-face .vf-portal-chip { white-space: nowrap; }
body.v-ui.v-face .vf-customer-editor .modal-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}
body.v-ui.v-face .vf-editor-section {
  padding: 22px 24px;
  border-bottom: 1px solid var(--vf-line-soft);
}
body.v-ui.v-face .vf-section-title {
  margin: 0 0 16px;
  color: var(--vf-accent);
  font-size: 11px;
  
  }
body.v-ui.v-face .vf-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}
body.v-ui.v-face .vf-section-head .vf-section-title { margin: 0; }
body.v-ui.v-face .vf-section-count {
  color: var(--vf-text-muted);
  font-size: 12px;
}
body.v-ui.v-face .vf-text-action {
  border: 0;
  background: transparent;
  color: var(--vf-accent);
  font: 500 13px/1 var(--font);
  cursor: pointer;
}
body.v-ui.v-face .vf-text-action { margin-top: 12px; padding: 5px 0; }
body.v-ui.v-face .vf-text-action:hover { color: var(--vf-text); }
body.v-ui.v-face .vf-empty-state {
  padding: 16px;
  border: 1px dashed var(--vf-line);
  border-radius: var(--vf-r);
  color: var(--vf-text-muted);
  font-size: 13px;
}
body.v-ui.v-face .vf-address-card {
  padding: 16px;
  border: 1px solid var(--vf-line);
  border-radius: var(--vf-r);
  background: var(--vf-panel);
}
body.v-ui.v-face .vf-address-layout {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
body.v-ui.v-face .vf-address-name {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--vf-text);
  font-size: 14px;
  
}
body.v-ui.v-face .vf-address-lines {
  margin-top: 5px;
  color: var(--vf-text);
  font-size: 13px;
  line-height: 1.55;
}
body.v-ui.v-face .vf-address-meta {
  margin-top: 5px;
  color: var(--vf-text-muted);
  font-size: 12px;
}
/* Holds the card's […] trigger. No `position: relative` any more — that
   anchored the retired in-card dropdown; VantageUI.rowMenu positions
   itself against the trigger from <body>. */
body.v-ui.v-face .vf-card-menu-wrap { flex: none; }
body.v-ui.v-face .v-portal-card { justify-content: flex-start; }
body.v-ui.v-face .vf-status-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: none;
  border: 1px solid color-mix(in srgb, var(--vf-positive) 30%, transparent);
  border-radius: var(--vf-r-sm);
  color: var(--vf-positive);
  background: color-mix(in srgb, var(--vf-positive) 8%, transparent);
}
/* Tone modifiers. The base badge is positive; a lock, an opt-out, or a
   not-yet-answered state must not read as green. */
body.v-ui.v-face .vf-status-icon.is-muted {
  border-color: var(--vf-line);
  color: var(--vf-text-muted);
  background: color-mix(in srgb, var(--vf-text-muted) 8%, transparent);
}
body.v-ui.v-face .vf-status-icon.is-danger {
  border-color: color-mix(in srgb, var(--vf-danger) 30%, transparent);
  color: var(--vf-danger);
  background: color-mix(in srgb, var(--vf-danger) 8%, transparent);
}
body.v-ui.v-face .vf-portal-copy { min-width: 0; flex: 1; }
body.v-ui.v-face .vf-portal-copy strong { color: var(--vf-text); font-size: 14px; }
body.v-ui.v-face .vf-portal-copy p { margin: 3px 0 0; color: var(--vf-text-muted); font-size: 12.5px; }
body.v-ui.v-face .vf-portal-actions {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}
body.v-ui.v-face .vf-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ── Chips: one geometry for identifiers and state ─────────────────────── */
/* Radius is 20px, not 999px, by Nate's ruling 2026-08-10. A full capsule on a
   ~20px-tall chip reads noticeably rounder than every other rounded element in
   the product, which is what surfaced it: the Comms Log's pills sat inches from
   the invoice tab's 20px badges on admin-service-finance and the difference was
   obvious. This is THE chip geometry — the legacy page-local .status-badge rules
   that used to compete with it were deleted in the same change (Rule G-1). */
body.v-ui.v-face .v-chip,
body.v-ui.v-face .v-billing-state,
body.v-ui.v-face .project-tag,
body.v-ui.v-face .proj-chip,
body.v-ui.v-face .v-scope-chip {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border: 1px solid var(--vf-line);
  border-radius: 20px;
  background: var(--vf-panel);
  color: var(--vf-text-muted);
  font-family: var(--font);
  font-size: 11.5px;
  
  line-height: 1.25;
  letter-spacing: 0.02em;
  text-transform: none;
  box-shadow: none;
}
body.v-ui.v-face :is(.project-tag, .proj-chip) {
  cursor: pointer;
  color: var(--vf-text);
  font-family: var(--v-font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
}
body.v-ui.v-face :is(.project-tag, .proj-chip):hover,
body.v-ui.v-face .project-tag.active {
  border-color: var(--vf-accent-line);
  background: var(--vf-accent-soft);
  color: var(--vf-text);
  box-shadow: none;
}
/* ── Scope chip — THE active-scope indicator ───────────────────────────────
   "You are looking at a filtered subset, here is what scopes it, here is how to
   get out of it." It answers a different question from .v-breadcrumbs, which
   says where you are in a hierarchy; a page may carry both, and neither does the
   other's job. Rendered by vantage-scope.js into the hero's left column, under
   the title. See docs/briefs/active-project-scope-standardization.md.

   G-1 convergence: this replaces FOUR implementations of one concept —
   .v-context-bar (vantage-ui.css, one consumer), .proj-context-bar
   (admin-selections.css), an inline style block with a HARDCODED gold literal
   that ignored the tenant brand entirely (admin-schedule.html), and
   .dh-project-badge (admin-design-hub.css). The first three were full-bleed
   bands spliced into their page's own stack at their own chosen point, which is
   why no two pages agreed on placement: there is no insertion point correct for
   every stack. Anchored to the hero, there is exactly one. The fourth was
   already a capsule, and is the shape this takes.

   It joins the chip geometry above rather than restating it. What it adds is the
   accent tint — Design Hub's badge filled with the panel colour and was close to
   invisible, and that visibility carries real weight here: a user who forgot a
   project is selected sees a short list and assumes data is missing.

   The BODY IS INERT. Only the × clears. The badge this replaces cleared on
   body-click, which reads backwards: the obvious affordance on a project pill is
   "go to this project", not "destroy this filter". (Nate, 2026-08-16.) */
body.v-ui.v-face .v-scope-chip {
  margin-top: 10px;
  padding-right: 4px;
  background: var(--vf-accent-soft);
  border-color: var(--vf-accent-line);
  color: var(--vf-text);
  cursor: default;
}
body.v-ui.v-face .v-scope-chip > [data-vi-icon] { color: var(--vf-text-subtle); }
body.v-ui.v-face .v-scope-chip-code {
  font-family: var(--v-font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
  color: var(--vf-text);
}
body.v-ui.v-face .v-scope-chip-name { color: var(--vf-text-muted); }
body.v-ui.v-face .v-scope-chip-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: 2px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--vf-text-subtle);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
body.v-ui.v-face .v-scope-chip-clear:hover {
  background: color-mix(in srgb, var(--vf-text) 10%, transparent);
  color: var(--vf-text);
}
body.v-ui.v-face .v-scope-chip-clear:focus-visible {
  outline: 2px solid var(--vf-focus);
  outline-offset: 1px;
}

/* Scoped empty state. The gap this closes: a scoped list that comes back empty
   used to say "No questionnaires match these filters", which never names the
   project as the reason. Built by VantageScope.emptyState() so the copy has one
   owner and cannot drift per page. */
body.v-ui.v-face .v-scope-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 32px 20px;
  text-align: center;
  color: var(--vf-text-muted);
}
body.v-ui.v-face .v-scope-empty-title {
  color: var(--vf-text);
  font-size: 14px;
}
body.v-ui.v-face .v-scope-empty-body { font-size: 13px; }
body.v-ui.v-face .v-scope-empty > button { margin-top: 4px; }

body.v-ui.v-face .v-chip__dot {
  width: 6px;
  height: 6px;
  flex: 0 0 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: .72;
}
body.v-ui.v-face .ticket-tag {
  cursor: pointer;
  font-family: var(--font);
  letter-spacing: .02em;
}
body.v-ui.v-face :is(.v-chip, .v-billing-state).is-good {
  color: var(--vf-positive);
  border-color: color-mix(in srgb, var(--vf-positive) 30%, transparent);
  background: color-mix(in srgb, var(--vf-positive) 9%, transparent);
}
body.v-ui.v-face :is(.v-chip, .v-billing-state).is-blocked {
  color: var(--vf-danger);
  border-color: color-mix(in srgb, var(--vf-danger) 32%, transparent);
  background: color-mix(in srgb, var(--vf-danger) 9%, transparent);
}
body.v-ui.v-face :is(.v-chip, .v-billing-state).is-attention {
  color: var(--vf-accent);
  border-color: var(--vf-accent-line);
  background: var(--vf-accent-soft);
}

/* Lifecycle status, one capsule for every entity. Same geometry as .v-chip
   above; only the hue changes, from the status chart in :root. Rendered by
   VantageUI.statusChip() so no page hand-rolls the markup again. */
body.v-ui.v-face .v-chip.is-status-open      { --vf-status: var(--vf-status-open); }
body.v-ui.v-face .v-chip.is-status-scheduled { --vf-status: var(--vf-status-scheduled); }
body.v-ui.v-face .v-chip.is-status-invoicing { --vf-status: var(--vf-status-invoicing); }
body.v-ui.v-face .v-chip.is-status-resolved  { --vf-status: var(--vf-status-resolved); }
body.v-ui.v-face .v-chip.is-status-hold      { --vf-status: var(--vf-status-hold); }
body.v-ui.v-face .v-chip.is-status-canceled  { --vf-status: var(--vf-status-canceled); }
body.v-ui.v-face .v-chip.is-status-active    { --vf-status: var(--vf-status-active); }
body.v-ui.v-face .v-chip.is-status-bidding   { --vf-status: var(--vf-status-bidding); }
body.v-ui.v-face .v-chip.is-status-complete  { --vf-status: var(--vf-status-complete); }
body.v-ui.v-face .v-chip[class*="is-status-"] {
  color: var(--vf-status);
  border-color: color-mix(in srgb, var(--vf-status) 32%, transparent);
  background: color-mix(in srgb, var(--vf-status) 12%, transparent);
}

/* ── Portal status card ────────────────────────────────────────────────── */
body.v-ui.v-face .v-portal-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 16px;
  border: 1px solid var(--vf-line);
  border-radius: var(--vf-r);
  background: var(--vf-panel);
}

/* ── Client document preview shell ──────────────────────────────────────
   Proposal, change-order, and invoice documents remain standalone customer
   surfaces. This shared shell owns their utility bar, disclosure, and toast
   chrome without introducing the authenticated admin sidebar. */
body.v-ui.v-face.v-preview-shell {
  background: var(--vf-canvas);
  color: var(--vf-text);
  font-family: var(--font, system-ui, sans-serif);
}
body.v-ui.v-face.v-preview-shell button { font-family: var(--font, system-ui, sans-serif); }
body.v-ui.v-face.v-preview-shell .v-backdrop-canvas { display: none !important; }
body.v-ui.v-face .v-preview-bar {
  position: fixed;
  inset: 0 0 auto;
  z-index: 200;
  /* Matches the shell's utility-bar band (vantage-ui.css --v-appbar-h: 56px).
     At 60px the bar overhung the sidenav by 4px on every shell page. */
  min-height: var(--v-appbar-h, 56px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 24px;
  border-bottom: 1px solid var(--vf-line);
  background: color-mix(in srgb, var(--vf-surface) 94%, transparent);
  color: var(--vf-text);
  backdrop-filter: blur(12px);
}
body.v-ui.v-face .v-preview-bar__brand,
body.v-ui.v-face .v-preview-bar__actions {
  display: flex;
  align-items: center;
  gap: 10px;
}
body.v-ui.v-face .v-preview-bar__brand {
  min-width: 0;
  color: var(--vf-text);
  text-decoration: none;
}
body.v-ui.v-face .v-preview-bar__brand img {
  width: auto;
  height: 34px;
}
body.v-ui.v-face .v-preview-bar__title {
  overflow: hidden;
  color: var(--vf-text);
  font-size: 13px;
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.v-ui.v-face .v-preview-ribbon {
  position: fixed;
  /* Pinned to the bar's own height. The bar was corrected 60px -> 56px above and
     this was left at 60, leaving a 4px strip of page showing between them. Both
     now read the same token so they cannot drift apart again. */
  inset: var(--v-appbar-h, 56px) 0 auto;
  z-index: 190;
  padding: 8px 18px;
  border-bottom: 1px solid var(--vf-accent-line);
  background: var(--vf-chrome);
  color: var(--vf-text);
  font-size: 12px;
  
  line-height: 1.4;
  text-align: center;
}
body.v-ui.v-face .v-disclosure {
  overflow: hidden;
  border: 1px solid var(--vf-line);
  border-radius: var(--vf-r);
  background: var(--vf-panel);
}
body.v-ui.v-face .v-disclosure > summary {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 24px;
  padding: 2px 10px;
  color: var(--vf-text);
  font-size: 12px;
  font-weight: 500;
  list-style: none;
  cursor: pointer;
}
body.v-ui.v-face .v-disclosure > summary::-webkit-details-marker { display: none; }
body.v-ui.v-face .v-disclosure > summary::before {
  content: '›';
  flex: 0 0 auto;
  color: var(--vf-accent);
  transition: transform 0.16s ease;
}
body.v-ui.v-face .v-disclosure[open] > summary::before { transform: rotate(90deg); }
body.v-ui.v-face .v-disclosure > summary:hover {
  background: var(--vf-accent-soft);
}
body.v-ui.v-face .v-toast-wrap {
  position: fixed;
  inset: auto auto 24px 50%;
  width: min(420px, calc(100vw - 32px));
  transform: translateX(-50%);
  z-index: 99999;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  pointer-events: none;
}
body.v-ui.v-face .v-toast {
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--vf-line);
  border-radius: var(--vf-r-sm);
  background: var(--vf-surface);
  color: var(--vf-text);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--vf-canvas) 56%, transparent);
  font-size: 12px;
  line-height: 1.45;
  pointer-events: auto;
}
body.v-ui.v-face .v-toast:empty {
  display: none;
}
body.v-ui.v-face .v-toast.success {
  border-color: color-mix(in srgb, var(--vf-positive) 42%, transparent);
  color: var(--vf-positive);
}
body.v-ui.v-face .v-toast.error {
  border-color: color-mix(in srgb, var(--vf-danger) 42%, transparent);
  color: var(--vf-danger);
}
body.v-ui.v-face .v-toast.info {
  border-color: var(--vf-accent-line);
  color: var(--vf-text);
}
@media (max-width: 600px) {
  body.v-ui.v-face .v-preview-bar { padding-inline: 14px; }
  body.v-ui.v-face .v-preview-bar__title { display: none; }
  body.v-ui.v-face .v-preview-bar__actions { gap: 6px; }
  body.v-ui.v-face .v-toast-wrap {
    bottom: calc(72px + env(safe-area-inset-bottom, 0px));
  }
}

/* ── Row menus ─────────────────────────────────────────────────────────────
   There is no facelift dropdown tier. The ONE anchored menu is
   VantageUI.rowMenu (vantage-ui.js) wearing `.v-rowmenu`, opened by the ONE
   […] trigger, VantageUI.moreBtn wearing `.v-more`. The `.v-more-menu` /
   `.v-more-sep` / `.v-card-menu-btn` block that stood here was a second menu
   system with its own chrome and its own open/close machinery; both consumers
   (customer-editor.js, vantage-materials.js) were migrated to the canon and it
   was deleted (Nate 2026-08-17). Design contract C37 fails the build if it
   comes back. */

/* ── Danger disclosure ─────────────────────────────────────────────────── */
body.v-ui.v-face details.v-danger summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  color: var(--vf-text-muted);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
}
body.v-ui.v-face details.v-danger summary::-webkit-details-marker { display: none; }
body.v-ui.v-face details.v-danger summary:hover { color: var(--vf-danger); }
body.v-ui.v-face details.v-danger .v-danger-caret { font-size: 10px; transition: transform 0.15s; }
body.v-ui.v-face details.v-danger[open] .v-danger-caret { transform: rotate(90deg); }
body.v-ui.v-face .v-danger-panel {
  margin-top: 12px;
  padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--vf-danger) 32%, transparent);
  border-radius: var(--vf-r);
  background: color-mix(in srgb, var(--vf-danger) 7%, transparent);
}

/* ── Sticky modal footer: one primary, right-aligned ───────────────────── */
body.v-ui.v-face .v-modal-foot {
  position: relative;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin: 0;
  padding: 14px 24px;
  border-top: 1px solid var(--vf-line-soft);
  background: color-mix(in srgb, var(--vf-surface) 94%, var(--vf-canvas));
  border-radius: 0 0 16px 16px;
}
body.v-ui.v-face .v-modal-foot :is(.btn-submit, .btn-outline) {
  margin: 0;
  padding-block: 0;
}

/* ── Compact page controls: same quiet hairline language as tables ────── */
body.v-ui.v-face .v-search-toggle {
  border-color: var(--vf-line);
  border-radius: var(--vf-r-sm);
  background: var(--vf-panel);
  color: var(--vf-text-muted);
  box-shadow: none;
}
body.v-ui.v-face .v-search-toggle:hover,
body.v-ui.v-face .v-search-toggle:focus-visible {
  border-color: var(--vf-accent-line);
  background: var(--vf-accent-soft);
  color: var(--vf-text);
}
body.v-ui.v-face .v-compact-filter {
  box-sizing: border-box;
  width: auto;
  min-width: 190px;
  max-width: min(260px, 100%);
  height: var(--v-control-height, 36px);
  padding: 0 34px 0 12px;
  border: 1px solid var(--vf-line);
  border-radius: var(--vf-r-sm);
  background-color: var(--vf-panel);
  color: var(--vf-text);
  font: 500 13px/1 var(--font);
}
body.v-ui.v-face .tab-search-wrap:has(> .v-compact-filter) {
  display: flex;
  justify-content: flex-end;
}
@media (max-width: 600px) {
  body.v-ui.v-face #edit-customer-modal { padding: 0; }
  body.v-ui.v-face .vf-customer-editor {
    width: 100vw;
    max-height: 100dvh;
    min-height: 100dvh;
    border-radius: 0;
  }
  body.v-ui.v-face .vf-customer-editor .modal-header {
    grid-template-columns: 42px minmax(0, 1fr) 32px;
    min-height: 72px;
    padding: 12px 16px;
  }
  body.v-ui.v-face .vf-customer-avatar { width: 40px; height: 40px; }
  body.v-ui.v-face .vf-portal-chip { display: none; }
  body.v-ui.v-face .vf-editor-section { padding: 20px 16px; }
  body.v-ui.v-face .vf-editor-section .form-row { grid-template-columns: 1fr; }
  body.v-ui.v-face .v-portal-card { align-items: flex-start; flex-wrap: wrap; }
  body.v-ui.v-face .vf-portal-actions { width: 100%; justify-content: flex-end; }
  body.v-ui.v-face .v-modal-foot { padding: 12px 16px; flex-wrap: wrap; }
  body.v-ui.v-face .v-modal-foot .toast { flex-basis: 100% !important; }
}

/* ── Drawer: top edge locks to the utility bar ─────────────────────────── */
body.v-ui.v-face .v-detail-drawer {
  top: 56px;
  border-left-color: var(--vf-line);
  box-shadow: -24px 0 60px rgba(0, 0, 0, 0.45);
}
body.v-ui.v-face .v-detail-drawer__header {
  background: linear-gradient(180deg, var(--vf-surface-raised), transparent);
}

/* Keep secondary customer-editor copy readable even when a tenant's muted
   token is intentionally very dark. This rule follows the component rules
   above so the semantic contrast role wins without hardcoded colors. */
body.v-ui.v-face .vf-customer-editor :is(
  .vf-section-count,
  .vf-address-meta,
  .vf-portal-copy p,
  .vf-empty-state,
  #household-contacts-list > p
) {
  color: var(--vf-text-muted);
}

/* ── POC system audit: one hairline, one control edge, no page glow ────── */
body.v-ui.v-face .divider {
  height: 0;
  border: 0 !important;
  border-top: 1px solid var(--vf-line-soft) !important;
  box-shadow: none !important;
}
body.v-ui.v-face #tab-manage > .divider {
  margin-block: 32px !important;
}
body.v-ui.v-face #browser-search {
  box-sizing: border-box;
  height: var(--v-control-height, 36px);
  padding-block: 0 !important;
  border: 1px solid var(--vf-line) !important;
  border-radius: var(--vf-r-sm) !important;
  background: var(--vf-panel) !important;
  color: var(--vf-text) !important;
  box-shadow: none !important;
  outline: none !important;
}
body.v-ui.v-face #browser-search:hover {
  border-color: var(--vf-accent-line) !important;
}
body.v-ui.v-face #browser-search:focus {
  border-color: var(--vf-accent) !important;
  box-shadow: 0 0 0 2px
    color-mix(in srgb, var(--vf-accent) 18%, transparent) !important;
}
body.v-ui.v-face #browser-dropdown {
  border: 1px solid var(--vf-line) !important;
  border-radius: var(--vf-r-sm) !important;
  background: var(--vf-surface) !important;
}
body.v-ui.v-face #tab-manage .section-title {
  color: var(--vf-text);
}
body.v-ui.v-face #tab-manage .section-sub {
  color: var(--vf-text-muted);
}
body.v-ui.v-face .v-kpi-card {
  border: 1px solid var(--vf-line);
  background: var(--vf-panel);
  box-shadow: none;
}
body.v-ui.v-face button.v-kpi-card:hover {
  border-color: var(--vf-accent-line);
  background: var(--vf-accent-soft);
  box-shadow: 0 2px 12px var(--vf-focus);
}
body.v-ui.v-face .v-kpi-value {
  color: var(--vf-text);
}
/* The KPI label is the card's heading (see .v-kpi-label in vantage-ui.css), so
   it takes the heading color; only the sub-caption stays muted. */
body.v-ui.v-face .v-kpi-label {
  color: var(--vf-text);
}
body.v-ui.v-face .v-kpi-sub {
  color: var(--vf-text-muted);
}
/* KPI value states — the same three names the chip family already uses, so a
   "good" number and a "good" chip agree. Added 2026-08-02 with the list-toolbar
   work, because three consumers were each inventing their own affirmative:
   admin-invoices set `style="color:var(--green)"` inline on Collected AND wrote a
   hardcoded `#e07070` into JS for an overdue Outstanding (a UI-rule violation),
   and po-manager.js put a bare `.green` class on its Complete KPI that MATCHED
   NOTHING — the only .green rule in the canon is `body.v-ui .stat-val.green`,
   which needs a class that element never had. That KPI had been silently not
   green. Colors here, never at the call site. */
body.v-ui.v-face .v-kpi-value.is-good { color: var(--vf-positive); }
body.v-ui.v-face .v-kpi-value.is-blocked { color: var(--vf-danger); }
body.v-ui.v-face .v-kpi-value.is-attention { color: var(--vf-accent); }
body.v-ui.v-face .v-panel {
  border-color: var(--vf-line-soft);
  background: var(--vf-panel);
}
body.v-ui.v-face .v-panel-header {
  border-bottom-color: var(--vf-line-soft);
}
body.v-ui.v-face .v-panel-header :is(h2, h3, .title) {
  color: var(--vf-text);
}
body.v-ui.v-face .fb-browser-card {
  border: 1px solid var(--vf-line-soft);
  border-radius: var(--vf-r);
  background: var(--vf-panel);
  box-shadow: none;
}
body.v-ui.v-face .v-drawer-backdrop {
  background: color-mix(in srgb, var(--vf-page) 22%, transparent);
}
body.v-ui.v-face .v-drawer {
  border-left-color: var(--vf-line);
  background:
    linear-gradient(180deg, var(--vf-surface-raised), transparent 96px),
    var(--vf-surface);
  box-shadow: -20px 0 64px rgba(0, 0, 0, .38);
}
body.v-ui.v-face :is(.v-drawer-header, .v-drawer-footer) {
  border-color: var(--vf-line-soft);
}
@media (max-width: 600px) {
  body.v-ui.v-face .v-detail-drawer { top: 54px; }
}

/* ── Settings page opt-in ─────────────────────────────────────────────────
   Domain selectors below own settings-only layout. Shared primitives own
   surface, field, button, tab, and type presentation. */
body.v-ui.v-face.v-settings .main {
  max-width: 1400px;
  gap: 24px;
}
body.v-ui.v-face.v-settings .v-panel {
  border-color: var(--vf-line-soft) !important;
  border-radius: var(--vf-r) !important;
  background: var(--vf-panel) !important;
  box-shadow: none !important;
}
body.v-ui.v-face.v-settings .card-head {
  border-bottom: 1px solid var(--vf-line-soft) !important;
  background: transparent !important;
}
body.v-ui.v-face.v-settings :is(
  .btn-save,
  .co-tab,
  .stab-btn,
  .stab-sidenav-item
) {
  font-family: var(--font) !important;
  text-transform: none !important;
}
body.v-ui.v-face.v-settings .v-section-title {
  color: var(--vf-text);
}
/* ── Service Finance page opt-in ───────────────────────────────────────────
   Page-scoped rules below are limited to domain layout/content. Shared cards,
   KPIs, tables, buttons, fields, and overlays use canonical contracts. */
body.v-ui.v-face.v-service-finance {
  --service-hairline: var(--vf-line-soft);
}
body.v-ui.v-face.v-service-finance :is(
  .section-title,
  .panel-header .section-title,
  .li-section-title,
  #parts-fees-wrap h2,
  #parts-djes-wrap h2,
  #ticket-po-wrap h2
) {
  color: var(--vf-text) !important;
}
body.v-ui.v-face.v-service-finance :is(
  .ticket-card,
  .dash-card,
  .panel,
  .card
) {
  border: 1px solid var(--service-hairline) !important;
  border-radius: var(--vf-r) !important;
  box-shadow: none !important;
}
/* One backdrop contract. Every service-finance overlay element carries
   .modal-overlay (verified: #ticket-modal markup, po-ipo-modal.js:63,
   vantage-item-picker.js:363, po-manager.js:2177), so the class is the only
   selector needed. Shared CSS must not target page/module element IDs. */
body.v-ui.v-face.v-service-finance .modal-overlay {
  /* backdrop color/blur comes from the ONE shared face rule above —
     service-finance no longer forks it (ruling 2026-07-25) */
  pointer-events: auto;
}
/* PO editor is a drawer, not a modal: preserve page context and never blur it. */
body.v-ui.v-face.v-service-finance #poe-overlay {
  background: color-mix(in srgb, var(--vf-page) 18%, transparent) !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  pointer-events: auto;
}
/* Frame ruling 2026-07-25 (Nate): flat glassy surface — NO gradient band, NO
   internal body scroll. The modal grows with its content and the overlay
   scrolls, like every modal in this app's history. Enforced by design-contract
   checks C18 (no modal-frame gradients) and C19 (no internal-scroll frames). */
body.v-ui.v-face .v-service-modal {
  pointer-events: auto;
  box-sizing: border-box;
  padding: 0 !important;
  color: var(--vf-text);
  background: var(--vf-surface) !important;
  border: 1px solid var(--vf-line) !important;
  border-radius: var(--vf-r) !important;
  box-shadow: 0 28px 80px rgba(0, 0, 0, .42) !important;
  overflow: hidden;
}
body.v-ui.v-face .v-service-modal .modal-header {
  box-sizing: border-box;
  min-height: 64px;
  height: 64px;
  padding: 0 24px !important;
  border-bottom: 1px solid var(--vf-line-soft) !important;
}
body.v-ui.v-face .v-service-modal .modal-header :is(h2, h3, p) {
  margin-block: 0;
}
body.v-ui.v-face .v-service-modal :is(h2, h3, .modal-title) {
  color: var(--vf-text) !important;
  font-family: var(--font) !important;
  
}
body.v-ui.v-face .v-service-modal :is(
  input:not([type="checkbox"]):not([type="radio"]),
  select,
  textarea
) {
  box-sizing: border-box;
  min-height: var(--v-control-height, 36px);
  /* background-COLOR, never the shorthand — an !important background shorthand
     here wiped the canonical select caret (vantage-ui.css background-image). */
  background-color: var(--vf-panel) !important;
  color: var(--vf-text) !important;
  border: 1px solid var(--vf-line) !important;
  border-radius: var(--vf-r-sm) !important;
  box-shadow: none !important;
}
body.v-ui.v-face .v-btn-tile {
  height: auto;
  min-height: 92px;
  padding: 14px;
  flex-direction: column;
  white-space: normal;
  text-align: center;
}
body.v-ui.v-face.v-service-finance .v-service-modal :is(
  input:not([type="checkbox"]):not([type="radio"]),
  select,
  textarea
):focus {
  border-color: var(--vf-accent) !important;
  outline: 2px solid color-mix(in srgb, var(--vf-accent) 16%, transparent);
  outline-offset: 1px;
}
body.v-ui.v-face.v-service-finance :is(
  .btn-submit,
  .btn-primary,
  .btn-ghost,
  .btn-outline,
  .v-btn-ghost,
  .si-btn,
  .poe-btn,
  .vip-btn
) {
  min-height: var(--v-control-height, 36px);
  height: var(--v-control-height, 36px);
  border-radius: var(--vf-r-sm) !important;
  font-family: var(--font) !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
}
body.v-ui.v-face.v-service-finance :is(
  .btn-submit,
  .btn-primary,
  .si-btn-primary,
  .poe-btn-gold,
  .vip-btn-primary
) {
  background: var(--vf-accent) !important;
  border: 1px solid var(--vf-accent) !important;
  color: var(--vf-on-accent) !important;
}
body.v-ui.v-face.v-service-finance :is(
  .btn-outline,
  .si-btn:not(.si-btn-primary),
  .poe-btn-ghost,
  .vip-btn-outline
) {
  background: transparent !important;
  border: 1px solid var(--vf-line) !important;
  color: var(--vf-text) !important;
}
body.v-ui.v-face.v-service-finance :is(.btn-ghost, .v-btn-ghost) {
  background: transparent !important;
  border-color: var(--vf-line) !important;
  color: var(--vf-text-muted) !important;
}
body.v-ui.v-face.v-service-finance :is(
  .btn-submit,
  .btn-primary,
  .btn-outline,
  .btn-ghost,
  .v-btn-ghost,
  .si-btn,
  .poe-btn,
  .vip-btn
):focus-visible {
  outline: 2px solid var(--vf-accent);
  outline-offset: 2px;
}
body.v-ui.v-face.v-service-finance :is(
  .btn-submit,
  .btn-primary,
  .btn-outline,
  .btn-ghost,
  .v-btn-ghost,
  .si-btn,
  .poe-btn,
  .vip-btn
):disabled {
  opacity: .45 !important;
  cursor: not-allowed !important;
}
body.v-ui.v-face.v-service-finance :is(
  .si-seg,
  #po-order-owner-seg,
  .pai-source-type,
  .pai-seg
) {
  height: var(--v-control-height, 36px) !important;
  border: 1px solid var(--vf-line) !important;
  border-radius: var(--vf-r-sm) !important;
  background: var(--vf-panel) !important;
  overflow: hidden;
}
body.v-ui.v-face.v-service-finance :is(
  .si-seg,
  #po-order-owner-seg,
  .pai-source-type,
  .pai-seg
) button {
  color: var(--vf-text-muted) !important;
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
}
body.v-ui.v-face.v-service-finance :is(
  .si-seg,
  #po-order-owner-seg
) button.active,
body.v-ui.v-face.v-service-finance .pai-st-btn.pai-st-active,
body.v-ui.v-face.v-service-finance .pai-seg-btn.pai-seg-active {
  color: var(--vf-text) !important;
  background: var(--vf-accent-soft) !important;
}
body.v-ui.v-face.v-service-finance :is(
  .po-card,
  .poe-card
) {
  background: var(--vf-panel) !important;
  border: 1px solid var(--vf-line-soft) !important;
  border-radius: var(--vf-r) !important;
  box-shadow: none !important;
}
/* .po-kpi / .po-kpi-val overrides deleted 2026-08-02. They existed to drag a
   page-local KPI copy back onto the facelift; po-manager.js now emits the
   canonical .v-kpi-card / .v-kpi-value, which this file already styles above —
   the compensation was the duplicate presentation path, not the fix. */
body.v-ui.v-face.v-service-finance :is(
  .poe-panel,
  .vip-modal,
  .pm-attach-modal
) {
  color: var(--vf-text);
}
body.v-ui.v-face.v-service-finance .poipo-search-row {
  display: grid !important;
  grid-template-columns: minmax(220px, 1fr) minmax(180px, auto);
}
body.v-ui.v-face.v-service-finance #poipo-search-q {
  box-sizing: border-box;
  width: 100% !important;
  min-width: 0;
  min-height: var(--v-control-height, 36px);
}
@media (max-width: 720px) {
  body.v-ui.v-face.v-service-finance .poipo-search-row {
    grid-template-columns: 1fr;
  }
  body.v-ui.v-face.v-service-finance .v-service-modal {
    width: min(100%, calc(100vw - 24px)) !important;
    padding-inline: 20px !important;
  }
}
