/* 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; }
.brand-mark {
  width: 26px; height: 26px; border-radius: 7px; background: var(--navy); color: #fff;
  display: grid; place-items: center; font-size: 10px; font-weight: 600; letter-spacing: .02em;
}
.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);
}
.page.bare { padding-left: 28px; max-width: 560px; margin-top: 8vh; }
.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; }

.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); }

/* ---------------------------------------------------------------- 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; }
.primary:disabled, .primary[disabled] {
  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; }
.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); } }
.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; }

/* ------------------------------------------------------------------ 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; }
.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 wordmark goes before the name does: knowing WHO you are signed in as
     matters on a shared laptop in an office; the logo does not. */
  .brand-name { display: none; }
  .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; }

@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; }

/* Property pictures. A grid so raising the limit needs no layout change. */
.shots { list-style: none; margin: 14px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.shots li { display: flex; flex-direction: column; gap: 8px; }
.shots img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: 12px; background: var(--fill);
}

/* 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); }


/* 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; }
.field .control:has(.dial) { display: flex; gap: 8px; }
.field .control select.dial { flex: 0 1 11rem; min-width: 6rem; }
.field .control:has(.dial) input { flex: 1 1 auto; 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; }
