/* Abatement Desk — the app.

   Extends the marketing site's identity (site/styles.css) rather than inventing a second one:
   same paper/document metaphor, same tokens, the same
   split between the serif (the record, arguing in prose) and the sans (the software around it).
   Design doc: docs/superpowers/specs/2026-09-04-app-visual-identity-design.md.

   No dark mode, no remote fonts, no inline styles — this app's CSP (BrowserGuards.cs,
   default-src 'self') forbids the last two outright, and the marketing site skips the first for
   the same reason it does: a printed form doesn't have one.

   The 2026-10-03 redesign (docs/superpowers/specs/2026-10-03-app-redesign-design.md, approved by
   Matt) supersedes the zero-radius rule: panels and cards take 8px corners, and five statuses
   carry a color, an icon and a word each. */

:root {
  color-scheme: light;

  --serif: Charter, "Iowan Old Style", "Sitka Text", Cambria, Georgia, "Times New Roman", serif;
  --sans: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  --paper: #f4f3ef;
  --sheet: #ffffff;
  --ink: #181818;
  --ink-2: #3f3f3d;
  --ink-3: #5c5c58;
  --green: #1d4d34;
  --rule: #cbc9c2;
  --rule-faint: #e0ded7;

  /* Refusals and failed sends only -- never a positive notice, never a table row. The marketing
     site's "no color-coded status" rule was written for a list of rows; it never had a case for
     "this action was refused," which .notice used to render identically to a success message. */
  --warn: #7a3b2e;
  --warn-bg: #f6ece9;
  --warn-border: #e3c9c2;

  /* Carried forward from the old .guidance, unchanged in value -- the one functional color this
     design keeps rather than invents. */
  --cite: #6b5a1e;
  --cite-bg: #fbf3d9;
  --cite-border: #ecd9a0;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  /* A page's measure: where its content stops on the right (main's end padding). Narrower than
     the marketing site's 62rem -- a working app read on a laptop, not a landing page, and 62rem
     of table left every list feeling adrift. */
  --frame: 56rem;
  --gutter: 1.25rem;

  /* The frame the letterhead, the observing banner and main stand in on every page, so the mark
     and every page's first word share one left edge however far the page's content runs (Matt,
     2026-10-03: the letterhead stands still across pages). Also the measure of a page carrying
     the notices table, through body:has(.wide): a seven-column logbook in the 56rem measure
     wrapped every site to five lines and broke every date at its hyphen. */
  --frame-wide: 72rem;

  /* ── The 2026-10-03 redesign (spec §1) ── */
  --radius: 8px;
  --radius-control: 6px;
  --radius-tag: 4px;
  --radius-pill: 999px;
  --space-5: 1.25rem;
  --space-10: 2.5rem;

  /* The five statuses: words on their tint, and a strip for a card's top edge. Every words/ground
     pair is held to WCAG AA by PaletteContrastTests; a failing pair is darkened, never kept. The
     strips are decoration -- the chip beside them says the status in a word. */
  --days-away: #8f3520;
  --days-away-bg: #fbeae4;
  --days-away-strip: #b4462b;
  --coming-up: #7a5200;
  --coming-up-bg: #fdf1d6;
  --coming-up-strip: #c98a12;
  --on-site: #1d4d34;
  --on-site-bg: #e3eee6;
  --starts-soon: #1e4e79;
  --starts-soon-bg: #e3edf7;
  --starts-soon-strip: #2f6aa3;
  --finished: #5c5c58;
  --finished-bg: #ecebe6;

  /* Where a notice goes: DNR green, DHS blue, City of Milwaukee purple. */
  --agency-dnr: #1d4d34;
  --agency-dnr-bg: #e3eee6;
  --agency-dhs: #1e4e79;
  --agency-dhs-bg: #e3edf7;
  --agency-milwaukee: #5b3a8c;
  --agency-milwaukee-bg: #efe8f8;

  /* The letterhead's current tab. */
  --tab-current-bg: #e8efe9;
}

*, *::before, *::after { box-sizing: border-box; }

/* The scrollbar's gutter is kept whether or not the page scrolls, so where a browser draws its
   scrollbar beside the page rather than over it, a short page and a long one centre the frame on
   the same width and the letterhead does not shift between them (Matt, 2026-10-03: nothing moves
   between tabs). The cost, on such a browser: a page too short to scroll shows the paper in the
   reserved strip, so the letterhead's white bar stops a scrollbar's width short of the window's
   right edge. */
html { scrollbar-gutter: stable; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 1rem/1.55 var(--sans);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

h1, h2 {
  margin: 0 0 var(--space-4);
  font-family: var(--serif);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.005em;
  color: var(--ink);
}

h1 { font-size: 1.75rem; }
h2 { font-size: 1.2rem; margin-top: var(--space-8); }

/* A panel's own heading sits flush with its top padding rather than repeating the page rhythm
   inside a box that already has its own. */
section.panel h2:first-child,
section.record h2:first-child,
section.notice-facts h2:first-child,
section.plan h2:first-child,
section.window-note h2:first-child { margin-top: 0; }

/* A panel's sub-heading -- "Asking to join", "Invite a colleague" -- a serif line with room above
   it and little below. */
section.panel h3 { margin: var(--space-6) 0 var(--space-2); font: 600 1rem/1.3 var(--serif); }

p { margin: 0 0 var(--space-4); }
p:last-child { margin-bottom: 0; }

/* No word left alone on a line (Matt, 2026-10-10: a single word wrapped onto its own line is a
   design smell). The marketing site carries the same two rules. Short lines -- headings and
   notices, one to three lines -- take `balance`, which evens them out and can never strand a
   word. Body text takes `pretty`, which only reworks the last few lines, because `balance` on a
   long paragraph would pull every line in from the measure. A browser without either wraps the
   way it always did, so there is nothing to fall back to. */
h1, h2, h3, .notice { text-wrap: balance; }
p, li, dd { text-wrap: pretty; }

a { color: var(--green); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { text-decoration-thickness: 2px; }

:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }

/* The one deliberate exception to "no monospace anywhere" (§4) -- request ids and DNR index
   keys, not narrative or UI text. */
code {
  font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
  font-size: 0.9em;
  background: var(--rule-faint);
  padding: 0.1em 0.35em;
}

/* An icon from the sprite (Icon.razor): the size of the words beside it, in their color. */
.icon {
  flex: none;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}

/* A status (StatusChip.razor): its words on its own tint, with its icon. The words carry the
   status and the color repeats it, never the other way round. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill);
  font: 700 0.8rem/1.2 var(--sans);
  white-space: nowrap;
}
.chip.status-days-away { color: var(--days-away); background: var(--days-away-bg); }
.chip.status-coming-up { color: var(--coming-up); background: var(--coming-up-bg); }
.chip.status-on-site { color: var(--on-site); background: var(--on-site-bg); }
.chip.status-starts-soon { color: var(--starts-soon); background: var(--starts-soon-bg); }
.chip.status-finished { color: var(--finished); background: var(--finished-bg); }

/* Where a notice goes (AgencyTag.razor). */
.agency-tag {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius-tag);
  font: 700 0.7rem/1.3 var(--sans);
  white-space: nowrap;
}
.agency-tag.agency-dnr { color: var(--agency-dnr); background: var(--agency-dnr-bg); }
.agency-tag.agency-dhs { color: var(--agency-dhs); background: var(--agency-dhs-bg); }
.agency-tag.agency-milwaukee { color: var(--agency-milwaukee); background: var(--agency-milwaukee-bg); }

/* What a job still needs (JobChips.razor), on the jobs list and a site's "Coming up here", and
   beside each tag on the job page's checklist (JobAnswer.razor): each notice's tag and its phrase,
   in a row that wraps. A notice marked sent reads green beside its check; one whose day has passed
   reads rust. The words say which; the color repeats them. */
.job-chips { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4); margin: 0; padding: 0; list-style: none; }
.job-chips li,
section.answer .agency .state { display: inline-flex; align-items: center; gap: var(--space-1); margin: 0; color: var(--ink-2); font-size: 0.85rem; white-space: nowrap; }
.job-chips li.sent,
section.answer .agency .state.sent { color: var(--on-site); font-weight: 600; }
.job-chips li.late,
section.answer .agency .state.late { color: var(--days-away); font-weight: 600; }
p.no-notice { margin: 0; color: var(--ink-3); font-size: 0.85rem; }

/* A link that is the page's action (TaskCard, EmptyState): the green fill, or the quieter tint on a
   card that is not yet days away. */
a.button {
  display: inline-block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-control);
  background: var(--green);
  color: var(--sheet);
  font: 600 0.9rem/1.2 var(--sans);
  text-decoration: none;
}
a.button:hover { opacity: 0.88; }
a.button.button-quiet { background: var(--tab-current-bg); color: var(--green); }

/* Task cards (TaskCard.razor) in a grid that wraps to one column on a phone. The strip on top
   repeats the chip's status; the chip says it. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-3);
}
.task-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-top: 5px solid var(--rule);
  border-radius: var(--radius);
}
.task-card.status-days-away { border-top-color: var(--days-away-strip); }
.task-card.status-coming-up { border-top-color: var(--coming-up-strip); }
.task-card.status-on-site { border-top-color: var(--on-site); }
.task-card.status-starts-soon { border-top-color: var(--starts-soon-strip); }
.task-card-top { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); }
/* A title that wraps keeps its icon on its first line, centred on that line: the line box is 1.3em
   and the icon 1em, so it sits 0.15em down from the top. */
.task-card-title {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-1) 0 0;
  font: 600 1.1rem/1.3 var(--serif);
}
.task-card-title .icon { margin-top: 0.15em; }
.task-card-firm { margin: 0; color: var(--ink-3); font-size: 0.85rem; }
.task-card-text { flex: 1; margin: 0; color: var(--ink-2); font-size: 0.9rem; line-height: 1.45; }
.task-card .button { align-self: flex-start; }

/* A section with nothing to list (EmptyState.razor). The paragraph's own bottom margin is the gap
   above the button: an inline-block's margin does not collapse into it, so a second one doubled it. */
.empty-state {
  margin: 0 0 var(--space-6);
  padding: var(--space-6);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.empty-state > .icon { width: 1.5rem; height: 1.5rem; margin-bottom: var(--space-2); color: var(--ink-3); }
.empty-state h2 { margin: 0 0 var(--space-2); }
.empty-state p { font-family: var(--serif); color: var(--ink-2); }

/* The quoted rule text in WindowNote -- "the record speaking for itself," same role as the
   marketing site's evidence line. */
q { font-family: var(--serif); font-style: italic; }

small { color: var(--ink-3); font-size: 0.85em; }

ul { margin: 0 0 var(--space-4); padding-left: 1.25rem; }
li { margin-bottom: var(--space-2); }

/* The letterhead (Letterhead.razor) -- the outreach page's letterhead carried into the app, so the
   page a recipient lands on visibly belongs to the page they came from: the mark and "Abatement
   Desk" in the serif, and the same double rule beneath, 3px of the firm's green over a hairline.
   Since the 2026-10-03 redesign it also carries the workspace's four tabs and the firm menu (spec
   §2). The bar runs the full width of the window; its contents sit in the wide frame on every page,
   and main starts at the same left edge (Matt, 2026-10-03: the letterhead stands still).

   Scoped to .letterhead rather than the bare element, because the share page's <article> has a
   <header> of its own. */
header.letterhead { background: var(--sheet); }

header.letterhead::after {
  content: "";
  display: block;
  height: 4px;
  border-top: 3px solid var(--green);
  border-bottom: 1px solid var(--rule);
}

header.letterhead > div {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  max-width: var(--frame-wide);
  margin-inline: auto;
  padding: var(--space-3) var(--gutter);
}

/* The wordmark takes the ink rather than the green: a letterhead that lights up is a navigation
   bar, and the mark beside it is already the green. */
header.letterhead .brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--serif);
  font-size: 1.2rem;
  line-height: 1.2;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
  color: var(--ink);
}
header.letterhead .brand:hover { text-decoration: underline; }

/* Sized in em so it tracks the wordmark, and nudged up: flex centring leaves a serif's optical
   middle about a tenth of an em below the box's. */
header.letterhead .mark {
  flex: none;
  width: 1.22em;
  height: 1.22em;
  position: relative;
  top: -0.08em;
  fill: var(--green);
}

/* The four tabs: an icon and a word each, the current one in the green's own tint. */
nav.tabs { display: flex; gap: var(--space-1); font-size: 0.9rem; }
nav.tabs a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-control);
  color: var(--ink-2);
  text-decoration: none;
  white-space: nowrap;
}
nav.tabs a:hover { color: var(--ink); background: var(--paper); }
nav.tabs a[aria-current="page"] { color: var(--green); background: var(--tab-current-bg); font-weight: 600; }

/* The firm menu (FirmMenu.razor): a native <details> whose panel floats over the page rather than
   pushing it down. Below .skip's z-index, above the sticky observing banner's. */
details.firm-menu { position: relative; margin-left: auto; font-size: 0.9rem; }
details.firm-menu > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-control);
  color: var(--ink-2);
  cursor: pointer;
  list-style: none;
}
details.firm-menu > summary::-webkit-details-marker { display: none; }
details.firm-menu > summary:hover { color: var(--ink); background: var(--paper); }
.firm-menu-label { max-width: 18rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.firm-menu-panel {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: 0;
  z-index: 6;
  min-width: 15rem;
  padding: var(--space-3) var(--space-4);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: 0 6px 18px rgb(24 24 24 / 0.08);
}
.firm-menu-panel .who {
  display: block;
  margin-bottom: var(--space-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-3);
  font-size: 0.85rem;
}
.firm-menu-panel ul { margin: 0 0 var(--space-2); padding: 0; list-style: none; }
.firm-menu-panel li { margin: 0; }
.firm-menu-panel a { display: block; padding: var(--space-1) 0; }
.firm-menu-panel button,
.firm-menu-panel button.secondary {
  padding: var(--space-1) 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: 400 0.9rem/1.2 var(--sans);
  cursor: pointer;
}
.firm-menu-panel button:hover { color: var(--ink); text-decoration: underline; }

/* main stands in the letterhead's frame and starts at its left edge on every page (Matt,
   2026-10-03); a page's content keeps the measure, --frame, and simply ends sooner on the right.
   The end padding is what the frame holds past the measure -- a padding's percentage is of the
   body, so min(100%, the wide frame) is main's own width -- and never less than the gutter, so in
   a window narrower than the measure the content has all of it less the gutters, as before. */
main {
  max-width: var(--frame-wide);
  margin: 0 auto;
  padding: var(--space-8) var(--gutter);
  padding-inline-end: max(var(--gutter), calc(min(100%, var(--frame-wide)) - var(--frame) + var(--gutter)));
}

/* A page holding a .wide table (NoticeTable's) takes the whole frame for it: its measure becomes
   the wide frame, so main's end padding falls back to the gutter. The measure grows for the table,
   not for the sentences, so prose on those pages keeps a reading measure. A notice is not prose --
   one status line in a box -- so it takes the frame, where the cap wrapped it with room to spare.
   A browser without :has() keeps the narrow measure and the table scrolls inside its .scroller. */
body:has(.wide) { --frame: var(--frame-wide); }
body:has(.wide) main p:not(.notice) { max-width: 50rem; }

/* A door -- sign-in, start, a link that expired, not found, the error page -- is one .auth-card,
   and its card stays centred on the window as it was before the frame was shared: the whole frame
   is its measure, and the card's auto margins centre it in a frame that is itself centred. The
   letterhead above it stands where it does on every page; only the card is centred. A browser
   without :has() centres the card in the narrow measure instead, left of the window's middle. */
body:has(.auth-card) { --frame: var(--frame-wide); }

/* The payment page (EmbeddedCheckoutPage) is a main holding Stripe's frame and no letterhead, so
   there is no mark for it to line up with, and like a door it stays centred on the window: the
   measure, centred, a gutter either side -- the box it had before main stood in the wide frame. It
   sets the box itself rather than --frame, because widening its measure to the frame would widen
   the payment form too; and this rule's #checkout outranks main's own, so it wins at any width.
   A browser without :has() gives it main's wide box with the measure's end padding instead, left
   of the window's middle. */
main:has(#checkout) {
  max-width: var(--frame);
  padding-inline-end: var(--gutter);
}

/* Forms */
label {
  display: block;
  margin: 0 0 var(--space-4);
  font-size: 0.9rem;
  color: var(--ink-2);
}

/* Every form keeps a line of space under it, so its button never sits flush against the panel,
   table or notice that follows -- it did on every notification page, on Settings, on Billing and
   under the firm search. A form that ends its container leaves the space to the container's own
   padding, and a form that is one item in a row of something else (a table cell, the letterhead,
   the observing banner, an .actions row) takes none. */
form { margin: 0 0 var(--space-4); }
form:last-child,
td form,
.observing form,
header.letterhead form,
.actions form { margin-bottom: 0; }

/* Buttons that belong together -- Subscribe and Manage billing -- side by side while they fit and
   wrapping when they do not, instead of stacked flush one on the other. */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-6);
}
.actions:last-child { margin-bottom: 0; }

/* The "Check the DNR for anything new" panel on the notification page: no border of its own, so
   it needs the space under it that a panel would have had. */
section.recheck { margin-bottom: var(--space-4); }

input[type="email"],
input[type="text"],
input[type="search"],
input[type="number"],
input[type="date"],
input[type="time"],
select,
textarea {
  display: block;
  margin-top: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--ink-3);
  border-radius: var(--radius-control);
  background: var(--sheet);
  color: var(--ink);
  font: 400 1rem/1.3 var(--sans);
  min-width: min(18rem, 100%);
  max-width: 100%;
}

input[type="file"] {
  display: block;
  margin-top: var(--space-1);
  padding: var(--space-2) 0;
  font: 400 0.95rem/1.3 var(--sans);
}

button {
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--green);
  border-radius: var(--radius-control);
  background: var(--green);
  color: var(--paper);
  font: 600 0.95rem/1.2 var(--sans);
  cursor: pointer;
}
button:hover { opacity: 0.88; }

/* A lesser action -- Remove, Check the DNR for anything new -- takes this instead of the primary
   fill. Not a "danger" color: a destructive action is distinguished by placement and confirmation
   copy, not hue, matching the marketing site's "state is carried by weight... never by hue." */
button.secondary {
  background: transparent;
  color: var(--ink-2);
  border-color: var(--ink-3);
  font-weight: 400;
}
button.secondary:hover { opacity: 1; border-color: var(--ink-3); color: var(--ink); }

/* Tables -- the marketing site's docket pattern: bordered sheet, a 2px ink rule under sentence-
   case headers (not uppercase letter-spacing, which the marketing CSS calls out by name as
   "dashboard convention"), hairlines between rows. Applied directly to every table in the app;
   no wrapper markup needed. */
table {
  width: 100%;
  margin: 0 0 var(--space-6);
  border-collapse: collapse;
  border: 1px solid var(--rule);
  background: var(--sheet);
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
}

th, td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  vertical-align: top;
}

thead th {
  padding-block: var(--space-2);
  border-bottom: 2px solid var(--ink);
  color: var(--ink-3);
  font-size: 0.8rem;
  font-weight: 400;
}

tbody tr + tr td { border-top: 1px solid var(--rule-faint); }

table.grid td { text-align: right; }

/* Definition lists -- Document, Draft, and Notification each render one for the record's own
   fields (filename, state, size, and the rest). */
dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-4); margin: 0 0 var(--space-4); }
dt { color: var(--ink-3); font-size: 0.9rem; }
dd { margin: 0; }

/* Panels -- every bordered content block in the app: the record, a notice's facts and each of its
   versions, the billing plan card, the window note, and the auth flow's card. Flat paper, no
   shadow, and since the 2026-10-03 redesign the 8px corners every panel and card takes. */
section.panel,
section.record,
section.version,
section.plan,
section.window-note,
section.notice-facts,
.auth-card {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--sheet);
  padding: var(--space-6);
  margin: 0 0 var(--space-4);
}

.auth-card {
  max-width: 26rem;
  margin: var(--space-8) auto;
}

/* A table that ends a panel -- Settings' Activity -- sits on the panel's own padding, where its
   bottom margin doubled the space under it. */
section.panel > table:last-child { margin-bottom: 0; }

section.record form.rate {
  display: flex;
  gap: var(--space-4);
  align-items: end;
  margin: 0 0 var(--space-4);
}
section.record form.rate label { margin: 0; }
section.record input[type="number"] { min-width: 0; width: 8rem; }

section.record p.line,
section.plan p.line {
  font-family: var(--serif);
  font-variant-numeric: tabular-nums;
  margin: 0 0 var(--space-3);
}
section.record p.line:last-child,
section.plan p.line:last-child { margin-bottom: 0; }

section.record p.unread {
  font-family: var(--serif);
  color: var(--ink-3);
  font-size: 0.9rem;
  margin: var(--space-3) 0 0;
}

/* BillingCard's subscription facts, set apart from the price above them by a rule: the status is
   the card's answer, and it reads as one. The values take the card's serif and tabular figures so
   a date and an amount line up with the price. A failed payment, the one status somebody has to
   act on, takes the warning colour; nothing else on the card is colour-coded. */
section.plan dl.subscription {
  /* One label width on every card, so a page holding two firms lines their facts up. */
  grid-template-columns: 7rem minmax(0, 1fr);
  align-items: baseline;
  margin: var(--space-4) 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--rule-faint);
}
section.plan dl.subscription:last-child { margin-bottom: 0; }
section.plan dl.subscription dd { font-family: var(--serif); font-variant-numeric: tabular-nums; }
section.plan dl.subscription dd:first-of-type { font-weight: 600; }
section.plan dl.subscription dd.warn { color: var(--warn); }

/* BillingCard's "The first 30 days are free." beside the Subscribe button. */
section.plan form span { margin-left: var(--space-3); color: var(--ink-3); font-size: 0.9rem; }

.provenance { font-family: var(--serif); color: var(--ink-3); font-size: 0.85rem; margin-bottom: var(--space-4); }

.hint { color: var(--ink-3); font-size: 0.85rem; margin: 0 0 var(--space-4); }

.counts { color: var(--ink-2); }

/* The folds: the older notices under a firm's record page's open table; under the Notices page's
   list, the other firms the record ties the reader's address to; and, on the claims console, the
   guidance under "How to decide". A summary line in the sans, quiet until opened, with the table or
   text it holds directly under it. */
details.older,
details.others { margin: 0 0 var(--space-6); }

details.older > summary,
details.others > summary {
  padding: var(--space-2) 0;
  color: var(--ink-2);
  font-weight: 600;
  cursor: pointer;
}

details.older[open] > summary,
details.others[open] > summary { margin-bottom: var(--space-3); }

details.others table { margin-bottom: 0; }

/* The breadcrumb trail. Sans, quiet, and above the h1 it describes. */
.crumbs {
  margin: 0 0 var(--space-3);
  color: var(--ink-3);
  font-size: 0.9rem;
}
.crumbs a { color: var(--ink-2); }
.crumbs .sep { margin: 0 var(--space-2); color: var(--ink-3); }
/* The page's own crumb is short ("Notification 26-0915") and was the one that broke mid-number on a
   phone; the crumbs before it may be long site names, which wrap between their words as before. */
.crumbs [aria-current="page"] { white-space: nowrap; }

/* A notice number never breaks at its hyphen, and a date range never breaks inside a date: each
   moves to the next line whole (cohort review, 2026-10-10: "Notification 26-" over "0915", and
   "Feb 18, 2026 to Apr" over "24, 2026"). td.id is the tables' own rule, below. */
.notice-number,
.version-line .when { white-space: nowrap; }

/* Badges */
.mine {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0.05rem 0.4rem;
  font-size: 0.8rem;
  color: #3a6b3a;
  background: #eef6ee;
  border: 1px solid #cde3cd;
}

.guidance {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 0.05rem 0.4rem;
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--cite);
  background: var(--cite-bg);
  border: 1px solid var(--cite-border);
  text-decoration: none;
}

/* Which state a firm or a filing belongs to, trailing the name it qualifies. Not a badge: .mine
   and .guidance above are signals -- something happened, something is worth reading -- and a tint
   and a border would say this one is too. It qualifies, so it reads as the quiet trailing note
   `small` already is, at the weight of the surrounding text rather than the heading it sits in. */
.jurisdiction {
  margin-left: var(--space-2);
  font-weight: 400;
  font-size: 0.85rem;
  color: var(--ink-3);
}

/* The role a firm held on a job when it was not the abatement contractor, and a notice filed after
   the fact: qualifiers, not badges, each on its own line under the notice number. Beside it, the
   role pushed the number onto two lines ("27-/0237 demolition") once the cell stopped wrapping. */
.role,
.notice-type {
  display: block;
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--ink-3);
}

/* Notice numbers and dates never break at their hyphen ("27-/0237", "2026-/09-16"): the cell
   grows instead, and the site, which is free text, takes the wrapping. The marker under a number
   goes on its own line for the reason .role does. */
td.id,
td.date { white-space: nowrap; }
td.id .mine { display: table; margin: var(--space-1) 0 0; }

/* The Verified mark (follow-and-own design §5.3) -- the one sentence saying a workspace is the
   firm, on its own page and on a share its counterparty opens. A qualifier like .jurisdiction and
   .role above rather than a badge: the mark is worth noticing and is never the loudest thing on
   the page, so it takes the one functional green this identity already has and a check that reads
   as punctuation. aria-hidden is not available on a ::before, which is why the glyph is here and
   not in the markup -- generated content is not in the accessibility tree, so a screen reader
   hears the sentence and not a tick. */
/* The second selector is the share page's own "Sent by ‹firm›", where .shared .from already sets
   an ink: a class on the element does not outrank a descendant selector, so the pairing is named
   here rather than losing to it. */
.confirmed,
.shared .from.confirmed {
  color: var(--green);
}

.confirmed::before {
  content: "\2713";
  margin-right: var(--space-2);
  font-weight: 600;
}

.window { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Notices -- split by outcome. Informational/positive keeps the app's original green-tinted
   treatment; a refusal or failed send (today rendered identically to a success message) takes
   the .notice-warn modifier instead. */
.notice {
  padding: var(--space-3) var(--space-4);
  background: #eef6ee;
  border: 1px solid #cde3cd;
  margin: 0 0 var(--space-4);
}

.notice-warn {
  background: var(--warn-bg);
  border-color: var(--warn-border);
  color: var(--warn);
}

/* The observing banner -- support looking at a workspace through a grant. Full-bleed and above
   the header rather than a notice inside main's column: a notice in the column reads as another
   remark about the page, and this one is about the whole session. It takes the --warn family for
   the same reason .notice-warn does, which is the rule that family exists for: not a refusal here,
   but a state a reader must not stop noticing. */
.observing {
  background: var(--warn-bg);
  border-bottom: 2px solid var(--warn);
  color: var(--warn);

  /* Sticky, because a banner that scrolls away is missable and missable is the one thing this
     element may not be. The pages it appears over are the long ones -- document lists, filing
     tables, settings -- so one screen of scrolling was enough to leave nothing on screen saying
     whose workspace this is. It also keeps "Stop viewing" reachable from anywhere on the page,
     which the way out was not once it had scrolled off with the sentence.

     Below .skip's z-index of 10, not above it: the skip link is absolutely positioned at the top
     of the document and is the keyboard escape hatch, so a focused one has to paint over this
     rather than under it. Anything at or above 0 already clears the page's own content, none of
     which is positioned. */
  position: sticky;
  top: 0;
  z-index: 5;
}

/* The skip link's target, moved down by the height this banner covers, and only when the banner is
   there to cover it -- main is a later sibling of it. Without this, "Skip to content" lands the top
   of main under the sticky banner, so the keyboard user's first line of content is the one they
   cannot see. */
.observing ~ main { scroll-margin-top: 4rem; }

/* The inner row carries the letterhead's wide frame, so the tint runs to the edge of the window
   while the words line up with the mark and main below it. */
.observing > div {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  max-width: var(--frame-wide);
  margin-inline: auto;
  padding: var(--space-3) var(--gutter);
  font-weight: 600;
}

/* The one button in the app that is neither the green commit nor the quiet secondary: it belongs
   to the banner's own family, and a green fill in here would read as the page's primary action. */
.observing button {
  border-color: var(--warn);
  background: var(--warn);
  color: var(--warn-bg);
}

/* ── The skip link ──────────────────────────────────────────────────────────
   Off-screen until focused. Every page puts up to five nav items between the
   top of the document and the content, and this is the only way past them on a
   keyboard. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--ink);
  background: var(--sheet);
  color: var(--ink);
  font: 600 0.95rem/1.2 var(--sans);
  text-decoration: none;
}
.skip:focus { left: var(--gutter); top: var(--space-2); }

/* ── Scrollable grids ───────────────────────────────────────────────────────
   For tables that stay tables at every width: the asbestos grid and the
   contractor table on the notification page. Both are genuine numeric grids
   where a stacked row reads worse than a scroll, which is the same split
   site/styles.css records for its own docket. The tabindex is required rather
   than decorative -- a scroll container only a mouse can reach is a keyboard
   trap in reverse. */
.scroller { overflow-x: auto; margin: 0 0 var(--space-6); }
.scroller table { margin-bottom: 0; }

/* ── Home (Tenant.razor, spec 2026-10-03 §3) ────────────────────────────────
   The greeting in the letterhead's serif, the date and the record's freshness under it, then the
   two sections a reader came for -- what to send, where the crews are -- each under a small label.
   The notices list is the Notices page's, below. */
h1.greeting { margin-bottom: var(--space-1); font-size: 1.65rem; }
p.dateline { margin: 0 0 var(--space-6); color: var(--ink-3); font-size: 0.85rem; }

.section-label {
  margin: 0 0 var(--space-3);
  font: 700 0.75rem/1.2 var(--sans);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.section-label .guidance { text-transform: none; letter-spacing: 0; }

section.to-send,
section.crews { margin: 0 0 var(--space-8); }

.section-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); }
.section-head a { font-size: 0.85rem; font-weight: 600; white-space: nowrap; }

.crew-list {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}
.crew-row {
  display: grid;
  grid-template-columns: minmax(11rem, 15rem) minmax(0, 1fr) auto;
  gap: var(--space-3);
  align-items: center;
  margin: 0;
  padding: var(--space-3) var(--space-4);
}
.crew-row + .crew-row { border-top: 1px solid var(--rule-faint); }
.crew-row .chip { justify-self: start; }   /* a grid item stretches to its column; the pill keeps its words' width */
.crew-site { font-family: var(--serif); font-weight: 600; }
.crew-meta { margin-left: var(--space-2); color: var(--ink-3); }
.crew-day { color: var(--ink-2); white-space: nowrap; }

p.record-line { margin: 0 0 var(--space-6); font-family: var(--serif); color: var(--ink-2); }

.trial-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0 0 var(--space-6);
  padding: var(--space-3) var(--space-4);
  background: var(--on-site-bg);
  border-radius: var(--radius);
}
.trial-banner p { margin: 0; }

/* ── Notices (Notices.razor, spec 2026-10-03 §4) ────────────────────────────
   The chips and the search box on one row that wraps, the list in the wide frame's sheet, the
   showing line and "Show 50 more" under it, and what the list comes from at the foot. */
.list-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}

/* A row of filters (FilterChips.razor): each chip a link, its words in its status's color on the
   sheet, and the chosen one on its status's tint inside a heavier border of its color --
   aria-current says it to assistive tech, the tint and the border to the eye. */
nav.filter-chips { margin: 0 0 var(--space-4); }
.list-controls nav.filter-chips { margin: 0; }
nav.filter-chips ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
nav.filter-chips li { margin: 0; }
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-pill);
  background: var(--sheet);
  font: 600 0.85rem/1.2 var(--sans);
  text-decoration: none;
  white-space: nowrap;
}
.filter-chip:hover { border-color: currentColor; }
.filter-chip-count { color: var(--ink-3); font-weight: 400; }
.filter-chip.tone-plain { color: var(--ink-2); }
.filter-chip.status-on-site { color: var(--on-site); }
.filter-chip.status-days-away { color: var(--days-away); }
.filter-chip.status-starts-soon { color: var(--starts-soon); }
.filter-chip.status-finished { color: var(--finished); }
.filter-chip[aria-current] { padding: calc(0.3rem - 1px) calc(0.75rem - 1px); border: 2px solid currentColor; }
.filter-chip.tone-plain[aria-current] { color: var(--green); background: var(--tab-current-bg); }
.filter-chip.status-on-site[aria-current] { background: var(--on-site-bg); }
.filter-chip.status-days-away[aria-current] { background: var(--days-away-bg); }
.filter-chip.status-starts-soon[aria-current] { background: var(--starts-soon-bg); }
.filter-chip.status-finished[aria-current] { background: var(--finished-bg); }

/* The one search box, at the row's right end while the row is wide enough, and wide enough itself
   to show its whole placeholder, "Site, town or notice number". */
form.notice-search { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 0 auto; }
form.notice-search label { margin: 0; }
form.notice-search input[type="search"] { margin-top: 0; width: 16rem; }
form.notice-search button { display: inline-flex; align-items: center; gap: var(--space-1); }

/* The list: the site's name in the serif with its town after it, the firm under both when the
   workspace holds more than one, and the column headings as quiet links -- the sorted one in the
   green, its chevron turned over for ascending. The notice page's header names its site
   .notice-site too, so each page's rule stays under its own container. */
table.notice-list td { vertical-align: middle; }
table.notice-list .notice-site { font-family: var(--serif); font-weight: 600; }
.notice-town { color: var(--ink-3); }
.notice-firm { display: block; font-size: 0.85rem; color: var(--ink-3); }
.sort-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}
.sort-link:hover { color: var(--ink); text-decoration: underline; }
.sort-link.sorted-ascending,
.sort-link.sorted-descending { color: var(--green); font-weight: 600; }
.sort-link.sorted-ascending .icon { transform: rotate(180deg); }

/* The sorts as a row of links (SortLinks.razor), for a phone only: above 52rem the headers are the sorts. */
.sort-links { display: none; }

.list-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-8);
}
.list-foot p { margin: 0; color: var(--ink-3); font-size: 0.85rem; }
.list-foot a { font-size: 0.9rem; font-weight: 600; }

.nothing-shown { margin: 0 0 var(--space-8); }
.nothing-shown p { margin: 0 0 var(--space-2); }

/* What the list comes from (ruling R-B7): the firms, the check for anything new, adding a firm,
   the reader's own filings, and the other firms the record ties their email to. */
section.from-record { margin-top: var(--space-10); }
section.from-record h3 { margin: var(--space-6) 0 var(--space-2); font: 600 1.05rem/1.3 var(--serif); }
ul.held-firms { margin: 0 0 var(--space-4); padding-left: var(--space-6); }

/* ── A site (Site.razor, spec 2026-10-03 §4) ────────────────────────────────
   The facility in the serif with where it is under it, then the current notice in a panel whose
   strip turns rust when its revise-by is days away, then every notice here and the jobs coming up
   beside the people the notices name and the documents kept against them. */
.place-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-4);
  margin: 0 0 var(--space-5);
}
.place-head h1 { margin: 0; }
.place-line { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-1) 0 0; color: var(--ink-2); }
.place-counts { margin: var(--space-1) 0 0; color: var(--ink-3); font-size: 0.85rem; }

/* A notice's status when none of the five names it (NoticeStatus.razor): words, quiet, no chip. */
.notice-status { color: var(--ink-3); font-size: 0.85rem; font-weight: 600; white-space: nowrap; }

section.now {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-4);
  align-items: center;
  margin: 0 0 var(--space-6);
  padding: var(--space-4) var(--space-5);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-left: 6px solid var(--on-site);
  border-radius: var(--radius);
}
section.now.now-urgent { border-left-color: var(--days-away-strip); }
section.now.now-ended { border-left-color: var(--finished); }
section.now .section-label { margin-bottom: var(--space-1); }
.now-dates { margin: 0 0 var(--space-1); font: 600 1.15rem/1.3 var(--serif); }
.now-revise { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; color: var(--ink-2); }
section.now.now-urgent .now-revise { color: var(--days-away); font-weight: 600; }
.now-actions { display: flex; flex-direction: column; gap: var(--space-2); }
.now-actions a.button { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); }

.place-columns {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
.place-columns section { margin: 0 0 var(--space-6); }

.place-list {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.place-list > li { margin: 0; padding: var(--space-3) var(--space-4); }
.place-list > li + li { border-top: 1px solid var(--rule-faint); }
.place-row { display: grid; grid-template-columns: 8rem minmax(0, 1fr) auto; gap: var(--space-3); align-items: center; }
.place-row .chip { justify-self: start; }   /* a grid item stretches to its column; the pill keeps its words' width */
.place-row-dates { display: block; font-weight: 600; }
.place-row-meta { display: block; color: var(--ink-3); font-size: 0.85rem; }
.coming-up-when { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-1); color: var(--starts-soon); font-size: 0.85rem; font-weight: 700; }
.place-documents .place-list > li { display: flex; align-items: center; gap: var(--space-2); }
.place-doc-date { color: var(--ink-3); font-size: 0.85rem; }
.place-add { margin: var(--space-2) 0 0; font-size: 0.9rem; font-weight: 600; }
p.place-empty { color: var(--ink-3); }

/* The people a job deals with (PeopleList.razor): an icon in a tinted circle, the role over the
   name, and who came before under it. The tints are the mockup's, taken from tokens whose pairs
   PaletteContrastTests already holds to AA. */
ul.people {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.person { display: flex; gap: var(--space-3); margin: 0; padding: var(--space-3) var(--space-4); }
.person + .person { border-top: 1px solid var(--rule-faint); }
.person-icon { display: flex; flex: none; align-items: center; justify-content: center; width: 1.9rem; height: 1.9rem; border-radius: 50%; }
.person-owner .person-icon { color: var(--agency-milwaukee); background: var(--agency-milwaukee-bg); }
.person-inspector .person-icon { color: var(--starts-soon); background: var(--starts-soon-bg); }
.person-demolition .person-icon { color: var(--coming-up); background: var(--coming-up-bg); }
.person-text { display: flex; flex-direction: column; gap: 0.1rem; }
.person-role { color: var(--ink-3); font-size: 0.8rem; font-weight: 600; }
.person-name { font-weight: 600; }
.person-note { color: var(--ink-3); font-size: 0.8rem; }
p.people-empty { color: var(--ink-3); }

/* ── The notice page (Notification.razor, spec 2026-10-03 §5) ───────────────
   Its site and number, then the facts a contractor plans by as cells under a rule each -- the rule
   is where a warning shows -- then the actions in one row, the share opening
   beneath them, then the versions beside the people the notice names. */
.notice-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-4);
  margin: 0 0 var(--space-5);
}
.notice-head h1 { margin: var(--space-1) 0 0; }
.notice-head h1 .agency-tag { margin-left: var(--space-2); vertical-align: middle; }
.notice-head .notice-site { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; font-family: var(--serif); font-weight: 600; }
.notice-place { color: var(--ink-3); font-weight: 400; }

section.notice-facts dl {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  gap: var(--space-4);
  margin: 0;
}
section.notice-facts .fact { padding-top: var(--space-2); border-top: 2px solid var(--ink); }
section.notice-facts .fact-warn { border-top-color: var(--warn); color: var(--warn); }
section.notice-facts dt { font-size: 0.8rem; }
section.notice-facts .fact-warn dt { color: inherit; }
section.notice-facts dd { margin: var(--space-1) 0 0; font-family: var(--serif); font-size: 0.95rem; }
section.notice-facts .fact-note { display: block; margin-top: var(--space-1); color: var(--ink-3); font: 400 0.8rem/1.4 var(--sans); }

.notice-actions a.button { display: inline-flex; align-items: center; gap: var(--space-2); }
.notice-actions details.share { margin: 0; }
.notice-actions details.share[open] { flex-basis: 100%; }
details.share > summary .icon { margin-right: var(--space-2); }

.notice-columns {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}
section.versions section.version { padding: var(--space-4); margin: 0 0 var(--space-3); }
section.versions section.version h3 { margin: 0 0 var(--space-1); font: 600 1.05rem/1.3 var(--serif); }
.version-line { margin: 0; color: var(--ink-2); }
details.version-detail > summary { margin-top: var(--space-2); color: var(--ink-2); font-size: 0.9rem; font-weight: 600; cursor: pointer; }
details.version-detail[open] > summary { margin-bottom: var(--space-3); }

/* ── Jobs (Jobs.razor, spec 2026-10-03 §5) ──────────────────────────────────
   One row per job: its title and start on one line, and under them a chip for each notice it needs
   (JobChips), or "No notice needed". Enter a job and the address book sit in .actions above. */
.job-list {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}
.job-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
  margin: 0;
  padding: var(--space-3) var(--space-4);
}
.job-row + .job-row { border-top: 1px solid var(--rule-faint); }
.job-title { font-family: var(--serif); font-weight: 600; }
.job-start { color: var(--ink-2); font-size: 0.9rem; white-space: nowrap; }
.job-row .job-chips,
.job-row .no-notice { grid-column: 1 / -1; margin: 0; }

/* ── Documents (Documents.razor, spec 2026-10-03 §5) ────────────────────────
   The site filter beside its button, and under each file's name what kind of file it is, quiet. A
   document kept at several sites lists each on its own line. At 52rem and below a record list's cell
   is a grid of its label and one value, so a cell that holds more than one thing wraps them in one
   div; unwrapped, the second would fall into the label's column. */
form.document-filter { display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-3); }
form.document-filter label { margin: 0; }
.document-kind { display: block; color: var(--ink-3); font-size: 0.85rem; }
a.document-site { display: block; }

/* The drafts section sits under the documents with the page's own rhythm above it. Send back is a
   <details> in the actions row: its summary reads as the quiet button beside Approve, and the box
   opens under the row. */
section.drafts { margin-top: var(--space-8); }
p.draft-approval { max-width: 40rem; color: var(--ink-2); }
details.send-back > summary {
  display: inline-block;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--ink-3);
  border-radius: var(--radius-control);
  color: var(--ink-2);
  font: 400 0.95rem/1.2 var(--sans);
  cursor: pointer;
}
details.send-back[open] { flex-basis: 100%; }
details.send-back[open] > summary { margin-bottom: var(--space-3); }

/* A document's notices, one box to a line, the label's words beside its box; the older ones fold
   under the same summary the firm page's older notices use. */
label.check { display: flex; gap: var(--space-2); align-items: baseline; margin: 0 0 var(--space-2); color: var(--ink); }
label.check input { margin: 0; }

/* ── Settings (Settings.razor, spec 2026-10-03 §5) ──────────────────────────
   Each section a panel with its own heading; what each role can do as a short list, the role's name
   in the ink. A form that is one item of an .actions row sits inline: its select and button side by
   side, the select's label heard and not seen. The firms the workspace holds are a plain list, one
   firm to a line, with what else it files under quiet beneath its name. */
ul.roles { margin: 0 0 var(--space-4); padding-left: var(--space-5); color: var(--ink-2); }
ul.roles strong { color: var(--ink); font-weight: 600; }
form.inline { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
form.inline label { margin: 0; }
form.inline select { margin-top: 0; min-width: 0; }
ul.firm-list { margin: 0 0 var(--space-4); padding: 0; list-style: none; }
ul.firm-list li { padding: var(--space-2) 0; border-top: 1px solid var(--rule-faint); }
ul.firm-list li:first-child { border-top: 0; }
.firm-also { display: block; color: var(--ink-3); font-size: 0.85rem; }

/* The claims console's facts (audit A4): labels in a column no wider than 11rem, values beside them.
   A class selector outranks the bare dl, so the 44rem block names this one too and a phone stacks
   them as it stacks every dl. */
dl.claim-facts { grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); }

/* ── Responsive ─────────────────────────────────────────────────────────────
   Three stops, a subset of the marketing site's six: the app has fewer
   distinct layouts, and 60/26/21.5rem answer questions it does not ask. The
   measure is 56rem and the wide frame 72rem, gutters included, and below 56rem
   both are the window less its gutters -- so 52rem is where the widest table,
   the seven-column notice list, stops fitting on any page. No stop here reads
   --frame or --frame-wide, so the letterhead, the banner and main keep one left
   edge at every width. */

@media (max-width: 52rem) {
  header.letterhead > div { flex-wrap: wrap; row-gap: var(--space-2); }

  /* The stacked-label treatment, for record lists only (Task 4 opts each table
     in with class="records"). thead is clipped rather than display:none so it
     stays in the accessibility tree; the roles are re-asserted in the markup
     because changing display on a table element strips the table role in every
     major browser. */
  table.records thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  table.records,
  table.records tbody,
  table.records tr,
  table.records td { display: block; }

  table.records { border: 0; background: none; }

  table.records tr {
    margin-bottom: var(--space-3);
    padding: var(--space-3);
    border: 1px solid var(--rule);
    background: var(--sheet);
  }

  table.records tbody tr + tr td { border-top: 0; }

  table.records td {
    display: grid;
    grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
    gap: var(--space-3);
    padding: var(--space-1) 0;
  }

  table.records td::before {
    content: attr(data-label);
    color: var(--ink-3);
    font-size: 0.8rem;
  }

  /* A cell with no value is noise once it has a label of its own. */
  table.records td:empty { display: none; }

  /* The links in a clipped header -- the citation on "Revise by", the Notices page's sort headings
     -- are links nobody can see, and a keyboard would still land on them. Out of the focus order
     with the rest of the header; a heading's words are shown again inside it, so a screen reader
     still names each column of the cards. Every notice page carries the full citation, and the
     Notices page's sorts are a row of their own (SortLinks). */
  table.records thead .guidance,
  table.records thead .sort-link { visibility: hidden; }
  table.records thead .sort-link > span { visibility: visible; }

  /* The links wear the headers' own .sort-link look: the green and the turned chevron for the sort in force. */
  .sort-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    margin: 0 0 var(--space-3);
    font-size: 0.9rem;
  }
  .sort-links a { color: var(--ink-2); }
  .sort-links .sort-links-label { color: var(--ink-3); }

  /* The two-column pages stack at the same stop as the record lists. */
  .place-columns,
  .notice-columns { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 44rem) {
  dl,
  dl.claim-facts { grid-template-columns: minmax(0, 1fr); gap: 0; }
  dt { margin-top: var(--space-2); }

  input[type="email"],
  input[type="text"],
  input[type="search"],
  input[type="number"],
  input[type="date"],
  input[type="time"],
  select,
  textarea { min-width: 0; width: 100%; }

  section.record form.rate { flex-direction: column; align-items: stretch; }
  section.record input[type="number"] { width: 100%; }
}

@media (max-width: 34rem) {
  :root { --gutter: 0.75rem; }
  h1 { font-size: 1.5rem; }
  main { padding-block: var(--space-6); }
  table.records td { grid-template-columns: minmax(0, 1fr); gap: 0; }
  table.records td::before { margin-top: var(--space-2); }
  nav.tabs { order: 3; flex-basis: 100%; overflow-x: auto; }
  /* All four tabs fit a 390 px phone: at the desktop padding they needed about 400 px of the 366 the
     gutters leave, so "Documents" was cut to "Document" at the edge with nothing saying it scrolls
     (cohort review, 2026-10-10). The row still scrolls on a narrower screen. */
  nav.tabs a { padding: var(--space-2); gap: var(--space-1); }
  /* The row scrolls, so it clips anything drawn outside it: the focus ring goes inside the tab. */
  nav.tabs a:focus-visible { outline-offset: -2px; }
  /* The firm menu takes what is left of the first row beside the mark and cuts a long name short with
     an ellipsis, so the tabs have the second row and the menu never takes a third: at 375 px a name of
     any length put it on a row of its own, because a flex line wraps on an item's whole width. */
  details.firm-menu { flex: 1 1 0; min-width: 0; }
  details.firm-menu > summary { width: fit-content; max-width: 100%; margin-left: auto; }
  .firm-menu-label { max-width: 10rem; }
  /* The greeting is an h1 like any other on a phone; its own rule above would otherwise outrank it. */
  h1.greeting { font-size: 1.5rem; }
  .crew-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  .job-row { grid-template-columns: minmax(0, 1fr); }
  /* The chips wrap above a search box as wide as the page. */
  form.notice-search { flex: 1 1 100%; margin-left: 0; }
  form.notice-search label { flex: 1; }
  form.notice-search input[type="search"] { width: 100%; }
  .place-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  section.now { grid-template-columns: minmax(0, 1fr); }
}

/* ── Forced colors ──────────────────────────────────────────────────────────
   Every signal in this app is a tinted background plus a border. In forced-
   colors mode the backgrounds are replaced by the user's own and the tint is
   gone, so each signal is restated in system colors and keeps its border. */
@media (forced-colors: active) {
  .notice,
  .mine,
  .guidance { border-color: CanvasText; }

  .notice-warn { color: Mark; border-color: Mark; }

  /* The banner's tint is its whole signal, and forced colors take it away. The rule under it is
     what survives, so it is restated in the system's own attention color. */
  .observing { color: Mark; border-bottom-color: Mark; }

  button { border-color: ButtonText; }

  .skip { border-color: CanvasText; }

  /* The facts panel carries its warning in the rule above a cell, so the rule is what has to
     survive when the tint does. */
  section.notice-facts .fact { border-top-color: CanvasText; }
  section.notice-facts .fact-warn { color: Mark; border-top-color: Mark; }

  /* A chip or a tag is a tint and its words; with the tint gone, a border keeps its shape. */
  .chip,
  .agency-tag { border: 1px solid CanvasText; }

  /* A link drawn as a button is a fill and its words; without the fill it would read as a link
     with its underline taken off. The border keeps it a button. */
  a.button { border: 1px solid LinkText; }

  /* Drawn a pixel inside the tab, because below 34rem the tab row scrolls and clips what is drawn
     outside a tab; and it gives way to the focus ring, which it would otherwise replace, leaving
     focus on the current tab unseen. */
  nav.tabs a[aria-current="page"]:not(:focus-visible) { outline: 1px solid CanvasText; outline-offset: -1px; }

  /* The chosen chip keeps a second line where its tint was, as the current tab does, and gives way
     to the focus ring as the tab's does. */
  .filter-chip[aria-current]:not(:focus-visible) { outline: 1px solid CanvasText; }
}

/* ── Reduced motion ─────────────────────────────────────────────────────────
   Nothing in this app animates today. This is the guard for what is added
   next, copied from the marketing site's own block. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── The shared notice ──────────────────────────────────────────────────────
   /s/{id}/{secret}: one job, read by a demolition contractor who has no account and who may well
   be printing it for a permit desk. Same paper as every other page -- it is the same record --
   with the narrative in the serif and the tables left in the sans, because on this page the
   sentences are the product and the version table is the evidence under them.

   The element is an <article> rather than a <main>: both layouts already wrap the page in
   <main id="main">, so a second main inside it would be two landmarks and invalid markup. Its
   <header> and <footer> belong to the article; the app chrome's rules are scoped to
   header.letterhead, so neither inherits anything from them. */
.shared > header { margin-bottom: var(--space-6); }

/* Who sent it sits above the number: the first thing a stranger needs is whose notice this is. */
.shared .from { margin: 0 0 var(--space-2); color: var(--ink-3); font-size: 0.9rem; }
.shared .site { font-family: var(--serif); margin: 0; }

/* The narrative: the paragraph for a notification that has left the record, and the sentences
   about when work can start. .provenance is already serif, and every table stays sans. */
.shared > p,
.shared section.start p { font-family: var(--serif); }

/* What each file is, under the link rather than beside it. */
.shared .files .note { display: block; color: var(--ink-3); font-size: 0.85rem; }

.shared > footer {
  margin-top: var(--space-8);
  padding-top: var(--space-4);
  border-top: 1px solid var(--rule);
  color: var(--ink-3);
  font-size: 0.9rem;
}

/* ── The shared notice, on paper ────────────────────────────────────────────
   Printed as a cover sheet for a raze permit: the app around it goes, the record and who sent it
   stay. Only absolute links are spelled out -- this page's own hrefs carry the share's secret,
   which has no business on a sheet of paper handed across a counter.

   Scoped to the page that asked for it, so no other page's printing changes. A browser without
   :has() prints the header too, which is a shabbier sheet rather than a broken one. */
@media print {
  body:has(.shared) > header,
  body:has(.shared) .skip { display: none; }

  body:has(.shared) main { max-width: none; padding: 0; }

  .shared a[href^="https://"]::after {
    content: " (" attr(href) ")";
    font-size: 0.8em;
    color: var(--ink-3);
  }
}

/* ── Sending a job to the demolition contractor ─────────────────────────────
   The share sits on the notification page beside "Mark this job complete" (design §3.7). It is a
   native <details>, so shut it is one line the reader can skip and open it is a panel like every
   other bordered block in this app.

   It is not in the panel group above for that reason: those take their padding on the section, and
   a closed disclosure with section padding is a tall empty box with a summary floating in it. The
   padding sits on the summary and on the opened body instead. Same border, same paper, same
   corners. */
details.share {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--sheet);
  margin: 0 0 var(--space-4);
}

details.share > summary {
  padding: var(--space-3) var(--space-4);
  font-weight: 600;
  cursor: pointer;
}

/* Everything after the summary is the opened sheet, held to the same gutter as the summary. */
details.share > :not(summary) { margin-inline: var(--space-4); }
details.share > :last-child { margin-bottom: var(--space-4); }

/* The boxes take a legend rather than a heading: the group is part of one form, not a section of
   the page. No border on the fieldset -- it would be a second frame inside the panel. */
details.share fieldset {
  margin: 0 0 var(--space-4);
  padding: 0;
  border: 0;
}

details.share legend {
  padding: 0;
  margin-bottom: var(--space-2);
  color: var(--ink-2);
  font-size: 0.9rem;
}

details.share fieldset label { display: flex; gap: var(--space-2); margin-bottom: var(--space-2); }
details.share input[type="checkbox"] { margin: 0.25em 0 0; }

/* The one sentence about who holds the link, directly above Send. */
details.share p.small {
  margin: 0 0 var(--space-4);
  color: var(--ink-3);
  font-size: 0.85rem;
}

/* Shown only when the mail did not go, and long enough to need breaking on a narrow screen. */
details.share code { word-break: break-all; }

/* A refused field (LabelledInput, RefusalMessages): the input takes the --warn family and the
   reason sits under it. Unscoped because the component is shared -- every form that asks its
   questions through it marks a refusal the same way. */
input[aria-invalid="true"] { border-color: var(--warn-border); background: var(--warn-bg); }
.refusal { display: block; margin-top: var(--space-1); color: var(--warn); font-size: 0.85rem; }

/* The job pages: what the job needs, as a checklist above the form (JobAnswer.razor, spec 2026-10-03
   §5) -- one card per agency, its tag and where its notice stands first, drawn by the jobs list's chip
   rules above (green once sent, rust once late). The left rule repeats the answer: green needed, the
   warn family not settled, the hairline not needed. The standing keeps the chip's weight, not the
   heading's bold. */
section.answer { margin: 0 0 var(--space-6); }
section.answer .agency {
  margin: 0 0 var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--rule);
  border-radius: var(--radius);
}
section.answer .agency h3 { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-2); font-size: 1rem; }
section.answer .agency .state { font-weight: 400; }
section.answer .agency p { margin: 0 0 var(--space-1); }
section.answer .agency p.headline { margin-bottom: var(--space-2); font-weight: 600; }
section.answer .agency p.hint { margin: var(--space-2) 0 0; }
section.answer .answer-needed { border-left-color: var(--green); }
section.answer .answer-open { border-left-color: var(--warn-border); }
section.answer .answer-not-needed { border-left-color: var(--rule); }

/* "Mark as sent" (SentMark.razor), at the foot of an agency's card under a hairline: the day and the
   button side by side, or who marked it and the way back. The check and the day are the heading's. */
section.answer .sent-mark {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--rule-faint);
}
section.answer .sent-mark form { display: flex; flex-wrap: wrap; align-items: end; gap: var(--space-3); margin: 0; }
section.answer .sent-mark label { margin: 0; }
section.answer .sent-mark .marked-by { color: var(--ink-3); font-size: 0.9rem; }

/* The job form (JobFields.razor, spec 2026-10-03 §5): its questions in their order, each top-level
   group a numbered panel, so the form reads one question at a time -- the rule's questions first,
   then where and when, the owner, the inspection and who removes it. A group inside a group keeps no
   frame of its own: the share disclosure's fieldset rules, scoped to this form so the notification
   page's own fieldset keeps its look. */
form.job-form { counter-reset: job-step; }
form.job-form fieldset { margin: 0 0 var(--space-4); padding: 0; border: 0; }
form.job-form legend { margin-bottom: var(--space-2); padding: 0; color: var(--ink-2); font-size: 0.9rem; font-weight: 600; }
form.job-form > fieldset {
  counter-increment: job-step;
  padding: var(--space-4) var(--space-5) var(--space-2);
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
/* Floated, so the legend sits inside its panel as a heading would rather than on the border. */
form.job-form > fieldset > legend {
  float: left;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  margin: 0 0 var(--space-4);
  color: var(--ink);
  font: 600 1.1rem/1.3 var(--serif);
}
form.job-form > fieldset > legend::before {
  content: counter(job-step);
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: var(--radius-pill);
  background: var(--tab-current-bg);
  color: var(--green);
  font: 700 0.85rem/1 var(--sans);
}
/* Every sibling, not only the next, so the rule does not hang on which question a panel opens with:
   "Where and when" opens with the site picker only when the workspace has sites. */
form.job-form > fieldset > legend ~ * { clear: both; }
form.job-form label.choice { display: flex; gap: var(--space-2); align-items: baseline; margin-bottom: var(--space-2); color: var(--ink); }

/* The amounts sit side by side, and so do the work days: a job fills one or two of the nine
   amounts, and a column of nine reads as nine questions. The bag choices after the amounts are
   block-level, so they start their own line. */
form.job-form fieldset.amounts > label:not(.choice) { display: inline-block; margin-right: var(--space-4); vertical-align: top; }
form.job-form fieldset.amounts input[type="text"] { min-width: 0; width: 9rem; }
form.job-form fieldset.days label.choice { display: inline-flex; margin-right: var(--space-4); }

/* Present to a screen reader, off the visible page -- unlike .skip, never meant to appear even on
   focus. The address book's remove column has no other heading a sighted reader needs. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  margin: -1px;
  padding: 0;
  border: 0;
}
