/* # off-scale spacing inventory (PR-2, --owner-space-* migration)
   Every padding/margin/gap whose components are all on the published
   4/8/12/16/20/24/32/48 scale (or 0) now references --owner-space-N.
   The literals below remain because at least one component sits OFF the
   scale and snapping it to the nearest step would change the computed
   value — this migration is a visual no-op by contract. Grouped by value:

   - 2px  — micro-gaps between stacked label/value pairs (.owner-fact-grid
            > div, .owner-detail-row__label, .goal-card__target), the
            combobox search input's 2px 0 padding, and the -2px optical
            centering on the combobox check glyph.
   - 3px  — glyph/pill geometry: .owner-table__variance-glyph margin-right,
            .goal-card__status 3px 10px padding.
   - 6px  — icon↔text micro-gap delmar uses inside controls (.owner-button,
            .owner-field, .owner-badge, .owner-progress, .goal-card__prior,
            .owner-filter-pill__trigger, .dmv-combobox-chip), tight paddings
            (stack-narrow td 6px 0, trigger-count 0 6px, panel-actions
            6px 12px, filter-pill trigger 6px 14px), and the -6px band-title
            margin that nets the 12px flex gap down to 6px.
   - 9px  — .dmv-combobox__input 9px 12px: 9+9+borders lands the field at
            the same 40px height as .owner-input.
   - 10px — the delmar table-cell rhythm (10px 12px on .owner-table th/td
            and the drilldown span mirror), control paddings (10px 16px
            buttons/detail-row--padded, 10px 12px pills, 10px 14px tabs +
            combobox empty), 10px gaps (.owner-sidebar-nav,
            .owner-fact-list, .goal-card, combobox option), tooltip padding
            (8px 10px), goal-card targets 10px 0 0, chip 4px 10px 4px 12px.
   - 14px — header/notice paddings one step tighter than 16
            (.owner-table-card__header 14px 16px, .owner-notice 12px 14px,
            stack-narrow tr 12px 14px, .owner-metric-card 14px 16px,
            filter-pill option 8px 14px), .owner-meta-list 0 14px gap,
            .goal-card__targets 14px gap.
   - 18px — .goal-card 16px 18px padding (scorecard tile's slightly wider
            gutter).
   - 36px — .owner-date-field__display padding-right: 12px inset + 16px
            calendar icon + 8px breathing room. */

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
}

body {
  background: var(--owner-page-bg);
  color: var(--owner-text-primary);
  font-family: var(--font-dm-sans);
  font-feature-settings: "kern" 1;
  text-rendering: optimizeLegibility;
}

.antialiased {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select,
textarea {
  font: inherit;
}

button:not(:disabled),
[role="button"],
summary,
.owner-button,
.owner-pill {
  cursor: pointer;
}

button:disabled {
  cursor: not-allowed;
}

.owner-layout {
  background: var(--owner-page-bg);
  display: flex;
  height: 100vh;
  overflow: hidden;
}

.owner-sidebar-container {
  display: block;
  flex: 0 0 auto;
}

.owner-main-area {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

.owner-content-scroll {
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
}

.owner-content-shell {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-5);
  margin: 0 auto;
  /* 1440px = wide-dashboard sweet spot. Earlier value (1188px) was tuned
     for prose-style pages and left ~500px of empty side margin on
     1920+px monitors, forcing horizontal scroll on the data-heavy
     pages (Annual Forecast, Portfolio Data) instead of using available
     space. 1440px fits Forecast + Portfolio + Cash Flow tables without
     scroll on a typical 1920px screen, still avoids stretched prose
     on 4K. */
  max-width: 1440px;
  /* 32px / 24px (mobile) bottom padding mirrors delmar's owner-portal page
     pattern (`pb-4 lg:pb-5` outer + section content padding). 24px sides
     match `lg:px-6` exactly. The 1440px max-width is our deliberate +40
     over delmar's 1400px — see the comment above on table fit at 1920px. */
  padding: 0 var(--owner-space-6) var(--owner-space-7);
  width: 100%;
}

.owner-mobile-header {
  align-items: center;
  background: #ffffff;
  border-bottom: 1px solid var(--owner-card-border);
  display: none;
  flex: 0 0 56px;
  height: 56px;
  justify-content: space-between;
  padding: 0 var(--owner-space-4);
  position: sticky;
  top: 0;
  z-index: 100;
}

.owner-mobile-header__menu {
  align-items: center;
  background: transparent;
  border: 0;
  border-radius: 4px;
  color: var(--owner-text-secondary);
  display: inline-flex;
  height: 40px;
  justify-content: center;
  padding: 0;
  width: 40px;
}

.owner-mobile-header__menu:hover {
  background: var(--owner-hover-bg);
}

.owner-mobile-header__brand {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: 0.15em;
  line-height: 1;
}

.owner-mobile-header__brand span,
.owner-sidebar-wordmark span {
  color: var(--owner-accent-orange);
}

.owner-mobile-backdrop {
  background: rgba(0, 0, 0, 0.4);
  display: none;
  inset: 0;
  position: fixed;
  z-index: 1000;
}

html[data-mobile-sidebar-open="true"] .owner-mobile-backdrop {
  display: block;
}

.owner-sidebar-container {
  display: block;
  flex: 0 0 auto;
}

.owner-app-sidebar {
  background: var(--owner-sidebar);
  border-right: 1px solid var(--owner-sidebar-accent);
  color: var(--owner-sidebar-text);
  display: flex;
  flex-direction: column;
  height: 100%;
  position: relative;
  transition: width 200ms ease;
  width: 252px;
}

html[data-sidebar-collapsed="true"] .owner-app-sidebar {
  width: 64px;
}

.owner-sidebar-logo-row {
  align-items: center;
  border-bottom: 1px solid var(--owner-sidebar-border);
  display: flex;
  flex: 0 0 68px;
  height: 68px;
  justify-content: space-between;
  padding: 0 var(--owner-space-4);
}

html[data-sidebar-collapsed="true"] .owner-sidebar-logo-row {
  justify-content: center;
  padding: 0 var(--owner-space-2);
}

.owner-sidebar-logo-link {
  align-items: center;
  display: flex;
  gap: var(--owner-space-2);
  height: 36px;
  overflow: hidden;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-logo-link {
  justify-content: center;
}

.owner-sidebar-logo {
  /* Matches delmar/apps/owner-ui/components/ui/owner/app-sidebar.tsx:
     <Image width={126} height={36}>. Both axes are bounded so the
     wider combo logo (4:1) fills the width and the taller solo logo
     (3:2) fills the height — whichever constraint is tighter wins. */
  height: auto;
  max-height: 36px;
  max-width: 126px;
  width: auto;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-logo {
  display: none;
}

.owner-sidebar-wordmark {
  color: #ffffff;
  font-family: var(--font-kepler);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.15em;
  white-space: nowrap;
}

.owner-sidebar-wordmark--compact {
  display: none;
  letter-spacing: normal;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-wordmark:not(.owner-sidebar-wordmark--compact) {
  display: none;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-wordmark--compact {
  display: inline;
}

.owner-sidebar-logo-toggle {
  align-items: center;
  background: transparent;
  border: 0;
  border-radius: 4px;
  color: var(--owner-muted-icon);
  display: inline-flex;
  height: 24px;
  justify-content: center;
  padding: var(--owner-space-1);
  transition:
    background-color 150ms ease,
    color 150ms ease;
  width: 24px;
}

.owner-sidebar-logo-toggle:hover {
  background: rgba(255, 255, 255, 0.08);
  color: var(--owner-sidebar-text-active);
}

.owner-sidebar-logo-toggle svg {
  fill: currentColor;
  height: 14px;
  width: 14px;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-logo-toggle {
  display: none;
}

.owner-sidebar-edge-toggle {
  align-items: center;
  background: var(--owner-sidebar);
  border: 1px solid var(--owner-sidebar-border);
  border-radius: 999px;
  color: var(--owner-sidebar-text-active);
  display: inline-flex;
  height: 24px;
  justify-content: center;
  padding: 0;
  position: absolute;
  right: -12px;
  top: 68px;
  transform: translateY(-50%);
  width: 24px;
  z-index: 20;
}

.owner-sidebar-edge-toggle svg {
  fill: currentColor;
  height: 10px;
  transition: transform 200ms ease;
  width: 6px;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-edge-toggle svg {
  transform: rotate(180deg);
}

.owner-sidebar-nav {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  padding: var(--owner-space-4);
}

html[data-sidebar-collapsed="true"] .owner-sidebar-nav {
  gap: var(--owner-space-1);
  padding: var(--owner-space-2);
}

.owner-sidebar-section {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-1);
}

.owner-sidebar-section-label {
  color: var(--owner-sidebar-text);
  font-size: var(--owner-font-size-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: var(--owner-line-height-xs);
  padding: var(--owner-space-1) var(--owner-space-2);
  text-transform: uppercase;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-section-label {
  display: none;
}

/* A collapsed nav group (NavSection.collapsed): the label is a <details>
   summary with a chevron that turns when open. Adapted from delmar
   owner/collapsible-group.tsx — 13px chevron, -90° when closed, 11px
   uppercase label — translated to a native disclosure so it works with
   scripts blocked. In the icon-only sidebar the label hides like every
   section label but the chevron stays, so the group is still reachable. */
.owner-sidebar-section-summary {
  align-items: center;
  cursor: pointer;
  display: flex;
  gap: var(--owner-space-2);
  list-style: none;
  user-select: none;
}

.owner-sidebar-section-summary::-webkit-details-marker {
  display: none;
}

.owner-sidebar-section-summary:hover,
.owner-sidebar-section--collapsible[open] > .owner-sidebar-section-summary {
  color: var(--owner-sidebar-text-active);
}

.owner-sidebar-section-summary:focus-visible {
  border-radius: var(--owner-radius-pill);
  outline: 2px solid var(--owner-accent-orange);
  outline-offset: 2px;
}

.owner-sidebar-section-summary__chevron {
  fill: none;
  flex: 0 0 13px;
  height: 13px;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
  transform: rotate(-90deg);
  transition: transform 150ms ease;
  width: 13px;
}

.owner-sidebar-section--collapsible[open] > .owner-sidebar-section-summary .owner-sidebar-section-summary__chevron {
  transform: none;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-section-summary {
  display: flex;
  justify-content: center;
  padding: var(--owner-space-1);
}

html[data-sidebar-collapsed="true"] .owner-sidebar-section-summary__label {
  display: none;
}

/* One muted line under a collapsed group's items: what it is and why it
   sits apart. Hidden with the labels in the icon-only sidebar. */
.owner-sidebar-section-note {
  color: var(--owner-sidebar-text);
  font-size: var(--owner-font-size-xs);
  line-height: var(--owner-line-height-xs);
  margin: var(--owner-space-1) 0 0;
  opacity: 0.8;
  padding: 0 var(--owner-space-2);
}

html[data-sidebar-collapsed="true"] .owner-sidebar-section-note {
  display: none;
}

.owner-sidebar-item {
  align-items: center;
  border-radius: var(--owner-radius-pill);
  color: var(--owner-sidebar-text);
  display: flex;
  font-size: var(--owner-font-size-md);
  font-weight: 500;
  gap: var(--owner-space-2);
  height: 36px;
  line-height: var(--owner-line-height-md);
  max-height: 36px;
  min-height: 36px;
  min-width: 0;
  padding: var(--owner-space-2) var(--owner-space-3) var(--owner-space-2) var(--owner-space-2);
  transition:
    background-color 150ms ease,
    color 150ms ease;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-item {
  justify-content: center;
  padding: var(--owner-space-2);
}

.owner-sidebar-item:hover,
.owner-sidebar-item[aria-current="page"] {
  background: var(--owner-sidebar-accent);
  color: var(--owner-sidebar-text-active);
}

.owner-sidebar-item__icon {
  align-items: center;
  display: inline-flex;
  flex: 0 0 20px;
  justify-content: center;
}

.owner-icon {
  fill: none;
  height: 20px;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.5;
  width: 20px;
}

.owner-sidebar-item__label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-item__label {
  display: none;
}

/* Trailing count on a nav item (design review 4, P1-1): one number in the
   danger tone, hidden at zero by the template, so the sidebar says which
   scorecards missed this week without opening them (design spec §3).
   Same tokens as `.owner-badge--attention`, the lib's quiet small flag;
   min-width keeps single digits round. Hidden with the label in the
   icon-only sidebar. */
.owner-sidebar-item__count {
  background: var(--owner-error-bg);
  border: 1px solid var(--owner-error-border);
  border-radius: var(--owner-radius-pill);
  color: var(--owner-error-text-strong);
  flex: 0 0 auto;
  font-size: var(--owner-font-size-xs);
  font-weight: 600;
  line-height: var(--owner-line-height-xs);
  margin-left: auto;
  min-width: 20px;
  padding: 0 5px;
  text-align: center;
}

html[data-sidebar-collapsed="true"] .owner-sidebar-item__count {
  display: none;
}

/* `padding-bottom: 4px` is intentional and slightly different from
   delmar's PageHeader (which uses `pb-5` = 20px). Combined with the
   shell's `gap: 20px` between siblings, the visible separation from
   header bottom to first content section is 24px (4 + 20) — 4px more
   breathing room than delmar's 20px-from-pb-only. The diff is below
   the threshold a human notices and matching exactly would force a
   refactor of the shell-gap system. Don't "fix" the 4px without first
   removing the shell gap and rewriting every inter-section spacing
   consumer. */
.owner-page-header {
  align-items: flex-end;
  display: flex;
  justify-content: space-between;
  gap: var(--owner-space-6);
  padding: var(--owner-space-6) 0 var(--owner-space-1);
}

.owner-page-header__copy {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-2);
  max-width: 1140px;
}

.owner-page-header__eyebrow {
  color: var(--owner-text-muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 16px;
  margin: 0;
  text-transform: uppercase;
}

.owner-page-header__title {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-feature-settings: "lnum" 1, "pnum" 1;
  font-size: 28px;
  font-weight: 500;
  letter-spacing: -0.75px;
  line-height: 38px;
  margin: 0;
}

.owner-page-header__subtitle {
  color: var(--owner-text-muted);
  font-size: 16px;
  font-variation-settings: "opsz" 14;
  font-weight: 500;
  line-height: 24px;
  margin: 0;
  max-width: 760px;
}

.owner-page-header__actions {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--owner-space-3);
  justify-content: flex-end;
}

.owner-section {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-4);
}

.owner-section-title {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.4px;
  margin: 0;
}

.owner-section-subtitle {
  color: var(--owner-text-secondary);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  margin: 0;
  text-transform: uppercase;
}

/* Section band — wraps a floating <h2> + optional caption + content slot
   for "section-divider" headings that sit between cards rather than
   inside one. Pairs with `components/section_band.html`. The 24 px
   margin-top stacks on .owner-content-shell's 20 px flex gap to give
   ~44 px breathing room above the title — same rhythm Forecast's
   "Likely actual range" pioneered. The 12 px flex gap is the inter-
   child rhythm; the negative-margin override on the title below tightens
   the title→first-sibling pair to 6 px to match Forecast's tight
   title→caption aesthetic. */
/* Hairline horizontal rule using the card-border token. Consumer chooses
   the margin via inline style if 4 px above/below isn't right; default is
   what most existing per-app dividers were using. Render as `<hr class=
   "owner-divider">` (still semantic) or compose on any 1-px-height block. */
.owner-divider {
  background: var(--owner-card-border);
  border: 0;
  height: 1px;
  margin: var(--owner-space-1) 0;
}

.owner-section-band {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-3);
  margin-top: var(--owner-space-6);
}

/* Tighten the title→next-sibling pair from the band's 12 px flex gap
   down to 6 px effective (12 − 6) — matches Forecast's original
   `.forecast-band__title { margin-bottom: 6 px }`. Scoped via direct-
   child selector so .owner-section-title used outside a band (e.g.
   retention tab headings) keeps its base margin: 0. */
.owner-section-band > .owner-section-title {
  margin-bottom: -6px;
}

/* Title + inline actions (e.g. a notice chip) on one row. The negative
   title margin above is scoped to the direct-child case, so the heading
   wrapper re-applies the same tightening. */
.owner-section-band__heading {
  align-items: center;
  display: flex;
  gap: var(--owner-space-3);
  margin-bottom: -6px;
}

.owner-section-band__heading > .owner-section-title {
  margin-bottom: 0;
}

.owner-section-band__caption {
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-sm);
  line-height: 1.5;
  margin: 0;
}

/* Eyebrow utility — small uppercase muted label used above headings, in
   summary card titles, fact-grid <dt>s, and feedback card labels. The
   primary variant is 12px / 0.06em; the small variant is 11px / 0.08em
   for tighter contexts (compact summary cards, in-card labels).

   Existing component-scoped rules (.owner-page-header__eyebrow,
   .owner-fact-grid dt, .owner-summary-card__title,
   .retention-feedback__label) currently inline these values; new
   markup should compose .owner-eyebrow / .owner-eyebrow--small instead
   of adding more bespoke rules. */
.owner-eyebrow {
  color: var(--owner-text-muted);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  margin: 0;
  text-transform: uppercase;
}

.owner-eyebrow--small {
  font-size: var(--owner-font-size-xs);
  letter-spacing: 0.08em;
}

.owner-button {
  align-items: center;
  border-radius: 8px;
  display: inline-flex;
  font-size: var(--owner-font-size-md);
  font-weight: 500;
  gap: 6px;
  justify-content: center;
  line-height: var(--owner-line-height-md);
  min-height: 40px;
  padding: 10px 16px;
  transition:
    background-color 150ms ease,
    border-color 150ms ease,
    color 150ms ease;
  white-space: nowrap;
}

.owner-spinner {
  animation: owner-spin 0.8s linear infinite;
  display: none;
  height: 16px;
  width: 16px;
}

.owner-button--loading .owner-spinner {
  display: inline-block;
}

@keyframes owner-spin {
  to { transform: rotate(360deg); }
}

.owner-button--primary {
  background: var(--owner-sidebar);
  border: 1px solid var(--owner-sidebar);
  color: #ffffff;
}

.owner-button--primary:hover {
  background: var(--owner-sidebar-accent);
}

/* Disabled state. base.css already sets `cursor: not-allowed` globally,
   but nothing dimmed the button, so a disabled button was visually
   identical to an enabled one — the only cue was the pointer, which
   touch users never see. Applies to every variant. */
.owner-button:disabled {
  opacity: 0.6;
}

/* Accent variant — the high-emphasis primary. `--primary` is navy and
   reads as chrome; `--accent` is for the one action a page exists to
   complete. Audience is not the criterion: this was first written as
   "the guest-facing primary", which was wrong about its only consumer.
   Adapted from delmar/apps/owner-ui/components/ui/owner/marked-as-read-button.tsx
   (`bg-owner-accent-orange`, `hover:bg-owner-accent-orange/90`,
   `text-white`, `rounded-lg`).

   **Deviates from delmar on the fill shade, deliberately.** White on the
   raw `--owner-accent-orange` (#e75339) is 3.69:1 — below the 4.5:1 WCAG
   AA floor for normal-weight 16px text, and delmar's own 90% hover only
   reaches 4.43:1. Darkening the fill to 85% of the brand orange gives
   4.89:1 at rest and 6.00:1 on hover, both AA, while keeping white text
   and the darken-on-hover interaction delmar uses.

   Expressed as color-mix against the token rather than two new hexes, so
   a brand-palette refresh still propagates. Contrast is pinned by
   tests/test_button_contrast.py — if the accent token changes, that test
   fails rather than the button silently dropping below AA. */
.owner-button--accent {
  background: color-mix(in srgb, var(--owner-accent-orange) 85%, #000000);
  border: 1px solid color-mix(in srgb, var(--owner-accent-orange) 85%, #000000);
  color: #ffffff;
}

.owner-button--accent:hover {
  background: color-mix(in srgb, var(--owner-accent-orange) 75%, #000000);
  border-color: color-mix(in srgb, var(--owner-accent-orange) 75%, #000000);
}

.owner-button--secondary {
  background: var(--owner-input-bg);
  border: 1px solid var(--owner-input-border);
  color: var(--owner-text-primary);
}

.owner-button--secondary:hover {
  background: var(--owner-hover-bg);
}

/* Destructive variant — for delete / cancel / remove actions where the
   button itself is the visual cue that the action is irreversible.
   Reach for `--secondary` for routine cancels and reserve this for
   "are you sure?" confirmations. */
.owner-button--destructive {
  background: var(--owner-error-text);
  border: 1px solid var(--owner-error-text);
  color: #ffffff;
}

.owner-button--destructive:hover {
  background: color-mix(in srgb, var(--owner-error-text) 88%, black);
  border-color: color-mix(in srgb, var(--owner-error-text) 88%, black);
}

.owner-button--destructive:focus-visible {
  box-shadow: 0 0 0 3px var(--owner-error-bg);
  outline: none;
}

.owner-form {
  align-items: end;
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-card);
  display: grid;
  gap: var(--owner-space-4);
  padding: var(--owner-space-4);
}

.owner-form--stacked {
  align-items: stretch;
  box-shadow: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}

.owner-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.owner-field__label {
  color: var(--owner-text-primary);
  font-size: var(--owner-font-size-md);
  font-weight: 600;
  line-height: var(--owner-line-height-md);
}

.owner-input,
.owner-select {
  background: var(--owner-input-bg);
  border: 1px solid var(--owner-input-border);
  border-radius: 8px;
  color: var(--owner-text-primary);
  height: 40px;
  outline: none;
  padding: 0 var(--owner-space-3);
  transition:
    border-color 150ms ease,
    box-shadow 150ms ease;
  width: 100%;
}

.owner-input:focus,
.owner-select:focus {
  border-color: var(--owner-accent-blue);
  /* Match delmar's `focus:ring-owner-accent-blue/20` — derive the ring
     from the brand token via color-mix so a palette refresh propagates. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--owner-accent-blue) 20%, transparent);
}

.owner-date-field {
  position: relative;
  width: 100%;
}

/* Calendar icon — painted via background-image on the visible
 * display input. SVG mirrors the lucide / delmar calendar glyph
 * at owner-text-secondary.
 */
.owner-date-field__display {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236A6A6A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='4' width='18' height='18' rx='2' ry='2'/><line x1='16' y1='2' x2='16' y2='6'/><line x1='8' y1='2' x2='8' y2='6'/><line x1='3' y1='10' x2='21' y2='10'/></svg>");
  background-position: right 12px center;
  background-repeat: no-repeat;
  background-size: 16px 16px;
  cursor: pointer;
  padding-right: 36px;
}

.metric-grid {
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-card);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  overflow: hidden;
}

.metric-grid--hero {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.metric-grid--hero-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Restore right border on card 2 (the --hero 2-col rule below hides it
   when both modifiers are present in the source) and drop it on card 3
   (last in the 3-col row). */
.metric-grid--hero-3 .owner-stat-card:nth-child(2n) {
  border-right: 1px solid var(--owner-card-border);
}

.metric-grid--hero-3 .owner-stat-card:nth-child(3n),
.metric-grid--hero-3 .owner-stat-card:last-child {
  border-right: 0;
}

/* Five-across KPI strip for 5-card pages (Portfolio Homes) — avoids the
   3x2 hanging-cell layout. Collapses through the shared .metric-grid
   responsive rules (2-col at 1180px, 1-col at 640px); the row-end border
   rule is scoped to the 5-col range so it can't fight the 2-col rule. */
.metric-grid--hero-5 {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

.metric-grid--hero-5 .owner-stat-card__value {
  font-size: 26px;
  line-height: 32px;
}

@media (min-width: 1181px) {
  /* The global :nth-child(4n) rule (4-col base grid) would remove card
     4's divider mid-row in a 5-col strip — restore it, then drop the
     border at the real row end. */
  .metric-grid--hero-5 .owner-stat-card:nth-child(4n) {
    border-right: 1px solid var(--owner-card-border);
  }

  .metric-grid--hero-5 .owner-stat-card:nth-child(5n),
  .metric-grid--hero-5 .owner-stat-card:last-child {
    border-right: 0;
  }
}

.owner-stat-card {
  background: #ffffff;
  border-right: 1px solid var(--owner-card-border);
  color: var(--owner-text-primary);
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--owner-space-3);
  min-width: 0;
  padding: var(--owner-space-4);
}

.owner-stat-card:nth-child(4n),
.owner-stat-card:last-child {
  border-right: 0;
}

.metric-grid--hero .owner-stat-card:nth-child(2n) {
  border-right: 0;
}

.owner-stat-card__icon {
  align-items: center;
  color: var(--owner-accent-orange);
  display: inline-flex;
  height: 20px;
  justify-content: center;
  width: 20px;
}

.owner-stat-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-1);
}

.owner-stat-card__value {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-feature-settings: "lnum" 1, "pnum" 1;
  /* 24px / 32px matches delmar StatCard's `text-2xl leading-8`. Hero
     variants push 32px / 38px in the .metric-grid--hero override below
     for top-of-page KPI strips. */
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.72px;
  line-height: 32px;
  overflow-wrap: anywhere;
}

/* Both --hero (2-col) and --hero-3 (3-col) page-hero strips use the
   bigger value type. Without --hero-3 in the selector, hero-3 cards
   silently fell back to the standard 24px and the comment above
   ("hero-3 [...] same large type") drifted from reality. */
.metric-grid--hero .owner-stat-card__value,
.metric-grid--hero-3 .owner-stat-card__value {
  font-size: 32px;
  line-height: 38px;
}

.owner-stat-card__label {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-md);
  font-variation-settings: "opsz" 14;
  line-height: 18px;
}

.owner-stat-card__detail {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-sm);
  font-variation-settings: "opsz" 14;
  line-height: var(--owner-line-height-sm);
}

.owner-stat-card--success .owner-stat-card__value {
  color: var(--owner-text-primary);
}

.owner-stat-card--warning {
  background: var(--owner-warning-bg);
}

/* Error tone colors the VALUE only — the card equivalent of
   .owner-table__value--negative, so a negative currency KPI carries the
   same negative ink as its table cells (Portfolio Homes round-2 handoff). */
.owner-stat-card--error .owner-stat-card__value {
  color: var(--owner-error-text);
}

.panel,
.owner-card,
.owner-empty-state {
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-card);
  overflow: hidden;
}

/* Tone accent — adds a 3px left border driven by a brand token. Compose
   with .owner-card (or any card-shaped surface) when the card needs a
   directional cue (positive feedback, blocking issue, info callout)
   without going full-tone-fill via .owner-notice. */
.owner-card--accent-success { border-left: 3px solid var(--owner-success-text); }
.owner-card--accent-warning { border-left: 3px solid var(--owner-warning-text); }
.owner-card--accent-error { border-left: 3px solid var(--owner-error-text); }
.owner-card--accent-info { border-left: 3px solid var(--owner-highlight-text); }
.owner-card--accent-neutral { border-left: 3px solid var(--owner-card-border); }

.panel {
  padding: var(--owner-space-6);
}

.panel__title {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-feature-settings: "lnum" 1, "pnum" 1;
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.6px;
  line-height: 28px;
  margin: 0 0 var(--owner-space-3);
}

.owner-table-card {
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-card);
  /* min-width: 0 is load-bearing, not defensive. The card is a flex
     item of .owner-content-shell (flex column), where min-width
     defaults to `auto` = the content's min-content width. A table wider
     than the shell (the 26-week trend grid is ~3000px of nowrap
     columns) then propagates its width UP through the card, past the
     1440px shell, and .owner-content-scroll becomes the horizontal
     scroller (its overflow-y: auto computes overflow-x to auto) — the
     whole page scrolls right and the sticky pinned columns float free
     of the table. With min-width: 0 the card stays at shell width and
     .owner-table-card__scroll does the scrolling, as designed. */
  min-width: 0;
  overflow: hidden;
}

.owner-table-card__header {
  align-items: center;
  border-bottom: 1px solid var(--owner-card-border);
  display: flex;
  justify-content: space-between;
  min-height: 56px;
  padding: 14px 16px;
}

/* Narrow viewports: the side-by-side title/subtitle row squeezes the
   title into an ~80px column that wraps 2-3 lines — stack them instead
   (round-3 handoff §B; generalizes the per-app stacking classes the
   forecast/pacing cards already opted into). */
@media (max-width: 760px) {
  .owner-table-card__header {
    align-items: stretch;
    flex-direction: column;
    min-height: 0;
  }
}

.owner-table-card__title {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.3px;
  line-height: 28px;
  margin: 0;
}

.owner-table-card__scroll {
  overflow-x: auto;
  /* position: relative is load-bearing: absolutely-positioned descendants
     (`.owner-visually-hidden` sr-only spans inside week cells) otherwise
     resolve their containing block past every overflow clip to the
     initial containing block, and their static positions — thousands of
     pixels into the scrolled table — stretch the PAGE's scroll width so
     the whole document pans right past the card. With the scroll
     container positioned, it becomes their containing block and its
     overflow clips them like everything else. */
  position: relative;
}

/* Scroll shell (design review 3, P1-3): wraps a horizontal (and
   optionally bounded vertical) scroller and paints an edge fade that
   says "more this way" — nothing else signalled that 22 more weeks or
   30 more rows existed. The right fade is always drawn (a scroller that
   does not overflow simply fades its own last pixels into white); the
   bottom fade is opt-in with `--bounded`, paired with
   `.owner-table-card__scroll--bounded` on the child. Fade depth is
   --owner-space-6 (24px). Pointer events
   pass through; the fades sit above the sticky cells (z 2-4). */
.owner-scroll-shell {
  position: relative;
}

.owner-scroll-shell::after {
  background: linear-gradient(to left, #ffffff, transparent);
  bottom: 0;
  content: "";
  pointer-events: none;
  position: absolute;
  right: 0;
  top: 0;
  width: var(--owner-space-6);
  z-index: 5;
}

.owner-scroll-shell--bounded::before {
  background: linear-gradient(to top, #ffffff, transparent);
  bottom: 0;
  content: "";
  height: var(--owner-space-6);
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  z-index: 5;
}

/* Opt-in bounded variant: gives the container a vertical scrollbox so a
   sticky thead has something to stick within. Without it the PAGE owns
   vertical scroll, and on a long table (Portfolio Success runs 42 rows)
   the column headings are gone by mid-table with every cell below an
   unlabeled number — `position: sticky` on the thead is a no-op when
   the scroll container has no vertical overflow. A modifier rather than
   the default so existing single-axis consumers keep page-owned
   vertical scroll. */
.owner-table-card__scroll--bounded {
  /* Design review 4, P0-3: the cap used to be `min(72vh, 720px)`, so on a
     tall monitor a 21-row grid stopped at ~620px with the page empty
     below it and the last row cut mid-cell. The bound now follows the
     viewport minus everything above the scroller (`--scroll-shell-offset`,
     default 240px: shell padding + page header + card header; the app
     overrides it on the shell when its chrome is taller). `max-height`
     only bites when the content is taller, so a grid that fits renders
     whole with no inner scrollbar. `100vh` first for browsers without
     `dvh`; `scrollbar-gutter: stable` so a scrollbar appearing never
     shifts the pinned columns. Not an `--owner-*` token: a layout offset
     is per-element geometry (like `--pinned-lead-offset`), not a theme
     value, and the token gate rejects unpublished `--owner-*` names. */
  max-height: calc(100vh - var(--scroll-shell-offset, 240px));
  max-height: calc(100dvh - var(--scroll-shell-offset, 240px));
  overflow: auto;
  scrollbar-gutter: stable;
}

/* A bounded scroller must be reachable and operable by keyboard: give
   the element `tabindex="0"`, `role="region"` and an `aria-label` at
   the call site (a scroll container with no focusable descendants is
   skipped by sequential focus in Safari, so its clipped content is
   unreachable). This draws the focus treatment once for every adopter. */
.owner-table-card__scroll--bounded:focus-visible {
  outline: 2px solid var(--owner-accent-orange);
  outline-offset: -2px;
}

.owner-table {
  border-collapse: collapse;
  color: var(--owner-text-primary);
  font-size: var(--owner-font-size-md);
  line-height: var(--owner-line-height-md);
  min-width: 720px;
  width: 100%;
}

.owner-table th,
.owner-table td {
  border-bottom: 1px solid var(--owner-table-border);
  padding: 10px 12px;
  text-align: left;
  vertical-align: top;
}

/* Header rows get a slightly darker rule than body rows so the <thead> reads as
   a distinct band (per the internal-tools design system / delmar owner tables). */
.owner-table thead th {
  border-bottom-color: var(--owner-table-header-border);
}

.owner-table th {
  color: var(--owner-text-secondary);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 16px;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Row headers that hold DATA (a date, a house name, a metric value) rather
   than a column label: keep the <th scope="row"> for table a11y but render
   like a body cell. Promoted from two duplicated dashboards rules
   (Portfolio Data aggregates + Weekly Pacing pace card). */
.owner-table th.owner-table__rowhead--data {
  color: var(--owner-text-primary);
  font-size: var(--owner-font-size-md);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

.owner-table tbody tr:hover {
  background: var(--owner-hover-bg-subtle);
}

/* Red-flag row tint for entities needing attention; pairs with the
   .owner-badge--attention capsule (Portfolio Homes redesign handoff). */
.owner-table tbody tr.owner-table__row--attention {
  background: var(--owner-row-attention-bg);
}

/* Error ink steps to the -strong token on tinted attention rows:
   --owner-error-text on the tint composites to 4.47:1 at 14px —
   under the AA 4.5:1 floor — while -strong measures 5.99:1 there. */
.owner-table tbody tr.owner-table__row--attention .owner-text--error,
.owner-table tbody tr.owner-table__row--attention .owner-table__value--negative {
  color: var(--owner-error-text-strong);
}

.owner-table tbody tr:last-child td,
.owner-table tfoot tr:last-child td {
  border-bottom: 0;
}

.owner-table tfoot td {
  background: var(--owner-total-bg);
  color: var(--owner-total-text);
  font-weight: 700;
}

/* Specificity note: this selector is intentionally tag-qualified so it
   beats the lib's own `.owner-table th, .owner-table td { text-align:
   left }` rule above (both have specificity 0,1,1; this rule wins by
   appearing later in source). A bare `.owner-table__value` selector
   (specificity 0,1,0) silently loses to the td rule, making the class
   fail to deliver what its name promises — numeric values in `<td>`s
   render left-aligned despite carrying the class. The same selector
   form also wins for `<span class="owner-table__value">` and similar
   non-table consumers, since the bare class still matches them and the
   tag-qualified form simply doesn't apply. */
.owner-table__value,
.owner-table th.owner-table__value,
.owner-table td.owner-table__value {
  font-feature-settings: "tnum" 1, "lnum" 1;
  text-align: right;
  white-space: nowrap;
}

.owner-table__value--positive {
  color: var(--owner-success-text);
}

.owner-table__value--negative {
  color: var(--owner-error-text);
}

.owner-table__value--muted {
  color: var(--owner-text-muted);
}

/* Finance variance cell — a directional glyph + the accounting value (rendered
   by the components/variance_cell.html `variance_cell()` macro). The ▲/▼ glyph
   encodes favorable/unfavorable by SHAPE so the cue survives without color
   (it is aria-hidden — the value's accounting sign + column context carry the
   meaning for screen readers). Tone color is the composed
   .owner-table__value--{positive,negative} class.
   Adapted from delmar owner-ui detail-table.tsx variance variants. */
.owner-table__variance-glyph {
  font-size: 0.82em;
  margin-right: 3px;
}

/* Scorecard status cell — holds a Hit/Miss text badge (scorecard_status()
   macro) or a muted dash when a row isn't yet scoreable. */
.owner-scorecard-status {
  white-space: nowrap;
}

/* Sortable column header — a clickable header link that sets the server sort
   (?sort=&dir=, see the sortable_th() macro). The active column shows a ▲/▼ in
   the brand accent; inactive columns show a faint ↕ affordance. aria-sort on
   the <th> announces the state. Server-sorted: cache-friendly + printable. */
.owner-table__sortable {
  cursor: pointer;
}

.owner-table__sort {
  align-items: center;
  color: inherit;
  display: inline-flex;
  gap: var(--owner-space-1);
  text-decoration: none;
}

.owner-table__sort:hover {
  color: var(--owner-accent-orange);
}

.owner-table__sort-arrow {
  color: var(--owner-text-muted);
  font-size: 0.8em;
}

.owner-table__sort--active .owner-table__sort-arrow {
  color: var(--owner-accent-orange);
}

.owner-table-card__subtitle {
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-sm);
  margin: var(--owner-space-1) 0 0;
}

.owner-table--wide th,
.owner-table--wide td {
  white-space: nowrap;
}

.owner-table--wide thead tr:first-child th[scope="colgroup"] {
  border-right: 1px solid var(--owner-table-border);
  text-align: center;
}

.owner-table__row--aggregate td {
  background: var(--owner-surface-subtle);
  font-weight: 600;
}

/* Pinned lead block — the first N columns stay put while the rest of the
   row scrolls horizontally. Nearest precedent was
   `.owner-tabular-drilldown__column-header`, but that pins a header to
   the TOP; no sticky *column* existed in this lib or in delmar.

   Built for the weekly-trend grid, where the metric name, its owner, its
   goal and its latest value must stay readable while history scrolls —
   on a screen-shared call everyone rides the driver's scroll position,
   so losing the row label loses the room.

   Each pinned cell sets BOTH `--pinned-lead-offset` (its `left`) and
   `--pinned-lead-width` (its own width), and the consumer must derive
   the offsets from the widths — see `PinnedColumn` in the scorecards
   weekly-trend service. They are not independent: `.owner-table` is
   auto-layout and `.owner-table--wide` sets `white-space: nowrap`, so
   content decides a column's real width. If a cell renders wider than
   the width declared here, the NEXT column freezes at an offset that
   lands inside it and paints over it mid-scroll. The width is therefore
   enforced, and over-long content ellipsises — put the full text in a
   `title` so nothing is lost. */
/* Sticky and `border-collapse: collapse` do not mix in Chromium: with
   collapsed borders the header cells are painted as part of the table
   box, so a stuck thead leaves GHOST COPIES of the header row down the
   body as you scroll (observed live on Portfolio Success at 26 weeks —
   dozens of interleaved METRIC/OWNER/… rows). Separate borders make
   each cell own its paint, which is what position: sticky needs. The
   visual is unchanged: these tables only ever set border-BOTTOM on
   cells, so there are no adjacent borders to double. */
.owner-table--pinned-lead {
  border-collapse: separate;
  border-spacing: 0;
}

.owner-table--pinned-lead th[data-pinned],
.owner-table--pinned-lead td[data-pinned] {
  /* Must be opaque, and must match whatever the card paints behind the
     table — otherwise scrolling week columns show through the frozen
     cells. `.owner-table-card` uses this literal directly; there is no
     published surface token for it. */
  background: #ffffff;
  /* The declared width must be the BORDER-box width, because the offset
     of the next column is measured from this column's outer edge. Under
     the default content-box these cells render `width + 24px` of padding
     — measured at 224/124/120/104/104/100 against offsets of
     0/200/300/396/476/556 — so every frozen boundary drifts a little
     further left than the column it is supposed to follow, and they
     overlap progressively as you scroll. */
  box-sizing: border-box;
  left: var(--pinned-lead-offset, 0);
  /* min and max both, so the declared width holds in either direction:
     `width` alone is a hint a nowrap cell wins against, and a short
     column would otherwise shrink below it. */
  max-width: var(--pinned-lead-width);
  min-width: var(--pinned-lead-width);
  overflow: hidden;
  position: sticky;
  text-overflow: ellipsis;
  z-index: 2;
}

/* The whole header row sticks to the top of the scroll container, so
   week labels survive vertical scrolling inside
   `.owner-table-card__scroll--bounded` (a no-op scrollport-wise when
   the container is unbounded). The background must be opaque for the
   same reason as the pinned cells: body rows scroll UNDER the header.
   With `border-collapse: separate` (above) the th's own bottom border
   travels with the stuck cell, so no box-shadow substitute is needed. */
.owner-table--pinned-lead thead th {
  background: #ffffff;
  position: sticky;
  top: 0;
  z-index: 3;
}

.owner-table--pinned-lead thead th[data-pinned] {
  /* The corner cells stick on BOTH axes (left from the pinned rule, top
     from the thead rule) and must win over the singly-stuck week
     headers beside them and body cells below them. */
  z-index: 4;
}

.owner-table--pinned-lead [data-pinned="last"] {
  box-shadow: 6px 0 6px -4px rgb(15 34 56 / 12%);
}

/* Opt-in two-line label. A frozen column is expensive — every pixel it
   takes is a pixel of scrolling content nobody can see — so a long label
   buys its room vertically instead of horizontally. Clamped at two lines
   so one verbose row cannot set the height of the whole table.

   The cell only relaxes `white-space`; the clamp goes on an inner
   `.owner-pinned-lead__clamp` span and NEVER on the `th`/`td` itself.
   `display: -webkit-box` on the cell would replace its `table-cell`
   display, and CSS table fix-up then wraps it in an ANONYMOUS table
   cell — leaving `position: sticky`, the offset, the width clamp and
   the opaque background on an inner box that is no longer the cell box,
   which silently breaks the pinned geometry this primitive exists to
   enforce. See https://www.w3.org/TR/css-tables-3/#table-fixup. */
.owner-table--pinned-lead [data-pinned-wrap] {
  white-space: normal;
}

/* `-webkit-line-clamp` is the only cross-browser way to ellipsise at a
   line count; it is unprefixed-equivalent everywhere this ships and
   degrades to a plain two-line clip if the box model is unsupported. */
.owner-pinned-lead__clamp {
  -webkit-box-orient: vertical;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-height: var(--owner-line-height-sm, 1.35);
  overflow: hidden;
}

/* Below the shell's comfortable width a frozen lead block wide enough to
   be useful leaves no room for the columns it exists to contextualise.
   Release the pin and let the whole table scroll as one — a horizontal
   scroll is recoverable; a viewport full of frozen columns is not. */
@media (max-width: 1180px) {
  .owner-table--pinned-lead th[data-pinned],
  .owner-table--pinned-lead td[data-pinned] {
    max-width: none;
    position: static;
  }

  /* Header cells release the horizontal pin with everyone else but keep
     sticking vertically — otherwise the lead headings scroll away while
     the week headings beside them stay put. */
  .owner-table--pinned-lead thead th[data-pinned] {
    left: auto;
    position: sticky;
  }

  .owner-table--pinned-lead [data-pinned="last"] {
    box-shadow: none;
  }
}

/* Phone-width tables (design review 3, P0-3 / P1-8). Two opt-ins that
   pair with `.owner-table--pinned-lead`:

   `.owner-table__col--desktop-only` hides a column under 760px — put it
   on the th AND every td of the column. For a wide grid the phone
   shows the identity column plus the measured values; owner / goal /
   channel fold into the identity cell as a muted line (consumer's
   markup), so nothing is lost, only re-stacked.

   `data-pinned-lead` marks the ONE lead column that stays frozen on a
   phone after the 1180px release above has unpinned the rest: the
   metric (or record identity) at `--pinned-lead-narrow-width` (44vw by
   default) while the value columns scroll beside it. Two-line clamp via
   `.owner-pinned-lead__clamp` as on desktop. Same opaque background
   rule as the desktop pin, for the same reason. */
@media (max-width: 760px) {
  .owner-table__col--desktop-only {
    display: none;
  }

  .owner-table--pinned-lead th[data-pinned-lead],
  .owner-table--pinned-lead td[data-pinned-lead] {
    background: #ffffff;
    left: 0;
    max-width: var(--pinned-lead-narrow-width, 44vw);
    min-width: var(--pinned-lead-narrow-width, 44vw);
    position: sticky;
    z-index: 2;
  }

  /* `left` is restated here on purpose: the 1180px release above sets
     `left: auto` on `thead th[data-pinned]` at this same specificity,
     and the lead heading carries both attributes — without it the body
     cells stayed pinned while the Metric heading scrolled away. */
  .owner-table--pinned-lead thead th[data-pinned-lead] {
    left: 0;
    z-index: 4;
  }
}

/* Screen-reader-only text. Content that a visual treatment carries — a
   dotted rule, a color, an icon — still needs words for anyone who
   cannot see the treatment. Same recipe as the `--stack-narrow` thead
   below, which predates this and should be folded in if it moves. */
.owner-visually-hidden {
  /* border and padding are zeroed deliberately: clip-path only clips the
     border box, so inherited padding on a table cell or list item still
     occupies layout and can leave a visible sliver. Matches the
     `--stack-narrow` thead recipe below, which this generalises. */
  border: 0;
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

/* Sparkline column — one small SVG per row carrying the trend shape,
   inside the pinned block so it survives horizontal scrolling. Replaces
   per-cell bars, which never read as a shape and scrolled away exactly
   when history was being read.

   Deliberately not `components/svg_line_chart.html`: that draws one
   series against a shared, labelled axis, and these rows span $350,000
   to 0.4% — there is no shared axis to draw against. Each spark is
   scaled within its own row and carries no axis at all. */
/* No width here on purpose: the SVG carries its own `width` attribute
   and sizes the cell in auto layout, and inside a pinned block the
   column geometry owns it. A width declared here that disagreed with the
   pinned width would be silently overridden by that rule's min/max. */
.owner-spark-cell {
  padding-right: var(--owner-space-3);
}

.owner-spark {
  display: block;
  overflow: visible;
}

.owner-spark__line {
  fill: none;
  stroke: var(--owner-text-muted);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.25;
}

/* A run whose weeks were reconstructed after the fact rather than
   captured live. Dashed rather than broken: the two runs share their
   boundary point, so the shape survives and only its confidence is
   qualified. Breaking would strand a lone newest point drawing nothing
   whenever the flip lands at the end of the window, which is exactly
   where a freshly-backfilled archive puts it. */
.owner-spark__line--reconstructed {
  stroke-dasharray: 2 2;
}

/* The newest point, so the eye lands on where the row is now. Navy
   (the chart Plan token), not the accent orange — red-orange now
   collides with the per-week verdict tinting, where red means MISS. */
.owner-spark__last {
  fill: var(--owner-chart-plan);
}

/* A week standing alone between two gaps: hollow, so it reads as a
   reading that exists without claiming the line that does not. */
.owner-spark__lone {
  fill: none;
  stroke: var(--owner-text-muted);
  stroke-width: 1;
}

/* Area band row — a filled separator inside the single scroll
   container. Separate cards per area would mean separate scrollbars
   that desynchronise as you scroll history. */
.owner-table__row--band th {
  background: var(--owner-surface-subtle);
  color: var(--owner-text-primary);
  font-size: var(--owner-font-size-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-align: left;
  text-transform: uppercase;
}

/* The band cell spans every column, so in a horizontally scrolling
   table its label scrolled off with the first screen of columns and the
   group heading was gone by the time you reached older history. The
   cell stays full-width (its fill must); the LABEL pins instead: wrap
   it in this span and it holds the left edge of the scrollport as the
   row scrolls beneath. `left` equals the cell's own horizontal padding
   so the pinned and at-rest positions coincide. Inline-block so sticky
   has a box to move; a bare inline does not stick. */
.owner-table__band-label {
  display: inline-block;
  left: var(--owner-space-3);
  position: sticky;
}

/* Mobile stacked-table opt-in. Tables that have many columns and
   genuine mobile use can compose `.owner-table--stack-narrow`; below
   760px the table flattens to one card per row, with each cell
   showing its column label via `data-label="..."` on the <td>.
   The thead is hidden on narrow viewports — the data-label callouts
   replace it.

   Consumers must set `data-label` on every <td> for the labels to
   appear. Tables without data-labels render as plain stacked rows
   (still useful — values are visible — but lose the column key). */
@media (max-width: 760px) {
  .owner-table--stack-narrow,
  .owner-table--stack-narrow tbody,
  .owner-table--stack-narrow tr,
  .owner-table--stack-narrow td,
  .owner-table--stack-narrow th {
    display: block;
    min-width: 0;
    width: auto;
  }

  /* Visually hide the thead but KEEP it in the accessibility tree —
     display: none would remove the column headers entirely, and the
     data-label ::before callouts are presentational (PR #294 review).
     Consumers must also add explicit ARIA table roles (role="table" /
     rowgroup / row / columnheader / rowheader / cell) on stacked
     tables: the display: block overrides below strip the native table
     semantics in most engines, and the roles restore the header/cell
     association programmatically. */
  .owner-table--stack-narrow thead {
    border: 0;
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    white-space: nowrap;
    width: 1px;
  }

  .owner-table--stack-narrow {
    min-width: 0;
  }

  .owner-table--stack-narrow tr {
    border: 1px solid var(--owner-card-border);
    border-radius: 8px;
    margin-bottom: var(--owner-space-3);
    padding: 12px 14px;
  }

  .owner-table--stack-narrow tbody tr:last-child td,
  .owner-table--stack-narrow tbody tr:hover {
    background: transparent;
  }

  .owner-table--stack-narrow td,
  .owner-table--stack-narrow th[scope="row"] {
    align-items: baseline;
    border: 0;
    display: flex;
    gap: var(--owner-space-3);
    justify-content: space-between;
    padding: 6px 0;
    text-align: right;
    white-space: normal;
  }

  .owner-table--stack-narrow td[data-label]::before,
  .owner-table--stack-narrow th[scope="row"][data-label]::before {
    color: var(--owner-text-muted);
    content: attr(data-label);
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-align: left;
    text-transform: uppercase;
  }
}

.owner-details {
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-card);
  overflow: hidden;
}

.owner-details__summary {
  align-items: center;
  color: var(--owner-text-primary);
  cursor: pointer;
  display: grid;
  font-size: 15px;
  font-weight: 600;
  gap: var(--owner-space-3);
  grid-template-columns: minmax(0, 1fr) minmax(120px, max-content) 18px;
  line-height: 22px;
  list-style: none;
  padding: var(--owner-space-4);
}

.owner-details__summary > span:first-child {
  min-width: 0;
}

.owner-details__summary > span:nth-child(2) {
  font-feature-settings: "tnum" 1, "lnum" 1;
  text-align: right;
  white-space: nowrap;
}

.owner-details__summary::-webkit-details-marker {
  display: none;
}

.owner-details__summary::after {
  color: var(--owner-text-muted);
  content: "›";
  display: inline-block;
  font-size: 18px;
  font-weight: 700;
  justify-self: end;
  line-height: 1;
  /* Rotation uses the standalone `rotate` property (CSS Transforms L2)
     instead of `transform: rotate(...)` so any app-side override that
     uses `transform` (e.g. `translateY(-50%)` for absolute-positioned
     chevrons) composes cleanly without clobbering the flip. The
     explicit `rotate: 0deg` initial value gives the transition a
     concrete from-state so every browser animates the flip. */
  rotate: 0deg;
  transition: rotate 150ms ease;
}

/* Use child combinator (>) so the [open]-state styling targets only the
   IMMEDIATE summary, not every nested .owner-details__summary underneath
   an open ancestor. Without `>`, a nested <details> (e.g. Cash Flow's
   "Adjustments to reconcile…" inside an open "Operating Activities") had
   its own chevron locked at 90deg whenever the outer was open —
   toggling the inner produced no visible change because the descendant-
   combinator rule kept matching the inner summary regardless of the
   inner [open] state. The border-bottom rule has the same shape and
   gets the same fix to stay consistent. */
.owner-details[open] > .owner-details__summary {
  border-bottom: 1px solid var(--owner-card-border);
}

.owner-details[open] > .owner-details__summary::after {
  rotate: 90deg;
}

/* Body slot of a disclosure: the content under the summary. */
.owner-details__body {
  padding: 0 var(--owner-space-4) var(--owner-space-4);
}

/* Inline disclosure (design review 3, P1-1 / P1-7): a one-line summary
   with the lib chevron and no card chrome, for definitions and
   methodology that belong beside content rather than in a card of their
   own — the trend grid's "How to read this grid", a scorecard page's
   "How these numbers are computed". Same element, same open/closed
   chevron; only the surface changes. */
.owner-details--inline {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}

.owner-details--inline > .owner-details__summary {
  color: var(--owner-text-secondary);
  display: inline-grid;
  font-size: var(--owner-font-size-sm);
  font-weight: 600;
  grid-template-columns: max-content 14px;
  line-height: var(--owner-line-height-sm);
  padding: var(--owner-space-1) 0;
}

.owner-details--inline > .owner-details__summary::after {
  font-size: var(--owner-font-size-md);
}

.owner-details--inline[open] > .owner-details__summary {
  border-bottom: 0;
}

.owner-details--inline > .owner-details__body {
  padding: var(--owner-space-2) 0 0;
}

/* Nested <details> shouldn't render as another full card — they should
   look like collapsible sub-rows inside their parent card. The parent
   keeps the border/shadow/rounded corners; the children become flat,
   hairline-divided rows. Consumers add their own depth-based indent
   on the inner summary class. */
.owner-details .owner-details {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}

.owner-details .owner-details > .owner-details__summary {
  border-top: 1px solid var(--owner-card-border);
  font-size: var(--owner-font-size-md);
  font-weight: 500;
}

.owner-details .owner-details > .owner-details__summary:hover {
  background: var(--owner-hover-bg-subtle);
}

/* Inline status pill. Use the badge() macro from components/badge.html.
   Tones map to the --owner-status-* token pairs in delmar-theme.css. */
.owner-badge {
  align-items: center;
  border-radius: 999px;
  display: inline-flex;
  font-size: 12px;
  font-weight: 600;
  gap: 6px;
  letter-spacing: 0.04em;
  padding: var(--owner-space-1) var(--owner-space-3);
  text-transform: uppercase;
  white-space: nowrap;
}

.owner-badge--success {
  background: var(--owner-status-published-bg);
  color: var(--owner-status-published-text);
}

.owner-badge--warning {
  background: var(--owner-status-awaiting-bg);
  color: var(--owner-status-awaiting-text);
}

.owner-badge--error {
  background: var(--owner-status-error-bg);
  color: var(--owner-status-error-text);
}

.owner-badge--info {
  background: var(--owner-status-sent-bg);
  color: var(--owner-status-sent-text);
}

.owner-badge--neutral {
  background: var(--owner-status-draft-bg);
  color: var(--owner-status-draft-text);
}

.owner-badge--violet {
  background: var(--owner-status-viewed-bg);
  color: var(--owner-status-viewed-text);
}

/* Quiet lowercase flag after an identifier ("needs attention") — smaller
   and squarer than the tone badges (Portfolio Homes redesign handoff). */
.owner-badge--attention {
  background: var(--owner-error-bg);
  /* 1px border so the capsule shape survives on the attention-row tint —
     the pale fill alone composites into the tint and reads as bare text
     (round-3 handoff §2). */
  border: 1px solid var(--owner-error-border);
  border-radius: var(--owner-radius-pill);
  /* -strong: AA contrast for sub-12px text on the error background. */
  color: var(--owner-error-text-strong);
  font-size: var(--owner-font-size-xs);
  letter-spacing: 0;
  padding: 1px 6px;
  text-transform: none;
}

/* Inline text color utilities. Use these for non-badge inline status text
   (e.g. "yes"/"no" indicators in tables). For table value cells prefer
   .owner-table__value--positive/negative/muted, which compose with
   tabular-num formatting. */
.owner-text--success {
  color: var(--owner-success-text);
}

.owner-text--warning {
  color: var(--owner-warning-text);
}

.owner-text--error {
  color: var(--owner-error-text);
}

.owner-text--info {
  color: var(--owner-highlight-text);
}

.owner-text--muted {
  color: var(--owner-text-muted);
}

.owner-text--strong {
  font-weight: 600;
}

/* Tabbed navigation. Use the active modifier for the current section and
   the loading modifier to surface optimistic-nav feedback (pulses the
   underline while the destination loads). */
.owner-tabs {
  border-bottom: 1px solid var(--owner-card-border);
  display: flex;
  gap: var(--owner-space-1);
  margin-top: var(--owner-space-2);
  padding: 0 var(--owner-space-1);
}

.owner-tab {
  border-bottom: 2px solid transparent;
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-md);
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 10px 14px;
  text-decoration: none;
}

.owner-tab--active {
  border-bottom-color: var(--owner-accent-orange);
  color: var(--owner-text-primary);
}

.owner-tab--loading {
  position: relative;
}

.owner-tab--loading::after {
  background: var(--owner-accent-orange);
  bottom: -2px;
  content: "";
  height: 2px;
  left: 0;
  position: absolute;
  right: 0;
  animation: owner-tab-pulse 0.9s ease-in-out infinite;
}

@keyframes owner-tab-pulse {
  0% { opacity: 0.4; transform: scaleX(0.6); transform-origin: left; }
  50% { opacity: 1; transform: scaleX(1); }
  100% { opacity: 0.4; transform: scaleX(0.6); transform-origin: right; }
}

.owner-tab--disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.owner-tab-panel {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-4);
  padding-top: var(--owner-space-5);
}

/* Narrow viewports: the strip scrolls horizontally instead of wrapping
   tabs mid-label, mirroring delmar's mobile tab strip (scrollable row,
   whitespace-nowrap, shrink-0 — owner-ui/components/ui/owner/tabs.tsx).
   Scrollbar hidden; a right-edge fade affords the scroll (round-3
   handoff §6 — a hard clip alone left the off-screen tab
   undiscoverable). The mask is anchored to the strip's box, so the
   fade sits at the visible edge regardless of scroll position. */
@media (max-width: 760px) {
  .owner-tabs {
    -webkit-mask-image: linear-gradient(
      to right,
      #000 calc(100% - var(--owner-space-7)),
      transparent
    );
    mask-image: linear-gradient(
      to right,
      #000 calc(100% - var(--owner-space-7)),
      transparent
    );
    overflow-x: auto;
    /* Scroll-end space = the fade width (one token, spacing scale), so
       at maximum scroll the fade covers empty padding — never the last
       tab's label, focus ring, or active underline. */
    padding-right: var(--owner-space-7);
    scrollbar-width: none;
  }

  .owner-tabs::-webkit-scrollbar {
    display: none;
  }

  .owner-tab {
    flex: none;
    white-space: nowrap;
  }
}

/* Progress bar with tone-driven fill. Apply --{tone} on the fill element. */
.owner-progress {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.owner-progress__bar {
  background: rgba(0, 0, 0, 0.06);
  border-radius: 999px;
  height: 8px;
  overflow: hidden;
  width: 100%;
}

/* Thin variant for in-table score/pace bars (Portfolio Homes redesign). */
.owner-progress__bar--thin {
  height: 4px;
}

.owner-progress__fill {
  background: var(--owner-accent-orange);
  display: block;
  height: 100%;
}

.owner-progress__fill--navy { background: var(--owner-sidebar); }
.owner-progress__fill--success { background: var(--owner-success-text); }
.owner-progress__fill--info { background: var(--owner-highlight-text); }
.owner-progress__fill--warning { background: var(--owner-warning-text); }
.owner-progress__fill--error { background: var(--owner-error-text); }

.owner-progress__caption {
  color: var(--owner-text-muted);
  font-size: 12px;
}

/* Inline year-over-year delta. Tones map to the YoYTone literal in
   dmv.ui.formatting (positive/negative/neutral). */
.owner-delta {
  font-size: 12px;
  font-weight: 600;
}

.owner-delta--positive { color: var(--owner-success-text); }
.owner-delta--negative { color: var(--owner-error-text); }
.owner-delta--neutral { color: var(--owner-text-muted); }

/* Pill treatment for the hero stat card's delta (redlines §KPI hero) —
   tone bg + tone text, full-round + padding matching .owner-badge.
   Rendered by metric_card.html via delta(metric.delta, pill=true).
   align-self keeps the pill content-width inside the stat card's
   stretch flex column; it's inert outside flex/grid parents. */
.owner-delta--pill {
  align-self: flex-start;
  border-radius: 999px;
  display: inline-flex;
  padding: var(--owner-space-1) var(--owner-space-3);
  white-space: nowrap;
}

.owner-delta--pill.owner-delta--positive {
  background: var(--owner-success-bg);
}

.owner-delta--pill.owner-delta--negative {
  background: var(--owner-error-bg);
  /* Not the plain-text --owner-error-text: #dc2626 on this bg is 4.415:1,
     under WCAG AA for the pill's 12px type. */
  color: var(--owner-error-text-strong);
}

/* Neutral pill reads on the draft status pair (redlines: "neutral delta
   --owner-status-draft-*"), not the plain-text muted gray. */
.owner-delta--pill.owner-delta--neutral {
  background: var(--owner-status-draft-bg);
  color: var(--owner-status-draft-text);
}

/* Vertical list of label/value rows. Use a <dl> with one <div> per row,
   <dt> for the label, <dd> for the value. Tabular-num + right-aligned dd. */
.owner-fact-list {
  display: grid;
  gap: 10px;
  margin: 0;
}

.owner-fact-list > div {
  display: flex;
  gap: var(--owner-space-4);
  justify-content: space-between;
}

.owner-fact-list dt {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-sm);
  margin: 0;
}

.owner-fact-list dd {
  color: var(--owner-text-primary);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  margin: 0;
}

/* Grid of stacked label/value cells (label above value). Used in entity
   headers and summary cards for fixed-width facts.

   Per-page tightening: override `--owner-fact-grid-min` (e.g. 96px) when
   you have many short-value columns that should sit on one row but the
   default 140px would wrap. */
.owner-fact-grid {
  display: grid;
  gap: var(--owner-space-4) var(--owner-space-7);
  grid-template-columns: repeat(auto-fit, minmax(var(--owner-fact-grid-min, 140px), 1fr));
  margin: 0;
}

.owner-fact-grid > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.owner-fact-grid dt {
  color: var(--owner-text-muted);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.owner-fact-grid dd {
  color: var(--owner-text-primary);
  font-size: 15px;
  font-weight: 500;
  margin: 0;
}

/* Two-up / three-up grid layouts that collapse to a single column at the
   shell's mobile breakpoint (760px). Use for side-by-side sections inside
   a page (e.g. retention "Visits / Refunds" pair). */
.owner-split-2 {
  display: grid;
  gap: var(--owner-space-5);
  grid-template-columns: 1fr 1fr;
}

.owner-split-3 {
  display: grid;
  gap: var(--owner-space-5);
  grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 760px) {
  .owner-split-2,
  .owner-split-3 {
    grid-template-columns: 1fr;
  }
}

/* Small summary card. Composes with `.owner-card` for the bordered/shadowed
   surface and pairs with `.owner-detail-row` for its body rows. Use for
   short label/value lists rendered inside or above a larger drilldown
   (Cash Flow Drivers / Reconciliation, Portfolio Data aggregates, etc.).

   Layout:
     <section class="owner-card owner-summary-card">
       <h2 class="owner-summary-card__title">…</h2>     (small uppercase eyebrow)
       <p class="owner-summary-card__caption">…</p>     (optional explainer)
       {% for row in rows %}
         {{ detail_row(row.label, row.value) }}
       {% endfor %}
     </section>                                                              */
.owner-summary-card {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-2);
  padding: var(--owner-space-4) var(--owner-space-5);
}

.owner-summary-card__title {
  color: var(--owner-text-muted);
  font-family: var(--font-dm-sans);
  font-size: var(--owner-font-size-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  margin: 0;
  text-transform: uppercase;
}

.owner-summary-card__caption {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-sm);
  margin: 0 0 var(--owner-space-1);
}

/* Two-column label/value detail row. Used in summary cards (Cash Flow
   Drivers / Reconciliation, Balance Sheet account rows, etc.). The
   primitive owns the grid-template-columns + tabular-num value alignment;
   consumers add their own padding-x via the wrapping container.

   Modifiers:
     --bordered     — force border-top on every row, not just sibling-
                      following. Use when there is no parent border above
                      the first row to act as a divider (Balance Sheet
                      account rows under a `<details>` summary).
     --total        — bolden + extra top spacing for a final summed row.
     --padded       — applies side padding (10px 16px) and reads the
                      --owner-detail-row-indent variable for nested-section
                      indent (Balance Sheet account rows). */
.owner-detail-row {
  align-items: center;
  display: grid;
  gap: var(--owner-space-3);
  grid-template-columns: minmax(0, 1fr) minmax(120px, max-content);
  padding: var(--owner-space-2) 0;
}

/* When .owner-detail-row is nested inside an .owner-details (e.g. Cash
   Flow account groups under a section summary, Balance Sheet asset rows
   under a section summary), match the summary's 3-column template so
   the value column ends at the same x as the summary's value column.
   The trailing 18 px column is empty for detail rows (only summaries
   render a chevron there) — its purpose is purely to reserve the same
   right gutter so section totals and account totals visibly line up.
   Detail rows OUTSIDE an .owner-details (e.g. the Cash Flow Key Drivers
   summary card) keep the 2-column template above so their values use
   the full row width. */
.owner-details .owner-detail-row {
  grid-template-columns: minmax(0, 1fr) minmax(120px, max-content) 18px;
}

.owner-detail-row + .owner-detail-row,
.owner-detail-row--bordered {
  border-top: 1px solid var(--owner-card-border);
}

.owner-detail-row--total {
  font-weight: 700;
  margin-top: var(--owner-space-1);
  padding-top: var(--owner-space-3);
}

.owner-detail-row--padded {
  padding: 10px 16px;
  padding-left: calc(var(--owner-space-4) + var(--owner-detail-row-indent, 0) * var(--owner-space-6));
}

.owner-detail-row__label {
  color: var(--owner-text-primary);
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.owner-detail-row__hint {
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-xs);
  font-style: italic;
  font-weight: 400;
}

.owner-detail-row__value {
  color: var(--owner-text-primary);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* Tabular drilldown — a drilldown where every row participates in a
   shared multi-column tabular template (1 name/label column + N numeric
   columns). Used by the P&L drill-down: a floating column-header row
   above N collapsible <details> cards whose <summary> is a tabular row
   of section totals and whose body is a `<table>` of detail rows that
   share the same column widths.

   Pairs with `components/tabular_colgroup.html`. The CSS variable
   --summary-cols is set on the .owner-tabular-drilldown wrapper inline
   (`style="--summary-cols: N;"`) where N is the total column count
   (1 name + numeric columns). The same value drives both the summary-
   row CSS grids and the colgroup-fed table column widths via the
   matching name-column percentage + auto-distributed numeric columns
   under table-layout: fixed.

   The lib's disclosure chevron (.owner-details__summary::after) stays
   visible — the column template reserves a trailing 28 px chevron
   column that every row participates in (empty on header / detail
   rows, populated by the chevron pseudo-element on summary rows,
   centered with ~9 px breathing room via a scoped justify-self
   override below).
   Tables emit a matching trailing `<col class="owner-tabular-
   drilldown-col--chevron">` plus an empty trailing `<th></th>` /
   `<td></td>` per row so column boundaries line up between summary
   grids and detail tables. */
.owner-tabular-drilldown {
  display: flex;
  flex-direction: column;
  /* Match .owner-content-shell's 20 px inter-section gap so the wrapper
     restores the page-shell rhythm broken by being itself a flex item. */
  gap: var(--owner-space-5);
}

.owner-tabular-drilldown__table {
  /* table-layout: fixed gives every consumer table the SAME column
     widths regardless of content — without this, auto-layout sizes
     columns per-table and the same logical column ends at different
     x-positions in different sections. */
  table-layout: fixed;
  /* Override .owner-table's min-width: 720px floor so all drilldown
     tables share an effective width driven by their parent
     .owner-table-card__scroll, not a per-table mobile minimum that
     would break alignment between summary rows and detail tables on
     narrow viewports. */
  min-width: 0;
}

.owner-tabular-drilldown-col--name {
  /* Generous slice for label content like "51010 - Homeowner Payments";
     the remaining ~70% is shared equally by the numeric .owner-tabular-
     drilldown-col--num cols via auto-distribution under
     table-layout: fixed (minus the trailing chevron col below). */
  width: 30%;
}

.owner-tabular-drilldown-col--chevron {
  /* Trailing column reserves space for the disclosure chevron on summary
     rows AND keeps that same right gutter on header / detail rows so
     every column right-edge stays in lockstep. The column is empty on
     tables and on the floating column-header (only summary rows render
     a chevron via .owner-details__summary::after, which auto-places
     into this last grid column). Without this reservation, summary's
     last data column would right-edge 28 px short of the table's last
     data column — the original P&L misalignment regression.

     Width is sized to fit the chevron glyph plus breathing room on
     both sides — chevron renders at ~10 px wide in DM Sans 18 px, so
     28 px gives ~9 px on each side once the chevron is centered (see
     .owner-tabular-drilldown__summary::after override below). */
  width: 28px;
}

.owner-tabular-drilldown__column-header,
.owner-tabular-drilldown__summary {
  display: grid;
  /* Mirror the table column template: 30 % name col + (N-1) equal-fr
     numeric cols + 28 px trailing chevron col, no inter-column gap.
     Per-cell padding below mimics .owner-table th/td so right-aligned
     values in summary rows sit at the same x as right-aligned values
     in detail tables. The trailing 28 px column matches the
     col--chevron width above so every column boundary lines up
     between the CSS-grid summary rows and the colgroup-driven detail
     tables. */
  grid-template-columns: 30% repeat(calc(var(--summary-cols) - 1), 1fr) 28px;
  gap: 0;
  align-items: center;
  /* Zero container padding so the grid's data area exactly fills its
     parent — same width as a `.owner-table` inside its
     .owner-table-card__scroll. The lib's `.owner-details__summary`
     base rule applies padding: 16 px which would inset summary rows
     16 px from each card edge, shifting every column 16 px right of
     its position in the sibling tables. The padding-zero override
     here is load-bearing — DO NOT REMOVE. */
  padding: 0;
}

.owner-tabular-drilldown__column-header > span,
.owner-tabular-drilldown__summary > span {
  /* Match .owner-table th/td padding so column content lines up
     vertically between the summary grid and the detail table inside
     the same section. */
  padding: 10px 12px;
}

.owner-tabular-drilldown__column-header {
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.owner-tabular-drilldown__column-header > span:not(:first-child),
.owner-tabular-drilldown__summary > span:not(:first-child) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.owner-tabular-drilldown__summary-name {
  font-weight: 600;
}

/* The lib's disclosure chevron (.owner-details__summary::after) auto-
   places into the trailing 28 px chevron column declared on the summary
   grid above. Override the lib's `justify-self: end` (which would flush
   the chevron against the column's right edge — i.e. against the card
   border) with `center` so the chevron sits in the middle of its 28 px
   column with ~9 px breathing room on either side. Rotation on [open]
   still applies via the standard .owner-details rule. */
.owner-tabular-drilldown__summary::after {
  justify-self: center;
}

/* Hide the per-section detail-table <thead> in the drilldown. The
   floating .owner-tabular-drilldown__column-header at the top already
   labels every numeric column for the entire drilldown, so each
   section repeating its own column labels is pure visual noise.
   `display: none` keeps colgroup-driven column widths intact under
   table-layout: fixed (verified empirically — `position: absolute`
   breaks column alignment, `display: none` does not). Trade-off: AT
   users lose per-section <th scope="col"> association for the nested
   tables; the floating header is aria-hidden so this needs a future
   single-semantic-table restructure to fully resolve. Scope is the
   nested .owner-details detail tables only — top-level tables in the
   drilldown wrapper (e.g. P&L's BM Summary / Standard P&L) keep their
   <thead> visible because no floating header above them labels their
   columns. */
.owner-tabular-drilldown .owner-details .owner-tabular-drilldown__table > thead {
  display: none;
}

/* Inline metadata list with bullet separators between items. Use an <ul>. */
.owner-meta-list {
  color: var(--owner-text-secondary);
  display: flex;
  flex-wrap: wrap;
  font-size: var(--owner-font-size-sm);
  gap: 0 14px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.owner-meta-list li {
  position: relative;
}

.owner-meta-list li + li::before {
  color: var(--owner-text-muted);
  content: "·";
  left: -10px;
  position: absolute;
}

/* Compact metric grid + card. Smaller than the hero .metric-grid pattern;
   used inside cards / tabs for label/value tiles. */
.owner-metric-grid {
  display: grid;
  gap: var(--owner-space-3);
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
}

.owner-metric-card {
  background: #fff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-1);
  padding: 14px 16px;
}

.owner-metric-card__label {
  color: var(--owner-text-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.owner-metric-card__value {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.3px;
}

/* Likely-range pace summary strip (internal-tools redlines §Likely-range
   strip). ONE connected card of three Low / Median / High cells separated
   by --owner-table-border dividers — deliberately not three separate
   .owner-metric-card tiles: the shared surface signals one distribution,
   not three independent metrics. The emphasized cell (the median) gets
   the warm --owner-emphasis-bg fill + a larger accent-orange Kepler
   value. Cell anatomy mirrors .owner-metric-card (label/value/padding);
   the treatment is redlines-mandated with no delmar counterpart. Pairs
   with the pace_summary_strip() macro in
   components/pace_summary_strip.html. */
.owner-pace-summary-strip {
  background: #fff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-card);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  overflow: hidden;
}

.owner-pace-summary-strip__cell {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-1);
  padding: 14px 16px;
}

.owner-pace-summary-strip__cell + .owner-pace-summary-strip__cell {
  border-left: 1px solid var(--owner-table-border);
}

.owner-pace-summary-strip__label {
  color: var(--owner-text-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.owner-pace-summary-strip__value {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-feature-settings: "lnum" 1, "pnum" 1;
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.3px;
}

.owner-pace-summary-strip__cell--emphasis {
  background: var(--owner-emphasis-bg);
}

.owner-pace-summary-strip__cell--emphasis .owner-pace-summary-strip__value {
  color: var(--owner-accent-orange);
  /* 26px is the redline value for the emphasized median cell — off the
     published type scale, kept literal (precedent: retention tile 23px,
     entity-header 14.5px). */
  font-size: 26px;
}

/* Narrow-viewport fallback (proposal: pages must reflow at 320px with no
   horizontal scroll). Three side-by-side money cells can't fit the
   ~288px content area of a 320px viewport, so below 480px the row
   becomes a vertical stack — same cells, the left divider becomes a top
   rule. */
@media (max-width: 480px) {
  .owner-pace-summary-strip {
    grid-template-columns: minmax(0, 1fr);
  }

  .owner-pace-summary-strip__cell + .owner-pace-summary-strip__cell {
    border-left: 0;
    border-top: 1px solid var(--owner-table-border);
  }
}

/* Scorecard goal card. Pairs with components/goal_card.html and
   MetricGoalCard view-model. Distinct from owner-stat-card: this one
   has goal/threshold rendering, a status pill, and a prior-period row.
   Tones drive value color + status pill background. */
.goal-card {
  background: #fff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  /* Size container so the header can stack by CARD width (below), not
     viewport width: the same card is 270px in a 4-up grid at 1440 and
     560px in a 2-up one. */
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 18px;
}

/* Label | status pill on one row. The pill never shrinks or clips (it
   is the honesty rule's own text); the label takes what is left and
   clamps at two lines with the full name on `title`. Design review 3
   P0-2: a 4-up grid crushed labels to one word per line and clipped
   the pill ("DATA UNAVAILA"). */
.goal-card__header {
  align-items: center;
  display: flex;
  gap: var(--owner-space-2);
  justify-content: space-between;
}

.goal-card__label {
  -webkit-box-orient: vertical;
  color: var(--owner-text-secondary);
  display: -webkit-box;
  font-size: var(--owner-font-size-md);
  -webkit-line-clamp: 2;
  line-height: 18px;
  min-width: 0;
  overflow: hidden;
}

/* Narrow card: stack the label over the pill, pill on its own line,
   left-aligned. 340px is the width at which a two-line label beside the
   widest current pill ("DATA UNAVAILABLE", ~156px) drops under ~120px of
   text; the 4-up grid at 1440 (≈270px cards) and the 3-up at 1280 both
   stack. Re-tune toward 260px once the pill vocabulary shortens. */
@container (max-width: 340px) {
  .goal-card__header {
    align-items: flex-start;
    flex-direction: column;
  }
}

.goal-card__status {
  background: var(--owner-status-draft-bg);
  border-radius: 999px;
  color: var(--owner-status-draft-text);
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 3px 10px;
  text-transform: uppercase;
  white-space: nowrap;
}

.goal-card__status--success {
  background: var(--owner-status-published-bg);
  color: var(--owner-status-published-text);
}

.goal-card__status--warning {
  background: var(--owner-status-awaiting-bg);
  color: var(--owner-status-awaiting-text);
}

.goal-card__status--error {
  background: var(--owner-status-error-bg);
  color: var(--owner-status-error-text);
}

.goal-card__value {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-feature-settings: "lnum" 1, "pnum" 1;
  font-size: 30px;
  font-weight: 500;
  letter-spacing: -0.6px;
  line-height: 36px;
}

.goal-card--success .goal-card__value {
  color: var(--owner-status-published-text);
}

.goal-card--warning .goal-card__value {
  color: var(--owner-status-awaiting-text);
}

.goal-card--error .goal-card__value {
  color: var(--owner-status-error-text);
}

.goal-card__detail {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-sm);
  line-height: var(--owner-line-height-sm);
}

.goal-card__targets {
  border-top: 1px solid var(--owner-card-border);
  display: flex;
  gap: 14px;
  list-style: none;
  margin: var(--owner-space-1) 0 0;
  padding: 10px 0 0;
}

.goal-card__target {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.goal-card__target-label {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.goal-card__target-value {
  color: var(--owner-text-primary);
  font-size: var(--owner-font-size-sm);
  font-variant-numeric: tabular-nums;
}

/* Optional footer action on a goal card ("View records"). A text
   link, not a whole-card target — a card-sized link would swallow the
   delta chip's aria-describedby reference. */
.goal-card__action {
  /* 13px, not 11px: a link is a sentence, not an eyebrow (design review
     3, P2-9). */
  font-size: var(--owner-font-size-sm);
  line-height: var(--owner-line-height-sm);
  margin-top: var(--owner-space-2);
}

.goal-card__prior {
  align-items: baseline;
  border-top: 1px dashed var(--owner-card-border);
  color: var(--owner-text-secondary);
  display: flex;
  font-size: 12px;
  gap: 6px;
  margin-top: 2px;
  padding-top: var(--owner-space-2);
}

.goal-card__prior-value {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}

.goal-card__prior-label {
  color: var(--owner-text-muted, var(--owner-text-secondary));
}

/* Grid wrapper for a row of goal cards. Mirrors metric-grid sizing
   conventions but renders as separate cards (gap, no shared border). */
.goal-card-grid {
  display: grid;
  gap: var(--owner-space-4);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* Retention CM tile. Pairs with components/retention_tile.html
   (retention_tile + retention_headline macros). Internal-tools redlines
   §CM component tiles: a compact KPI tile with a 3px tone-colored TOP
   border — the top-edge sibling of .owner-card--accent-*'s left border,
   same 3px weight and the same tone tokens — over an uppercase label and
   a Kepler tone-colored value. Card surface mirrors .owner-metric-card
   (delmar stat-card.tsx anatomy); the tone-top-border grouping treatment
   is redlines-mandated with no direct delmar counterpart. Lay tiles out
   with the existing .owner-metric-grid auto-fit wrapper; the headline
   rollup + tile row compose inside .owner-retention-summary. */
.owner-retention-summary {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-3);
}

.owner-retention-tile {
  background: #fff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  border-top: 3px solid var(--owner-card-border);
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-1);
  padding: 14px 16px;
}

.owner-retention-tile__label {
  color: var(--owner-text-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.owner-retention-tile__value {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-feature-settings: "lnum" 1, "pnum" 1;
  /* 23px is the redline value for the CM component tile — off the
     published type scale, kept literal (precedent: entity-header 14.5px). */
  font-size: 23px;
  font-weight: 500;
  letter-spacing: -0.3px;
  line-height: 30px;
}

.owner-retention-tile__detail {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-sm);
  line-height: var(--owner-line-height-sm);
}

.owner-retention-tile--success { border-top-color: var(--owner-success-text); }
.owner-retention-tile--warning { border-top-color: var(--owner-warning-text); }
.owner-retention-tile--error { border-top-color: var(--owner-error-text); }
.owner-retention-tile--info { border-top-color: var(--owner-highlight-text); }

.owner-retention-tile--success .owner-retention-tile__value {
  color: var(--owner-success-text);
}

.owner-retention-tile--warning .owner-retention-tile__value {
  color: var(--owner-warning-text);
}

.owner-retention-tile--error .owner-retention-tile__value {
  color: var(--owner-error-text);
}

.owner-retention-tile--info .owner-retention-tile__value {
  color: var(--owner-highlight-text);
}

/* Total CM Change headline rollup above the component tiles (redlines
   §Retention /all headline: Kepler 32px tone-colored value + a muted
   formula-note caption). Not a card — it floats over the page surface so
   the 4 tiles below carry the card weight. */
.owner-retention-headline {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-1);
}

.owner-retention-headline__label {
  color: var(--owner-text-muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  margin: 0;
  text-transform: uppercase;
}

.owner-retention-headline__value {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-feature-settings: "lnum" 1, "pnum" 1;
  /* 32px / 38px matches the .metric-grid--hero stat-card value type. */
  font-size: 32px;
  font-weight: 500;
  letter-spacing: -0.72px;
  line-height: 38px;
  margin: 0;
}

.owner-retention-headline__caption {
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-sm);
  line-height: var(--owner-line-height-sm);
  margin: 0;
}

.owner-retention-headline--success .owner-retention-headline__value {
  color: var(--owner-success-text);
}

.owner-retention-headline--warning .owner-retention-headline__value {
  color: var(--owner-warning-text);
}

.owner-retention-headline--error .owner-retention-headline__value {
  color: var(--owner-error-text);
}

.owner-retention-headline--info .owner-retention-headline__value {
  color: var(--owner-highlight-text);
}

/* Entity header card. Compose with .owner-card. Render via the
   entity_header() macro (components/entity_header.html) for the composed
   title + fact-grid pattern; hand-compose only for bespoke bodies
   (e.g. retention's badge + .owner-meta-list). Layout:
     <section class="owner-card owner-entity-header">
       <div class="owner-entity-header__main">
         <div class="owner-entity-header__heading">
           <h2 class="owner-entity-header__title">…</h2>
           <p class="owner-entity-header__subtitle">…</p>  (optional)
         </div>
         <span class="owner-badge …">…</span>             (optional action/badge)
       </div>
       <ul class="owner-meta-list">…</ul>  OR  <dl class="owner-fact-grid">…</dl>
     </section>                                                                    */
.owner-entity-header {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-3);
  padding: var(--owner-space-5);
}

.owner-entity-header__main {
  align-items: start;
  display: flex;
  gap: var(--owner-space-4);
  justify-content: space-between;
}

.owner-entity-header__heading {
  min-width: 0;
}

.owner-entity-header__title {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.5px;
  line-height: 30px;
  margin: 0;
}

.owner-entity-header__subtitle {
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-md);
  margin: var(--owner-space-1) 0 0;
}

/* Optional top-right slot on the heading row (badges / small controls),
   rendered by the entity_header() macro's `actions` arg. */
.owner-entity-header__actions {
  align-items: center;
  display: flex;
  gap: var(--owner-space-2);
}

/* Composed facts grid (redlines §Entity header): label 11px uppercase
   --owner-text-secondary, value 14.5px, 96px min column so ~6 short facts
   sit on one row at card width. Scoped to the entity-header composition so
   standalone .owner-fact-grid keeps its 12px/15px muted-label sizing.
   14.5px is off every published font-size step by design (redline value) —
   don't snap it to --owner-font-size-md. Label treatment adapted from
   delmar/apps/owner-ui/components/ui/owner/owner-contacts-hint.tsx. */
.owner-entity-header .owner-fact-grid {
  --owner-fact-grid-min: 96px;
}

.owner-entity-header .owner-fact-grid dt {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-xs);
}

.owner-entity-header .owner-fact-grid dd {
  font-size: 14.5px;
}

@media (max-width: 760px) {
  .owner-entity-header__main {
    flex-direction: column;
  }

  /* Filter-row forms collapse to a single column on narrow viewports.
     Per-page modifiers (e.g. .finance-controls, .retention-controls) set
     a multi-column grid-template-columns at the desktop breakpoint; this
     override flattens them all here so each consumer doesn't have to
     repeat the same media query.

     Doubled-class selector (specificity 0,0,2,0) is required because
     base.css loads BEFORE app stylesheets, so a single-class rule would
     lose source-order to per-page modifiers like `.finance-controls`
     that have the same specificity (0,0,1,0). Pages that need a
     different mobile layout (e.g. 2-up) override with their own
     equally-or-more-specific media query rule. */
  .owner-form.owner-form {
    grid-template-columns: 1fr;
  }
}

.owner-pill-grid {
  display: grid;
  gap: var(--owner-space-2);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  max-height: 260px;
  overflow-y: auto;
}

.owner-pill {
  align-items: center;
  background: var(--owner-tag-bg);
  border: 1px solid var(--owner-tag-border);
  border-radius: 8px;
  color: var(--owner-tag-text);
  cursor: pointer;
  display: flex;
  font-size: var(--owner-font-size-sm);
  gap: var(--owner-space-2);
  line-height: var(--owner-line-height-sm);
  min-height: 40px;
  padding: 10px 12px;
}

.owner-pill--selected {
  background: var(--owner-pill-selected-bg);
  border-color: var(--owner-pill-selected-border);
  color: var(--owner-pill-selected-text);
}

/* Block notice / banner. Tones: success, warning, error, info, neutral.
   .owner-warning-box is kept as a legacy alias of .owner-notice--warning so
   existing callers don't have to migrate. */
.owner-notice,
.owner-warning-box {
  border: 1px solid transparent;
  border-radius: 8px;
  font-size: var(--owner-font-size-md);
  line-height: var(--owner-line-height-md);
  padding: 12px 14px;
}

.owner-notice--success {
  background: var(--owner-success-bg);
  border-color: var(--owner-success-bg);
  color: var(--owner-success-text);
}

.owner-notice--warning,
.owner-warning-box {
  background: var(--owner-warning-bg);
  border-color: var(--owner-warning-border);
  color: var(--owner-warning-text);
}

.owner-notice--error {
  background: var(--owner-error-bg);
  border-color: var(--owner-error-border);
  color: var(--owner-error-text);
}

.owner-notice--info {
  background: var(--owner-info-bg);
  border-color: var(--owner-info-border);
  color: var(--owner-info-text);
}

.owner-notice--neutral {
  background: var(--owner-input-bg);
  border-color: var(--owner-card-border);
  color: var(--owner-text-secondary);
}

/* Multi-paragraph notice (design review 4, P1-3): the tone colour stays on
   the title — the first child, whether a `<p><strong>…</strong></p>` or a
   heading — and every paragraph after it reads in ink. Warning text on
   the warning fill is ~3.3:1, under AA at 13px, which is fine for a
   one-line flag and not for body copy. Single-line notices
   (`<p class="owner-notice">…</p>` or a text-only `<div>`) have no child
   paragraphs and are unaffected. Enforced here, not per app. */
.owner-notice > p:not(:first-child),
.owner-warning-box > p:not(:first-child) {
  color: var(--owner-text-primary);
}

/* Degraded-state "Partial · cached" banner over dimmed last-known content
   (redlines §Error / partial; composes the delmar-adapted notice + badge
   vocabulary rather than a new surface). Compose with
   .owner-notice--warning; the awaiting-toned chip is badge(label,
   "warning"). Render via the partial_banner() macro from
   components/partial_banner.html — its call-block form wraps the
   last-known markup in __content, rendered grayscale, and reproduces the
   content shell's column stacking so wrapped sections keep their rhythm.
   Grayscale, NOT the redline's opacity .72: subtree opacity dropped
   already-muted 13px text to ~3:1, under WCAG AA. Grayscale desaturates
   without the wholesale luminance drop; it is NOT a mathematical
   contrast invariant (filters work in gamma-encoded sRGB, WCAG
   luminance is linearized), but the current palette was verified AA
   under it — muted/secondary text stays above 5.2:1 (PR #291 review;
   redline updated to match). Re-verify if the text palette shifts. */
.owner-partial-banner {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--owner-space-2) var(--owner-space-3);
}

.owner-partial-banner__as-of {
  font-weight: 600;
}

.owner-partial-banner__content {
  display: flex;
  filter: grayscale(1);
  flex-direction: column;
  gap: var(--owner-space-5);
}

.link-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.quick-link {
  align-items: center;
  border-top: 1px solid var(--owner-card-border);
  color: var(--owner-text-primary);
  display: flex;
  font-size: var(--owner-font-size-md);
  font-weight: 500;
  gap: var(--owner-space-4);
  justify-content: space-between;
  line-height: var(--owner-line-height-md);
  min-height: 56px;
  padding: var(--owner-space-4) 0;
  transition:
    background-color 150ms ease,
    color 150ms ease;
}

.quick-link:hover {
  background: var(--owner-hover-bg);
  color: var(--owner-text-primary);
}

.owner-empty-state {
  padding: var(--owner-space-8);
  text-align: center;
}

.owner-empty-state--inline {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: var(--owner-space-6);
}

.owner-empty-state__title {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.5px;
  line-height: 28px;
  margin: 0 0 var(--owner-space-2);
}

.owner-empty-state__body {
  color: var(--owner-text-secondary);
  font-size: var(--owner-font-size-md);
  line-height: 22px;
  margin: 0;
}

.page-footer {
  color: var(--owner-text-muted);
  font-size: 12px;
  line-height: 18px;
}

@media (max-width: 1180px) {
  .metric-grid,
  .link-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .metric-grid .owner-stat-card:nth-child(2n) {
    border-right: 0;
  }
}

@media (max-width: 1023px) {
  .owner-layout {
    flex-direction: column;
  }

  .owner-sidebar-container {
    bottom: 0;
    left: 0;
    position: fixed;
    top: 0;
    transform: translateX(-100%);
    transition: transform 250ms ease-out;
    width: 252px;
    z-index: 2000;
  }

  html[data-mobile-sidebar-open="true"] .owner-sidebar-container {
    transform: translateX(0);
  }

  .owner-mobile-header {
    display: flex;
  }

  .owner-content-shell {
    padding: 0 var(--owner-space-4) var(--owner-space-6);
  }

  .owner-page-header {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--owner-space-4);
    padding-top: var(--owner-space-5);
  }

  .owner-page-header__title {
    font-size: 24px;
    letter-spacing: -0.5px;
    line-height: 32px;
  }

  .owner-page-header__subtitle {
    font-size: var(--owner-font-size-md);
    line-height: var(--owner-line-height-md);
  }

  .owner-sidebar-logo-toggle,
  .owner-sidebar-edge-toggle {
    display: none;
  }
}

@media (max-width: 640px) {
  .metric-grid,
  .metric-grid--hero,
  .metric-grid--hero-3,
  .link-grid {
    grid-template-columns: 1fr;
  }

  .metric-grid .owner-stat-card {
    border-right: 0;
    border-top: 1px solid var(--owner-card-border);
  }

  .metric-grid .owner-stat-card:first-child {
    border-top: 0;
  }

  .metric-grid--hero .owner-stat-card__value,
  .metric-grid--hero-3 .owner-stat-card__value {
    font-size: 28px;
    line-height: 34px;
  }

  .panel {
    padding: var(--owner-space-5);
  }

  .owner-form {
    align-items: stretch;
  }

  .owner-empty-state {
    padding: var(--owner-space-7) var(--owner-space-5);
  }
}

/* Narrow-viewport hero strips stay 2-up instead of collapsing to one
   ~120px card per row — five stacked cards pushed the content a full
   screen down (Portfolio Homes round-2 handoff). Placed AFTER the 640px
   block above so these equal-specificity rules win below 640px too. */
@media (max-width: 760px) {
  .metric-grid--hero-5,
  .metric-grid--hero-3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .metric-grid--hero-5 .owner-stat-card,
  .metric-grid--hero-3 .owner-stat-card {
    border-right: 0;
    border-top: 1px solid var(--owner-card-border);
  }

  .metric-grid--hero-5 .owner-stat-card:nth-child(odd),
  .metric-grid--hero-3 .owner-stat-card:nth-child(odd) {
    border-right: 1px solid var(--owner-card-border);
  }

  .metric-grid--hero-5 .owner-stat-card:nth-child(-n + 2),
  .metric-grid--hero-3 .owner-stat-card:nth-child(-n + 2) {
    border-top: 0;
  }

  /* Odd card count: the trailing card takes the full row. */
  .metric-grid--hero-5 .owner-stat-card:nth-child(odd):last-child,
  .metric-grid--hero-3 .owner-stat-card:nth-child(odd):last-child {
    border-right: 0;
    grid-column: 1 / -1;
  }

  /* One notch down from each variant's desktop value size. */
  .metric-grid--hero-5 .owner-stat-card__value {
    font-size: 24px;
    line-height: 30px;
  }

  .metric-grid--hero-3 .owner-stat-card__value {
    font-size: 28px;
    line-height: 34px;
  }
}

.dmv-top-progress {
  background: linear-gradient(
    90deg,
    transparent,
    var(--owner-accent-orange),
    transparent
  );
  background-size: 30% 100%;
  background-repeat: no-repeat;
  background-position: -30% 0;
  height: 3px;
  left: 0;
  opacity: 0;
  pointer-events: none;
  position: fixed;
  right: 0;
  top: 0;
  transition: opacity 120ms linear;
  z-index: 9999;
}

.dmv-top-progress--active {
  opacity: 1;
  animation: dmv-top-progress 1.1s ease-in-out infinite;
}

@keyframes dmv-top-progress {
  0% { background-position: -30% 0; }
  100% { background-position: 130% 0; }
}

.owner-content-shell {
  transition: opacity 80ms linear;
}

.owner-content-shell--leaving {
  opacity: 0.45;
  pointer-events: none;
}

/* dmv.ui.web combobox — shared single + multi-select picker.
   Both modes adapted from delmar/apps/owner-ui/components/ui:
   single → autocomplete.tsx, multi → owner/multi-select-filter-dropdown.tsx
   See packages/dmv-ui/src/dmv/ui/web/assets/combobox.js. */

.dmv-combobox {
  position: relative;
  width: 100%;
}

/* ---- single mode: typeahead text input ---- */

.dmv-combobox__field {
  background: var(--owner-input-bg, #fff);
  border: 1px solid var(--owner-input-border);
  border-radius: var(--owner-input-radius);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

.dmv-combobox__field:focus-within {
  border-color: var(--owner-pill-selected-border);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--owner-pill-selected-border) 15%, transparent);
}

.dmv-combobox__input {
  background: transparent;
  border: 0;
  color: var(--owner-text-primary);
  display: block;
  font-size: var(--owner-font-size-md);
  outline: none;
  padding: 9px 12px;
  width: 100%;
}

.dmv-combobox__input::placeholder {
  color: var(--owner-input-placeholder);
}

/* ---- multi mode: trigger button ---- */

.dmv-combobox__trigger {
  align-items: center;
  background: var(--owner-input-bg, #fff);
  border: 1px solid var(--owner-input-border);
  border-radius: var(--owner-input-radius);
  color: var(--owner-text-primary);
  cursor: pointer;
  display: flex;
  font-size: var(--owner-font-size-md);
  gap: var(--owner-space-2);
  min-height: 40px;
  padding: var(--owner-space-2) var(--owner-space-3);
  text-align: left;
  transition: border-color 120ms ease, box-shadow 120ms ease;
  width: 100%;
}

.dmv-combobox__trigger:hover {
  background: var(--owner-input-bg, #f4f5f7);
}

.dmv-combobox__trigger[aria-expanded="true"],
.dmv-combobox__trigger:focus-visible {
  border-color: var(--owner-pill-selected-border);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--owner-pill-selected-border) 15%, transparent);
  outline: none;
}

.dmv-combobox__trigger-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dmv-combobox__trigger-label--placeholder {
  color: var(--owner-input-placeholder);
}

.dmv-combobox__trigger-count {
  align-items: center;
  background: var(--owner-info-bg, #eef2ff);
  border-radius: 999px;
  color: var(--owner-info-text, #4f46e5);
  display: inline-flex;
  font-size: 12px;
  font-weight: 600;
  height: 20px;
  justify-content: center;
  min-width: 20px;
  padding: 0 6px;
}

.dmv-combobox__trigger-chevron {
  color: var(--owner-text-muted);
  flex-shrink: 0;
  height: 18px;
  transition: transform 120ms ease;
  width: 18px;
}

.dmv-combobox__trigger[aria-expanded="true"] .dmv-combobox__trigger-chevron {
  transform: rotate(180deg);
}

/* ---- panel (shared by both modes) ---- */

.dmv-combobox__panel {
  background: #fff;
  border: 1px solid var(--owner-card-border, #d4d4d4);
  border-radius: 8px;
  /* Stacked shadow — small crisp edge layered with the popover token's
     longer drop. The crisp 1px-blur layer is bespoke to the combobox
     because its panel sits very close to its trigger; tokenizing both
     would lose that finished edge. */
  box-shadow: 0 1px 2px rgba(15, 34, 56, 0.08), var(--owner-shadow-popover);
  left: 0;
  margin-top: var(--owner-space-1);
  min-width: 220px;
  overflow: hidden;
  position: absolute;
  right: 0;
  top: 100%;
  z-index: 50;
}

.dmv-combobox--single .dmv-combobox__panel {
  /* Single mode panel just contains the listbox; tighter padding. */
  padding: var(--owner-space-1) 0;
}

.dmv-combobox__panel-search {
  align-items: center;
  border-bottom: 1px solid var(--owner-card-border, #eaeef3);
  display: flex;
  gap: var(--owner-space-2);
  padding: var(--owner-space-2) var(--owner-space-3);
}

.dmv-combobox__panel-search svg {
  color: var(--owner-input-placeholder);
  flex-shrink: 0;
  height: 14px;
  width: 14px;
}

.dmv-combobox__panel-search input {
  background: transparent;
  border: 0;
  color: var(--owner-text-primary);
  flex: 1 1 auto;
  font-size: var(--owner-font-size-sm);
  outline: none;
  padding: 2px 0;
}

.dmv-combobox__panel-search input::placeholder {
  color: var(--owner-input-placeholder);
}

.dmv-combobox__panel-actions {
  border-bottom: 1px solid var(--owner-card-border, #eaeef3);
  display: flex;
  gap: var(--owner-space-4);
  padding: 6px 12px;
}

.dmv-combobox__panel-action {
  background: transparent;
  border: 0;
  color: var(--owner-info-text, #4f46e5);
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
  padding: 0;
}

.dmv-combobox__panel-action:hover {
  text-decoration: underline;
}

.dmv-combobox__list {
  list-style: none;
  margin: 0;
  max-height: 280px;
  overflow-y: auto;
  padding: var(--owner-space-1) 0;
}

.dmv-combobox__group {
  background: #fff;
  color: var(--owner-text-muted);
  font-size: var(--owner-font-size-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: var(--owner-space-2) var(--owner-space-3) var(--owner-space-1);
  position: sticky;
  text-transform: uppercase;
  top: 0;
}

.dmv-combobox__option {
  font-size: var(--owner-font-size-md);
}

.dmv-combobox__option-label,
.dmv-combobox--single .dmv-combobox__option {
  align-items: center;
  cursor: pointer;
  display: flex;
  gap: 10px;
  padding: var(--owner-space-2) var(--owner-space-3);
  transition: background-color 120ms ease;
}

.dmv-combobox__option-label {
  width: 100%;
}

.dmv-combobox__option:hover,
.dmv-combobox__option--active {
  background: var(--owner-input-bg, #f4f5f7);
}

.dmv-combobox__option--selected {
  background: var(--owner-info-bg, #eef2ff);
}

.dmv-combobox__option--selected:hover,
.dmv-combobox__option--selected.dmv-combobox__option--active {
  filter: brightness(0.97);
}

/* Hide native checkbox visually — it remains the source of truth and is
   focusable via the wrapping <label>. */
.dmv-combobox__option-checkbox {
  height: 0;
  margin: 0;
  opacity: 0;
  pointer-events: none;
  position: absolute;
  width: 0;
}

.dmv-combobox__option-check {
  align-items: center;
  background: #fff;
  border: 1.5px solid var(--owner-input-border);
  border-radius: 4px;
  display: inline-flex;
  flex-shrink: 0;
  height: 16px;
  justify-content: center;
  width: 16px;
}

.dmv-combobox__option--selected .dmv-combobox__option-check {
  background: var(--owner-info-text, #4f46e5);
  border-color: var(--owner-info-text, #4f46e5);
}

.dmv-combobox__option--selected .dmv-combobox__option-check::after {
  border: solid #fff;
  border-width: 0 2px 2px 0;
  content: "";
  height: 8px;
  margin-top: -2px;
  transform: rotate(45deg);
  width: 4px;
}

.dmv-combobox__option-num {
  color: var(--owner-text-muted);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  min-width: 56px;
}

.dmv-combobox__option--selected .dmv-combobox__option-name {
  font-weight: 500;
}

.dmv-combobox__option-name {
  color: var(--owner-text-primary);
}

.dmv-combobox__empty {
  color: var(--owner-text-secondary, #6a6a6a);
  font-size: var(--owner-font-size-sm);
  padding: 10px 14px;
}

/* The UA stylesheet's `[hidden] { display: none }` rule has lower
   specificity than `.dmv-combobox--single .dmv-combobox__option`
   (which sets `display: flex`), so without this override the search
   filter's `el.hidden = true` would have no visible effect — every
   option would stay shown regardless of what the user types. */
.dmv-combobox__option[hidden],
.dmv-combobox__group[hidden] {
  display: none;
}

/* Combobox chip strip
   Companion to the multi-mode combobox: the trigger only shows a count,
   so chips give users a visible record of what they picked plus per-item
   X-to-remove and an optional Reset link to clear everything. Server-
   rendered links — no JS state. */
.dmv-combobox-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--owner-space-2);
  margin-bottom: var(--owner-space-4);
}

.dmv-combobox-chip {
  align-items: center;
  background: var(--owner-status-draft-bg, #eef0f3);
  border: 1px solid var(--owner-tag-border);
  border-radius: 999px;
  color: var(--owner-status-draft-text, #0f2238);
  display: inline-flex;
  font-size: var(--owner-font-size-sm);
  font-weight: 500;
  gap: 6px;
  padding: 4px 10px 4px 12px;
  text-decoration: none;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.dmv-combobox-chip:hover,
.dmv-combobox-chip:focus-visible {
  background: var(--owner-status-error-bg, #fdecec);
  border-color: var(--owner-status-error-text, #b3261e);
  color: var(--owner-status-error-text, #b3261e);
  outline: none;
}

.dmv-combobox-chip__remove {
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
}

.dmv-combobox-chip-reset {
  align-items: center;
  background: transparent;
  border: 1px dashed var(--owner-tag-border);
  border-radius: 999px;
  color: var(--owner-text-muted, #6a6a6a);
  display: inline-flex;
  font-size: var(--owner-font-size-sm);
  font-weight: 500;
  padding: var(--owner-space-1) var(--owner-space-3);
  text-decoration: none;
  transition: border-color 120ms ease, color 120ms ease;
}

.dmv-combobox-chip-reset:hover,
.dmv-combobox-chip-reset:focus-visible {
  border-color: var(--owner-text-primary);
  color: var(--owner-text-primary);
  outline: none;
}

/* Server-rendered SVG line chart (components/svg_line_chart.html). */
.dmv-line-chart {
  margin: 0;
  /* Narrow-viewport strategy (design-system acceptance: 320px width and
     200% zoom with no page-level horizontal scroll): the chart scrolls
     inside its own container instead of scaling the fixed-viewBox SVG
     down to illegible text sizes. Mirrors the owner-table-card__scroll
     pattern for wide tables. */
  overflow-x: auto;
  width: 100%;
}

.dmv-line-chart__svg {
  display: block;
  height: auto;
  /* Floor the rendered width so viewBox-unit text never drops below
     ~80% scale (12-unit labels stay >=9.6px); below this the container
     scrolls. */
  min-width: 640px;
  width: 100%;
}

.dmv-line-chart__grid {
  /* --owner-chart-grid (#eef1f5, per the design-system redlines) replaces
     the earlier card-border stand-in (#efefef) — the one intentional
     computed-value change in the PR-2 token migration. */
  stroke: var(--owner-chart-grid, #eef1f5);
  stroke-width: 1;
}

.dmv-line-chart__y-tick {
  fill: var(--owner-text-muted, #6a6a6a);
  font-family: var(--font-dm-sans);
  font-size: var(--owner-font-size-sm);
}

.dmv-line-chart__x-tick {
  fill: var(--owner-text-secondary, #6a6a6a);
  font-family: var(--font-dm-sans);
  font-size: var(--owner-font-size-sm);
}

.dmv-line-chart__x-annotation {
  fill: var(--owner-text-muted, #6a6a6a);
  font-family: var(--font-dm-sans);
  font-size: var(--owner-font-size-xs);
  font-weight: 600;
}

.dmv-line-chart__legend {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  font-size: var(--owner-font-size-sm);
  gap: var(--owner-space-4);
  /* Mockup: legend header-right above the plot, not bottom-centered. */
  justify-content: flex-end;
  margin-bottom: var(--owner-space-2);
  margin-top: var(--owner-space-3);
}

.dmv-line-chart__legend-item {
  align-items: center;
  color: var(--owner-text-secondary, #6a6a6a);
  display: inline-flex;
  font-weight: 500;
  gap: var(--owner-space-2);
}

/* Forecast extensions (B1, redlines §Pace / §Forecast chart rows):
   dashed tail past the today rule, faint area fill under the actual
   line, and the likely-range band. The dashed tail keeps its series'
   OWN stroke color (design-system-spec §Charts: "Plan (navy) vs
   Actual/Forecast (orange), forecast tail dashed past a dashed `today`
   rule") — --owner-chart-forecast-dash (#d2dae3) colors the today rule,
   not the tail. Stroke/fill tokens are emitted inline by the macro so
   the token usage is assertable at render time; only geometry lives
   here. Adapted from delmar/apps/owner-ui/components/proposals/owner/
   revenue-chart.tsx (Recharts strokeDasharray + ReferenceLine idiom). */
.dmv-line-chart__line--dashed {
  /* Mockup: forecast tail 5 4 dash at .7 opacity in the series' own
     color (the gray token belongs to the today rule, not the tail). */
  opacity: 0.7;
  stroke-dasharray: 5 4;
}

.dmv-line-chart__today-rule {
  stroke-dasharray: 4 4;
  stroke-width: 1.5;
}

.dmv-line-chart__series-label {
  font-family: var(--font-dm-sans);
  font-size: var(--owner-font-size-xs);
  font-weight: 600;
}

.dmv-line-chart__today-label {
  /* Mockup: 10.5px regular, --owner-muted-icon (#8a9bad), centered on
     the rule — quiet marker, not a heading. */
  fill: var(--owner-muted-icon, #8a9bad);
  font-family: var(--font-dm-sans);
  font-size: 10.5px;
  font-weight: 400;
}

/* Round dot — matches delmar's Recharts legend (`iconType="circle"` in
   revenue-chart.tsx). Dot reads cleaner than a rectangle for line/bar
   charts where the series indicator is conventionally a swatch. */
.dmv-line-chart__legend-swatch {
  /* Mockup: 14x3 line swatch matching the series stroke, not a dot. */
  border-radius: 1.5px;
  display: inline-block;
  height: 3px;
  width: 14px;
}

/* Per-point tooltip — rendered via foreignObject inside each <g>.point.
   Hidden by default; shown on hover or focus of the parent group. SVG
   <title> tooltips are unreliable across browsers (Safari notably
   flaky), so we ship a real HTML tooltip that renders instantly. */
.dmv-line-chart__tooltip-fo {
  opacity: 0;
  transition: opacity 80ms ease-in;
  pointer-events: none;
}

.dmv-line-chart__point:hover .dmv-line-chart__tooltip-fo,
.dmv-line-chart__point:focus-within .dmv-line-chart__tooltip-fo {
  opacity: 1;
}

/* Light card surface — matches delmar's Recharts default Tooltip
   contentStyle (`border, borderRadius, fontSize` over white). Dark
   tooltips (sidebar Radix tooltips on dark surface) stay separate.
   Adapted from delmar/apps/owner-ui/components/proposals/owner/revenue-chart.tsx:91-98 */
.dmv-line-chart__tooltip {
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-pill);
  box-shadow: var(--owner-shadow-tooltip);
  color: var(--owner-text-primary);
  font-family: var(--font-dm-sans);
  font-size: var(--owner-font-size-sm);
  font-weight: 500;
  line-height: 1.4;
  max-width: 240px;
  padding: 8px 10px;
  text-align: left;
  white-space: normal;
}

.dmv-line-chart__point:focus {
  outline: none;
}

/* Compact rounded-pill filter dropdown. For "set-and-forget" filter
   rows where the heavier .owner-form combobox is too loud (e.g.
   "narrow what's already shown" filters above a table). Built on
   <details> so toggling is free — no JS state. Each option is a server-
   rendered <a> link that mutates the query string; clicking updates
   the page via normal navigation.

   Adapted from delmar/apps/owner-ui/components/ui/owner/filter-dropdown.tsx. */
.owner-filter-pill {
  display: inline-block;
  position: relative;
}

.owner-filter-pill__trigger {
  align-items: center;
  background: #ffffff;
  border: 1px solid var(--owner-input-border);
  border-radius: 999px;
  color: var(--owner-text-primary);
  cursor: pointer;
  display: inline-flex;
  font-size: var(--owner-font-size-md);
  font-weight: 500;
  gap: 6px;
  list-style: none;
  padding: 6px 14px;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.owner-filter-pill__trigger::-webkit-details-marker { display: none; }
.owner-filter-pill__trigger::marker { content: ""; }

.owner-filter-pill__trigger:hover {
  background: var(--owner-hover-bg);
}

.owner-filter-pill__trigger:focus-visible {
  box-shadow: 0 0 0 2px var(--owner-pill-selected-border);
  outline: none;
}

.owner-filter-pill__chevron {
  flex-shrink: 0;
  height: 14px;
  transition: transform 120ms ease;
  width: 14px;
}

.owner-filter-pill[open] .owner-filter-pill__chevron {
  transform: rotate(180deg);
}

.owner-filter-pill__panel {
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: 8px;
  box-shadow: var(--owner-shadow-popover);
  display: flex;
  flex-direction: column;
  left: 0;
  margin-top: var(--owner-space-1);
  max-height: 280px;
  min-width: 180px;
  overflow-y: auto;
  padding: var(--owner-space-1) 0;
  position: absolute;
  top: 100%;
  z-index: 30;
}

.owner-filter-pill__option {
  color: var(--owner-text-primary);
  display: block;
  font-size: var(--owner-font-size-md);
  padding: 8px 14px;
  text-decoration: none;
  transition: background-color 120ms ease;
  white-space: nowrap;
}

.owner-filter-pill__option:hover,
.owner-filter-pill__option:focus-visible {
  background: var(--owner-hover-bg);
  outline: none;
}

.owner-filter-pill__option--selected {
  background: var(--owner-info-bg);
  color: var(--owner-info-text);
  font-weight: 500;
}

/* Segmented period / as-of control for the page-header action slot
   (This week / MTD / QTD / Custom). A connected row of GET-link
   segments — selection is server-side: each segment mutates the
   period query params and the route re-renders with the matching
   segment filled. Pairs with the period_control() macro in
   components/period_control.html.

   Adapted from delmar/apps/owner-ui/components/jobs/view-toggle.tsx
   (the Grouped-by-status / List segmented view toggle): white
   overflow-hidden rounded-lg bordered row, h-8 px-3 13px semibold
   segments, selected = sidebar-navy fill + white text. Deviations:
   unselected hover uses --owner-hover-bg (the lib's canonical control
   hover wash) instead of delmar's bg-owner-page-bg, and the focus
   ring is inset (the container clips overflow) using the filter-pill
   ring token so keyboard focus stays visible on every segment. */
.owner-period-control {
  background: #ffffff;
  border: 1px solid var(--owner-input-border);
  border-radius: 8px; /* matches .owner-button / delmar rounded-lg */
  display: inline-flex;
  overflow: hidden;
}

.owner-period-control__segment {
  align-items: center;
  color: var(--owner-text-secondary);
  display: inline-flex;
  font-size: var(--owner-font-size-sm);
  font-weight: 600;
  height: 32px;
  padding: 0 var(--owner-space-3);
  text-decoration: none;
  transition: background-color 120ms ease, color 120ms ease;
  white-space: nowrap;
}

.owner-period-control__segment:hover {
  background: var(--owner-hover-bg);
}

.owner-period-control__segment:focus-visible {
  box-shadow: inset 0 0 0 2px var(--owner-pill-selected-border);
  outline: none;
}

.owner-period-control__segment--selected,
.owner-period-control__segment--selected:hover {
  background: var(--owner-sidebar);
  color: #ffffff;
}

/* Narrow-viewport fallback (proposal: pages must reflow at 320px with
   no horizontal scroll). Below 480px the connected row becomes a grid
   of equal columns — same segments, centered labels. Up to three
   segments share one row (design review 4, P2-5: the 2-column grid
   wrapped `26 weeks` alone on a second row at 390). Four nowrap
   segments need ~319px while the 320px-viewport content area is ~288px,
   so four or more fall back to two columns, with a hairline row divider
   standing in for the lost single-row adjacency. Column flow rather
   than `repeat(auto-fit, minmax(0, 1fr))`: a zero minimum gives
   auto-fit no defined repetition count. `:has()` is the only way to
   count children from the parent without a per-consumer class. */
@media (max-width: 480px) {
  .owner-period-control {
    display: grid;
    grid-auto-columns: minmax(0, 1fr);
    grid-auto-flow: column;
  }

  .owner-period-control__segment {
    justify-content: center;
  }

  .owner-period-control:has(> :nth-child(4)) {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .owner-period-control:has(> :nth-child(4)) .owner-period-control__segment:nth-child(n + 3) {
    border-top: 1px solid var(--owner-input-border);
  }
}

/* Skeleton loading placeholder. Use when a page renders structurally
   first (top progress bar already conveys "loading"; skeletons let the
   user see the upcoming layout) and the data swaps in via reload or
   poll-based handler.

   Variants control the shape — text rows, title bars, full cards, KPI
   tiles, table rows. Compose with inline `style="width: …"` if you need
   per-instance tweaks (e.g. shorter trailing line of a paragraph).

   Marked aria-hidden — assistive tech ignores them so the live region or
   "Analyzing guest feedback…" notice carries the announce, not the
   structural placeholder. */
@keyframes owner-skeleton-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

.owner-skeleton {
  animation: owner-skeleton-pulse 1.6s ease-in-out infinite;
  background: var(--owner-status-draft-bg);
  border-radius: var(--owner-radius-pill);
  display: block;
}

.owner-skeleton--text {
  height: 14px;
  margin: var(--owner-space-1) 0;
  width: 100%;
}

.owner-skeleton--title {
  height: 24px;
  margin: var(--owner-space-1) 0;
  width: 60%;
}

.owner-skeleton--row {
  height: 36px;
  width: 100%;
}

.owner-skeleton--metric {
  border-radius: var(--owner-radius-card);
  height: 88px;
  width: 100%;
}

.owner-skeleton--card {
  border-radius: var(--owner-radius-card);
  height: 200px;
  width: 100%;
}

/* Streaming body placeholder. The wrapping element is the htmx swap
   target rendered by `components/skeleton_panel.html`; the response
   replaces it via outerHTML on load. The min-height comes inline from
   the panel context (`panel.min_height`) so each page reserves a
   sensible footprint for its body. */
.owner-skeleton-panel {
  display: flex;
  flex-direction: column;
  gap: var(--owner-space-3);
  padding: var(--owner-space-6);
  /* White stays literal: delmar publishes no white panel surface (only
     --owner-input-bg), and the gate deliberately never infers a role for
     #fff. Inventing an --owner-surface here would fork the vocabulary. */
  background: #fff; /* brand-drift: allowed */
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-card);
}

.owner-skeleton-panel__label {
  margin: 0 0 var(--owner-space-1);
  font-size: var(--owner-font-size-sm);
}

/* Inline info tooltip. Pure CSS — no JS. The trigger is a small "?"
   button that toggles a positioned bubble on :hover or :focus-within
   (keyboard + screen-reader friendly via the aria-label on the button).
   Use for column-header explanations or table-cell diagnostics that
   would otherwise cost a paragraph of subtitle prose. */
/* Notice chip — capsule counting a section's data notices, with a
   click-opened popover (state model adapted from delmar
   field-tooltip.tsx via notice-chip.js; see the component template).
   The notice text stays in the DOM while closed. */
.owner-notice-chip {
  display: inline-flex;
  position: relative;
}

.owner-notice-chip__trigger {
  align-items: center;
  background: var(--owner-warning-bg);
  border: 1px solid var(--owner-warning-border);
  border-radius: 999px;
  color: var(--owner-warning-text);
  cursor: pointer;
  display: inline-flex;
  font-family: var(--font-dm-sans);
  font-size: 12px;
  font-weight: 500;
  gap: 6px;
  line-height: 16px;
  padding: 3px 10px;
}

.owner-notice-chip__dot {
  background: var(--owner-warning-icon);
  border-radius: 999px;
  flex: none;
  height: 6px;
  width: 6px;
}

/* Any error-severity notice escalates the COLLAPSED chip too — severity
   must not disappear while the popover is closed. */
.owner-notice-chip--error .owner-notice-chip__trigger {
  background: var(--owner-error-bg);
  border-color: var(--owner-error-border);
  color: var(--owner-error-text-strong);
}

.owner-notice-chip--error .owner-notice-chip__dot {
  background: var(--owner-error-text);
}

/* Closed = visibility:hidden — leaves the accessibility tree and blocks
   pointer events. Opened only by notice-chip.js (`is-open`, synced with
   the trigger's aria-expanded), so the visible and announced states can
   never disagree. */
.owner-notice-chip__popover {
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-card);
  box-shadow: var(--owner-shadow-popover);
  display: flex;
  flex-direction: column;
  gap: 6px;
  left: 0;
  /* Bounded + scrollable: many verbatim notices (or 400% zoom) must
     never push content past the viewport unreachably. */
  max-height: min(60vh, 480px);
  opacity: 0;
  overflow-y: auto;
  padding: 10px 16px;
  position: absolute;
  text-transform: none;
  top: calc(100% + 6px);
  transition: opacity 120ms ease-in-out, visibility 120ms ease-in-out;
  visibility: hidden;
  white-space: normal;
  /* Collision-aware width (delmar: min(360px, calc(100vw - 32px))). */
  width: min(440px, calc(100vw - 32px));
  z-index: 30;
}

.owner-notice-chip.is-open .owner-notice-chip__popover {
  opacity: 1;
  visibility: visible;
}

/* Explicit close affordance (delmar field-tooltip's Popover.Close). */
.owner-notice-chip__close {
  align-self: flex-end;
  background: none;
  border: 0;
  border-radius: var(--owner-radius-pill);
  color: var(--owner-text-secondary);
  cursor: pointer;
  line-height: 0;
  margin: -4px -8px -2px 0;
  padding: 2px;
}

.owner-notice-chip__close:hover,
.owner-notice-chip__close:focus-visible {
  background: var(--owner-surface-subtle);
  color: var(--owner-text-primary);
}

/* On narrow viewports a left-anchored 440px card would clip past the
   right edge (the chip sits after a section title). Pin to the viewport
   instead: fixed positioning with auto top keeps the hypothetical static
   vertical slot (just under the chip) while the horizontal insets span
   the screen. */
@media (max-width: 760px) {
  .owner-notice-chip__popover {
    left: 12px;
    max-width: none;
    position: fixed;
    right: 12px;
    top: auto;
    width: auto;
  }
}

.owner-notice-chip__item {
  align-items: baseline;
  color: var(--owner-text-secondary);
  display: flex;
  font-size: 12px;
  font-weight: 400;
  gap: var(--owner-space-2);
  line-height: 18px;
  text-align: left;
}

.owner-notice-chip__item-dot {
  background: var(--owner-warning-icon);
  border-radius: 999px;
  flex: none;
  height: 6px;
  position: relative;
  top: -1px;
  width: 6px;
}

.owner-notice-chip__item--error .owner-notice-chip__item-dot {
  background: var(--owner-error-text);
}

.owner-tooltip {
  align-items: center;
  cursor: help;
  display: inline-flex;
  position: relative;
  vertical-align: middle;
}

.owner-tooltip__trigger {
  align-items: center;
  background: var(--owner-status-draft-bg);
  border: 0;
  border-radius: 50%;
  color: var(--owner-text-muted);
  cursor: help;
  display: inline-flex;
  font-size: var(--owner-font-size-xs);
  font-weight: 700;
  height: 16px;
  justify-content: center;
  letter-spacing: 0;
  line-height: 1;
  padding: 0;
  width: 16px;
}

.owner-tooltip__trigger:focus-visible {
  box-shadow: 0 0 0 2px var(--owner-pill-selected-border);
  outline: none;
}

/* Light card surface — matches the chart tooltip (.dmv-line-chart__tooltip)
   so both info-tooltips and chart hovers share visual vocabulary over the
   light page surface. Dark variant (used by delmar's collapsed-sidebar
   Radix tooltips) is reserved for dark-on-dark contexts. */
.owner-tooltip__bubble {
  background: #ffffff;
  border: 1px solid var(--owner-card-border);
  border-radius: var(--owner-radius-pill);
  bottom: calc(100% + 6px);
  box-shadow: var(--owner-shadow-tooltip);
  color: var(--owner-text-primary);
  font-family: var(--font-dm-sans);
  font-size: var(--owner-font-size-sm);
  font-weight: 400;
  left: 50%;
  line-height: 1.4;
  max-width: 240px;
  min-width: 160px;
  opacity: 0;
  padding: 8px 10px;
  pointer-events: none;
  position: absolute;
  text-align: left;
  text-transform: none;
  transform: translateX(-50%);
  transition: opacity 80ms ease-in;
  /* Override inherited `nowrap` from `.owner-table--wide th, td`. Without
     this, the bubble's `width: max-content` reports its full unwrapped
     line width via `scrollWidth` (~1250px for a typical info-tooltip),
     which the ancestor `.owner-table-card__scroll` (overflow-x: auto)
     includes in its scrollable area — producing ~1500px of phantom
     horizontal scroll into empty space past the table. */
  white-space: normal;
  width: max-content;
  z-index: 5;
}

.owner-tooltip:hover .owner-tooltip__bubble,
.owner-tooltip:focus-within .owner-tooltip__bubble {
  opacity: 1;
}




/* ---------------------------------------------------------------------
   Tooltips escape their clipping ancestor (progressive enhancement).

   CSS-only callers share one anchor-positioning rule. This replaced three
   separate positional workarounds, each added after its own bug report: stat-card
   inset pinning (horizontal clipping at `.metric-grid` edges), wide-table
   right-anchoring (phantom horizontal scroll from the bubble's width
   inflating `scrollWidth`), and a table-header downward flip (vertical
   clipping at the table's top edge). All three were the same bug wearing
   different hats, and each fixed exactly one location — so the fourth
   report, a tall bubble on a table BODY row (2026-08-22, cash-forecast
   scenario table, first line cut off mid-sentence), needed a fourth patch.

   The cause: `.owner-table-card__scroll` is `overflow-x: auto`, which per
   spec computes `overflow-y` to `auto` as well, so an absolutely-positioned
   bubble is clipped ABOVE and BELOW — and "flip it to whichever side has
   room" runs out of sides. A tall bubble on a short table has neither.

   A `position: fixed` bubble is not clipped by ancestor overflow at all,
   which subsumes all three cases at once — including the phantom-scroll
   one, since an out-of-flow bubble cannot contribute to `scrollWidth`.

   (Strictly: a fixed element is clipped only by an ancestor establishing a
   containing block via transform, filter or contain — none of these do.)
   CSS anchor positioning supplies the coordinates without JS, and
   `position-try-fallbacks` flips the bubble when the preferred side has no
   viewport room.

   `anchor-scope` is what makes one shared anchor name safe: without it
   every trigger declares the same `--owner-tooltip` and the name resolves
   across instances. Scoping it to `.owner-tooltip` confines each name to
   its own wrapper.

   Behind `@supports` on purpose: a browser without anchor positioning keeps
   the plain absolutely-positioned bubble above, which is the pre-2026
   behaviour rather than a regression.

   The condition tests EVERY load-bearing property, not just one. These
   shipped separately — Chrome had the current `position-area` syntax in 129
   but `anchor-scope` only in 131 — and a partial match is worse than none:
   the enhancement would activate while `anchor-scope` was ignored, so the
   single shared `--owner-tooltip` name would resolve across components and a
   bubble could anchor to some other component's trigger.

   Managed callers use the opt-in portal rule below because a transformed
   drawer can establish a new fixed-position containing block. Keep legacy
   callers on this CSS baseline; test_tooltip_visual.py checks their paint
   and scroll behavior rather than the number of positioning declarations.
   --------------------------------------------------------------------- */
@supports (anchor-name: --owner-tooltip) and (anchor-scope: --owner-tooltip) and
  (position-anchor: --owner-tooltip) and (position-area: block-start) and
  (position-try-fallbacks: block-end) {
  /* The WRAPPER carries the anchor, not the trigger. Not every caller has a
     trigger: the Forecast Accuracy grid wraps a plain cell value, deliberately
     omitting the button so 200+ cells do not each become a Tab stop. Anchoring
     the trigger left those bubbles fixed with nothing to anchor to — unpositioned
     and not scroll-tracked. The wrapper exists in every shape, and for the macro
     it is the trigger's own box plus the bubble, so the geometry is unchanged. */
  .owner-tooltip {
    anchor-name: --owner-tooltip;
    anchor-scope: --owner-tooltip;
  }

  .owner-tooltip__bubble {
    inset: auto;
    margin-block-end: 6px;
    max-width: 260px;
    min-width: 160px;
    position: fixed;
    position-anchor: --owner-tooltip;
    position-area: block-start;
    position-try-fallbacks: block-end, inline-end, inline-start;
    transform: none;
    width: max-content;
  }
}

/* Managed field help adapts delmar/apps/owner-ui/components/proposals/
   retention/admin/field-tooltip.tsx. The scorecards controller owns state
   and portal geometry; promoting that controller is a shared-library candidate.
   Unlike the Delmar dialog, these bubbles contain no interactive content. */
.owner-tooltip[data-owner-tooltip] > .owner-tooltip__trigger:not(.owner-tooltip__trigger--text) {
  background: none;
  cursor: pointer;
  height: var(--owner-space-6);
  width: var(--owner-space-6);
}

.owner-tooltip__glyph {
  align-items: center;
  background: var(--owner-status-draft-bg);
  border-radius: 50%;
  display: inline-flex;
  height: var(--owner-space-4);
  justify-content: center;
  width: var(--owner-space-4);
}

.owner-tooltip__trigger--text {
  align-items: flex-start;
  background: none;
  border-radius: var(--owner-radius-pill);
  color: inherit;
  cursor: pointer;
  flex-direction: column;
  font: inherit;
  height: auto;
  min-height: 40px;
  min-width: var(--owner-space-6);
  text-align: inherit;
  text-transform: none;
  white-space: inherit;
  width: auto;
}

.owner-tooltip__secondary {
  display: block;
  font-size: var(--owner-font-size-sm);
  font-weight: 400;
  line-height: var(--owner-line-height-sm);
}

.owner-tooltip[data-owner-tooltip] > .owner-tooltip__trigger:focus-visible,
.owner-tooltip__trigger--text:focus-visible {
  box-shadow: none;
  outline: 2px solid var(--owner-accent-blue);
  outline-offset: 2px;
}

/* Without an adopting controller, managed markup keeps the CSS baseline.
   Once enabled, closed bubbles cannot add scrollable overflow or reopen
   through :hover/:focus-within after an explicit dismissal. */
[data-owner-tooltips-managed] .owner-tooltip[data-owner-tooltip] > .owner-tooltip__bubble {
  display: none;
}

.owner-tooltip__bubble--managed {
  background: var(--owner-input-bg);
  box-sizing: border-box;
  display: block;
  inset: auto;
  letter-spacing: normal;
  line-height: var(--owner-line-height-sm);
  margin: 0;
  max-height: calc(100dvh - var(--owner-space-7));
  max-width: none;
  min-width: 0;
  opacity: 1;
  overflow: auto;
  overflow-wrap: anywhere;
  overscroll-behavior: contain;
  padding: var(--owner-space-3) var(--owner-space-4);
  pointer-events: auto;
  position: fixed;
  position-anchor: auto;
  position-area: none;
  position-try-fallbacks: none;
  transform: none;
  transition: none;
  white-space: pre-line;
  width: min(320px, calc(100vw - var(--owner-space-7)));
  z-index: 110;
}

/* Right-side slide-in drawer. Server-rendered, anchor-driven via the
   `:target` selector — no JS state, no client framework. Clicking a
   link with `href="#drawer-<id>"` opens the drawer; clicking the
   backdrop or close button (`href="#"`) closes it.

   Caveat: `href="#"` close scrolls to the top of the page on close
   because the URL becomes the empty fragment. Acceptable for short
   pages; for long-scroll pages, consumers can wire a tiny JS handler
   that calls `history.replaceState(null, '', location.pathname +
   location.search)` on close. The CSS contract is unchanged either
   way — just the navigation behavior. Enhanced JS handler can ship
   later if a real consumer needs it.

   Adapted from delmar/apps/owner-ui/components/ui/owner/drawer.tsx. */
.owner-drawer {
  display: none;
}

/* `.is-open` is the JS-managed twin of `:target`, for consumers that
   layer in the focus-trap/replaceState shim the drawer doc anticipates
   (the scorecards drill drawer is the first). Same visual state. */
.owner-drawer:target,
.owner-drawer.is-open {
  display: block;
  inset: 0;
  position: fixed;
  z-index: 100;
}

.owner-drawer__backdrop {
  background: rgba(0, 0, 0, 0.4);
  display: block;
  inset: 0;
  position: absolute;
}

.owner-drawer__panel {
  background: #ffffff;
  bottom: 0;
  display: flex;
  flex-direction: column;
  max-width: 480px;
  position: absolute;
  right: 0;
  top: 0;
  width: 100%;
}

/* A drawer that holds a table, not prose (design review 3, P2-3): half
   the viewport between 480 and 760px, full width on a phone. The
   scorecards drill drawer used to carry this as an app override. */
.owner-drawer__panel--wide {
  max-width: clamp(480px, 50vw, 760px);
  width: clamp(480px, 50vw, 760px);
}

@media (max-width: 600px) {
  .owner-drawer__panel--wide {
    max-width: 100%;
    width: 100%;
  }
}

.owner-drawer__header {
  align-items: center;
  border-bottom: 1px solid var(--owner-card-border);
  display: flex;
  flex: 0 0 auto;
  gap: var(--owner-space-3);
  justify-content: space-between;
  padding: var(--owner-space-5) var(--owner-space-6);
}

.owner-drawer__title {
  color: var(--owner-text-primary);
  font-family: var(--font-kepler);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.5px;
  line-height: 28px;
  margin: 0;
}

.owner-drawer__close {
  align-items: center;
  border-radius: var(--owner-radius-pill);
  color: var(--owner-text-secondary);
  display: inline-flex;
  font-size: 24px;
  height: 32px;
  justify-content: center;
  line-height: 1;
  text-decoration: none;
  transition: background-color 120ms ease, color 120ms ease;
  width: 32px;
}

.owner-drawer__close:hover {
  background: var(--owner-hover-bg);
  color: var(--owner-text-primary);
}

.owner-drawer__body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--owner-space-6);
}

.owner-drawer__footer {
  align-items: center;
  border-top: 1px solid var(--owner-card-border);
  display: flex;
  flex: 0 0 auto;
  gap: var(--owner-space-3);
  justify-content: flex-end;
  padding: var(--owner-space-4) var(--owner-space-6);
}

@media (max-width: 600px) {
  .owner-drawer__panel {
    max-width: 100%;
  }
}

/* Print stylesheet — hides chrome (sidebar, mobile header, top progress
   bar, page-header action buttons) and widens the content shell so a
   printed P&L / Cash Flow / Balance Sheet uses the full page. Cards
   lose their drop-shadow + rounded corners for cleaner B/W output, and
   every <details> opens by default so nothing the user hasn't expanded
   is missing from the printout. */
@media print {
  /* Hide chrome that has no print value: navigation, the loading-bar, and
     submit affordances. Forms themselves stay visible because their
     filled-in values (e.g. "As of: 2024-12-31" on the Balance Sheet) are
     the only place the report's period or scope appears — hiding the form
     would print a Balance Sheet with no indication of when it was as-of. */
  .owner-app-sidebar,
  .owner-sidebar-container,
  .owner-mobile-header,
  .dmv-top-progress,
  .owner-tabs,
  .owner-button {
    display: none !important;
  }

  body,
  .owner-layout {
    background: #fff;
    height: auto;
    overflow: visible;
  }

  .owner-main-area,
  .owner-content-scroll {
    overflow: visible;
  }

  .owner-content-shell {
    max-width: none;
    padding: 0;
  }

  .owner-card,
  .owner-table-card,
  .owner-details,
  .owner-summary-card,
  .panel,
  .metric-grid {
    box-shadow: none;
    border-radius: 0;
  }

  .owner-card,
  .owner-table-card,
  .owner-details {
    page-break-inside: avoid;
  }

  /* `<details>` hides its children when not [open]; force them visible in
     print so a collapsed Balance Sheet section still shows its rows. CSS
     can't toggle the `open` attribute, but overriding the children's
     `display` reaches the same result. */
  .owner-details > *:not(summary) {
    display: block !important;
  }

  /* Strip link decoration — printed pages can't be clicked. */
  a {
    color: inherit;
    text-decoration: none;
  }

  .owner-page-header {
    padding: 0 0 var(--owner-space-3);
  }

  /* Form widgets in the page-header collapse to plain text-on-white;
     their values are still readable, but Air Datepicker chrome and
     focus rings are visual noise on paper. */
  .owner-input,
  .owner-select,
  .owner-date-field__display {
    background: transparent;
    border: 0;
    box-shadow: none;
    padding: 0;
  }
}
