/* ============================================================================
   CALDWELL COUNTY DEVELOPMENT SERVICES — portal.css

   Loaded AFTER web/foundation.css, never instead of it. foundation.css is
   byte-identical to every sibling county app and is never edited; everything
   this product needs that the shared system does not already carry lives here,
   in the shared system's own token names.

   WHAT THIS FILE IS FOR, IN ORDER OF IMPORTANCE
   ---------------------------------------------
   1. foundation.css describes a CONSOLE: a fixed-height app frame with a
      248px sidebar, `body { overflow: hidden }`, and a single scrolling pane.
      CCDS is a PUBLIC SERVICE with a top bar, a footer, and a document that
      scrolls — core.js proves it, because App.enter() calls
      `window.scrollTo(0, 0)` on every navigation, not `root.scrollTop = 0`.
      Section 2 below unwinds the frame. Nothing else works until it does.

   2. Every component core.js actually renders. The class names here were read
      out of web/core.js, not invented: statusPill, chip, detail, emptyState,
      callout, ordinanceRef, the Controls factories, uploader, stepRail,
      commentThread, historyTimeline and queueTable. If a name does not appear
      in core.js or shell.html it is called out as a page-author contract where
      it is defined (there is exactly one such block, the PDF pane in §16).

   3. Mobile first. Everything works at 375px. The staff queue table becomes a
      card list at 700px. The page body NEVER scrolls sideways — anything wide
      (tables, the PDF frame) scrolls inside its own container.

   4. The government accessibility bar, which is above WCAG AA on purpose:
        · a visible focus ring on every interactive element, in both themes;
        · a 44px minimum touch target (--tap), not the 24px WCAG 2.5.8 floor;
        · secondary/tertiary ink held WELL above 4.5:1 — the ratios quoted in
          the comments below are measured against the DARKEST surface the ink
          actually lands on, in both themes, and they are held, not merely met.

   Both themes are styled throughout. The theme toggle stamps data-theme on
   <html> (see App.applyTheme + theme-boot.js), and because every colour here is
   a foundation token, an explicit stamp wins over prefers-color-scheme in both
   directions without this file repeating a single hex value. That is the whole
   reason to stay on the tokens: there are no per-theme override blocks down
   here to drift out of step.
   ============================================================================ */


/* ============================================================================
   1. NEW TOKENS
   Two, both deliberately theme-INDEPENDENT, which is why they are declared once
   in :root and not repeated in the three theme blocks. A token that cannot
   differ between themes cannot drift between them.
   ============================================================================ */

:root {
  /* The touch-target floor. WCAG 2.5.8 (AA) asks for 24px and foundation.css
     sets that globally; this is a county service used by residents on phones,
     so the floor here is the 2.5.5 (AAA) 44px. Stated once so that raising it
     is one edit, and so a reviewer can see the number rather than infer it from
     forty separate min-heights. */
  --tap: 44px;

  /* The shared measure. The header bar, the page body and the footer all line
     their content up on this, so the three read as one column rather than three
     independently centred blocks. 1080px is a reading measure, not a console
     measure — this half of the product is prose, forms and decisions. The staff
     queue opts out with .wrap.wide (§17). */
  --page-max: 1080px;
}


/* ============================================================================
   2. UNWINDING THE CONSOLE FRAME
   foundation.css §"app frame" builds a fixed-height flex console. This is a
   scrolling document with a sticky bar and a footer. Everything in this section
   exists to undo a specific rule up there; each one says which.
   ============================================================================ */

/* foundation: `html, body { height: 100% }` + `body { overflow: hidden }`.
   A hidden overflow on <body> means window.scrollTo() — which core.js calls on
   every navigation — silently does nothing, and the footer is unreachable. */
html, body {
  height: auto;
  min-height: 100%;
  overflow: visible;
}
html {
  /* iOS Safari inflates text in landscape unless told not to. On a form this
     long that reflows the whole wizard mid-answer. */
  -webkit-text-size-adjust: 100%;
}
body {
  /* 14px is a console body size. This half of the product is read by residents
     on phones, often outdoors; 15px is the smallest that is comfortable for
     continuous prose, and it cascades into every component below. */
  font-size: 15px;
  overflow-x: hidden;   /* a belt-and-braces guard: nothing may scroll the page sideways */
}

/* foundation: `#app { display: flex; height: 100vh }` — a row, viewport-tall. */
#app {
  display: flex;
  flex-direction: column;
  height: auto;
  min-height: 100vh;
  min-height: 100dvh;   /* dvh so the mobile URL bar collapsing does not leave a gap */
}

/* foundation: `#view-root { flex: 1; overflow: auto }` — its own scroll pane. */
#view-root {
  flex: 1 0 auto;
  overflow: visible;
  min-width: 0;
}
/* #view-root carries tabindex="-1" so the skip link and every navigation can
   move focus into it. It is never keyboard-reachable by tabbing, so a ring
   around the entire page would only ever appear as noise after a route change —
   the page heading is what announces the change. */
#view-root:focus { outline: none; }

/* `hidden` has to actually hide. core.js sets it as an attribute on
   .route-progress and on every .field-error, and any display value declared by
   an author rule beats the UA's `[hidden] { display: none }` regardless of
   specificity. One rule, once, rather than a :not([hidden]) on each. */
[hidden] { display: none !important; }

/* Modal.open() stamps this while a dialog is up. Without it the page behind the
   overlay scrolls under the user's thumb on a phone. */
body.modal-open { overflow: hidden; }


/* ============================================================================
   3. ACCESSIBILITY PRIMITIVES
   foundation.css defines neither .sr-only nor .skip-link, and core.js and
   shell.html use both heavily.
   ============================================================================ */

/* Visually gone, still in the accessibility tree, still focusable. The file
   input inside the uploader wears this, so it must NOT be display:none — a
   display:none input cannot be reached by Tab, and the visible <label> is not
   focusable in its place. */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The first focusable thing on the page, and visible the moment it is focused.
   A skip link that never becomes visible is the single most common finding in a
   government accessibility audit, so this one is a solid, high-contrast panel
   rather than a hairline outline. */
.skip-link {
  position: fixed;
  top: 0; left: 12px;
  z-index: 700;
  transform: translateY(-120%);
  padding: 12px 18px;
  min-height: var(--tap);
  display: inline-flex; align-items: center;
  background: var(--surface);
  color: var(--accent-soft);          /* 11.1:1 light on --surface, 8.0:1 dark */
  border: 2px solid var(--focus-ring);
  border-top: none;
  border-radius: 0 0 var(--r-m) var(--r-m);
  box-shadow: var(--shadow-pop);
  font-weight: 700;
  text-decoration: none;
  transition: transform 0.14s ease;
}
.skip-link:focus {
  transform: translateY(0);
  text-decoration: none;
}

/* foundation's global ring is 2px --focus-ring at offset 1. That is right on a
   surface and wrong on two grounds this app has: inside an input (where the
   ring lands on the border and reads as a thicker border) and on a wash. Widen
   the offset so the ring always sits clear of the control it belongs to. */
*:focus-visible {
  outline: 2.5px solid var(--focus-ring);
  outline-offset: 2px;
}
/* Focus inside a dark ground (the header) has to be gold, not the blue ring —
   --focus-ring is #35578f in light mode and disappears into the slate bar.
   foundation already does this for #topbar; the footer needs the same. */
.site-foot :focus-visible { outline-color: var(--focus-ring); }

/* Every icon core.js draws. Never the only carrier of meaning — each one sits
   beside real words — so it inherits colour and is aria-hidden at source. */
.ico { flex: none; display: block; }


/* ============================================================================
   4. THE HEADER
   shell.html: #topbar.site-header > .site-header-in > .brand + #site-nav +
   .site-header-actions, with #route-progress pinned to its lower edge.

   The ground and the inks come from foundation's --topbar-* tokens, so this
   bar is slate in light mode and graphite in dark with nothing here knowing
   which. Measured: --topbar-ink 10.1:1 and --topbar-ink-2 6.0:1 on the light
   slate (#323d4a); 12.5:1 and 7.7:1 on the dark ground (#1b1c1f). Both inks are
   far above the floor in both themes, which is the point — at 13px a
   bare-minimum ratio on a dark bar reads as fog.
   ============================================================================ */

/* foundation: `#topbar { height: 52px; flex: 0 0 52px; display: flex; padding: 0 16px 0 10px }`.
   Here the bar is a full-bleed band and .site-header-in is the flex row inside
   it, so the ground runs edge to edge while the content lines up on --page-max. */
#topbar.site-header {
  display: block;
  position: sticky;
  top: 0;
  z-index: 100;
  height: auto;
  flex: none;
  padding: 0;
}

.site-header-in {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 8px 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 60px;
}

/* ---------- brand ---------- */
/* foundation styles .brand only inside .side-navy (the sidebar it does not have
   here), so the whole lockup is defined from scratch. */
#topbar .brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 4px 0 0;
  min-width: 0;                /* lets the wordmark truncate instead of shoving the bar wide */
  min-height: var(--tap);      /* it is the "home" control, not decoration */
  color: var(--topbar-ink);
  text-decoration: none;
  border-radius: var(--r-m);
  flex: 0 1 auto;
}
#topbar .brand:hover { text-decoration: none; }
#topbar .brand-mark {
  flex: 0 0 30px;
  width: 30px; height: 30px;
  display: block;
}
#topbar .brand-text {
  display: block;
  line-height: 1.15;
  min-width: 0;
}
#topbar .brand-cap {
  display: block;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--topbar-ink-2);
  white-space: nowrap;
}
#topbar .brand-word {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 650;
  color: var(--topbar-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The county's gold carries the identity on the bar; it is decorative weight on
   a word that is already legible in --topbar-ink, never load-bearing colour.
   #D4AA28 measures 5.0:1 on the light slate and 7.8:1 on the dark ground. */
#topbar .brand-word .gold { color: var(--gold); }

/* ---------- primary navigation ---------- */
/* Built by App.renderHeader() as #site-nav > ul.nav-list > li > a.nav-item.
   foundation's .nav-item is a SIDEBAR item — --ink-2 on a near-white sidebar —
   and is unreadable on this bar in light mode, so every one of its colours is
   replaced. The ID in the selector is what beats foundation's .nav-item.active. */
.site-nav { min-width: 0; }
.site-nav .nav-list {
  display: flex;
  align-items: center;
  gap: 2px;
  list-style: none;
  margin: 0;
  padding: 0;
}
#site-nav .nav-item {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 6px 12px;
  margin: 0;
  border-radius: var(--r-m);
  font-size: 14px;
  font-weight: 600;
  color: var(--topbar-ink-2);
  background: none;
  white-space: nowrap;
  text-decoration: none;
}
#site-nav .nav-item:hover {
  background: var(--topbar-hover);
  color: var(--topbar-ink);
  text-decoration: none;
}
/* Gold underline for the current section, matching foundation's own
   #topbar .proj-tab.active convention. The `aria-current="page"` core.js sets
   is the non-visual half of the same statement. */
#site-nav .nav-item.active {
  background: none;
  color: var(--topbar-ink);
  font-weight: 700;
  box-shadow: inset 0 -2.5px 0 var(--gold);
  border-radius: var(--r-s) var(--r-s) 0 0;
}

/* ---------- header actions: theme toggle, account, menu ---------- */
.site-header-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}
#topbar .btn-icon {
  min-width: var(--tap);
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-m);
}

.account-slot { display: flex; align-items: center; }
.account {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.account-name {
  font-size: 13.5px;
  font-weight: 650;
  color: var(--topbar-ink);
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The role line is the one piece of header text a reviewer checks before acting
   on somebody else's application, so it takes --topbar-ink-2 (6.0:1 light /
   7.7:1 dark) rather than a third, paler step. There is no --topbar-ink-3. */
.account-role {
  font-size: 11.5px;
  color: var(--topbar-ink-2);
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account .btn { min-height: 38px; }   /* inside a 60px bar; the row itself is the large target */

/* The menu button exists only below the nav breakpoint. core.js always wires
   it, so hiding it here is the whole of the desktop behaviour. */
.nav-toggle { display: none !important; }

/* ---------- route progress ----------
   A 2.5px bar on the lower edge of the header while a page loads. It is
   announced through #live-region, never from here, so it carries no ARIA of its
   own. The track is always visible when the bar is not [hidden]; the sweep is
   decoration on top of it. */
.route-progress {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2.5px;
  overflow: hidden;
  background: var(--topbar-line);
}
.route-progress::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 38%;
  background: var(--gold);
  animation: ccdsSweep 1.1s ease-in-out infinite;
}
@keyframes ccdsSweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(340%); }
}
/* foundation crushes every animation to 0.01ms under prefers-reduced-motion,
   which would leave this sweep parked off-screen at translateX(-100%) — i.e. a
   loading indicator that shows nothing to exactly the users least able to guess
   that the page is working. Fill the track instead. */
@media (prefers-reduced-motion: reduce) {
  .route-progress::after { width: 100%; animation: none; transform: none; }
}
.route-progress[hidden] { display: none; }


/* ============================================================================
   5. THE PAGE BODY
   core.js paints `h('div', { class: 'wrap' }, …)` straight into #view-root —
   App.guarded's error state, App.skeleton, renderNotFound and every page
   module. It is the page container.

   COLLISION, HANDLED DELIBERATELY: foundation.css already owns `.wrap` as a
   one-line flex utility (`.wrap { flex-wrap: wrap }`), used as `.row.wrap`.
   Redefining a bare `.wrap` here would silently pad and centre every wrapping
   flex row in the product. So the page-container rules are scoped to
   `#view-root > .wrap`, which is exactly and only where core.js puts one, and
   foundation's utility keeps working untouched everywhere else.
   ============================================================================ */

#view-root > .wrap {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 24px 16px 56px;
  min-width: 0;
}
/* The staff queue and the property page are tabular and want the room. This is
   the only page-layout modifier this file introduces. */
#view-root > .wrap.wide { max-width: 1360px; }

/* Page typography. Styled on the ELEMENTS rather than on classes because the
   page modules are still being written: a page that simply appends an <h1> and
   a couple of <p>s has to come out right with no class vocabulary to learn.
   clamp() so the h1 does not eat a third of a 375px screen.

   Every one of these is wrapped in :where(), which drops the whole selector to
   ZERO specificity — the same device foundation.css uses on its default input
   width, and for the same reason. These are DEFAULTS: a <p> that is also a
   .field-hint, a .callout-text paragraph or a page's own class must win without
   having to out-specify an ID. Author rules still beat the UA sheet at any
   specificity, so nothing is lost. */
:where(#view-root) :where(h1) {
  font-size: clamp(22px, 5.4vw, 28px);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.2;
  margin: 0 0 6px;
}
:where(#view-root) :where(h2) {
  font-size: clamp(17px, 3.6vw, 20px);
  font-weight: 700;
  line-height: 1.25;
  margin: 28px 0 10px;
}
:where(#view-root) :where(h3) {
  font-size: 15.5px;
  font-weight: 700;
  line-height: 1.3;
  margin: 20px 0 8px;
}
:where(#view-root) :where(h1, h2, h3):first-child { margin-top: 0; }
/* Prose measure. A resident reading why their plat was returned should not be
   tracking a 1080px line. */
:where(#view-root) :where(p) { max-width: 68ch; line-height: 1.55; }
:where(#view-root) :where(p + p) { margin-top: 10px; }
/* --ink-2 is 8.5:1 on white and 7.7:1 on the dark surface: a lead paragraph is
   still body copy and never drops to the tertiary step. */
#view-root .lede {
  font-size: 16px;
  color: var(--ink-2);
  margin-bottom: 18px;
}

/* foundation's .card is 16px of padding and a hairline; give it room to breathe
   as a page section and let it stack. */
#view-root .card { padding: 18px; }
#view-root .card + .card { margin-top: 14px; }

/* Links inside the page body get a visible underline, not colour alone —
   --accent-soft against --ink is a hue change of about 3:1, which is a
   distinction a red-green or low-vision reader cannot rely on. */
#view-root a:not(.btn):not(.nav-item):not(.up-cta) {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
#view-root a:not(.btn):not(.nav-item):not(.up-cta):hover {
  text-decoration-thickness: 2px;
}


/* ============================================================================
   6. THE FOOTER
   shell.html: footer.site-foot > .site-foot-in > p.foot-line (+ .foot-meta).
   The second line is the ordinance notice — a legal statement, so it takes
   --ink-2 (8.5:1 light / 7.7:1 dark on the footer's --surface-2 ground it is
   measured against: 7.4:1 light), not a faded tertiary grey.
   ============================================================================ */

.site-foot {
  flex: none;
  margin-top: 48px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
}
.site-foot-in {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 22px 16px 30px;
}
.foot-line {
  font-size: 13px;
  color: var(--ink-2);
  max-width: 70ch;
}
.foot-line + .foot-line { margin-top: 8px; }
.foot-meta {
  font-size: 12.5px;
  color: var(--ink-2);   /* 7.35:1 on --surface-2 — deliberately not --ink-3 */
  line-height: 1.5;
}


/* ============================================================================
   7. STATUS PILLS — one per lifecycle status
   core.js: <span class="status-pill sp-{status} tone-{tone}"><i class="sp-dot">
   The tone comes from the STATUS table in core.js and there are eight of them:
   neutral · neutral-dark · accent · progress · warn · danger · good ·
   violet-alert. Each is mapped onto a foundation semantic wash — the ink is
   ALWAYS measured on the wash it sits on, which is the tightest pairing there
   is, and foundation states each of those ratios in its own comments.

   Colour is never the only signal. Every pill prints the status in words, and
   the dot is a shape as well as a hue.

   .sp-{status} is emitted for all twenty-one statuses (and sp-unknown for a
   status the client does not know). Nothing keys off it here — the tone carries
   the appearance — but it is a stable hook for a page that needs to point at
   one particular status, and sp-unknown proves an unrecognised status still
   renders as a legible neutral pill rather than as unstyled text.
   ============================================================================ */

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 12.5px;
  font-weight: 650;
  line-height: 1.45;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
.status-pill .sp-dot {
  flex: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: currentColor;
}

/* draft · withdrawn — in hand, nothing has happened to it */
.status-pill.tone-neutral {
  background: var(--surface-2);
  color: var(--ink-2);              /* 7.35:1 light · 8.3:1 dark, on this wash */
  border-color: var(--line-2);
}
/* expired · deemed withdrawn · superseded — closed for good. Full-strength ink,
   a firm --ink-3 ring, and a HOLLOW dot: the ring reads as "shut" at a glance
   without relying on a second grey nobody can name against the first. */
.status-pill.tone-neutral-dark {
  background: var(--surface-2);
  color: var(--ink);                /* 13.6:1 light · 12.9:1 dark, on this wash */
  border-color: var(--ink-3);
}
.status-pill.tone-neutral-dark .sp-dot {
  background: none;
  box-shadow: inset 0 0 0 2px currentColor;
}
/* submitted · intake scheduled · response received — filed and moving */
.status-pill.tone-accent {
  background: var(--accent-wash);
  color: var(--accent-soft);
  border-color: var(--accent-wash-line);
}
/* administratively complete · in technical review · on the Court agenda ·
   ready to record — the county is actively working it. Violet rather than a
   second amber: --warn-wash and --gold-tint are both pale yellows with nearly
   identical inks, so a gold "in review" and an amber "comments issued" would be
   indistinguishable at 12.5px, and those two are precisely the pair an
   applicant must tell apart to know whose move it is. */
.status-pill.tone-progress {
  background: var(--violet-wash);
  color: var(--violet);             /* 6.97:1 on wash */
  border-color: var(--violet-wash-line);
}
/* comments issued · awaiting your response · extra review cost · held in
   abeyance — the applicant's move */
.status-pill.tone-warn {
  background: var(--warn-wash);
  color: var(--warn);               /* 5.78:1 on wash */
  border-color: var(--warn-wash-line);
}
/* returned incomplete · disapproved */
.status-pill.tone-danger {
  background: var(--danger-wash);
  color: var(--danger);             /* 5.40:1 on wash */
  border-color: var(--danger-wash-line);
}
/* approved · approved with conditions · recorded · permit issued */
.status-pill.tone-good {
  background: var(--good-wash);
  color: var(--good);               /* 5.87:1 on wash */
  border-color: var(--good-wash-line);
}
/* deemed approved by operation of law. One status, and an extraordinary one:
   the statutory period elapsed without a decision (Tex. Loc. Gov't Code ch.
   232). It is the same violet family as "progress" turned SOLID, so it cannot
   be mistaken for ordinary in-flight work. --accent-ink is white in light mode
   and near-black in dark, which is the one token that stays legible on a
   saturated ground in both: 8.3:1 light, 7.9:1 dark. */
.status-pill.tone-violet-alert {
  background: var(--violet);
  color: var(--accent-ink);
  border-color: var(--violet);
  font-weight: 700;
}


/* ============================================================================
   8. CHIPS
   core.js: chip(text, tone) -> <span class="chip tone-{tone}">, used for the
   review discipline on a comment and for anything a page needs that is not a
   lifecycle status. foundation.css already owns .chip's shape; only the tones
   are new, and they reuse the same measured wash pairings as the pills above.
   ============================================================================ */

.chip.tone-neutral { background: var(--surface-2);   color: var(--ink-2);       border-color: var(--line-2); }
.chip.tone-accent  { background: var(--accent-wash); color: var(--accent-soft); border-color: var(--accent-wash-line); }
.chip.tone-good    { background: var(--good-wash);   color: var(--good);        border-color: var(--good-wash-line); }
.chip.tone-warn    { background: var(--warn-wash);   color: var(--warn);        border-color: var(--warn-wash-line); }
.chip.tone-danger  { background: var(--danger-wash); color: var(--danger);      border-color: var(--danger-wash-line); }
.chip.tone-violet  { background: var(--violet-wash); color: var(--violet);      border-color: var(--violet-wash-line); }
.chip.tone-gold    { background: var(--gold-tint);   color: var(--gold-deep);   border-color: var(--gold-deep); }
.chip.tone-progress { background: var(--violet-wash); color: var(--violet);     border-color: var(--violet-wash-line); }
.chip-interactive { min-height: var(--tap); padding-left: 12px; padding-right: 12px; }


/* ============================================================================
   9. DETAIL PAIRS, ORDINANCE CITATIONS, CALLOUTS
   ============================================================================ */

/* core.js: <div class="detail"><dt class="detail-label"><dd class="detail-value">
   The <dt>/<dd> want a <dl> ancestor to be valid, so a page wraps a run of these
   in one; both the wrapped and the bare case have to look the same, which is why
   nothing here depends on the parent. */
.detail { min-width: 0; }
.detail + .detail { margin-top: 12px; }
.detail-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  /* --ink-2, not --ink-3: at 11px in caps the tertiary step is the first thing
     that goes muddy, and these labels are what tell an applicant which number on
     the page is the acreage and which is the fee. 8.5:1 light / 7.7:1 dark. */
  color: var(--ink-2);
  margin: 0 0 3px;
}
.detail-value {
  margin: 0;
  font-size: 15px;
  color: var(--ink);
  overflow-wrap: anywhere;   /* parcel ids and legal descriptions have no spaces */
}
.detail-value .muted { color: var(--ink-2); }

/* core.js: ordinanceRef(ref) -> <span class="ord-ref" title="…the ordinance…">.
   Printed wherever a rule is being applied, so an applicant can go and read the
   rule. Monospaced because it is a citation, not prose. */
.ord-ref {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.86em;
  font-weight: 600;
  line-height: 1.5;
  padding: 1px 6px;
  border-radius: var(--r-s);
  background: var(--surface-2);
  color: var(--ink-2);          /* 7.35:1 light · 8.3:1 dark, on --surface-2 */
  border: 1px solid var(--line);
  cursor: help;                 /* it carries a title; say so */
  white-space: nowrap;
}

/* core.js: callout(tone, title, …body) ->
   <div class="callout tone-{info|warn|danger|good}" [role=alert]>
     <div class="callout-ico"><div class="callout-body">
       <b class="callout-title"><div class="callout-text">
   The BODY text is always --ink (>12.8:1 on every wash, both themes) and only
   the title and the icon take the semantic colour. A whole paragraph set in
   --danger is both harder to read and a weaker signal than one red heading. */
.callout {
  display: flex;
  gap: 12px;
  padding: 14px 16px;
  border-radius: var(--r-l);
  border: 1px solid var(--line-2);
  border-left-width: 4px;
  background: var(--surface-2);
  color: var(--ink);
  margin: 16px 0;
}
.callout-ico { flex: none; padding-top: 1px; color: var(--ink-2); }
.callout-body { min-width: 0; flex: 1; }
.callout-title {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 4px;
}
.callout-text { font-size: 14.5px; line-height: 1.55; }
.callout-text > p { max-width: none; }
.callout-text > :first-child { margin-top: 0; }

.callout.tone-info   { background: var(--accent-wash); border-color: var(--accent-wash-line); }
.callout.tone-info   .callout-ico, .callout.tone-info   .callout-title { color: var(--accent-soft); }
.callout.tone-good   { background: var(--good-wash);   border-color: var(--good-wash-line); }
.callout.tone-good   .callout-ico, .callout.tone-good   .callout-title { color: var(--good); }
.callout.tone-warn   { background: var(--warn-wash);   border-color: var(--warn-wash-line); }
.callout.tone-warn   .callout-ico, .callout.tone-warn   .callout-title { color: var(--warn); }
.callout.tone-danger { background: var(--danger-wash); border-color: var(--danger-wash-line); }
.callout.tone-danger .callout-ico, .callout.tone-danger .callout-title { color: var(--danger); }


/* ============================================================================
   10. FORM CONTROLS
   Every input type in forms/catalog.json, as built by the Controls factories in
   core.js: text · textarea · email · phone · number · acreage · money · date ·
   select · multiselect · checkbox · signature · static.

   Shared frame (fieldFrame): a .field wrapper — a <div>, or a <fieldset> for
   multiselect — containing .field-label (or a <legend> with the same class),
   .field-hint, the control, and a .field-error that starts [hidden].

   fieldFrame also stamps a per-type modifier: .field-text, .field-acreage,
   .field-money, .field-signature and so on. Most of those are DELIBERATELY
   given no rule of their own — the control inside carries the appearance, and a
   modifier that exists only to repeat it would be one more thing to keep in
   step with catalog.json. They are left as stable targeting hooks for the two
   cases that do need one (.field-checkbox below, and any page that has to reach
   a single question type), and their absence from this file is a decision, not
   an omission.
   ============================================================================ */

.field { margin: 0 0 20px; min-width: 0; }
/* multiselect's frame is a real <fieldset> with a <legend>. Both carry browser
   defaults (a border, an inset legend, and an intrinsic min-width that refuses
   to shrink inside a flex or grid parent) that have to go. */
fieldset.field {
  border: 0;
  padding: 0;
  min-width: 0;
}

/* foundation's .field-label is an 11px uppercase micro-label — right for a
   console panel, wrong here. These are questions lifted verbatim from the
   county's own PDFs ("Total acreage of the tract being subdivided"), and set in
   caps at 11px they are genuinely hard to read. Sentence case, --ink, and big
   enough to be the primary text of the field. */
.field-label {
  display: block;
  font-size: 14.5px;
  font-weight: 650;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  margin: 0 0 6px;
  padding: 0;            /* <legend> defaults to 2px of side padding */
  max-width: 60ch;
}
/* The asterisk is aria-hidden and paired with a visually hidden " (required)",
   so this is purely the sighted half of the statement. */
.req {
  color: var(--danger);
  margin-left: 3px;
  font-weight: 700;
}
/* A hint is an instruction, not a footnote: --ink-2 at 13.5px, never --ink-3. */
.field-hint {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink-2);   /* 8.5:1 light · 7.7:1 dark, on --surface */
  margin: 0 0 8px;
  max-width: 62ch;
}
/* The error node is referenced by aria-describedby and the control gets
   aria-invalid, so this is the visual half of a message that is also announced.
   It is deliberately not colour-only — the ▲ and the weight carry it too. */
.field-error {
  display: flex;
  gap: 6px;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--danger);   /* 6.6:1 light · 6.1:1 dark, on --surface */
  margin: 7px 0 0;
  max-width: 62ch;
}
.field-error::before {
  content: '\25B2';       /* a filled triangle; the message says what is wrong */
  flex: none;
  font-size: 10px;
  line-height: 1.7;
}

/* ---------- the boxes ----------
   16px is not a taste decision: iOS Safari zooms the viewport whenever a focused
   input is under 16px, and on a wizard with thirty fields that means the page
   jumps and re-centres on every single tap. foundation's 13.5px would do it on
   every field in the product. */
.input,
.textarea,
select.input {
  font-size: 16px;
  line-height: 1.4;
  min-height: var(--tap);
  padding: 10px 12px;
  border-radius: var(--r-m);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  max-width: 100%;
}
.textarea, textarea.input {
  min-height: 110px;
  line-height: 1.55;
  resize: vertical;
}
.input:hover, .textarea:hover, select.input:hover { border-color: var(--ink-3); }
.input:focus, .textarea:focus, select.input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}
/* Placeholders are hints of last resort here — every control has a real
   <label for> — but where one exists it still has to be readable. */
.input::placeholder, .textarea::placeholder { color: var(--ink-3); opacity: 1; }
.input:disabled, .textarea:disabled, select.input:disabled {
  background: var(--surface-2);
  color: var(--ink-2);
  cursor: not-allowed;
}

/* Native <select> keeps its native appearance on purpose (see the comment on
   Controls.select in core.js): on a phone it opens the platform picker, which is
   larger, thumb-scrollable and already speaks to every assistive technology. */
select.input {
  padding-right: 30px;
  cursor: pointer;
}
/* iOS renders a date input at its own intrinsic height and ignores padding, so
   it collapses to about 30px next to its 44px neighbours unless height is
   pinned. min-height, not height, so a zoomed-text user's larger control grows. */
input[type="date"].input {
  min-height: var(--tap);
  display: block;
  width: 100%;
}

/* The error state paints the box as well as printing the message. */
.field.has-error .input,
.field.has-error .textarea,
.field.has-error select.input,
.field.has-error .input-prefix,
.field.has-error .input-suffix {
  border-color: var(--danger);
}
.field.has-error .input:focus,
.field.has-error .textarea:focus,
.field.has-error select.input:focus,
.field.has-error .input-prefix:focus-within,
.field.has-error .input-suffix:focus-within {
  box-shadow: 0 0 0 3px var(--danger-wash);
}

/* ---------- affixed boxes: money ($ before) and acreage (acres after) ----------
   core.js wraps the real input in a <div class="input-prefix"> (span, input) or
   <div class="input-suffix"> (input, span). The WRAPPER becomes the visible
   control and the input inside it goes transparent, so the focus ring surrounds
   the whole thing including the unit rather than half of it. */
.input-prefix,
.input-suffix {
  display: flex;
  align-items: stretch;
  min-height: var(--tap);
  border: 1px solid var(--line-2);
  border-radius: var(--r-m);
  background: var(--surface);
  overflow: hidden;
}
.input-prefix:hover, .input-suffix:hover { border-color: var(--ink-3); }
.input-prefix:focus-within,
.input-suffix:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}
/* The wrapper clips its own overflow (that is what keeps the $ / acres block
   inside the rounded corner), and an outline on the input INSIDE it is clipped
   along with everything else — so a keyboard user would get the accent border
   and no ring, alone among every control in the product. Put the ring on the
   wrapper, which is the thing that looks like the control anyway.
   :has(:focus-visible) rather than :focus-within so a mouse click still gets
   only the border and halo, exactly like every other input. */
.input-prefix:has(.input:focus-visible),
.input-suffix:has(.input:focus-visible) {
  outline: 2.5px solid var(--focus-ring);
  outline-offset: 2px;
}
.input-prefix > span,
.input-suffix > span {
  flex: none;
  display: flex;
  align-items: center;
  padding: 0 12px;
  font-size: 15px;
  font-weight: 650;
  color: var(--ink-2);        /* 8.5:1 light · 7.7:1 dark */
  background: var(--surface-2);
  user-select: none;
}
.input-prefix > span { border-right: 1px solid var(--line); }
.input-suffix > span { border-left: 1px solid var(--line); }
.input-prefix > .input,
.input-suffix > .input {
  border: none;
  background: none;
  box-shadow: none;
  border-radius: 0;
  min-height: 0;
  flex: 1;
  min-width: 0;
}
.input-prefix > .input:focus,
.input-suffix > .input:focus { box-shadow: none; }
/* Both are numeric fields — an amount and an acreage — so they line up. */
.input-prefix > .input,
.input-suffix > .input { font-variant-numeric: tabular-nums; }

/* ---------- checkboxes: multiselect groups and single booleans ----------
   core.js: <label class="check-row" for=id><input class="check-box"><span
   class="check-text">. The LABEL is the target, which is how a 15px native box
   still clears 44px — exactly what the comment on Controls.checkbox promises. */
.check-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.check-row {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  min-height: var(--tap);
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface);
  cursor: pointer;
  font-size: 15px;
  line-height: 1.45;
}
.check-row:hover { background: var(--surface-3); border-color: var(--line-2); }
/* The whole row lights up when it is ticked, so the answer is legible from a
   metre away rather than from a 15px glyph. :has() is the only way to reach the
   label from its own descendant input; where it is unsupported the native box
   still shows the state, which is why this is an enhancement and not the
   mechanism. */
.check-row:has(.check-box:checked) {
  background: var(--accent-wash);
  border-color: var(--accent-wash-line);
}
/* The input is sr-only-adjacent in size, so the ring has to go on the row. */
.check-row:has(.check-box:focus-visible) {
  outline: 2.5px solid var(--focus-ring);
  outline-offset: 2px;
}
/* foundation pins every checkbox to 15px with `input[type="checkbox"]`
   (specificity 0-1-1), which a bare .check-box (0-1-0) loses to. Matching on
   both is what makes this stick. 20px because these are the answers a plat
   application is held to, not a table's row selector. */
input[type="checkbox"].check-box {
  flex: none;
  width: 20px; height: 20px;
  margin: 1px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.check-text { min-width: 0; color: var(--ink); }

/* A single boolean gets no separate label above it — the row IS the label — so
   the field needs the hint and error to line up under the row, not the box. */
.field-checkbox .field-hint,
.field-checkbox .field-error { margin-left: 2px; }
.field-checkbox .field-hint { margin-top: 7px; margin-bottom: 0; }

/* ---------- the typed attestation ----------
   NOT a signature pad. What is captured is a typed name bound to an
   authenticated account, with the account id, the UTC time and the source IP
   recorded server-side — see the long comment on Controls.signature in core.js.
   The panel is styled to look like what it is: a certification being agreed to,
   with the wording the applicant is bound to printed above the line. */
.sig-wrap {
  border: 1px solid var(--line-2);
  border-radius: var(--r-l);
  background: var(--surface-2);
  padding: 16px;
}
.sig-statement {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 14px;
  max-width: 62ch;
}
.sig-input {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 0.01em;
  min-height: 54px;
  background: var(--surface);
}
/* The provenance line is evidence, not a footnote — it is what the applicant is
   told is being recorded against their name. --ink-2 on --surface-2 is 7.35:1
   light and 8.3:1 dark; it never drops to the tertiary step. */
.sig-provenance {
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
  margin: 10px 0 0;
  max-width: 68ch;
}
.sig-provenance b { color: var(--ink); font-weight: 650; }

/* ---------- read-only values the county filled in ---------- */
.static-value {
  display: block;
  min-height: var(--tap);
  padding: 10px 12px;
  border-radius: var(--r-m);
  border: 1px dashed var(--line-2);
  background: var(--surface-2);
  color: var(--ink);
  font-size: 15px;
  overflow-wrap: anywhere;
}

/* ---------- two-up fields on a wide screen ----------
   spec.half adds `.half` to the frame. Below 720px everything is full width,
   because a half-width date field at 375px is a date field nobody can read.

   inline-block rather than a grid or flex, because .field's parent is whatever
   container a page module chose and this must not depend on it. Two rules keep
   that honest:
     · the GAP is padding INSIDE the box (box-sizing is border-box, from
       foundation), never a margin. Two boxes at `50% - margin` plus two margins
       come to more than 100% and wrap to one per line — which is exactly the
       bug this replaced.
     · 4px is shaved off each width so a stray whitespace text node between two
       fields (core.js's append() never emits one, but a page assembled some
       other way might) cannot tip the pair onto two lines either. */
@media (min-width: 720px) {
  .field.half,
  fieldset.field.half {
    display: inline-block;
    vertical-align: top;
    width: calc(50% - 4px);
    padding-right: 12px;
  }
}


/* ============================================================================
   11. THE FILE UPLOADER
   core.js: .uploader > (input.sr-only.up-input) + .up-drop + ul.up-list,
   one li.up-row per file with its own .up-bar progressbar and a real cancel.
   ============================================================================ */

.uploader { margin: 4px 0 0; }

.up-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  padding: 24px 18px;
  border: 2px dashed var(--line-2);
  border-radius: var(--r-l);
  background: var(--surface-2);
  color: var(--ink-2);
  transition: background 0.12s ease, border-color 0.12s ease;
}
.up-drop.over {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-wash);
}
.up-drop.disabled { opacity: 1; border-style: solid; background: var(--surface-2); }
.up-drop.disabled .up-cta {
  pointer-events: none;
  background: var(--surface-2);
  color: var(--ink-2);
  border-color: var(--line-2);
}
/* The only keyboard path into the uploader is the visually hidden file input —
   the visible .up-cta is a <label>, and a label is not focusable. Without this
   ring, tabbing through the wizard passes through the uploader with no visible
   focus anywhere on screen. The input precedes .up-drop in the DOM, which is
   what makes the sibling combinator work. */
.up-input:focus-visible + .up-drop {
  outline: 2.5px solid var(--focus-ring);
  outline-offset: 3px;
  border-color: var(--accent);
}
.up-drop-text {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* A <label for> that has to look and measure like a button. */
.up-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 8px 16px;
  border-radius: var(--r-m);
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 15px;
  font-weight: 650;
  cursor: pointer;
}
.up-cta:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.up-or { font-size: 14px; color: var(--ink-2); }
.up-hint {
  font-size: 13px;
  color: var(--ink-2);      /* the accepted formats and the size cap: real instructions */
  margin: 0;
  max-width: 46ch;
}

.up-list { list-style: none; margin: 12px 0 0; padding: 0; }
.up-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  background: var(--surface);
  font-size: 14px;
}
.up-row + .up-row { margin-top: 8px; }
.up-ico { flex: none; display: flex; color: var(--ink-2); }
.up-name {
  flex: 1 1 140px;
  min-width: 0;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.up-size {
  flex: none;
  font-size: 13px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
/* The bar takes its own line so a 375px row never has to choose between the
   filename and the progress. `order` puts it after the name/size pair whatever
   the source order does. */
.up-bar {
  order: 3;
  flex: 1 1 100%;
  height: 7px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
}
.up-bar-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.18s ease;
}
.up-pct {
  order: 4;
  flex: none;
  font-size: 13px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}
.up-status {
  order: 5;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink-2);
}
.up-row > .btn { order: 6; margin-left: auto; }

.up-row.ok        { border-color: var(--good-wash-line);   background: var(--good-wash); }
.up-row.ok .up-status     { color: var(--good); }      /* 5.87:1 on this wash */
.up-row.failed    { border-color: var(--danger-wash-line); background: var(--danger-wash); }
.up-row.failed .up-status { color: var(--danger); }    /* 5.40:1 on this wash */
.up-row.cancelled { border-color: var(--line-2);           background: var(--surface-2); }
.up-row.cancelled .up-status { color: var(--ink-2); }  /* 7.35:1 on this wash */


/* ============================================================================
   12. THE WIZARD STEP RAIL
   core.js: nav.step-rail > ol.step-list > li.step.step-{done|current|todo},
   each holding a .step-btn (a real <button> where the step is reachable, a
   <span> where it is not) with .step-marker and .step-label.

   The rail is a SEGMENTED TRACK: every step owns one segment of a 3px underline,
   and the segments fill in as the applicant advances. That is what makes it
   survive 375px — at that width the labels drop away and six numbered markers
   over six coloured segments still say exactly where you are, which a rail built
   out of connector lines and labels cannot do.
   ============================================================================ */

.step-rail { margin: 0 0 26px; }
.step-list {
  display: flex;
  align-items: stretch;
  list-style: none;
  margin: 0;
  padding: 0;
  gap: 4px;
}
.step {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  border-bottom: 3px solid var(--line-2);
  padding-bottom: 6px;
}
.step-btn {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: 4px 6px;
  border: none;
  background: none;
  border-radius: var(--r-m);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  color: var(--ink-2);
}
button.step-btn { cursor: pointer; }
button.step-btn:hover { background: var(--surface-3); color: var(--ink); }
.step-marker {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 27px; height: 27px;
  border-radius: 50%;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--surface-2);
  color: var(--ink-2);
  box-shadow: inset 0 0 0 1px var(--line-2);
}
.step-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.step-done { border-bottom-color: var(--good); }
.step-done .step-marker {
  background: var(--good-wash);
  color: var(--good);                      /* 5.87:1 on wash */
  box-shadow: inset 0 0 0 1px var(--good-wash-line);
}
.step-current { border-bottom-color: var(--accent); }
.step-current .step-btn { color: var(--ink); font-weight: 700; }
.step-current .step-marker {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: none;
}
.step-todo .step-btn { color: var(--ink-3); }   /* 7.4:1 light · 6.2:1 dark, on --surface */

/* At 375px six labels cannot fit and truncating them to three characters each
   tells nobody anything. Only the current step keeps its words, and it takes the
   space the others give up — so the rail reads "①②③ Property ⑤⑥" and the answer
   to "where am I?" is right there. */
@media (max-width: 620px) {
  .step-list { gap: 3px; }
  .step { flex: 0 0 auto; }
  .step-current { flex: 1 1 auto; min-width: 0; }
  .step-label { display: none; }
  .step-current .step-label { display: block; }
  .step-btn { justify-content: center; padding: 4px; }
  .step-current .step-btn { justify-content: flex-start; }
}


/* ============================================================================
   13. REVIEW COMMENT THREADS
   core.js: article.comment.sev-{severity}.cs-{status} with .comment-head
   (.comment-sev.tone-*, a discipline chip, an .ord-ref, .comment-meta),
   .comment-body, an optional .comment-response, and .comment-foot
   (.comment-status.cs-*, plus Respond / Mark resolved).

   §3.4(F) requires review comments be returned IN WRITING, and this component
   is shared between the applicant's page and the county console precisely so
   the two cannot render the same comment differently. Nothing here is styled
   per-audience for the same reason.
   ============================================================================ */

.comment {
  display: block;
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-2);
  border-radius: var(--r-l);
  background: var(--surface);
  padding: 14px 16px;
}
.comment + .comment { margin-top: 12px; }

.comment-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.comment-sev {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 4px 9px;
  border-radius: var(--r-s);
  border: 1px solid transparent;
  white-space: nowrap;
}
.comment-sev.tone-danger  { background: var(--danger-wash); color: var(--danger); border-color: var(--danger-wash-line); }
.comment-sev.tone-warn    { background: var(--warn-wash);   color: var(--warn);   border-color: var(--warn-wash-line); }
.comment-sev.tone-neutral { background: var(--surface-2);   color: var(--ink-2);  border-color: var(--line-2); }
/* Round, sequence and date. Small, but it is what an applicant quotes back to
   the county on the phone, so it holds --ink-2 rather than the tertiary step. */
.comment-meta {
  font-size: 12.5px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.comment-body {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-wrap;      /* reviewers write lists; the newlines are meaning */
  overflow-wrap: anywhere;
  max-width: 68ch;
}

.comment-response {
  margin-top: 12px;
  padding: 12px 14px;
  border-radius: var(--r-m);
  background: var(--surface-2);
  border-left: 3px solid var(--accent);
  font-size: 14.5px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.comment-response-label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--accent-soft);   /* 11.1:1 light · 8.0:1 dark, on --surface-2 */
  margin-bottom: 5px;
}
.comment-response .comment-meta { display: block; margin-top: 6px; white-space: normal; }

.comment-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
  padding-top: 11px;
  border-top: 1px solid var(--line);
}
.comment-status {
  display: inline-flex;
  align-items: center;
  font-size: 12px;
  font-weight: 650;
  padding: 3px 10px;
  border-radius: 999px;
  border: 1px solid transparent;
}
.comment-status.cs-open      { background: var(--warn-wash);   color: var(--warn);        border-color: var(--warn-wash-line); }
.comment-status.cs-responded { background: var(--accent-wash); color: var(--accent-soft); border-color: var(--accent-wash-line); }
.comment-status.cs-resolved  { background: var(--good-wash);   color: var(--good);        border-color: var(--good-wash-line); }
.comment-status.cs-withdrawn { background: var(--surface-2);   color: var(--ink-2);       border-color: var(--line-2); }
.comment-foot .btn { margin-left: auto; }

/* The left rail carries severity; the card ground carries the state. A resolved
   comment recedes onto --surface-2 — it is NOT faded with opacity, because
   opacity would drag the body text below the contrast floor exactly when
   somebody is checking what they agreed to. */
.comment.sev-must_address  { border-left-color: var(--danger); }
.comment.sev-advisory      { border-left-color: var(--warn); }
.comment.sev-informational { border-left-color: var(--line-2); }
.comment.cs-resolved  { background: var(--surface-2); border-left-color: var(--good); }
.comment.cs-withdrawn { background: var(--surface-2); border-left-color: var(--line-2); }


/* ============================================================================
   14. THE PROPERTY HISTORY TIMELINE
   core.js: ol.timeline > li.tl-item.tl-{kind} > span.tl-dot + div.tl-body
   (.tl-head with a link and a status pill, .tl-when, .tl-detail).
   The parcel is the spine of this product — every application ever filed
   against a piece of land, whoever filed it — so this is a record, not a feed.
   ============================================================================ */

.timeline { list-style: none; margin: 0; padding: 0; }
.tl-item {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 14px;
  position: relative;
  padding-bottom: 22px;
}
/* The rail between the markers. Drawn from the dot's lower edge to the bottom of
   the row, and dropped on the last item so the timeline ends rather than trails. */
.tl-item::before {
  content: '';
  position: absolute;
  left: 16px;
  top: 36px;
  bottom: 0;
  width: 2px;
  background: var(--line);
}
.tl-item:last-child { padding-bottom: 0; }
.tl-item:last-child::before { display: none; }

.tl-dot {
  position: relative;
  z-index: 1;
  grid-column: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink-2);
  border: 1px solid var(--line-2);
}
.tl-body { grid-column: 2; min-width: 0; padding-top: 5px; }
.tl-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  font-size: 15px;
  font-weight: 650;
}
.tl-head b { font-weight: 650; }
.tl-when {
  font-size: 13px;
  color: var(--ink-2);       /* a date on a legal record: never the tertiary step */
  margin-top: 3px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
}
.tl-detail {
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink);
  margin-top: 6px;
  max-width: 68ch;
}

/* Marker colour by kind, on the same measured wash pairings as everywhere else.
   The icon inside already differs per kind (core.js's KIND_ICON), so the colour
   is reinforcement, not the signal. */
.tl-application .tl-dot { background: var(--accent-wash); color: var(--accent-soft); border-color: var(--accent-wash-line); }
.tl-permit      .tl-dot { background: var(--good-wash);   color: var(--good);        border-color: var(--good-wash-line); }
.tl-inspection  .tl-dot { background: var(--good-wash);   color: var(--good);        border-color: var(--good-wash-line); }
.tl-plat        .tl-dot { background: var(--violet-wash); color: var(--violet);      border-color: var(--violet-wash-line); }
.tl-restriction .tl-dot { background: var(--danger-wash); color: var(--danger);      border-color: var(--danger-wash-line); }
.tl-meeting     .tl-dot { background: var(--accent-wash); color: var(--accent-soft); border-color: var(--accent-wash-line); }
.tl-invoice     .tl-dot { background: var(--warn-wash);   color: var(--warn);        border-color: var(--warn-wash-line); }
.tl-document    .tl-dot { background: var(--surface-2);   color: var(--ink-2);       border-color: var(--line-2); }


/* ============================================================================
   15. THE STAFF QUEUE TABLE AND ITS CLOCK BADGES
   core.js: .table-scroll > table.data-table.queue-table with a .sr-only caption,
   a real <thead>, and one tr.queue-row per application whose first cell is a
   <th scope="row">. Every body cell carries data-label — that attribute is the
   entire mechanism behind the card collapse below.
   ============================================================================ */

/* Wide content scrolls inside its own box. The page body must never scroll
   sideways — that rule is why this exists at all. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: var(--r-l);
}

.queue-table { width: 100%; }
.queue-table th { padding: 10px 12px; }
.queue-table td { padding: 10px 12px; }
/* foundation's `.data-table th` styles COLUMN headers — 10.5px, uppercase,
   letterspaced — and it matches the <th scope="row"> in each body row too,
   which would print every application number as a tiny caps label. Specificity
   0-1-2 here beats it. */
.queue-table tbody th {
  font-size: 15px;
  font-weight: 650;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  white-space: normal;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.queue-table thead th { color: var(--ink-2); }   /* 8.5:1: column headers are read, not skimmed */
/* foundation makes every data-table row a pointer. Here the row is not
   clickable — the application number inside it is a real link, which is what
   lets it be opened in a new tab, copied, and reached by keyboard.
   .queue-row is the class core.js actually puts on the <tr>; it is named here
   rather than left to the `tbody tr` selectors alone so the emitted hook is not
   an orphan, and so a page module can restyle a row without knowing that the
   row happens to be a <tr>. */
.queue-table tbody tr,
.queue-row { cursor: default; }
.queue-table tbody tr:hover { background: var(--surface-3); }
.q-project {
  display: block;
  font-size: 13px;
  font-weight: 400;
  color: var(--ink-2);
  margin-top: 2px;
}

/* ---------- clock badges ----------
   One ordinance clock per row. The tone comes from core.js: unset when there is
   no due date (which includes a deadline_rule the county has never confirmed —
   BUILD-PLAN non-negotiable 4 — and which must never look like "no deadline"),
   ok, soon at 3 days, breached past due.

   The colour lands on the row surface, not on a wash, so: --danger measures
   6.6:1 light and 6.1:1 dark on --surface; --warn 6.6:1 and 8.4:1. A breached
   statutory clock additionally takes the danger wash across the cell — it is the
   one thing in this table allowed to shout. */
.q-clock {
  white-space: nowrap;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}
.q-breached {
  color: var(--danger);
  background: var(--danger-wash);
}
.q-soon { color: var(--warn); }
.q-ok   { color: var(--ink-2); }
.q-unset { color: var(--ink-3); font-weight: 600; }

/* The marker dot lives in ::before — and so does the cell's data-label once the
   table collapses to cards below, and an element has only ONE ::before. So the
   dot is confined to the table layout, where the label is in the <thead>
   instead. Below 700px the label wins the pseudo-element and the dot goes; the
   card gives the clock its own boxed row there, and the words ("3 days
   overdue") were always the actual signal. */
@media (min-width: 701px) {
  .q-clock::before {
    content: '';
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: currentColor;
    margin-right: 8px;
    vertical-align: 0;
  }
  /* A clock that has not started and a clock that is fine are not the same fact
     as one running out, so their markers are rings rather than filled dots. */
  .q-ok::before,
  .q-unset::before {
    background: none;
    box-shadow: inset 0 0 0 2px currentColor;
  }
}

/* ---------- the card collapse ----------
   Below 700px the table becomes one card per application. Six columns cannot be
   read at 375px and a sideways-scrolling queue is a queue nobody scans.

   <thead> is hidden rather than clipped-but-present: once the rows are blocks
   the table semantics are gone for assistive technology anyway, and a detached
   header row read as a run-on sentence at the top is worse than nothing. What
   replaces it is per-cell — every cell prints its own data-label through
   ::before, so each value arrives already named, in place, in the reading order
   it belongs to. That is why core.js sets data-label on every body cell. */
@media (max-width: 700px) {
  .table-scroll { overflow-x: visible; }
  .queue-table,
  .queue-table tbody,
  .queue-table tbody tr,
  .queue-table tbody th,
  .queue-table tbody td { display: block; width: auto; }
  .queue-table thead { display: none; }

  .queue-table tbody tr {
    border: 1px solid var(--line);
    border-radius: var(--r-l);
    background: var(--surface);
    padding: 14px 16px;
    margin-bottom: 12px;
  }
  .queue-table tbody tr:hover { background: var(--surface); }

  /* The application number is the card's heading. */
  .queue-table tbody th {
    border-bottom: none;
    padding: 0 0 10px;
    font-size: 17px;
    font-weight: 700;
  }
  .queue-table tbody th::before { display: none; }

  .queue-table tbody td {
    border-bottom: none;
    padding: 5px 0;
    display: flex;
    align-items: baseline;
    gap: 10px;
  }
  .queue-table tbody td::before {
    content: attr(data-label);
    flex: 0 0 34%;
    max-width: 130px;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-2);      /* 8.5:1 light · 7.7:1 dark — caps at 11.5px needs it */
  }
  /* The clock is the reason a card is on screen; give it the badge treatment
     instead of leaving it as one more labelled line. */
  .queue-table tbody td.q-clock {
    margin-top: 8px;
    padding: 9px 11px;
    border-radius: var(--r-m);
    background: var(--surface-2);
    align-items: center;
  }
  .queue-table tbody td.q-breached { background: var(--danger-wash); }
}


/* ============================================================================
   16. THE PDF PREVIEW PANE
   ---------------------------------------------------------------------------
   THE ONE PLACE IN THIS FILE THAT NAMES CLASSES core.js DOES NOT RENDER.
   GET /applications/:id/preview.pdf and /:id/pdf are page-level views, so the
   markup below is the CONTRACT for page-apply.js and page-application.js:

     <section class="pdf-pane">
       <header class="pdf-pane-head">
         <h2 class="pdf-pane-title">The county's form</h2>
         <div class="pdf-pane-actions"> … buttons … </div>
       </header>
       <div class="pdf-frame"><iframe title="…" src="…/preview.pdf"></iframe></div>
       <p class="pdf-note">…what this is, and that it is not yet filed…</p>
       <div class="pdf-fallback"> … an Open / Download button … </div>
     </section>

   Both blocks are always rendered; CSS chooses which one the device sees.
   ---------------------------------------------------------------------------
   The frame is US Letter (8.5 × 11) so the preview is the shape of the page it
   represents. Below 760px it is REMOVED, not shrunk: an embedded PDF viewer on a
   phone is a pinch-zoom trap inside a scrolling page, and every mobile browser
   already has a better one a tap away. .pdf-fallback is that tap.
   ============================================================================ */

.pdf-pane {
  border: 1px solid var(--line);
  border-radius: var(--r-l);
  background: var(--surface);
  padding: 16px;
  margin: 18px 0;
}
.pdf-pane-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 12px;
}
.pdf-pane-title {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  margin: 0;
}
.pdf-pane-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pdf-frame {
  position: relative;
  aspect-ratio: 8.5 / 11;
  width: 100%;
  border: 1px solid var(--line-2);
  border-radius: var(--r-m);
  background: var(--surface-2);
  overflow: hidden;
}
.pdf-frame > iframe,
.pdf-frame > object,
.pdf-frame > embed {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  border: 0;
  display: block;
}
.pdf-note {
  font-size: 13.5px;
  color: var(--ink-2);
  margin-top: 10px;
  max-width: 68ch;
}
/* Hidden on a screen big enough to show the real thing. */
.pdf-fallback { display: none; }

@media (max-width: 760px) {
  .pdf-frame { display: none; }
  .pdf-fallback {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    padding: 16px;
    border: 1px dashed var(--line-2);
    border-radius: var(--r-m);
    background: var(--surface-2);
    color: var(--ink-2);
    font-size: 14px;
  }
}


/* ============================================================================
   17. EMPTY STATES, SKELETONS, TOASTS, MODALS
   ============================================================================ */

/* foundation owns .empty-state; these are the two things it gets wrong for a
   public page — a 320px sub-line is too narrow to explain a scope 404, and
   --ink-3 on a large reassuring message is paler than it needs to be. */
.empty-state { color: var(--ink-2); }
.empty-state .es-title { font-size: 17px; color: var(--ink); }
.empty-state .es-sub { font-size: 14.5px; max-width: 44ch; line-height: 1.55; color: var(--ink-2); }
.empty-state .es-ico { color: var(--ink-2); }
.empty-state .btn, .empty-state a { margin-top: 16px; display: inline-flex; }

/* App.skeleton() paints .skel + .skel-line / .skel-block / .skel-row. It exists
   to hold the page height steady so nothing jumps when the data lands.
   @keyframes shimmer is foundation's. */
.skel {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 50%, var(--surface-2) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.4s infinite;
  border-radius: var(--r-m);
}
.skel-line { height: 30px; width: 42%; min-width: 180px; margin-bottom: 20px; }
.skel-block { height: 260px; }
.skel-row { height: 56px; margin-bottom: 10px; }

/* foundation's toast is --ink on --bg: a dark pill in light mode and a LIGHT
   pill in dark mode, because both tokens flip. That inversion leaves no colour
   that can carry a tone on both grounds. Here the toast is a surface card, so
   the semantic inks land on a ground they are measured against — --good 6.8:1
   light / 7.6:1 dark, --danger 6.6 / 6.1, --warn 6.6 / 8.4 — and the tone reads
   as a rail plus an icon rather than as tinted text. */
.toast {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-left: 4px solid var(--ink-3);
  border-radius: var(--r-m);
  box-shadow: var(--shadow-pop);
  padding: 11px 12px 11px 14px;
  gap: 10px;
  align-items: flex-start;
  max-width: min(520px, calc(100vw - 24px));
  font-weight: 500;
  font-size: 14.5px;
}
.toast .t-ico { flex: none; color: var(--ink-2); padding-top: 1px; }
.toast .t-text { min-width: 0; line-height: 1.45; overflow-wrap: anywhere; }
.toast .t-x { flex: none; margin-left: 4px; }   /* sized by the 44px floor in §18 */
.toast.tone-good   { border-left-color: var(--good); }
.toast.tone-good   .t-ico { color: var(--good); }
.toast.tone-info   { border-left-color: var(--accent); }
.toast.tone-info   .t-ico { color: var(--accent-soft); }
.toast.tone-warn   { border-left-color: var(--warn); }
.toast.tone-warn   .t-ico { color: var(--warn); }
.toast.tone-danger { border-left-color: var(--danger); }
.toast.tone-danger .t-ico { color: var(--danger); }
/* core.js adds .leaving 320ms before it removes the node. */
.toast.leaving { opacity: 0; transform: translateY(6px); transition: opacity 0.3s ease, transform 0.3s ease; }
#toast-root { bottom: max(20px, env(safe-area-inset-bottom)); }

/* Modal.open() emits .modal-{sm|md|lg}; foundation only knows a single 560px
   .modal and a .modal-task it does not use here. */
.modal-sm { max-width: 440px; }
.modal-md { max-width: 580px; }
.modal-lg { max-width: 860px; }
.modal-head { padding: 16px 18px 0; align-items: flex-start; }
.modal-title { font-size: 17px; line-height: 1.3; }
.modal-x { margin-left: auto; flex: none; }
.modal-body { font-size: 15px; line-height: 1.55; }
.modal-body p { max-width: none; }
.modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 10px;
  padding: 0 18px 18px;
}


/* ============================================================================
   18. BUTTONS AND SHARED CONTROLS AT THE 44px FLOOR
   foundation sets a 24px minimum (WCAG 2.5.8 AA). This is the AAA target, and
   it is deliberate: the primary users of the public half are residents on
   phones, and the primary users of the county half are staff on tablets at an
   intake counter. Declared last in the file so it wins on source order against
   foundation's identical-specificity `.btn`.
   ============================================================================ */

.btn,
button.btn,
a.btn,
.btn-sm,
.btn-icon,
.seg button {
  min-height: var(--tap);
}
.btn {
  padding: 9px 16px;
  font-size: 14.5px;
  border-radius: var(--r-m);
  gap: 8px;
}
.btn-sm { padding: 7px 13px; font-size: 14px; }
.btn-icon { min-width: var(--tap); justify-content: center; }
/* An outlined secondary button on a card and on a wash must both read as a
   control — foundation's --line-2 border is faint on the toned callout grounds. */
.callout .btn { border-color: var(--ink-3); background: var(--surface); }
/* A form's primary action is the widest, most obvious thing on a phone screen. */
.btn-block { display: flex; width: 100%; justify-content: center; }

/* ---------- standalone links that are really controls ----------
   The application number in a queue row, the property link beside it and the
   title on a timeline entry are how staff open a record — they are navigation
   wearing link markup, not words in a sentence, so the WCAG "inline" target
   exception does not honestly cover them. At 15px they measure about 20px tall.

   The padding is cancelled by an equal negative margin, so the HIT AREA grows to
   44px while the layout does not move at all — a queue that grew 24px per row to
   satisfy this would just push the next application off the screen. Nothing
   interactive is stacked closely enough inside these components for the enlarged
   boxes to overlap each other; what they overlap is the row's own padding and
   the non-interactive project-name line.

   Prose links (§5) are deliberately NOT included: they are inside a block of
   text, which is exactly the case the exception exists for, and expanding them
   would put overlapping hit boxes on top of the lines above and below. */
.queue-table tbody a,
.tl-head a {
  display: inline-block;
  padding: 12px 4px;
  margin: -12px -4px;
}

/* The action row that closes every wizard step and every staff form. Stacks —
   and puts the primary action FIRST in the visual order at the top of the stack
   is wrong on a phone, so it stays last: the thumb reaches the bottom of the
   screen most easily, and "Back" above "Continue" matches the reading order. */
.form-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 24px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}
@media (max-width: 560px) {
  .form-actions { flex-direction: column-reverse; align-items: stretch; }
  .form-actions .btn { width: 100%; justify-content: center; }
}


/* ============================================================================
   19. RESPONSIVE — the header collapse
   The nav breakpoint is 860px: below it the primary navigation moves into a
   panel under the bar, toggled by #nav-toggle (core.js toggles body.nav-open and
   keeps aria-expanded in step, and closes the panel on any #site-nav link).

   #account-slot stays OUT of the panel and visible at every width. Signed out it
   holds the "Sign in" button, which is the single most important control on the
   public half and must never be one tap further away than it has to be; signed
   in it is the only place a resident can see which account they are filing
   under. Only the identity TEXT is dropped as the bar narrows.
   ============================================================================ */

@media (max-width: 860px) {
  .site-header-in { flex-wrap: wrap; padding: 6px 14px; min-height: 56px; gap: 8px; }

  .nav-toggle { display: inline-flex !important; }

  /* The panel: full width, below the bar row, in the flow so it pushes the page
     down instead of covering it. A dropdown that overlays the first paragraph of
     a page is a dropdown people dismiss without reading what is under it. */
  .site-nav {
    flex: 0 0 100%;
    order: 5;
    display: none;
    margin: 4px -14px 0;
    padding: 6px 8px 8px;
    border-top: 1px solid var(--topbar-line);
  }
  body.nav-open .site-nav { display: block; }
  .site-nav .nav-list { flex-direction: column; align-items: stretch; gap: 2px; }
  #site-nav .nav-item {
    width: 100%;
    padding: 10px 14px;
    font-size: 15px;
    border-radius: var(--r-m);
  }
  #site-nav .nav-item.active {
    box-shadow: inset 3px 0 0 var(--gold);
    background: var(--topbar-hover);
    border-radius: var(--r-m);
  }

  .account-role { display: none; }
  #view-root > .wrap { padding: 20px 14px 44px; }
  .site-foot-in { padding: 20px 14px 26px; }
}

@media (max-width: 560px) {
  .site-header-in { gap: 6px; padding: 6px 10px; }
  #topbar .brand-cap { display: none; }
  #topbar .brand-word { font-size: 14px; }
  #topbar .brand-mark { flex-basis: 28px; width: 28px; height: 28px; }
  .account-name { display: none; }
  .site-nav { margin-left: -10px; margin-right: -10px; }

  #view-root > .wrap { padding: 18px 12px 40px; }
  .site-foot-in { padding: 18px 12px 24px; }
  #view-root .card { padding: 14px; border-radius: var(--r-m); }
  .callout { padding: 12px 13px; border-radius: var(--r-m); }
  .comment { padding: 12px 13px; }
  .modal-overlay { padding: 16px 10px 32px; }
  .up-drop { padding: 18px 12px; }
  .sig-wrap { padding: 13px; }
  .sig-input { font-size: 18px; }
}


/* ============================================================================
   20. PRINT
   A permit, an approval letter and a fee invoice all get printed at the counter
   and stapled into a paper file. Chrome, navigation and controls come off;
   citations and statuses stay, because a printed page with the ordinance section
   missing is a page the county cannot act on.
   ============================================================================ */

@media print {
  #topbar, .site-foot, .skip-link, #toast-root, #tip-layer,
  .step-rail, .uploader, .form-actions, .pdf-pane-actions,
  .btn, .btn-icon { display: none !important; }

  html, body { background: #fff; color: #000; font-size: 11pt; }
  #app { display: block; min-height: 0; }
  #view-root > .wrap { max-width: none; padding: 0; }

  /* Ink on paper, not screen washes. The words on every pill and badge are what
     survive; the colour was never the signal. */
  .status-pill, .chip, .comment-sev, .comment-status, .ord-ref {
    background: none !important;
    color: #000 !important;
    border: 1px solid #666 !important;
  }
  .card, .comment, .callout, .pdf-pane {
    border: 1px solid #999 !important;
    background: none !important;
    break-inside: avoid;
  }
  .tl-item, .up-row, .queue-table tbody tr { break-inside: avoid; }
  .skel { animation: none; background: none; }
  a[href] { text-decoration: underline; }
}
