/* ==========================================================
   PROPERTY DASHBOARD: dashboard-local layer

   Cascade order:
     1. design-system.css, tokens, resets, .btn/.field/.checkbox/
                            .switch/.eyebrow/.prose, utilities,
                            ambient glow, scrollbar
     2. components.css, .app shell, .panel, .card, .tbl, .badge,
                            modals, dropdowns, toasts, intel cards,
                            intel-map overlay primitives, volume-trend,
                            plan-card, help-link
     3. dashboard.css, THIS FILE: dashboard-only surfaces:
                            · app body width override
                            · setup wizard, auth landing
                            · import flow, item form
                            · search-queries scope panel
                            · intelligence map frame sizing
                            · placement form + detail
                            · storefront surface config
                            · mobile shell rules
                            · maintenance banner

   Rule of thumb: if admin would want it too, promote to
   components.css. If only the property dashboard needs it, keep
   it here. Surface-specific colors/fonts go through design-
   system.css tokens, never hard-code rgba()/hex.
========================================================== */

/* ==========================================================
   PANEL CARDS · responsive sub-cards inside a .panel
   A grid of raised cards (paper-2, L2) for laying out two or more
   sub-sections within a .panel (L1): side by side when there's room,
   stacking when the panel gets narrow. Shared by the reports
   generators and the partner referral link/code fields. The card is
   flex-column so a child can pin to the bottom with margin-top: auto.
========================================================== */
.panel-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--s-4);
}
.panel-card {
  background: var(--paper-2);
  border-radius: var(--radius-md);
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  /* The card sits at L2, so lift any form control inside it one step
     (L3 resting, L4 hover/focus) via the --field-bg contract in
     design-system.css. Inputs and .copy-input fields inherit this;
     <select> uses a fixed background, so a select-bearing card needs
     its own rule (see reports.css). */
  --field-bg: var(--paper-3);
  --field-bg-hover: var(--paper-4);
}

/* ==========================================================
   APP BODY: sidebar + main grid
   The `.app`, `.app__body`, `.app__sidebar`, and `.app__main` base
   layout all live in shared components, including the content cap +
   centering (full-width scroll container, content centered via inline
   padding). Dashboard uses the default cap (--app-main-max: 68.75rem),
   so there's nothing to override here.
========================================================== */

/* Layout-transparent wrapper for divs that exist only as JS render
   targets (async data, re-render-after-interaction). display: contents
   makes the wrapper invisible to layout; its children become direct
   flex children of .app__main and participate in the gap rhythm. */
.app__slot { display: contents; }

/* "Email" panel row: shows the current verified email next to a
   secondary "Change email" button. */
.user__email-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-2) 0;
}


/* Subscribe-page block-state shown when an install-token claim is
   refused because the User is at their property cap. */
.install-blocked {
  max-width: 36rem;
  margin: var(--s-8) auto;
  padding: 0 var(--gutter);
}
.install-blocked h1 {
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  margin-bottom: var(--s-3);
}
.install-blocked p {
  margin-bottom: var(--s-3);
  line-height: 1.6;
}
.install-blocked p:last-child {
  display: flex;
  gap: var(--s-2);
  flex-wrap: wrap;
}

/* ==========================================================
   AUTH LANDING: login / signup / forgot-password
========================================================== */
.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--s-6);
}
.auth__card {
  max-width: 27.5rem;
  width: 100%;
  background: var(--paper-1);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--s-7);
  box-shadow: var(--shadow-md);
}
/* Brand mark inside an .auth__card uses the shared .app__mark
   styling; only the spacing to the sub-copy below is auth-specific. */
.auth__card > .app__mark { margin-bottom: var(--s-3); }
.auth__sub {
  font-size: var(--t-sm);
  color: var(--ink-mid);
  margin-bottom: var(--s-6);
}
.auth__form { display: flex; flex-direction: column; gap: var(--s-4); }
.auth__form--narrow { max-width: 27.5rem; }
.auth__submit { justify-self: start; margin-top: var(--s-3); }
/* Button row for non-form auth states (invitation confirm / mismatch /
   sign-in prompt). Spacing comes from the preceding .auth__sub margin. */
.auth__actions { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; }

/* Resume-flow banner shown atop the auth card when arriving with an
   active hint param (e.g. ?vq_accept=<token> from accept-invitation). */
.auth__banner {
  /* L2, raised inside the auth card (L1). */
  background: var(--paper-2);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-4);
  font-size: var(--t-sm);
  text-align: center;
}

/* Closed-signup card: shown in place of the signup form when
   platform.signupsEnabled is false. */
.auth__closed { gap: var(--s-3); }
.auth__closed-head {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--t-xl);
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--ink);
  margin: 0;
}
.auth__closed-body {
  font-family: var(--font-body);
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-mid);
  margin: 0;
}
.auth__alt {
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  font-size: var(--t-sm);
  color: var(--ink-mid);
  text-align: center;
}
.auth__alt a {
  color: var(--accent);
  font-weight: 500;
  transition: color 120ms var(--ease);
}
.auth__alt a:hover { color: var(--accent-deep); }

.auth__error {
  margin-top: var(--s-3);
  font-size: var(--t-sm);
  color: var(--status-error);
  min-height: 1em;
}
/* Empty until a message is set (via textContent); collapse so it claims no
   space (and no flex gap) when there's no error. */
.auth__error:empty { display: none; }

.auth__legal {
  margin-top: var(--s-3);
  font-size: var(--t-xs);
  color: var(--ink-mid);
  line-height: 1.5;
}
.auth__legal a { color: var(--accent); text-decoration: none; }
.auth__legal a:hover { color: var(--accent-deep); text-decoration: underline; }

/* ==========================================================
   IMPORT PAGE: side-by-side input + live preview/progress
   Layout: two equal columns on desktop. Left = input (textarea
   + file upload + collapsed format help). Right = live preview
   that cycles through empty / preview / importing / done states
   without displacing the layout. The right column is sticky so
   the summary + Import button stay in view while the textarea
   grows.
========================================================== */
.import-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-5);
  align-items: start;
}
@media (max-width: 56.25em) {
  .import-grid { grid-template-columns: 1fr; }
}


.import-input { margin-bottom: 0; }
.import-input textarea {
  width: 100%;
  min-height: 28rem;
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  line-height: 1.5;
}
.import-input__tools {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.import-input__file { display: none; }
.import-input__file-label { cursor: pointer; }

.import-side {
  position: sticky;
  top: var(--s-4);
  min-height: 28rem;
  margin-bottom: 0;
  /* display:flex inherited from .panel; needed for the
     `margin-top: auto` push on .page__actions to work. */
}
@media (max-width: 56.25em) {
  .import-side { position: static; }
}
.import-side__empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-mid);
  font-style: italic;
  text-align: center;
  padding: var(--s-5);
}
/* margin-top: auto inside the panel's flex column pushes actions
   to the bottom. The panel's gap handles spacing between siblings. */
.import-side .page__actions { margin-top: auto; padding-top: var(--s-4); }

/* Full-width collapsed disclosure below the input/preview grid. */
.format-help {
  margin-top: var(--s-4);
}
.format-help > summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-mid);
  list-style: none;
  padding: var(--s-1) 0;
  user-select: none;
}
.format-help > summary::-webkit-details-marker { display: none; }
.format-help > summary::before {
  content: '▸';
  color: var(--accent);
  display: inline-block;
  width: 1.1em;
  transition: transform var(--dur-1) var(--ease-out);
}
.format-help[open] > summary::before { transform: rotate(90deg); }
.format-help > summary:hover { color: var(--ink); }
.format-help__body {
  /* Spacing above this body comes from the panel's gap; the
     padding-top + border-top is structural decoration only. */
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-6);
  align-items: start;
}
@media (max-width: 56.25em) {
  .format-help__body { grid-template-columns: 1fr; gap: var(--s-4); }
}

.help-list {
  font-size: var(--t-sm);
  line-height: 1.6;
  padding-left: var(--s-4);
  margin: 0;
}
.help-list li + li { margin-top: var(--s-1); }

.example-table {
  font-size: var(--t-xs);
  border-collapse: collapse;
  width: 100%;
}
.example-table td {
  padding: var(--s-2) 0;
  vertical-align: top;
}
.example-table td:first-child {
  font-family: var(--font-mono);
  padding-right: var(--s-4);
  white-space: nowrap;
}

.import-note {
  /* L2, raised inside the import panel (L1). */
  font-size: var(--t-xs);
  color: var(--ink-mid);
  padding: var(--s-3);
  background: var(--paper-2);
  border-radius: var(--radius-md);
  border-left: 2px solid var(--rule);
}
.import-note--danger {
  color: var(--status-error);
  background: var(--status-error-bg);
  border-left-color: var(--status-error);
}
.import-note + .import-note { margin-top: var(--s-3); }

.preview-meta { font-size: var(--t-xs); }
.preview-section__title {
  margin: 0 0 var(--s-2) 0;
  font-size: var(--t-sm);
  font-family: var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mid);
}
.preview-section__title--danger { color: var(--status-error); }
.preview-list {
  font-size: var(--t-sm);
  line-height: 1.7;
  list-style: none;
  padding: 0;
  margin: 0;
  max-height: 14rem;
  overflow-y: auto;
}
.preview-list--tight { font-size: var(--t-xs); line-height: 1.5; max-height: 10rem; }

/* ==========================================================
   ITEM FORM: create / edit surface
========================================================== */
.item-form { display: flex; flex-direction: column; gap: var(--s-5); }
.item-form .field + .field,
.item-form .field + .item-form__row,
.item-form__row + .field,
.item-form__row + .item-form__row { margin-top: 0; }

.item-form__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s-5);
}
@media (max-width: 43.75em) {
  .item-form__row { grid-template-columns: 1fr; }
}

.item-form textarea.description { min-height: 10rem; }

/* In-stock tri-state chip toggle */
.instock-group {
  display: inline-flex;
  gap: var(--s-2);
  padding: var(--s-2) 0;
}

/* Image list */
.image-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin-top: var(--s-2);
}
.image-row {
  display: grid;
  grid-template-columns: var(--s-7) minmax(0, 1fr) auto;
  gap: var(--s-3);
  align-items: center;
}
.image-thumb {
  width: var(--s-7);
  height: var(--s-7);
  border: 1px solid var(--rule);
  background: var(--paper-2);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  color: var(--ink-mid);
  text-align: center;
}
.image-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.image-thumb--error { color: var(--status-error); }
.image-row__remove {
  background: none;
  border: 0;
  color: var(--ink-mid);
  cursor: pointer;
  font-size: var(--t-md);
  padding: var(--s-2);
  line-height: 1;
}
.image-row__remove:hover { color: var(--status-error); }
.image-row input { width: 100%; }
.image-add { align-self: flex-start; margin-top: var(--s-2); }

/* JSON metadata disclosure */
.metadata-details {
  border-top: 1px solid var(--rule);
  padding-top: var(--s-3);
}
.metadata-details > summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-mid);
  list-style: none;
  padding: var(--s-1) 0;
  user-select: none;
}
.metadata-details > summary::-webkit-details-marker { display: none; }
.metadata-details > summary::before {
  content: '▸';
  color: var(--accent);
  display: inline-block;
  width: 1.1em;
  transition: transform var(--dur-1) var(--ease-out);
}
.metadata-details[open] > summary::before { transform: rotate(90deg); }
.metadata-details textarea {
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  min-height: 8rem;
  margin-top: var(--s-3);
}

/* ==========================================================
   API KEYS: masked key preview
========================================================== */
.api-key-mask {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-mid);
}

/* ==========================================================
   SETTINGS LEDE (User/Org settings page intros)
========================================================== */
.settings-lede {
  font-size: var(--t-sm);
  color: var(--ink-mid);
}

/* ==========================================================
   SCOPE PANEL: control + chart instrument on Search queries.
   The filters and volume chart are one mental unit, turn the
   dial, watch the readout, so they share a single .panel
   surface. The internal dashed soft-rule keeps them visually
   linked as controls→readout rather than reading as two
   unrelated stacks. Tighter gap than default .panel because
   they aren't independent sections; they're one instrument.
========================================================== */
.scope-panel { gap: var(--s-3); }
.scope__controls {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.scope__chart {
  /* Dashed rule lives on the chart, not the controls, when the
     chart is hidden (no data, .volume-trend:empty rule) the
     divider disappears with it instead of dangling. */
  padding-top: var(--s-3);
  border-top: 1px dashed var(--rule-soft);
  margin: 0;
}
.scope__chart .volume-trend__skeleton {
  /* Skeleton already gets a surface from the surrounding panel;
     drop its own bg + border so it reads as a placeholder, not a
     framed rectangle inside a framed rectangle. */
  background: transparent;
  border: 0;
}

/* ==========================================================
   INTELLIGENCE MAP FRAME (dashboard-local sizing)
   The overlay primitives (canvas, svg, dot, label, tooltip,
   legend) all live in shared components.css since they're
   identical wherever the VQHeatmap module is consumed. This
   surface owns only the frame's chrome and dimensions.
========================================================== */
.intel-map-frame {
  position: relative;
  width: 100%;
  height: clamp(20rem, 50vh, 35rem);
  background: var(--paper-2);
  /* border: 1px solid var(--rule); */
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* ==========================================================
   INTELLIGENCE: section eyebrow used inside the unified Intelligence
   page (placement detail, demand-vs-supply table).
========================================================== */
.intel-positioning__section {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-mid);
  margin: var(--s-4) 0 var(--s-2);
}

.intel-recent {
  display: flex;
  flex-direction: column;
}
.intel-recent__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: 1px dashed var(--rule-soft);
  text-decoration: none;
  color: inherit;
}
.intel-recent__row:last-child { border-bottom: 0; }
.intel-recent__row:hover .intel-recent__title { color: var(--accent-deep); }
.intel-recent__row.is-active .intel-recent__title { color: var(--accent-deep); font-weight: 500; }
.intel-recent__title {
  font-size: var(--t-sm);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.intel-recent__when {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--ink-mid);
  white-space: nowrap;
}

/* ==========================================================
   INTEL LIST ROW + BAR

   Two-column row used inside intel cards (Close matches,
   Distinctive queries on /intelligence/items/<id>): title + meta
   stack on the left and fill available width; a fixed-width bar
   on the right visualizes the score (similarity, distinctiveness)
   in 11 bucketed steps (--w0 to --w100). The bucket classes are
   emitted by JS so we don't write inline width styles.

   .intel-list-row is rendered as either `<div>` (static, no link)
   or `<a>` (close-matches → links to that item's intel page).
   The :hover treatment only fires on the <a> variant since the
   selector targets `a.intel-list-row`.
========================================================== */
.intel-card__scroll {
  display: flex;
  flex-direction: column;
  max-height: 22rem;
  overflow-y: auto;
}

.intel-list-row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) 0;
  border-bottom: 1px solid var(--rule-soft);
  color: inherit;
  text-decoration: none;
}
.intel-list-row:last-child { border-bottom: 0; }
a.intel-list-row { transition: color var(--dur-fast) var(--ease); }
a.intel-list-row:hover .intel-list-row__title { color: var(--accent-deep); }

.intel-list-row__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.intel-list-row__title {
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.intel-list-row__meta {
  font-size: var(--t-xs);
  color: var(--ink-mid);
  white-space: nowrap;
}

.intel-bar {
  flex: 0 0 5.5rem;
  height: 0.375rem;
  background: var(--paper-2);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.intel-bar__fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-full);
  transition: width var(--dur) var(--ease);
}
.intel-bar__fill--w0   { width: 0%; }
.intel-bar__fill--w10  { width: 10%; }
.intel-bar__fill--w20  { width: 20%; }
.intel-bar__fill--w30  { width: 30%; }
.intel-bar__fill--w40  { width: 40%; }
.intel-bar__fill--w50  { width: 50%; }
.intel-bar__fill--w60  { width: 60%; }
.intel-bar__fill--w70  { width: 70%; }
.intel-bar__fill--w80  { width: 80%; }
.intel-bar__fill--w90  { width: 90%; }
.intel-bar__fill--w100 { width: 100%; }

/* ==========================================================
   POSITIONING MAP PIN

   Marks the projected position of the hypothetical product on the
   catalog map. Drawn over the .intel-map__svg layer alongside the
   anchor dots; the halo softens contrast where the heatmap is hot.
========================================================== */
.positioning-map__pin {
  fill: var(--accent);
  stroke: var(--paper-0);
  stroke-width: 3;
  /* Pin drops in with a brief overshoot, like setting a survey
     marker on the chart. transform-origin is the dot center so the
     scale doesn't drift. */
  transform-origin: center;
  transform-box: fill-box;
  animation: placement-pin-drop 320ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.positioning-map__pin-halo {
  fill: none;
  stroke: var(--paper-0);
  stroke-width: 3;
  stroke-opacity: 0.85;
  transform-origin: center;
  transform-box: fill-box;
  animation: placement-pin-halo 600ms ease-out both;
}
@keyframes placement-pin-drop {
  0%   { transform: scale(0); }
  60%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}
@keyframes placement-pin-halo {
  0%   { transform: scale(0.4); stroke-opacity: 0.95; }
  100% { transform: scale(1);   stroke-opacity: 0.85; }
}

/* ==========================================================
   MAP CONTROLS ROW
   The row above the map frame uses the shared `.toolbar`
   (components.css). "Test a product" lives in the panel head
   below (rendered by intelligence.js) rather than on this row,
   so the row is purely the View / Period / Queries selectors.
   This override widens those selects so dimension names and
   lookback period labels don't cramp.
========================================================== */
.intel-map__toolbar select {
  min-width: 12rem;
}

/* ==========================================================
   PLACEMENT FORM
   The collapsible "Test a product" form panel sits between the
   controls row and the map frame on /intelligence/. Closed by
   default. Opens via the controls-row opener button, closes via
   its own X (top-right corner) or Cancel. Enters with a fast fade
   + slight rise so it feels like a slip being laid on the desk.
========================================================== */
.placement-form {
  position: relative;
  animation: placement-form-in 200ms ease-out both;
}
@keyframes placement-form-in {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}
.placement-form__close {
  position: absolute;
  top: var(--s-3);
  right: var(--s-3);
  width: 1.75rem;
  height: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color 120ms var(--ease), background 120ms var(--ease);
}
.placement-form__close:hover {
  color: var(--ink);
  background: var(--paper-2);
}
.placement-form__close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.placement-form__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--accent-deep);
}
.placement-form__hint {
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-mid);
  max-width: 60ch;
  margin: 0;
}

/* ==========================================================
   PLACEMENT DETAIL
   Two-column annotation that appears below the map after a
   placement is dropped. Left column (marker) is the placement
   annotation card, accent leading rule, the typed title, the
   description echo, confidence, and the clear action. Right
   column (reading) is the narrative + best-fit list.
========================================================== */
.placement-detail {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: var(--s-5);
  margin-top: var(--s-5);
  animation: placement-detail-in 240ms ease-out both;
}
@media (max-width: 56em) {
  .placement-detail { grid-template-columns: 1fr; }
}
@keyframes placement-detail-in {
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}
.placement-detail__marker {
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  /* Accent rule on the leading edge marks this card as the
     placement annotation (paired with the pin on the map). */
  border-left: 3px solid var(--accent);
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  align-self: start;
}
.placement-detail__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ink-mid);
}
.placement-detail__eyebrow--spaced {
  margin-top: var(--s-4);
}
.placement-detail__title {
  font-family: var(--font-display);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.25;
  color: var(--ink);
}
.placement-detail__desc {
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-mid);
}
.placement-detail__confidence {
  font-size: var(--t-xs);
  color: var(--ink-mid);
}
.placement-detail__confidence-k {
  font-family: var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-right: var(--s-2);
}
.placement-detail__reading {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.placement-detail__narrative {
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--ink);
  max-width: 64ch;
}

.positioning-map__filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* ==========================================================
   INTELLIGENCE: lens rows

   Each lens row inside a panel (the demand-vs-supply table, the
   placement-detail candidate list) is its own flex column so the
   head, narrative, and meta line don't smash into each other.
========================================================== */
.intel-lens-row {
  display: flex;
  flex-direction: column;
  /* Horizontal padding is intentionally 0: every consumer wraps these
     rows in a `.panel`, which already provides horizontal padding. The
     dashed row separator extends to the panel's inner edge. */
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--rule-soft);
  gap: var(--s-1);
}
.intel-lens-row:first-child { padding-top: 0; }
.intel-lens-row:last-child { border-bottom: 0; padding-bottom: 0; }
.intel-lens-row__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  flex-wrap: wrap;
}
.intel-lens-row__name {
  font-size: var(--t-md);
  font-weight: 800;
  color: var(--ink);
}
.intel-lens-row__score {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  color: var(--ink-mid);
  white-space: nowrap;
}
.intel-lens-row__narrative { color: var(--ink-ghost); }
.intel-trend {
  font-family: var(--font-mono);
  font-size: var(--t-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.intel-trend--rising  { color: var(--accent); }
.intel-trend--cooling { color: var(--ink-mid); }
.intel-callout {
  background: var(--paper-2);
  border: 1px solid var(--rule-soft);
  padding: var(--s-4);
  font-size: var(--t-sm);
  line-height: 1.55;
  color: var(--ink-mid);
}
.intel-callout strong { color: var(--ink); font-weight: 500; }

/* ==========================================================
   Connection card (integration status).
   Layout uses .panel + .panel__head; the only custom rule is
   the inline group of state pieces (badge, domain, since-date)
   that reads as one sentence next to the action button.
========================================================== */
.connection-card__status {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  min-width: 0;
}
/* ==========================================================
   Storefront surface panels
   Search is the only surface with a dashboard panel, it's a takeover
   (singleton, recurring per-query cost), so all its config lives here.
   The other embeds (Similar Items, Smart Categories, Curated Items)
   are multi-instance theme app blocks; their per-instance settings
   (heading, density, etc.) live in the theme editor. The
   panel head holds a cluster of actions (Search has an enabled toggle
   next to its Save; the embed-status panel has theme-editor link +
   Refresh). Search subdivides into sub-sections (Filters / Layout /
   Card extras) so 10+ controls don't read as one wall of fields.
   Other surface panels skip
   sub-sections, fields go straight under the panel head.
========================================================== */
/* Cluster in the panel head for the per-panel actions. Holds Save and,
   on Search, the enabled toggle; on the embed-status panel, the theme-
   editor link and Refresh button. */
.surface-actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

/* Sub-section inside a surface panel. Used in Search to break the
   Layout / Filters / Card extras grouping; other panels skip the
   wrapper and put fields directly under the head. */
.surface-subgroup + .surface-subgroup {
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
}
.surface-subgroup__title {
  font-family: var(--font-display);
  font-size: var(--t-lg);
  margin: 0;
  line-height: 1.2;
}
.surface-subgroup__fields {
  margin-top: var(--s-3);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-5);
}
@media (max-width: 600px) {
  .surface-subgroup__fields {
    grid-template-columns: 1fr;
  }
}
/* Inline fields (label + switch on one line) occupy one cell of the
   2-column grid like any other field. Two toggles pair up; an
   odd-numbered trailing toggle sits alone in its cell next to the
   next field. `justify-content: space-between` anchors the switch to
   the cell's right edge regardless of label length. */
.surface-subgroup__fields .field--inline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.surface-subgroup__fields .field--inline .field__k {
  display: block;
}
/* Full-width field: spans both columns of the sub-group grid. Used
   for inline-toggle rows whose label is long enough that boxing them
   into half a row would look awkward. */
.surface-subgroup__fields .field--full {
  grid-column: 1 / -1;
}

/* Toggle row inside a field: label on the left, switch anchored
   right. Used when a toggle needs a hint paragraph underneath, the
   hint sits as a sibling below this row, so the label/switch stay
   aligned regardless of how long the hint runs. */
.field__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}

/* Sub-sub grouping inside a surface sub-group: one cell of the
   sub-group's 2-column grid, with its own vertical stack of fields.
   Used in Search > Card extras to put the "More like this" controls
   in one column and "Add to cart" in the other, side by side. Each
   col is content-dense (toggle row + input + hint), so when the
   parent contains cols we swap the parent's fixed 2-col grid for
   an auto-fit that wraps to a single column whenever a col would
   drop below ~320px. */
.surface-subgroup__col {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.surface-subgroup__fields:has(> .surface-subgroup__col) {
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}

/* Embed-status panel: list of theme blocks (signals / search /
   similar / categories) with an Added / Not added / Checking badge
   per row. No longer collapsible, it's its own panel now. */
.embed-status__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.embed-status__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
}
/* Label (name + hint) takes the remaining width and wraps inside;
   the badge stays anchored on the right. `min-width: 0` is what
   actually lets a long hint break instead of forcing the row to
   overflow and the badge to fall to the next line. */
.embed-status__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  flex-wrap: nowrap;
  flex-direction: column;
}
.embed-status__row > .badge { flex: 0 0 auto; }
.embed-status__name {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--t-sm);
  color: var(--ink);
  font-weight: 500;
}
.embed-status__hint { min-width: 0; }


/* ==========================================================
   Theme panel: Re-adapt button (in panel head) + preview pane.
   The preview row renders inside a Shadow DOM (vq-host) hosted
   directly in this pane, so the embed's CSS is isolated from
   the dashboard chrome without an iframe. The pane provides only
   surrounding chrome + a minimum height so empty states look right.
========================================================== */
.theme-adapt-preview__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
  flex-wrap: wrap;
}
.theme-adapt-preview__pane {
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  padding: var(--s-3);
  min-height: 15rem;
  /* White canvas is intentional, the storefront embed renders here
     inside a Shadow DOM, and the embed expects to sit on a light
     page background like a real product page. */
  background: #fff;
}

/* In-flight + failure messages above the preview pane. Plain text
   sits between the panel body copy and the preview; no chip / banner
   chrome so it reads as an inline progress note. */
.theme-adapt-status {
  display: block;
  margin: var(--s-2) 0;
}
.theme-adapt-status--err {
  color: var(--status-error);
}

/* Theme customize editor: disclosure under the preview that exposes
   the full token list for power-user / agency tuning. Two-column
   responsive grid of stacked rows (label + variable name + input).
   The panel's gap supplies the top spacing; the rule is structural
   (a section break inside the panel). */
.theme-customize {
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}
.theme-customize summary {
  cursor: pointer;
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-mid);
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  user-select: none;
}
.theme-customize summary::-webkit-details-marker { display: none; }
.theme-customize summary::before {
  content: "+";
  display: inline-block;
  width: 1ch;
  font-weight: 600;
  color: var(--ink);
}
.theme-customize[open] summary::before { content: "−"; }
.theme-customize summary:hover { color: var(--ink); }
.theme-customize__count {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0;
  text-transform: none;
  color: var(--accent);
}

.theme-customize__body {
  margin-top: var(--s-3);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.theme-customize__body > p { margin: 0; max-width: 64ch; }

.theme-customize__group {
  border: 1px solid var(--rule);
  padding: var(--s-3) var(--s-4);
  margin: 0;
}
.theme-customize__group legend {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-mid);
  padding: 0 var(--s-2);
}
.theme-customize__rows {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--s-4);
}
/* Each row is a `.field` for consistent input chrome (border, padding,
   hover/focus states); the only override here is `min-width: 0` so the
   column can shrink without the input forcing horizontal overflow on
   narrow widths. */
.theme-customize__row { min-width: 0; }
.theme-customize__label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
  flex-wrap: wrap;
  cursor: pointer;
}
.theme-customize__name {
  font-size: var(--t-sm);
  color: var(--ink);
}
.theme-customize__var {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--ink-mid);
  word-break: break-all;
}
/* Token values are CSS strings: keep them in mono and at sm size so
   long values like `1px solid rgba(14, 27, 77, 1)` stay readable in a
   narrow column. */
.theme-customize__row input {
  font-family: var(--font-mono);
  font-size: var(--t-sm);
}

.theme-customize__footer {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  flex-wrap: wrap;
}

/* ==========================================================
   MOBILE SAFETY NET (dashboard-shell parts)
   Component-shaped mobile rules (panel padding, table scroll
   containment) live in components.css. This block only handles
   the dashboard's own shell: header tightening, sidebar reshape
   into a horizontal scroll strip, main column gutter.
========================================================== */
@media (max-width: 51.25em) {
  .app__header {
    padding: var(--s-3) var(--s-4);
    gap: var(--s-3);
    flex-wrap: wrap;
  }

  /* Sidebar: instead of disappearing at the mobile breakpoint (which
     stranded users without nav), reshape it into a horizontal-scroll
     strip under the header. No JS, no hamburger. */
  .app__body { grid-template-columns: 1fr; }
  .app__sidebar {
    display: block;
    position: static;
    background: var(--paper-0);
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    padding: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .app__sidebar ul {
    flex-direction: row;
    gap: 0;
    width: max-content;
    min-width: 100%;
    padding: 0 var(--s-3);
  }
  .app__sidebar a {
    border-bottom: 2px solid transparent;
    padding: var(--s-3) var(--s-3);
    white-space: nowrap;
    font-size: var(--t-2xs);
  }
  .app__sidebar a.is-active {
    border-bottom-color: var(--accent);
    background: transparent;
  }
  /* Crosshair monument is a left-rail indicator; on the mobile
     horizontal scroll strip the bottom-rule does the work instead. */
  .app__sidebar a.is-active::before { display: none; }
  /* Bottom-pinned support group (Help + Report an issue) doesn't fit
     the horizontal scroll-strip; user can still open the modal from
     the user menu via the Help link surfaces (or future inline
     error-toast affordances). */
  .nav__group--pin-bottom { display: none; }
  /* Paper-card treatment on nav groups is desktop-only. On the
     horizontal scroll strip the cards would stack awkwardly. */
  .nav__group {
    background: transparent;
    border-radius: 0;
    padding: 0;
  }
  .app__main { padding: var(--s-5) var(--s-4); }
}

/* Very narrow phones: tighter still. */
@media (max-width: 30em) {
  .app__main { padding: var(--s-4) var(--s-3); }
  .app__header { padding: var(--s-2) var(--s-3); }
}

/* Maintenance banner, painted by API.js on the first 503 MAINTENANCE_MODE
   response. Persistent (no dismiss UI) until the user reloads the page,
   so subsequent failed calls don't re-stack. */
.vq-maintenance-banner {
  position: sticky;
  top: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  padding: var(--s-3) var(--s-5);
  background: var(--status-warning-bg);
  color: var(--status-warning);
  border-bottom: 1px solid var(--status-warning-border);
  font-size: var(--t-sm);
  text-align: center;
}
.vq-maintenance-banner strong {
  font-weight: 600;
  color: var(--status-warning);
}
.vq-maintenance-banner__sub {
  opacity: 0.75;
  font-size: var(--t-xs);
}


/* ==========================================================
   Merchandising, picker modal
   Two rules: widen the shared .modal scaffold to fit a card grid,
   and shape the per-result product tile (image + title + meta).
   Everything else reuses .modal, .card-grid, .card, .chip, .field,
   .dashboard-toolbar, .switch, .badge, .empty, .muted, .callout.
========================================================== */

/* Picker variant of .modal, wider + taller than the default
   confirm/edit modal so a result grid breathes. Inherits all the
   shared shell styling (radius, padding, head/body/foot dividers,
   scroll, shadow). */
.modal--picker {
  width: 80vw;
  max-width: 80vw;
  /* Pinned height, same dialog footprint whether the results pane is
     empty, has 3 hits, or 50. Without this the modal jumps as the
     merchant types. Capped at 85vh so small viewports still fit. */
  height: 80vh;
  display: flex;
  flex-direction: column;
  /* Matches the .panel gap rhythm, spaces head, tabs, body,
     selection tray, and foot without each child needing its own
     margin. */
  gap: 1rem;
}
.modal--picker .modal__body {
  flex: 1 1 auto;
  overflow-y: auto;
  /* Stack input + results with 1rem spacing inside the body. */
  display: flex;
  flex-direction: column;
  gap: 1rem;
  /* The shared .modal__body has a 1.25rem bottom margin; we're
     using flex-gap on the parent instead, so cancel the legacy
     margin here. */
  margin-bottom: 0;
}

/* Compact thumbnail used inside picker + playground result rows.
   Items render in a regular .tbl table, the thumb sits in the
   leading cell next to the title. Sized to ~48px so it's
   recognizable without dominating the row. Falls back to a flat
   paper-2 square when an item has no image. */
.merch-thumb {
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-sm);
  background: var(--paper-2);
  overflow: hidden;
  flex-shrink: 0;
}
.merch-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Head-level on/off toggle on the merch rule editor. Lives inside
   .page__actions so it sits opposite the title. Combines an inline
   "On" / "Off" state label with the standard .switch component. */
.merch-edit-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  cursor: pointer;
  user-select: none;
}
.merch-edit-toggle__state {
  font-weight: var(--font-weight-medium);
  color: var(--ink-muted);
  min-width: 1.75rem;
  text-align: right;
}
.merch-edit-toggle__state.is-on { color: var(--ink); }

/* Danger zone, used on edit pages for destructive actions
   (delete rule, etc.). A muted-border panel set apart from the
   primary save flow so destructive clicks aren't adjacent to the
   save button. */
.panel--danger {
  border-color: var(--danger-border, var(--paper-3));
}
.panel--danger .panel__title {
  color: var(--ink-danger, var(--ink));
}

/* ==========================================================
   LEGAL ACCEPTANCE PAGE  (/legal/)

   Standalone page the user is redirected to when the gating
   middleware on /vq/* returns 403 OUTSTANDING_ACCEPTANCE, or when
   Common.initPage / initUserPage sees `me.legal.hasBlocking` from
   the eager /vq/auth/me check. Mirrors the auth-required redirect
   shape so the same intuition covers both blockers.

   Layout: standard `app` shell with the brand header only (no
   sidebar, there's nothing the user can do besides accept or
   sign out). Card listing each outstanding doc; per-doc Read +
   Accept; once all accepted, page redirects to ?next= (or /home/).
========================================================== */
.legal-page {
  width: 100%;
  max-width: 36rem;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}
.legal-page__docs {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.legal-page__hint {
  font-size: var(--t-xs);
  color: var(--ink-mid);
  margin: 0;
}
.legal-page__hint a { color: var(--accent); }
.legal-page__doc {
  background: var(--paper-1);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  padding: var(--s-4);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.legal-page__doc-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--t-3xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.legal-page__doc-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
}
.legal-page__doc-text {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}
.legal-page__doc-title {
  font-family: var(--font-display);
  font-size: var(--t-md);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink);
  margin: 0;
}
.legal-page__link {
  font-size: var(--t-sm);
  color: var(--accent);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  align-self: flex-start;
}
.legal-page__link:hover { color: var(--accent-deep); }
.legal-page__doc.is-accepted {
  opacity: 0.55;
}
.legal-page__doc-status {
  font-family: var(--font-mono);
  font-size: var(--t-3xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--status-success);
}
.legal-page__footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  font-size: var(--t-xs);
  color: var(--ink-mid);
}
.legal-page__footer a { color: var(--accent); }

/* Drill-out trail (header): the base .app__crumbs/.app__crumb styles now live once
   in the shared cdn components.css (used by dashboard + admin). Only the
   dashboard-specific mobile override remains, in the responsive section below. */

/* Standard in-page drill-out affordance: a lightweight back crumb above a page
   title, used to back out of a drilled-in view. Works identically as an <a>
   (navigate up, e.g. watcher results "← Watchers") or a <button> (reset in-page
   drill state, e.g. the Map's "← All regions"). One look for the whole app. */
.page__back {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  margin-bottom: var(--s-2);
  padding: 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--t-sm);
  line-height: 1.2;
  color: var(--ink-mid);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.page__back:hover { color: var(--accent); }
.page__back:focus-visible { outline: var(--rule-w) solid var(--accent); outline-offset: 0.25rem; border-radius: var(--radius-sm); }

/* ==========================================================
   Index pages ( / , /org/, /property/ ), three equal stacked panels (Properties, Organizations,
   User), standard panel treatment, no featured weighting. Links inherit
   the global accent treatment; only layout lives here.
   ========================================================== */
.app__main--index { display: block; }
.index { display: flex; flex-direction: column; gap: var(--s-6); }
.index__head { display: flex; flex-direction: column; gap: var(--s-1); }
.index__panels { display: flex; flex-direction: column; gap: var(--s-5); }
.index__links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

/* The Sign-out <button> can't inherit the global a{} rule, so it adopts
   the same accent link tone explicitly. */
.linklike {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-align: left;
}

/* Billing "Current plan" hero, tier name prominent with the price (or
   complimentary note) beside it; the status badge sits in the panel head. */
.plan-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  padding: var(--s-1) 0 var(--s-3);
}
.plan-summary__tier {
  font-family: var(--font-display);
  font-size: var(--t-xl);
  font-weight: 600;
  line-height: 1.1;
  color: var(--ink);
}
.plan-summary__price {
  font-size: var(--t-sm);
  color: var(--ink-mid);
}

@media (max-width: 51.25em) {
  /* On header wrap, the breadcrumb takes its own full-width row. */
  .app__crumbs { order: 3; flex-basis: 100%; }
}

/* ==========================================================
   EVENT / SUBJECT DETAIL (EVENT_DETAIL Step 2)
   The A-first "story of this one thing" rendered by event-detail.js into a
   same-document view on the Map and watcher pages.
========================================================== */
/* A reading column, so long descriptions wrap at a comfortable measure instead
   of sprawling across the wide map layout. */
.ed { display: flex; flex-direction: column; gap: var(--s-7); max-width: 60rem; }
.ed-mono { font-family: var(--font-mono); }

/* Record header: the identity as one unit. A signal pill (the record's most
   recent event, in its signal color) sits above the address; the value + date
   are a right-aligned stat. One deliberate spot of color, driven by --signal. */
.ed-record { display: flex; justify-content: space-between; gap: var(--s-5); align-items: flex-start; flex-wrap: wrap; }
.ed-record__main { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
.ed-record__addr {
  font-family: var(--font-display); font-weight: var(--font-weight-semibold);
  font-size: clamp(1.75rem, 4vw, 2.5rem); line-height: 1.1; letter-spacing: -0.02em;
  color: var(--ink); margin: 0; text-wrap: balance;
}
.ed-record__meta { color: var(--ink-mid); font-size: var(--t-sm); margin: 0; }
.ed-signal { display: inline-flex; align-items: center; gap: var(--s-2); }
.ed-signal__dot {
  width: 0.5rem; height: 0.5rem; border-radius: var(--radius-full); background: var(--signal);
  box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--signal) 20%, transparent);
}
.ed-signal__label {
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: var(--font-weight-semibold); color: var(--signal);
}
.ed-stat { display: flex; flex-direction: column; align-items: flex-end; gap: var(--s-1); text-align: right; }
.ed-stat__val { font-size: var(--t-2xl); font-weight: var(--font-weight-semibold); color: var(--ink); font-variant-numeric: tabular-nums; }
.ed-stat__label { font-size: var(--t-sm); color: var(--ink-mid); }

.ed__sec { display: flex; flex-direction: column; gap: var(--s-3); }
/* A button that is a direct child of a flex-column section would stretch full
   width; keep link-outs natural-width and left-aligned. */
.ed__sec > .btn { align-self: flex-start; }
.ed__h {
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-mid); font-weight: var(--font-weight-semibold); margin: 0;
}

/* Timeline (A): the subject's own change events, newest first, on a vertical
   rail so the sequence reads as a sequence. */
.ed-timeline { list-style: none; margin: 0; padding: 0; position: relative; display: flex; flex-direction: column; gap: var(--s-4); }
.ed-timeline::before { content: ''; position: absolute; left: 0.34rem; top: 0.5rem; bottom: 0.5rem; width: var(--rule-w); background: var(--rule-soft); }
.ed-tl { position: relative; padding-left: var(--s-6); }
.ed-tl__dot {
  position: absolute; left: 0; top: 0.3rem; width: 0.75rem; height: 0.75rem;
  border-radius: var(--radius-full); box-shadow: 0 0 0 0.2rem var(--paper-0);
}
.ed-tl__body { min-width: 0; }
.ed-tl__top { display: flex; justify-content: space-between; gap: var(--s-3); align-items: baseline; }
.ed-tl__label { color: var(--ink); font-weight: var(--font-weight-medium); }
.ed-tl__val { color: var(--ink); font-variant-numeric: tabular-nums; }
.ed-tl__sub { display: flex; gap: var(--s-3); align-items: center; color: var(--ink-mid); font-size: var(--t-sm); }
.ed-tl__when { font-family: var(--font-mono); }
.ed-tl__status { color: var(--ink-mid); }
.ed-tl__delta { margin-top: var(--s-1); }

/* Body: an aligned definition list; record identifiers (permit no., APN) in
   mono because they are codes and they line up. */
.ed-kv { display: grid; grid-template-columns: minmax(9rem, 14rem) 1fr; gap: var(--s-2) var(--s-5); align-items: baseline; margin: 0; }
.ed-kv__k { color: var(--ink-mid); font-size: var(--t-sm); }
.ed-kv__v { color: var(--ink); margin: 0; min-width: 0; }
.ed-kv__v--id { font-family: var(--font-mono); font-size: var(--t-sm); }

/* Parties + owner as discrete cards. */
.ed-parties { display: flex; flex-direction: column; gap: var(--s-2); }
.ed-party, .ed-owner { padding: var(--s-3); background: var(--paper-1); border: var(--rule-w) solid var(--rule-soft); border-radius: var(--radius-md); }
.ed-party { display: flex; justify-content: space-between; gap: var(--s-4); align-items: center; }
.ed-party__who { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.ed-party__role { font-size: var(--t-2xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-mid); }
.ed-party__name { color: var(--ink); font-weight: var(--font-weight-medium); }
.ed-party__lic { font-size: var(--t-xs); color: var(--ink-mid); font-family: var(--font-mono); }
.ed-owner { display: flex; flex-direction: column; gap: var(--s-2); align-items: flex-start; }
.ed-owner__name { color: var(--ink); font-weight: var(--font-weight-medium); display: flex; gap: var(--s-2); align-items: center; }
