/* Napoli CRM — plain, high-contrast styles for a slow connection and no client-side JS. */

:root {
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
    sans-serif;
  --color-bg: #f5f6f7;
  --color-surface: #ffffff;
  --color-border: #dcdfe3;
  --color-text: #1b1f24;
  --color-text-muted: #5c6773;
  --color-accent: #33628a;
  --color-accent-contrast: #ffffff;
  --color-warn-bg: #fdf6e6;
  --color-warn-border: #c9962f;
  /* Danger is a separate lane from accent on purpose: accent marks links and
     primary actions, danger marks things that are actually wrong (a lease
     that would breach exclusivity, an SLA past due). If the two ever share a
     colour, a real warning reads as an ordinary link. */
  --color-danger: #b3261e;
  --color-danger-bg: #fdeceb;
  --color-danger-border: #e5a29c;
  --color-ok: #2f6b4f;
  font-size: 16px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  line-height: 1.5;
}

a {
  color: var(--color-accent);
}

h1,
h2 {
  font-family: var(--font-sans);
  line-height: 1.25;
}

h1 {
  font-size: 1.6rem;
  margin: 1.25rem 0 0.85rem;
}

h2 {
  font-size: 1.15rem;
  margin: 0 0 0.65rem;
}

main {
  margin: 0 auto;
  max-width: 60rem;
  padding: 0 1.25rem 2.5rem;
}

/* Board-like pages opt out of the reading-width cap -- a kanban squashed into
   60rem is unusable once a department has more than a few columns. Only the
   cap changes; centering and padding stay with the base rule above. */
main.main-wide {
  max-width: none;
}

/* ── header ─────────────────────────────────────────────────────────────── */

/* Two calm rows: brand + prototype note + role pills on top, then the tab
   strip sitting on the header's bottom border. The tab strip never wraps —
   on a narrow window it scrolls sideways, because a nav squashed onto two
   lines reads as clutter, not navigation. */
.site-header {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  padding: 0.75rem 1.25rem 0;
}

.header-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem 1.5rem;
}

.brand-group {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.9rem;
  min-width: 0;
}

.brand {
  font-family: var(--font-sans);
  font-size: 1.35rem;
  font-weight: bold;
  color: var(--color-accent);
}

.prototype-note {
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

.main-nav {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.45rem;
  overflow-x: auto;
  scrollbar-width: thin;
  font-size: 1rem;
}

.main-nav a {
  padding: 0.4rem 0.4rem 0.5rem;
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.main-nav a:hover,
.main-nav a:focus {
  border-bottom-color: var(--color-accent);
}

.main-nav a.nav-link-current {
  border-bottom-color: var(--color-accent);
  font-weight: bold;
}

.role-switcher {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.9rem;
}

.role-switcher-label {
  color: var(--color-text-muted);
}

.role-link {
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  text-decoration: none;
  color: var(--color-text);
  background: var(--color-surface);
}

.role-link-current {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-contrast);
  font-weight: bold;
}

.prototype-banner {
  max-width: 60rem;
  margin: 0.75rem auto 0;
  padding: 0.6rem 0.9rem;
  background: var(--color-warn-bg);
  border: 1px solid var(--color-warn-border);
  border-radius: 0.3rem;
  font-size: 0.95rem;
  color: var(--color-text);
}

/* ── unit inventory ─────────────────────────────────────────────────────── */

.unit-group {
  margin: 1.75rem 0;
}

.count {
  color: var(--color-text-muted);
  font-weight: normal;
}

.empty-state {
  color: var(--color-text-muted);
  font-style: italic;
}

/* Wide tables scroll inside their own box rather than the page — the page
   itself must never scroll sideways on a narrow screen. */
.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  background: var(--color-surface);
}

table {
  width: 100%;
  border-collapse: collapse;
  min-width: 32rem;
}

th,
td {
  text-align: left;
  padding: 0.6rem 0.85rem;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

th {
  font-family: var(--font-sans);
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-muted);
  background: var(--color-bg);
}

tr:last-child td {
  border-bottom: none;
}

tr:hover td {
  background: var(--color-bg);
}

/* ── sheet tabs ──────────────────────────────────────────────────────────── */

/* Styled after literal spreadsheet tabs, since /sheets exists to reproduce
   Mutinta's own four tracker tabs: attached along a shared baseline, the
   current tab popped forward and merged seamlessly into the panel below it. */
.sheet-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin: 1.25rem 0 0;
  border-bottom: 1px solid var(--color-border);
}

.sheet-tab {
  position: relative;
  top: 1px;
  padding: 0.55rem 1rem;
  border: 1px solid var(--color-border);
  border-bottom: none;
  border-radius: 0.4rem 0.4rem 0 0;
  background: var(--color-bg);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 0.95rem;
}

.sheet-tab:hover,
.sheet-tab:focus {
  color: var(--color-text);
}

.sheet-tab-current {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-surface);
  color: var(--color-text);
  font-weight: bold;
}

.sheet-panel {
  margin: 0 0 1.75rem;
  padding: 1.25rem;
  border: 1px solid var(--color-border);
  border-top: none;
  border-radius: 0 0.4rem 0.4rem 0.4rem;
  background: var(--color-surface);
}

/* Same three tiers as .board-card-fresh/aging/stale, tuned for a dense table
   cell rather than a card — a left border disappears in a packed row, so
   this is a background tint instead (fresh gets none; it is the quiet
   default). See cardStaleness — ACTIVE_TENANCY rows get none of these three
   classes at all, deliberately. */
.sheet-cell-aging {
  background: var(--color-warn-bg);
}

.sheet-cell-stale {
  background: var(--color-danger-bg);
  color: var(--color-danger);
  font-weight: bold;
}

/* The LC signal column's quiet case — 'aligned' gets a small positive tick,
   never as loud as the <mark> the 'shifted'/'no_lc_recorded' cases use. */
.lc-signal-ok {
  color: var(--color-ok);
}

/* The Advanced/Simple toggle between the sheet tables and the board — one
   small link in each view's header area, right-aligned so it never competes
   with the page's own title for attention. */
.view-toggle {
  margin: 0.75rem 0 0;
  text-align: right;
  font-size: 0.9rem;
}

/* ── status markers ─────────────────────────────────────────────────────── */

/* Semantic <mark> for breached SLAs, invalid documents, and override reasons
   (see deals.tsx, deal-detail.tsx). These are load-bearing warnings — a
   breached SLA is what tells Accounts they're billing from the wrong date —
   so they get the danger tokens, never the browser's default yellow
   highlight and never the accent colour used for links. */
mark {
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-radius: 0.2rem;
  padding: 0.05rem 0.3rem;
  color: var(--color-danger);
  font-weight: bold;
}

/* ── buttons ────────────────────────────────────────────────────────────── */

.button-primary,
.button-secondary,
.button-danger {
  display: inline-block;
  font: inherit;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  padding: 0.5rem 1rem;
  border-radius: 0.3rem;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
}

.button-primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-contrast);
}

/* A lower-stakes action than button-primary (e.g. leaving a note, per
   nap-ybr.8's unresolved queue) — bordered, not filled, so it never
   competes with the page's real primary action for attention. */
.button-secondary {
  background: var(--color-surface);
  border-color: var(--color-border);
  color: var(--color-text);
}

.button-danger {
  background: var(--color-surface);
  border-color: var(--color-danger);
  color: var(--color-danger);
}

.button-primary:hover,
.button-primary:focus,
.button-secondary:hover,
.button-secondary:focus,
.button-danger:hover,
.button-danger:focus {
  opacity: 0.85;
}

/* ── verdict banner ─────────────────────────────────────────────────────── */

.verdict-banner {
  padding: 0.75rem 1rem;
  border-radius: 0.4rem;
  margin: 0 0 1rem;
}

/* Blocks a lease that would breach an exclusivity clause — this has to read
   as unambiguously wrong, never as a tinted version of the link/accent
   colour. */
.verdict-banner-block {
  background: var(--color-danger-bg);
  border: 2px solid var(--color-danger);
  color: var(--color-text);
}

/* ── department boards ──────────────────────────────────────────────────── */

/* Stage columns read left to right as the process itself, so they scroll
   horizontally as one row rather than wrapping into a grid — wrapping would
   break the left-to-right reading order the operator asked for. A narrow
   viewport instead stacks them into full-width blocks (see the media query
   below), each one still fully readable, just further down the page. */
.board-columns {
  display: flex;
  align-items: start;
  gap: 1.75rem;
  margin: 1.25rem 0;
  overflow-x: auto;
  padding: 0 0.15rem 0.65rem;
}

.board-column {
  position: relative; /* anchors the connector arrow in ::after below */
  flex: 1 1 15rem;
  min-width: 15rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  padding: 0.75rem;
}

/* Inbox and Outbox are handover PLACES, not workflow steps (see
   BoardColumnKind) — dashed rather than solid, so the difference survives a
   monochrome screen and doesn't lean on colour alone. Neither gets the
   numbered step badge board.tsx gives real stage columns. */
.board-column-inbox,
.board-column-outbox {
  border-style: dashed;
  background: var(--color-bg);
}

/* Outbox is the one handover column a drop actually acts on — the deal is
   done at this department's last stage, and dropping it here is the real
   move to the next one. Kept at the accent colour so it still reads as
   "live", not merely "different" from Inbox. */
.board-column-outbox {
  border-color: var(--color-accent);
}

/* An empty column carries no cards worth the same width as a busy one, so it
   collapses to a slim fixed strip and lets real work have the room instead —
   overrides the flexible 15rem sizing above, deliberately not min-width so
   it cannot be stretched back out by the flex row. Header and count stay
   visible; the empty-state sentence does not fit the strip and is hidden
   below rather than dropped from markup. */
.board-column-empty {
  flex: 0 0 3.5rem;
  width: 3.5rem;
  min-width: 0;
  padding: 0.5rem 0.35rem;
}

.board-column-empty .empty-state {
  display: none;
}

/* The header goes vertical instead of truncating — at 3.5rem wide, even a
   short label like "Inbox ← Marketing" would truncate to one or two
   characters, which is no more readable than nothing. Vertical text plus the
   title attribute (see board.tsx) keeps the label genuinely legible. */
.board-column-empty .board-column-header {
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
}

.board-column-empty .board-column-header h2 {
  flex: none;
  writing-mode: vertical-rl;
  text-orientation: mixed;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-height: 9rem;
  font-size: 0.85rem;
}

/* The directional cue between stages — a plain chevron sitting in the gap,
   never text, so it reads instantly as "flow" without competing with the
   card copy. Every column but the last gets one. */
.board-column:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 1.15rem;
  right: -1.35rem;
  width: 0;
  height: 0;
  border-top: 0.45rem solid transparent;
  border-bottom: 0.45rem solid transparent;
  border-left: 0.6rem solid var(--color-border);
}

@media (max-width: 60rem) {
  .board-columns {
    flex-direction: column;
    overflow-x: visible;
    padding: 0;
  }

  .board-column {
    min-width: 0;
    width: 100%;
  }

  /* On a phone the slim-strip trick has nowhere to go — columns already
     stack full-width, so an empty one would just be a dead header taking a
     whole screen's-worth of scroll for zero cards. Cut it entirely instead;
     the count is still visible on desktop for anyone who wants to know it's
     merely empty rather than missing. */
  .board-column-empty {
    display: none;
  }

  /* Stacked columns read top to bottom, so the connector rotates to point
     down instead of right. */
  .board-column:not(:last-child)::after {
    top: auto;
    right: 1.15rem;
    bottom: -1.35rem;
    border-top: 0.6rem solid var(--color-border);
    border-bottom: none;
    border-left: 0.45rem solid transparent;
    border-right: 0.45rem solid transparent;
  }
}

.board-column-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.35rem;
  font-family: var(--font-sans);
  font-size: 1.05rem;
}

.board-column-header h2 {
  flex: 1;
  margin: 0;
}

/* The step number is a second, colour-independent cue that this is an
   ordered sequence and not a set of filters — it still reads as "step 3 of
   7" with no colour vision at all. */
.board-step-number {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  font-size: 0.8rem;
  font-weight: bold;
}

.board-column-subtitle {
  margin: 0 0 0.75rem;
  color: var(--color-text-muted);
  font-size: 0.85rem;
}

/* Plain-words key for the left-edge staleness colour below — the card text
   already carries the same fact in numbers, this just names the colours. */
.board-staleness-legend {
  margin: 0 0 1rem;
  color: var(--color-text-muted);
  font-size: 0.8rem;
}

/* ── board filter bar ───────────────────────────────────────────────────── */

/* Shared by board.tsx (FilterBar) and chase.tsx — one plain GET form, laid
   out as a single row that wraps onto its own lines on a narrow screen
   rather than squeezing the select/input down to unusable widths. */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.75rem 1rem;
  margin: 0 0 1rem;
  padding: 0.85rem 1rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
}

.filter-bar-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.filter-bar-field select,
.filter-bar-field input[type='text'] {
  font: inherit;
  font-size: 0.95rem;
  color: var(--color-text);
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  background: var(--color-bg);
  min-height: 2.4rem;
}

.filter-bar-field select {
  min-width: 12rem;
}

.filter-bar-field input[type='text'] {
  min-width: 14rem;
}

.filter-bar-clear {
  align-self: center;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.filter-bar-summary {
  margin: 0 0 1rem;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.board-count {
  display: inline-block;
  min-width: 1.7em;
  padding: 0.1rem 0.5rem;
  border-radius: 0.3rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  font-size: 0.85rem;
  text-align: center;
}

.board-card {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  padding: 0.6rem 0.75rem;
  margin: 0 0 0.6rem;
}

.board-card:last-child {
  margin-bottom: 0;
}

.board-card-tenant {
  display: block;
  font-weight: bold;
}

/* ── card emphasis (the four lane meanings, now carried per card) ────────
   Each emphasis is distinguishable on border weight + border style + fill +
   opacity + its own text badge — never colour alone, so it survives a
   monochrome screen and a colour-blind reader. */

.board-card-emphasis-badge {
  display: inline-block;
  margin: 0 0 0.35rem;
  font-size: 0.75rem;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* action: your move, and it should look like it — solid, full-weight border,
   full opacity, the loudest badge on the board. */
.board-card-action {
  border: 2px solid var(--color-accent);
  background: var(--color-surface);
}

.board-card-action .board-card-emphasis-badge {
  color: var(--color-accent);
}

/* waiting: your work here is done, someone else owes the next move — dashed
   border says "paused on you", not "broken". */
.board-card-waiting {
  border-style: dashed;
  opacity: 0.85;
}

.board-card-waiting .board-card-emphasis-badge {
  color: var(--color-text-muted);
}

/* upcoming: hasn't reached this department yet — dotted border and the
   lowest opacity on the board, deliberately closest to invisible. */
.board-card-upcoming {
  border-style: dotted;
  opacity: 0.55;
}

.board-card-upcoming .board-card-emphasis-badge {
  color: var(--color-text-muted);
  font-style: italic;
}

/* done: finished and quiet — thin border, muted fill, no loud badge colour. */
.board-card-done {
  border-color: var(--color-border);
  background: var(--color-bg);
  opacity: 0.75;
}

.board-card-done .board-card-emphasis-badge {
  color: var(--color-text-muted);
}

/* ── card staleness (green → amber → red as a card sits still) ───────────
   Declared AFTER emphasis on purpose: action/waiting/upcoming/done above set
   the `border` shorthand or border-style on all four sides, so overriding
   just border-left here (same specificity, later in source) replaces only
   the left edge and leaves the emphasis border's top/right/bottom alone —
   staleness owns the left edge, emphasis owns the rest, and neither fights
   the other. Colour is never the only signal — the "N days in this state"
   text in board-card-meta says the same thing in words, and stays visible in
   every tier. */
.board-card-fresh {
  border-left: 4px solid var(--color-ok);
}

.board-card-aging {
  border-left: 4px solid var(--color-warn-border);
}

/* Stale gets the background tint too — it is the one tier that should
   actually catch the eye across a whole column, not just on close look.
   Emphasis rules above only ever set `background` on the low-signal tiers
   (waiting has none, done sets --color-bg), so this still reads as the
   loudest background on a stale+action card, which is the combination
   most worth noticing. */
.board-card-stale {
  border-left: 4px solid var(--color-danger);
  background: var(--color-danger-bg);
}

/* A card the domain says can never be dragged to its only forward stage
   (the approval-only move) drops the grab cursor and gets a plain default
   cursor instead, so its non-draggability is felt, not just documented. */
.board-card:not([draggable='true']) {
  cursor: default;
}

.board-card[draggable='true'] {
  cursor: grab;
}

.board-card[draggable='true']:active {
  cursor: grabbing;
}

.board-card.dragging {
  opacity: 0.4;
}

/* Valid/invalid drop affordance on the column itself while a card is being
   dragged over it — outline rather than border so it never shifts layout. */
.board-column.drag-target-valid {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
  background: var(--color-bg);
}

.board-column.drag-target-invalid {
  outline: 3px dashed var(--color-danger);
  outline-offset: 2px;
}

.board-card-meta {
  margin: 0.15rem 0 0.5rem;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.board-card-list {
  margin: 0.4rem 0;
  padding-left: 1.2rem;
}

/* Holds a mix of <form> (a submit button) and <a> (the approval-screen link)
   depending on the offered move — a plain vertical stack of whichever it is. */
.board-card-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  margin: 0.6rem 0 0;
}

.board-card-actions form {
  margin: 0;
}

/* ── exec chase view ────────────────────────────────────────────────────── */

.chase-row {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  padding: 0.7rem 1rem;
  margin: 0 0 0.6rem;
}

.chase-row-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 1.5rem;
  margin: 0 0 0.4rem;
}

/* The single most-stalled row leads the page — its own emphasis, kept
   separate from the three-tier staleness colours below (this row is "where
   to push today", not a staleness reading), same danger treatment as
   .board-card-stale so the two still read as the same family of signal. */
.chase-row-lead {
  border-width: 2px;
  border-color: var(--color-danger);
  background: var(--color-danger-bg);
}

.chase-days {
  font-weight: bold;
}

/* ── map view ───────────────────────────────────────────────────────────── */

.map-lens-switcher,
.map-floor-switcher {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
}

.map-switch-link {
  display: inline-block;
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.9rem;
}

.map-switch-link:hover,
.map-switch-link:focus {
  border-color: var(--color-accent);
}

.map-switch-link-current {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-contrast);
}

.map-legend {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  padding: 0.75rem 1rem;
  margin: 0 0 1rem;
}

.map-legend h2 {
  font-size: 1rem;
  margin: 0 0 0.5rem;
}

.map-legend-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.35rem;
  font-size: 0.9rem;
}

.map-legend-swatch {
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 0.2rem;
  border: 1px solid var(--color-border);
}

/* Same hatch as the map's own no-data fill (see map-svg.ts's NO_DATA_PATTERN_ID) so the legend swatch and the shapes on the plan read as the same thing. */
.map-legend-swatch-nodata {
  background-image: repeating-linear-gradient(
    45deg,
    var(--color-surface),
    var(--color-surface) 3px,
    var(--color-border) 3px,
    var(--color-border) 6px
  );
}

.map-legend-gradient {
  margin: 0 0 0.35rem;
}

.map-legend-gradient-bar {
  display: block;
  height: 0.9rem;
  border-radius: 0.2rem;
  border: 1px solid var(--color-border);
}

.map-legend-gradient-labels {
  display: flex;
  justify-content: space-between;
  margin: 0.25rem 0 0;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

/* The pan/zoom clipping box map.js transforms .map-svg inside. Fixed height
   so zooming in never grows the page underneath it; overflow hidden is what
   makes "pan" mean anything at all. Works exactly the same, just static,
   with map.js absent or failed — see map.js's own header comment. */
.map-viewport {
  position: relative;
  overflow: hidden;
  /* Height, not just width, is what makes the map bigger. The SVG viewBox is
     square and East Park Mall is 645m x 584m (ratio 1.10), so the plan is
     near-square and letterboxes inside its box -- widening alone would just
     add empty margins either side. Tie the height to the window so a wide
     screen actually gets a larger plan, capped so it never outgrows a very
     tall one, floored so it stays usable on a laptop. */
  height: min(75vh, 52rem);
  min-height: 24rem;
  max-width: 100%;
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  background: var(--color-surface);
}

.map-svg {
  display: block;
  width: 100%;
  height: 100%;
  transform-origin: 0 0;
  cursor: grab;
}

.map-panning .map-svg {
  cursor: grabbing;
}

.map-unit-link {
  cursor: pointer;
}

.map-unit-shape {
  stroke: var(--color-surface);
  stroke-width: 2;
}

.map-unit-link:hover .map-unit-shape,
.map-unit-link:focus .map-unit-shape {
  stroke: var(--color-text);
  stroke-width: 3;
}

/* Outdoor areas are not lettable stock the same way retail units are (see
   UnitKind in domain/types.ts) — a dashed outline marks that regardless of
   lens or whether the shape has a joined CRM unit at all. */
.map-unit-shape-outdoor {
  stroke-dasharray: 4 3;
}

/* Stacked-shape marker (see map-svg.ts's stackedMarkerFor): a small dot near
   a shape's centre, distinct in colour and motif from both the heat-lens
   colours and the no-data hatch so it never reads as a third tone. Never
   intercepts hover/click — the polygon underneath must behave exactly as if
   the marker were not there. One colour, one shape (a circle) for both
   confidence levels; solid vs hollow is the only difference between them,
   deliberately not two separate visual languages. */
.map-stacked-marker {
  pointer-events: none;
  stroke: var(--color-surface);
  stroke-width: 1.5;
}

.map-stacked-marker-confirmed {
  fill: #7b3fb0;
}

.map-stacked-marker-probable {
  fill: none;
  stroke: #7b3fb0;
  stroke-width: 3;
}

.map-legend-swatch-stacked-confirmed,
.map-legend-swatch-stacked-probable {
  border-radius: 50%;
}

.map-legend-swatch-stacked-confirmed {
  background: #7b3fb0;
  border-color: #7b3fb0;
}

.map-legend-swatch-stacked-probable {
  background: none;
  border: 2px solid #7b3fb0;
}

/* The red conflict-flag triangle (nap-ybr.9, map-svg.ts's conflictMarkerFor)
   — deliberately RED (var(--color-danger), the same "something's wrong"
   token the rest of the app uses for breached SLAs), a different SHAPE
   (triangle, not circle) AND a different position (bounding-box corner, not
   centroid) from the violet stacking dots above, so the two never read as
   the same kind of mark even when both sit on one shape. pointer-events:
   none for the same reason as the stacking dot -- purely decorative. */
.map-conflict-marker {
  pointer-events: none;
  fill: var(--color-danger);
  stroke: var(--color-surface);
  stroke-width: 1.5;
}

.map-legend-swatch-conflict {
  background: var(--color-danger);
  border-color: var(--color-danger);
  clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}

/* The selected shape itself (see map-svg.ts's shapeMarkupFor and nap-ybr.7's
   click-through panel) — the actual double-stroke ring lives in
   .map-selection-ring-outer/-inner below, drawn as separate elements on top
   of every marker; this class exists so app.css can exempt the selected
   shape from the has-selection dimming rule further down, and so tests can
   assert which shape is selected without parsing marker geometry. */
.map-unit-shape-selected {
  /* Intentionally no stroke override here — see the ring elements below. */
}

/* nap-ybr.10 "Selection visibility": the operator's complaint was that the
   old single 4px accent stroke above was too subtle at mall zoom. Two
   outline-only polygons, same points as the selected shape, drawn LAST
   (topmost — SVG has no z-index) — a wide dark ring, then a narrower light
   ring on the exact same centreline, so the dark shows only at the outer
   and inner edges: a real two-colour ring, not one flat stroke that can
   wash out against a similarly-toned shape fill. Widths are generous in
   raw viewBox units on purpose (this SVG's viewBox never changes; map.js
   zooms via a CSS transform on the whole element) specifically so the ring
   still reads at scale 1 — zoomed out over the entire mall. Never yellow
   or any other colour already used by a heat/consolidated tone, a stacking
   dot, or the conflict flag, so "this is the selection" can never be read
   as a data value. pointer-events: none — purely decorative, exactly like
   the stacked-shape and conflict markers. */
.map-selection-ring-outer,
.map-selection-ring-inner {
  pointer-events: none;
  fill: none;
}

.map-selection-ring-outer {
  stroke: var(--color-text);
  stroke-width: 8;
}

.map-selection-ring-inner {
  stroke: #ffd60a;
  stroke-width: 4;
}

/* Dims every OTHER shape while a selection is active, so the ring above
   reads as unmissable against the whole floor, not just its immediate
   neighbours — the operator's own ask ("not obvious enough which unit is
   selected"). Scoped to .has-selection (map-svg.ts sets this on the <svg>
   root only when `?unit=` names a real shape) so a plain, no-selection map
   render is completely unaffected — every shape at full opacity, exactly
   as before this feature. */
.map-svg.has-selection .map-unit-shape:not(.map-unit-shape-selected) {
  fill-opacity: 0.55;
}

/* Side-by-side layout for the plan and its info panel (nap-ybr.7). Stacks
   to one column on a narrow viewport -- the panel simply falls below the
   map, no JS required either way. */
.map-layout {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 1rem;
}

.map-layout .map-viewport {
  flex: 1 1 24rem;
}

/* A plain <aside>, not a <dialog>, so it degrades to ordinary block content
   with no JS and needs no polyfill -- see src/web/AGENTS.md's "the browser
   is never the authority". */
.map-info-panel {
  flex: 0 0 20rem;
  max-width: 100%;
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  padding: 0.75rem 1rem;
  background: var(--color-surface);
  max-height: min(75vh, 52rem);
  overflow-y: auto;
}

.map-info-panel h2 {
  margin: 0;
  font-size: 1.1rem;
}

.map-info-panel h3 {
  font-size: 0.95rem;
  margin: 0.75rem 0 0.35rem;
}

.map-info-panel-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.map-info-panel-close {
  font-size: 0.85rem;
}

.map-info-panel-note {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin: 0.5rem 0;
}

/* The panel's own explanation for the red conflict flag (nap-ybr.9) --
   the danger tokens, not muted like map-info-panel-note above, because a
   flag nobody can explain is noise and this section IS the explanation:
   it has to read as unmissable, not as background context. */
.map-info-panel-conflicts {
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-radius: 0.3rem;
  padding: 0.5rem 0.75rem;
  margin: 0.5rem 0;
}

.map-info-panel-conflicts h3 {
  margin-top: 0;
  color: var(--color-danger);
}

.map-info-panel-facts {
  width: 100%;
  margin: 0.5rem 0;
  font-size: 0.9rem;
}

.map-info-panel-facts th {
  text-align: left;
  font-weight: normal;
  color: var(--color-text-muted);
  padding: 0.15rem 0.5rem 0.15rem 0;
  white-space: nowrap;
}

.map-info-panel-facts td {
  padding: 0.15rem 0;
}

/* One lease row plus its own link controls (nap-ybr.10, "Lease links") --
   a light divider between rows on a unit carrying several leases, so the
   Confirm/Unlink form for row N is never mistaken for belonging to row
   N-1. */
.map-info-panel-lease-row {
  padding: 0.4rem 0;
  border-top: 1px solid var(--color-border);
}

.map-info-panel-lease-row:first-child {
  border-top: none;
  padding-top: 0;
}

.map-info-panel-inline-form {
  margin: 0.25rem 0 0;
}

/* The map opts out of the shared reading column (Layout's `wide` prop). A
   floor plan is a spatial document, so the constraint that makes prose
   readable is the one thing working against it here. Padding stays, so the
   plan never runs flush into the window edge. */
.main-wide {
  max-width: none;
}

/* ── unresolved queue (nap-ybr.8) ──────────────────────────────────────── */

.unresolved-item {
  padding: 0.75rem 1rem;
  margin: 0 0 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  background: var(--color-surface);
}

/* Visibly marked, never hidden — a noted problem is still a problem (the
   operator's own framing, see docs/map-view-contract.md's "Unresolved
   queue" section). A tint, not a colour swap, so it never reads as
   "resolved" the way a green/success tone would. */
.unresolved-item-noted {
  border-color: var(--color-accent);
  background: var(--color-bg);
}

.unresolved-note {
  font-size: 0.9rem;
  padding: 0.4rem 0.6rem;
  margin: 0.4rem 0;
  background: var(--color-surface);
  border-left: 3px solid var(--color-accent);
}

.unresolved-note-form {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  margin: 0.6rem 0 0;
}

.unresolved-note-form label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  flex: 1 1 auto;
}

.unresolved-note-form input[type='text'] {
  font: inherit;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  background: var(--color-bg);
  color: var(--color-text);
}
/* Same three tiers as .board-card-fresh/aging/stale, in text colour rather
   than a left border — a day-count badge is inline text, not a card — so the
   board and the chase view speak one colour language (see cardStaleness). */
.chase-days-fresh {
  color: var(--color-ok);
}

.chase-days-aging {
  color: var(--color-warn-border);
}

.chase-days-stale {
  color: var(--color-danger);
}

/* ── works register ──────────────────────────────────────────────────────── */

/* Scope is the one free-text cell long enough to need wrapping; every other
   cell stays on the base table's single line. */
.works-scope-cell {
  white-space: normal;
  min-width: 18rem;
}

/* Marks the three cells the register exists to surface: an approval that says
   work is running unsigned, a payer nobody has settled, and a finished job
   with no invoice. Danger, not accent, per the variable's own note. */
.works-cell-risk {
  color: var(--color-danger);
  font-weight: 600;
}

/* The action register's form is the same form with a different name: same
   grid, same controls. The two share these rules rather than owning a copy
   each, so the registers cannot drift apart visually. */
.works-form,
.action-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0.75rem 1rem;
  align-items: end;
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  background: var(--color-surface);
}

.works-form label,
.action-form label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

.works-form input[type='text'],
.works-form select,
.action-form input[type='text'],
.action-form input[type='date'],
.action-form select {
  font: inherit;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  background: var(--color-bg);
  color: var(--color-text);
}

/* Scope is the row's headline, so it spans the whole form rather than sharing
   a line with a select. */
.works-form-wide,
.action-form-wide {
  grid-column: 1 / -1;
}

.works-form-check,
.action-form-check {
  flex-direction: row;
  align-items: center;
  gap: 0.4rem;
}

.works-form-actions,
.action-form-actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 1rem;
}

/* ── action register ─────────────────────────────────────────────────────── */

/* The description is the row's headline and the one cell long enough to need
   wrapping, since it carries the marker and the source quote under it. */
.action-description-cell {
  white-space: normal;
  min-width: 20rem;
}

/* An imported row nobody has checked. Warn, not danger: the row is not wrong,
   it is unconfirmed — and a page of red would stop anyone reading any of it. */
.action-unverified {
  display: inline-block;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--color-warn-border);
  border-radius: 0.2rem;
  background: var(--color-warn-bg);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* Where an imported row's words came from, kept verbatim under the action so
   the operator can check it without opening the minutes. */
.action-source-quote {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--color-text-muted);
}

/* A missing due date is a STATED gap, not an empty cell: it reads in muted
   italics so it is plainly an absence rather than mistaken for a date. */
.action-cell-gap {
  font-style: italic;
  color: var(--color-text-muted);
}

/* Nav help: each tab carries a small circled "?" that opens a native HTML
   popover (no script) explaining what the screen is for. The popover element
   is invisible until invoked, so keeping it inline next to the link costs
   nothing in layout. */
.nav-item {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 0.2rem;
  white-space: nowrap;
}

.nav-help-button {
  width: 1.05rem;
  height: 1.05rem;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: bold;
  line-height: 1;
  cursor: pointer;
}

.nav-help-button:hover,
.nav-help-button:focus {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.nav-help-popover {
  /* The UA default for [popover] centres it in the viewport; these styles
     only dress the card. The card sits inside .nav-item, whose
     white-space: nowrap it would inherit — undo that or nothing wraps. */
  white-space: normal;
  max-width: 34rem;
  max-height: 85vh;
  overflow-y: auto;
  padding: 1rem 1.25rem;
  border: 1px solid var(--color-border);
  border-radius: 0.4rem;
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: 0 8px 30px rgba(27, 31, 36, 0.25);
}

.nav-help-popover::backdrop {
  background: rgba(27, 31, 36, 0.35);
}

.nav-help-popover h2 {
  margin: 0 0 0.35rem;
  font-size: 1.1rem;
}

/* The one-sentence answer leads and reads bigger than the detail below it. */
.nav-help-plain {
  margin: 0 0 0.75rem;
  font-size: 1rem;
}

.nav-help-popover ul {
  margin: 0 0 0.9rem;
  padding-left: 1.2rem;
  font-size: 0.9rem;
}

.nav-help-popover li {
  margin-bottom: 0.3rem;
}

.nav-help-close {
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  background: var(--color-surface);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 0.9rem;
  cursor: pointer;
}

.nav-help-close:hover,
.nav-help-close:focus {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* ── login and session ───────────────────────────────────────────────────── */

/* The sign-in page is a standalone document (no Layout): one narrow column. */
.login-main {
  max-width: 22rem;
  margin: 15vh auto 0;
  padding: 0 1rem;
}

.login-hint {
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.login-error {
  color: var(--color-danger);
  font-size: 0.9rem;
}

.login-form label {
  display: block;
  margin-bottom: 0.75rem;
  font-size: 0.9rem;
}

.login-form input {
  display: block;
  width: 100%;
  margin-top: 0.25rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  font-family: var(--font-sans);
  font-size: 1rem;
  box-sizing: border-box;
}

.login-form button {
  padding: 0.45rem 1.1rem;
  border: 1px solid var(--color-accent);
  border-radius: 0.3rem;
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  font-family: var(--font-sans);
  font-size: 0.95rem;
  cursor: pointer;
}

/* "Signed in as X" + Sign out, sitting beside the desk switcher. */
.session-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
}

.session-name {
  color: var(--color-text-muted);
  white-space: nowrap;
}

.logout-form {
  display: inline;
  margin: 0;
}

.logout-button {
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 0.3rem;
  background: var(--color-surface);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 0.85rem;
  cursor: pointer;
}

.logout-button:hover,
.logout-button:focus {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* A failed form submission's message, above the re-shown form. */
.form-error {
  color: var(--color-danger);
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-radius: 0.3rem;
  padding: 0.5rem 0.75rem;
  font-size: 0.9rem;
}
