/* Read My Meter admin — the Vector Foundry shell (spec 27, Part A).
   Source: VF repository commit 770bb52b, `WorkspaceLayout.tsx` and
   `PainMapView.reference.tsx`, ported from React/Tailwind to plain
   CSS here. This is a one-time fork: this repository owns the shell
   code from now on, and a later change on the Vector Foundry side
   does not cascade here (spec 27, decision 6).

   The `--vf-*` colour tokens live in `tokens.css` (the VF UI pack's
   own set, plus this app's own `--vf-danger-hover`, added there so no
   raw colour sits outside a tokens file); this file adds the spacing,
   radius, shadow, and motion tokens the pack's `tokens.css` does not
   carry. */

:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;
  --radius-pill: 999px;

  --shadow-subtle: 0 8px 30px rgba(0, 0, 0, .055);
  --shadow-overlay: 0 24px 80px rgba(0, 0, 0, .16);

  --motion-fast: 140ms;
  --motion-standard: 200ms;
  --motion-overlay: 240ms;
}

/* --- The shell (spec 27, Part A, A3) ----------------------------------------
   Three columns inside `.shell`: the sidebar (`.shell-nav`, column 1),
   the section's own pages (`.shell-side`, column 2, only on a page
   that fills `{% block side %}`), and `main.content` (column 3). The
   compact top bar (`.shell-topbar`) and the sidebar-as-sheet rules
   sit in their own breakpoint below, at 980px (the kit's own drawer
   breakpoint; spec 27 keeps the standard's 760/1050px split for the
   content gutter and column 2's row instead). */

.shell {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
  padding: var(--space-3);
}

.shell-nav {
  width: 248px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: 12px;
  position: sticky;
  top: var(--space-3);
  max-height: calc(100vh - 24px);
  overflow-y: auto;
}

.shell-nav-header { padding: var(--space-4); border-bottom: 1px solid var(--vf-border); }
.shell-nav-header-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2); }

.shell-nav-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  text-decoration: none;
  color: var(--vf-text);
}
.shell-nav-brand img { flex-shrink: 0; border-radius: 8px; }
.shell-nav-brand-text { display: flex; flex-direction: column; min-width: 0; }
.shell-nav-brand-name {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.shell-nav-brand-sub { font-size: 12px; line-height: 1.25; color: var(--vf-text-muted); }

.shell-nav-close {
  display: none;
  flex-shrink: 0;
  background: none;
  border: none;
  padding: var(--space-1);
  color: var(--vf-text-muted);
  cursor: pointer;
}
.shell-nav-close svg { width: 16px; height: 16px; }
.shell-nav-close:hover { color: var(--vf-text); }

.shell-nav-context {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--vf-border);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.shell-nav-context-all {
  font-size: 12px;
  font-weight: 500;
  color: var(--vf-text-muted);
  text-decoration: none;
}
.shell-nav-context-all:hover { color: var(--vf-text); text-decoration: underline; }
.shell-nav-context-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--vf-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 220px;
}
.shell-nav-context-agency {
  font-size: 12px;
  color: var(--vf-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.shell-nav-context .org-switch { padding: var(--space-1) 0; }

.shell-nav-groups {
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.shell-nav-group { display: flex; flex-direction: column; gap: 2px; }
.shell-nav-group-label {
  margin: 0;
  padding: 0 var(--space-3) var(--space-1);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--vf-text-muted);
}

.shell-nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: 8px;
  font-size: 14px;
  color: var(--vf-text-muted);
  text-decoration: none;
  transition: color var(--motion-fast) ease-out, background-color var(--motion-fast) ease-out;
}
.shell-nav-item svg { width: 16px; height: 16px; flex-shrink: 0; }
.shell-nav-item:hover { background: rgba(0, 0, 0, .03); color: var(--vf-text); }
.shell-nav-item[aria-current="page"] {
  background: var(--vf-accent-subtle);
  color: var(--vf-accent);
  font-weight: 500;
}

.shell-nav-account {
  margin-top: auto;
  padding: var(--space-4);
  border-top: 1px solid var(--vf-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.shell-nav-account-name {
  margin: 0;
  font-size: 13px;
  color: var(--vf-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 200px;
}
.shell-nav-account-links { display: flex; align-items: center; gap: var(--space-3); }
.shell-nav-account-links a,
.shell-nav-account-links button {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--vf-text-muted);
  text-decoration: none;
  cursor: pointer;
}
.shell-nav-account-links a:hover,
.shell-nav-account-links button:hover { color: var(--vf-text); text-decoration: underline; }
.shell-nav-account-links form { margin: 0; }

/* `.shell-body` groups column 2 and column 3. It is its own flex
   context so column 2 can sit beside the content at 1050px and above
   (a row, sidebar | column 2 | content) and stack above it below
   that (a column, sidebar | [column 2, then content]), independently
   of the sidebar's own 980px breakpoint above. */
.shell-body {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  gap: var(--space-5);
}
/* Column 2 sits beside the content from 1500px (2026-09-27: at 1440px
   the sidebar, column 2, and a side panel left a table no room). Below
   that it is the row of links under the page title. */
/* Column 2 is always the row of links under the page title
   (2026-09-27): a column of two links left a hole beside the sidebar. */

/* Column 2: the section's own pages, from `_setup_side.html` or
   `_billing_side.html`. The links keep their `.setup-side` and
   `.setup-side-link` styling (below); this wrapper only places the
   column. */
.shell-side {
  width: 200px;
  flex-shrink: 0;
  position: sticky;
  top: var(--space-3);
}

/* The compact top bar (below 980px only) and the sidebar sheet it
   opens. Hidden by default; the breakpoint below turns them on. */
.shell-topbar {
  display: none;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  padding: 0 var(--space-4);
  background: var(--vf-surface);
  border-bottom: 1px solid var(--vf-border);
  position: relative;
  /* Above the scrim (z-index 39), so the Menu button stays clickable
     to close the sheet, not just to open it. */
  z-index: 41;
}
.shell-topbar-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--vf-text);
  font-size: 15px;
  font-weight: 650;
}
.shell-topbar-brand img { border-radius: 6px; }

.shell-menu-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--vf-surface);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: 13px;
  font-weight: 600;
  color: var(--vf-text);
  cursor: pointer;
  min-height: 44px;
}
.shell-menu-btn svg { width: 16px; height: 16px; }

.shell-scrim {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .3);
  z-index: 39;
}

@media (max-width: 979px) {
  /* `.shell-topbar` is a normal-flow sibling of `.shell-body` here
     (the sidebar itself is `position: fixed`, out of flow): without
     `flex-direction: column`, `.shell`'s default row would set them
     side by side instead of stacking the bar above the page. */
  /* `align-items: flex-start` (above) suits the wide row layout,
     where it keeps the sidebar's top edge level with the content's
     instead of stretching every column to the tallest one's height.
     In this column layout it does the opposite of what is wanted: it
     stops `.shell-topbar` and `.shell-body` stretching to the full
     width, so each one shrinks to its own content's width instead
     (a wide table inside showed through as a page that scrolled
     sideways). `stretch` is the property's own default; naming it
     here undoes the row rule for this breakpoint only. */
  .shell { flex-direction: column; align-items: stretch; padding: 0; gap: 0; }
  .shell-topbar { display: flex; }
  .shell-nav {
    position: fixed;
    /* Starts below the 56px top bar, not under it: the top bar keeps
       a higher z-index than the sheet so it stays reachable, but a
       sheet that started at the viewport's own top hid its own brand
       block and Close button behind that bar (hostile review). */
    top: calc(56px + var(--space-3));
    left: var(--space-3);
    bottom: var(--space-3);
    max-height: calc(100vh - 56px - 2 * var(--space-3));
    width: min(248px, calc(100vw - 2 * var(--space-3)));
    z-index: 40;
    transform: translateX(-110%);
    visibility: hidden;
    box-shadow: var(--shadow-overlay);
    transition: transform var(--motion-standard) ease-out, visibility 0s linear var(--motion-standard);
  }
  .shell-nav.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--motion-standard) ease-out, visibility 0s linear 0s;
  }
  .shell-nav-close { display: inline-flex; }
  /* The close button sits first in the document (the way out, for a
     keyboard user), but still reads left-to-right as brand, then
     close: `row-reverse` paints DOM-first last, without touching tab
     order, which follows the document, not paint order. */
  .shell-nav-header-row { flex-direction: row-reverse; }
  .shell-nav-item { min-height: 44px; }
  .shell:has(.shell-nav.is-open) .shell-scrim { display: block; }
}

/* Column 2 stacks above the content below 1050px, as a row of links
   instead of a column (section "The context column", medium and
   compact screens). Its own gutter matches `main.content`'s, at the
   same 759px cutoff the standard uses for the page gutter. */
.shell-side {
  width: auto;
  position: static;
  padding: 0 var(--space-6);
}
@media (max-width: 759px) {
  .shell-side { padding: 0 var(--space-4); }
}

/* --- Reset and base --------------------------------------------------- */

* { box-sizing: border-box; }

/* The `hidden` attribute must always win. Without this rule, a later
   `display` declaration at equal specificity can beat the browser's
   own `[hidden] { display: none }` default, and a "hidden" element
   still shows. `:not([hidden])` rules below still set the element's
   visible display value when the attribute is absent. */
[hidden] {
  display: none !important;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

html {
  /* A wide table inside `.table-wrap` scrolls in its own box (see
     Tables below). Without this, some browsers still let that width
     push the whole page into a horizontal scroll. */
  overflow-x: hidden;
}

body {
  margin: 0;
  overflow-x: hidden;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  color: var(--vf-text);
  background: var(--vf-bg);
}

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.01em; }
p { margin: 0; }

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

:focus-visible {
  outline: 2px solid var(--vf-accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* --- App shell ---------------------------------------------------------- */

/* The sign-in and landing pages, outside the shell (A4): a centred
   header (the mark, the name, and "Log in" except on the login page
   itself) above whatever card the page itself renders. */
.auth-shell { display: flex; flex-direction: column; align-items: center; }
.auth-shell-header {
  width: 100%;
  max-width: 1180px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: var(--space-5) var(--space-4) 0;
}
.auth-shell-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--vf-text);
  font-size: 15px;
  font-weight: 650;
}
.auth-shell-brand img { border-radius: 8px; }
.auth-shell-login {
  position: absolute;
  right: var(--space-4);
  color: var(--vf-text-muted);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
}
.auth-shell-login:hover { color: var(--vf-text); text-decoration: underline; }

.logout-form { margin: 0; flex-shrink: 0; }
/* "Log out" never wraps to two lines under pressure: the account
   link and the agency crumb give way first, both by ellipsis, since
   they can lose characters and stay meaningful; "Log out" cannot. */
.logout-form button { white-space: nowrap; }

/* The organisation switcher: a select next to the session controls,
   shown only for a user with more than one active organisation. */
.org-switch {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.org-switch select {
  min-height: 32px;
  padding: var(--space-1) var(--space-2);
  font-size: 13px;
  max-width: 180px;
}

.content {
  width: 100%;
  padding: var(--space-6);
  max-width: none;
  margin: 0;
  /* Inside `.shell-body` this is a flex item: `width: 100%` gives it
     a definite cross size, so a wide descendant (a table, say) cannot
     push it past its container instead of scrolling inside its own
     `.table-wrap` — an auto margin alone does not force that, and
     silently falls back to shrink-to-fit sizing instead. It still
     caps at 1180px and the auto margins still centre it in whatever
     is left over. Outside the shell (the auth pages) these
     properties do nothing beyond ordinary block centring, since a
     plain block is not a flex item. */
  flex: 1 1 1180px;
  min-width: 0;
}

/* A page with a column 2 (Setup, Billing) does not also centre its
   content: centring only reads as "off" once a sidebar sits beside
   it. `.content-has-side` cancels the auto margin so the content's
   left edge lines up with column 2's. */
.content-has-side { margin-left: 0; margin-right: 0; }

/* --- Page opening pattern ------------------------------------------------ */

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  margin-bottom: var(--space-6);
}

.page-header-text h1 {
  font-size: 26px;
}

.page-header-text .page-purpose {
  margin-top: var(--space-2);
  color: var(--vf-text-muted);
  font-size: 14px;
  max-width: 60ch;
}

.page-header .btn-primary { flex-shrink: 0; }

/* `.page-head`, `.card`, and `.card-title` are the shell's own page
   opening and card (spec 27, Part A, A3; section 5.2 and 5.5 of the
   standard). `.page-header` above stays for a page outside this
   spec's scope; a page this spec (or spec 27 Part B) touches uses
   these instead. */
.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  margin-bottom: var(--space-6);
}
.page-head-text h1 { font-size: 24px; }
.page-head-text .page-purpose {
  margin-top: var(--space-2);
  color: var(--vf-text-muted);
  font-size: 15px;
  max-width: 60ch;
}
/* The change page's status sentence (hostile review, spec 27 Part B)
   carries two dates and two names in one sentence; the 60ch cap above
   wrapped it at about 480px while the page head itself was much
   wider. This sentence uses the head's own full width instead. */
.page-head-text .rules-change-status { max-width: none; }
/* A date inside a sentence never splits across two lines. */
.nowrap-date { white-space: nowrap; }
.page-head .btn-primary { flex-shrink: 0; }
/* `.page-head-actions` wraps the page-head's action area (spec 27,
   Part B): one link, a form, or a link plus a secondary link. It
   keeps the same shrink and compact-width behaviour as a lone
   `.btn-primary` gave before, whatever markup sits inside it. */
.page-head-actions { display: flex; align-items: center; gap: var(--space-4); flex-shrink: 0; }
@media (max-width: 759px) {
  .page-head { flex-direction: column; align-items: stretch; }
  .page-head .btn-primary { align-self: flex-start; }
  .page-head-actions { align-self: flex-start; }
}

.card {
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
  padding: var(--space-5);
}
.card + .card { margin-top: var(--space-5); }
.card > * + * { margin-top: var(--space-4); }
.card-title {
  margin: 0 0 var(--space-4);
  font-size: 17px;
  font-weight: 600;
}

section + section,
.page-section + .page-section {
  margin-top: var(--space-7);
  padding-top: var(--space-6);
  border-top: 1px solid var(--vf-border);
}

.section-title {
  font-size: 18px;
  margin-bottom: var(--space-4);
}

/* --- Buttons -------------------------------------------------------------- */

/* `.btn-primary`, `.btn-secondary`, and `.btn-destructive` style both
   a <button> and an <a>, so a button-styled link looks and behaves the
   same as a real button: no underline, same padding, radius, hover,
   focus, active. */
button, .btn, .btn-primary, .btn-secondary, .btn-destructive {
  display: inline-block;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background-color var(--motion-fast) ease-out, border-color var(--motion-fast) ease-out, transform 100ms ease-out;
}

button:disabled { cursor: not-allowed; opacity: 0.55; }

.btn-primary {
  background: var(--vf-accent);
  color: var(--vf-on-accent);
  border: 1px solid var(--vf-accent);
  padding: var(--space-3) var(--space-4);
}
.btn-primary:hover:not(:disabled) { background: var(--vf-accent-hover); border-color: var(--vf-accent-hover); }
.btn-primary:active:not(:disabled) { transform: translateY(1px); }

.btn-secondary, .btn-destructive {
  background: var(--vf-surface);
  color: var(--vf-text);
  border: 1px solid var(--vf-border-strong);
  padding: var(--space-3) var(--space-4);
}
.btn-secondary:hover:not(:disabled) { background: var(--vf-surface-subtle); }

/* Compact size for actions inside table rows, so a row does not
   grow noticeably taller when its links become buttons. It changes
   size only, so it composes with the button variants above. */
.btn-compact {
  padding: var(--space-2) var(--space-3);
  font-size: 13px;
}

/* A destructive action keeps the secondary shape and takes the danger
   colour, so red still means delete only. The hover red is the one
   the danger links already turn on hover. */
.btn-destructive { color: var(--vf-danger); border-color: var(--vf-danger); }
.btn-destructive:hover:not(:disabled) { background: var(--vf-danger-bg); color: var(--vf-danger-hover); }
.btn-destructive:active:not(:disabled) { transform: translateY(1px); }

/* The quiet danger text style for confirm pages and similar one-off
   destructive controls. Not a button shape; the button-shaped danger
   variant is `.btn-destructive`. */
.btn-danger {
  background: transparent;
  color: var(--vf-danger);
  border: none;
  padding: 0;
  font-size: 13px;
  font-weight: 500;
  text-decoration: underline;
}
.btn-danger:hover:not(:disabled) { color: var(--vf-danger-hover); }

.link-button {
  background: none;
  border: none;
  padding: 0;
  color: var(--vf-accent);
  font-size: 13px;
  font-weight: 500;
  text-decoration: underline;
}
.link-button:hover { color: var(--vf-accent-hover); }

.cancel-link {
  align-self: center;
  color: var(--vf-text-muted);
  font-size: 14px;
  text-decoration: none;
}
.cancel-link:hover { color: var(--vf-text); }

/* --- Forms ----------------------------------------------------------------- */

/* Sentence case, styled as a normal field label (hostile review, spec
   27 Part B): ALL CAPS is for small metadata and table headings only
   (standard 4.2), and a fieldset legend is neither. */
.form-section legend,
.form-legend {
  font-size: 13px;
  font-weight: 600;
  color: var(--vf-text);
  padding: 0;
  margin-bottom: var(--space-3);
}

fieldset.form-section {
  border: none;
  padding: 0;
  margin: 0 0 var(--space-5) 0;
}

/* One radio option per line, the button and its text on one
   baseline, the whole line a click target (spec 19 review, V3). */
.radio-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.radio-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
}
.radio-option input[type="radio"] {
  /* The general `input` rule below sets a padded, min-height 40px box,
     which pushes the round radio glyph to the top left of that box
     (spec 19 review, V3). A radio needs the small, square box a
     checkbox already gets. */
  flex-shrink: 0;
  margin: 0;
  min-height: auto;
  width: 18px;
  height: 18px;
}

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

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 420px;
}

.field label,
.field-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--vf-text);
}

.field .optional {
  font-weight: 400;
  color: var(--vf-text-muted);
}

.field-hint {
  font-size: 12px;
  color: var(--vf-text-muted);
}

.field-error {
  font-size: 12px;
  color: var(--vf-danger);
}

.checkbox-field {
  flex-direction: row;
  align-items: center;
  gap: var(--space-2);
  max-width: none;
}
.checkbox-field label { font-weight: 500; }

/* `--vf-border-control` gives every input, checkbox, and radio a
   resting border at 3:1 contrast against its surface (UX standard
   v1.1, section 4.1); `--vf-border-strong` stays a hairline/separator
   token only. */
input, select, textarea {
  font: inherit;
  font-size: 14px;
  color: var(--vf-text);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--vf-border-control);
  border-radius: var(--radius-sm);
  background: var(--vf-surface);
  min-height: 40px;
}

input[type="checkbox"] { min-height: auto; width: 18px; height: 18px; }

textarea { min-height: 80px; resize: vertical; }

input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color: var(--vf-accent);
}

.form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-2);
}

.supporting-text {
  color: var(--vf-text-muted);
  font-size: 13px;
}

.import-hint { margin-bottom: var(--space-5); }

/* The summary on a delete confirmation page: the facts and the
   warning, in one column with one gap, above the confirm form. */
.confirm-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.confirm-summary .banner { margin-bottom: 0; }

/* --- Feedback --------------------------------------------------------------- */

.banner {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: 14px;
  margin-bottom: var(--space-5);
}

.banner-error {
  background: var(--vf-danger-bg);
  color: var(--vf-danger);
}

.banner-success {
  background: var(--vf-success-bg);
  color: var(--vf-success);
}

.banner-warning {
  background: var(--vf-warning-bg);
  color: var(--vf-warning);
}

.banner a { color: inherit; text-decoration: underline; }

.temp-password-banner p { margin-bottom: var(--space-2); }
.temp-password-banner p:last-child { margin-bottom: 0; }

/* The toast: quiet, temporary feedback after a successful action
   (standard 6.9). It sits above the page, so it carries the overlay
   shadow. An error never uses a toast; an error stays in a banner. */
.toast {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  max-width: 420px;
  padding: var(--space-3) var(--space-4);
  background: var(--vf-surface);
  color: var(--vf-text);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-overlay);
  font-size: 14px;
  animation: toast-in var(--motion-overlay) ease-out;
  transition: opacity var(--motion-standard) ease-out, transform var(--motion-standard) ease-out;
}
.toast.is-leaving { opacity: 0; transform: translateY(var(--space-2)); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(var(--space-2)); }
  to { opacity: 1; transform: none; }
}

.toast-dismiss {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: var(--space-2) 0;
  color: var(--vf-text-muted);
  font-size: 13px;
  font-weight: 500;
  text-decoration: underline;
}
.toast-dismiss:hover { color: var(--vf-text); }

@media (max-width: 759px) {
  .toast { left: var(--space-4); right: var(--space-4); bottom: var(--space-4); max-width: none; justify-content: space-between; }
  .toast-dismiss { min-height: 44px; }
}

.temp-password-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-2) 0;
}
.temp-password-value {
  font-family: "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: 16px;
  font-weight: 650;
  padding: var(--space-2) var(--space-3);
  background: var(--vf-surface);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-sm);
  color: var(--vf-text);
}

.inline-form { margin: 0; }
.inline-form select { min-height: 32px; padding: var(--space-1) var(--space-2); font-size: 13px; }

/* --- Tables --------------------------------------------------------------- */

.table-wrap { overflow-x: auto; position: relative; }
/* position: relative makes .table-wrap the containing block for the
   absolutely positioned .sr-only column headers inside wide tables.
   Without it they escape the scroll container and widen the whole
   document on small screens. */

table {
  border-collapse: collapse;
  width: 100%;
  min-width: 560px;
}

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

th, td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: 1px solid var(--vf-border);
  font-size: 14px;
}

th {
  font-size: 12px;
  font-weight: 600;
  color: var(--vf-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

tbody tr {
  transition: background-color var(--motion-fast) ease-out;
}
tbody tr:hover { background: var(--vf-surface-subtle); }

td.num, th.num { text-align: right; }

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

.row-actions { display: flex; gap: var(--space-3); white-space: nowrap; }
.row-actions form { margin: 0; }
/* A row of actions that follows a card, with no card of its own around
   it (spec 27, Part B: the change page's Edit/Discard row, two
   buttons, is not "a meaningful group" that needs a border). */
.card + .row-actions { margin-top: var(--space-5); }

/* SU-04: on the Setup list screens the row actions read as one tidy
   row of buttons at the table's right edge: one gap, one order per
   row, and the same right edge in every row. On a narrow screen (a
   phone) the row wraps to a second line instead of overflowing, and
   no button ever breaks in the middle. At a desktop width the row
   stays on one line, so rows do not grow taller. The scope was the
   Setup layout's own wrapper div; spec 27 removed that div (the
   shell places column 2 now), so `.content-has-side` (set on
   `main.content` whenever a page fills column 2 — the Setup and
   Billing pages) carries the same scope: other screens' row actions
   keep their look. */
.content-has-side .row-actions {
  flex-wrap: wrap;
  white-space: normal;
  justify-content: flex-end;
}
.content-has-side .row-actions a,
.content-has-side .row-actions button { white-space: nowrap; }
.content-has-side a.btn-secondary,
.content-has-side a.btn-destructive { white-space: nowrap; }

@media (min-width: 900px) {
  .content-has-side .row-actions { flex-wrap: nowrap; }
}

/* --- Empty state ------------------------------------------------------------ */

.empty-state {
  padding: var(--space-7) var(--space-5);
  text-align: center;
  border: 1px dashed var(--vf-border-strong);
  border-radius: var(--radius-md);
  color: var(--vf-text-muted);
}
.empty-state h3 { color: var(--vf-text); margin-bottom: var(--space-2); font-size: 16px; }
.empty-state p { max-width: 44ch; margin: 0 auto; font-size: 14px; }
.empty-state .btn-primary { margin-top: var(--space-4); }

/* --- Overview stats --------------------------------------------------------- */

.overview-status {
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-6);
  font-size: 15px;
}
.overview-status.is-complete {
  background: var(--vf-success-bg);
  color: var(--vf-success);
}
.overview-status.has-gaps {
  background: var(--vf-warning-bg);
  color: var(--vf-warning);
}

.kpi-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-6);
}

.kpi {
  min-width: 140px;
  display: block;
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: color var(--motion-fast) ease-out;
}
.kpi:hover .kpi-value { color: var(--vf-accent); }

.kpi-value {
  display: block;
  font-size: 32px;
  font-weight: 650;
  line-height: 1.1;
}

.kpi-label {
  display: block;
  margin-top: var(--space-1);
  font-size: 13px;
  color: var(--vf-text-muted);
}

.gap-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.gap-list li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--vf-border);
  font-size: 14px;
}

.gap-list li:last-child { border-bottom: none; }

.gap-count { font-weight: 600; }

/* --- Onboarding checklist ----------------------------------------------- */

.onboarding-embed {
  padding: var(--space-5);
  margin-bottom: var(--space-6);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
  background: var(--vf-surface);
}

.onboarding-embed-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}
.onboarding-embed-header .section-title { margin-bottom: 0; }

.onboarding-resume-link { margin-bottom: var(--space-6); }

.checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.checklist-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--vf-border);
}
.checklist-item:last-child { border-bottom: none; }

.checklist-status {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--vf-surface-subtle);
  border: 1px solid var(--vf-border-strong);
  font-size: 13px;
  font-weight: 650;
  color: var(--vf-text-muted);
}
.checklist-item.is-done .checklist-status {
  background: var(--vf-success-bg);
  border-color: var(--vf-success);
  color: var(--vf-success);
}

.checklist-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.checklist-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--vf-text);
  text-decoration: none;
}
.checklist-label:hover { color: var(--vf-accent); }
.checklist-item.is-done .checklist-label { color: var(--vf-text-muted); font-weight: 500; }

.checklist-detail {
  font-size: 12px;
  color: var(--vf-text-muted);
}

.checklist-state {
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--vf-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.checklist-item.is-done .checklist-state { color: var(--vf-success); }

.checklist-skip { margin-top: var(--space-5); }

/* A guided step can carry one or two links, not only one (spec 20,
   Part C: step 2, "Units, owners, and meters", needs both "Add a
   unit" and "Import from a CSV file"). */
.checklist-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-1);
}
.checklist-links a {
  font-size: 13px;
  font-weight: 600;
  color: var(--vf-accent);
  text-decoration: none;
}
.checklist-links a:hover { text-decoration: underline; }

/* --- Setup and Billing side list (column 2 of the shell) ------------------- */
/* `.setup-layout`/`.setup-layout-main` (spec 20, Part C) are gone:
   the shell's `.shell-side`/`.shell-body` place column 2 now (spec
   27, Part A, A2). `.setup-side` and `.setup-side-link` stay as the
   link styling inside it. */

.setup-side {
  display: flex;
  flex-direction: column;
  /* The item style of the sidebar panel (`.shell-nav-group`, spec 27,
     Part A): a small gap between items, since each item's own
     padding already sets it apart from the next. */
  gap: 2px;
}

.setup-side-link {
  /* The VF source's plain link (14px, weight 400), with the panel's
     own item padding, so column 2 reads as one item style with the
     sidebar, not a heavier, larger one of its own (hostile review). */
  padding: var(--space-2) var(--space-3);
  color: var(--vf-text-muted);
  text-decoration: none;
  font-size: 14px;
  font-weight: 400;
}
.setup-side-link:hover { color: var(--vf-text); }
.setup-side-link[aria-current="page"] { color: var(--vf-accent); font-weight: 500; }

.setup-crumb {
  margin: 0 0 var(--space-4);
  font-size: 13px;
}
.setup-crumb a { color: var(--vf-text-muted); text-decoration: none; }
.setup-crumb a:hover { color: var(--vf-text); text-decoration: underline; }

.setup-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.setup-group {
  margin-top: var(--space-7);
  padding-top: var(--space-6);
  border-top: 1px solid var(--vf-border);
  scroll-margin-top: var(--space-6);
}
.setup-group:first-of-type { margin-top: var(--space-6); padding-top: 0; border-top: none; }

/* One set of column widths for every group table, so "Block A",
   "Block B", and "No group" line up with each other at any width
   (hostile review R12). SU-02 widened the action column to 26%, so
   the two compact buttons sit side by side at a desktop width; the
   points came off the three widest text columns. */
.setup-rows-table {
  table-layout: fixed;
}
.setup-rows-table th:nth-child(1), .setup-rows-table td:nth-child(1) { width: 9%; }
.setup-rows-table th:nth-child(2), .setup-rows-table td:nth-child(2) { width: 21%; }
.setup-rows-table th:nth-child(3), .setup-rows-table td:nth-child(3) { width: 16%; }
.setup-rows-table th:nth-child(4), .setup-rows-table td:nth-child(4) { width: 8%; }
.setup-rows-table th:nth-child(5), .setup-rows-table td:nth-child(5) { width: 10%; }
.setup-rows-table th:nth-child(6), .setup-rows-table td:nth-child(6) { width: 10%; }
.setup-rows-table th:nth-child(7), .setup-rows-table td:nth-child(7) { width: 26%; }

/* A row carries two compact buttons, "Edit unit" and "Edit meter". A
   fixed column cuts content that is too wide for it, so the buttons
   wrap to the next line in a narrow window instead of being cut. A
   button never breaks in the middle — the same rule covers the
   "Add owner" button in the owner column. The row-actions wrap and
   the button nowrap now come from the Setup-layout scope above. */
.setup-rows-table a.btn-secondary { white-space: nowrap; }

/* Below 1050px `.shell-side` stacks column 2 above the content as a
   row (see the shell rules at the top of this file), and `.setup-side`
   fills that row: a wrapping row at medium width, a row that scrolls
   sideways once it reaches the compact width (hostile review R13's
   reasoning still holds: a row, not an empty 200px column). */
@media (min-width: 0px) {
  .setup-side {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    /* Tighter between wrapped rows than between columns, so the row
       of links reads as one calm group, not two uneven strips. */
    row-gap: var(--space-2);
    column-gap: var(--space-4);
    padding-bottom: var(--space-4);
    margin-bottom: var(--space-2);
    border-bottom: 1px solid var(--vf-border);
  }
}
@media (max-width: 759px) {
  .setup-side {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .setup-side-link { flex-shrink: 0; white-space: nowrap; }
}

/* --- Responsive: the Setup page's tables (<= 899px) ------------------------
   Below 900px the page stacks in one column, so a group table has
   about 600px. Digits, Route order, and Status no longer fit beside
   Unit, Owner, Meter serial, and the two edit links. They hide, so
   the table needs no sideways scroll and the links stay on one line
   (R12). The Meters page still shows all three. */
@media (max-width: 899px) {
  .setup-rows-table { min-width: 0; }
  .setup-rows-table th:nth-child(4), .setup-rows-table td:nth-child(4),
  .setup-rows-table th:nth-child(5), .setup-rows-table td:nth-child(5),
  .setup-rows-table th:nth-child(6), .setup-rows-table td:nth-child(6) {
    display: none;
  }
  .setup-rows-table th:nth-child(1), .setup-rows-table td:nth-child(1) { width: 13%; }
  .setup-rows-table th:nth-child(2), .setup-rows-table td:nth-child(2) { width: 29%; }
  .setup-rows-table th:nth-child(3), .setup-rows-table td:nth-child(3) { width: 26%; }
  .setup-rows-table th:nth-child(7), .setup-rows-table td:nth-child(7) { width: 32%; }
}

/* --- The agency crumb ------------------------------------------------------
   `_agency_crumb.html` renders the sidebar's context block for an
   agency member inside a complex (never on an agency page: hostile
   review R7 to R10, carried into the shell by spec 27). It keeps its
   old link classes (`agency-crumb-all`, `agency-crumb-complex`)
   beside the `shell-nav-context-*` ones that now style it (see the
   shell rules at the top of this file), so anything that still
   selects the old classes keeps working. The old inline, divided
   layout and its separate mobile twin are retired: the sidebar is
   the same element at every width now, so one block serves all of
   them. */

/* --- Login ------------------------------------------------------------------- */

.login-page {
  max-width: 360px;
  margin: var(--space-7) auto 0;
  padding: var(--space-6) var(--space-5);
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
}

.login-page h1 { font-size: 24px; margin-bottom: var(--space-6); text-align: center; }

.login-form { display: flex; flex-direction: column; gap: var(--space-4); }
.login-form .btn-primary { width: 100%; }

.login-install-link {
  text-align: center;
  font-size: 13px;
  color: var(--vf-text-muted);
  margin-top: var(--space-5);
}

/* --- Landing --------------------------------------------------------------- */

.landing-page {
  /* Wide enough that beat 2 of the headline fits one line from about
     900px up (spec 19 review, V2). The purpose line and the step
     strip keep their own, narrower measure below. */
  max-width: 820px;
  margin: var(--space-7) auto 0;
  padding: var(--space-7) var(--space-5);
  text-align: center;
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
}

/* The headline loops through two beats with `opacity` only. Both
   beats are always in the document and always hold their own line,
   so the loop never shifts the layout (UX standard section 7). */
.landing-headline {
  font-size: 40px;
  line-height: 1.25;
  margin-bottom: var(--space-6);
}

.landing-beat { display: block; }
.landing-beat-1 { animation: landing-beat-1 9s ease-in-out infinite; }
.landing-beat-2 { animation: landing-beat-2 9s ease-in-out infinite; }

/* One 9s cycle: beat 1 fades in over 0.6s, beat 2 starts fading in at
   1.4s over 0.6s, both hold, then both fade out over the last 0.8s. */
@keyframes landing-beat-1 {
  0% { opacity: 0; }
  6.7% { opacity: 1; }
  91.1% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes landing-beat-2 {
  0%, 15.6% { opacity: 0; }
  22.2% { opacity: 1; }
  91.1% { opacity: 1; }
  100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .landing-beat { opacity: 1; animation: none; }
}

.landing-purpose {
  color: var(--vf-text-muted);
  font-size: 16px;
  line-height: 1.5;
  max-width: 46ch;
  margin: 0 auto var(--space-6);
}
.landing-actions {
  display: flex;
  justify-content: center;
  gap: var(--space-4);
}
.landing-page > .btn-primary { margin-bottom: var(--space-7); }

.landing-steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-5);
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: left;
}
.landing-step-title { font-size: 16px; margin-bottom: var(--space-2); }
.landing-step p {
  color: var(--vf-text-muted);
  font-size: 14px;
  line-height: 1.5;
}

@media (max-width: 759px) {
  .landing-headline { font-size: 30px; }
  /* At this width beat 2 wraps to more than one line by design; balance
     its own wrap so it does not leave one short word alone (V2). */
  .landing-beat-2 { text-wrap: balance; }
  .landing-steps { grid-template-columns: 1fr; }
}

/* --- Sign-up ----------------------------------------------------------------- */

.signup-page {
  max-width: 420px;
  margin: var(--space-6) auto 0;
  padding: var(--space-6) var(--space-5);
  background: var(--vf-surface);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
}
.signup-page h1 { font-size: 24px; margin-bottom: var(--space-2); text-align: center; }
.signup-page .page-purpose { text-align: center; margin-bottom: var(--space-6); }
.signup-form { display: flex; flex-direction: column; gap: var(--space-4); }
.signup-form .field { max-width: none; }
.signup-form .btn-primary { width: 100%; margin-top: var(--space-2); }
.signup-login-link { text-align: center; font-size: 13px; color: var(--vf-text-muted); margin-top: var(--space-5); }

/* --- Labels ------------------------------------------------------------------ */

.label-picker-actions {
  display: flex;
  gap: var(--space-3);
  margin: var(--space-4) 0;
}

.checkbox-list {
  list-style: none;
  margin: 0 0 var(--space-5) 0;
  padding: 0;
  columns: 3;
  column-gap: var(--space-5);
}

.checkbox-list li {
  break-inside: avoid;
  padding: var(--space-1) 0;
  font-size: 14px;
}

.checkbox-list label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 400;
}

.label-sheet-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

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

.label {
  border: 1px dashed var(--vf-border-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  text-align: center;
}

.label-unit { font-size: 22px; font-weight: 650; margin-top: var(--space-2); }
.label-serial, .label-site { font-size: 12px; color: var(--vf-text-muted); }

@media print {
  .no-print { display: none !important; }
  .content { max-width: none; padding: 0; margin: 0; }
  /* A4 at 100 percent: three labels per row, each QR at least 30 mm
     wide so a phone scans it from 15 to 20 cm. A flex line, not a
     grid, so Chromium fragments the sheet at a label edge and never
     across the middle of a label. */
  @page { margin: 10mm; }
  .label-grid { display: flex; flex-wrap: wrap; gap: 0; }
  .label {
    border: 1px solid black;
    border-radius: 0;
    width: 56mm;
    break-inside: avoid;
    page-break-inside: avoid; /* Safari */
  }
  .label-qr svg { width: 30mm; height: 30mm; display: block; margin: 0 auto; }
  body { background: white; }
}

/* --- Cycle and reading review (spec 03) --------------------------------------- */

.breadcrumb {
  margin-bottom: var(--space-4);
  font-size: 13px;
}
.breadcrumb a { color: var(--vf-text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--vf-text); }

.cycle-review-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}
.cycle-review-actions form { margin: 0; }

.section-summary {
  cursor: pointer;
  list-style: revert;
}
.section-summary::marker { color: var(--vf-text-muted); }

.reading-photo {
  display: block;
  width: 100%;
  max-width: 600px;
  border-radius: var(--radius-md);
  background: var(--vf-surface-subtle);
}

.reading-review-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.reading-review-actions form { margin: 0; }

.reveal-panel {
  max-width: 420px;
  padding-top: var(--space-4);
  border-top: 1px solid var(--vf-border);
}

/* The admin types corrected digits with a real keyboard, unlike the
   reader's on-screen keypad, so the caret must stay visible here. */
.admin-digit-boxes .digit-input { caret-color: var(--vf-text); }

/* `.reader-ai-label` centres itself for the narrow reader PWA column;
   the admin review page is left-aligned like the rest of its content.
   (A `p` qualifier outranks the later `.reader-ai-label` rule below,
   which shares its plain-class specificity but comes after it in the
   cascade.) */
p.admin-ai-label { text-align: left; }

/* --- Reader app (floor-user PWA, section 10.3) --------------------------------
   Phone-only: one column, large touch targets, few choices. Not the
   admin shell above: a management console and a floor PWA do not
   share density. */

.reader-body {
  min-height: 100vh;
  background: var(--vf-bg);
}

/* Block pull-to-refresh on the reader PWA. A downward swipe on the
   confirm or review step must not reload the page and lose the
   pending read; `resumePendingRead` in reader.js is the recovery
   path for a reload that still happens for another reason. Scoped to
   the reader layout only, through the `.reader-body` class that only
   `reader/_base.html` sets on `<body>`. */
body.reader-body {
  overscroll-behavior-y: none;
}

/* A separate rule, not joined to the one above by a comma: an older
   browser that does not know `:has()` drops a whole comma-joined
   selector list, which would silently lose the `body` rule too. */
html:has(> body.reader-body) {
  overscroll-behavior-y: none;
}

.reader-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 56px;
  padding: 0 var(--space-4);
  background: var(--vf-surface);
  border-bottom: 1px solid var(--vf-border);
}

.reader-brand {
  font-size: 16px;
  font-weight: 650;
  color: var(--vf-text);
  flex-shrink: 0;
}

.reader-session {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}

/* At a narrow phone width, the select shrinks first, so the brand
   and the log-out button always stay on one line. */
.reader-session .org-switch select {
  max-width: 96px;
}

.reader-main {
  display: flex;
  justify-content: center;
}

.reader-page {
  width: 100%;
  max-width: 480px;
  padding: var(--space-5) var(--space-4) calc(var(--space-7) + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.reader-eyebrow {
  font-size: 13px;
  color: var(--vf-text-muted);
}

.reader-back {
  color: var(--vf-text-muted);
  text-decoration: none;
  font-size: 13px;
}
.reader-back:hover { color: var(--vf-text); }

.reader-title { font-size: 24px; }

.reader-subtitle {
  color: var(--vf-text-muted);
  font-size: 14px;
}

/* Which meter the reader is on, shown above every step (meter,
   camera, confirm, review), so it never scrolls out of reach behind
   a hidden view. */
.reader-meter-header { margin-bottom: 0; }

/* Every reader step is a <section class="reader-view">, only one
   visible at a time via `hidden`. The admin `section + section` rule
   (style.css, "Page opening pattern") adds a top border and margin
   between adjacent <section> elements, and CSS sibling selectors
   match DOM order, not visibility: a hidden `<section>` still counts
   as the "previous section" for its visible neighbour. Without this
   reset, every step but the first shows a stray hairline and gap. */
.reader-view {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.reader-progress { font-size: 16px; }

.reader-hint {
  color: var(--vf-text-muted);
  text-align: center;
  font-size: 14px;
}

/* Marks the digit boxes below as the model's suggestion, not yet
   confirmed (section 13: AI output must be visually distinguishable,
   not by colour/tint alone). */
.reader-ai-label {
  color: var(--vf-accent);
  font-size: 13px;
  font-weight: 600;
  text-align: center;
}

.reader-previous,
.reader-value-line,
.reader-consumption {
  font-size: 16px;
}

.reader-value-line { font-weight: 650; }

/* :not([hidden]) keeps this authored `display` from beating the
   browser's own `[hidden] { display: none }` rule: an author style
   always wins over a user-agent style at equal specificity, so a
   plain `.reader-summary { display: flex }` would show the element
   even while its `hidden` attribute is set. */
.reader-summary:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.reader-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.reader-flags {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* One banner, one line per mid-roll drum, instead of one banner per
   drum (spec 13). */
.mid-roll-list {
  margin: 0;
  padding-left: 1.2em;
}
.mid-roll-list li + li {
  margin-top: var(--space-1);
}

.reader-spinner {
  color: var(--vf-text-muted);
  text-align: center;
}

/* A button-styled control that is easy to hit with a thumb. */
.btn-large {
  width: 100%;
  min-height: 56px;
  font-size: 16px;
  text-align: center;
}

/* -- Reader meter list -- */

.reader-meter-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.reader-meter-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 56px;
  padding: 0 var(--space-4);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--vf-text);
  font-size: 15px;
  transition: border-color var(--motion-fast) ease-out;
}
.reader-meter-row:hover { border-color: var(--vf-border-strong); }

.reader-meter-status { color: var(--vf-text-muted); }
.reader-meter-row.is-done .reader-meter-status { color: var(--vf-success); font-weight: 600; }

/* -- Offline outbox (spec 14) -- */

#offline-banner { margin: 0 0 var(--space-4); }

.outbox {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-md);
  background: var(--vf-surface);
}

.outbox-refused {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.outbox-refused li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--vf-warning-bg);
  color: var(--vf-warning);
  font-size: 13px;
}

/* -- Camera guide frame (meter photo and QR scan) -- */

.video-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: black;
}

#camera-video,
#scan-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A rectangle at 90% width, for the meter register. Its aspect ratio
   is set inline from the meter's drum count (digit_count + decimal
   count, floor 3:1), so it matches the register shape. The box-shadow
   dims every part of the video outside the frame. The verdict classes
   below colour the border with the live frame-fit hint; the text of
   #fit-hint carries the same meaning, so colour is never the only
   signal (UX standard, section 4). */
.guide-frame {
  position: absolute;
  top: 50%;
  left: 5%;
  width: 90%;
  aspect-ratio: 5 / 1;
  transform: translateY(-50%);
  border: 3px solid var(--vf-accent);
  border-radius: var(--radius-sm);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  transition: border-color var(--motion-fast) ease-out;
}
.guide-frame.is-good { border-color: var(--vf-success); }
.guide-frame.is-closer,
.guide-frame.is-blur { border-color: var(--vf-warning); }
.guide-frame.is-too-close { border-color: var(--vf-danger, var(--vf-warning)); }

/* A square guide, centred, for the QR label. */
.scan-guide {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 65%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  border: 3px solid var(--vf-accent);
  border-radius: var(--radius-md);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  pointer-events: none;
}

.capture-button {
  display: block;
  width: 72px;
  height: 72px;
  min-height: 0;
  margin: var(--space-4) auto 0;
  border-radius: 50%;
  border: 4px solid var(--vf-surface);
  outline: 1px solid var(--vf-border-strong);
  background: var(--vf-accent);
  cursor: pointer;
}
.capture-button:active { background: var(--vf-accent-hover); }

/* The hold-to-fire ring while auto-capture counts down. The hint text
   ("Good. Hold still, capturing…") carries the same meaning, so
   colour is never the only signal (UX standard, section 4). */
.capture-button.is-arming {
  outline: 4px solid var(--vf-success);
  outline-offset: 3px;
}

/* Live, advisory-only frame-fit hint. A fixed min-height keeps the
   layout from jumping as the text changes. */
.fit-hint {
  min-height: 20px;
  margin: var(--space-2) 0 0;
}

.camera-controls {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.camera-control {
  min-height: 44px;
}
.camera-control[aria-pressed="true"] {
  background: var(--vf-accent-subtle);
  border-color: var(--vf-accent);
  color: var(--vf-accent);
}

#photo-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}

.reader-crop-image:not([hidden]) {
  width: 100%;
  border-radius: var(--radius-md);
  display: block;
}

/* -- Digit boxes and keypad -- */

.digit-boxes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

.digit-input {
  width: 38px;
  height: 54px;
  min-height: 0;
  padding: 0;
  text-align: center;
  font-size: 24px;
  font-weight: 650;
  font-family: "SFMono-Regular", Menlo, Consolas, monospace;
  border: 1px solid var(--vf-border-control);
  border-radius: var(--radius-sm);
  background: var(--vf-surface);
  color: var(--vf-text);
  caret-color: transparent;
}

.digit-input.is-active {
  border-color: var(--vf-accent);
  box-shadow: 0 0 0 3px var(--vf-accent-subtle);
}

/* An AI-suggested digit, not yet confirmed by the reader (section 13). */
.digit-input.is-ai-suggested {
  background: var(--vf-accent-subtle);
  border-color: var(--vf-accent);
  color: var(--vf-accent-hover);
}

.digit-input.mid-roll {
  background: var(--vf-warning-bg);
  border-color: var(--vf-warning);
  color: var(--vf-warning);
}

/* The one-tap carry-chain choice: the reader picks the whole chain's
   reading at once, instead of typing each drum. Once tapped, the
   choice is the reader's decision, so the pressed button gets the
   accent treatment used for a selected control elsewhere (section 6.1). */
.mid-roll-choices {
  margin-top: var(--space-2);
}

.mid-roll-choice-row {
  display: flex;
  gap: var(--space-3);
}

.mid-roll-choice {
  flex: 1;
  min-height: 56px;
  font-size: 14px;
  font-weight: 600;
  background: var(--vf-surface);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-sm);
  color: var(--vf-text);
}
.mid-roll-choice:hover { background: var(--vf-surface-subtle); }
.mid-roll-choice[aria-pressed="true"] {
  background: var(--vf-accent-subtle);
  border-color: var(--vf-accent);
  color: var(--vf-accent);
}

.digit-separator {
  font-size: 24px;
  font-weight: 650;
  padding: 0 var(--space-1);
}

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

.keypad-key {
  min-height: 56px;
  font-size: 20px;
  font-weight: 600;
  background: var(--vf-surface);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-sm);
  color: var(--vf-text);
}
.keypad-key:hover:not(:disabled) { background: var(--vf-surface-subtle); }

.keypad-key-blank {
  background: transparent;
  border-color: transparent;
}

/* --- Responsive: compact (< 760px) ------------------------------------------- */

@media (max-width: 759px) {
  .content { padding: var(--space-4); }

  .page-header { flex-direction: column; align-items: stretch; }
  .page-header .btn-primary { align-self: flex-start; }

  .kpi-row { gap: var(--space-5); }

  .checkbox-list { columns: 1; }
  .label-grid { grid-template-columns: repeat(2, 1fr); }
  .label { width: auto; }

  .cycle-review-actions { flex-direction: column; align-items: stretch; width: 100%; }
  .cycle-review-actions form,
  .cycle-review-actions .btn-primary,
  .cycle-review-actions .btn-secondary { width: 100%; text-align: center; }

  .reading-review-actions { flex-direction: column; align-items: stretch; }
  .reading-review-actions form,
  .reading-review-actions .btn-primary,
  .reading-review-actions .btn-secondary { width: 100%; }

  .reveal-panel { max-width: none; }
}

/* --- New-owner fields (unit form) ------------------------------------------- */

.new-owner-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-sm);
  background: var(--vf-surface-subtle);
  max-width: 420px;
}

.new-owner-fields .field-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--vf-text);
}

.new-owner-fields[hidden] { display: none; }

/* --- Reader meter list room headings ---------------------------------------- */

.reader-room-heading {
  margin: var(--space-5) 0 var(--space-2);
  font-size: 14px;
  font-weight: 600;
  color: var(--vf-text-muted);
}

.reader-room-heading:first-of-type { margin-top: var(--space-4); }

/* --- Guided reader flow (spec 06 Part G) -------------------------------------- */

.next-meter {
  margin: var(--space-5) 0;
  padding: var(--space-5) var(--space-4);
  border: 1px solid var(--vf-border-strong);
  border-radius: var(--radius-md);
  background: var(--vf-surface);
  text-align: center;
}

.next-meter p { margin: 0 0 var(--space-2); }

.next-meter-room {
  font-size: 14px;
  font-weight: 600;
  color: var(--vf-text-muted);
}

.next-meter-unit {
  font-size: 48px;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--vf-text);
  line-height: 1.1;
}

.next-meter-serial {
  font-size: 14px;
  color: var(--vf-text-muted);
}

.next-meter-position {
  font-size: 14px;
  color: var(--vf-text);
}

.next-meter .btn-primary { margin-top: var(--space-4); }

.next-meter-all-read {
  font-size: 16px;
  font-weight: 600;
  color: var(--vf-success);
  margin: var(--space-5) 0;
}

.scan-expect { margin-bottom: var(--space-4); }

.scan-no-hint {
  margin-top: var(--space-3);
  font-size: 13px;
  color: var(--vf-warning);
}

.scan-fallback {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--vf-border);
}

.scan-fallback label {
  font-size: 14px;
  font-weight: 600;
  color: var(--vf-text);
  white-space: nowrap;
}

.scan-fallback input {
  flex: 1;
  min-width: 0;
  max-width: 200px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--vf-border-control);
  border-radius: var(--radius-sm);
  font-size: 16px;
  font-family: inherit;
  background: var(--vf-surface);
  color: var(--vf-text);
}

.scan-mismatch { margin-bottom: var(--space-5); }

/* --- Billing (spec 10, Part C) ----------------------------------------------- */

.plan-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-5);
}

.plan-card {
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-md);
  padding: var(--space-5);
}

/* A `.plan-card` inside a `.card` (the Eskom bill card, read on the
   cycle's charges page, spec 27, Part B) drops its own border and
   padding: it already sits inside a card, and a card inside a card is
   a hard-fail under section 17.1 of the standard. */
.card .plan-card { border: none; border-radius: 0; padding: 0; }

.plan-card.is-current {
  border-color: var(--vf-accent);
  background: var(--vf-accent-subtle);
}

.plan-card-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.plan-card-header h3 { font-size: 16px; font-weight: 650; }

.plan-badge {
  font-size: 12px;
  font-weight: 600;
  color: var(--vf-accent);
}

.plan-price {
  font-size: 20px;
  font-weight: 650;
  margin: var(--space-2) 0;
}

.plan-radio-group {
  display: flex;
  gap: var(--space-4);
  border: none;
  padding: 0;
  margin: var(--space-3) 0;
  font-size: 13px;
}

.plan-radio-group label { display: flex; align-items: center; gap: var(--space-1); }

.plan-choose-form .btn-primary { width: 100%; }

.invoice-parties {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-5);
}

.invoice-total-row td { border-top: 1px solid var(--vf-border-strong); }

/* The shell's chrome (`.shell-topbar`, `.shell-nav`, `.shell-side`,
   `.shell-scrim`) all carry `no-print`, so the `@media print` rule
   above already hides them; this page needs no rule of its own. */
@media print {
  .content { padding: 0; }
}

/* Spec 15 owner portal — /my pages. Open sections with hairline
   separators, no cards. */

.breadcrumb { margin: 0 0 var(--space-2); }
.breadcrumb a { font-size: 14px; }

.my-units { max-width: 640px; }

.my-unit { padding: var(--space-5) 0; }
.my-unit + .my-unit { border-top: 1px solid var(--vf-border); }

.my-unit-heading h2 { margin: 0 0 var(--space-1); font-size: 18px; font-weight: 600; }
.my-unit-heading .supporting-text { margin: 0 0 var(--space-3); }

.my-unit-line { margin: 0 0 var(--space-2); font-size: 15px; }
.my-unit-link { font-size: 14px; font-weight: 500; }

.my-chart { width: 100%; height: auto; display: block; }

/* Two layouts, one chart. The wide chart is designed on a 640-unit
   viewBox; below the app's compact breakpoint a phone would scale its
   12 px axis text down to about 7 px. The compact chart is laid out
   1:1 for a 390 px phone's 358 px content width (COMPACT_WIDTH in
   app/charts.py), so its text renders at 12 px, and it caps at its
   natural width instead of upscaling on larger screens. */
.my-chart-compact { display: none; }

@media (max-width: 759px) {
  .my-chart-wide { display: none; }
  .my-chart-compact { display: block; width: 358px; max-width: 100%; }
}

.my-chart-line {
  fill: none;
  stroke: var(--vf-accent);
  stroke-width: 2px;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.my-chart-marker {
  fill: var(--vf-accent);
  stroke: var(--vf-surface);
  stroke-width: 2px;
}

.my-chart-grid { stroke: var(--vf-border); stroke-width: 1px; }

.my-chart-text { font-size: 12px; fill: var(--vf-text-muted); }

/* Spec 15 reader — re-read status and the disclosed scan fallback. */

.reader-meter-row.is-reread .reader-meter-status {
  color: var(--vf-warning);
  font-weight: 600;
}

.scan-fallback-details summary {
  cursor: pointer;
  font-size: 13px;
  color: var(--vf-text-muted);
}

/* Spec 16 reader */
/* The queued list mirrors the refused list: one row per capture that
   still waits, with a Discard action when a run stopped on an error. */
.outbox-queued {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.outbox-queued[hidden] {
  display: none;
}

.outbox-queued li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Charges */
/* The empty state's two preset buttons sit below its paragraph, with
   the same spacing a primary button gets elsewhere in an empty state. */
.empty-state .landing-actions {
  margin-top: var(--space-4);
}

/* The scheme form's lines table: name, group, method, and rate each
   need the full width of their cell, unlike a standalone `.field`
   input, which caps at 420px for a one-column form. */
.charge-lines-table input,
.charge-lines-table select {
  width: 100%;
  min-width: 10ch;
}

.charge-lines-table td {
  vertical-align: top;
}

/* The change form's two-row line (hostile review, spec 27 Part B):
   row 1's four columns each get room for their longest real value —
   "Service charges line" (group), "Amount from the Eskom bill, shared
   equally" (method) — so none of them truncates; row 2 holds "In your
   own words" across the full table width, since GP types real
   sentences there. `table-layout: auto` (the default) then stretches
   every column beyond this floor to fill the card, so the table reads
   as generous on a wide screen and scrolls sideways as one block
   (row 1 and row 2 together) only once the card is narrower than
   these floors add up to. */
.charge-lines-table th.charge-line-name,
.charge-lines-table td.charge-line-name { min-width: 220px; }
.charge-lines-table th.charge-line-group,
.charge-lines-table td.charge-line-group { min-width: 170px; }
.charge-lines-table th.charge-line-method,
.charge-lines-table td.charge-line-method { min-width: 260px; }
.charge-lines-table th.charge-line-rate,
.charge-lines-table td.charge-line-rate { min-width: 120px; width: 120px; }

/* No hairline between a line's own two rows; the statement row's own
   hairline (kept, from the general `th, td` rule) separates one
   line's pair from the next. */
.charge-line-row td { border-bottom: none; }
.charge-line-statement-row td.charge-line-statement { padding-top: 0; }
.charge-line-statement .field-label { display: block; margin-bottom: var(--space-1); }
.charge-line-statement input { width: 100%; }

/* --- Install guide (spec 19) ------------------------------------------------- */

.install-page {
  max-width: 720px;
  margin: var(--space-8) auto 0;
  padding: 0 var(--space-4) var(--space-8);
}
.install-page h1 { font-size: 28px; margin-bottom: var(--space-2); }
.install-page .page-purpose {
  color: var(--vf-text-muted);
  font-size: 15px;
  max-width: 60ch;
  margin-bottom: var(--space-7);
}

.install-section + .install-section {
  margin-top: var(--space-7);
  padding-top: var(--space-6);
  border-top: 1px solid var(--vf-border);
}
.install-section h2 { font-size: 20px; margin-bottom: var(--space-4); }

.install-steps {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  margin: var(--space-4) 0 0;
  padding: 0;
  counter-reset: install-step;
}

/* A fixed-width text column keeps every step's image at the same left
   edge, whatever the length of that step's own text (spec 19 review,
   V1). The image keeps its own fixed width, so the three images form
   one aligned column. */
.install-step {
  counter-increment: install-step;
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
}
.install-step-text {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  flex: 0 0 320px;
  max-width: 320px;
}
.install-step p { margin: 0; }

/* A visible step number, built with a CSS counter (spec 19 review, M2). */
.install-step-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-top: 2px;
  border-radius: var(--radius-pill);
  background: var(--vf-accent-subtle);
  color: var(--vf-accent);
  font-size: 12px;
  font-weight: 600;
}
.install-step-badge::before {
  content: counter(install-step);
}

.install-step img {
  width: 100%;
  max-width: 280px;
  flex: 0 0 280px;
  height: auto;
  border: 1px solid var(--vf-border);
  border-radius: var(--radius-sm);
}

@media (max-width: 759px) {
  .install-step { flex-direction: column; }
  .install-step-text { flex: 1 1 auto; max-width: none; }
}

/* --- Site footer (spec 19) ---------------------------------------------------- */

/* Quiet by design: muted text, the small type size, a top border, and
   the page gutter. It sits after the page content, never pinned to
   the viewport, so it never covers anything (UX standard section 3.5). */
.site-footer {
  max-width: 1180px;
  margin: var(--space-7) auto 0;
  padding: var(--space-5) var(--space-6) var(--space-6);
  border-top: 1px solid var(--vf-border);
  color: var(--vf-text-muted);
  font-size: 13px;
  line-height: 1.5;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.site-footer a { color: var(--vf-text-muted); }
.site-footer a:hover { color: var(--vf-text); }

@media (max-width: 759px) {
  .site-footer { padding: var(--space-4); }
}

/* Charge rules */

/* The change form reuses the older `.charge-lines-table` inside
   `.form-grid` (a column flex container). A flex item, and a
   `<fieldset>` on its own, both refuse by default to shrink below
   their content's natural width, so the lines table's own scrollbar
   (`.table-wrap`) never got a chance to run at a narrow width, and
   the whole page scrolled sideways instead (UX standard 10.2: no
   page-level horizontal scroll). Scoped to this form only. */
.rules-change-form,
.rules-change-form .form-section,
.rules-change-form .table-wrap {
  min-width: 0;
}

/* The change form's cards space themselves with `.card + .card`
   (spec 27, Part B); the form's own flex `gap` would otherwise stack
   on top of that margin and space the cards twice. */
.rules-change-form.form-grid { gap: 0; }
.card + .form-actions { margin-top: var(--space-5); }

/* Inputs at 16px in the change form only (spec 27, Part B, B3; UX
   standard 6.2, v1.1), so a phone does not zoom in on focus. */
.rules-change-form input,
.rules-change-form select,
.rules-change-form textarea {
  font-size: 16px;
}

/* The failed-submit error summary (UX standard 6.2, v1.1): one list
   of the problems, each a link to its field, at the top of the form. */
.error-summary-title { margin: 0 0 var(--space-2); font-size: 16px; }
.error-summary-list { margin: 0; padding-left: var(--space-4); }
.error-summary-list li + li { margin-top: var(--space-1); }

/* The rules page: the rules in the main column, the assistant card in
   a sticky side column, at 1050px and above (spec 27, Part B, B1.6:
   the assistant follows "The geometry, from the source"'s columns
   breakpoint). Below that, one column, rules first, then the
   assistant card. */
.rules-layout {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  align-items: flex-start;
}
.rules-layout-main { flex: 1 1 auto; min-width: 0; width: 100%; }
.rules-layout-side { width: 100%; }

@media (min-width: 1050px) {
  .rules-layout { flex-direction: row; gap: var(--space-6); }
  .rules-layout-main { flex: 3 1 0; }
  .rules-layout-side {
    flex: 2 1 0;
    position: sticky;
    top: var(--space-5);
    align-self: flex-start;
    max-height: calc(100vh - var(--space-6));
    overflow-y: auto;
  }
}

.rules-empty-actions { flex-wrap: wrap; }

.rules-owner-line { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-4); }
.rules-owner-form { margin-top: var(--space-1); }
.rules-owner-form .inline-form { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }

/* One rule's plain-English statement, the calculation sentence under
   it in muted text (so a mismatch between the two is visible, per the
   spec), and the version/approval line under that. */
.rules-rule + .rules-rule { margin-top: var(--space-5); padding-top: var(--space-5); border-top: 1px solid var(--vf-border); }
.rules-statement { margin: 0; }
.rules-calculation { margin: var(--space-1) 0 0; color: var(--vf-text-muted); font-size: 13px; }
.rules-meta { margin: var(--space-2) 0 0; color: var(--vf-text-muted); font-size: 12px; }
.rules-future {
  margin: var(--space-2) 0 0;
  padding: var(--space-2) var(--space-3);
  background: var(--vf-accent-subtle);
  color: var(--vf-text);
  border-radius: var(--radius-sm);
  font-size: 13px;
}

.rules-pending-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.rules-pending-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--vf-border);
}
.rules-pending-item:last-child { border-bottom: none; }
/* A change that waits shows its rules in full (hotfix 2026-09-27): the
   button sits at the top, beside the summary, and the rules follow. */
.rules-pending-item { align-items: flex-start; }
.rules-pending-body { flex: 1; min-width: 0; }
.rules-rule-proposed { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--vf-border); }
.rules-pending-summary { margin: 0; font-weight: 600; }
.rules-waiting-text { margin: var(--space-1) 0 0; color: var(--vf-warning); font-size: 12px; font-weight: 600; }

.rules-item-view + .rules-item-view { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--vf-border); }
/* Sentence case (hostile review, spec 27 Part B): "Now:"/"After:" are
   not table headings or a sidebar group label, so ALL CAPS (standard
   4.2) does not apply. */
.rules-item-label { margin: 0; font-size: 12px; font-weight: 600; color: var(--vf-text-muted); }
.rules-item-view .rules-statement { margin-top: var(--space-1); }
.rules-item-view .rules-calculation { margin-bottom: var(--space-3); }

.rules-example-marker { color: var(--vf-text-muted); font-size: 12px; font-weight: 500; }
.rules-all-units { margin-top: var(--space-4); }
.rules-all-units summary { cursor: pointer; color: var(--vf-accent); font-weight: 600; font-size: 13px; }

/* A check row: an icon plus its own text always states pass or fail,
   so no check relies on colour alone (UX standard 6.7, 17). */
.rules-checks-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.rules-check { display: flex; align-items: center; gap: var(--space-3); font-size: 14px; }
.rules-check-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 650;
}
.rules-check.is-passed .rules-check-icon { background: var(--vf-success-bg); color: var(--vf-success); }
.rules-check.is-failed .rules-check-icon { background: var(--vf-danger-bg); color: var(--vf-danger); }
.rules-check.is-failed { color: var(--vf-danger); }

/* Charges by cycle (spec 23 hostile review, C1 and C2) */

/* `.table-stack` turns a table into one stacked block per row below
   759px: "Charges by cycle" then shows its one button in full view
   under each cycle, with no sideways scroll to reach it (C1); the
   Calculate table then shows every amount input at its full width
   instead of clipped to a sliver (C2). Each `td` prints its own
   column header as a small label, from `data-label`, since the real
   `<th>` row is hidden at this width. */
@media (max-width: 759px) {
  .table-stack { min-width: 0; }
  .table-stack thead { display: none; }
  .table-stack, .table-stack tbody, .table-stack tr, .table-stack td {
    display: block;
    width: 100%;
  }
  .table-stack tbody tr {
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--vf-border);
  }
  .table-stack tbody tr:last-child { border-bottom: none; }
  .table-stack td { border-bottom: none; padding: var(--space-1) 0; }
  .table-stack td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: var(--vf-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 2px;
  }
  .table-stack td.row-actions { margin-top: var(--space-2); }
  .table-stack input[type="text"] { width: 100%; }
}

/* The rules table (hotfix 2026-09-27): one row per rule, the trustees'
   words beside what the app does. Replaces the list of paragraphs. */
.rules-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.rules-table th, .rules-table td { padding: var(--space-2) var(--space-3); vertical-align: top; border-bottom: 1px solid var(--vf-border); text-align: left; }
.rules-table thead th { font-size: 12px; color: var(--vf-text-muted); }
.rules-table tbody th { font-weight: 600; font-size: 14px; color: var(--vf-text); text-transform: none; letter-spacing: 0; min-width: 180px; }
.rules-table td[data-label="In the trustees' words"] { min-width: 280px; width: 55%; }
.rules-table td[data-label="What the app does"] { min-width: 200px; }
.rules-table td[data-label="Since"] { white-space: nowrap; }
.rules-table { min-width: 0; }
.rules-table tbody tr:last-child th, .rules-table tbody tr:last-child td { border-bottom: none; }
.rules-short { color: var(--vf-text-muted); }
.rules-settings-line { margin: var(--space-2) 0 var(--space-3); font-size: 14px; color: var(--vf-text); list-style: none; }
.rules-pending-table { list-style: none; padding: 0; }
.rules-pending-list { padding-left: 0; }
@media (min-width: 1050px) {
  .rules-layout-side { flex: 0 0 300px; }
}
.rule-id { display: block; font-family: "SFMono-Regular", Menlo, Consolas, monospace; font-size: 12px; font-weight: 400; color: var(--vf-text-muted); overflow-wrap: anywhere; }
.rule-no { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--vf-text-muted); margin-right: var(--space-2); }
