/* One stylesheet, matched to the Figma set in docs/designs.
 *
 * The design language, read off the frames rather than invented:
 *
 * - A **navy rail** of icons down the left, and a white top bar. Not a
 *   horizontal nav — the sale track adds seven more sections and a row of tabs
 *   would have run out of width at four.
 * - **Filled grey inputs with no border.** The field is the fill; a border as
 *   well is two lines saying the same thing.
 * - **Navy for structure, blue for action, amber for provenance, green for
 *   done.** Amber in particular is load-bearing: "Edited by collaborator on 10
 *   Mar" is how somebody sees what changed under them.
 * - **Generous radii and one soft shadow.** Cards are 16px; controls are 8px.
 */

:root {
  /* Declared so the browser does not invent a dark theme of its own. Chrome on
     Android auto-darkens any page that has not said which schemes it supports,
     and it does so by inverting colours it does not understand — which turned
     this product's navy-on-white into unreadable grey-on-black on a phone.
     When a real dark palette is designed, this becomes ``light dark``. */
  color-scheme: light;
  /* Structure. The rail and the wizard panel are the same family, which is what
     ties the two halves of the product together. The rail is the darkest — it
     frames the app — and the wizard panel sits one step lighter inside it. */
  --navy: #071e48;
  --navy-2: #122e5e;
  --navy-3: #1e3359;

  /* Action. Distinctly brighter than the structural navy, or a primary button
     disappears into the panel behind it. Note that the hover is LIGHTER, not
     darker: on a navy-framed page a darkening button reads as being disabled. */
  --accent: #1b3a8f;
  --accent-ink: #2247a8;
  --accent-lift: #3563c9;
  --accent-wash: #e6ecfa;
  --accent-tint: #f1f5fe;

  --ink: #12172b;
  --ink-2: #5a6373;
  --muted: #8b93a3;
  --line: #e4e7ec;
  /* A fill, not a line — it is the ground under a count chip and a table
     heading. The darker hairline is `--line-strong`. */
  --line-2: #eef0f4;
  --line-strong: #d5d9e2;
  --fill: #f4f5f7;
  --bg: #f3f4f7;
  --card: #ffffff;

  --ok: #1d8a4e;
  --ok-wash: #e3f5ea;
  /* Provenance. Not a warning — a note about who touched a field. */
  --amber: #d9932b;
  --amber-ink: #8a5a06;
  --amber-wash: #fdf3e0;
  --bad: #c0362c;
  --bad-ink: #a52a2e;
  --bad-wash: #fbeae8;

  --radius: 14px;
  --radius-sm: 9px;
  /* Cards carry a hairline and NO shadow. Every surface having its own soft
     halo is what made a page of them read as a pile rather than a layout;
     depth is spent only where something genuinely floats — a modal, a toast,
     the tab in front of its panel. */
  --shadow: 0 18px 48px rgba(10, 18, 40, .22);
  --shadow-sm: 0 10px 30px rgba(10, 18, 40, .14);

  --rail: 58px;
  --topbar: 64px;

  /* Controls. One height for anything you press, one for anything you type. */
  --control: 40px;
  --field-h: 44px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.5 Inter, "Segoe UI", ui-sans-serif, system-ui, -apple-system, Roboto,
        "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-ink); }

/* Numbers that sit under each other in a column line up. Money that jitters as
   it changes reads as unreliable, whatever the number says. */
.amt, .tile-n, .facts dd, .feed .when, .steps .pct { font-variant-numeric: tabular-nums; }

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

.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 22px;
  background: var(--card); border-bottom: 1px solid var(--line);
  padding: 0 22px 0 calc(var(--rail) + 22px); height: var(--topbar);
  position: sticky; top: 0; z-index: 20;
}
.brand { display: flex; align-items: center; gap: 8px; text-decoration: none; flex: none; }
/* The signed-out header holds the lockup and nothing else — there is no search
   to offer somebody with no session, and no account chip to draw. The rail is
   not there either, so it starts at the page edge rather than behind it. */
/* The signed-out header: the lockup at the left, the marketing links at the
   right, as the designs draw the strip. No rail behind it, so it starts at the
   page edge rather than 58px in. */
.topbar.signedout { padding-left: 22px; }
.signedout-links { display: flex; gap: 26px; align-items: center; }
.signedout-links a {
  color: var(--ink); text-decoration: none; font-size: 14px; font-weight: 500;
  /* A label is one thing. Without this "Contact Us" broke across two lines on a
     phone and the bar grew a row to hold half a word. */
  white-space: nowrap;
}
.signedout-links a:hover { color: var(--accent-ink); text-decoration: underline; }

/* The signed-out screens, as `full_login.svg` draws them: the illustration on
   the left, the form on the right.

   **`align-items: stretch`, and the two panels share every surface value.** It
   was `start`, on the reasoning that two sides holding different amounts should
   each be their own height — which is true of the content and wrong about the
   page. Two white cards of different heights, with different padding, sitting
   at different bottoms, read as two things that happen to be near each other
   rather than one screen. They are the same card now: same background, same
   border, same radius, same top and bottom. The one that has less in it centres
   what it has instead of leaving a hole at the bottom. */
/* ONE card with a line down it, not two cards side by side.

   Two panels of equal height, equal padding and equal everything still read as
   two containers that happen to line up — the eye counts edges, and there were
   four of them. `docs/designs/html/frames/02/02-001` draws it as a single card
   with a rule between the halves, which is the version that cannot be misread.
   (`full_login.svg` draws two; this is the older frame and the better answer to
   the question actually being asked.)

   `overflow: hidden` so the divider runs edge to edge inside the rounded
   corners rather than stopping short of them. */
.authsplit {
  /* 32rem. The form half is the one with a width requirement — the intro
     paragraph wrapped to three lines at 26rem and at 30rem, and the third line
     held four words. It also lands the two halves within about 20px of each
     other, which is the other thing that makes them read as one card. */
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 32rem);
  gap: 0; align-items: stretch; margin-top: 2vh;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
/* The halves carry padding and nothing else. Anything with an edge of its own
   puts a second boundary inside the one boundary. */
.authside, .authsplit .authcard {
  background: none; border: 0; border-radius: 0; box-shadow: none;
  padding: 36px 40px; margin: 0;
}
/* The line. On the form's side rather than the panel's so it sits against the
   grid track edge with no gap to fall into. */
.authsplit .authform { border-left: 1px solid var(--line); }
.authside {
  text-align: center;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
}
/* The gap between a label and the box it names. 18px is the spacing BETWEEN
   fields — on a label that sits above its own input it reads as a label
   floating between two inputs, belonging to neither. Scoped to these cards:
   the wizard's own forms are a separate decision. */
.authsplit .authcard .field > label { margin-bottom: 4px; }

/* The rest of the card, as the designs draw it: heading centred over the
   fields, one full-width action, then a rule with OR on it and the way to the
   other form. */
.authsplit .authcard > h1 { text-align: center; margin-bottom: 10px; }
.authsplit .authcard > .muted { text-align: center; }
.authcard button.wide, .authcard a.button.wide { width: 100%; justify-content: center; }
/* Frame 04-074: under the password, at its right edge, in the quiet weight of
   help text — the way out for somebody who needs it, not a second action. */
.authcard .forgot {
  display: block; text-align: right; margin-top: 6px; font-size: 12.5px; color: var(--muted);
}
.authcard form.stacked .field:last-of-type { margin-bottom: 18px; }

/* A rule with a word sitting on it. Two lines and a label rather than a border
   with a background-coloured span over it: the span approach needs to know what
   is behind it, and behind this is a card that is a different colour from the
   page. */
.orline {
  display: flex; align-items: center; gap: 12px;
  margin: 18px 0 12px; color: var(--muted); font-size: 12px;
}
.orline::before, .orline::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line);
}
.authcard .crosslink { text-align: center; margin-bottom: 0; }

/* Show the password. The designs put an eye in the box on every password field
   — it is the one control that lets somebody check what they typed before
   committing to it, and on a phone it is the difference between a typo caught
   and a reset email. */
.control.reveal { position: relative; display: block; }
.control.reveal input { padding-right: 42px; }
.control.reveal button {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  border: 0; background: none; padding: 6px; cursor: pointer; line-height: 0;
  color: var(--ink-2); border-radius: var(--radius-sm);
}
.control.reveal button:hover { color: var(--ink); background: var(--fill); }
.control.reveal button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Capped by HEIGHT as well as width: the artwork is square, and on a short
   laptop screen a 280px-wide illustration pushes the headline below the fold on
   the one screen whose job is to say what the product is. */
/* The illustration takes whatever height the panel has spare.

   It was a fixed 280px in a panel stretched to match the form beside it, which
   on the sign-up page left about 300px of white above it and nothing in it —
   the emptiness read as a mistake rather than as space. Growing into the gap is
   what makes the panel look drawn rather than padded.

   `display: block`, and it matters: an <img> is inline by default, so it shared
   a text line with the logo that used to sit above it and the two were centred
   together as one line of inline content. */
.authside .art-slot {
  flex: 1 1 auto; min-height: 0; display: grid; place-items: center; width: 100%;
}
.authside .authart {
  display: block; margin: 0 auto;
  /* `width`, not `max-width`. An <img> with `width: auto` renders at its
     intrinsic size and a `max-width` above that does nothing — the art stayed
     280px in a slot with room for more, and the first attempt at this changed
     nothing at all. `height: auto` with a `max-height` keeps the ratio when the
     slot is the shorter constraint. */
  width: min(360px, 100%); height: auto; max-height: 100%;
}
.authside h2 { font-size: 19px; margin: 22px 0 10px; }
.authside p { color: var(--muted); margin: 0; max-width: 42ch; margin-inline: auto; }
/* `.authform` is the grid child, not the card inside it, so stretching the grid
   stretched a transparent wrapper and left the card its own height — the two
   panels still ended at different places. A grid container passes it on. */
.authform { display: grid; }
/* Whichever panel holds less centres what it has, rather than leaving the space
   in a heap at the bottom. It cannot overflow: the row is already as tall as
   the taller of the two. */
.authsplit .authcard {
  display: flex; flex-direction: column; justify-content: center;
}
.authsplit .authcard > h1 { margin-top: 0; }
/* `form:has(+ .helper) { margin-bottom: 0 }` is right on a page where the helper
   explains the form it follows. Here the helper is a separate offer — "New here?
   Create an account" — and with no gap the button and the sentence read as one
   control. Scoped to these cards rather than changed globally: the rule earns
   its keep everywhere else. */
.authsplit .authcard form:has(+ .helper) { margin-bottom: 16px; }
/* Tighter than the product's own forms, on purpose. 18px between fields is
   right on a wizard step somebody works through with a document open beside
   them; on a five-field card it is 90px of air that pushes the button below the
   fold and leaves the panel next to it looking half-empty. The hints keep their
   own spacing — they are the part somebody actually reads. */
.authsplit .authcard .field { margin-bottom: 12px; }
.authsplit .authcard .hint { margin-top: 2px; }
.authsplit .authcard > .muted { margin-bottom: 18px; }

/* One column below the width where two would each be too narrow to hold a form.
   The illustration goes first in the source and stays first here: it is the
   thing that says whose page this is, and a form above it on a phone is a
   password box with no name on it. */
@media (max-width: 900px) {
  .authsplit { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the line runs across rather than down. */
  .authsplit .authform { border-left: 0; border-top: 1px solid var(--line); }
  .authside, .authsplit .authcard { padding: 24px; }
  /* Stacked, the panel is its own height and the art has no gap to grow into —
     so it is capped against the viewport instead, or it pushes the headline
     off a short phone screen. */
  .authside .art-slot { flex: 0 0 auto; }
  .authside .authart { max-height: 26vh; }
}
/* The logo is the artwork from the designs, at its own aspect ratio. Height is
   what is set, never width: the lockup is 149x38 and a width that assumed
   otherwise would squash the tagline into a line of grey. */
.brand-logo { display: block; height: 34px; width: auto; }
.brand-name { color: var(--navy); font-size: 15px; font-weight: 700; letter-spacing: .05em; }
.brand-name strong { color: var(--ok); font-weight: 700; }
/* The line under the wordmark in every frame. It says what the product is for
   to somebody who has just been sent a link and has never heard of it. */
.brand-tag {
  display: block; font-size: 8px; font-weight: 400; letter-spacing: .02em; color: var(--muted);
}

/* Shrinkable, and shrinking from the NAME. The chip was `flex: none`, so on a
   360px screen it kept the full width its email wanted and pushed the whole
   page 6px sideways. The name already ellipsises; it just needed permission to
   be narrower than its content. */
.topbar .who {
  display: flex; align-items: center; gap: 10px; font-size: 14px;
  flex: 0 1 auto; min-width: 0;
}
.topbar .who form { flex: none; }
/* The person, in a bordered chip. Loose in the bar the avatar, the name and
   the role read as three unrelated things at the right-hand end; the border
   makes them one statement about one person. Sign out stays OUTSIDE it — it
   is an action, not part of who you are. */
.topbar .who-chip {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  border: 1px solid var(--line); border-radius: 24px; padding: 4px 14px 4px 4px;
}
.topbar .who-chip:hover { border-color: var(--line-strong); }
/* Truncated at EVERY width, not only on a phone. An email address is one
   unbreakable word, and a header that wraps it mid-address to fit looks broken
   on a laptop too — the ellipsis says "there is more" where a mid-word wrap
   just looks like a mistake. */
.topbar .who-name {
  font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  flex: 0 1 auto; min-width: 0; max-width: min(40vw, 22rem);
}
.topbar .who .role {
  color: var(--ink-2); background: var(--line-2); border-radius: 999px;
  padding: 3px 10px; font-size: 11.5px; font-weight: 500;
}
.topbar .who form { margin: 0; }
.topbar .who button { height: 34px; padding: 0 14px; font-size: 13px; }

/* The rail. Icons with the label on hover, as the designs draw it. */
.nav {
  position: fixed; top: 0; left: 0; bottom: 0; width: var(--rail); z-index: 30;
  background: var(--navy); display: flex; flex-direction: column; align-items: center;
  gap: 4px; padding: 78px 0 20px;
}
.nav a, .nav > span {
  position: relative; width: 38px; height: 38px; border-radius: 9px;
  display: grid; place-items: center; color: #8fa3c9; text-decoration: none;
  transition: background .12s, color .12s;
}
.nav svg { width: 19px; height: 19px; }
.nav a:hover { background: rgba(255, 255, 255, .08); color: #fff; }
/* Blue, not white. A white chip on navy is a hole punched in the rail; the
   blue is the same one every primary action uses, so "where I am" and "what
   this product's action colour is" are one idea rather than two. */
.nav a.on { background: var(--accent); color: #fff; }
.nav span.off { color: #4a5b7d; cursor: not-allowed; }
.rail-gap { flex: 1; height: auto !important; width: auto !important; }

/* The label, on hover. A tooltip rather than a caption: eight labels stacked
   under eight icons is a wall of text nobody reads after the first day. */
.nav .tip {
  position: absolute; left: 46px; top: 50%; transform: translateY(-50%);
  background: #3f4756; color: #fff; font-size: 12px; font-weight: 500;
  padding: 7px 10px; border-radius: 5px; pointer-events: none;
  opacity: 0; transition: opacity .12s; z-index: 40;
  /* Wrapping, with a ceiling. `nowrap` made "Sale Agreement - Part of the sale
     track, not yet built" one line wider than the window, so the reason - the
     whole point of showing a disabled item - ran off the screen. */
  max-width: 15rem; white-space: normal; line-height: 1.35;
}
.nav a:hover .tip, .nav > span:hover .tip { opacity: 1; }

.page {
  max-width: 1240px; margin: 0 auto;
  padding: 30px 28px 90px calc(var(--rail) + 28px);
}
/* Was `max-width: 560px`, sized for a single narrow card. The signed-out pages
   are a two-column split now and 560px gave each side 260px. */
.page.bare { padding-left: 28px; max-width: 1040px; margin-top: 4vh; }
.page.narrow { max-width: 760px; }
@media (max-width: 700px) {
  .nav { flex-direction: row; inset: auto 0 0 0; width: auto; height: 60px; padding: 0 8px; }
  .page, .topbar { padding-left: 28px; }
  .page { padding-bottom: 90px; }
  .rail-gap { display: none; }
  .nav .tip { display: none; }
}

h1 { font-size: 26px; line-height: 1.25; margin: 0 0 6px; letter-spacing: -.01em; font-weight: 600; }
h2 { font-size: 18px; margin: 26px 0 12px; color: var(--ink); font-weight: 600; letter-spacing: -.01em; }
h3 { font-size: 15px; margin: 18px 0 8px; font-weight: 600; }
p { margin: 0 0 12px; }
.muted { color: var(--muted); }

.row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.between { justify-content: space-between; }
/* ``minmax(0, …)`` on both tracks. A grid item defaults to ``min-width: auto``,
   which means it refuses to shrink below its content — so a wide child kept the
   whole two-column layout at its own width and pushed the page sideways. */
.split {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 26px; align-items: start;
}
.split > * { min-width: 0; }
@media (max-width: 900px) { .split { grid-template-columns: minmax(0, 1fr); } }

/* ---------------------------------------------------------------- cards */

.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px 22px;
}
.card.narrow { max-width: 600px; }
.card + .card { margin-top: 18px; }

/* A panel INSIDE a card — the "Not sure about the details?" strip, the payment
   sub-sections. Filled rather than outlined, so nesting does not become a
   stack of competing borders. */
.panel {
  background: var(--fill); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 14px 16px; margin: 0 0 22px;
}
/* The same strip, waiting on somebody else. Blue rather than grey: it is a
   state, not a suggestion. */
.panel.waiting { background: var(--accent-tint); border-color: var(--accent-wash); }
.panel.row { gap: 16px; }
/* A control inside a panel. Every input is filled with `--fill`, and so is the
   panel, so the email box of "Only have their email?" had no edge at all — its
   placeholder floated as loose text. And a block, full-width input pushed its
   own button onto the next line. White with a hairline here.

   Words on the left, the box and its button on the right, the same in every
   panel so two of them one above the other line up. A grid rather than a
   wrapping row: when space runs short the WORDS wrap, instead of the form
   dropping under them in one panel and not the other — or, squeezed, the
   button dropping under its own box. When the CARD is too narrow for both —
   which happens on a mid-size screen beside the rail, not only on a phone —
   it is one column: words, then the box and button. Measured on the card,
   not the screen, because the card is what the panel has to fit in. */
.wizard > .card { container-type: inline-size; }
.panel.row:has(> form.row) {
  display: grid; grid-template-columns: minmax(min(12em, 100%), 1fr) minmax(0, auto);
  align-items: center;
}
.panel form.row { flex-wrap: nowrap; justify-content: flex-end; }
.panel form.row input, .panel form.row select {
  flex: 0 1 260px; width: auto; min-width: 120px; margin-top: 0;
  background-color: var(--card); border-color: var(--line-strong);
}
.panel form.row button { flex: none; }
@container (max-width: 720px) {
  .panel.row:has(> form.row) { grid-template-columns: minmax(0, 1fr); }
  .panel form.row { justify-content: flex-start; }
}
@container (max-width: 480px) {
  .panel form.row { flex-wrap: wrap; }
  .panel form.row input, .panel form.row select { flex: 1 1 100%; }
}

.messages { list-style: none; padding: 0; margin: 0 0 20px; }
.messages li {
  padding: 13px 18px; border-radius: 12px; background: var(--accent-wash);
  border: 1px solid #cfe0f8; margin-bottom: 8px; font-size: 14px;
}
.messages li.error { background: var(--bad-wash); border-color: #f6cfd0; color: var(--bad-ink); }
.error { color: var(--bad); }

/* `hidden` means hidden (#318). The browser hides a [hidden] element with a
   rule of its own, and any rule here that gives the element a `display` wins
   over it — so `input, select, textarea { display: block }` below made the
   attribute do nothing on every field in the product, and `.button`'s flex did
   the same to buttons. The letter editor's raw HTML box stayed on screen under
   the editor that replaced it, and Clear all showed with scripts off, a button
   that does nothing. Important, because it has to beat every display rule
   written after it, including ones nobody has written yet. */
[hidden] { display: none !important; }

/* ---------------------------------------------------------------- forms */

label { display: block; margin: 0 0 18px; font-size: 12.5px; color: var(--ink-2); font-weight: 400; }
/* The asterisk the designs put on every required label. */
.req-mark::after { content: "*"; color: var(--bad); font-weight: 700; }

input, select, textarea {
  display: block; width: 100%; height: var(--field-h); margin-top: 6px; padding: 0 13px;
  border: 1px solid transparent; border-radius: var(--radius-sm); font: inherit;
  color: var(--ink); background: var(--fill);
  transition: background .12s, border-color .12s, box-shadow .12s;
}
/* A textarea is the exception: it grows, so it cannot take the fixed height,
   and it needs the vertical padding a single-line control gets from centring. */
textarea { height: auto; padding: 12px 13px; line-height: 1.5; }
input::placeholder, textarea::placeholder { color: var(--muted); }
input:hover, select:hover, textarea:hover { border-color: var(--line-strong); }
input:focus, select:focus, textarea:focus {
  outline: none; background: var(--card); border-color: var(--accent-lift);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
input[readonly] { color: var(--ink-2); }
select {
  appearance: none; cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: calc(100% - 19px) 55%, calc(100% - 14px) 55%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 36px;
}
/* The blanket rule above is for text-like inputs. A checkbox is not one, and
   full-width is how it ends up floating in the middle of its own row.
   HEIGHT is the same mistake as width and was missed: --field-h is 44px, so
   the box stayed 44px tall with its tick centred in it, landing the tick a
   line and a half BELOW the label it belongs to. Anywhere a size was set by
   hand — `.fieldrules` — looked right, which is what hid it. */
input[type="checkbox"], input[type="radio"] {
  display: inline-block; flex: none; width: auto; height: auto;
  margin: 0 8px 0 0; vertical-align: middle;
  accent-color: var(--accent); background: none;
}

/* A secondary button is INK on white with a grey hairline, not blue on blue.
   When every button was blue, a row of four gave no clue which one the screen
   wanted; the primary now carries the only fill on the row. */
button, .button {
  font: inherit; font-weight: 500; font-size: 14px; border-radius: var(--radius-sm);
  height: var(--control); padding: 0 18px; cursor: pointer;
  border: 1px solid var(--line-strong);
  background: var(--card); text-decoration: none; color: var(--ink);
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  white-space: nowrap; line-height: 1;
  transition: background .12s, border-color .12s, color .12s, transform .04s;
}
button:hover, .button:hover { background: var(--fill); border-color: var(--muted); color: var(--ink); }
button:active, .button:active { transform: translateY(1px); }
button:focus-visible, .button:focus-visible {
  outline: 2px solid var(--accent-lift); outline-offset: 2px;
}
.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); color: #fff; }
/* Any button that cannot be pressed has to look it. Only the primary one did,
   so Publish on the field rules screen sat disabled and looked ready (#42). */
button:disabled {
  color: var(--muted); background: var(--card); border-color: var(--line); cursor: not-allowed;
}
.primary:disabled, .primary[disabled] {
  background: #c9d3e8; border-color: #c9d3e8; color: #fff; cursor: not-allowed;
}
/* A button that is drawn but cannot be pressed — the same greyed-with-a-reason
   convention the navigation rail uses, because a control that vanishes is a
   product somebody thinks is broken. It is a <span>, not a disabled <a>: an
   anchor without an href is already unclickable, and this makes it look it. */
.button.off, .primary.button.off {
  background: #c9d3e8; border-color: #c9d3e8; color: #fff; cursor: not-allowed;
}
/* The outlined variant, for a secondary action that is still the point of its
   own panel — "Ask collaborator to edit" beside its explanation. */
.button.outline, button.outline { border-color: var(--accent); color: var(--accent); }
.button.outline:hover, button.outline:hover { background: var(--accent-tint); border-color: var(--accent); }
.button.small, button.small { height: 34px; padding: 0 13px; font-size: 13px; }
/* "Clear all" in the designs is red TEXT, not a red button — it is an escape
   hatch, and giving it a button's weight would invite pressing it. */
.danger { color: var(--bad); border-color: transparent; background: none; }
.danger:hover { background: var(--bad-wash); color: var(--bad); border-color: transparent; }
/* A button that is only a label — no border, no fill — for an action beside
   others that must not compete with them. */
.ghost { border-color: transparent; background: none; color: var(--ink-2); }
.ghost:hover { background: var(--fill); border-color: transparent; }

/* No negative top margin. It tightened the gap under a heading by pulling the
   paragraph 8px INTO the box above it, which is a real overlap — the bottom of
   a control above a helper line was covered by it. The gap is closed from the
   other side instead, by the element that owns the space. */
.helper { font-size: 12.5px; line-height: 1.55; color: var(--muted); margin: 0 0 18px; }
h1:has(+ .helper), h2:has(+ .helper), .row:has(+ .helper) { margin-bottom: 6px; }
form:has(+ .helper) { margin-bottom: 0; }

/* The wizard's action bar, pinned to the bottom of the frame as the designs
   draw it. The next step is always in the same place, whatever the step. */
/* A real bar with a hairline above it, not a gradient fade.
   The fade was trying to hide the fact that content passes underneath; a
   frosted bar with a top border says so instead, which is legible at every
   scroll position rather than only when the form happens to end there. */
.actionbar {
  position: sticky; bottom: 0; display: flex; gap: 12px;
  align-items: center; padding: 14px 2px; margin-top: 20px;
  background: rgba(243, 244, 247, .92); border-top: 1px solid var(--line);
}
@supports (backdrop-filter: blur(6px)) {
  .actionbar { backdrop-filter: blur(6px); }
}
/* Everything after the spacer is pushed right. Back and Clear sit on the left
   where they are out of the way of the button somebody actually wants. */
.actionbar .grow { flex: 1; }
/* The left of the bar, and the reason it is not empty (#197). A grey strip the
   width of the card with a border and nothing in it reads as an input — which
   is exactly how it was reported. It takes the space `grow` used to, so the
   buttons still sit right, and it carries the sentence that belongs next to
   them: what this last changed, or what pressing the button will do. */
.actionbar .actionbar-note {
  flex: 1; font-size: 13px; color: var(--muted); line-height: 1.4;
}
.actionbar .saved { font-size: 12.5px; color: var(--muted); }

/* ---------------------------------------------------------------- tiles */

.tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin: 22px 0 8px; }
@media (max-width: 760px) { .tiles { grid-template-columns: repeat(2, 1fr); } }

/* The department overview's own panels (#214): the same two-up grid the staff
   console uses, so one organisation is drawn the way the platform is. */
.panel-note { font-size: 12.5px; margin: 10px 0 0; }
/* A panel that takes the whole row. Declared rather than derived from
   :nth-child so the console, which has an even number of panels, is not
   quietly restyled by a rule meant for this page. */
.panels .panel-wide { grid-column: 1 / -1; }
/* Two charts side by side are a matched PAIR, and the console's
   `align-items: start` is right for its four unlike panels and wrong here:
   one caption's worth of extra text put the left card's rule and its counts
   lower than the right card's, which reads as a misalignment rather than as
   two cards of honest different heights.

   Equal height, and the bottom row pushed down in both — which is what puts
   the two rules on one line whatever sits above them. */
/* `.panels.panels--paired`, not `.panels--paired`: the generic rule has the
   same specificity and sits LATER in this file, so a single class here loses
   on source order and the cards stay unequal. */
/* Signing status in the parties table (#422). Colour never carries the
   meaning on its own — every state says what it is in words, because a party
   whose turn has not come is not a problem and must not read as one. */
.sign { font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.sign--signed { color: #156c3b; }
.sign--open { color: #1b3a6b; }
.sign--waiting { color: var(--muted); }
.sign--declined { color: #9b2c2c; }

/* Occupancy (#410). Colour carries no meaning on its own — every state also
   says what it is in words, because a tenant who has not renewed is not an
   error and must not read as one. */
.let { font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.let--on { color: #156c3b; }
.let--soon { color: #1b3a6b; }
.let--past { color: #8a5a00; }
.let--warn { color: #9b2c2c; font-weight: 700; margin-left: 4px; }

.panels.panels--paired { align-items: stretch; }
.panels.panels--paired > .card { display: flex; flex-direction: column; }
.panels.panels--paired .tilerow { margin-top: auto; }
/* The small clickable counts under the process chart. The chart says the shape;
   these say the number and go somewhere a chart segment cannot. */
.tilerow { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 14px;
           padding-top: 12px; border-top: 1px solid var(--line, #e3e6ef); }
.tilesmall { font-size: 13px; color: var(--muted); text-decoration: none; }
.tilesmall strong { color: var(--ink, #1b2033); font-variant-numeric: tabular-nums; }
.tilesmall:hover { color: var(--accent); }

/* The department overview (#214). Three counts, so three columns rather than
   three-quarters of the four-column agreement strip above it. */
.department { margin: 22px 0 8px; }
.department .tiles { grid-template-columns: repeat(3, 1fr); margin: 0; }
@media (max-width: 760px) { .department .tiles { grid-template-columns: repeat(3, 1fr); } }
.tile {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px; text-decoration: none; color: var(--ink); display: block;
  transition: border-color .12s;
}
.tile:hover { border-color: var(--line-strong); color: var(--ink); }
.tile-n { display: block; font-size: 30px; font-weight: 700; letter-spacing: -.03em; line-height: 1.1; }
.tile-label { color: var(--muted); font-size: 13px; }

/* ---------------------------------------------------------------- tables */

table {
  width: 100%; border-collapse: separate; border-spacing: 0; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
th, td { text-align: left; padding: 14px 16px; border-bottom: 1px solid var(--line); font-size: 13.5px; }
/* Sentence case at readable weight, not small uppercase. A heading row set in
   letter-spaced caps shouts the one part of the table nobody needs to re-read. */
th {
  color: var(--ink-2); font-weight: 500; font-size: 12px;
  background: var(--fill); border-bottom-color: var(--line); white-space: nowrap;
}
th:first-child, td:first-child { padding-left: 22px; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: #fafbfd; }

/* A status carries a dot in its own colour. Six stages all set in the same
   pill shape are told apart only by reading them; the dot gives the row a
   mark the eye can group by while scanning a column of thirty. */
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: 999px; font-size: 12px;
  font-weight: 500; background: var(--line-2); color: var(--ink-2); white-space: nowrap;
}
.pill::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor; flex: none;
}
.pill.DRAFT, .pill.SECTIONS_PENDING { background: var(--line-2); color: var(--ink-2); }
.pill.AWAITING_CONFIRMATION, .pill.AWAITING_PAYMENT, .pill.AWAITING_SIGNATURE,
.pill.PAID, .pill.STAMPING { background: var(--amber-wash); color: var(--amber-ink); }
.pill.EXECUTED, .pill.CLOSED, .pill.STAMPED, .pill.CONFIRMED { background: var(--ok-wash); color: var(--ok); }
.pill.STAMP_FAILED, .pill.SIGNING_DECLINED, .pill.SIGNING_EXPIRED, .pill.ABANDONED {
  background: var(--bad-wash); color: var(--bad);
}


/* ---------------------------------------------------------------- progress */

.bar { height: 6px; background: var(--line); border-radius: 999px; overflow: hidden; margin: 10px 0 18px; }
.bar span { display: block; height: 100%; background: var(--accent); border-radius: 999px; transition: width .3s ease; }

/* ---------------------------------------------------------------- wizard */

.wizard { display: grid; grid-template-columns: 280px 1fr; gap: 32px; align-items: start; }
@media (max-width: 900px) { .wizard { grid-template-columns: 1fr; } }

/* The step panel is NAVY, as the designs draw it — the same navy as the rail,
   which is what ties the two halves of the product together. The active step
   is a white card inside it, so where you are reads at a glance from across a
   desk. */
.wizard aside {
  background: var(--navy-2); border-radius: var(--radius); padding: 0 0 8px; overflow: hidden;
}
.wizard aside .rail-head {
  color: #fff; font-weight: 650; font-size: 15px; padding: 18px 20px 14px;
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
}
.wizard aside .progress { padding: 0 20px 16px; }
.wizard aside .progress .muted { color: #93a4c4; font-size: 12px; }

.steps { list-style: none; padding: 0 12px 20px; margin: 0; }
.steps li { margin-bottom: 2px; position: relative; }
.steps a, .steps > li > span {
  display: flex; justify-content: space-between; gap: 8px; align-items: center;
  padding: 12px 14px; border-radius: 8px; text-decoration: none;
  color: #c3cfe4; font-size: 14px;
}
.steps a:hover { background: rgba(255, 255, 255, .07); color: #fff; }
.steps a.on { background: #fff; color: var(--navy); font-weight: 650; }
.steps span.locked { color: #55668a; }
.steps .pct { font-size: 11.5px; color: #93a4c4; }
.steps a.on .pct { color: var(--muted); }
.steps .done { color: var(--ok); font-weight: 700; }
.steps .tag { background: rgba(217, 147, 43, .18); color: #f0c07a; margin: 4px 0 0 14px; }

.bar { height: 6px; background: rgba(255, 255, 255, .16); border-radius: 999px; overflow: hidden; margin: 6px 0 0; }
.bar span { display: block; height: 100%; background: var(--ok); border-radius: 999px; transition: width .3s ease; }
/* Outside the navy panel the track needs a light ground instead. */
.card .bar, .page > .bar { background: var(--line); }
.card .bar span, .page > .bar span { background: var(--accent); }

/* One shape for every activity list: a round mark, what happened, and who and
   when under it.
 *
 * There were two — a three-column when/who/what grid for an agreement's own
 * history, and this one everywhere else. The grid read well across a full page
 * and not at all in the column that tab actually occupies: at 430px the name
 * clipped mid-address and every sentence wrapped to three lines. Two shapes
 * also meant two sets of responsive rules, and the narrow one was never the
 * one being looked at while they were written. */
.feed { list-style: none; padding: 0; margin: 0; }
/* A long record scrolls in place rather than pushing the rest of the page
   down or sending the reader to a second page and losing where they were.
   About ten entries tall, so what just happened is visible without scrolling
   and the history is one gesture away. */
.feed.scrollbox {
  max-height: 420px; overflow-y: auto; overscroll-behavior: contain;
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 0 12px;
}
.feed.scrollbox li:last-child { border-bottom: none; }
.feed li {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start; column-gap: 12px;
  padding: 14px 0; border-bottom: 1px solid var(--line); font-size: 13.5px;
}
.feed li:last-child { border-bottom: none; }
.feed .txt { min-width: 0; }
.feed .txt > strong, .feed .what {
  display: block; font-weight: 500; font-size: 13.5px; color: var(--ink);
  overflow-wrap: break-word;
}
/* Who and when on one line under it, separated by a dot. Two facts about the
   same entry; stacking them makes a three-line row out of a one-line event. */
.feed .meta { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
.feed .meta .who { overflow-wrap: break-word; }
.feed .meta .who:not(:empty) + .when::before { content: " · "; }
.feed .when { color: var(--muted); }
.feed .view { font-size: 13px; text-decoration: none; white-space: nowrap; }
.feed .view:hover { text-decoration: underline; }
.feed .changes, .feed .hint { margin: 6px 0 0; }
.feed .changes { list-style: none; padding: 0; }
.feed .changes li {
  display: block; border: none; padding: 2px 0; font-size: 12.5px; color: var(--ink-2);
  font-variant-numeric: tabular-nums; overflow-wrap: break-word;
}
/* The mark. Centred on the first line of the entry rather than on the whole
   block, so a row with four change lines under it does not float its mark
   halfway down the card. */
.feed .dot {
  width: 34px; height: 34px; border-radius: 50%; flex: none; margin-top: -2px;
  display: grid; place-items: center;
  background: var(--accent-wash); color: var(--accent);
}
.feed .dot svg { width: 15px; height: 15px; }
.feed .dot.ok { background: var(--ok-wash); color: var(--ok); }
.feed .dot.warn { background: var(--amber-wash); color: var(--amber-ink); }
.feed .dot.bad { background: var(--bad-wash); color: var(--bad); }
@media (max-width: 420px) {
  /* The mark goes before the words do. At this width 34px plus a gap is a
     tenth of the screen spent on decoration. */
  .feed .dot { display: none; }
  .feed li { grid-template-columns: minmax(0, 1fr) auto; }
}

/* ``minmax(0, 160px)`` rather than a flat 160px: a fixed track cannot shrink,
   so on a phone the label column kept its width and pushed the value off the
   right of the card. */
.facts {
  display: grid; grid-template-columns: minmax(0, 160px) minmax(0, 1fr);
  gap: 12px 20px; margin: 18px 0 0; font-size: 14px;
}
.facts dd { overflow-wrap: break-word; }
@media (max-width: 480px) {
  /* Label above value. Two columns at this width gives four-character lines. */
  .facts { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
  .facts dd { margin-bottom: 10px; }
}
.facts dt { color: var(--muted); }
.facts dd { margin: 0; font-weight: 500; }

.notice {
  background: var(--amber-wash); border: 1px solid #f0e3cb;
  border-radius: 12px; padding: 15px 18px; font-size: 13.5px; margin: 20px 0;
}
.notice strong { color: var(--amber-ink); }
.empty {
  background: var(--fill); border-radius: 14px;
  padding: 64px 24px; text-align: center; color: var(--muted);
}
.empty .button { margin-top: 12px; }

/* ---------------------------------------------------------------- R2 · collaboration */

/* The collaborator's pages. Deliberately plainer than the staff shell: there is
   no navigation, because there is nowhere else this person may go. */
body.guest { background: var(--bg); }
.topbar.guest { justify-content: space-between; }
.tag {
  display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 999px;
  background: var(--accent-wash); color: var(--accent-ink); font-size: 11px; font-weight: 600;
}
.tag.warn { background: var(--bad-wash); color: var(--bad); }

.pill.frozen { background: var(--line-2); color: var(--ink-2); }
.notice.frozen { background: #f4f6f9; border-color: #dfe3ea; border-left-color: #9aa3b2; }
.notice.frozen strong { color: var(--ink); }

.card.collaborate { box-shadow: inset 0 0 0 1px var(--accent-wash); }
.card.collaborate select { margin-bottom: 14px; }

form.inline { display: inline; }
form.inline button { height: 32px; padding: 0 12px; font-size: 12.5px; }

.comments { list-style: none; padding: 0; margin: 0; }
/* A quoted comment. Indented and tinted rather than barred: the bar was
   doing the work the indent already does. */
.comments li {
  background: var(--fill); border-radius: 10px;
  padding: 10px 14px; margin-bottom: 12px;
}
.comments .who { font-weight: 650; font-size: 13.5px; display: inline; }
.comments .when { color: var(--muted); font-size: 12px; margin-left: 8px; }
.comments p { margin: 4px 0 0; font-size: 14px; }

details summary { cursor: pointer; margin: 14px 0; font-size: 14px; color: var(--muted); }
details summary:hover { color: var(--ink); }
textarea { min-height: 90px; resize: vertical; }

/* ------------------------------------------------------- the review screen */

.review { display: grid; grid-template-columns: 1.35fr 1fr; gap: 20px; align-items: start; }
@media (max-width: 1040px) { .review { grid-template-columns: minmax(0, 1fr); } }
.review .card { margin: 0; }
iframe.pdf {
  width: 100%; height: 640px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: #fff; display: block;
}
/* Shown only when the browser falls back — an embedded PDF replaces it. */
.pdf-instead { padding: 4px 0 8px; }
.pdf-instead p { margin: 0 0 12px; }
.pdf-instead .row { display: flex; flex-wrap: wrap; gap: 10px; }

@media (max-width: 820px) {
  /* No phone browser renders a PDF inside a page, so on a small screen the
     fallback IS the document view. The fixed 640px height belonged to an
     embedded document; keeping it around a two-line panel is what produced a
     screen-and-a-half of empty box with a notice floating at the top of it. */
  iframe.pdf {
    height: auto; min-height: 0; border: none; background: none; border-radius: 0;
  }
}

.termlist { margin: 14px 0 0; }
.term {
  display: grid; grid-template-columns: 116px 1fr; gap: 12px;
  padding: 9px 12px; border-bottom: 1px solid var(--line-2); border-radius: 8px;
}
.term:last-child { border-bottom: none; }
.term dt { color: var(--muted); font-size: 13px; margin: 0; }
.term dd { margin: 0; font-size: 14px; }
.term .value { font-weight: 600; }

/* A field somebody has changed is marked, and says who and when. Derived from
   the append-only record, so it cannot disagree with the history below it. */
.term.touched { background: var(--amber-wash); }
.term .prov { display: block; color: #8a6314; font-size: 11.5px; margin-top: 2px; }
.term .tweak { font-size: 11.5px; margin-left: 8px; }
/* A term this reader has corrected and not yet applied (#296). The panel
   already SHOWS the staged value, so without a mark a held correction reads
   exactly like a stored one — worse than not showing it. */
.term.staged { background: var(--amber-wash); }
.term.staged .value { border-bottom: 2px dashed var(--amber); }
.term .prov.held { color: var(--amber-ink, #8a5a12); font-weight: 600; }
/* `correct` is a button now, because it opens an editor in place rather than
   navigating to a form (#282). It has to go on reading as the quiet link it
   has always been beside the value, so the button chrome is taken off. */
.term button.tweak {
  background: none; border: 0; padding: 0; margin: 0 0 0 8px;
  color: var(--accent-ink); text-decoration: underline;
  font: inherit; font-size: 11.5px; cursor: pointer; vertical-align: baseline;
}
/* The editor sits where the value was, inside the row. */
.term .field-editor { margin: 6px 0 0; }

/* ------------------------------------------------------------------ money */

.breakup { width: 100%; margin: 18px 0; box-shadow: none; border: none; }
.breakup td { padding: 10px 0; border-bottom: 1px solid var(--line-2); font-size: 14px; background: none; }
.breakup td.amt { text-align: right; }
.breakup tbody tr:hover td { background: none; }
/* Money collected on somebody else's behalf is shown as such, not as ours. */
.breakup tr.passthrough td { color: var(--muted); }
.breakup tr.total td {
  font-weight: 700; font-size: 17px; border-bottom: none; padding-top: 16px;
  border-top: 2px solid var(--ink); color: var(--ink);
}
.notice.warn { background: var(--bad-wash); border-color: #f4c9c5; border-left-color: var(--bad); }
.notice.warn strong { color: #8a1c12; }
/* flex-start, not center: the sentence wraps to two lines on a narrow screen
   and a centred box then floats beside the gap between them. The nudge puts
   the box on the optical centre of the FIRST line. */
label.ack { display: flex; align-items: flex-start; gap: 0; margin: 16px 0; font-size: 13.5px; }
label.ack input[type="checkbox"] { margin-top: 2px; }
pre.trace {
  background: #f7f8fa; border: 1px solid var(--line); padding: 14px; border-radius: var(--radius-sm);
  font-size: 12px; line-height: 1.5; overflow-x: auto;
}

/* A simulated vendor page says so, loudly and permanently. */
.card.simulated { border: 2px dashed #e0a94a; }
.sim-banner {
  background: var(--amber-wash); color: #7a4508; margin: -24px -24px 20px; padding: 10px 16px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-align: center;
  text-transform: uppercase; border-radius: 8px 8px 0 0;
}

/* ------------------------------------------------------------------ errors */

/* Django renders field errors as a bare <ul class="errorlist"> — grey, small,
   and easy to scroll past. A validation message nobody notices is a form that
   silently does not submit, which reads as the product being broken. */
ul.errorlist {
  list-style: none; margin: 6px 0 8px; padding: 9px 12px;
  background: var(--bad-wash); border: 1px solid #f2cfcb;
  border-radius: var(--radius-sm); color: #8a1c12; font-size: 13px;
}
ul.errorlist li { margin: 0; }

/* The field itself is marked too, so the eye lands on the input and not only on
   the sentence above it. */
p:has(> ul.errorlist) label, li:has(> ul.errorlist) label { color: #8a1c12; font-weight: 650; }
p:has(> ul.errorlist) input, p:has(> ul.errorlist) select, p:has(> ul.errorlist) textarea {
  border-color: var(--bad); background: #fffbfb;
}

/* A summary at the top of the form. On a long form the broken field can be
   below the fold, and a submit that appears to do nothing is the worst
   possible feedback. */
/* A pastel field with a hairline round it, not a slab with a thick bar down one
   side. The bar was doing the work that colour and spacing already do. */
.form-errors {
  background: var(--bad-wash); border: 1px solid #f2cfcb;
  border-radius: 12px; padding: 14px 16px; margin: 0 0 20px; font-size: 14px;
}
.form-errors strong { color: #8a1c12; }
.form-errors ul { margin: 8px 0 0; padding-left: 18px; }
.form-errors li { margin-bottom: 3px; }
.form-errors a { color: #8a1c12; font-weight: 600; }

/* ------------------------------------------------------- waiting on you */

.todo { margin: 26px 0 8px; }
.actions { list-style: none; padding: 0; margin: 10px 0 24px; }
.actions li {
  display: grid; grid-template-columns: 96px 210px 1fr; gap: 14px; align-items: baseline;
  padding: 13px 16px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--card); margin-bottom: 7px; font-size: 14px;
}
@media (max-width: 760px) { .actions li { grid-template-columns: 1fr; gap: 3px; } }
.actions .ref { font-weight: 650; text-decoration: none; }
.actions .act { font-weight: 650; }
.actions .why { color: var(--muted); font-size: 13px; }
/* What somebody else owes you is context, not a task. It reads quieter. */
.actions.muted-list li { background: transparent; border-style: dashed; }
.actions.muted-list .act { font-weight: 500; color: var(--muted); }

/* ------------------------------------------------------------------ *
 * The administrator's console
 *
 * A screen with a hundred checkboxes on it needs one thing above all:
 * a person has to be able to scan a section and see what it demands
 * WITHOUT reading every row. Hence the two-column grid on a wide screen
 * and the single column below 720px, where two columns of checkboxes
 * start wrapping their labels into each other.
 * ------------------------------------------------------------------ */

table.packs { width: 100%; border-collapse: collapse; }
table.packs th {
  text-align: left; font-size: 12px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); padding: 0 12px 10px;
}
table.packs td { padding: 12px; border-top: 1px solid var(--line); vertical-align: middle; }
table.packs td.endcol { text-align: right; width: 1%; white-space: nowrap; }

.pill.LIVE { background: #e7f3ea; color: #1d6b39; }

.reasons { margin: 10px 0 0; padding-left: 18px; }
.reasons li { margin: 4px 0; }

.fieldrules {
  list-style: none; margin: 12px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px 24px;
}
.fieldrules li { padding: 8px 0; border-top: 1px solid var(--line); min-width: 0; }
.fieldrules label {
  display: flex; align-items: center; gap: 10px; cursor: pointer; min-width: 0;
}
.fieldrules input[type=checkbox] { flex: none; width: 16px; height: 16px; accent-color: var(--accent); }
.fieldrules .name { flex: 1 1 auto; min-width: 0; overflow-wrap: break-word; }
.fieldrules .kind {
  flex: none; font-size: 11px; letter-spacing: .04em; text-transform: lowercase;
  color: var(--muted); background: var(--fill); border-radius: 4px; padding: 2px 6px;
}
.fieldrules .hint { margin: 2px 0 0 26px; font-size: 12px; color: var(--muted); }
/* Declared but not collected yet. Shown rather than hidden, with the reason:
   a field an administrator cannot see is a field they ask for again next month. */
.fieldrules li.off { opacity: .55; }
/* The three steps above the field rules — numbered, and ticked off as they are
   done, because the people using that screen follow steps, not explanations. */
.howto ol { margin: 0; padding-left: 22px; }
.howto li { margin: 10px 0; }
.howto li .helper { display: block; margin: 2px 0 0; }
.howto li.done::marker { color: var(--ok); }
.ok { color: var(--ok); font-weight: 600; }
.fieldrules li.off label { cursor: not-allowed; }

@media (max-width: 720px) {
  .fieldrules { grid-template-columns: minmax(0, 1fr); }
  table.packs td.endcol { text-align: left; }
}

/* A row of one or two controls with a button on the end — a file to upload, a
   reason to type. `align-items: flex-end` is what lines the button up with the
   inputs rather than with their labels, and it only works while every item in
   the row is the same height. Help text therefore does NOT go inside a field:
   it goes in a `.rowhint`, which takes the full width and so falls to its own
   line under the whole row.
   For a form of several fields, use `.formgrid` — that is what it is for. */
.uploader { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin-top: 12px; }
.uploader .field {
  display: flex; flex-direction: column; gap: 4px; flex: 1 1 240px; min-width: 0; margin: 0;
}
.uploader .field span { font-size: 12px; color: var(--muted); }
.uploader input[type=file] { font: inherit; }
.uploader .rowhint { flex: 1 0 100%; margin: 0; }
form.inline { display: inline; }
table.packs td .button + form.inline { margin-left: 6px; }
.pill.RETIRED { background: var(--fill); color: var(--muted); }
table.placeholders code { font-size: 12px; }
table.placeholders tr.nested td:first-child { padding-left: 28px; }


/* ------------------------------------------------------------------ *
 * Holding together at every width
 *
 * Two rules, and they are the two that decide whether a page looks made
 * or assembled: wide content scrolls INSIDE its own box, and the shell
 * gives up its decoration before it gives up the content.
 * ------------------------------------------------------------------ */

/* A table is the one thing in this product that genuinely cannot narrow past a
   point — five columns of a person's name and a date do not reflow. So it gets
   its own scrollbar rather than making the whole PAGE scroll sideways, which
   would put the right-hand column of every other element off the screen too. */
.scroller { overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; }
.scroller > table { min-width: 100%; }
/* A column heading never wraps. It is two or three words naming what the
   column is, and the moment it is allowed to wrap, a narrow screen squeezes it
   to one letter per line — the table then LOOKS like it fits while being
   unreadable, which is worse than the scrollbar it was avoiding. Headings that
   cannot wrap give the table a real minimum width, and the scroller does its
   job. */
.scroller th { white-space: nowrap; }
/* Short, atomic values do not wrap either — a version, a status, a date. */
.scroller td .pill, .scroller td .button, .scroller .endcol { white-space: nowrap; }

/* ------------------------------------------------------------------ *
 * A table that becomes a list
 *
 * A table is the right shape for a list of agreements on a laptop and the
 * wrong one on a phone. Five columns of an address and a date cannot
 * narrow, so the only alternatives are a sideways scroll — which hides
 * the rent and the Edit link behind a swipe nobody knows is there — or
 * this: one block per row, each value labelled by the column it came
 * from. The labels come from ``data-label`` on the cell, so the heading
 * and the label cannot drift apart.
 * ------------------------------------------------------------------ */
@media (max-width: 720px) {
  .scroller:has(> table.stack) { overflow-x: visible; }
  /* Clipped rather than `display: none`: a screen reader still announces the
     headings, and this layout is the same table, read differently. */
  table.stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  table.stack, table.stack tbody, table.stack tr, table.stack td { display: block; width: auto; }
  /* The horizontal inset has to come back HERE. On desktop it is
     `td:first-child { padding-left: 22px }`, and the card these tables sit in
     is `card--flush` — deliberately no padding of its own, because "the
     padding belongs to the rows". Stacked, every cell is a block with
     `padding: 5px 0`, so that gutter disappeared and the labels printed hard
     against the card's edge. It goes on the ROW, so the separator still runs
     the full width the way it does on desktop. */
  table.stack tr { padding: 12px 16px; border-top: 1px solid var(--line); }
  table.stack tr:first-child { border-top: none; }
  table.stack td {
    display: grid; grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr);
    gap: 12px; align-items: baseline; padding: 5px 0; border: none;
  }
  table.stack td::before {
    content: attr(data-label);
    font-size: 11.5px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
  }
  /* A pill and a button size to their own content. Left to fill the grid track
     they stretch to the full width of the card, which makes a one-word status
     look like a banner and a link look like a primary action. */
  table.stack td .pill, table.stack td .tag, table.stack td .button,
  table.stack td button { justify-self: start; }
  table.stack td.endcol { grid-template-columns: minmax(0, 1fr); padding-top: 10px; }
  table.stack td.endcol::before { display: none; }
  /* `width: 1%` on a cell is the TABLE trick for "shrink to fit the content",
     and two columns use it — `.worklist .sr` and `.packs .endcol`. Here the
     cell is a GRID, where 1% means one per cent: about 3px on a phone. Both
     tracks are `minmax(0, ...)`, so they collapse to zero inside it and the
     label prints one character per line — "S 0 R 1 N O ." down the screen.
     Reset it wherever a stacked cell carries a table width. */
  table.stack td.sr, table.stack td.endcol { width: auto; }
  /* EVERY element child of the cell is its own grid item, and the label
     ``::before`` is the first of them. So a cell holding two things — the
     document name and the reference under it, a state name and its code —
     puts the second one in the NEXT auto-placed slot, which is column ONE:
     the label column. The reference printed hard left, under "DOCUMENT NAME",
     looking like a row whose label had gone missing.

     Pinning the children to column two keeps them under the value, whatever
     the cell holds. `endcol` is excluded because it is a single-column grid
     with no label, and column two there would be one it does not have. */
  table.stack td:not(.endcol) > * { grid-column: 2; }
}

/* Nothing is allowed to set the page's width. A long unbroken reference number
   or email address in a cell is the usual culprit. */
.page, .card, .panel, .notice { min-width: 0; }
/* ``break-word``, NOT ``anywhere``. They look identical on a paragraph and
   behave completely differently in a grid or a table: ``anywhere`` counts
   toward min-content sizing, so it tells the layout that one character is an
   acceptable column width — which is exactly how the state pack table came to
   render its headings as a vertical column of single letters. ``anywhere`` is
   now used only where a genuinely unbreakable token has to be forced to fit. */
.card, .panel, .notice { overflow-wrap: break-word; }

@media (max-width: 560px) {
  /* The logo gets smaller rather than losing its wordmark.

     It used to be hidden outright here, on the argument that knowing WHO you
     are signed in as matters on a shared laptop and the logo does not. That
     argument holds for the room it buys; it does not survive the logo being one
     image, because hiding half of a picture is not something CSS does and the
     alternative — a second, cropped copy — is a second file to keep in step for
     no gain. Scaling buys the same room and keeps the whole mark.

     Signed out it stays full size regardless: that header holds nothing else,
     and it is the only thing on the page saying whose it is. */
  .topbar:not(.signedout) .brand-logo { height: 24px; }
  /* Three labels and a lockup on a 390px bar. Tighter and smaller rather than
     wrapped or scrolled: they are a handful of short words and they fit.

     Checked down to 320px — below this suite's own 360px floor, because a
     first-generation SE is still a phone somebody signs up on, and a bar that
     pushes the page sideways is the kind of fault nobody reports, they just
     leave. */
  .signedout-links { gap: 10px; }
  .signedout-links a { font-size: 12.5px; }
  .topbar.signedout { padding-left: 14px; padding-right: 14px; }
  .topbar.signedout .brand-logo { height: 24px; }
  .topbar .who .role { display: none; }
  .topbar { padding-left: 16px; padding-right: 16px; }
  .page { padding-left: 16px; padding-right: 16px; }

  /* The rail is a bottom bar here, and ten icons do not fit across a phone.
     It scrolls rather than wrapping onto a second row that would eat a third
     of the screen. */
  .nav { overflow-x: auto; justify-content: flex-start; }
  .nav a, .nav span.off { flex: none; }

  /* A row of buttons becomes a column rather than each one shrinking to a
     width its label cannot fit in. */
  .actionbar { flex-wrap: wrap; }
  .actionbar button, .actionbar .button { flex: 1 1 auto; }
  .row.between { flex-wrap: wrap; gap: 8px; }
}

/* ------------------------------------------------------------------ *
 * Illustration
 *
 * One rule for every drawing: a thin stroke in the ink colour at low
 * weight, over a soft tinted blob. The designs use a line-art set in
 * exactly this register, and the register is what makes a product feel
 * drawn rather than assembled — colour would fight the navy and make
 * these decorations instead of furniture.
 * ------------------------------------------------------------------ */

.art { width: 100%; max-width: 240px; height: auto; color: var(--ink-2); opacity: .85; }
/* The frames put a soft GREEN wash behind the line work — the same green as
   the wordmark. It is the only colour in the drawing, which is what stops an
   illustration becoming decoration. */
.art .blob { fill: var(--ok); opacity: .13; }
.hero-dark .art { color: #fff; opacity: .9; }
.hero-dark .art .blob { fill: #fff; opacity: .1; }

/* The page's opening row: what this section is, and what has just happened
   in it. `start`, so a short activity panel keeps its own height rather than
   stretching to match the hero and showing a field of empty card. */
.grid-hero {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 16px; align-items: stretch; margin: 0 0 22px;
}
.grid-hero .hero { margin: 0; }
/* Narrower art than a full-width hero takes, so the sentence and the two
   buttons still fit in one column at half the page. */
.grid-hero .hero { grid-template-columns: minmax(0, 1fr) minmax(0, 165px); padding: 22px 24px; gap: 16px; }
.grid-hero .hero .art { max-width: 165px; }
@media (max-width: 1080px) { .grid-hero { grid-template-columns: minmax(0, 1fr); } }

/* The activity panel beside it. It takes the hero's height and scrolls inside,
   rather than being trimmed to three entries: a dozen would make the card twice
   as tall, and dropping the rest would lose events with nowhere to go and read
   them — there is no "all activity" page. */
.feedcard { display: flex; flex-direction: column; }
.feedcard .feed {
  /* `flex: 1` so it fills the row when the hero is the taller of the two, and
     `max-height` so it never becomes the reason the row is tall. Without the
     cap a dozen entries set the height and the hero stretched to a screenful
     of empty card beside them. */
  flex: 1; min-height: 0; max-height: 296px; overflow-y: auto;
  /* Clearance for the scrollbar. Without it the "View" link on every row sits
     hard against the bar — and on a platform that draws a permanent one, the
     two touch and the link reads as clipped. */
  padding-right: 12px;
  /* The last row fades instead of being sliced through the middle of a
     timestamp, which reads as a rendering fault rather than as "more below". */
  mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
}
.feedcard .feed li:first-child { padding-top: 0; }
/* A card whose content reaches its own edges — a tab strip, a table. The
   padding belongs to the rows, not to the box around them. */
.card--flush { padding: 0; overflow: hidden; }
.card--flush > .tabs { border-bottom: 1px solid var(--line); }
/* The table inside it has already been given a border by the card. */
.card--flush table, .card--flush .scroller > table {
  border: none; border-radius: 0; background: none;
}
.card--flush .state { margin: 0; border-radius: 0; }

/* ------------------------------------------------------------------ *
 * The hero
 *
 * The designs open every section with one: a title, a sentence saying
 * what the thing is for, and a drawing. It is not decoration. A product
 * that starts with a table starts by asking somebody to read; one that
 * starts with a sentence tells them where they are first.
 * ------------------------------------------------------------------ */

.hero {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 240px);
  align-items: center; gap: 24px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  padding: 26px 30px; margin: 0 0 26px;
}
.hero h1 { margin: 0 0 6px; }
.hero p { margin: 0; color: var(--muted); font-size: 14.5px; max-width: 46ch; }
.hero .hero-do { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 10px; }
.hero .art { justify-self: end; }

/* The hero is WHITE in every frame, with the colour carried by the drawing
   rather than by a slab behind it. The navy version below was mine, not the
   designs' — kept because the console and the sale track may want a section
   that announces itself, and removed from the screens the designs actually
   draw. */
.hero.hero-dark {
  background: linear-gradient(120deg, var(--navy) 0%, var(--navy-3) 100%);
  border-color: transparent; color: #fff;
}
.hero.hero-dark h1 { color: #fff; }
.hero.hero-dark p { color: rgba(255, 255, 255, .78); }
.hero.hero-dark .button { background: rgba(255, 255, 255, .12); color: #fff; border-color: transparent; }
.hero.hero-dark .button:hover { background: rgba(255, 255, 255, .2); color: #fff; }
.hero.hero-dark .button.primary { background: #fff; color: var(--navy); }

@media (max-width: 760px) {
  /* The drawing goes first and small, because a 240px illustration above the
     fold on a phone is a screen of decoration before any content. */
  .hero { grid-template-columns: minmax(0, 1fr); padding: 20px; gap: 14px; }
  .hero .art { justify-self: start; max-width: 130px; order: -1; }
}

/* ------------------------------------------------------------------ *
 * Progress
 *
 * A ring beside the title, as the designs draw it. A number alone is a
 * fact; a ring is a fact somebody can read without stopping.
 * ------------------------------------------------------------------ */

.ring { display: inline-grid; place-items: center; width: 54px; height: 54px; flex: none; }
.ring svg { grid-area: 1 / 1; width: 54px; height: 54px; transform: rotate(-90deg); }
.ring .track { stroke: var(--line); }
.ring .run { stroke: var(--accent); transition: stroke-dashoffset .4s ease; }
.ring .ring-n {
  grid-area: 1 / 1; font-size: 12.5px; font-weight: 650; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ring.full .run { stroke: #2f8a4d; }

.titlerow { display: flex; align-items: center; gap: 16px; margin: 0 0 4px; }
.titlerow h1, .titlerow h2 { margin: 0; }

/* ------------------------------------------------------------------ *
 * The stepper
 *
 * The designs draw the WHOLE journey — generate, pay, review — with the
 * sections nested under their group and a chip saying what each still
 * needs. Showing only the current group is what made this feel like a
 * form rather than a process with an end somebody can see.
 * ------------------------------------------------------------------ */

/* Groups separated by a hairline, steps as flat rows inside them.
 *
 * This was a timeline: a connector line down the left with a bullet per
 * group. The line drew the SHAPE of the journey, which nobody needed —
 * the groups are already in order, one above the next — while spending
 * 30px of a 280px rail on decoration and pushing every label right. The
 * renders divide the groups with a rule and give the room back to the
 * words.
 */
.journey { list-style: none; margin: 0; padding: 0; }
.journey > li { position: relative; padding: 0; border-bottom: 1px solid var(--line); }
.journey > li:last-child { border-bottom: none; }
/* The bullet and its connector are gone. Kept in the markup because the
   stepper's states — on, done — still hang off the same element. */
.journey > li > .dot { display: none; }

.journey .group {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 17px 18px; font-weight: 600; font-size: 14.5px; color: var(--ink);
}
.journey .substeps { list-style: none; margin: 0; padding: 0 8px 10px; }
.journey .substeps li { margin: 0; }
.journey .substeps a {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 11px 12px; border-radius: 7px; margin-bottom: 2px;
  text-decoration: none; color: var(--ink-2); font-size: 13.5px;
}
.journey .substeps a:hover { background: var(--fill); color: var(--ink); }
.journey .substeps a.on { background: var(--card); box-shadow: inset 0 0 0 1px var(--line); font-weight: 600; }
.journey .substeps a.off { color: var(--muted); cursor: default; }

/* The chip at the end of a step row. Amber for "Incomplete" rather than red:
   an unfinished section is the normal state of a form somebody is filling in,
   and colouring it as an error is how people learn to ignore red. */
.chip {
  flex: none; font-size: 11.5px; font-weight: 500; border-radius: 999px;
  padding: 3px 10px; background: var(--amber-wash); color: var(--amber-ink);
}
.chip.waiting { background: var(--accent-wash); color: var(--accent-ink); }
/* Done is a tick in a circle, not a word. It is the one state that repeats
   down the whole rail, and six copies of "Done" is a column of text saying
   the same thing six times. */
.chip.done {
  width: 17px; height: 17px; padding: 0; border-radius: 50%;
  background: var(--ok); color: #fff; font-size: 0;
  display: inline-grid; place-items: center;
}
.chip.done::before {
  content: ""; width: 4px; height: 8px; margin-top: -2px;
  border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(42deg);
}

/* ------------------------------------------------------------------ *
 * A state with something to say
 *
 * The designs answer an empty panel with a mark, a sentence and a line
 * of detail — never a bare "nothing here".
 * ------------------------------------------------------------------ */

.state { text-align: center; padding: 34px 20px; }
.state .art { margin: 0 auto 14px; max-width: 180px; }
.state h3 { margin: 0 0 4px; font-size: 15px; }
.state p { margin: 0 auto; color: var(--muted); font-size: 13.5px; max-width: 44ch; }
.state .actions-row { margin-top: 16px; display: flex; justify-content: center; gap: 10px; }
.state .mark { display: block; margin: 0 auto 10px; width: 26px; height: 26px; color: #2f8a4d; }

/* ------------------------------------------------------------------ *
 * The journey, on navy
 *
 * The stepper above is written for a light card, because that is where
 * a stepper usually lives. This rail is the navy panel from the designs,
 * so every colour in it inverts. Kept as an override rather than as a
 * second component: there is one stepper with one structure, and a
 * palette is not a reason to fork a component.
 * ------------------------------------------------------------------ */

.wizard aside .titlerow { padding: 18px 18px 12px; border-bottom: 1px solid rgba(255, 255, 255, .09); }
.wizard aside .titlerow strong { color: #fff; font-size: 15px; }
.wizard aside .titlerow .muted { color: #9fb2d6; margin: 2px 0 0; font-size: 12.5px; }
.wizard aside .ring .track { stroke: rgba(255, 255, 255, .18); }
.wizard aside .ring .run { stroke: #7aa7f0; }
.wizard aside .ring.full .run { stroke: #6fd08c; }
.wizard aside .ring-n { color: #fff; }

.wizard aside .journey { padding: 0; }
.wizard aside .journey > li { border-bottom-color: rgba(255, 255, 255, .09); }
.wizard aside .journey .group { color: #fff; }
/* A step nobody has reached yet is dimmed, not hidden — seeing what comes
   after is the whole reason the rail shows the full journey. */
.wizard aside .journey .substeps a { color: #c7d3e9; }
.wizard aside .journey .substeps a:hover { background: rgba(255, 255, 255, .07); color: #fff; }
.wizard aside .journey .substeps a.on {
  background: #fff; color: var(--navy); box-shadow: none;
}
.wizard aside .journey .substeps a.off { color: #7c8db0; }
/* A percentage on navy needs its own contrast; the amber wash meant for a
   white card goes muddy against it. */
.wizard aside .chip { background: rgba(255, 255, 255, .14); color: #dbe4f4; }
.wizard aside .chip.waiting { background: rgba(122, 167, 240, .24); color: #dbe4f4; }
.wizard aside .journey .substeps a.on .chip { background: var(--amber-wash); color: var(--amber-ink); }
.wizard aside .journey .substeps a.on .chip.waiting { background: var(--accent-wash); color: var(--accent-ink); }
.wizard aside .chip.done, .wizard aside .journey .substeps a.on .chip.done {
  background: var(--ok); color: #fff;
}

@media (max-width: 900px) {
  /* Stacked above the form, so it becomes a summary rather than a sidebar. */
  .wizard aside .journey { padding-bottom: 6px; }
}

/* ------------------------------------------------------------------ *
 * Charts
 *
 * Four bars and a ring, drawn by hand. A charting library would bring
 * its own typography, its own colours and its own idea of what a chart
 * looks like, and then this product would have two design languages.
 * ------------------------------------------------------------------ */

/* ONE grid for the whole chart, with three rows every column shares: the
   number, the bar, and the label.
 *
 * Each column used to be its own three-row grid, aligned to the bottom of the
 * row. That works only while every label is one line. The moment one wraps —
 * "Owners & tenants", "Agency admins" — that column becomes taller, and since
 * they are bottom-aligned the extra height comes off the TOP: its number rides
 * 17px above its neighbours and the chart reads as broken.
 *
 * `display: contents` on the column dissolves it, so its three spans become
 * items of the chart's own grid and land in the same three rows as everybody
 * else's. Labels may then wrap as much as they like. */
.chart {
  display: grid;
  grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  grid-template-rows: auto 132px auto;
  gap: 6px 18px; padding: 4px 4px 0;
}
.chart-col { display: contents; }
.chart-n {
  justify-self: center; align-self: end;
  font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.chart-bar {
  justify-self: center; align-self: end;
  width: 100%; max-width: 44px; min-height: 3px;
  border-radius: 6px 6px 0 0;
  background: linear-gradient(to top, var(--navy), var(--accent));
  /* The axis line, drawn under the bars rather than under the labels. */
  box-shadow: 0 1px 0 var(--line);
}
.chart-label {
  justify-self: center; align-self: start;
  font-size: 11.5px; color: var(--ink-2); text-align: center; line-height: 1.3;
  overflow-wrap: break-word;
}

.donut { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.donut svg { width: 116px; height: 116px; transform: rotate(-90deg); flex: none; }
.donut-track { stroke: var(--line-2); }
.donut-slice.s1 { stroke: var(--navy); }
.donut-slice.s2 { stroke: var(--accent); }
.donut-slice.s3 { stroke: #7aa7f0; }
.donut-slice.s4 { stroke: var(--amber); }
/* The legend sizes to its own content and no wider, so the amounts sit next to
   what they label instead of being flung to the far side of the card. */
.legend { list-style: none; margin: 0; padding: 0; font-size: 13px; min-width: 11rem; }
.legend li { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.legend .key { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.legend .key.s1 { background: var(--navy); }
.legend .key.s2 { background: var(--accent); }
.legend .key.s3 { background: #7aa7f0; }
.legend .key.s4 { background: var(--amber); }
.legend strong { margin-left: auto; font-variant-numeric: tabular-nums; }

/* The console's KPI row: a number, what it counts, and where to go next. */
.kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin: 0 0 22px; }
.kpi { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 20px 22px; }
.kpi-n { display: block; font-size: 30px; font-weight: 700; letter-spacing: -.02em; }
.kpi-label { display: block; color: var(--muted); font-size: 13px; margin: 4px 0 10px; }
.kpi a { font-size: 13px; font-weight: 600; }
.kpi.off .kpi-n { color: var(--muted); }

/* `start`, so a short panel keeps its own height instead of stretching to
   match a tall one and showing a field of empty card. */
.panels {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px; align-items: start;
}
.panels .card { margin: 0; }
.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.panel-head h2 { margin: 0; }

@media (max-width: 900px) {
  .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .panels { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 520px) { .kpis { grid-template-columns: minmax(0, 1fr); } }

/* A repeated person — Owner 1, Owner 2 — is a heading and a rule, not a
   framed box. The browser's default fieldset draws a groove with the legend
   notched into it, which is chrome from another era and reads as a bug beside
   flat cards. */
fieldset { border: 0; padding: 0; margin: 0 0 26px; min-width: 0; }
fieldset > legend {
  padding: 0; margin: 0 0 14px; width: 100%;
  font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); border-bottom: 1px solid var(--line); padding-bottom: 8px;
}

/* ------------------------------------------------------------------ *
 * Form layout
 *
 * Two columns, as every form frame in the designs is laid out. A name
 * and an age side by side halves the page and puts a whole section in
 * one screen — the difference between a form somebody finishes and one
 * they scroll.
 * ------------------------------------------------------------------ */

.formgrid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 24px; align-items: start;
}
.formgrid > p, .formgrid > .field { min-width: 0; }
.formgrid > .field.wide { grid-column: 1 / -1; }
/* Some fields are a line of prose, not a value. Which ones is decided in
   Python — see WIDE_SUFFIXES — because a template cannot ask what kind of
   widget it is holding without becoming a program. */
.formgrid > p:has(textarea) { grid-column: 1 / -1; }
/* One column, on purpose. Not every form wants two: a short one on a narrow
   card gives each field half of 600px, which is not enough for a field that is
   itself two controls. Opted into per include rather than inferred from the
   field count, because "three fields or fewer" is a rule that would be wrong
   the first time somebody added a fourth. */
.formgrid.single { grid-template-columns: minmax(0, 1fr); }

@media (max-width: 700px) { .formgrid { grid-template-columns: minmax(0, 1fr); } }

/* The worklist: the same fact in the same place on every line.
 *
 * A serial number that is a label rather than data, a document name that
 * leads, and everything else quiet enough that the eye lands on the name and
 * the status. */
table.worklist .sr { color: var(--muted); font-variant-numeric: tabular-nums; width: 1%; }
table.worklist .doc { font-weight: 500; text-decoration: none; }
table.worklist .doc:hover { text-decoration: underline; }
/* The reference under the name is a caption, not a chip. `.rec-id` carries a
   pill's fill and padding for the card layout, which as a block in a table cell
   stretched into a grey bar the width of the column. */
table.worklist .rec-id {
  display: block; margin-top: 2px; padding: 0; background: none; border-radius: 0;
  font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
table.worklist td.muted { color: var(--ink-2); }
/* A date is one token and must not be broken across two lines to save width
   the name column can give up instead. */
table.worklist td.when { white-space: nowrap; }
table.worklist .sr { width: 1%; }
/* Widths, so the columns that cannot wrap do not, and the ones that can do it
   where a wrap costs least — a property address, not a stage name. */
table.worklist th:nth-child(2) { width: 24%; }
table.worklist th:nth-child(3) { width: 1%; }
table.worklist th:nth-child(4) { width: 17%; }
table.worklist th:nth-child(5) { width: 15%; }

/* ------------------------------------------------------------------ *
 * Tabs
 *
 * A flat strip with the active tab underlined, sitting on a hairline
 * that runs the width of the panel.
 *
 * These were folder tabs — overlapping, slanted, one rounded corner.
 * The shape was the problem rather than the contrast: every boundary
 * between two tabs was a diagonal, the counts sat in their own pills,
 * and the whole strip carried more drawing than the choice it offers.
 * An underline says the same thing with one line, and the count can
 * then be plain text beside the label.
 * ------------------------------------------------------------------ */

.tabs {
  display: flex; align-items: flex-end; gap: 2px; margin: 0; padding: 0 6px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }

.tabs a {
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  padding: 13px 18px; text-decoration: none; font-size: 14px; font-weight: 500;
  color: var(--ink-2);
  /* Overlapping the strip's own hairline by a pixel, so the active underline
     replaces that line rather than sitting under it as a second one. */
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .12s, border-color .12s;
}
.tabs a:hover { color: var(--ink); }
.tabs a.on { color: var(--accent); border-bottom-color: var(--accent); }

.tabs .count { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Tighter on a phone, so the usual four fit without a swipe. They still scroll
   when there are more of them — see the layout probe. */
@media (max-width: 560px) { .tabs a { padding: 13px 12px; } .tabs { padding: 0 2px; } }
.tabs a.on .count { color: var(--accent-ink); }

/* The panel below, where the strip is not already inside a card. */
.tabbed {
  background: var(--card); border: 1px solid var(--line); border-top: none;
  border-radius: 0 0 var(--radius) var(--radius); padding: 14px;
}
/* A card list inside the panel is ROWS, not a stack of cards in a box.
   Each `.rec` carrying its own border inside a bordered panel is a box in a
   box in a box — the thing that makes a page read as a pile rather than a
   layout. Inside the panel they lose their frame and are divided by a rule. */
.tabbed > .cards { gap: 0; }
.tabbed > .cards > .rec {
  border: none; border-bottom: 1px solid var(--line); border-radius: 0;
  padding: 16px 8px;
}
.tabbed > .cards > .rec:last-child { border-bottom: none; padding-bottom: 8px; }
.tabbed > .cards > .rec:hover { background: #fafbfd; }
.tabs + table, .tabs + .scroller > table {
  border-top: none; border-radius: 0 0 var(--radius) var(--radius);
}
.tabs + .empty { border-radius: 0 0 var(--radius) var(--radius); }

/* ------------------------------------------------------------------ *
 * A record as a card
 *
 * Every list in the designs is cards, not rows: a thumbnail, the name
 * and its id, a stage pill, the facts as icons, the people, and the
 * actions on the right. A table makes five columns compete for width;
 * a card gives each fact the room its own length needs, which is why
 * the same idiom carries properties, users and agreements.
 * ------------------------------------------------------------------ */

.cards { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.rec {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 18px; align-items: start;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 18px 20px;
}
.rec:hover { border-color: #d9dee7; }
.rec-thumb {
  width: 116px; height: 84px; border-radius: 12px; flex: none;
  background: var(--fill); display: grid; place-items: center; color: var(--muted);
  overflow: hidden;
}
.rec-thumb .art { max-width: 78px; opacity: .6; }
.rec-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 2px; }
.rec-title a { font-size: 16px; font-weight: 650; text-decoration: none; color: var(--ink); }
.rec-title a:hover { color: var(--accent); }
.rec-id {
  font-size: 11.5px; color: var(--muted); background: var(--fill);
  border-radius: 999px; padding: 2px 9px; font-variant-numeric: tabular-nums;
}
.rec-sub { margin: 0 0 10px; color: var(--muted); font-size: 13.5px; }
.rec-facts { display: flex; flex-wrap: wrap; gap: 8px 20px; margin: 10px 0 0; font-size: 13px; }
.rec-facts span { color: var(--ink-2); }
.rec-facts .k { color: var(--muted); display: block; font-size: 11.5px; }
.rec-do { display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }
.rec-do .button { white-space: nowrap; }

@media (max-width: 760px) {
  /* The thumbnail goes; everything else stacks. A 116px picture beside a
     wrapped address is two narrow columns and neither of them readable. */
  .rec { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .rec-thumb { display: none; }
  .rec-do { flex-direction: row; flex-wrap: wrap; align-items: stretch; }
}

/* The search in the top bar, and the person on the right of it. */
.topsearch {
  display: flex; align-items: center; gap: 8px; flex: 1 1 320px; max-width: 420px;
  background: var(--fill); border: 1px solid transparent; border-radius: 22px;
  padding: 0 14px; height: var(--control); margin: 0 auto 0 0;
}
.topsearch svg { width: 16px; height: 16px; color: var(--muted); flex: none; }
.topsearch input, .topsearch select {
  border: none; background: none; padding: 0; height: 100%; margin: 0;
  font: inherit; font-size: 14px; color: var(--ink); min-width: 0;
}
.topsearch input { width: 100%; }
.topsearch input:focus, .topsearch select:focus { outline: none; box-shadow: none; }
.topsearch:focus-within { border-color: var(--accent-lift); background: var(--card); }
/* The scope picker, and the hairline that separates it from what you type.
   Searching "Bengaluru" across references and addresses at once returns a
   muddle; saying which one first is why the renders put it inside the box. */
.topsearch select { flex: none; width: auto; cursor: pointer; padding-right: 18px;
  background-position: right 4px center; }
.topsearch .div { width: 1px; height: 20px; background: var(--line-strong); flex: none; }

.avatar {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: grid; place-items: center; background: var(--navy); color: #fff;
  font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
}

@media (max-width: 900px) {
  /* The search goes before the name does: on a phone the box would take the
     whole bar, and the rail below is how somebody navigates anyway. */
  .topsearch { display: none; }
}

.avatar.big { width: 56px; height: 56px; font-size: 18px; align-self: start; }

/* The error screen. Monospace where the value is a symbol somebody will paste
   into an editor, proportional where it is a sentence somebody reads. */
.mono, .fault code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; }
.fault { grid-template-columns: minmax(0, 1fr) auto; }
.fault .rec-sub { font-family: ui-monospace, monospace; font-size: 12.5px; }
.fault details { margin-top: 12px; }
.fault summary { cursor: pointer; font-size: 13px; color: var(--accent); }
.trace {
  margin: 10px 0 0; padding: 12px 14px; background: var(--fill); border-radius: 10px;
  font-size: 11.5px; line-height: 1.5; overflow-x: auto; max-height: 320px;
}
.fault .rec-do form { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }

/* The picture on the profile page. The picture IS the control: hovering it
   shows a camera over a dim, and the file input sits on top of the whole thing
   so a click anywhere on it opens the picker. The input is present and
   focusable rather than hidden, so a keyboard reaches it too. */
.picture-edit { margin: 14px 0 0; }
.picture-swap {
  position: relative; display: inline-grid; place-items: center;
  border-radius: 50%; overflow: hidden; cursor: pointer; margin: 0;
}
.picture-swap .avatar.big { width: 112px; height: 112px; font-size: 34px; object-fit: cover; }
.picture-swap .picture-over {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: rgba(15, 38, 80, .55); color: #fff;
  opacity: 0; transition: opacity .14s;
}
.picture-swap .picture-over svg { width: 30px; height: 30px; }
.picture-swap:hover .picture-over,
.picture-swap:focus-within .picture-over { opacity: 1; }
.picture-swap input[type=file] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; font-size: 0;
}
img.avatar { padding: 0; }

/* The message beside the button that was just pressed. Red, with a mark, and
   in the flow rather than floating — a bubble that disappears on the next
   keystroke is a message somebody has to reproduce to read again. */
.fix-here {
  display: flex; align-items: center; gap: 9px;
  margin: 4px 0 14px; color: var(--bad); font-size: 13.5px; font-weight: 600;
}
.fix-here svg { width: 18px; height: 18px; flex: none; }

/* A field that failed. The border and the label both, so the eye lands on the
   input rather than only on the sentence above it. */
p:has(> ul.errorlist) input,
p:has(> ul.errorlist) select,
p:has(> ul.errorlist) textarea,
li:has(> ul.errorlist) input {
  border: 1px solid var(--bad); background: #fffbfb;
  box-shadow: 0 0 0 3px rgba(191, 58, 44, .1);
}


/* A field that failed, and the sentence saying why.
 *
 * The message sits BELOW the input, next to where the cursor already is. Above
 * it — which is where `as_p` puts it — the reader has to look back up past the
 * label to find out what is wrong with what they just typed.
 */
.field.bad > label { color: var(--bad); font-weight: 650; }
.field.bad input, .field.bad select, .field.bad textarea {
  border: 1px solid var(--bad); background: #fffbfb;
  box-shadow: 0 0 0 3px rgba(191, 58, 44, .1);
}
.field-error {
  display: flex; gap: 6px; margin: 6px 0 0; color: var(--bad);
  font-size: 12.5px; font-weight: 600; line-height: 1.45;
}
.field-error::before { content: "!"; font-weight: 800; }
.field .hint { margin: 6px 0 0; font-size: 12.5px; color: var(--muted); }

/* A tick-box and its sentence are one line, not a label with a stranded square
   under it (#454). `flex-start` rather than `center` for the reason `label.ack`
   gives: the sentence wraps in a narrow column, and a centred box would then
   float beside the gap between the two lines rather than sit on the first. */
.field.tick > label { display: flex; align-items: flex-start; margin: 0; }
.field.tick > label input[type="checkbox"] { margin-top: 2px; }


/* Room for the sticky action bar to float over, rather than over a field. */
form:has(.actionbar) > .formgrid:last-of-type,
form:has(.actionbar) > fieldset:last-of-type { margin-bottom: 24px; }

/* An extracted drawing rather than the drawn fallback. It has its own colours
   and its own white ground, so none of the ink-and-blob rules apply to it. */
.art.art-photo { opacity: 1; max-width: 260px; mix-blend-mode: multiply; }

/* A quote's working: what it was, and how it got there. */
.working { margin: 12px 0 0; font-size: 13.5px; }
.working > div {
  display: grid; grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: 8px 18px; padding: 6px 0; border-bottom: 1px solid var(--line-2);
}
.working > div:last-child { border-bottom: none; }
.working dt { color: var(--muted); margin: 0; }
.working dd { margin: 0; font-variant-numeric: tabular-nums; }
@media (max-width: 560px) { .working > div { grid-template-columns: minmax(0, 1fr); gap: 0; } }


.pdf-do { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 0; }

/* The party's review page carries a document and a list of terms side by side.
   `narrow` is 760px, which split into two columns gave two columns too narrow
   to read either — the address wrapped one word per line. */
.page.narrow:has(.review) { max-width: 1120px; }

/* A country beside a number. The picker takes what it needs and the number
   takes the rest, so a long country name never squeezes the digits. */
/* The gap between one field and the next. It used to live on `.formgrid > *`,
   so a form NOT laid out in the grid — three password boxes in one column —
   had its fields touching. It belongs to the field. */
.field { margin: 0 0 18px; min-width: 0; }
.formgrid > p { margin: 0 0 18px; }

.field .control { display: block; }
/* A country beside a number. The picker was `flex: 0 1 11rem`, which let it
   SHRINK — and in a narrow column it shrank until "New Zealand +64" read
   "New Zealand" with the code cut off, which is the one part of the label the
   field exists to show. It does not shrink now; below about a phone width the
   number wraps under it instead, which costs a line and loses nothing.
   The width is set by the longest label the list can produce — see
   `dialling.SHORT`, which is what keeps that from being "United Arab
   Emirates +971". */
.field .control:has(.dial) { display: flex; gap: 8px; flex-wrap: wrap; }
.field .control select.dial { flex: 0 0 14rem; }
/* The number asks for as much room as the picker takes, so that when the two
   together will not fit it WRAPS rather than shrinking — which is what the
   comment above has always said should happen and what `11ch` was too small to
   make true: at a phone width the number sat beside the picker at 110px, about
   eight digits of a ten digit number (#442). It still grows past this wherever
   there is room. */
.field .control:has(.dial) input { flex: 1 1 14rem; min-width: 0; }

/* ------------------------------------------------------------------ *
 * The profile
 *
 * One column of small cards rather than two columns of mixed ones. The
 * page is a series of separate decisions — who you are, your details,
 * how you sign in, what the account belongs to — and each gets a card
 * of its own in the order somebody thinks about them.
 * ------------------------------------------------------------------ */

.page-narrow { max-width: 720px; margin: 0 auto; }
.page-narrow .card { margin: 0 0 14px; padding: 20px 22px; }
.sub { font-size: 14px; color: var(--ink-2); margin: 0 0 22px; }

.card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 0 0 16px;
}
.card-head h2 { margin: 0; font-size: 15px; }
.card-head .hint { font-size: 12px; color: var(--muted); margin: 0; }
/* A quiet way out of the panel, not a call to action competing with its
   heading. */
.card-head a.hint { color: var(--ink-2); text-decoration: none; }
.card-head a.hint:hover { color: var(--accent); text-decoration: underline; }

/* Who you are, at the top. */
.identity { display: flex; align-items: center; gap: 18px; }
.identity-name {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 3px; font-size: 19px; font-weight: 650;
}
.identity-mail { margin: 0; font-size: 13.5px; color: var(--ink-2); }
.avatar-form { margin: 0; flex: none; }
.picture-swap .avatar.big { width: 68px; height: 68px; font-size: 22px; }
.picture-swap { overflow: visible; }
/* A badge, not a full cover: the picture stays visible and the camera says it
   can be changed. The cover version hid the face on hover, which is the one
   thing somebody is looking at. */
.picture-badge {
  position: absolute; right: -3px; bottom: -3px; width: 26px; height: 26px;
  border-radius: 50%; background: var(--card); border: 1px solid var(--line);
  display: grid; place-items: center; color: var(--ink-2); opacity: 1;
}
.picture-badge svg { width: 14px; height: 14px; }
.picture-swap:hover .picture-badge,
.picture-swap:focus-within .picture-badge { background: var(--accent); color: #fff; }

/* What is missing, and why it matters. */
.nudge {
  display: flex; align-items: center; gap: 11px; margin: 0 0 14px;
  background: var(--amber-wash); border: 1px solid #f0e3cb; border-radius: 12px;
  padding: 12px 16px; color: var(--amber-ink); font-size: 13.5px;
}
.nudge svg { width: 18px; height: 18px; flex: none; }
.nudge-text { flex: 1; }
.nudge-count { font-size: 12px; opacity: .8; white-space: nowrap; }

.card-foot {
  display: flex; align-items: center; gap: 14px;
  margin: 20px 0 0; padding: 16px 0 0; border-top: 1px solid var(--line);
}
.card-foot .state { font-size: 13px; color: var(--muted); }
.card-foot .state.dirty { color: var(--amber-ink); font-weight: 600; }

/* A thing you can change, and the button that changes it. */
.lrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--line);
}
.lrow:last-child { border-bottom: none; padding-bottom: 0; }
.lrow-title { margin: 0; font-size: 14px; font-weight: 500; }
.lrow-detail { margin: 2px 0 0; font-size: 13px; color: var(--ink-2); }

.kv { display: flex; justify-content: space-between; gap: 16px; font-size: 13.5px; padding: 7px 0; }
.kv .k { color: var(--ink-2); }
.kv .v { font-weight: 500; }

/* A button that reads as a link, for an action that undoes rather than does. */
.linkish {
  background: none; border: none; padding: 0; margin: 8px 0 0;
  color: var(--accent); font: inherit; font-size: 13px; cursor: pointer;
  text-decoration: underline;
}

@media (max-width: 560px) {
  .lrow { flex-direction: column; align-items: stretch; }
  .identity { flex-wrap: wrap; }
}

/* The section title inside the wizard panel. An `h2` for the outline's sake,
   at the size the heading of a panel wants to be. */
.step-title { margin: 0 0 2px; font-size: 19px; }

/* The page's own heading row: what this is, and the one action it offers. */
.pagehead { margin: 0 0 16px; }
.pagehead h1 { margin: 0; }

/* A caption under a chart. Separated from the labels it would otherwise sit on
   top of, and quiet — the chart is the point, this is the footnote. */
.chart + .helper, .donut + .helper {
  margin: 16px 0 0; padding-top: 14px; border-top: 1px solid var(--line);
}

/* A picture, not a letter. Without `object-fit` a non-square upload is squashed
   into the circle rather than cropped to it. */
img.avatar { object-fit: cover; }

/* Edge draws its OWN reveal button inside every password box as soon as
   something is typed, so the sign-in page showed two eyes: ours and the
   browser's (#77). Chrome and Firefox draw none, which is why it was only ever
   reported on Edge. Hidden here rather than lived with, because the two do not
   agree — ours is keyboard-labelled, says "Show password"/"Hide password" to a
   screen reader, and is not drawn at all when the script is absent.

   Scoped to `.js-reveal`, which `live-check.js` sets on the root element as it
   draws ours: with the script blocked there is no eye of ours to replace it
   with, and a box with none at all is worse than the two this hides.

   On the INPUT rather than on `.control.reveal input`: a password box that has
   no eye of ours must not be left with the browser's instead, and every box in
   the product now has ours. */
.js-reveal input[type="password"]::-ms-reveal { display: none; }
/* ------------------------------------------------------------------ *
   A state's note under a field, and the notice one word of it opens
   (#51, #60). Shown with `:target` rather than a script: the sentence,
   the notice and the OK button all have to be readable, and a control
   that cannot work without JavaScript is not drawn without it here.
 * ------------------------------------------------------------------ */
.noticeword { color: var(--accent-ink); text-decoration: underline; }

.noticebox { display: none; }
.noticebox:target {
  display: flex; position: fixed; inset: 0; z-index: 60;
  align-items: center; justify-content: center; padding: 24px;
  /* Dark enough to say the page behind it is waiting, light enough to keep the
     form readable — nothing has been lost, and OK puts you back on the field. */
  background: rgba(7, 30, 72, .45);
}
.noticebox-card { max-width: 560px; margin: 0; box-shadow: var(--shadow); }
.noticebox-card h2 { margin: 0 0 10px; font-size: 18px; }
.noticebox-card p { margin: 0; color: var(--ink-2); }
/* The bar this borrows is the sticky one at the foot of a form: inside a card
   that floats already, its blur and its hairline read as a second footer. */
.noticebox-card .actionbar {
  margin: 18px 0 0; padding: 0; position: static;
  background: none; border-top: 0; backdrop-filter: none;
}

/* The agreement, read as a document (#104, #111).
 *
 * It has to LOOK like the document it is, because a party checking their own
 * agreement is reading a deed, not a web page: a sheet with margins, serif
 * text, justified paragraphs where the template justifies them, and the
 * bulleted conditions as bullets. The formatting is carried across from the
 * .docx rather than invented here — alignment, headings, lists — so a template
 * counsel re-lay out reaches the screen looking as they left it.
 *
 * Amber is provenance everywhere else in this product, so it is provenance
 * here too: a value that has moved since the parties were last asked carries
 * the amber wash, and nothing else does. */
.staged-note form { display: inline; margin: 0; }

/* The document is replaced WHOLESALE on every correction — one value is
   printed in more than one place, so htmx swaps the lot. Chrome's scroll
   anchoring picks a node to hold still across a reflow, and when the node it
   picked is inside the subtree being replaced the adjustment it makes is
   wrong: on the real template the page jumped to the very end, every time,
   for any value whose editor is tall enough to matter (#278).

   Measured against try.zerochaos.in before and after: the owner's address,
   the configuration, the furnishing and the parking all threw the reader to
   the bottom of a 9,500px page, and all four hold their place with this.
   Excluding the swapped subtree is exactly what this property is for — the
   rest of the page keeps anchoring as the browser intends. */
/* `#sitting` is here because the swap target MOVED (#304). #296 wrapped the
   document in it so the "not applied yet" banner is redrawn with the values it
   describes — and the element htmx replaces is the one the browser must not
   anchor inside. Excluding the old target and not the new one put the jump
   straight back, reported as "changing values and saving has again started
   going to the end of the page".
 *
 * Both are named rather than the wrapper alone: `#document` is still the
 * target on the STAFF screen, which has no sitting to wrap it. */
#sitting,
#sitting *,
#document,
#document * {
  overflow-anchor: none;
}

.paper {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 6px; padding: 52px 64px; margin: 14px auto 18px; max-width: 940px;
  box-shadow: 0 1px 3px rgba(9, 20, 48, .06), 0 10px 30px rgba(9, 20, 48, .05);
  font-family: Georgia, "Times New Roman", serif; color: var(--ink);
  font-size: 1rem; line-height: 1.7;
}
.paper h1, .paper h2, .paper h3 {
  font-family: inherit; letter-spacing: .02em; margin: 26px 0 10px;
}
.paper h1 { font-size: 1.2rem; text-align: center; text-transform: uppercase; }
.paper h2 { font-size: 1.05rem; }
.paper h3 { font-size: .98rem; }
.paper p { margin: 11px 0; }
.paper .align-justify { text-align: justify; text-justify: inter-word; }
.paper .align-center { text-align: center; }
.paper .align-right { text-align: right; }
.paper .align-left { text-align: left; }
.paper ul.clauses { margin: 10px 0 14px; padding-left: 28px; }
.paper ul.clauses li { margin: 6px 0; }
.paper table { width: 100%; border-collapse: collapse; margin: 14px 0 18px; }
.paper table.particulars td {
  border: 1px solid var(--line-strong); padding: 8px 12px; vertical-align: top;
}
/* A two-column table is a list of particulars, and its labels line up. */
.paper table.particulars tr td:first-child { width: 42%; }
/* Anything else is a LAYOUT table — the signature block is one row of two
   panels — so no borders, no label column, and the columns share the width
   rather than being sized by their contents, which is what let one run off the
   side of the sheet. */
.paper table.grid { table-layout: fixed; }
.paper table.grid td {
  padding: 6px 12px; vertical-align: top; overflow-wrap: anywhere;
}

/* The head of the page: the six facts somebody checks before reading a word. */
.paper .heads {
  margin: 0 0 22px; padding: 16px 20px 16px 38px;
  background: var(--accent-tint); border-radius: 8px; font-family: inherit;
}
.paper .heads li { margin: 5px 0; }

/* A value on the page. The template underlines its own placeholders — that is
   how a deed marks a thing to be filled in — so a value keeps the underline and
   an EDITABLE one is unmistakably offered: a tinted box, a stronger rule under
   it, and a pencil when the pointer is on it. Being shy about this was the
   first thing anybody said about the screen. */
.paper .value {
  border-bottom: 1px solid var(--line-strong); padding: 0 1px;
}
.paper .value.editable {
  cursor: pointer; background: var(--accent-tint);
  border-bottom: 2px solid var(--accent-lift);
  border-radius: 3px 3px 0 0; padding: 1px 4px;
  transition: background .12s ease;
}
.paper .value.editable::after {
  content: "✎"; font-size: .72em; margin-left: 5px; opacity: .55;
  font-family: system-ui, sans-serif; vertical-align: super;
}
.paper .value.editable:hover, .paper .value.editable:focus {
  background: var(--accent-wash); outline: none;
}
.paper .value.editable:hover::after, .paper .value.editable:focus::after { opacity: 1; }
/* Waiting for somebody. The field says its own name, the way the designs draw
   an empty form field — a gap says only that the sentence reads oddly. */
.paper .value.blank { color: var(--ink-2); font-style: italic; }
.paper .value.moved {
  background: var(--amber-wash); box-shadow: 0 0 0 2px var(--amber-wash);
  border-bottom: 2px solid var(--amber);
}
.paper .value.moved.editable { background: var(--amber-wash); }
/* This reader's own correction, not yet applied (#289). It has to be visible
   as UNSETTLED, or somebody walks away believing they changed the agreement.
 *
 * Written as `.paper .value.staged` and placed HERE on purpose (#294). The
 * first version was `.value.staged` higher up the file, which lost twice
 * over: `.paper .value.editable` is more specific AND later, so the staged
 * background never painted and the banner said one thing while the value
 * looked like every other value on the page.
 *
 * The amber is the same one a moved value wears, because it means the same
 * thing to a reader — look at this. The DASHED underline is what separates
 * them: moved is settled and somebody else's, staged is neither. */
.paper .value.staged,
.paper .value.staged.editable {
  background: var(--amber-wash); box-shadow: 0 0 0 2px var(--amber-wash);
  border-bottom: 2px dashed var(--amber);
}
.paper .moved-note, .paper .notice { font-family: system-ui, sans-serif; }
.paper .add-party { font-family: system-ui, sans-serif; margin: 6px 0 16px; }

/* The editor that replaces one value in place. Deliberately compact: it sits
   inside a sentence, and a full-width form would push the paragraph apart. */
.field-editor {
  display: inline-flex; flex-direction: column; gap: 6px; vertical-align: baseline;
  font-family: system-ui, sans-serif; font-size: .9rem;
  background: var(--card); border: 1px solid var(--accent-lift);
  border-radius: 10px; padding: 12px 14px; margin: 2px 0;
  box-shadow: 0 6px 20px rgba(9, 20, 48, .12);
}
.field-editor .field-editor-title { margin: 0; font-weight: 600; color: var(--ink-2); }
.field-editor label { display: flex; flex-direction: column; gap: 3px; }
.field-editor input, .field-editor select, .field-editor textarea { min-width: 220px; }
.field-editor .error { color: var(--bad); }

/* The preview's "everything as entered" tables. Two or three columns of plain
   facts, so they are quiet: no zebra, no borders except the hairline between
   rows, and the label column fixed so the values line up down the page. */
table.entered { width: 100%; border-collapse: collapse; }
table.entered td, table.entered th { padding: 7px 10px; border-bottom: 1px solid var(--line-2); }
table.entered th { text-align: left; color: var(--ink-2); font-weight: 600; }
table.entered td:first-child { width: 34%; color: var(--ink-2); }
.party-heading { margin: 18px 0 6px; font-size: 1rem; }

/* Shown, not taken away (#111).
 *
 * Text that cannot be selected is friction, not protection — view-source,
 * devtools, print and a screenshot all still work, and nothing here should
 * ever be described as preventing copying. What it does do is make casually
 * lifting the clause text awkward, and the watermark makes a copy that does
 * leave traceable to whoever was shown it. */
.paper.watermarked { position: relative; overflow: hidden; }
.paper.watermarked > *:not(.watermark) { position: relative; z-index: 1; }
.paper.watermarked,
.paper.watermarked * { user-select: none; -webkit-user-select: none; }
/* Tiled by laying out even CELLS and rotating each one, rather than rotating
   a grid. A rotated container puts its own layout at an angle to the box it is
   clipped by: the marks bunched down one side, ran into each other, and left
   the right half of the sheet blank. A cell that is a fixed fraction of the
   width, with the text turned inside it, tiles the way a watermark should. */
.watermark {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  display: flex; flex-wrap: wrap; align-content: flex-start;
  opacity: .085; color: var(--navy);
  font-family: system-ui, sans-serif; font-size: 1rem; font-weight: 700;
  letter-spacing: .06em; white-space: nowrap;
}
.watermark span {
  flex: 0 0 33.333%; height: 170px;
  display: flex; align-items: center; justify-content: center;
  transform: rotate(-24deg);
}

/* The editor is the one thing on the page a person types into, so selection
   comes back inside it — a form you cannot select text in is a form you cannot
   correct a typo in. Written to out-specify `.paper.watermarked *` above, which
   otherwise wins and takes the input with it. */
.paper.watermarked .field-editor,
.paper.watermarked .field-editor * { user-select: text; -webkit-user-select: text; }

/* The parties' own clauses, where the document prints them (#104). Set apart
   from the prose around them so it is obvious which text the parties added and
   which came from the template — that distinction matters if anybody ever
   argues about a clause. */
.own-clauses {
  font-family: system-ui, sans-serif; font-size: .94rem;
  margin: 10px 0 18px; padding: 14px 16px;
  border-left: 3px solid var(--accent-wash); background: var(--accent-tint);
  border-radius: 0 8px 8px 0;
}
.own-clause { display: flex; gap: 8px; align-items: baseline; margin: 6px 0; }
.own-clause-number { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.own-clause-text { flex: 1; }
.add-clause { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.add-clause label { color: var(--ink-2); font-weight: 600; }
.add-clause textarea { width: 100%; }
.add-clause button { align-self: flex-start; }
button.linkish {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent-ink); text-decoration: underline; font: inherit;
}

/* The e-stamp paper the agreement is printed on, as a header (#111). Drawn so
 * a reader knows what they are buying — and marked a SPECIMEN while it is one,
 * in the same words the PDF's placeholder page uses. A header that looked like
 * a real certificate is a screenshot waiting to be mistaken for one, which is
 * why the band is loud rather than tasteful. */
.stamp-paper {
  font-family: system-ui, sans-serif; margin: 0 0 22px;
  border: 2px solid var(--navy-3); border-radius: 8px; padding: 16px 18px;
  background:
    repeating-linear-gradient(135deg, rgba(7,30,72,.02) 0 12px, transparent 12px 24px),
    var(--card);
}
.stamp-paper-head { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.stamp-emblem {
  width: 42px; height: 42px; border-radius: 50%; display: grid; place-items: center;
  border: 2px solid var(--navy-3); color: var(--navy); font-size: 1.3rem; font-weight: 700;
}
.stamp-title { margin: 0; font-weight: 700; letter-spacing: .04em; color: var(--navy); }
.stamp-sub { margin: 0; color: var(--ink-2); text-transform: uppercase; letter-spacing: .12em; font-size: .78rem; }
.stamp-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px 18px; margin: 0; }
.stamp-facts div { display: flex; flex-direction: column; }
.stamp-facts dt { color: var(--ink-2); font-size: .74rem; text-transform: uppercase; letter-spacing: .08em; }
.stamp-facts dd { margin: 2px 0 0; font-variant-numeric: tabular-nums; }
.stamp-paper.specimen { border-style: dashed; }
.stamp-warning {
  margin: 12px 0 0; padding: 8px 10px; border-radius: 6px;
  background: var(--amber-wash); color: var(--amber-ink); font-size: .85rem;
}

/* A picture out of the template. Word sizes them for a page; a browser has to
   be told, or a letterhead scanned at 300dpi takes over the screen. */
.paper img { max-width: 100%; height: auto; display: inline-block; }

/* A party's screens (#111). Two widths: a form is narrow, a document is not. */
.page.wide { max-width: 1120px; }

/* The approval screen, rearranged. What a party has to DO was at the bottom of
   a very long page with the whole right-hand side empty; it now sits beside
   the agreement and follows them down it. */
.approve { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 22px; align-items: start; }
.approve .actions { position: sticky; top: 84px; display: flex; flex-direction: column; gap: 14px; }
.approve .actions .card { margin: 0; }
.approve .actions .button, .approve .actions button { width: 100%; }
@media (max-width: 940px) {
  .approve { grid-template-columns: minmax(0, 1fr); }
  .approve .actions { position: static; }
}
/* Everything the document will say, folded away until somebody wants it. The
   ten terms people actually check stay in the open. */
.more-terms > summary {
  cursor: pointer; color: var(--accent-ink); font-weight: 600; padding: 6px 0;
}
.more-terms[open] > summary { margin-bottom: 6px; }

/* The terms and what has happened, side by side under the agreement (#111).
   Two columns of facts read faster than one column twice as long, and the
   history is what tells a party whether anything moved since they last looked. */
.terms-and-history { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 18px; align-items: start; }
.terms-and-history .card { margin: 0; }
.terms-and-history .feed { max-height: 420px; overflow: auto; }
@media (max-width: 1180px) { .terms-and-history { grid-template-columns: minmax(0, 1fr); } }
/* A term that changed is never folded away, and says so where it sits. */
.termlist .term.touched { background: var(--amber-wash); border-radius: 6px; }

/* The invite form on the Users page, folded away (#215). The list is what
   somebody came for; the form is the occasional thing, so it reads as a
   control rather than as the muted aside `details summary` is by default. */
.invite > summary {
  cursor: pointer; font-size: 18px; font-weight: 600; color: var(--ink); margin: 0;
}
.invite[open] > summary { margin-bottom: 10px; }

/* The letter editor (#226). The pane is a contenteditable div the script puts
   in place of a textarea, so it has to LOOK like the textarea it replaced —
   otherwise the screen changes shape when the script loads. */
.lettertools { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.lettertools button {
  font: inherit; padding: 4px 10px; border: 1px solid var(--line);
  background: var(--card); border-radius: 6px; cursor: pointer;
}
.lettertools button:hover { border-color: var(--brand); }
.lettertools button.bold { font-weight: 700; }
.lettertools button.italic { font-style: italic; }
.letterpane {
  min-height: 320px; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--card); overflow-y: auto; line-height: 1.6;
}
.letterpane:focus { outline: 2px solid var(--brand); outline-offset: 1px; }
.letterpane ul, .letterpane ol { padding-left: 22px; }
.letterpane p { margin: 0 0 10px; }

/* Renaming a department opens a box in place (#313). The summary reads as a
   small control, not the muted aside `details summary` is by default, and the
   form takes the whole cell rather than the width of the old name. */
.rename > summary {
  display: inline-block; margin: 6px 0 0; padding: 3px 12px; font-size: 13px;
  color: var(--ink); background: var(--card); border: 1px solid var(--line);
  border-radius: 6px; cursor: pointer; list-style: none;
}
.rename > summary::-webkit-details-marker { display: none; }
.rename > summary:hover { border-color: var(--brand); }
.rename[open] > summary { color: var(--muted); }
.rename-form { margin-top: 8px; display: grid; gap: 8px; max-width: 560px; }
.rename-form input[type=text] { width: 100%; }

/* ------------------------------------------------ preview section cards */

/* One card per section of the preview (#308). A <details> that reads as a
   card: the title and a line of its own values on the summary, so a closed
   card still says what it holds. The marker is ours, because the browser's
   triangle sits differently in every engine. */
.cards-head { margin-top: 26px; }
.cards-head h2 { margin: 0; }
.section-card {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--card); margin: 12px 0;
}
.section-card > summary {
  list-style: none; display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 4px 14px; margin: 0; padding: 14px 16px 14px 40px; position: relative;
  color: var(--ink); font-size: 15px;
}
.section-card > summary::-webkit-details-marker { display: none; }
.section-card > summary::before {
  content: ""; position: absolute; left: 17px; top: 20px; width: 7px; height: 7px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg); transition: transform .15s;
}
.section-card[open] > summary::before { transform: rotate(45deg); top: 18px; }
.section-card > summary:hover { color: var(--ink); background: var(--fill); }
.section-card-title { font-weight: 600; }
.section-card-summary { color: var(--muted); font-size: 13.5px; min-width: 0; overflow-wrap: anywhere; }
.section-card[open] > summary { border-bottom: 1px solid var(--line); }
.section-card > .scroller, .section-card > .row { margin: 0; padding: 12px 16px; }
.section-card > .scroller + .row { padding-top: 0; }
