/* ===========================================================================
   Ledger design system (SPEC-01-ledger-design-system.md)
   ---------------------------------------------------------------------------
   EB Garamond is self-hosted, not loaded from Google Fonts. The Google API
   strips `smcp`, `c2sc` and `lnum` from the files it serves (verified with
   fonttools against the woff2 the API actually returns), so small caps
   rendered through it are synthesized — thin and squat. These three files
   are instanced from Google's own upstream variable fonts (v1.003, SIL OFL)
   at wght 400/500 upright and 400 italic, subset to Latin + General
   Punctuation + Currency (+ U+2212, the real minus the |money filter emits)
   with EVERY layout feature kept (`--layout-features='*'`), which is exactly
   what the API's default feature list drops. One source for every surface:
   the door and the app shell both render from these files. font-display:
   swap; base.html preloads all three; sw.js precaches them.
   =========================================================================== */

@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/EBGaramond-Regular.a645323631bd.woff2") format("woff2");
}

@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/EBGaramond-Medium.daab4d9c746b.woff2") format("woff2");
}

@font-face {
  font-family: "EB Garamond";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/EBGaramond-Italic.163c92734da4.woff2") format("woff2");
}

/* Archivo, self-hosted too (SPEC-10): 400 and 500 instanced at width 100
   from the upstream variable font, the same source as the PDF faces,
   subset to the Garamond files' ranges with every layout feature kept.
   Nothing loads from Google any more; the worker precaches both. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/Archivo-Regular.e13ace00531c.woff2") format("woff2");
}

@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/Archivo-Medium.5dbbd38ead05.woff2") format("woff2");
}

/* ===========================================================================
   Cypress Row — the door
   ---------------------------------------------------------------------------
   Palette is green-forward on purpose. Limewash appears only on the sign-in
   plate, the way a brass plate sits on a dark painted door.
   =========================================================================== */

:root {
  --iron:      #131E19;  /* near-black, green cast — deepest ground */
  --cypress:   #1E382C;  /* the dominant field */
  --river:     #2E7370;  /* the only interactive accent */
  --river-lit: #3D918D;  /* hover / focus */
  --limewash:  #F4F1E8;  /* type on cypress: the cover, the rail, the mobile bars (SPEC-09: one token) */
  --parchment: #D8D0BC;  /* rules on light */
  --slate:     #6E6B5F;  /* muted text on light */
  /* Deliberately distinct from --plate-radius: 2px reads fine on a hairline
     form field or the login plate, but is invisible against a photograph.
     Used by the property detail page's photo (the card grid's own photo
     moved to --card-radius below, since it no longer carries a radius of
     its own — see that token). */
  /* A third, separate radius — --plate-radius stays exactly as it was for
     the login plate and the record-row outlines (the property card moved
     off it, see --card-radius below; the More screen moved off it too,
     see .rent-row--button in the More screen section); this is only for actual
     controls: text inputs, selects, textareas, file inputs, buttons, and
     (explicitly, per CONTROLS_SPEC.md) lease cards. Don't reach for this
     as a general "bigger radius" — it names a specific category of
     element, not a size preference. */
  /* CARD_AND_LAYOUT_SPEC.md: the property card's own radius — genuinely a
     fourth value, not a coincidental match with any of the three above.
     Applied to .property-card only (border-radius on all four corners)
     and never separately to .property-card__photo — the card's own
     `overflow: hidden` clips the photo automatically, so the two edges
     always trace each other instead of drifting out of sync the way a
     second, independently-maintained photo radius could. */
  --font-ui: "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  
  /* materials */
  --stock: #F3EBDA;            /* the working sheet */
  --stock-light: #F6F0E2;      /* tonal drift, top-left of the sheet */
  --stock-dark: #EFE5D2;       /* tonal drift, edges; read-only field fill */
  --paper: #FBF7EC;            /* input and secondary-button fill */
  --ink: #1B2A22;              /* text */
  --sage: #5F675A;             /* secondary text and labels; 4.9:1 on stock */
  --hair: #DBD1BC;             /* hairline rule */
  --edge: #B8B19E;             /* control borders; underline for text actions */
  --red: #A3372B;              /* second ink: a figure owed; an irreversible action */
  --red-rule: rgba(163, 55, 43, 0.32);          /* money-column rule; underline for red actions */
  --on-cypress-dim: rgba(244, 241, 232, 0.74);  /* inactive nav text */
  --on-cypress-rule: rgba(244, 241, 232, 0.18); /* rules inside the rail */

  /* type */
  --serif: "EB Garamond", Garamond, "Iowan Old Style", Georgia, serif;
  --sans: Archivo, "Helvetica Neue", Arial, sans-serif;

  /* small-caps scale */
  --sc-s: 14.5px;   /* column heads (SPEC-16: was 13.5) */
  --sc-m: 15.5px;   /* field and filter labels, stats, back links, cross-references (was 14.5) */
  --sc-l: 17px;     /* nav, sub-nav tabs, section marks, buttons, text actions (was 15.5) */

  /* shape */
  --radius: 0;

  /* rhythm, desktop (SPEC-16 §1 turned every value up one notch; the
     values it replaced are in parentheses) */
  --row: 40px;          /* one-line entry (36) */
  --row-thumb: 50px;    /* entry with a 48×36 thumbnail (46) */
  --row-2: 52px;        /* entry carrying its annotation on a second line, desktop (48) */
  --row-head: 30px;     /* column heads (28) */
  --row-section: 38px;  /* section mark (34) */
  --row-sum: 32px;      /* subtotal (30) */
  --row-final: 42px;    /* final total (38) */
  --control: 36px;      /* inputs, selects (34) */
  --button: 36px;       /* (32) */
}

/* Mobile rhythm — SPEC-01 §2, at the app's one and only chrome breakpoint
   (1024px, the same one the rail/tab-bar swap already uses — see
   "Navigation" in CLAUDE.md; the spec's 767/768 was revised to this, see
   its Decisions section). Below it: two-line entries, 44pt controls. */
@media (max-width: 1023px) {
  :root {
    /* SPEC-16 §1, a half-step: --sc-m/l up one (15/17 before); --sc-s,
       --row-head and --row-sum inherit the desktop values, as they always
       did (heads read 14.5 on the phone now, as the reference draws them;
       the top bar's stat pins its own 13.5); --row-section is the desktop
       38 too (the phone pinned 36 before). --row stays 56. */
    --sc-m: 16px;
    --sc-l: 18px;
    --row: 56px;          /* two-line entry */
    --row-thumb: 56px;    /* 56×42 thumbnail */
    --row-2: 56px;        /* two-line entry: the mobile row already is one */
    --row-final: 40px;
    --control: 44px;
    --button: 44px;
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
  /* FAB_AND_NAV_SPEC.md: the browser's default blue tap-highlight flashed
     on nav items, rows, chips, cards, and the FAB alike — global, not
     scoped to nav, so nothing tappable is missed. There were no stray
     :active/:focus color rules anywhere in this file to account for
     (checked directly), so this alone is the complete fix. It only
     affects the touch highlight — it does NOT touch `outline`, so it
     must never be treated as a substitute for the app's existing
     :focus-visible rings, which stay exactly as they were and remain the
     only thing keyboard users see. */
  -webkit-tap-highlight-color: transparent;
}

/* Several components (.formset-row, .checkbox-list__item, and anything
   else that sets its own `display`) set it unconditionally, which — being
   an author-origin rule — outranks the browser's built-in `[hidden] {
   display: none }` rule at equal specificity, even though that built-in
   rule looks like it should always win. Concretely: `el.hidden = true`
   silently did nothing on those elements. This is a global, !important
   backstop so `.hidden`/`[hidden]` toggled from JS always actually hides
   the element, regardless of what display value a component rule sets. */
[hidden] {
  display: none !important;
}

/* Native <input type="date"> carries its own intrinsic minimum content
   width (to fit its MM/DD/YYYY segments and spinner) that a bare
   `width: 100%` on the element never overrides — a flex/block child's
   min-width defaults to the size its content needs, not 0, so on a
   narrow viewport the input pushes past its container's edge instead of
   shrinking to fit it. A global, unscoped rule rather than a per-form
   fix: every date input in the app (lease start/end dates, payment
   received-on, expense date, deposit received-on, deposit-return
   returned-on) needs the same treatment, and a future one would hit the
   same bug silently if this lived on a single page's own CSS instead.
   box-sizing: border-box is already set globally (see *, above) but is
   restated here so this rule is self-contained regardless of that.

   min-width/max-width/box-sizing alone were verified in Chromium and
   did nothing on an installed iOS PWA — the actual cause is Safari's
   native date control, which carries an intrinsic minimum width CSS
   cannot reach AT ALL while the native appearance is still active; no
   width rule takes effect until that appearance is removed first.
   -webkit-appearance/appearance: none is the load-bearing fix here —
   the width rules above are necessary but were never sufficient on
   their own. Safari also centers date text by default; text-align:
   left matches every other input in the app. Once the native
   appearance is gone, the control picks up this app's normal
   .field input background/border/padding for free (same selector
   already matches input[type="date"]), so nothing else needs
   restating here. */
input[type="date"] {
  -webkit-appearance: none;
  appearance: none;
  text-align: left;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
}

/* Removing the native appearance above can take the built-in calendar
   affordance with it in some WebKit versions — this pseudo-element is
   what actually renders that icon, kept explicitly visible/clickable
   so the control still reads as a date field rather than plain text.
   No effect outside WebKit (the selector simply doesn't exist there). */
input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: 1;
  cursor: pointer;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background-color: var(--cypress);
  color: var(--limewash);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* --- Transom ------------------------------------------------------------- */
/* The signature. A cast-iron grille above the door, full width, once only. */

/* The shell never showed base.html's footer bar and its markup is unchanged
   (SPEC-09 §6.9: the shell renders identically); the cover pages empty the
   block instead. */
.has-tabbar .sill {
  display: none;
}

/* --- The cover (SPEC-09) ------------------------------------------------- */
/* The door is the cover of the register: flat cloth, the title stamped,
   one paper label for the writing. Nothing here is owed, so nothing is
   red. The shell's own form rules are untouched: every selector below is
   scoped to the cover's classes. */
body.cover {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

/* The stack and the label are one block, centred in the viewport with at
   least 72px above; on a short viewport the page scrolls and the foot
   follows the content. */
.cover__page {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 72px 24px 48px;
}

.cover__stack {
  width: 520px;
  max-width: 100%;
  text-align: center;
}

/* The two-weight rule: 2px limewash, then a 1px line at 45% 3px beneath;
   flipped at the foot of the stack. */
.cover__rule {
  position: relative;
  height: 6px;
}

.cover__rule::before,
.cover__rule::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
}

.cover__rule::before {
  top: 0;
  height: 2px;
  background: var(--limewash);
}

.cover__rule::after {
  top: 5px;
  height: 1px;
  background: rgba(244, 241, 232, 0.45);
}

.cover__rule--flip::before {
  top: 4px;
}

.cover__rule--flip::after {
  top: 0;
}

.cover__name {
  margin: 30px 0 22px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 62px;
  line-height: 1;
  color: var(--limewash);
}

.cover__mark {
  display: block;
  width: 48px;
  height: 10px;
  margin: 0 auto;
  fill: none;
  stroke: rgba(244, 241, 232, 0.5);
  stroke-width: 0.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.cover__tag {
  margin: 20px auto 28px;
  max-width: 360px;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 19px;
  line-height: 1.3;
  color: rgba(244, 241, 232, 0.8);
  text-wrap: balance;
}

/* The label: the pages' stock, flat, with a printed border 4px inside
   the edge — the one box-shadow on the cover, and it is inset. */
.cover__label {
  width: 340px;
  max-width: 100%;
  margin: 48px 0 0;
  padding: 24px 28px 26px;
  background: var(--stock);
  color: var(--ink);
  box-shadow: inset 0 0 0 4px var(--stock), inset 0 0 0 5px var(--ink);
  text-align: left;
}

.cover__label form {
  margin: 0;
}

.cover__lbl {
  display: block;
  font-size: var(--sc-m);
  color: var(--sage);
}

/* The ruled line: an input with no box but the rule beneath it, the text
   on the rule's baseline. On focus the rule becomes the two-weight rule
   (the field's ::after is the 1px line 3px beneath) and the label turns
   ink; there is no ring. */
.cover__field {
  position: relative;
}

.cover__field::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 1px;
  background: var(--ink);
  opacity: 0;
}

.cover__field:focus-within::after {
  opacity: 1;
}

.cover__label:focus-within .cover__lbl {
  color: var(--ink);
}

.cover__line {
  display: block;
  width: 100%;
  height: 44px;
  margin: 0;
  padding: 0 0 8px;
  border: 0;
  border-bottom: 1px solid var(--ink);
  border-radius: 0;
  background: transparent;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1;
  color: var(--ink);
  -webkit-appearance: none;
  appearance: none;
}

.cover__line:focus {
  outline: none;
  border-bottom-width: 2px;
  padding-bottom: 7px;
}

.cover__line--code {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 28px;
  font-variant-numeric: oldstyle-nums tabular-nums;
  letter-spacing: 0.25em;
  padding-bottom: 5px;
}

.cover__line--code:focus {
  padding-bottom: 4px;
}

.cover__error {
  margin: 8px 0 0;
  font-family: var(--sans);
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink);
}

.cover__message {
  margin: 0;
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink);
}

/* The app's primary button, full width, 44px, the inset double rule —
   scoped under the label so it outranks .button's own --button height. */
.cover__label .cover__button {
  display: flex;
  width: 100%;
  height: 44px;
  margin-top: 20px;
  align-items: center;
  justify-content: center;
}

.cover__resend {
  margin: 16px 0 0;
}

.cover__action {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0;
  font-family: var(--serif);
  font-size: var(--sc-l);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.12em;
  color: var(--sage);
  background: none;
  border: 0;
  border-radius: 0;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--edge);
  text-underline-offset: 4px;
  cursor: pointer;
}

.cover__action:hover {
  color: var(--ink);
}

.cover__action:focus-visible,
.cover__button:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.cover__foot {
  flex: none;
  margin: 0;
  padding: 24px 24px 34px;
  text-align: center;
  font-size: 14px;
  color: rgba(244, 241, 232, 0.6);
}

@media (max-width: 1023px) {
  .cover__page {
    padding: 48px 24px 40px;
  }

  .cover__stack {
    width: 100%;
  }

  .cover__name {
    margin: 22px 0 16px;
    font-size: 40px;
  }

  .cover__mark {
    width: 40px;
    height: 8px;
  }

  .cover__tag {
    margin: 14px auto 22px;
    max-width: 290px;
    font-size: 16px;
  }

  .cover__label {
    width: 100%;
    margin-top: 36px;
    padding: 20px 22px 22px;
  }

  .cover__line--code {
    font-size: 26px;
  }

  .cover__action {
    min-height: 44px;
  }

  .cover__foot {
    padding-bottom: 26px;
    font-size: 13px;
  }
}

/* --- Frame --------------------------------------------------------------- */

/* --- Identity ------------------------------------------------------------ */

/* --- Sign-in block --------------------------------------------------------- */
/* BRANDING_SPEC.md: replaces the old .plate — a solid cream card punched
   through the dark green field. The email/code screens now sit directly
   on that field instead ("in the green field rather than being punched
   through it"), so there's no card background/shadow/padding-as-a-box
   here at all — just the heading and the form, in flow on the same
   surface as the wordmark above them. */

/* --- Fields -------------------------------------------------------------- */

/* FORM_LAYOUT_SPEC.md: every field group — a label plus its control, or a
   row of related controls like city/state/zip below — is separated from
   the next by this same distance. Don't give an individual field or
   section its own bespoke margin-bottom/margin-top to open up more or
   less space than its neighbors; if a form reads unevenly, this is the
   one number to fix. */
.field {
  margin-bottom: 1.5rem;
}

.field input {
  width: 100%;
  padding: 0.8125rem 0.875rem;
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  color: var(--iron);
  background: #F6F3EA;
  border: 1px solid var(--parchment);
}

/* --- Links (base; the shell overrides bare links under .has-tabbar) ----- */
a {
  color: var(--river);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  color: var(--river-lit);
}

a:focus-visible {
  outline: 2px solid var(--river);
  outline-offset: 2px;
}

/* --- Sill ---------------------------------------------------------------- */

/* --- Ledger primitives: type roles and rule grammar (SPEC-01 §3, §4) ------- */
/* Surface-agnostic building blocks the shared components (passes 2-4) are
   assembled from. The type division in one sentence: serif for the printed
   form, sans for what is written into it. Two rules that must not be
   crossed: the serif never sets a data word inside a row or an input; the
   sans never sets a label, heading, button, or figure in a money column. */

/* Structure — everything structural is EB Garamond in TRUE small caps
   (the self-hosted files carry `smcp`/`c2sc`; see the @font-face block at
   the top). Author the text in sentence case ("Unpaid", "Show archived");
   never type capitals. Size comes from context (--sc-s/m/l). Buttons and
   text actions widen the tracking to .12em on their own selectors. */
.sc {
  font-family: var(--serif);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
}

/* Figure — every money amount in a row, column, total, or stat, including
   the `$`. Tabular lining numerals are what make `$` signs and digit
   positions align down a column (§8.2). */
.fig {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 17px;      /* SPEC-16: was 15.5 */
  font-variant-numeric: tabular-nums lining-nums;
  text-align: right;
}

/* Entry — the row's subject: address, person, vendor, task. */
.entry {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 14.5px;    /* SPEC-16: was 13 */
}

/* Annotation — second-line or secondary-column detail. --sage, not the old
   --slate: the warmer sheet lowers contrast, and a small annotation needs
   4.5:1. 13.5px desktop, 13px phone (SPEC-16; 12.5 at both before). */
.ann {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 13.5px;
  color: var(--sage);
}

/* The second ink. On a figure it means owed; on an action it means
   irreversible. Never a fill, never a border, never a validation error.
   A bare class: ledger cells take it because their shared declarations
   sit at zero specificity (:where(.ledger td)); Today's rows are ledger
   rows now, so no other twin is needed. */
.red {
  color: var(--red);
}

/* A figure absent from the record (a returned deposit with no amount
   recorded, SPEC-03 Decisions): an em dash in sage, in the figure's face
   so the money column keeps its rhythm. */
.fig--absent {
  color: var(--sage);
}

@media (max-width: 1023px) {
  .fig {
    font-size: 17px;    /* SPEC-16: was 16 */
  }

  .entry {
    font-size: 15px;    /* was 14 */
  }

  .ann {
    font-size: 13px;    /* was 12.5 */
  }
}

/* Rule grammar — in a data grid rules are decoration; here each rule means
   one thing, and these four are the only permitted separators. No card
   borders, no boxes around list items, no zebra fills, no drop shadows. */

/* Money-column rule: every cell of a money column carries this, from the
   column-head row through the final-total row, so the rule is continuous. */

/* --- App shell ------------------------------------------------------------ */
/* Everything behind the tab bar: /app/, /app/properties/, etc. The door
   keeps its full green field (one deliberate dramatic screen); the app
   shell is a light working surface — dark labels on dark green were close
   to unreadable, and cream fields punched into a green page read as holes
   rather than inputs. Cypress green becomes the accent here instead: the
   nav bar's active state, buttons, headings. */

:root {
  /* SPEC-01 §5.1-5.3. Both bars add the device's safe-area inset on top
     of these (see .mobile-topbar/.bottom-tabbar) — the tokens are the
     bars' own heights, not the total. */
  --tabbar-height: 48px;   /* mobile bottom tab bar: the label block (fixes-02; was 60) */
  --topbar-height: 48px;   /* mobile top bar: section title (or wordmark on Today) */
  --rail-width: 200px;     /* desktop rail */
}

.has-tabbar {
  /* The app shell's palette is close to but distinct from the door's own
     --limewash/--iron/--slate, so it's redefined in this scope rather than
     changed at :root — every existing rule that reads var(--limewash),
     var(--iron), or var(--slate) on an element inside <body class="has-tabbar">
     picks these up automatically; door.html/code.html (no has-tabbar class)
     keep the :root originals untouched. */
  /* SPEC-01: the door's --iron/--slate/--parchment are remapped onto the
     ledger's --ink/--sage/--hair here so every existing app-shell rule that
     reads them picks up the new palette at once, while the door (no
     .has-tabbar) keeps the :root originals. Passes 2-4 move components onto
     the ledger names directly; until then these aliases carry them. */
  --iron: var(--ink);        /* primary text */
  --slate: var(--sage);      /* secondary/muted text */
  --parchment: var(--hair);  /* hairline rules */
  /* The sheet's tonal drift, defined ONCE and painted on the sheet element
     (.app-main; body.tenant-shell for the portal, which has no bars) —
     never on rows, cells or panels, and, since fixes-03, never on body:
     body and html are the cloth (--cypress, body's own base rule), the
     sheet is the page, and nothing that could ever show beneath the tab
     bar is cream. */
  --sheet: radial-gradient(ellipse 95% 85% at 28% 18%, var(--stock-light) 0%, var(--stock) 52%, var(--stock-dark) 100%);
  color: var(--iron);
  /* fixes-02: the tab bar is fixed to the viewport, but on a phone the
     document itself moves — the root scroller rubber-bands on every drag
     of a page too short to scroll (Reports, More), carrying every fixed
     element with it and exposing the canvas beneath the bar. A long page
     scrolls instead, so Today never shows it. This asks the viewport not
     to be dragged off the bar (the property is on body, which the
     viewport takes when html has none); the installed PWA still bounced,
     so html.shell below makes the canvas the cloth as well. The door (no
     .has-tabbar) is untouched. */
  overscroll-behavior-y: none;
  /* fixes-03, from the phone's own numbers: in the installed app on an
     844pt screen the layout viewport is 797pt — short by exactly the 47pt
     top inset — and 100svh resolves to the same 797, so a short page's
     document is never taller than that viewport, the viewport never grows
     to the screen as it does on a page that overflows (Today), and the
     bar, fixed to the viewport's bottom, sits 47pt above the screen's
     edge. Sized to the LARGE viewport the document is the screen's height
     on every page, so every page is in Today's state. Where the two
     viewports agree (Chromium, a desktop) this is the same value. The
     door (body.cover) keeps svh. */
  min-height: 100lvh;
}

/* The canvas beneath the app shell is the cloth (fixes-02). Since fixes-03
   body is the cloth too, so the canvas takes cypress from body regardless;
   the class stays as the explicit statement. app_base.html sets it; the
   door and the tenant shell don't carry it. */
html.shell {
  background-color: var(--cypress);
}

/* The tenant portal (no rail, no bars) keeps its sheet on body: its main
   is a 592px column, so the sheet has to be the whole page there. */
body.tenant-shell {
  background-color: var(--stock);
  background-image: var(--sheet);
}

/* Print: the drift goes, the sheet is flat. */
@media print {
  .app-main,
  body.tenant-shell {
    background-image: none;
    background-color: var(--stock);
  }
}

/* Form controls for the app shell live in the Forms section further down
   (SPEC-01 §5.16), scoped under .has-tabbar so the door's own field and
   button rules above stay exactly as they are. */

/* The shell is a flex column that fills body (fixes-03): before, it was a
   plain block, so the sheet's `flex: 1` was inert and body's own box —
   painted as the sheet then — filled the rest of a short page. Now the
   sheet stretches to the tab bar on its own. The tab bar is still a
   direct child, fixed to the viewport; a flex column transforms nothing. */
.app-shell {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}

/* The sheet (SPEC-01 §5.4). Mobile: `0 16px 24px` — the top bar is sticky
   (in flow), so nothing has to be cleared above, and the title block's own
   48pt row (§5.6) is what separates a title from the bar. The tab bar is
   fixed, so its height plus the safe-area inset is left beneath the sheet
   as MARGIN, not padding (fixes-03): the sheet ends where the bar begins,
   and whatever lies below the bar — body, html, a bounce, a viewport that
   ends short of the screen — is the cloth, never cream. The sheet carries
   the drift, framed on the document as it was when body painted it: the
   gradient is sized to body's box (the sheet plus the top bar above it and
   the bar's margin below it) and shifted up by the top bar, so the sheet
   reads exactly as before — verified pixel for pixel at 375 and 1280.
   Desktop padding and framing live in the 1024px block below. The sheet
   itself is never capped: it fills the width beside the rail at every
   desktop width, cream to the viewport's edge; the 1100px content cap
   that used to sit on this element is on .app-main__inner, the content
   column inside it. */
.app-main {
  flex: 1;
  width: 100%;
  margin: 0 auto calc(var(--tabbar-height) + env(safe-area-inset-bottom, 0px));
  padding: 0 16px 24px;
  background-color: var(--stock);
  background-image: var(--sheet);
  background-repeat: no-repeat;
  background-size: 100% calc(100% + var(--topbar-height) + env(safe-area-inset-top, 0px) + var(--tabbar-height) + env(safe-area-inset-bottom, 0px));
  background-position: 0 calc(-1 * (var(--topbar-height) + env(safe-area-inset-top, 0px)));
}

/* A page with the mobile action button reserves 96pt under its content so
   the last row is never trapped beneath it (§5.15). Set by app_base.html
   on <main> whenever the FAB registry has an action for the page. */
.app-main--has-action {
  padding-bottom: 96px;
}

/* The content column: what .app-main's max-width: 1100px capped before
   the sheet's background moved onto it — the same 1028px of content
   (1100 less the desktop padding of 36 a side), flush against the rail,
   with the sheet running past it to the edge. */
.app-main__inner {
  max-width: 1028px;
}

/* Forms read easier narrower than the full content column — a thousand
   pixel wide text input is harder to use, not easier. No margin: 0 auto
   here — CARD_AND_LAYOUT_SPEC.md: .app-main already centers/positions
   itself (auto-centered on mobile, flush against the rail at 1024px+
   — see the breakpoint rule below), so a second auto-centering rule on
   this nested column re-centered it AGAIN inside .app-main's content
   box, offsetting it from the left gutter every other page (list,
   Today, detail) starts flush against. Capping the width alone is
   enough; let it sit at the same left edge as everything else. */
.page--form {
  max-width: 560px;
}

/* --- Title block, title rule and ornament (SPEC-01 §5.6, §5.8) ------------ */
/* One italic h1 per screen, and directly under it the one decorative
   element in the app: an 18px row — the ornament (46×18, traced from the
   transom, a stand-in until it is) at the left, then a 1px ink line filling
   the rest. Both are drawn by ONE pseudo-element on the title, painted in
   --ink through a two-layer mask: the ornament SVG (static/icons/
   ornament-rule.svg — the asset is the single definition; there is no
   inline copy to drift) sized 46×18 at the left, and a 1px-tall gradient
   layer from x=46 to the right edge at the ornament's own centre line.
   Every page that already has an <h1 class="page__title"> gets the rule
   for free; pages with no title (the Properties list) wait for their
   screen spec. Where the h1 shares a flex row with something else (the
   Occupied/Vacant badge on a property, the month cross-references on
   Money), the rule hangs on that row's container instead so it spans the
   full width — see .property-detail__header and .rent-month below. */
.page__title {
  margin: 0;
  padding: 6px 0 0;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 34px;      /* SPEC-16: was 30 */
  line-height: 1;
  letter-spacing: 0;
  color: var(--ink);
}

.page__title::after,
.property-detail__header::after,
.rent-month::after,
.title-row::after,
.title-block::after {
  content: "";
  display: block;
  height: 18px;
  margin: 12px 0 6px;
  background-color: var(--ink);
  -webkit-mask-image: url("../icons/ornament-rule.8262d840c9cd.svg"), linear-gradient(#000, #000);
  mask-image: url("../icons/ornament-rule.8262d840c9cd.svg"), linear-gradient(#000, #000);
  -webkit-mask-size: 46px 18px, calc(100% - 46px) 1px;
  mask-size: 46px 18px, calc(100% - 46px) 1px;
  -webkit-mask-position: left top, right 0 top 8px;
  mask-position: left top, right 0 top 8px;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* A title sharing a row with a badge or the month cross-references: the
   row wraps, and its own ::after (the rule, above) takes a full line
   beneath both; the h1's own rule is suppressed so there's exactly one. */
.property-detail__header,
.rent-month {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 22px;
  margin: 0;
}

.property-detail__header::after,
.rent-month::after {
  flex: 0 0 100%;
  /* Last in the row regardless of the siblings' own `order` (the month
     arrows on Money are reordered around the title). */
  order: 99;
}

.property-detail__header .page__title::after,
.rent-month .page__title::after,
.title-row .page__title::after,
.title-block .page__title::after {
  display: none;
}

/* The generic title row (SPEC-03, the .rent-month pattern reused): the
   title at the left, a stat or an action cluster at the right, the rule
   beneath both. The row wraps: on a phone, when the title and the stat
   don't fit on one line, the stat sits beneath the title, left-aligned,
   same style — never truncated or shrunk ("Wednesday, September 30"). */
.title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 22px;
  margin: 0;
  padding: 6px 0 0;
}

.title-row::after {
  flex: 0 0 100%;
  order: 99;
  margin-top: 12px;
}

.title-row .page__title {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
}

.title-row__stat {
  flex: none;
  font-family: var(--serif);
  font-size: var(--sc-m);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
  white-space: nowrap;
}

/* The action cluster at a statement's title (SPEC-03 §3-4, SPEC-01
   §5.6): the scope select at --control, the boolean filter as a real
   checkbox, then the buttons — vertically centred at the right on
   desktop. On a phone the cluster drops beneath the title rule as the
   44pt filter row (order past the rule's 99), with a hairline below,
   and the buttons leave it: the primary action is the action button,
   Send report reappears under the sheet's foot (.sheet-foot-action). */
.title-row__actions {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: none;
  align-self: center;
}

.filter-form {
  display: flex;
  align-items: center;
  gap: 16px;
}

/* .has-tabbar .field's 16px rhythm would otherwise win on order and make
   the filter row 60px (verified: 61px at 375, 50px at 1280). */
.has-tabbar .filter-form .field {
  width: 200px;
  margin: 0;
}

.filter-check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  font-family: var(--serif);
  font-size: var(--sc-m);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
  white-space: nowrap;
  cursor: pointer;
}

.filter-check:hover {
  color: var(--ink);
}

.sheet-foot-action {
  display: none;
}

@media (min-width: 1024px) {
  .filter-check {
    min-height: 0;
  }
}

@media (max-width: 1023px) {
  .title-row__actions {
    order: 100;
    flex: 0 0 100%;
    min-height: 44px;
    align-self: auto;
    justify-content: flex-start;
    border-bottom: 1px solid var(--hair);
  }

  .filter-form {
    flex: 1 1 auto;
  }

  .has-tabbar .filter-form .field {
    flex: 1 1 auto;
    width: auto;
    max-width: 220px;
  }

  /* Scoped, so it outranks .button's own display rule (equal specificity
     would lose on order — verified: both buttons stayed visible at 375
     and pushed the sheet to 414px). */
  .title-row__actions .title-row__desktop-only {
    display: none;
  }

  /* Send report on a phone: a secondary button at natural width,
     right-aligned below the sheet's final total, 16pt clear of the
     ledger and above the action-button padding. */
  .sheet-foot-action {
    display: flex;
    justify-content: flex-end;
    margin-top: 16px;
  }
}

/* A title with a line beneath it (the tenants under a period detail's
   address): a column, the rule after the last line. */
.title-block {
  display: flex;
  flex-direction: column;
}

@media (max-width: 1023px) {
  /* A 50pt row: 26px type plus 12px above and below (SPEC-16; 24 before). */
  .page__title {
    padding: 12px 0;
    font-size: 26px;
  }

  .page__title::after,
  .property-detail__header::after,
  .rent-month::after,
  .title-row::after,
  .title-block::after {
    margin: 0 0 4px;
  }

  .title-row {
    min-height: 48px;
    padding: 0;
    align-items: center;
    gap: 0 16px;
  }

  .title-row .page__title {
    padding: 12px 0;
  }

  /* When the stat wraps under the title it is its own 44pt-tall line at
     the left; when it fits, it is centered against the title. */
  .title-row__stat {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
  }

  .property-detail__header,
  .rent-month {
    gap: 0 16px;
  }
}

.page--placeholder {
  padding-top: 0;
}

/* Empty states (§5.17): one line of sans in sage, no box, no icon. */
.empty-state {
  margin: 0 0 20px;
  font-family: var(--sans);
  font-size: 14.5px;    /* the entry's size (SPEC-16: was 13) */
  color: var(--sage);
}

@media (max-width: 1023px) {
  .empty-state {
    font-size: 15px;
  }
}

/* Back link (§5.13): `‹ Properties` in the same small-caps voice as the
   month cross-references, no border or fill, 8px above the title block.
   On mobile it is a 44pt hit area (the text stays small; the padding is
   what grows). Its move into the top bar is per-screen work. */
.back-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: 0 0 8px;
  padding: 0;
  font-family: var(--serif);
  font-size: var(--sc-m);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
  background: none;
  border: 0;
  text-decoration: none;
}

.back-link:hover {
  color: var(--ink);
}

@media (min-width: 1024px) {
  .back-link {
    min-height: 0;
  }
}

/* Focus (§5.11), for every button, action, filter, row link and back link
   in the app shell: a cypress outline, offset 2px. Fields get their own
   in the forms section. */
.has-tabbar a:focus-visible,
.has-tabbar button:focus-visible {
  outline: 2px solid var(--cypress);
  outline-offset: 2px;
}

/* Inline links in running text (a lease named on a deposit, "Add one on
   the Users screen"): ink, underlined in --edge. Matched by the absence of
   a class — every row, action, button and nav item carries one and sets
   its own color, and a bare `.has-tabbar a` would outrank all of them
   (verified: it turned the rail and tab-bar labels ink). */
.has-tabbar a:not([class]) {
  color: var(--ink);
  text-decoration-color: var(--edge);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* Controls that sit on cypress take a limewash ring instead (§5.11). */
.has-tabbar .rail__item:focus-visible,
.has-tabbar .bottom-tabbar__item:focus-visible,
.has-tabbar .action-button:focus-visible {
  outline-color: var(--limewash);
  outline-offset: -3px;
}

/* Messages and form-level notices on the sheet: ink text on the sheet
   with a hairline beneath — no fill, no colored border, no radius, no red
   (§2: red is never a validation color). The door's own .notice (the
   login error) is untouched: this is scoped to the app shell. */
.has-tabbar .notice {
  margin: 0 0 16px;
  padding: 8px 0;
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--hair);
  border-radius: 0;
}

.has-tabbar .messages {
  margin: 0 0 16px;
  gap: 0;
}

.has-tabbar .messages .notice {
  margin: 0;
}

/* --- Actions (SPEC-01 §5.11) ------------------------------------------------ */
/* Three kinds. A BUTTON (.button--primary / .button--secondary, and the
   .action-link--primary links detail pages use for Add lease, Add payment,
   Download PDF and the like, which take the secondary button's whole
   treatment) carries a double rule: a 1px border at the edge and a 1px
   rule 2px inside it, drawn with inset box-shadows so the label area stays
   a flat fill — the one box-shadow use the spec permits. A TEXT ACTION
   (.action-link--quiet: Restore, Resend) is small caps in sage with an
   --edge underline. A DESTRUCTIVE text action (.action-link--danger:
   Archive) is the same in red — never a filled button; when a
   confirmation step exists, its confirming control is a secondary button
   labelled with the verb. Labels are small caps at --sc-l with .12em
   tracking, authored in sentence case. On mobile a text action is a 44pt
   hit area; the text itself does not grow — and it is ink with an ink
   underline there (SPEC-16 §2; the phone block below the desktop reset). */
.button,
.action-link--primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--button);
  padding: 0 16px;
  font-family: var(--serif);
  font-size: var(--sc-l);
  font-weight: 500;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.12em;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  border: 1px solid var(--ink);
  border-radius: 0;
  cursor: pointer;
  transition: background-color 120ms ease;
}

.button--primary {
  color: var(--limewash);
  background: var(--cypress);
  border-color: var(--cypress);
  box-shadow: inset 0 0 0 2px var(--cypress), inset 0 0 0 3px rgba(244, 241, 232, 0.85);
}

.button--primary:hover {
  background: #264636;
  box-shadow: inset 0 0 0 2px #264636, inset 0 0 0 3px rgba(244, 241, 232, 0.85);
}

.button--secondary,
.action-link--primary {
  color: var(--ink);
  background: var(--paper);
  border-color: var(--ink);
  box-shadow: inset 0 0 0 2px var(--paper), inset 0 0 0 3px var(--ink);
}

.button--secondary:hover,
.action-link--primary:hover {
  background: var(--stock-dark);
  box-shadow: inset 0 0 0 2px var(--stock-dark), inset 0 0 0 3px var(--ink);
}

.button:disabled,
.button:disabled:hover {
  opacity: 0.6;
  cursor: not-allowed;
}

@media (max-width: 1023px) {
  .button,
  .action-link--primary {
    padding: 0 20px;
  }
}

.action-link--quiet,
.action-link--danger,
.document-row__actions a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  font-family: var(--serif);
  font-size: var(--sc-l);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.12em;
  color: var(--sage);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: var(--edge);
}

.action-link--quiet:hover,
.document-row__actions a:hover {
  color: var(--ink);
}

.action-link--danger,
.document-row__actions a.action-link--danger {
  color: var(--red);
  text-decoration-color: var(--red-rule);
}

.action-link--danger:hover,
.document-row__actions a.action-link--danger:hover {
  color: var(--red);
}

@media (min-width: 1024px) {
  .action-link--quiet,
  .action-link--danger,
  .document-row__actions a {
    min-height: 0;
  }
}

/* SPEC-16 §2, the phone's affordance for a text action: there is no hover
   to promise anything, so it looks like a link — ink, a 1px ink underline
   5px below, at --sc-l as before; a destructive one stays red with a red
   underline. Desktop keeps the quiet tier above exactly (sage, --edge,
   ink on hover). Hit areas are unchanged: 44pt, padded. Send a new code on
   the cover (.cover__action) takes the same treatment — SPEC-16 §5.8. */
@media (max-width: 1023px) {
  .action-link--quiet,
  .document-row__actions a,
  .cover__action {
    color: var(--ink);
    text-decoration-color: var(--ink);
    text-underline-offset: 5px;
  }

  .action-link--danger,
  .document-row__actions a.action-link--danger {
    color: var(--red);
    text-decoration-color: var(--red);
    text-underline-offset: 5px;
  }
}

/* Archive/Restore sit apart from the primary pair on a detail page's
   action row (pushed right by the auto margin), and at the opposite end
   from Cancel/Save in a form's footer (see .form-actions). */
.action-link--quiet,
.action-link--danger {
  margin-left: auto;
}

/* --- Nav chrome (SPEC-01 §5.1-5.3, §5.15) -------------------------------- */
/* The green lives here permanently: the rail (desktop) and the top bar +
   tab bar (mobile) are cypress at every width; the sheet underneath stays
   stock. No icons anywhere in navigation — a ledger index has no
   pictograms. No fills, no bars, no radii in any state: the active item is
   marked by color and a MARGIN MARK — in the rail, a small limewash triangle
   drawn with CSS borders on ::before, pointing right at the item; in the tab
   bar, a 2px limewash rule the width of the label, 6px above it (fixes-02;
   the rotated triangle is gone). Below 1024px: sticky top bar + fixed bottom tab bar. At 1024px and
   up: the fixed rail instead — a hard cutover at one breakpoint (see the
   media block near the end of this file). */

/* Mobile top bar — 48pt plus the top safe-area inset. Sticky, not fixed,
   so it sits in flow and the sheet needs no top offset to clear it. Left:
   the current section's title as small caps in limewash (the wordmark on
   Today only — SPEC-01 §5.2 — and on a page with no active section at
   all, where there's nothing else to say). Detail pages show the section
   title too until a screen spec supplies the back link that replaces it
   (Decisions) — never something derived from the URL. Right: an optional
   stat in dim small caps, via app_base.html's topbar_stat block. */
.mobile-topbar {
  position: sticky;
  top: 0;
  z-index: 25;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: calc(var(--topbar-height) + env(safe-area-inset-top, 0px));
  padding: env(safe-area-inset-top, 0px) 16px 0;
  background: var(--cypress);
  color: var(--limewash);
}

.mobile-topbar__title,
.mobile-topbar__wordmark {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 18px;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--limewash);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mobile-topbar__stat {
  flex: none;
  font-family: var(--serif);
  font-size: 13.5px;    /* was --sc-s, which SPEC-16 moved; the top bar doesn't (§3) */
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--on-cypress-dim);
  white-space: nowrap;
}

/* Mobile tab bar (SPEC-01 §5.3, revised by fixes-02) — a 48px label block
   plus the bottom safe-area inset and nothing else; fixed to the viewport's
   bottom edge, over the inset, and the sheet's own bottom padding
   compensates (.app-main). Six items at most, text only, in the serif's
   true small caps — the sans label that "Properties" once needed at 62pt
   sixths is no longer needed once the items are sized to their labels:
   EB Garamond 13px/500, letter-spacing .06em (the tab bar's own tracking,
   tighter than .sc's .09em so six labels fit at 375). Items sit at their
   label widths and are distributed with equal gaps — space-between inside
   16px side padding — never as equal sixths. */
.bottom-tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 25;
  display: flex;
  justify-content: space-between;
  align-items: stretch;
  height: calc(var(--tabbar-height) + env(safe-area-inset-bottom, 0px));
  padding: 0 16px env(safe-area-inset-bottom, 0px);
  background: var(--cypress);
}

/* An item is its label plus a symmetric 5px pad: the pad, not the label,
   is what makes the narrowest hit area ("More", 34px of text) at least
   44pt wide, and the same pad on every item keeps the label-to-label gaps
   as equal as the item gaps. 5px is also the most the bar affords: at 375
   the six labels sum to 281px and the pads to 60, inside the 343px between
   the side paddings — 2px to spare. Full bar height (the 48px block),
   label centred. */
.bottom-tabbar__item {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
  font-family: var(--serif);
  font-size: 13px;
  font-weight: 500;
  line-height: normal;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.06em;
  color: var(--on-cypress-dim);
  text-decoration: none;
  white-space: nowrap;
}

.bottom-tabbar__item--active {
  color: var(--limewash);
}

/* The margin mark on the active item: a 2px limewash rule exactly the width
   of the label (the item's content box — the pad is excluded), 6px above
   the label's text box. The label is centred, so the rule's bottom edge is
   half a line up from the centre plus the 6px gap; with line-height normal
   the line is the font's own box (17px for EB Garamond at 13px), which is
   what `lh` reads. The px line is the same figure for the browsers without
   `lh` (Safari before 16.4), listed first so the unit wins where it exists.
   No caret. */
.bottom-tabbar__item--active::before {
  content: "";
  position: absolute;
  left: 5px;
  right: 5px;
  bottom: calc(50% + 8.5px + 6px);
  bottom: calc(50% + 0.5lh + 6px);
  height: 2px;
  background: var(--limewash);
}

/* Desktop rail — hidden until 1024px (see the media block below), where
   it becomes a fixed 200px column: wordmark, a rail rule, the primary
   group, a spacer, a rail rule, and the bottom group (Settings, Admin,
   Sign out). Rules inside the rail are 1px --on-cypress-rule, inset 20px
   from each edge. */
.rail {
  display: none;
}

.rail__wordmark {
  padding: 0 20px 16px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 21px;      /* SPEC-16: was 19 */
  color: var(--limewash);
}

.rail__rule {
  margin: 0 20px;
  border: 0;
  border-top: 1px solid var(--on-cypress-rule);
}

.rail__group {
  list-style: none;
  margin: 0;
  padding: 10px 0;
  display: flex;
  flex-direction: column;
}

.rail__group form {
  display: contents;
}

.rail__spacer {
  flex: 1;
}

/* One item: 36px tall (SPEC-16; 32 before), label in small caps at --sc-l, dim until active,
   no background in any state. Every type property is declared here
   directly (not left to .sc) so the Sign out <button> picks them up over
   the browser's own button font without a `font: inherit` reset — which
   would also reset font-variant-caps back to normal. */
.rail__item {
  position: relative;
  display: flex;
  align-items: center;
  height: 36px;
  padding: 0 20px 0 36px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: var(--sc-l);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  line-height: 1;
  color: var(--on-cypress-dim);
  text-decoration: none;
  white-space: nowrap;
}

.rail__item:hover {
  color: var(--limewash);
}

/* Active: limewash, plus the margin mark — a 5×7 limewash triangle
   pointing right, its left edge 20px from the rail edge, centered on the
   row. No fill, no left bar. */
.rail__item--active {
  color: var(--limewash);
}

.rail__item--active::before {
  content: "";
  position: absolute;
  left: 20px;
  top: 50%;
  margin-top: -3.5px;
  border-style: solid;
  border-width: 3.5px 0 3.5px 5px;
  border-color: transparent transparent transparent var(--limewash);
}

/* Sign out is a <button>, not an <a>: reset only the browser's box chrome;
   the type is already set on .rail__item above. */
.rail__item--button {
  width: 100%;
  margin: 0;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
}

/* --- Action button (mobile only, SPEC-01 §5.15) --------------------------- */
/* Replaces the FAB. A 48pt square of cypress with the primary button's
   inner rule (the same inset-shadow double rule §5.11 gives every button)
   and a 28px limewash +. Square corners, no drop shadow — the inset rule
   is the one box-shadow use the spec permits. Fixed above the tab bar;
   pages that show it carry .app-main--has-action for the bottom padding.
   Not rendered on desktop, where the primary action lives in the title
   block — hidden in the 1024px media block below. */
.action-button {
  position: fixed;
  right: 16px;
  bottom: calc(var(--tabbar-height) + 16px + env(safe-area-inset-bottom, 0px));
  z-index: 21;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 28px;
  line-height: 1;
  color: var(--limewash);
  background: var(--cypress);
  border: 1px solid var(--cypress);
  box-shadow: inset 0 0 0 2px var(--cypress), inset 0 0 0 3px rgba(244, 241, 232, 0.85);
  text-decoration: none;
}

/* --- Status (SPEC-01 §5.14; badges per Decisions) -------------------------- */
/* Nothing survives as a box. A status word — Occupied, Vacant, Archived —
   is small caps in sage, no border, no fill. The status dot is the only
   round element in the app: 7px cypress for occupied/active; 6px,
   transparent with a sage outline, for vacant/inactive. Always followed
   by text; never the only signal. */
.badge {
  display: inline;
  margin-left: 8px;
  padding: 0;
  font-family: var(--serif);
  font-size: var(--sc-s);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
  background: none;
  border: 0;
  border-radius: 0;
  white-space: nowrap;
}

.status-dot {
  display: inline-block;
  flex: none;
  width: 6px;
  height: 6px;
  border: 1px solid var(--sage);
  border-radius: 50%;
  background: transparent;
  vertical-align: 1px;
}

/* The filled state: occupied on a property, On in the Users register's
   Login column (SPEC-05 §3) — one drawing, two names. */
.status-dot--occupied,
.status-dot--on {
  width: 7px;
  height: 7px;
  border: 0;
  background: var(--cypress);
}

/* --- Filters (SPEC-01 §5.12) ----------------------------------------------- */
/* Every boolean filter is a checkbox: a 15px square with an --edge border
   on --paper; checked, a cypress fill and border with a 1.5px limewash
   check. The label is small caps at --sc-m in sage and the whole label is
   the control. For now (Decisions) the control is still the existing
   toggle LINK — the box is drawn on it with ::before and the on state is
   the .filter-chip--active class; a real <input type="checkbox"> arrives
   with each screen's conversion. On mobile the row is 44pt tall. */
.filter-chip {
  display: inline-flex;
  align-items: center;
  flex: none;
  gap: 8px;
  min-height: 44px;
  padding: 0;
  font-family: var(--serif);
  font-size: var(--sc-m);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
  background: none;
  border: 0;
  border-radius: 0;
  text-decoration: none;
  white-space: nowrap;
}

.filter-chip::before {
  content: "";
  flex: none;
  width: 15px;
  height: 15px;
  box-sizing: border-box;
  border: 1px solid var(--edge);
  background: var(--paper);
}

.filter-chip:hover {
  color: var(--ink);
}

.filter-chip--active::before {
  border-color: var(--cypress);
  background-color: var(--cypress);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.5l2.5 2.5 4.5-5' fill='none' stroke='%23F4F1E8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px;
}

@media (min-width: 1024px) {
  .filter-chip {
    min-height: 0;
  }
}

/* The page-level action/filter row. On a list page it holds filters only
   and is the spec's filter row: one line, a hairline below, right-aligned
   on mobile. On a detail page it holds the action pair (Edit, Add lease…)
   plus Archive, and gets no rule — told apart by what it contains. */
.page__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin: 0 0 16px;
}

.page > .page__links:has(> .filter-chip) {
  box-sizing: border-box;
  min-height: 40px;
  /* The list starts directly under the filter row's hairline — its own
     column heads are the gap. With a 16px margin here the eleven-row
     check (§8.7) missed by 1px at 812pt; measured, not guessed. */
  margin-bottom: 0;
  justify-content: flex-end;
  border-bottom: 1px solid var(--hair);
}

@media (min-width: 1024px) {
  .page > .page__links:has(> .filter-chip) {
    justify-content: flex-start;
  }

  .page__links .filter-chip:first-child {
    margin-left: auto;
  }
}

/* The Expenses/Deposits/Tasks/Users filter cluster: the scope <select>
   (a native control at --control height — its treatment is in the forms
   section) beside the boolean filter. Right-aligned by default; Tasks
   keeps its cluster at the left (--start). */

/* --- Ledger table (SPEC-01 §5.9) ------------------------------------------- */
/* One shared block for every list in the app once its screen spec
   converts it: a real <table class="ledger"> with a <colgroup>. Row types
   by class on <tr>: .ch column heads, .sh section mark, entry (no class),
   .tot subtotal (desktop only), .gt final total. Cells: .entry subject,
   .ann annotation (its own column on desktop; a second line inside the
   subject cell on mobile), .n .fig money, .lbl the right-aligned label
   beside a total, .th a thumbnail cell. The money column's red rule runs
   down every .n cell from the column heads to the double rule, unbroken.
   Because the view can't know the viewport, a list renders twice —
   .ledger-desktop and .ledger-mobile — and the wrappers toggle at 1024px.
   Row links: SPEC-02 decides the mechanism; .ledger tr.link is the hook
   for the hover (--stock-dark, no underline). */
.ledger {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

/* The shared cell declarations at ZERO specificity — :where() — so that
   any class on a cell wins by default: .sc, .fig, .entry, .ann and .red
   apply to a ledger cell exactly as they apply anywhere else, and no
   cell-level twin is needed to beat a `.ledger td` selector. That
   selector, at (0,1,1), used to outrank the bare roles and silently
   rendered desktop figures in Archivo 13px (SPEC-03 Decisions); the
   twins that covered for it are gone. Row-level rules (tr.ch td, tr.sh
   td:first-child, tr.tot/tr.gt) still set the heights, head sizes and
   rules on purpose, and the ledger-specific cell roles (.n, .th, .lbl)
   keep their own rules. */
:where(.ledger td) {
  height: var(--row);
  padding: 0 0 0 14px;
  border-bottom: 1px solid var(--hair);
  vertical-align: middle;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--sans);
  font-size: 14.5px;    /* the entry's size (SPEC-16: was 13) */
  color: var(--ink);
}

:where(.ledger td:first-child) {
  padding-left: 0;
}

.ledger td.n {
  text-align: right;
  padding: 0 6px 0 16px;
  border-left: 1px solid var(--red-rule);
}

.ledger td.th {
  padding: 0;
}

.ledger td.th img,
.ledger td.th .thumb {
  display: block;
  width: 48px;
  height: 36px;
  object-fit: cover;
  background: var(--hair);
}

.ledger td.lbl {
  text-align: right;
  padding-right: 14px;
  font-family: var(--serif);
  font-size: var(--sc-s);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
}

.ledger tr.ch td {
  height: var(--row-head);
  font-size: var(--sc-s);
  color: var(--sage);
}

.ledger tr.sh td {
  height: var(--row-section);
  padding-top: 5px;
}

/* The mark's label cell — by its .sc, not :first-child, because a
   thumbnail ledger's first cell is the empty thumbnail column (verified:
   "Active"/"Archived" rendered at 13px until this changed). */
.ledger tr.sh td.sc {
  font-size: var(--sc-l);
  font-weight: 500;
  color: var(--ink);
}

/* A ledger whose entries carry a thumbnail (SPEC-01 §5.10, SPEC-04 §2):
   entry rows at --row-thumb (46px desktop; 56px mobile, the same as
   --row there). Heads, marks and totals keep their own heights. */
.ledger--thumb tr.link td {
  height: var(--row-thumb);
}

.ledger tr.tot td {
  height: var(--row-sum);
  border-bottom: 0;
}

.ledger tr.tot td.n {
  border-top: 1px solid var(--ink);
}

.ledger tr.gt td {
  height: var(--row-final);
  border-top: 2px solid var(--ink);
  border-bottom: 0;
}

.ledger tr.gt td.n {
  border-bottom: 3px double var(--ink);
}

.ledger tr.link:hover td {
  background: var(--stock-dark);
}

/* SPEC-16 §2, the phone's affordance for a row that opens something: the
   ledger's own mark for "this continues" — › in the serif, 20px, sage,
   centred — in an 18px gutter after the money column. Every row of a
   ledger that can link carries the cell (heads, marks and totals too) so
   the money column's right edge stays aligned; the mark is drawn here, on
   tr.link only, so a row that links conditionally (a payment when
   can_write) carries it exactly when it links, and a template never
   decides. Desktop draws no gutter and no mark — the cell is hidden and
   its column zero, so the money column's edge is where it was; hover does
   that work there. The mark is not a link of its own; the row is. */
.ledger td.gut {
  width: 18px;
  padding: 0;
  text-align: right;
  font-family: var(--serif);
  font-size: 20px;
  line-height: 1;
  color: var(--sage);
}

.ledger tr.link td.gut::before {
  content: "\203A";
}

@media (min-width: 1024px) {
  .ledger td.gut {
    display: none;
  }

  .ledger col.gut {
    width: 0;
  }
}

@media (max-width: 1023px) {
  .ledger col.gut {
    width: 18px;
  }

  /* The Properties list at 375 (SPEC-16 Decisions): with the gutter in,
     the subject cell's text space is what a 24-character address needs —
     the thumbnail column 60 (a 4px gap after the 56px thumbnail), the
     money column 78 (the widest figure is 66.34 in its 12px of padding),
     the subject's left padding 10 instead of the ledger's 14: 177px of
     text space at 375. */
  .ledger--thumb td.th + td {
    padding-left: 10px;
  }

  /* Pressed (SPEC-16 §2): the same --stock-dark the desktop hover paints.
     iOS needs a touchstart listener somewhere above the row for :active
     to match a non-anchor element; the row-link script adds a no-op. */
  .ledger tr.link:active td {
    background: var(--stock-dark);
  }
}

/* An archived entry (SPEC-04 §2): the whole row in sage, its figure
   blank. Row-level on purpose — it outranks the roles' own colors. */
.ledger tr.muted td,
.ledger tr.muted .entry,
.ledger tr.muted .ann,
.ledger tr.muted .ledger__link {
  color: var(--sage);
}

/* The status dot inside an annotation (SPEC-04 §2): sits before the text
   on the text's baseline line. */
/* The Users register's Login cell: the dot and its word as small caps at
   --sc-s in sage (SPEC-05 §3). */
/* …and the lease reports list's Status cell (SPEC-06 §3): the app's
   word — Active, Month-to-month, Ended — the same way; inside a mobile
   annotation the word is a .sc span at the annotation's size. */
.ledger td.login,
.ledger td.status {
  font-size: var(--sc-s);
  color: var(--sage);
  white-space: nowrap;
}

.ledger .ann .status,
.ledger--particulars td.val .status {
  font-size: var(--sc-s);
  color: var(--sage);
}

/* A status word in a trailing cell (the expense report's Recipients,
   SPEC-06 §6): Sent in sage; Failed in ink at weight 500 — a failure is
   not red — with Resend beside it as a text action. */
.ledger td.acts .status {
  font-family: var(--serif);
  font-size: var(--sc-s);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
}

.ledger td.acts .status--ink {
  color: var(--ink);
  font-weight: 500;
}

.ledger td.acts .row-form {
  display: inline;
  margin: 0;
}

/* The mobile pattern for an entry that carries a figure and a status
   word (SPEC-06 Decisions): a two-line money cell, the figure over the
   word, centred in the 56pt row. The lease reports list's column is
   124px (the single-tenant line then fits: 205px in 219); the word may
   still break at a hyphen — "Month-to-month" measures 124px and the
   cell's padding leaves 112 — and two lines under the figure sit inside
   56pt with this leading (verified). */
.ledger td.n > .fig {
  display: block;
  line-height: 1.2;
}

.ledger td.n .status {
  display: block;
  margin-top: 1px;
  font-size: var(--sc-s);
  line-height: 1.1;
  color: var(--sage);
  white-space: normal;
}

/* The lease report's periods ledger (SPEC-06 §4; SPEC-11 §1–2): every
   money column carrying its rule, at the sheet's full width. Column
   widths live here, not on the <col> elements, so they can narrow at
   1024–1279 (Period to 140px, Received to 200px) before anything else
   gives. Period is 15% and Received 21% at 1280 (SPEC-16; the reference's
   17% and 23% before) and a money column is 128px at every width
   (SPEC-16; 120 before): the Adjustments head measures 98.27px of small
   caps at --sc-s 14.5 (91.5 at 13.5) and the cell pads 16 and 8, so 120
   cut it by 3.27px at 1024, 1077 and 1280 the day the scale moved, as
   SPEC-11 §1's 96px floor had cut it at the old size ("Adjustmen…"; a
   six-column table's surplus had widened every column past it before;
   the seventh column spent the surplus), and 116 still cut it by a
   sub-pixel at 1077 — caught in a screenshot, not by an integer
   scrollWidth check. The two shares gave up the 40px the five money
   columns took, so the seven columns still fit the 1008px sheet at 1280
   (1002.9) instead of scrolling by 35.
   Where the ledger still cannot fit the sheet, .ledger-scroll
   scrolls it horizontally INSIDE the sheet — a fixed-layout table's
   used width is the greater of its width property and the sum of its
   columns, so the table grows and the wrapper scrolls — with the Period
   column pinned at the left (position: sticky) so a reader always knows
   which month they are on. Nothing is ever clipped, and nothing above
   the ledger moves: the wrapper is the only scroller. The pinned cell
   paints flat --stock over what scrolls beneath it (the sheet's tonal
   drift is body's; a cell can't carry it) — and only while the ledger
   is scrolled: lease_report_detail.html's script marks the wrapper
   .ledger-scroll--start whenever scrollLeft is 0, which makes the cell
   transparent again, since flat stock over the drift reads a shade
   lighter at rest. Without script the cell stays opaque, the safe
   fallback. */
.ledger--report td.n {
  padding-right: 8px;
}

.ledger--report col.col-period {
  width: 15%;
}

.ledger--report col.col-received {
  width: 21%;
}

.ledger--report col.col-money {
  width: 128px;
}

@media (min-width: 1024px) and (max-width: 1279px) {
  .ledger--report col.col-period {
    width: 140px;
  }

  /* Received holds its longest realistic line un-ellipsized where the
     sheet is narrow — a fixed width, not a share of a table that can no
     longer fit; the deficit scrolls. The longest line is the year case,
     "Jan 4, 2026 · $2,100 · Check 1101", not §4's "Sep 28 · $1,100 ·
     Transfer": 184px cut it (measured), 200px holds it. */
  .ledger--report col.col-received {
    width: 200px;
  }
}

/* The Received cell (SPEC-11 §2–3): each receipt its own line, the
   cell's lines at 1.35 with 7px above and below. A one-line cell still
   measures --row (a table cell's height is a minimum: 7 + 17 + 7 is
   under 36, and under the mobile 56); a two- or three-line cell sets
   its own height from its lines and only that row grows. Each line
   keeps the ledger's nowrap-and-ellipsis on its own. On the phone the
   receipts follow the expected/paid line inside the Period cell, the
   same lines under the same rule. */
.ledger td.rcv {
  padding-top: 7px;
  padding-bottom: 7px;
  white-space: normal;
}

.ledger td.rcv .receipt {
  line-height: 1.35;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* SPEC-14 §4: a receipt's filename beneath an expense report's Detail
   annotation is a link in ink, underlined — the one underlined link in
   a ledger, because it leaves the app for a file (fixes-05) and the sage
   line around it is not a link. A receipt LINK line takes a 24px line
   and the anchor is an inline block of that height, so each link is a
   24pt target — the inline minimum, and the one exception to the 44pt
   rule, for links set inside a row: stacked 44pt targets would spread
   the row past what a statement can bear (SPEC-14 Decisions). The lease
   and period reports' receipt lines share .receipt but are not links
   and keep 1.35. */
.ledger td.rcv .receipt--link {
  line-height: 24px;
}

.ledger .receipt .receipt-link {
  display: inline-block;
  max-width: 100%;
  line-height: 24px;
  vertical-align: top;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

.ledger-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

.ledger-scroll .ledger td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background-color: var(--stock);
}

.ledger-scroll--start .ledger td:first-child {
  background-color: transparent;
}

.ledger .ann .status-dot,
.ledger td.login .status-dot {
  margin-right: 7px;
  vertical-align: 1px;
}

/* A mobile thumbnail column wider than its image leaves the image at the
   cell's left edge. */
.ledger td.th img,
.ledger td.th .thumb {
  margin: 0;
}

/* A subject cell holding both lines on mobile: each line truncates on its
   own (a block child doesn't inherit the cell's text-overflow). */
.ledger td > .entry,
.ledger td > .ann {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ledger td > .ann {
  margin-top: 3px;
}

.ledger-mobile {
  display: none;
}

@media (max-width: 1023px) {
  .ledger-desktop {
    display: none;
  }

  .ledger-mobile {
    display: block;
  }

  :where(.ledger td) {
    font-size: 15px;    /* the phone entry's size (SPEC-16: was 14) */
  }

  .ledger td.n {
    padding: 0 0 0 12px;
  }

  .ledger td.th img,
  .ledger td.th .thumb {
    width: 56px;
    height: 42px;
  }
}

/* --- Rows (SPEC-01 §4, §6) -------------------------------------------------- */
/* Every list that hasn't yet become a ledger table keeps its markup and
   takes the rule grammar instead of its old card: no background, border,
   or radius on the item; a hairline between entries; the subject as an
   .entry (sans 500), the second line as an .ann (sans, sage), the amount
   as a .fig (serif 500, tabular lining numerals, ink — red on a figure
   owed arrives with SPEC-02's rule, Decisions). Rows keep min-height
   --row so nothing on mobile drops under 44pt once the card padding is
   gone. A row that links hovers --stock-dark; one rendered with no href
   (read-only) doesn't. */
.rent-list,
.record-list,
.document-list,
.today__row-list {
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
  display: block;
}

.rent-row,
.record-row,
.document-row,
.today__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-height: var(--row);
  padding: 4px 0;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--hair);
  border-radius: 0;
  color: var(--ink);
  text-decoration: none;
}

.rent-row[href]:hover {
  background: var(--stock-dark);
}

.rent-row:not([href]) {
  cursor: default;
}

/* Sign out on the More screen is a <button> wearing the row: reset only
   the browser's box chrome. */
.rent-row--button {
  width: 100%;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.rent-row__lease,
.today__row-main,
.document-row__text,
.record-row__name {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  flex: 1 1 auto;
  text-decoration: none;
  color: inherit;
}

.rent-row__address,
.today__row-address,
.document-row__title,
.record-row__name {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 14.5px;    /* the entry's size (SPEC-16: was 13) */
  line-height: 1.3;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.rent-row__tenants,
.today__row-caption,
.document-row__meta,
.rent-detail__tenants {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 13.5px;    /* the annotation's size (SPEC-16: was 12.5) */
  line-height: 1.3;
  color: var(--sage);
  overflow-wrap: anywhere;
}

.rent-row__figure {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
}

.rent-row__amount,
.record-row__amount {
  flex: none;
  font-family: var(--serif);
  font-weight: 500;
  font-size: 17px;      /* the figure's size (SPEC-16: was 15.5) */
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--ink);
  text-align: right;
}

@media (max-width: 1023px) {
  .rent-row__address,
.today__row-address,
.document-row__title,
.record-row__name {
    font-size: 15px;    /* SPEC-16: was 14 */
  }

  .rent-row__tenants,
.today__row-caption,
.document-row__meta,
.rent-detail__tenants {
    font-size: 13px;    /* was 12.5 */
  }

  .rent-row__amount,
.record-row__amount {
    font-size: 17px;    /* was 16 */
  }
}

/* Hub rows (More, Settings, Reports) are navigation — the mobile
   continuation of the rail's bottom group — so they take its voice:
   small caps at --sc-l, no icon, no chevron. */
.rent-row--nav .rent-row__address {
  font-family: var(--serif);
  font-weight: 400;
  font-size: var(--sc-l);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--ink);
}

/* Today's three tiers, by color only: Unpaid ink (no red until SPEC-02's
   rule), the default ink, Paid in sage. */
.today__row--quiet .today__row-address {
  color: var(--sage);
}

.rent-detail__tenants {
  margin: 0 0 16px;
}

/* Tasks: two tap targets in one row (the checkbox completes in place, the
   body opens the edit form). The checkbox is a real <button> — the 44pt
   hit area is the button; the 15px box (§5.12) is drawn inside it. */
/* --- Tasks register (SPEC-05 §2) ------------------------------------------- */
/* A leading control column, as the Properties list's thumbnail column:
   44px, no padding, holding the completion form. The button is the
   §5.12 box — 15px, edge on paper; cypress with the limewash check when
   done — inside a 44pt hit area. The row stays at --row: the button's
   negative margins pull its 44px back to the row's own height, the way
   .ledger__link's hit box does on mobile. */
.ledger td.ctl {
  width: 44px;
  padding: 0;
  overflow: visible;
}

.ledger td.ctl form {
  display: flex;
  margin: 0;
}

.task-check {
  position: relative;
  display: block;
  width: 44px;
  height: 44px;
  margin: -4px 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  cursor: pointer;
}

.task-check::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 15px;
  height: 15px;
  margin: -7.5px 0 0 -7.5px;
  box-sizing: border-box;
  border: 1px solid var(--edge);
  background-color: var(--paper);
}

.task-check--done::before {
  border-color: var(--cypress);
  background-color: var(--cypress);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.5l2.5 2.5 4.5-5' fill='none' stroke='%23F4F1E8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px;
}

.task-check:focus-visible {
  outline: 2px solid var(--cypress);
  outline-offset: -6px;
}

/* The trailing cell: a count on a mark, blank on an entry. A figure but
   not money — right-aligned, and NO money-column rule (§1). */
.ledger td.trail {
  text-align: right;
  padding: 0 6px 0 16px;
}

/* The desktop filter form holds three controls: the scope select, Mine
   and Show completed. On a phone the row may wrap to a second 44pt line. */
#task-filter {
  flex-wrap: wrap;
  row-gap: 0;
}

/* Documents: the row is a plain <li>; the open link and the download
   control are siblings. Icon slot at the thumbnail's own size (§5.10):
   48×36, square corners; a PDF shows a file glyph on a --hair rectangle;
   an image shows itself. */
.document-row__link {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  flex: 1 1 auto;
  text-decoration: none;
  color: inherit;
}

.document-row__icon-slot {
  flex: none;
  width: 48px;
  height: 36px;
  overflow: hidden;
  border-radius: 0;
  background: var(--hair);
}

.document-row__icon-slot--file {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
}

.document-row__icon-slot--file svg {
  width: 18px;
  height: 18px;
}

.document-row__icon-slot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.document-row__download {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: var(--sage);
  border-radius: 0;
  text-decoration: none;
}

.document-row__download:hover {
  color: var(--ink);
}

.document-row__download svg {
  width: 18px;
  height: 18px;
}

.document-row__actions {
  flex: none;
  display: flex;
  gap: 16px;
}

@media (max-width: 1023px) {
  .document-row__icon-slot {
    width: 56px;
    height: 42px;
  }
}

/* Accounts: the type in small caps (scanned first), the provider as the
   entry, the account beneath; the phone/globe controls are 44pt targets
   with no fill. */

/* --- Sections, detail lists, lease groups ----------------------------------- */
/* A section mark (§5.9 .sh, on a page): small caps at --sc-l, weight 500,
   ink. A detail list is label/value pairs on hairlines: the label is
   printed (small caps, sage), the value is written (sans, ink). */
.section {
  margin-top: 28px;
}

.section__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 8px;
}

.section__header .section__title {
  margin-bottom: 0;
}

.section__header .page__links {
  margin: 0;
}

.section__title {
  margin: 0 0 8px;
  font-family: var(--serif);
  font-size: var(--sc-l);
  font-weight: 500;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  text-transform: none;
  color: var(--ink);
}

.detail-list {
  margin: 0 0 20px;
}

.detail-list__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  min-height: var(--row);
  padding: 6px 0;
  border-bottom: 1px solid var(--hair);
  font-family: var(--sans);
  font-size: 14.5px;    /* the entry's size (SPEC-16: was 13) */
  color: var(--ink);
}

.detail-list__row dt {
  flex: none;
  font-family: var(--serif);
  font-size: var(--sc-m);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
}

.detail-list__row dd {
  margin: 0;
  min-width: 0;
  text-align: right;
  overflow-wrap: anywhere;
}

@media (max-width: 1023px) {
  .detail-list__row {
    font-size: 15px;    /* SPEC-16: was 14 */
  }
}

/* A lease on the property page: a group of detail rows, no box. Groups in
   the past-leases list are separated by space, not a border. */

/* --- Property detail ---------------------------------------------------------- */

/* Supporting information, not a hero: 4:3, a hairline border, square
   corners; the no-photo placeholder is the same slot with the street
   number in the serif on cypress. */

/* The Address row opens the map app (ADDRESS_AND_NAV_SPEC.md): a bordered
   block in the input treatment — --paper, --edge, square — with the pin
   column at its right. */

/* --- Property grid (interim until the Properties screen spec) --------------- */
/* The card grid keeps its shape but loses the card: no background, border
   or radius; square photo; the address as an entry, the status and tenant
   as annotations. The list itself becomes a thumbnail ledger with its
   screen spec. */

/* --- Money: sub-nav tabs and the period title (SPEC-01 §5.5-5.7) ------------ */
/* Rent / Expenses / Deposits: small caps at --sc-l, 36px tall (SPEC-16;
   32 before; 44pt mobile), inactive sage, active ink with a 2px ink rule,
   a hairline under the row. Sits 4px higher than the sheet's 18px desktop
   top so the sheet reads as 14px with a sub-nav (§5.4). */
.money-tabs {
  display: flex;
  gap: 26px;
  height: 36px;
  margin: 0 0 4px;
  border-bottom: 1px solid var(--hair);
}

.money-tabs__tab {
  display: flex;
  align-items: flex-end;
  padding: 0 0 7px;
  margin-bottom: -1px;
  font-family: var(--serif);
  font-size: var(--sc-l);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.money-tabs__tab:hover {
  color: var(--ink);
}

.money-tabs__tab--active {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

@media (min-width: 1024px) {
  .money-tabs {
    margin-top: -4px;
  }
}

@media (max-width: 1023px) {
  .money-tabs {
    gap: 24px;
    height: 44px;
  }

  .money-tabs__tab {
    align-items: center;
    padding: 0;
  }
}

/* The period title block (SPEC-02 §1-2, SPEC-01 §5.6-5.7): the month as
   the italic h1 at the left, the month cross-references (`‹ August`,
   `October ›`) at the right in the same small-caps voice as the back
   link, the rule beneath both (drawn by .rent-month::after — see the
   title rules above). Desktop `22px 0 8px` under the sub-nav; mobile a
   48pt row with 44pt-tall cross-references. */
.rent-month {
  padding: 22px 0 0;
}

.rent-month .page__title {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
}

.rent-month__refs {
  display: flex;
  align-items: center;
  gap: 22px;
  flex: none;
  align-self: center;
}

.rent-month__ref {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--serif);
  font-size: var(--sc-m);
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--sage);
  text-decoration: none;
  white-space: nowrap;
}

.rent-month__ref:hover {
  color: var(--ink);
}

.rent-month::after {
  margin-top: 8px;
}

@media (min-width: 1024px) {
  .rent-month__ref {
    min-height: 0;
  }
}

@media (max-width: 1023px) {
  .rent-month {
    min-height: 48px;
    padding: 0;
    /* A 48pt row with the 24px title and the 44pt cross-references both
       centered in it — baseline alignment (right for the desktop block)
       parked the title at the top of the row here. */
    align-items: center;
  }

  .rent-month__refs {
    gap: 16px;
  }

  .rent-month::after {
    margin-top: 0;
  }
}

/* Ledger entries that link (SPEC-02 Decisions): the subject's <a> is the
   real link — inherits the entry's type, no underline, takes the app's
   focus ring — and the row itself is the hover/click target. */
.ledger tr.link {
  cursor: pointer;
}

.ledger .ledger__link {
  color: inherit;
  text-decoration: none;
}

/* On mobile the anchor itself is a 44pt target, not only the row it sits
   in: vertical padding pulled back by an equal negative margin grows its
   hit box (15px of text becomes 45px) without moving anything. */
@media (max-width: 1023px) {
  .ledger .ledger__link {
    display: inline-block;
    padding: 15px 0;
    margin: -15px 0;
  }
}

/* A figure quoted inside an annotation ("paid $1,000 of $2,100", "$20
   overpaid") stays tabular but at the annotation's size (13.5 desktop, 13
   phone — SPEC-16; 12.5 before). */
.ledger .ann .fig {
  display: inline;
  font-size: 13.5px;
  text-align: inherit;
}

@media (max-width: 1023px) {
  .ledger .ann .fig {
    font-size: 13px;
  }
}

/* --- Property detail (SPEC-04 §3) -------------------------------------------- */
/* The actions row under the title rule: right-aligned on desktop, left at
   44pt on a phone. The foot holds the one irreversible action, 24px below
   the last section, right-aligned. */
.actions-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 12px;
  margin: 0 0 20px;
}

.sheet-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 24px;
}

/* Particulars: the full photo at 240×180 (square, hair when missing)
   beside a two-column ledger capped at 560px with no money-column rule.
   Labels small caps at --sc-s in sage; values sans in ink, right-aligned;
   an owner share's percentage is a figure. */
.particulars {
  display: flex;
  align-items: flex-start;
  gap: 24px;
  margin: 0 0 8px;
}

.particulars__photo {
  flex: none;
  width: 240px;
  height: 180px;
  overflow: hidden;
  background: var(--hair);
}

.particulars__photo img,
.particulars__photo .thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--hair);
}

.ledger--particulars {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 560px;
}

.ledger--particulars td {
  white-space: normal;
  height: auto;
  min-height: var(--row);
  padding: 8px 0 8px 14px;
  vertical-align: top;
}

.ledger--particulars td.particulars__label {
  padding-left: 0;
  font-size: var(--sc-s);
  color: var(--sage);
}

.ledger--particulars td.val {
  text-align: right;
  font-family: var(--sans);
  font-size: 14.5px;    /* SPEC-16: was 13 */
  line-height: 1.4;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.particulars__share {
  display: inline-block;
  min-width: 72px;
  text-align: right;
}

.particulars__map {
  margin-left: 0;
}

/* A mark row that carries a control where a subtotal would sit (Past
   leases' and Accounts' Show archived): the checkbox label at --sc-m,
   right-aligned, 44pt on a phone, no money-column rule. */
/* A mark row that carries a filter (Past leases, Accounts): one cell
   across both columns, the label left and the checkbox right, so the
   44pt control takes its width from the row, never from the money
   column — where it overflowed the sheet by 12px at 375 (verified). */
.ledger td.mark-control {
  padding-right: 0;
}

.mark-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.ledger td.mark-control .filter-check {
  min-height: 0;
  font-weight: 400;
}

.ledger td.mark-control .mark-form {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* A ledger with no money column (Accounts, the Tasks mark): entries
   carry their per-row controls — Call and Website, text actions, 44pt on
   a phone — in a trailing cell instead. No pictograms: SPEC-01 §5.1. */
.ledger--plain td.acts {
  text-align: right;
  white-space: nowrap;
}

.ledger--plain td.acts .row-action {
  display: inline-flex;
  align-items: center;
  min-height: var(--button);
  padding: 0 8px;
  margin-left: 4px;
}

/* A child of Settings on a phone (SPEC-05 §4): the top bar carries the
   back link AND the page's title — a gap between them, or they run
   together (verified: "‹ SettingsUtility providers"). */
.mobile-topbar__back + .mobile-topbar__title {
  margin-left: 14px;
}

@media (max-width: 1023px) {
  /* Call and Website side by side in the providers register's 168px
     trailing cell: the pair measured 158px at desktop spacing and the
     second action ellipsized. */
  .ledger--plain td.acts .row-action {
    padding: 0 6px;
    margin-left: 2px;
  }
}

@media (max-width: 1023px) {
  .actions-row {
    justify-content: flex-start;
  }

  .particulars {
    flex-direction: column;
    gap: 16px;
  }

  .particulars__photo {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
  }

  .ledger--particulars {
    max-width: none;
  }

  /* SPEC-16: the label at 15.5 (it read --sc-s, 13.5, on the phone
     before — the spec's "was 15" was never so) and the value at 14.5, both
     as the reference draws them. */
  .ledger--particulars td.particulars__label {
    font-size: 15.5px;
  }

  .ledger--particulars td.val {
    font-size: 14.5px;
  }

  .ledger td.mark-control .filter-check {
    min-height: 44px;
  }
}

/* --- Period detail (SPEC-02 §4) --------------------------------------------- */
/* One single-column layout for both widths, 560px on desktop. The back
   link sits in the mobile top bar (topbar_left) and on the sheet only
   from 1024px. The summary is a three-row ledger with no heads: printed
   labels in small caps, the Balance row as the final total. */
.mobile-topbar__back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--serif);
  font-size: 18px;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  color: var(--limewash);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.back-link--sheet {
  display: none;
}

.rent-detail__tenants {
  margin: 6px 0 0;
  font-family: var(--sans);
  font-size: 13.5px;    /* the annotation's size (SPEC-16: was 13) */
  color: var(--sage);
}

.ledger--summary {
  margin-top: 6px;
}

/* A single-column ledger whose desktop entries carry the annotation on a
   second line (the period detail's sections, any future single-column
   ledger): --row-2 (48px) instead of --row's one-line 36px, so a two-line
   entry has a stated height rather than whatever its content adds up to.
   Mobile already sets --row-2 to the same 56px as --row. */
.ledger--two-line td {
  height: var(--row-2);
}

.ledger--summary td.sc {
  font-size: var(--sc-m);
  color: var(--sage);
}

.ledger--summary tr.gt td.sc {
  font-size: var(--sc-l);
  font-weight: 500;
  color: var(--ink);
}

.rent-detail__section {
  margin-top: 24px;
}

.rent-detail__action {
  margin-top: 12px;
}

.rent-detail__section .empty-state {
  margin: 10px 0 0;
}

@media (min-width: 1024px) {
  .page--detail {
    max-width: 560px;
  }

  .back-link--sheet {
    display: inline-flex;
  }
}

/* Tasks — TASKS_SPEC.md. The quick-add row: description grows, the
   optional property picker stays narrow beside it, both wrap onto their
   own line under the button on a narrow viewport rather than overflow.
   (Control treatment: forms section.) */
.task-quick-add {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 12px;
  margin: 16px 0 20px;
}

.task-quick-add .field--description {
  flex: 1 1 12rem;
  margin-bottom: 0;
}

.task-quick-add .field--property {
  flex: 0 0 180px;
  margin-bottom: 0;
}

.task-quick-add .button {
  height: var(--control);
}

@media (max-width: 1023px) {
  .task-quick-add {
    margin-top: 12px;
  }

  .task-quick-add .field--property {
    flex: 1 1 10rem;
  }
}

/* --- Forms (SPEC-01 §5.16) -------------------------------------------------- */
/* The label is printed on the form; the value is written into it. Labels
   — field labels and checkbox labels alike — are small caps at --sc-m in
   sage, 4px above the control. Inputs, selects and textareas are the
   written part: sans, 13px on desktop and 16px on mobile (below 16px iOS
   zooms the viewport on focus), on --paper inside a 1px --edge border,
   square, at --control height (34px / 44px). Field groups sit 1rem apart.
   Everything here is scoped to .has-tabbar: the door keeps its own .field
   rules above untouched (§7). */

.has-tabbar .field,
.has-tabbar .field-row {
  margin-bottom: 16px;
}

.has-tabbar .field-row .field,
.has-tabbar .picker-row .field {
  margin-bottom: 0;
}

.has-tabbar .field__label,
.has-tabbar .field__checkbox-label,
.has-tabbar .checkbox-list__item label {
  display: block;
  margin: 0 0 4px;
  font-family: var(--serif);
  font-size: var(--sc-m);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.09em;
  text-transform: none;
  color: var(--sage);
}

/* The written part. The door's base .field input rule supplies structure
   these override property by property — background, border, radius,
   padding, type, the hover/focus box-shadow ring (none here; the ring is
   the §5.11 outline below). `background-color`, not the shorthand: the
   select's chevron is a background-image on the same element. */
.has-tabbar .field input,
.has-tabbar .field select,
.has-tabbar .field textarea,
.has-tabbar .task-quick-add input,
.has-tabbar .task-quick-add select {
  width: 100%;
  height: var(--control);
  padding: 0 10px;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink);
  background-color: var(--paper);
  border: 1px solid var(--edge);
  border-radius: 0;
  box-shadow: none;
  transition: none;
}

.has-tabbar .field input:hover,
.has-tabbar .field select:hover,
.has-tabbar .field textarea:hover {
  border-color: var(--edge);
}

.has-tabbar .field input::placeholder,
.has-tabbar .field textarea::placeholder {
  color: var(--sage);
}

.has-tabbar .field textarea {
  height: auto;
  min-height: calc(3 * 1.4em + 16px);
  padding: 8px 10px;
  resize: vertical;
}

/* A native select with a 1px ink chevron drawn in place of the browser's
   own. The chevron is a background-image, so the group rule above must
   never set the `background` shorthand (it would reset repeat/position
   and tile this across the control). */
.has-tabbar .field select,
.has-tabbar .task-quick-add select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%231B2A22' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 12px;
}

/* Focus (§5.11): the same cypress ring every control gets. Replaces the
   soft box-shadow ring the door's base rules draw. */
.has-tabbar .field input:focus,
.has-tabbar .field select:focus,
.has-tabbar .field textarea:focus,
.has-tabbar .task-quick-add input:focus,
.has-tabbar .task-quick-add select:focus {
  outline: none;
  border-color: var(--edge);
  box-shadow: none;
}

.has-tabbar .field input:focus-visible,
.has-tabbar .field select:focus-visible,
.has-tabbar .field textarea:focus-visible,
.has-tabbar .task-quick-add input:focus-visible,
.has-tabbar .task-quick-add select:focus-visible,
.has-tabbar input[type="file"]:focus-visible {
  outline: 2px solid var(--cypress);
  outline-offset: 2px;
}

/* A field with an error: an ink border on the control and a sans 12.5px
   ink message beneath it. No red, no icon. The message is the sibling
   the templates already render (.field-error); the border comes from the
   field containing one. */
.has-tabbar .field:has(.field-error) input,
.has-tabbar .field:has(.field-error) select,
.has-tabbar .field:has(.field-error) textarea {
  border-color: var(--ink);
}

.has-tabbar .field-error {
  margin: 4px 0 0;
  font-family: var(--sans);
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink);
}

/* Read-only fields (Lease, Period on Record payment): --stock-dark fill,
   no border, sage text. */
.has-tabbar .field__static {
  display: flex;
  align-items: center;
  min-height: var(--control);
  margin: 0;
  padding: 0 10px;
  font-family: var(--sans);
  font-size: 13px;
  color: var(--sage);
  background: var(--stock-dark);
  border: 0;
  border-radius: 0;
}

/* Help text beneath a control is an annotation. */
.has-tabbar .field__hint {
  margin: 4px 0 0;
  font-family: var(--sans);
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--sage);
}

/* Checkboxes (§5.12): the native control, drawn — 15px, --edge on --paper;
   checked, cypress with a 1.5px limewash check. Still a real <input>, so
   nothing about submission or the no-JavaScript path changes. A checkbox
   label lays the box and its small-caps text on one 44pt line. */
.has-tabbar input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 15px;
  height: 15px;
  margin: 0;
  /* Restated: the generic .has-tabbar .field input rule above has the
     same specificity and gives every input 0 10px of padding, which on a
     15px border-box turns into a 22px-wide box (verified). */
  padding: 0;
  box-sizing: border-box;
  border: 1px solid var(--edge);
  border-radius: 0;
  background-color: var(--paper);
  cursor: pointer;
}

.has-tabbar input[type="checkbox"]:checked {
  border-color: var(--cypress);
  background-color: var(--cypress);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.5l2.5 2.5 4.5-5' fill='none' stroke='%23F4F1E8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px;
}

.has-tabbar input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--cypress);
  outline-offset: 2px;
}

.has-tabbar .field__checkbox-label,
.has-tabbar .checkbox-list__item label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  margin: 0;
  cursor: pointer;
}

.has-tabbar .field--checkbox {
  display: flex;
  align-items: center;
}

.has-tabbar .checkbox-list {
  display: flex;
  flex-direction: column;
}

.has-tabbar .checkbox-list__item {
  display: flex;
  align-items: center;
  min-height: 44px;
}

/* A checkbox that stands for a person (a recipient, a property to mute)
   carries an entry-style name: the value, not the label, is what's
   written — so those keep sans, ink. */
.has-tabbar .checkbox-list__item .picker-row__name,
.has-tabbar .checkbox-list__item .today__row-main {
  font-family: var(--sans);
  font-variant-caps: normal;
  letter-spacing: 0;
}

/* The photo control: the native file input, its button as a secondary
   button, the filename beside it an annotation. No box around the
   control itself. */
.has-tabbar input[type="file"] {
  display: block;
  width: 100%;
  padding: 0;
  font-family: var(--sans);
  font-size: 12.5px;
  color: var(--sage);
  background: none;
  border: 0;
}

.has-tabbar input[type="file"]::file-selector-button {
  height: var(--button);
  margin: 0 12px 0 0;
  padding: 0 16px;
  font-family: var(--serif);
  font-size: var(--sc-l);
  font-weight: 500;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.12em;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 0;
  box-shadow: inset 0 0 0 2px var(--paper), inset 0 0 0 3px var(--ink);
  cursor: pointer;
}

.has-tabbar input[type="file"]::file-selector-button:hover {
  background: var(--stock-dark);
  box-shadow: inset 0 0 0 2px var(--stock-dark), inset 0 0 0 3px var(--ink);
}

/* FORM_LAYOUT_SPEC.md: the preview sits beside the file input. */
.field__photo-row {
  display: flex;
  align-items: center;
  gap: 16px;
}

.field__photo-input {
  flex: 1 1 auto;
  min-width: 0;
}

.photo-preview {
  flex: none;
}

.photo-preview img {
  display: block;
  width: 96px;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--hair);
  border-radius: 0;
}

/* City/state/zip as one address line, sized to content. */
.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.field-row .field--city {
  flex: 2 1 10rem;
}

.field-row .field--state {
  flex: 0 1 3.25rem;
}

.field-row .field--zip {
  flex: 1 1 6rem;
}

/* Start date / End date on the lease form. Native <input type="date">
   carries its own intrinsic minimum content width that CSS can't shrink
   below, and that minimum varies by browser/OS (Chromium and Safari
   don't agree) — pairing the two side by side at a width chosen by
   guesswork risks overflow. Stacked below 1024px; side by side only at
   the rail breakpoint, where .page--form's full 560px is guaranteed
   (274px per field after the gap — clear of either engine's minimum).
   The Safari fix itself (-webkit-appearance: none BEFORE any width rule)
   is the global input[type="date"] rule near the top of this file. */
.field-row--dates {
  flex-direction: column;
  gap: 16px;
}

@media (min-width: 1024px) {
  .field-row--dates {
    flex-direction: row;
    gap: 12px;
  }

  .field-row--dates .field--date {
    flex: 1 1 0;
  }
}

@media (max-width: 1023px) {
  .has-tabbar .field input,
  .has-tabbar .field select,
  .has-tabbar .field textarea,
  .has-tabbar .task-quick-add input,
  .has-tabbar .task-quick-add select,
  .has-tabbar .field__static {
    font-size: 16px;
  }
}

/* --- Repeaters: ownership shares, pickers, deductions --------------------- */
/* No boxes. A share is a group of hairline rows with its percentage at
   the foot; shares are separated by a hairline. The remove control on any
   row is the destructive text action (built by picker.js's
   buildRemoveButton — "Remove" for a person or a deduction, "Remove
   share" for the share itself, so two controls in one group never read
   alike). .formset-row is a bare wrapper; the raw DELETE checkbox stays
   in the DOM, hidden by JS with the [hidden] attribute only. */
.formset-row {
  display: block;
}

.ownership-share {
  margin: 0;
  padding: 0 0 8px;
  border: 0;
  border-bottom: 1px solid var(--hair);
  border-radius: 0;
}

.ownership-share + .ownership-share {
  padding-top: 8px;
}

.ownership-share__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  margin-top: 4px;
  padding: 0;
  border-top: 0;
}

.ownership-share__footer .field--share {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 0;
}

.ownership-share__footer .field--share input {
  width: 72px;
  text-align: right;
}

.picker-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--row);
  padding: 2px 0;
}

/* The divider is toggled by JS from the visible order (hidden spare rows
   sit between visible ones in the DOM, so :last-child can't tell them
   apart) — see property_form.html/deposit_return.html's updateDividers. */
.picker-row--divider {
  border-bottom: 1px solid var(--hair);
}

[data-people-mount] .type-ahead,
#manager-type-ahead-mount .type-ahead,
#steward-type-ahead-mount .type-ahead,
#tenant-type-ahead-mount .type-ahead {
  margin-bottom: 4px;
}

.picker-row .field__checkbox-label {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
}

.picker-row .field--description {
  flex: 1 1 auto;
  min-width: 0;
}

.picker-row .field--amount {
  flex: 0 0 auto;
}

.picker-row .field--amount input {
  width: 96px;
  text-align: right;
}

.picker-row__percent {
  font-family: var(--sans);
  font-size: 13px;
  color: var(--sage);
}

.formset-row__delete {
  flex: none;
  white-space: nowrap;
}

/* A picked person's name is a value (an entry), not a label. */
.picker-row__name {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 14.5px;    /* the entry's size (SPEC-16: was 13) */
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 1023px) {
  .picker-row__name {
    font-size: 15px;    /* SPEC-16: was 14 */
  }
}

/* buildRemoveButton() renders `.picker-row__remove action-link
   action-link--danger` — the destructive text action's own rules apply;
   only the button reset is needed here. */
.picker-row__remove {
  flex: none;
  padding: 0;
  background: none;
  border: 0;
}

/* --- Type-ahead (owner/manager/tenant/provider pickers) ---------------- */

.type-ahead {
  position: relative;
}

/* The open list is the topmost interactive thing on the page — above the
   sticky form footer (15) and the nav chrome (20-25). A paper sheet with
   an --edge border, square, no shadow; the highlighted option is
   --stock-dark. */
.type-ahead__results {
  position: absolute;
  z-index: 30;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: 0;
  box-shadow: none;
}

/* Toggled by createTypeAhead() (static/js/picker.js) when there's no room
   below the field — opens upward instead. */
.type-ahead__results--above {
  top: auto;
  bottom: calc(100% + 2px);
}

.type-ahead__option {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: var(--row);
  justify-content: center;
  padding: 4px 10px;
  border-bottom: 1px solid var(--hair);
  border-radius: 0;
  cursor: pointer;
}

.type-ahead__option:last-child {
  border-bottom: 0;
}

.type-ahead__option--active {
  background: var(--stock-dark);
}

.type-ahead__name {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 13px;
  color: var(--ink);
}

.type-ahead__secondary,
.type-ahead__empty {
  font-family: var(--sans);
  font-size: 12.5px;
  color: var(--sage);
}

.type-ahead__empty {
  padding: 8px 10px;
}

/* --- Ownership allocation bar ------------------------------------------- */
/* One glance confirms the split adds up. Square, a hairline border, the
   segments in the palette's greens. */
.allocation-bar {
  display: flex;
  height: 24px;
  margin: 8px 0 0;
  overflow: hidden;
  border: 1px solid var(--hair);
  border-radius: 0;
}

.allocation-bar__segment {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 500;
  color: var(--limewash);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- Sticky footer ------------------------------------------------------- */
/* Save/Cancel stay reachable for the whole scroll of a long form: stock
   background, a hairline above, no shadow. Archive (the destructive text
   action) at the left; Cancel (secondary) and Save (primary) at the
   right, at --button height (44pt on mobile). On mobile it sits ABOVE
   the fixed tab bar — its height plus the safe-area inset — never behind
   it; on desktop, at the bottom of the viewport. */
.form-actions {
  position: sticky;
  bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom, 0px));
  z-index: 15;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 16px;
  padding: 12px 0;
  background: var(--stock);
  border-top: 1px solid var(--hair);
  box-shadow: none;
}

.form-actions .button {
  flex: none;
}

.form-actions .action-link--quiet,
.form-actions .action-link--danger {
  margin-left: 0;
  margin-right: auto;
}

/* --- App shell: the 1024px chrome breakpoint ---------------------------- */
/* Mobile is the design target; this is the adaptation, not the other way
   round. Below this, top banner + bottom tab bar. At and above it, a fixed
   left rail instead — a genuinely different layout, not a repositioned
   version of the mobile bars, so the swap is a hard cutover at one
   breakpoint rather than incremental adjustments across several. */

@media (min-width: 1024px) {
  .mobile-topbar,
  .bottom-tabbar,
  .action-button {
    display: none;
  }

  /* SPEC-01 §5.1: 200px, full height, `padding: 18px 0 14px` — the
     horizontal inset is on the items/rules themselves (20px), not the
     rail, so the margin mark and the rules measure from the rail's edge. */
  .rail {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--rail-width);
    padding: 18px 0 14px;
    background: var(--cypress);
    overflow-y: auto;
    z-index: 20;
  }

  .app-main {
    /* The base rule's `width: 100%` is a percentage of the containing
       block (body) — with a nonzero margin-left added on top of that
       (not subtracted from it, since width and margin are computed
       independently in the standard box model), .app-main overflowed
       the viewport by exactly --rail-width whenever max-width:1100px
       wasn't already capping it below the available space (i.e. any
       viewport under ~1340px — 1024px, right at this breakpoint,
       included). `width: auto` here lets the normal fill-available-
       space-then-cap-by-max-width algorithm account for the margins
       correctly instead. Found via CDP viewport verification while
       adding .field-row--dates' own 1024px breakpoint, pre-existing and
       unrelated to it — flagged and fixed rather than worked around. */
    width: auto;
    margin-left: var(--rail-width);
    margin-right: 0;
    margin-bottom: 0;
    /* SPEC-01 §5.4: `14px 36px 40px` with a sub-nav, 18px top without.
       18px for every page until pass 3 gives sub-nav pages their own
       treatment. */
    padding: 18px 36px 40px;
    /* The drift framed on the document (see the base rule): beside the
       rail the sheet is body's box less the rail's width, and body's
       height exactly (no bar, the top bar hidden), so the gradient is
       one rail wider and shifted left by it, at every desktop width. */
    background-size: calc(100% + var(--rail-width)) 100%;
    background-position: calc(-1 * var(--rail-width)) 0;
  }

  .form-actions {
    bottom: 0;
  }
}

/* --- Tenant portal (TENANT_PORTAL_SPEC.md) -------------------------------- */
/* Deliberately its own small header, not app_base.html's sidebar/mobile-
   topbar/bottom-tabbar/FAB chrome — a tenant gets no navigation at all,
   just a wordmark and a sign-out control (see templates/tenant_base.html).
   The body still carries .has-tabbar, so real app content underneath
   (.rent-row/.detail-list/.section, all reused as-is from the rest of the
   app) renders on the same light limewash surface as every other /app/
   page; only this header is new markup and CSS. */

.tenant-shell__header {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--topbar-height);
  padding: 0 1.5rem;
  background: var(--cypress);
  color: var(--limewash);
}

/* SPEC-01 §7: the tenant portal receives the tokens and type roles, nothing
   else — no rail, no tab bar, no margin marks. The wordmark takes the
   wordmark type role (serif 400, 19px); the header itself is unchanged. */
.tenant-shell__wordmark {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 19px;
  color: var(--limewash);
  white-space: nowrap;
}

.tenant-shell__actions {
  display: flex;
  align-items: center;
  gap: 20px;
}

/* SPEC-07 §1: Notifications and Sign out as text actions in limewash —
   small caps at --sc-l, no fill, no border — 44pt on a phone. */
.tenant-shell__action {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0;
  font-family: var(--serif);
  font-size: var(--sc-l);
  font-weight: 400;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.12em;
  color: var(--limewash);
  background: none;
  border: 0;
  border-radius: 0;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.tenant-shell__action:hover {
  text-decoration: underline;
  text-decoration-color: rgba(244, 241, 232, 0.5);
  text-underline-offset: 3px;
}

.tenant-shell__signout {
  margin: 0;
}

/* One sheet, one column: 560px on desktop, full width on a phone, on the
   stock with its drift (body.has-tabbar). */
.tenant-shell__main {
  max-width: 592px;
  margin: 0 auto;
  padding: 18px 16px 40px;
}

.page--tenant .title-row__ann {
  flex: 0 0 100%;
  margin-top: 4px;
}

/* An annotation row inside a ledger (the portal's "Considered late
   after…" line under This month's subtotal, ledger-14): its own height,
   no rule of its own, wrapping. */
.ledger tr.note td {
  height: auto;
  padding: 2px 0 10px;
  border-bottom: 0;
  white-space: normal;
}

/* A line beneath the sheet's final figure ("Nothing outstanding."). */
.page--tenant .sheet-note {
  margin: 8px 0 0;
}

/* The portal's document ledgers (ledger-14): a mark naming a lease by
   its property and dates spans both columns and wraps onto a second
   line where it must, since the dates are what tell two tenancies
   apart — the one mark in the app allowed to wrap. */
.ledger--documents tr.sh td {
  white-space: normal;
  line-height: 1.25;
  padding-top: 8px;
  padding-bottom: 6px;
}

.page--tenant .no-access__line {
  margin: 16px 0 0;
  max-width: 40em;
}

@media (max-width: 1023px) {
  /* 375 has to hold the wordmark and both actions on one line: the
     actions at --sc-m with the .sc tracking, 14px apart, 16px in from
     the edges (measured: 366px of 375; the wordmark and Sign out both
     wrapped at --sc-l). */
  .tenant-shell__header {
    padding: 0 16px;
  }

  .tenant-shell__actions {
    gap: 14px;
  }

  .tenant-shell__action {
    min-height: 44px;
    font-size: var(--sc-m);
    letter-spacing: 0.09em;
  }
}

@media (min-width: 1024px) {
  .tenant-shell__main {
    padding: 18px 16px 40px;
  }
}

/* --- Reports: lease report period grid (REPORTS_SPEC.md) ------------------ */
/* Six columns can't work at 390px, so below 1024px each period is a
   stacked block (month as a section mark, then detail rows); the real
   <table> renders only at 1024px+. Both are always in the DOM; the
   breakpoint toggles them. The grid takes the ledger's rule grammar:
   small-caps column heads on a hairline, serif tabular figures, a heavy
   rule closing the body, the double rule under each final figure. */

/* --- Page load ----------------------------------------------------------- */
/* One orchestrated moment, then stillness. */

