/* ============================================================================
   Mortal Me — signup flow
   Shared stylesheet for the six journey screens.

   COLOUR COMES FROM site/tokens.css, generated by scripts/build-tokens.py from
   the six Style Guide v2 colours and served alongside this file. This
   stylesheet declares no brand colour of its own — it used to, hand-ported
   from the design doc, and it had already drifted from the generated set on
   eight values by the time main landed.

   The generator refuses to write tokens.css if a pair drops below WCAG AA, so
   the floor is enforced upstream rather than re-argued here. What is still
   worth recording is the pairs THIS file creates, which the generator cannot
   see — a translucent card over a Forest ground is not a token pair:

        7.62:1  --sand on --forest-deep                the header band
        5.97:1  --sand @0.95 on the cover card         the cover headline
        5.22:1  --sand @0.86 on the cover card         the cover body
        4.61:1  --sun-pale on the tinted marker        placeholder text

   And one deliberate non-use: tokens.css defines --forest-light as 3.02:1 on
   a FOREST ground (#adbfaa) — on Sand it is 1.85:1. It is not a border or a
   dot colour here; those use --forest.

   The translucent surfaces on the cover are written as color-mix() of a token
   rather than rgba() of its hex components, so they follow the palette
   instead of quietly outliving it.
   ========================================================================== */

:root {
  /* Type. Serif for the personal, sans for the platform —
     docs/design/04-design-system.md, "A conflict worth knowing about". */
  --serif: 'Noto Serif', Georgia, serif;
  --sans: 'DM Sans', system-ui, -apple-system, sans-serif;

  /* WCAG 2.5.5 — 44x44 minimum for anything you tap. */
  --tap: 44px;

  /* A DELIBERATE EXCEPTION to the line above, and the only one.
     The chips in the chosen-values panel are 34px, chosen by the client for
     density in a panel that opens over the list. They are the one control in
     the flow under the floor: every chip duplicates a checkbox that is still
     44px in the grid below, and removing a value is never the only way to do
     anything. Tracked as a launch-review item — see tests/flow.spec.js,
     "the chips are a deliberate exception to the tap floor". */
  --tap-compact: 34px;

  /* The content column for every page from joining onward — `main` uses it as a
     max-width, and .room-shell as its second grid track.

     64rem, not a reading measure, and deliberately the same number as the
     landing page's container (site/landing/index.html): the product reads at
     one width from the first page to the last. A reading measure would be the
     wrong job for this token anyway — prose is capped separately at 62ch (see
     the `p` rule), so the column can be wide without any line getting long.
     What the width is actually for is the rooms: the values, category and
     resource grids are all auto-fill/auto-fit, and at 38rem the values grid
     resolved to two columns on a 1280px screen.

     Both consumers wrap it in minmax(0, ...) or a max-width, so on a narrower
     screen the column takes what there is rather than overflowing. */
  --measure: 64rem;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* The UA's [hidden] rule is a bare attribute selector, so any class that sets
   display beats it — .btn { display: inline-flex } silently un-hides a hidden
   button. Restate it as a floor. */
[hidden] { display: none !important; }

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

body {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--sand);
  color: var(--text-main);
  font-family: var(--serif);
  font-size: 1rem;               /* 16px floor — accessibility, not decoration */
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

/* Available to screen readers, absent from the page. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* main gets focus when the skip link is used; it should not draw a ring when
   clicked into, only when moved to deliberately. */
main:focus { outline: none; }

/* --------------------------------------------------------------------------
   Skip link — first tab stop on every page.
   -------------------------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -4rem;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--sun);
  color: var(--charcoal);
  font-family: var(--sans);
  font-weight: 500;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus { top: 0; }

/* --------------------------------------------------------------------------
   Header band.

   Deep forest, not Sand. The only wordmark asset the client has supplied is
   the REVERSED (light) version — on Sand it is very nearly invisible. Rather
   than recolour a brand asset, this reuses the treatment the brand already
   uses: light mark on forest, exactly as the wireframe's welcome screen and
   sidebar do. Same reasoning as site/landing/index.html.
   -------------------------------------------------------------------------- */
.band {
  position: relative;   /* the room burger is placed against it — see .room-nav-toggle */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem 1.25rem;
  background: var(--forest-deep);
}

.band-wordmark {
  width: min(13rem, 52vw);
  height: auto;
  display: block;
}

/* --------------------------------------------------------------------------
   Cover — the welcome screen, served at staging's ROOT (flow.json gives it a
   `path` of "/").

   The landing page's treatment, carrying the welcome copy. Ported from
   site/landing/index.html, not reinvented: the client's p5 splash beside the
   words on a Sand ground, so the first thing anyone meets on either domain is
   the same composition.

   DESKTOP FIRST, like the landing page. This is the two-column arrangement;
   below 900px the artwork stops being a column and becomes the ground — see
   the media query at the end of this section.
   -------------------------------------------------------------------------- */
body.cover {
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: 2.5rem 3rem;
  background: var(--sand);
  color: var(--text-main);
}

/* The columns size to their content and the pair is centred as a unit —
   stretching them to fill the max-width leaves the lockup drifting off-centre
   on a wide screen, with the words stranded in the middle of empty Sand. */
body.cover main {
  display: grid;
  grid-template-columns: minmax(0, 21rem) minmax(0, 28rem);
  justify-content: center;
  align-items: center;
  gap: clamp(2.5rem, 5vw, 4rem);
  width: 100%;
  max-width: 64rem;
  padding: 0;
  animation: coverRise 0.9s ease-out both;
}

body.cover main > * + * { margin-top: 0; }

/* The Sun disc sits behind the objects. On p5 it is cropped by the top of the
   screen; here it is a complete disc, which is how p20 shows it. */
.hero {
  position: relative;
  display: flex;
  justify-content: center;
  width: 100%;
  max-width: 22rem;
  margin-inline: auto;
}

.hero::before {
  content: '';
  position: absolute;
  top: 2%;
  left: 12%;
  width: 76%;
  aspect-ratio: 1;
  background: var(--sun);
  border-radius: 50%;
}

/* The SVGs carry fill="currentColor", so `color` is what tints them. */
.hero svg {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
  color: var(--forest);
}

.words { display: flex; flex-direction: column; align-items: center; gap: 1.5rem; }

.cover-wordmark { width: min(18rem, 100%); color: var(--charcoal); }
.cover-wordmark svg { display: block; width: 100%; height: auto; }

.cover-card {
  width: 100%;
  padding: 2.5rem clamp(1.5rem, 4vw, 2.75rem);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  text-align: center;
}

.cover-card h1 {
  font-size: clamp(1.5rem, 3vw, 2rem);
  line-height: 1.18;
}

.cover-card p {
  max-width: none;
  margin-top: 1rem;
  color: var(--text-muted);
}

.cover-card .actions { justify-content: center; margin-top: 2rem; }

.cover-card .btn-primary { border-radius: var(--radius-lg); padding: 0.85rem 2rem; }

.cover-signin { font-size: 0.9375rem; font-style: italic; }
.cover-signin a { color: var(--ochre-deep); }

@keyframes coverRise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* Below 900px the artwork stops being a column and becomes the ground.

   Stacking it above the card was the obvious move and the wrong one: the
   splash is a tall portrait (154:262), so at any size worth looking at it
   pushes Begin below the fold — the one thing on this page anyone needs to
   reach — and shrinking it until it fits leaves a thumbnail of the client's
   illustration. As a wash it costs no layout at all, so it can be large.

   It is the SAME <svg>, restyled, not a second copy as a background image.
   flow.css is served verbatim, so a CSS url() here would mean a data URI in
   the stylesheet; and the asset is fill="currentColor", so only the element
   can follow the theme — the Forest rule further down recolours it to Sand.

   Two deliberate differences from the two-column treatment: no Sun disc (the
   circle reads as a shape sitting behind the card rather than as ground), and
   the objects are pushed off the right edge, so the composition arrives from
   the side instead of sitting squarely behind the words. */
@media (max-width: 56.25rem) {
  body.cover { padding: 2rem 1.5rem; }

  body.cover main {
    grid-template-columns: minmax(0, 28rem);
    gap: 2rem;
  }

  /* .hero is a child of main, so the words have to be raised alongside it
     rather than main being raised as a whole — a z-index on the parent makes
     a stacking context that both of them live inside, and the wash wins it. */
  .words {
    position: relative;
    z-index: 1;
  }

  .hero {
    position: fixed;
    inset: 0;
    z-index: 0;
    width: auto;
    max-width: none;
    margin-inline: 0;
    align-items: center;
    justify-content: flex-end;
    opacity: 0.5;
    pointer-events: none;    /* decorative: it never takes a tap */
  }

  .hero::before { display: none; }   /* the Sun disc is not part of the wash */

  .hero svg {
    width: auto;
    height: 90vh;
    margin-right: -14vw;     /* offset right, part of it off the edge */
  }

}

/* --------------------------------------------------------------------------
   Page shell
   -------------------------------------------------------------------------- */
main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 3rem 1.25rem 11rem;   /* bottom clearance for the fixed owl stack */
}

main > * + * { margin-top: 1.5rem; }

/* --------------------------------------------------------------------------
   Room shell — the room nav beside the page.

   Everything under /rooms/ is laid out as two columns: where you are among
   the five rooms, and the room itself. The nav orients and it moves you
   sideways; it never gates. Every room links to every other, in both
   directions, including back to a room you have already been in — non-linear
   navigation is a product rule, not a preference, so there is no locked
   state here and there should never be one.

   DESKTOP FIRST. The base rules are the two-column arrangement; the query at
   the bottom of this block turns the column into a drawer behind a burger,
   sliding in from the left over the room.

   CONTRAST, computed not eyeballed — the pairs this block composes, which the
   token generator cannot see:

     15.31:1  --text-main on --sand              a room you are not in
      6.90:1  --text-muted on --sand             its theme line
     12.98:1  --text-main on --forest-pale       hover
      5.85:1  --text-muted on --forest-pale      its theme line on hover
      5.58:1  --on-forest on --forest            the room you are in
      4.54:1  --on-forest-muted on --forest      its theme line
   -------------------------------------------------------------------------- */
.room-shell {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, var(--measure));
  justify-content: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  padding: 3rem 1.25rem 11rem;   /* bottom clearance for the fixed owl stack */
}

/* main carries its own measure and padding everywhere else; inside the shell
   the grid owns both, or the page would be inset twice over. */
.room-shell > main {
  max-width: none;
  margin: 0;
  padding: 0;
}

.room-nav {
  position: sticky;
  top: 1.5rem;
  align-self: start;
  font-family: var(--sans);       /* navigation is functional UI — Style Guide p12 */
}

.room-nav-title {
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ochre-deep);
}

.room-nav-list {
  margin-top: 0.75rem;
  list-style: none;
  border-top: 1px solid var(--border-mid);
}

.room-nav-item { border-bottom: 1px solid var(--border); }

/* The line between two rooms is also the first room's progress.
   ---------------------------------------------------------------------------
   Every room's 1px hairline is a 3px track of the same colour, filled from the
   left as the room fills — all six, all of the time, the dashboard's being the
   overall one. Empty, it reads as the separator it replaced, which is the
   point: a room you have not started must not look like a room you are behind
   on. "Progress indicators celebrate engagement; they never block, nag or
   shame."

   No percentage, no numbers, and Forest rather than any colour that reads as a
   warning. The state is carried as words inside the link for anyone who cannot
   see the bar; the bar itself is aria-hidden. See flow_manifest.render_room_bar().

     5.58:1  --forest on --sand      the fill against the page
     3.02:1  --forest on --border    the fill against its own track
   Both clear the 3:1 WCAG 1.4.11 asks of a graphical object, and the bar is
   supplementary to the words besides. */
.room-nav-item.has-progress { border-bottom: 0; }

/* The margins are not cosmetic. A bar belongs to the room ABOVE it, so it sits
   close to that room and further from the next — and the room you are standing
   in is a filled Forest block, which a bar flush against it would either
   disappear into or crowd. Hence 3px above and 8px below. */
.room-nav-bar {
  display: block;
  margin: 0.1875rem 0 0.5rem;
  height: 0.1875rem;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}

.room-nav-fill {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--forest);
  transition: width 0.25s ease;
}

/* One pick of fifteen is 6% of a 15rem column — visible, but a sliver. The
   floor keeps the first thing you do looking like something you did. */
.room-nav-fill.is-started { min-width: 0.375rem; }

/* 44px floor is the accessibility minimum, not the target — these are taller.
   Two lines: the room's name, and the client's own theme for it. */
.room-nav-link {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-height: var(--tap);
  padding: 0.625rem 0.875rem;
  color: var(--text-main);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.35;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: background-color 0.15s ease, color 0.15s ease;
}

.room-nav-theme {
  font-weight: 400;
  font-size: 1rem;
  color: var(--text-muted);
}

a.room-nav-link:hover {
  background: var(--forest-pale);
  color: var(--text-main);
}
a.room-nav-link:hover .room-nav-theme { color: var(--text-muted); }

/* A room waiting on the Values Room.
   ---------------------------------------------------------------------------
   Greyed, and greyed rather than hidden or removed: the four rooms are still
   the product, and a menu that grew from two items to six once you finished
   Values would hide what the product is from the person deciding whether to
   start. They are named, readable, and plainly not yet yours.

   NO NOTE AND NO PADLOCK. The grey is the whole of it — any sentence here would
   be ours where the client's copy belongs (cl 9.1), and it would be the same
   sentence four times down a six-item menu. "They never block, nag or shame"
   still governs how this looks even now that it does block, so there is no
   warning colour and no strike-through either.

   --text-subtle, NOT an opacity. It is the greyest this palette has: solved by
   build-tokens.py as the furthest charcoal can be blended toward sand while
   still holding 4.5:1, so it is as faded as a thing anyone has to read can get.
   Fading with `opacity` instead would pull the label and its ground together
   and land wherever the arithmetic fell — and the contrast audit reads `color`,
   not `opacity`, so nothing would catch it. Same reasoning as the disabled
   Continue button; see docs/design/05-rooms.md.

   The theme line goes with it. Left at --text-muted it would be DARKER than the
   room name above it, which is the emphasis upside down.

   Applied by room-lock.js. Without it none of this matches anything: the build
   renders every room open. */
.room-nav-item.is-locked > .room-nav-link,
.room-nav-item.is-locked .room-nav-theme {
  color: var(--text-subtle);
}

.room-nav-item.is-locked > .room-nav-link { cursor: default; }

/* The hover wash says "this will do something". Locked, it will not. */
.room-nav-item.is-locked > a.room-nav-link:hover {
  background: none;
  color: var(--text-subtle);
}
.room-nav-item.is-locked > a.room-nav-link:hover .room-nav-theme { color: var(--text-subtle); }

a.room-nav-link:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}

/* The room you are standing in. Not a link — a link to here is a small lie,
   and one more tab stop that goes nowhere. Marked twice: this fill, and
   aria-current="page" for anyone who cannot see it.

   The Forest is on the ITEM rather than the link, so the room's progress bar —
   which is a sibling of the link — sits INSIDE the highlight rather than under
   it. The bar belongs to the room, so on the room you are in it belongs in the
   block, inset to the same 0.875rem as the text it lines up with. */
.room-nav-item.is-current {
  background: var(--forest);
  border-radius: var(--radius-sm);
  box-shadow: inset 0.25rem 0 0 var(--sun);
  /* PADDING, not a margin on the bar. A last child's bottom margin collapses
     out through its parent, which put the gap outside the block and left the
     bar sitting on the bottom edge — measured, not guessed. */
  padding-bottom: 0.625rem;
  margin-bottom: 0.5rem;         /* the same air the bars leave beneath them */
}

.room-nav-item.is-current > .room-nav-link { color: var(--on-forest); }
.room-nav-item.is-current .room-nav-theme { color: var(--on-forest-muted); }

/* Hover on the room you are in — only the aria-current="location" variant is a
   link at all. The Forest-pale wash the other rooms get would paint over the
   block, so this one underlines instead of lighting up. */
.room-nav-item.is-current > a.room-nav-link:hover {
  background: none;
  color: var(--on-forest);
  text-decoration: underline;
}
.room-nav-item.is-current > a.room-nav-link:hover .room-nav-theme { color: var(--on-forest-muted); }

/* Inside the block the ground is Forest, so the bar's colours invert: a deeper
   Forest track, and the Sun that already marks this item as the one you are on.

     4.05:1  --sun on --forest-deep     the fill against its own track
     3.32:1  --sun on --forest          the fill against the block
   Both clear the 3:1 WCAG 1.4.11 asks of a graphical object. The empty track is
   deliberately quiet against the block — on the room you are standing in, the
   page itself is the fuller answer, and a loud groove would compete with the
   Sun edge that says "you are here". */
.room-nav-item.is-current > .room-nav-bar {
  margin: 0 0.875rem;
  background: var(--forest-deep);
}

.room-nav-item.is-current .room-nav-fill { background: var(--sun); }

/* The way to your account, under the rooms.
   ---------------------------------------------------------------------------
   In the menu, not of it. The five rooms are fixed elements (cl 2.3) and the
   list above is exactly them, so this is a sibling of the list rather than a
   sixth item in it — in the markup as well as here, which is what keeps it
   from being announced as a sixth room (flow_manifest.render_account_link()).

   Quieter, and quieter by WEIGHT AND COLOUR RATHER THAN SIZE: 1rem is the
   product's body-text floor and the account page is where somebody goes to
   sign out, which is no place to be reading small print. So it is muted text
   at regular weight against the rooms' medium, and it keeps the same 44px
   target and the same focus ring every room link has.

     6.90:1  --text-muted on --sand    the resting link
    15.31:1  --text-main on --sand     hover and the page you are on
   Both clear WCAG AA for body text. */
.room-nav-minor {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-mid);
}

.room-nav-minor-link {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0.5rem 0.875rem;
  color: var(--text-muted);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.35;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: background-color 0.15s ease, color 0.15s ease;
}

a.room-nav-minor-link:hover {
  background: var(--forest-pale);
  color: var(--text-main);
}

a.room-nav-minor-link:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
}

/* Standing on it. The rooms mark the current one with a Forest block and a Sun
   edge; this marks it with weight and the Sun edge alone — the same vocabulary,
   a quieter sentence in it, and no second Forest block competing with the one
   a room gets. */
[aria-current='page'].room-nav-minor-link {
  color: var(--text-main);
  font-weight: 500;
  box-shadow: inset 0.25rem 0 0 var(--sun);
}

/* The burger, in the header band. Hidden on a desktop, where the menu is
   simply the left-hand column and a button to reveal it would be a button to
   reveal something already there.

   Absolutely placed rather than laid out in the flex row, so the wordmark
   stays centred on the band whether or not a page has rooms to open. It takes
   its colour from the band, which is Forest by default and Sand under the
   Sand theme — hence currentColor on the bars.

     7.62:1  --sand on --forest-deep     the burger on the Forest band
     5.58:1  --forest on --sand          the burger on the Sand band */
.room-nav-toggle { display: none; }

.room-nav-burger {
  position: relative;
  width: 1.25rem;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}

/* The other two lines. One element, three bars — nothing to keep in sync. */
.room-nav-burger::before,
.room-nav-burger::after {
  content: '';
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}

.room-nav-burger::before { top: -0.375rem; }
.room-nav-burger::after { top: 0.375rem; }

/* Tablet and phone: the column becomes a drawer behind the burger, sliding in
   from the left over the room rather than pushing it down. A menu of six rooms
   above every page is six rooms of scrolling before the room itself; the
   drawer keeps the whole screen for what you came to read, and keeps every
   room one tap away.

   The drawer is off-canvas, not display:none — it slides, and something that
   is not rendered cannot. room-nav.js takes it out of the tab order with
   `inert` while it is closed, which is the part CSS cannot do. */
@media (max-width: 50rem) {
  .room-shell {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .room-nav-toggle {
    display: inline-flex;
    position: absolute;
    left: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    align-items: center;
    justify-content: center;

    /* Square, and the accessibility minimum in both directions — the icon is
       small, the target is not. */
    width: var(--tap);
    height: var(--tap);
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--on-forest);
    cursor: pointer;
  }

  :root[data-theme='sand'] .room-nav-toggle { color: var(--forest); }

  .room-nav-toggle:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
  }

  .room-nav {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;

    /* The desktop column sets align-self:start so it sits at the top of its
       grid row. Box alignment applies to positioned boxes too, so left in
       place it stops top:0/bottom:0 from stretching the drawer and the panel
       ends wherever the last room does. */
    align-self: stretch;
    z-index: 30;
    width: min(20rem, 84vw);
    padding: 1.5rem 1.25rem;
    overflow-y: auto;
    background: var(--sand);
    border-right: 1px solid var(--border-mid);
    box-shadow: var(--shadow-lg);
    transform: translateX(-100%);

    /* Hidden as well as off-canvas: an off-screen menu that is still
       "visible" is still in the accessibility tree and still announced.
       Visibility waits for the slide out to finish, then switches. */
    visibility: hidden;
    transition: transform 0.25s ease, visibility 0s linear 0.25s;
  }

  .room-nav-is-open .room-nav {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.25s ease;
  }

  /* Dims the room behind the drawer, and is the tap that closes it. */
  .room-nav-backdrop {
    position: fixed;
    inset: 0;
    z-index: 25;
    background: color-mix(in srgb, var(--charcoal) 45%, transparent);
  }

  /* The page behind a drawer does not scroll — scrolling it is how a drawer
     loses the place you were reading. */
  .room-nav-is-open { overflow: hidden; }
}

/* The slide is decoration; the drawer must still arrive. */
@media (prefers-reduced-motion: reduce) {
  .room-nav { transition: none; }
}

/* --------------------------------------------------------------------------
   Step indicator for the post-signup screens.

   It orients, it does not gate. Nothing in this product is locked behind
   completion — there is no minimum, no lock and no nagging, and the dots are
   aria-hidden with a real text label beside them so a screen reader gets the
   position as words rather than as decoration.
   -------------------------------------------------------------------------- */
.steps {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: none;
  font-family: var(--sans);
}

.step {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--border-strong);
}

.step.is-done { background: var(--forest); }
.step.is-current { background: var(--sun-deep); width: 1.75rem; border-radius: 999px; }

.steps-label {
  margin-left: 0.375rem;
  font-size: 0.9375rem;
  color: var(--text-muted);
}

.eyebrow {
  font-family: var(--sans);
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ochre-deep);
}

h1 {
  font-size: clamp(1.75rem, 6vw, 2.5rem);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

h2 {
  font-size: clamp(1.375rem, 4.5vw, 1.75rem);
  font-weight: 700;
  line-height: 1.3;
  text-wrap: balance;
}

h3 {
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
}

p { max-width: 40ch; max-width: 62ch; }

/* The lead runs the width of whatever holds it — `main` at var(--measure), or
   the card on the pages where it sits inside one. Without this it inherits the
   62ch above and breaks at half the page while the heading over it runs the
   full measure, which reads as a mistake rather than as a measure. */
.lead { font-size: 1.0625rem; max-width: none; }

/* The paragraph a lead hands off to is the second half of the same opening, so
   it takes the same width. Without this it keeps the 62ch above and breaks
   mid-sentence while the line over it runs on — the break lands wherever the
   count happens to fall, which reads as a decision nobody made. */
.lead + p { max-width: none; }

/* A heading's summary stands over a list that runs the whole column, so it runs
   the whole column too. Held to the 62ch above it broke at half the page under
   a heading and over a list that both ran the full width — the same mistake
   .lead exists to avoid, one level down. */
.section-summary { max-width: none; }

section { margin-top: 3rem; }
section > * + * { margin-top: 1rem; }

hr.rule {
  width: 3.5rem;
  height: 2px;
  border: 0;
  background: var(--sun);
  margin: 2.5rem 0;
}

/* --------------------------------------------------------------------------
   O.W.L. speech bubble — the owl is never AI and never generative (cl 2.3).
   Every line in these bubbles is client-written copy.
   -------------------------------------------------------------------------- */
/* Ochre, not terra. Terra (#c77760) was a wireframe invention and main has
   formally retired it as off-brand; ochre is one of the six Style Guide
   colours and already the flow's accent, so the owl stops being the only
   thing on the page outside the palette. */
.owl-quote {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  padding: 1.25rem 1.5rem;
  background: var(--ochre-pale);
  border-left: 4px solid var(--ochre);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.owl-quote img {
  width: 3.5rem;
  height: 3.5rem;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  background: var(--sand);
}

.owl-quote blockquote {
  font-size: 1.0625rem;
  font-style: italic;
  color: var(--text-main);
}

.owl-quote cite {
  display: block;
  margin-top: 0.5rem;
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-style: normal;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   O.W.L.'s introduction — the /owl/ screen only.

   The other screens quote the owl in the callout above. This one introduces
   it, so the owl is drawn at a size where the client's own line-art is
   actually readable rather than cropped into a 56px circle.

   The drawing is site/assets/owls/standing-right.svg — one of the seven guides
   on Style Guide v2 p16, extracted, never traced or generated. It is INLINED
   by build-flow.py, not linked: it is fill="currentColor", and `color` below is
   the only thing that tints it. An <img> would ignore the theme.

   No ochre fill here. The callout's red block was the loudest thing on the one
   screen whose whole job is a gentle hello; the speech now sits on a sand card
   with a sun rule, which is the same accent the rest of the flow uses.
   -------------------------------------------------------------------------- */
.owl-intro {
  display: flex;
  gap: 1.75rem;
  align-items: center;
}

.owl-intro-figure {
  flex: none;
  color: var(--forest);
  line-height: 0;
}

.owl-intro-figure svg {
  display: block;
  height: 13rem;
  width: auto;
}

/* The animated owl, and the still that stands in for it.
   ------------------------------------------------------
   The figure holds both. Where there is an animation the still is hidden, and
   under prefers-reduced-motion they swap. That swap is not a nicety: an
   animated GIF ignores CSS entirely — animation-play-state does nothing to it —
   so hiding it and showing something else is the only way to stop it.

   The still is also the better reduced-motion state on its own merits. It is
   fill="currentColor", so it keeps the theme's Forest tint; the animation is
   fixed charcoal ink and cannot be recoloured.

   The still is wrapped in .owl-intro-still rather than selected with :has().
   :has() would read better, but a browser that does not support it falls
   through to showing BOTH — two owls side by side — and this audience skews
   older. A class the markup carries cannot fail that way.

   Sized wider than the still, at 15rem against its 13rem: the frame is square
   and has to hold a full wingspan, so an owl scaled to the same box reads
   smaller than the standing drawing it replaces. The speech bubble gets a
   floor of its own below, because a 15rem figure would otherwise squeeze it
   into a column of two-word lines. */
.owl-intro-animation {
  display: block;
  height: 15rem;
  width: auto;
}

.owl-intro-still { display: none; }

@media (prefers-reduced-motion: reduce) {
  .owl-intro-animation { display: none; }
  .owl-intro-still { display: block; }
}

.owl-intro-speech {
  position: relative;
  flex: 1;
  /* A floor, not a width. The client's line is short; broken into a two-word
     column beside a wide figure it stops reading as speech. */
  min-width: 18rem;
  padding: 1.25rem 1.5rem;
  background: var(--sun-pale);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
}

/* The tail — two stacked triangles, the border one behind the fill one, which
   is how you get a 1px outline to continue around a CSS arrow. Decorative and
   inert: it carries no content and nothing keyboard-reachable. */
.owl-intro-speech::before,
.owl-intro-speech::after {
  content: '';
  position: absolute;
  top: 2rem;
  width: 0;
  height: 0;
  border: 0.75rem solid transparent;
  border-left-width: 0;
}

.owl-intro-speech::before { left: -0.75rem; border-right-color: var(--border-mid); }
.owl-intro-speech::after { left: calc(-0.75rem + 1px); border-right-color: var(--sun-pale); }

.owl-intro-speech blockquote {
  font-size: 1.125rem;
  font-style: italic;
  color: var(--text-main);
}

.owl-intro-speech cite {
  display: block;
  margin-top: 0.5rem;
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-style: normal;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Owl treatment comparison — /owl1/ through /owl3/. STAGING FURNITURE.
   Deliberately plain and set apart from the screen above it: it is a reviewer's
   tool sitting on a real page, and it must not read as part of the product.
   Delete this block, OWL_VARIANTS in scripts/build-flow.py and the token in the
   page when a treatment is chosen.
   -------------------------------------------------------------------------- */
.owl-variants {
  margin-top: 3rem;
  padding: 1.25rem 1.5rem;
  border: 1px dashed var(--border-mid);
  border-radius: var(--radius-md);
  font-family: var(--sans);
}

.owl-variants-lede {
  margin: 0 0 0.75rem;
  font-size: 0.9375rem;
  color: var(--text-muted);
}

.owl-variants ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.owl-variants li {
  padding-left: 0.75rem;
  border-left: 3px solid var(--border-mid);
}

.owl-variants [aria-current="page"] { border-left-color: var(--sun); }

.owl-variant-name {
  display: block;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-main);
}

.owl-variant-weight {
  display: block;
  margin-top: 0.125rem;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  color: var(--ochre);
}

.owl-variant-note {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Rooms table — scrolls inside itself rather than pushing the page sideways.
   -------------------------------------------------------------------------- */
.table-scroll { overflow-x: auto; }

table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: 1rem;
}

th, td {
  padding: 0.875rem 1rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}

th {
  font-family: var(--sans);
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  background: var(--sand-deep);
}

tbody th { background: none; text-transform: none; letter-spacing: 0; font-size: 1rem; color: var(--forest); font-weight: 500; }

/* --------------------------------------------------------------------------
   Trust / commitment list
   -------------------------------------------------------------------------- */
/* Capped to the prose measure, so a card's edge lands on the paragraph edge
   above it rather than a third of the page further out.

   The rooms do not settle this either way. The Reflections prompts and the
   Instructions category tiles run the full column, which is right for what
   they hold — a question, a label, a count — and so does the research note on
   Wishes, which has prose in it. What IS consistent is the prose: every
   paragraph in this product caps at 62ch, and .insight-quote blockquote caps
   its text inside a block that does not. These cards are paragraphs with a
   label on top, so they follow the prose. At the full 64rem column the lines here ran to about
   95 characters, which is past the point where the eye loses its place coming
   back to the left. Matching the paragraphs also gives the page one left and
   one right edge all the way down. */
.points { list-style: none; display: grid; gap: 1rem; max-width: 62ch; }

.points li {
  padding: 1.125rem 1.25rem;
  background: var(--forest-pale);
  border-radius: var(--radius-md);
}

.points strong {
  display: block;
  font-family: var(--sans);
  font-weight: 500;
  color: var(--forest);
  margin-bottom: 0.25rem;
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: var(--tap);
  padding: 0.75rem 1.75rem;
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.3;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.btn-primary { background: var(--sun); color: var(--charcoal); }
.btn-primary:hover { background: var(--sun-deep); }

/* Back is a way out, not a way on. No border and no fill, so the eye lands on
   the primary action first — but still a full 44px target, still keyboard
   focusable, and still visibly a control on hover. */
.btn-secondary {
  background: transparent;
  color: var(--forest);
  border-color: transparent;
  padding-left: 1rem;
  padding-right: 1rem;
}
.btn-secondary:hover { background: var(--forest-pale); }

/* A secondary that has to LOOK like a button, because it is the only action in
   its column and a borderless one reads as a line of text there. The 2px is
   already reserved in .btn, so nothing shifts when it fills on hover.

     5.58:1  --forest on --sand    the words and the border */
.btn-outline {
  background: transparent;
  color: var(--forest);
  border-color: var(--forest);
}

.btn-outline:hover { background: var(--forest-pale); }

/* An outlined button waiting on something that does not exist yet — today only
   Edit beside the email address. Greyed the way the primary is: flat token
   colours rather than an opacity, so what is measured is what is drawn, and no
   :hover change, because hovering something that will not go anywhere should
   not suggest it might.

   --text-muted rather than the greyer --text-subtle the locked rooms use: that
   one measures 4.5018:1 here, which passes AA by 0.0018 and is no margin at all
   for a button LABEL. The quiet border carries the "not yet" as much as the
   words do.

     6.90:1  --text-muted on --sand   the words */
.btn-outline[aria-disabled='true'],
.btn-outline[aria-disabled='true']:hover {
  background: transparent;
  color: var(--text-muted);
  border-color: var(--border-mid);
  cursor: not-allowed;
}

.btn:disabled { opacity: 0.55; cursor: not-allowed; }

/* A primary action that is waiting for something — today only "Continue to
   Reflections" on the Values selection screen, which waits for five values.
   values.js sets and clears the attribute; see the gate note in that file.
 *
 * A FLAT TOKEN COLOUR, NOT `opacity`. Fading a filled button fades its text and
 * its ground TOWARDS EACH OTHER, so a large CTA lands near the AA floor at the
 * moment it is least readable — and tests/helpers/audit-contrast.js reads
 * background-color, not opacity, so nothing would catch it. Charcoal on
 * sand-deep is a quiet grey that stays comfortably above 4.5:1 in both
 * treatments, which is why it is spelled out rather than dimmed.
 *
 * No :hover change: hovering something that will not go anywhere should not
 * suggest it might. The cursor says the rest. */
.btn-primary[aria-disabled='true'],
.btn-primary[aria-disabled='true']:hover {
  background: var(--sand-deep);
  color: var(--charcoal);
  cursor: not-allowed;
}

/* --------------------------------------------------------------------------
   Read-aloud — the browser's own voice (Schedule 1; scripts/read_aloud.py)

   OFF UNTIL ASKED FOR (issue #319): the markup ships hidden and read-aloud.js
   shows it only for an account whose Accessibility setting says so, so most
   pages never paint any of this.

   THE BOTTOM-LEFT CORNER, on every page (issue #244) — the one corner free
   at every width: the owl has bottom-right, the chosen-values tab top-right,
   and on a phone the room burger top-left. Fixed, so it is in the same place
   on every page and at every scroll, at the owl's layer; the phone drawer
   (z 30) slides over it as it slides over the owl. The bottom clearance the
   owl stack already asks of every page keeps content out from under it.

   In the DOM it is the second tab stop, after the skip link (read_aloud.py):
   Tab, then Space, reads the page. The outline gets a Sand fill and the
   owl's shadow here, because an outlined button over scrolling text is a
   button whose words the text shows through.

   THE X beside it is the owl bubble's dismiss, made a round Sand chip of its
   own so it clears the 44px floor as a target and reads as one. It hides
   the whole control until the account setting is ticked again.

   No motion of its own beyond the outline's hover fade, which the rule at
   the end quiets under prefers-reduced-motion. The labels carry no drafted
   mark (read_aloud.py says why), so nothing here styles one.
   -------------------------------------------------------------------------- */
.read-aloud {
  position: fixed;
  left: 1.25rem;
  bottom: 1.25rem;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.read-aloud .btn {
  gap: 0.5rem;
  background: var(--sand);
  box-shadow: var(--shadow-md);
}

.read-aloud .btn:hover { background: var(--forest-pale); }

.read-aloud [data-read-aloud-label] {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.read-aloud-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
}

.read-aloud-hide {
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--sand);
  box-shadow: var(--shadow-md);
  color: var(--text-muted);
  font-family: var(--sans);
  font-size: 1.375rem;
  line-height: 1;
  cursor: pointer;
}

.read-aloud-hide:hover { color: var(--text-main); }

/* A phone is the one width where the two corners meet: the button, its X
   and the owl all along one 375px edge. The button gives up its side
   padding and the gap tightens, and the three clear one another with room
   to spare; the label and the 44px floor are untouched. */
@media (max-width: 30rem) {
  .read-aloud { gap: 0.375rem; }
  .read-aloud .btn { padding-inline: 0.875rem; }
}

/* The outline's hover fade is the one thing here that moves. */
@media (prefers-reduced-motion: reduce) {
  .read-aloud .btn { transition: none; }
}

/* Visible focus everywhere. Never remove without replacing. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 3px solid var(--ochre-deep);
  outline-offset: 2px;
  border-radius: 4px;
}

a { color: var(--ochre-deep); text-underline-offset: 0.2em; }

/* --------------------------------------------------------------------------
   Prototype notice.

   The join page asks for a password and stores nothing. Saying so plainly is
   a correctness requirement, not decoration — a realistic-looking password
   field with no disclosure would be a dark pattern.
   -------------------------------------------------------------------------- */
.notice {
  display: flex;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  background: var(--sun-pale);
  border: 2px solid var(--sun-deep);
  border-radius: var(--radius-md);
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-main);
}

.notice strong { font-weight: 500; }

/* Marks copy the client has not yet supplied (contract cl 9.1). Deliberately
   conspicuous — a placeholder that blends in is a placeholder that ships. */
.placeholder {
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--ochre-deep);
  background: var(--ochre-pale);
  padding: 0.125rem 0.5rem;
  border-radius: 4px;
  text-decoration-style: dashed;
}

/* Marks copy WE wrote where the client's is expected (contract cl 9.1).
   Different from .placeholder, which marks copy that is simply missing —
   this marks words that are present but not hers, and therefore need signing
   off before they can ship. */
.drafted {
  background: var(--forest-pale);
  box-shadow: inset 0 -0.55em 0 color-mix(in srgb, var(--forest) 16%, transparent);
}

/* --------------------------------------------------------------------------
   Form
   -------------------------------------------------------------------------- */
/* Capped, while the page around it is not. main is --measure wide and the
   headings, .policy sections and .actions all span it; a text input that did
   the same would be a 1024px-wide box with its label a screen away from its
   end. The audience skews older and the contract asks for the simplest, most
   accessible implementation (cl 6.2) — a form column is the accessible shape,
   whatever the page it sits on is doing. */
.field { display: grid; gap: 0.375rem; margin-top: 1.5rem; max-width: 34rem; }

/* The honeypot on /join/ (see the comment in join/index.html). Off-screen
   rather than display:none — a hidden-by-display field is the first thing a
   form-filling script learns to skip — and clipped to a pixel so it can never
   push the page wide. It contributes nothing to layout, focus or reading. */
.hp {
  position: absolute;
  left: -10000px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.field label {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
}

.field .help {
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);      /* 6.93:1 — not --text-subtle, not --text-ghost */
  max-width: none;               /* the box's width, not the page's 62ch: a helper
                                    line that stops short of the box it explains
                                    reads as a column of its own */
}

/* HOW FULL A BOX IS — field-counters.js writes this into every capped field.
   Top right, on the label's row: the label and the count share the first
   cell, one at each end, the way a heading carries an aside — a label never
   runs that wide, and the box beneath keeps its width. 1rem, not the helper
   line's 0.9375rem: it is watched while somebody types, and 16px is the
   floor. Muted so it is read when wanted and ignored the rest of the time.
   At the cap it turns Ochre and says so in words — a fact, not a fault: no
   red, no border, no disabled button. Nothing here moves, so
   prefers-reduced-motion has nothing to do. */
/* A label CAN run that wide — "Where the password is kept, or who has access"
   in a half-width column does — so it keeps the count's width clear on its
   right and wraps short of it rather than printing underneath it. Room for
   "5,000 of 5,000", the longest count the rooms show. */
.field.has-count > label       { grid-row: 1; grid-column: 1; padding-right: 7.5rem; }
.field.has-count > .field-count {
  grid-row: 1;
  grid-column: 1;
  justify-self: end;
  align-self: start;             /* the label's first line, where the eye starts */
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);      /* 6.93:1 on Sand */
  text-align: right;
  max-width: 65%;                /* over the page's p { max-width: 62ch }; the cap's
                                    sentence wraps on the right rather than run
                                    into the label */
}

.field .field-count.is-full { color: var(--ochre-deep); }

/* THE EXAMPLE UNDER THE HELPER LINE. The client asked for it by name: a member
   number or an HIN is confusing described and obvious shown. Smaller and italic
   so it reads as an aside rather than a second instruction — and used on four
   fields in the whole room, because an example under every box is a wall of
   grey text and the helper lines stop being read at all. */
.field .example {
  font-family: var(--sans);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--text-muted);      /* 6.93:1 — small text, so never --text-subtle */
  margin-top: 0.125rem;
}

/* A CHOICE IS A FIELDSET, and a fieldset must not draw a box around itself here
   — the rows inside it are the visible thing. The legend is the field's label
   and is styled as one. */
.field .choice {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.field .choice > legend {
  padding: 0;
  font-family: var(--sans);
  font-weight: 600;
  color: var(--text-main);
}

/* THE ANSWERS ON ONE ROW. Every choice in the product is three short answers
   — online, on paper, with a person — and three rows of them made a short
   question tall. They wrap when they must, so a long answer takes a second
   line rather than a squeeze. The box that belongs under one answer stays
   beneath the row, where it was. */
.choice-options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-top: 0.5rem;
}

.field .choice .consent-line { margin-top: 0; }

/* WHO TO ASK, AS ONE ROW OF CARDS. A card for each person already in
   Professional Contacts and a last card for somebody new: one question, one
   row, where a tick box, a menu, their details and two boxes used to stand.
   It wraps rather than squeezes — on a phone the cards drop to a second line,
   still one row of the same answer. */
/* SOMEBODY NEW IS FIRST, far left, and its chosen card is a tab opening into
   the panel below. wrap-reverse stacks the lines bottom-up, so when a long
   directory wraps, the line holding it is the one that meets the panel — no
   person's card sits between the tab and its boxes. One line reads as wrap. */
.contact-options {
  display: flex;
  flex-wrap: wrap-reverse;
  gap: 0.5rem 0.75rem;
  margin-top: 0.75rem;
}

/* A card is a label around a native radio, so the group is a group to the
   keyboard and the screen reader. The radio covers the whole card, invisible,
   which is what makes the card the target rather than a dot beside it. */
.contact-option {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-height: var(--tap);
  padding: 0.375rem 1rem 0.375rem 0.375rem;
  background: var(--sand);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-family: var(--sans);
  cursor: pointer;
}

.contact-option:hover { background: var(--forest-pale); }

.contact-option input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* Chosen: Forest, the colour every other chosen thing in the rooms wears.
   Marked by a class contacts.js keeps in step with the radio, so the look
   does not depend on :has(). */
.contact-option.is-picked {
  background: var(--forest-pale);
  border-color: var(--forest);
  box-shadow: inset 0 0 0 1px var(--forest);
}

.contact-option:focus-within {
  outline: 3px solid var(--forest);
  outline-offset: 2px;
}

/* The face on a card is the directory's — the same Forest disc a person's
   own card wears — and not the record card's: an asset never has one, and
   tests/instructions-assets.spec.js holds it to that by class. */
.contact-option-av {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  background: var(--forest);
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--on-forest);
}

/* Somebody new: an empty seat rather than a face. */
.contact-option-new .contact-option-av {
  background: var(--sand-deep);
  border: 1px dashed var(--border-strong);
  color: var(--text-main);
  font-size: 1.25rem;
  font-weight: 500;
}

.contact-option-who {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.contact-option-name {
  font-weight: 600;
  color: var(--text-main);
}

/* WHAT WE HOLD ON THEM — their role, their number — under the name, read
   out of the directory. Never copied into this record: it is theirs and is
   edited there. */
.contact-option-detail {
  font-size: 0.9375rem;
  color: var(--text-muted);      /* 6.93:1 */
}

/* THE BOXES FOR SOMEBODY NEW, IN THE CARD'S OWN COLOURS. Full width, pulled
   up under the row so the chosen "Somebody new" card and the panel beneath
   read as one answer: the same Forest-pale, the same Forest edge. The card
   loses its bottom corners when chosen, so it sits on the panel like a tab. */
.contact-new-panel {
  grid-column: 1 / -1;
  margin-top: -1.5rem;           /* the grid's row-gap, taken back: the panel meets the row */
  padding: 1rem 1rem 1.125rem;
  background: var(--forest-pale);
  border: 1px solid var(--forest);
  border-radius: var(--radius-md);
  /* The tab is the first card, flush left, so its straight edge runs on
     into the panel's: a rounded corner here kinks the line where they meet. */
  border-top-left-radius: 0;
}

.contact-new-panel[hidden] { display: none; }
/* A compact grid's row gap is a rem, so the panel climbs a rem, not more:
   its edge meets the row's bottom rather than crossing the person cards. */
.field-grid.is-compact .contact-new-panel { margin-top: -1rem; }

/* The picker is one fieldset and an error slot: the subgrid tracks between
   them are empty, and the field's own gap across three empty tracks was the
   sliver of daylight between the tab and its panel. */
.field-grid [data-contact-field] { gap: 0; }
.contact-new-panel > .contact-new-grid.field-grid { margin-top: 0; }   /* over .register-form .field-grid */

.contact-option-new.is-picked {
  z-index: 1;                    /* over the panel's top edge, so the tab opens into it */
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: var(--forest-pale);
  box-shadow: none;
  margin-bottom: -1px;
}

/* NOBODY TO PICK FROM: the row is gone, and the boxes are ordinary fields of
   the form — no tint, no edge, no panel to belong to. */
.contact-new-panel.is-alone {
  margin-top: 0;
  padding: 0;
  background: none;
  border: 0;
}

/* A FORM AS ITS OWN PAGE. While one is open, main shows the eyebrow, the
   heading and the register the form belongs to, and that register shows only
   its form: no list, no Add, no links onward. See formPage in contacts.js. */
main.is-form-page > :not(.eyebrow):not(h1):not(.is-form-page-form) { display: none; }
.is-form-page-form > :not([data-register-form]) { display: none; }
/* The page's heading has taken the form's title, so the form's own is not shown twice. */
.is-form-page-form [data-register-form-title] { display: none; }
main > .register-child { margin: 0; padding: 0; border: 0; background: none; }
main h1:focus { outline: none; }

/* THE BUTTON A FIELD SITS BEHIND. Left-aligned on its own row inside the grid,
   so it reads as one more thing you may do rather than as an action on the
   form — Save is the only primary button here, and this must not compete with
   it. It takes the whole row because what it opens does. */
.field-grid .field-reveal {
  grid-column: 1 / -1;
  grid-row: auto;
  justify-content: start;
  margin-top: 0;
}

.field-grid .field-reveal[hidden] { display: none; }

/* Nobody to choose from yet, so nothing to choose with. The line under it says
   where the people come from — see render_contact_field. */
.field select[hidden] { display: none; }

.field input[type='text'],
/* The name editor on /account/ is a bare input in a row rather than a .field,
   so it is named here instead of growing a second copy of these rules. */
.account-editor input[type='text'],
.field input[type='email'],
.field input[type='password'],
.field input[type='search'],
.field input[type='tel'],
.field input[type='date'],
.field textarea,
/* The one select in the flow — which document a page is about. It takes the
   same treatment as the boxes above it: a control that looked like the
   browser's default beside them would read as belonging to a different page. */
.field select {
  min-height: var(--tap);
  padding: 0.625rem 0.875rem;
  border: 2px solid var(--border-mid);
  border-radius: var(--radius-sm);
  background: #fff;
  font-family: var(--sans);
  font-size: 1.0625rem;          /* ≥16px: stops iOS zooming the viewport on focus */
  color: var(--text-main);
}

/* A register's paragraph box — Pet Care's care routine, and nothing else in the
   flow. Resizable vertically only, as the answer editor's is: somebody writing
   at length should be able to open it up, and nobody needs it wider than the
   measure. */
.field textarea {
  resize: vertical;
  min-height: 6rem;
  line-height: 1.5;
}

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

/* Errors are never signalled by colour alone: border weight, an icon glyph and
   a text message all carry it. */
.field input[aria-invalid='true'],
.field textarea[aria-invalid='true'] { border-color: var(--ochre-deep); border-width: 3px; }

.field-error {
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--ochre-deep);
}
.field-error:not(:empty)::before { content: '! '; }

/* The one-time-code field, on /signin/ and /reset/. Designed for the actual
   user (docs/design/05-rooms.md): numerals big enough to check against the
   email at a glance, tabular so six digits sit evenly, and no maxlength —
   a code pasted as "441 587" is judged by its digits, in the script. */
.field input.code-input {
  font-size: 2rem;
  letter-spacing: 0.3em;
  font-variant-numeric: tabular-nums;
  text-align: center;
  max-width: 12ch;
}

/* What the account forms say between steps — sending, checking, and every
   gentle failure. Announced (role="status"), and never only a colour. */
.form-status {
  font-family: var(--sans);
  font-size: 1rem;
  min-height: 1.5em;
  margin-top: 1rem;
}
.form-status.is-error {
  font-weight: 500;
  color: var(--ochre-deep);
}
.form-status.is-error:not(:empty)::before { content: '! '; }

/* --------------------------------------------------------------------------
   Two columns of fields, for a form on the wider page. Joining is the only
   screen with enough fields to want it: the two names on one row, the two
   credentials on the next, rather than four fields running down the left of a
   64rem page.

   SUBGRID, not four independent grids. The four parts of a field — label,
   help, input, error — share the row's tracks with the field beside it, so the
   inputs line up even though "Last Name" carries no help line and the password
   carries two. Aligning the tops instead would step the inputs against each
   other by a line of help text, and aligning the bottoms would move a
   neighbouring input every time an error appeared under one of them.

   The children are placed by class rather than by source order, which is the
   part that makes it work: a field with no help paragraph leaves that track
   empty instead of pulling its input up into it.
   -------------------------------------------------------------------------- */
.field-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 1.5rem;
  row-gap: 1.5rem;
  margin-top: 1.5rem;
}

/* The gap between a field's own four tracks comes from .field's `gap`; the
   1.5rem row-gap above only ever falls between one pair of fields and the
   next, which is why the fields carry no margin of their own in here. */
.field-grid .field {
  grid-row: span 4;
  grid-template-rows: subgrid;
  max-width: none;      /* the column is the measure here, not the field */
  margin-top: 0;
}

.field-grid .field > label        { grid-row: 1; }
.field-grid .field > .help        { grid-row: 2; }
.field-grid .field > input        { grid-row: 3; }
.field-grid .field > textarea     { grid-row: 3; }
/* A dropdown is a box like the others and sits on the box row — the role a
   professional contact holds is the one in the flow. Left out, it lands on the
   help track and rides a line above the field beside it. */
.field-grid .field > select       { grid-row: 3; }
.field-grid .field > .field-error { grid-row: 4; }

/* A FIFTH ROW, AND ONLY WHERE SOMETHING IS IN IT. The example line is on four
   fields in the room, and the subgrid is what keeps the boxes level across a
   pair of fields whose helper lines are different lengths — so the row has to
   be reserved on every field of a form that uses it, not only the ones that
   fill it. Reserved on forms that do NOT use it, it was an empty track with a
   1.5rem row-gap either side: three centimetres of nothing above every tick
   box, bought for a line that was never going to appear. The build adds this
   class when the register declares an example anywhere — see
   render_register_fields. */
/* COMPACT: the title, the box with its (?) beside it, the description behind
   the button. The same four tracks, so a compact pair stays level; the
   description track is empty until it is opened, and the example is part of
   it rather than a fifth row. */
.field-grid.is-compact { row-gap: 1rem; }   /* the description track is usually empty */
.field-grid .field-compact > .field-box      { grid-row: 2; }
.field-grid .field-compact > .field-why-text { grid-row: 3; }

.field-box {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.field-box > input,
.field-box > select,
.field-box > textarea { flex: 1 1 auto; min-width: 0; }

/* A small round question, Forest on Sand, and Forest through when it is open. */
.field-why {
  flex: 0 0 auto;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 1px solid var(--forest);
  border-radius: 999px;
  background: var(--sand);
  color: var(--forest);
  font-family: var(--sans);
  font-size: 1.125rem;
  font-weight: 600;
  cursor: pointer;
}

.field-why:hover { background: var(--forest-pale); }
.field-why[aria-expanded="true"] { background: var(--forest); color: var(--on-forest); }
.field-why:focus-visible { outline: 3px solid var(--forest); outline-offset: 2px; }

.field-why-text { display: grid; gap: 0.125rem; }
.field-why-text[hidden] { display: none; }

/* On a choice or a row of cards the (?) sits at the end of the row. */
.choice-options .field-why,
.contact-choice > .field-why { align-self: center; }

.field-grid.has-examples .field          { grid-row: span 5; }
.field-grid.has-examples .field > .example     { grid-row: 3; }
.field-grid.has-examples .field > input        { grid-row: 4; }
.field-grid.has-examples .field > textarea     { grid-row: 4; }
.field-grid.has-examples .field > select       { grid-row: 4; }
.field-grid.has-examples .field > .field-error { grid-row: 5; }

/* A CHOICE AND A CONTACT PICKER ARE NOT THAT SHAPE. Neither is a label, a
   helper, a box and a complaint in stacked rows — one is a fieldset of
   answers, the other a tick box that reveals a list — so they step out of the
   shared rows entirely and take the width instead. */
.field-grid .field-free {
  grid-row: auto;
  grid-template-rows: none;
}

/* A paragraph box takes the whole row. Pet Care's care routine is the only
   register field in the flow that is neither a line nor a room's own answer
   editor, and half a row is not the measure for a routine somebody writes out. */
.field-grid .field-wide { grid-column: 1 / -1; }

/* One column before the pair gets too narrow to hold an email address. */
@media (max-width: 46rem) {
  .field-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ----------------------------------------------------------------------------
   A FORM IN NAMED SECTIONS. Pet Care's, and the only one so far: sixteen fields
   about one animal, which is four questions — who they are, who will care for
   them, what that person needs to know, and the insurance — rather than one
   long run of boxes.

   Each section carries a .field-grid of its own rather than sharing one, which
   is what keeps the subgrid honest: the fields in a section line up with each
   other, and a section of two lines is not stretched to line up with the
   paragraph box three rows below it.

   The rule is off a fieldset, so the legend is the accessible name of every
   field inside it: "Insurance, Policy number" rather than "Policy number"
   alone, sixteen fields into a form.
   -------------------------------------------------------------------------- */
.field-section { margin-top: 2rem; padding: 0; }

/* The first section sits under the form's own heading, which has set it off
   already. */
.field-section:first-of-type { margin-top: 1.5rem; }

.field-section > legend {
  font-family: var(--sans);
  font-weight: 600;
  color: var(--forest);
  margin-bottom: 0;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
  width: 100%;
}

/* The grid's own top margin is measured from a heading, and the legend's
   padding has already made that gap. */
.field-section > .field-grid { margin-top: 0.75rem; }

/* The same sections, on the page with JavaScript off — a heading above each
   list of what one record holds. */
.records-group { margin-top: 1.75rem; margin-bottom: 0.5rem; }

/* ----------------------------------------------------------------------------
   HOW FAR ALONG ONE RECORD IS — the line under a pet's name on their card.

   The room nav's bar, at card size and with its words showing: an empty bar is
   the colour of the hairline it replaces, the fill is Forest and never a
   warning colour, and there is no percentage. A pet with only a name has to
   look unstarted, not unfinished.
   -------------------------------------------------------------------------- */
.record-progress {
  /* Its own row under the name, whatever else is in the who column: the name
     is what the card is read by, and a count beside it would compete. */
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0.25rem 0 0;
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);          /* 6.93:1, the same floor as field help */
  white-space: nowrap;               /* "5 of 15 recorded" is one phrase */
}

.record-progress-bar {
  display: block;
  flex: 0 1 5rem;
  height: 0.1875rem;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}

.record-progress-fill {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--forest);
  transition: width 0.25s ease;
}

/* One field of sixteen is 6% of a 5rem bar — a sliver. The floor keeps the
   first thing somebody typed looking like something they did. */
.record-progress-fill.is-started { min-width: 0.25rem; }

@media (prefers-reduced-motion: reduce) {
  .record-progress-fill { transition: none; }
}

/* ----------------------------------------------------------------------------
   THE BAR ABOVE A FORM IN SECTIONS. One button per section, the one showing
   marked — and marked by weight and a rule under it as well as by colour, so
   the mark survives being read without colour.

   It wraps rather than scrolling sideways: four headings in the client's own
   words are long ("What their carer needs to know"), and a row that scrolls
   hides the sections it cannot fit.
   -------------------------------------------------------------------------- */
.form-steps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  margin-top: 1.5rem;
  border-bottom: 1px solid var(--border);
}

.form-step {
  min-height: var(--tap);
  padding: 0.5rem 0.75rem;
  border: 0;
  border-bottom: 3px solid transparent;
  background: none;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
  cursor: pointer;
}

.form-step:hover { color: var(--forest); }

.form-step[aria-current='step'] {
  font-weight: 600;
  color: var(--forest);
  border-bottom-color: var(--forest);
}

/* The rule under the current button sits ON the bar's own rule; without this
   the two would stack into a step in the line. */
.form-steps { padding-bottom: 0; }
.form-step { margin-bottom: -1px; }

/* A section shown on its own has its heading in the bar above it, so its
   legend is .visually-hidden in the markup: the same words twice on screen,
   once for a screen reader, which reads the legend with every field inside it
   and cannot see which button is lit. Nothing sets the section off from the
   bar but its own margin now. */
.field-section:first-of-type { margin-top: 0; }
.field-section > .field-grid { margin-top: 1.5rem; }

/* A <legend> is laid out by the fieldset rather than by the flow, and taken
   out of it with `position: absolute` it keeps that static position — which on
   a phone was 40px past the right edge, giving the whole page a sideways
   scroll. Pinned to a fieldset made its containing block, it lands where every
   other .visually-hidden element does: nowhere anybody can see, and nowhere
   that widens the page. */
.field-section { position: relative; }
.field-section > legend.visually-hidden { top: 0; left: 0; }

/* Three-up, for a form whose fields are one question asked three ways — the
   trusted contact, where two-up left the third field alone on its own row.
   The subgrid above does not care how many columns there are: the inputs still
   line up across fields carrying help lines of different lengths.

   Its narrow rule has to sit AFTER the declaration rather than joining the one
   above: same specificity, later wins, media query or not. */
.field-grid-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }

@media (max-width: 46rem) {
  .field-grid-three { grid-template-columns: minmax(0, 1fr); }
}

fieldset { border: 0; }

legend {
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
  margin-bottom: 0.5rem;
}

.consent { display: grid; gap: 0.75rem; margin-top: 0.75rem; }

.consent label {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.75rem;
  align-items: start;
  min-height: var(--tap);
  padding: 0.625rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: 1rem;
  cursor: pointer;
}

.consent label:hover { background: var(--forest-pale); }

.consent input[type='checkbox'] {
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 0.125rem;
  accent-color: var(--forest);
  cursor: pointer;
}

.consent .help { display: block; color: var(--text-muted); font-size: 0.9375rem; }

.fineprint {
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Values selector
   -------------------------------------------------------------------------- */
/* A full-width strip under the header band, outside .room-shell — a list of
   chips is a poor fit for the measure the room reads at, and out here a row
   holds noticeably more, since the strip is not paying for the 15rem nav
   column beside it. Still capped and scrollable: a long
   selection scrolls inside the strip rather than pushing the room down. */
.values-chosen-region { position: relative; }

.values-chosen {
  max-height: 30vh;
  overflow-y: auto;
  padding: 0.75rem clamp(1.25rem, 4vw, 3rem);
  background: var(--sand-deep);
  border-bottom: 1px solid var(--border-mid);
}

/* The handle, on the right and hanging into the room shell's 3rem of top
   padding. Positioned out of flow deliberately: it is a full 44px target that
   costs no layout height, so a collapsed strip is a small tab under the header
   rather than a permanent 44px band across the page.

   Closed it reads "13 Values" — the only place the running count lives since
   the count bar came out of the room. Open, the strip above it is the list, so
   the words go and the caret is the whole control. */
.values-chosen-tab {
  position: absolute;
  top: 100%;
  right: clamp(1.25rem, 4vw, 3rem);
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 4rem;
  min-height: var(--tap);
  padding: 0 0.875rem;
  background: var(--sand-deep);
  border: 1px solid var(--border-mid);
  border-top: 0;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  color: var(--forest);
  cursor: pointer;
}
.values-chosen-tab:hover { background: var(--sun-pale); }

/* Open, the caret is alone in the tab and should sit in the middle of it. */
.values-chosen-tab[aria-expanded='true'] { justify-content: center; padding: 0; }

/* Borders rather than a glyph, for the same reason as the count's caret: it
   cannot be read out as text and cannot go missing with a font. */
.values-chosen-tab-caret {
  width: 0;
  height: 0;
  border-left: 0.4375rem solid transparent;
  border-right: 0.4375rem solid transparent;
  border-top: 0.5rem solid var(--forest);
}
[aria-expanded='true'] .values-chosen-tab-caret { transform: rotate(180deg); }
.values-chosen-empty {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);
  max-width: none;
  /* It stands in the same slot as the chips, so it is centred with them —
     otherwise the strip jumps from corner to middle the moment you pick one. */
  text-align: center;
}

/* Centred, on every page that shows them. The strip runs the full width of the
   window while the values in it are five or ten short words: left-aligned they
   sat in the corner of a very wide band, reading as the start of a list that
   had been cut off. Centred, the row is the strip's content rather than its
   first item. Wrapping is unaffected — a long row still fills and breaks. */
.values-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.3125rem;
  list-style: none;
}

/* 34px, not the usual 44 — see --tap-compact. The type stays at 16px: the chip
   carries a value's name and the audience skews older, so the height comes out
   of the space around the label rather than out of the label. */
.values-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  min-height: var(--tap-compact);
  padding: 0.125rem 0.375rem 0.125rem 0.625rem;
  background: #fff;
  border: 1px solid var(--forest);
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-main);
  cursor: pointer;
}
/* The same chip in a room is a label rather than a control: nothing removes a
   value from Reflections, so it must not offer to. Same shape, same type, no
   pointer and no hover — a chip that looks pressable and is not is a worse
   answer than one that plainly is not. The hover is scoped rather than undone,
   so the static chip needs no background of its own to restate. */
.values-chip:not(.is-static):hover { background: var(--sand-deep); }
.values-chip.is-static { cursor: default; }

/* The cross is decoration — the button is named "Remove Kindness" by its
   aria-label, so the glyph never has to carry the meaning on its own. It is a
   bare glyph rather than a disc: the whole chip is the target, so the disc was
   never the hit area, and it cost ~17px of width on every chip — which is a
   row of them across a panel this size. */
.values-chip-cross {
  color: var(--forest);
  font-size: 1.125rem;
  line-height: 1;
}

.values-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 0.5rem;
  margin-top: 1rem;
}

.values-grid label {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.625rem;
  align-items: center;
  min-height: var(--tap);
  padding: 0.5rem 0.75rem;
  background: #fff;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

.values-grid label:hover { border-color: var(--forest); }

.values-grid input[type='checkbox'] {
  width: 1.375rem;
  height: 1.375rem;
  accent-color: var(--forest);
  cursor: pointer;
}

/* Selection is carried by the checkbox itself plus a border and tint — not by
   colour alone. */
.values-grid label:has(input:checked) {
  background: var(--forest-pale);
  border-color: var(--forest);
  font-weight: 500;
}

/* Values step out of the grid three ways — outside the ten on choose-five,
   outside the search, or on another page. The label is display:grid above,
   which would otherwise win over `hidden`. */
.values-grid label[hidden] { display: none; }

/* The offer to add a value of your own, shown in the grid when a search comes
   back with nothing. Deliberately NOT a tile like the others: it is dashed and
   unfilled because it is not a value yet — it is the outline of one, waiting
   for you. Sun on the plus is the same accent every primary action in the flow
   carries, so it reads as the thing to do here without being a second CTA. */
.values-add {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  min-height: var(--tap);
  padding: 0.5rem 0.75rem;
  background: transparent;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-main);
  text-align: left;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

/* The same 1.375rem square the checkboxes occupy, so the words start on the
   column the values start on and the grid does not look knocked out of line. */
.values-add::before {
  content: '+';
  flex: 0 0 1.375rem;
  font-size: 1.5rem;
  line-height: 1;
  text-align: center;
  color: var(--sun-deep);
}

.values-add:hover {
  background: var(--sun-pale);
  border-color: var(--sun-deep);
}

.values-add[hidden] { display: none; }

/* The lead on the selection screens sits closer than the flow's 1.5rem
   rhythm, on both sides. On the selection screen it reads on from the heading, so the
   gap above it was pulling apart a sentence; and the list below belongs to
   the search box, not to this line. 24px to 16px each way. */
.values-lead { margin-top: 1rem; }
.values-lead + form { margin-top: 1rem; }

/* 1rem, not 1.5: the form carries no padding or border, so this margin
   collapses through its top edge and is what actually sets the gap under the
   lead. Left at 1.5rem it won the collapse and the line above stayed where it
   was. It is also the gap under the message when that is showing. */
.values-filter { margin-top: 1rem; }

/* The result count shares the label's line rather than taking one of its own,
   and keeps the smaller muted .help type so it cannot be read as the heading. */
.values-filter-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 1rem;
}
.values-filter-head .help { margin: 0; }

/* The search belongs to the list under it, so the gap here is a small one —
   enough to separate the control from the values, not enough to read as a
   break between two sections. */
.values-filter + fieldset { margin-top: 0.75rem; }

/* Sixteen to a page — eight rows of two. Previous and Next are disabled at the ends rather
   than hidden, so the control does not change shape under your hand. */
.values-pager {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  justify-content: center;
  margin-top: 1.5rem;
}

.values-pager-pages { display: flex; flex-wrap: wrap; gap: 0.375rem; list-style: none; }

.values-pager-gap {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 0.25rem;
  color: var(--text-muted);
}

.values-page {
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 0.5rem;
  background: transparent;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-main);
  cursor: pointer;
}
.values-page:hover { border-color: var(--forest); }

/* The page you are on is marked by fill and weight as well as by
   aria-current — never by colour alone. */
.values-page[aria-current='page'] {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--sand);
  font-weight: 600;
}

/* aria-live region: announces the cap and progress. */
.values-message {
  margin-top: 1rem;
  padding: 0.875rem 1.125rem;
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-weight: 500;
}
.values-message:empty { display: none; }
.values-message[data-tone='limit'] { background: var(--ochre-pale); color: var(--ochre-deep); }
.values-message[data-tone='progress'] { background: var(--forest-pale); color: var(--forest); }

/* A row of controls is not prose, and must not be measured like it.
   `p` carries the reading measure — 62ch, so a paragraph is read down rather
   than swept across — and .actions is a <p> only because it is a line in the
   page's flow. Held to that measure it had 6px to spare on the tightest walk
   row in Wishes: "Next Ceremony Preference" beside "Back to Ceremony
   Preferences", 548px of 554. Six pixels is not a margin, it is a coin toss —
   the same row wrapped to two lines on Linux, where the identical webfont sets
   a few percent wider, and it would wrap for good the day the client renames a
   section to something longer. Both labels are hers to change.
   The rule above at `.field` already states the intent this restores: main is
   --measure wide and the headings, .policy sections and .actions all span it. */
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-top: 2rem; max-width: none; }

/* --------------------------------------------------------------------------
   The count beside Continue, and the button it fills.

   Built by values.js on the selection screen only — see the comment there for
   why the two parts split the way they do. Both are aria-hidden: the words are
   in the lead line and on the tab, and this is the same state drawn.
   -------------------------------------------------------------------------- */

/* SOLID AT EVERY COUNT. A disc, not a dial: it is a number you read, and a
   part-drawn ring beside a part-filled button would be the same progress said
   twice. 44px so it holds the tap floor as a rhythm even though nothing here
   is tappable. */
.values-counter {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  border-radius: 999px;
  background: var(--forest);
  color: var(--on-forest);
  font-family: var(--sans);
  font-size: 1.125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;  /* 1 → 10 must not shuffle the digit */
}

/* THE BUTTON IS THE PROGRESS BAR. Sun arrives from the left and is complete at
   five, which is the moment the button goes live — so the fill finishing and
   the button lighting up are one event rather than two.

   The layer is a ::before at z-index -1 over an `isolation: isolate` ground:
   below the label, above the button's own background, and unable to fall
   behind the page. Scoped to [data-values-fill], which only values.js sets, so
   no other primary button in the flow can pick this up. */
.btn-primary[data-values-fill] {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.btn-primary[data-values-fill]::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  z-index: -1;
  width: var(--values-fill, 0%);
  background: var(--sun);
  transition: width 0.45s ease;  /* the slow fill; reduced-motion cuts it below */
}

/* The Values intro's way back in, under the Continue row. Revealed by
   initValuesIntro() only once there is something to go back to, so it is a
   shortcut for someone returning and invisible to everyone else. */
.values-resume {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin-top: 1rem;
  font-family: var(--sans);
}
.values-resume a { min-height: var(--tap); display: inline-flex; align-items: center; }

/* --------------------------------------------------------------------------
   The owl companion — one gentle prompt per room, never AI (cl 2.3).
   Present on every page: "click it and your values will appear as a reminder."
   -------------------------------------------------------------------------- */
.owl {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.75rem;
}

.owl-toggle {
  width: 4rem;
  height: 4rem;
  padding: 0;
  border: 3px solid var(--sun);
  border-radius: 50%;
  background: var(--sand);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  overflow: hidden;
}

.owl-toggle img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* O.W.L.'s one gentle prompt for the room — the client's own line, rendered by
   flow_manifest.render_owl() from her five-rooms table. Same speech treatment
   as .owl-intro-speech on /owl/, so the owl sounds like the same owl in both
   places, with the tail turned to point down at the avatar beneath it. */
.owl-bubble {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.875rem;
  /* Wider than it was, because the owl beside the words takes a third of it:
     at 23rem the measure dropped to about six words a line. */
  width: min(28rem, calc(100vw - 2.5rem));
  padding: 1rem 1.25rem;
  padding-right: 2.75rem;   /* clearance for the dismiss, which overlays it */
  background: var(--sun-pale);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.owl-bubble p {
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-main);
}

/* The owl saying it — one of the client's seven guides (Style Guide v2 p16),
   chosen per line at /admin/content/owl/ and named by --owl-pose.

   Painted as a MASK, like .thumb-mask in assets-page.css: the drawings are
   fill="currentColor", and an <img> would resolve that against the SVG's own
   document and come out black rather than Forest. Inlining the SVG instead
   would take a room page from 3 KB gzipped to 38 KB, on every one of the
   thirty-five pages that carry a bubble.

   Inside @supports because the fallback for a browser with no mask support is
   a solid forest rectangle beside the words — worse than no picture at all. */
.owl-bubble-figure { display: none; }

@supports ((mask-image: url("#m")) or (-webkit-mask-image: url("#m"))) {
  /* STRETCH, with a floor. `align-self: stretch` makes the owl as tall as the
     bubble however many lines the prompt runs to — it never sits in a corner of
     a card it is supposed to be standing in — and the min-height is the size it
     is when the words are short, which is most of the time. */
  .owl-bubble-figure {
    display: block;
    flex: 0 0 auto;
    align-self: stretch;
    width: 6.5rem;
    min-height: 9rem;
    background-color: var(--forest);
    -webkit-mask: var(--owl-pose) no-repeat center / contain;
    mask: var(--owl-pose) no-repeat center / contain;
  }
}

/* Tail: two stacked triangles, border behind fill — see .owl-intro-speech. It
   sits toward the right, above the avatar it comes from. Decorative and inert. */
.owl-bubble::before,
.owl-bubble::after {
  content: '';
  position: absolute;
  right: 1.5rem;
  width: 0;
  height: 0;
  border: 0.625rem solid transparent;
  border-bottom-width: 0;
}

.owl-bubble::before { top: 100%; border-top-color: var(--border-mid); }
.owl-bubble::after { top: calc(100% - 1px); border-top-color: var(--sun-pale); }

/* Ships hidden and revealed by owl.js: a × that cannot dismiss anything is
   worse than no ×. Full tap target, drawn as a plain glyph rather than a
   button-looking control — dismissing a gentle prompt should not need a
   decision. */
.owl-bubble-dismiss {
  position: absolute;
  top: 0.25rem;
  right: 0.25rem;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--text-muted);
  font-size: 1.375rem;
  line-height: 1;
  cursor: pointer;
}

.owl-bubble-dismiss:hover { color: var(--text-main); }

/* The entry — 14px up with a simultaneous fade, 400ms ease-out (brief §3).
   Applied by owl.js on arrival and on every recall. */
.owl-bubble.is-entering { animation: owl-bubble-in 400ms ease-out; }

@keyframes owl-bubble-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

.owl-panel {
  width: min(20rem, calc(100vw - 2.5rem));
  max-height: min(24rem, 60vh);
  overflow-y: auto;
  padding: 1.25rem;
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}

.owl-panel h2 {
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
  margin-bottom: 0.75rem;
}

.owl-panel p { font-family: var(--sans); font-size: 0.9375rem; color: var(--text-muted); }

.owl-chips { display: flex; flex-wrap: wrap; gap: 0.4375rem; list-style: none; }

.owl-chips li {
  padding: 0.3125rem 0.75rem;
  background: var(--forest-pale);
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 0.9375rem;
}

footer {
  padding: 2rem 1.25rem 3rem;
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);
  text-align: center;
}

/* --------------------------------------------------------------------------
   Non-negotiable: a product about mortality does not move at people who have
   asked it not to.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* O.W.L.'s bubble arrives without travelling. The rule above already stops
     the animation running, so this is belt and braces — but it is the one
     animation in the flow that fires at a reader rather than in response to
     them, and the brief is explicit that reduced motion replaces it with a
     plain opacity change (05-rooms.md § The Owl). Named separately so the
     14px slide cannot come back through a future edit to the keyframes. */
  .owl-bubble.is-entering { animation-name: owl-bubble-fade; }
}

@keyframes owl-bubble-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (max-width: 32rem) {
  main { padding-top: 2rem; }
  .owl-quote { flex-direction: column; gap: 0.75rem; }

  /* The owl stands above its speech rather than beside it, and comes down a
     size — a 13rem drawing next to a 4rem column of text is a picture with a
     caption, not an introduction. */
  .owl-intro { flex-direction: column; align-items: flex-start; gap: 1.25rem; }
  .owl-intro-figure svg { height: 10rem; }
  .owl-intro-animation { height: 12rem; }
  .owl-intro-speech { min-width: 0; }
  .owl-intro-speech { align-self: stretch; }

  /* Stacked, so the tail points up at the owl rather than sideways at nothing. */
  .owl-intro-speech::before,
  .owl-intro-speech::after {
    top: auto;
    border-left-width: 0.75rem;
    border-right-color: transparent;
  }

  .owl-intro-speech::before {
    left: 2rem;
    bottom: 100%;
    border-top-width: 0;
    border-bottom-color: var(--border-mid);
  }

  .owl-intro-speech::after {
    left: 2rem;
    bottom: calc(100% - 1px);
    border-top-width: 0;
    border-bottom-color: var(--sun-pale);
  }

  /* Still a 44px target, but it covers less of the content it floats over. */
  .owl { right: 0.75rem; bottom: 0.75rem; }
  .owl-toggle { width: 3.25rem; height: 3.25rem; }

  /* A corner with less of it to spare: the bubble takes the width the margins
     leave and the tail follows the smaller avatar in. (Below 640px the brief
     eventually wants the prompt as a card at the top of the room rather than a
     floating one — that belongs with the owl animation work, not here.) */
  .owl-bubble { width: calc(100vw - 1.5rem); padding: 0.875rem 1rem; padding-right: 2.75rem; }
  .owl-bubble::before,
  .owl-bubble::after { right: 1.125rem; }

  /* The owl comes down with the rest of the corner. At 320px the desktop size
     leaves about twenty characters a line for the words, which is a column of
     single words — the picture must not crowd out the sentence it illustrates. */
  .owl-bubble-figure { width: 4.5rem; min-height: 6.25rem; }

  /* Full-width, her longest line runs to three or four rows here, with the
     avatar under it. The desktop clearance leaves that sitting on the room's
     own buttons — measured at 320px, where it overlapped by 65px.
     tests/owl.spec.js holds this. */
  main,
  .room-shell { padding-bottom: 16rem; }
}

/* --------------------------------------------------------------------------
   Policy sections on the Security screen.

   One card per policy with its own consent, so what is being agreed to sits
   next to the agreement rather than behind a link nobody opens.
   -------------------------------------------------------------------------- */
/* The three consents in a row across the foot of the card, rather than down
   it. Three of them stacked full-width in a 64rem card is three long strips of
   mostly empty tint; across, each one is about as wide as the words it
   carries.

   A fieldset, so the group has a name of its own — see the hidden legend in
   secure/index.html and the note there about why the row needs one. */
.consents {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.75rem;
  margin-top: 1.25rem;
}

/* The stack owns the spacing in here; the standalone margin would double it. */
.consents .consent-line { margin-top: 0; }

/* The Security screen's agreement: the documents listed, then the line saying
   Agree and continue agrees to them. Each link sits at the tap floor so it is
   a target, not a line of small print. */
.consent-group {
  margin-top: 1.25rem;
  padding: 0;
  border: 0;
}

.consent-docs {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.125rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.consent-docs .consent-for > * + * { margin-top: 0.375rem; }

.consent-by-button {
  margin-top: 1rem;
  font-family: var(--sans);
  font-size: 1rem;
}

.consent-doc {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 0.875rem;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  color: var(--forest);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  border-radius: var(--radius-sm);
}

.consent-doc:hover { background: var(--forest-pale); }

/* The line that says a policy has changed since this person agreed (#190) —
   shown by consent-form.js under the link, above the box. Ochre is the
   accent, the way the callout tint is used elsewhere, and the words carry
   the meaning: nothing here says it by colour alone. */
.consent-changed {
  margin: 0;
  padding: 0.5rem 0.875rem;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--charcoal);
  background: var(--ochre-pale);
  border-left: 3px solid var(--ochre);
  border-radius: var(--radius-sm);
}

.consent-changed-lead { font-weight: 700; }

/* When a trusted person's key opens: more answers than three to a row, so
   they wrap two or three abreast rather than squeezing to a word a line. */
.consents-wide { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); }

/* One per row before a label loses its second line to the checkbox beside it. */
@media (max-width: 46rem) {
  .consents { grid-template-columns: minmax(0, 1fr); }
}

.policy {
  margin-top: 1.5rem;
  padding: 1.25rem 1.5rem;
  background: #fff;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
}

.policy > * + * { margin-top: 0.75rem; }

.policy h2 {
  font-family: var(--sans);
  font-size: 1.125rem;
  font-weight: 500;
  color: var(--forest);
}

.policy .lead { font-size: 1.0625rem; font-weight: 700; }

/* Selection is carried by the checkbox and the tint, never by colour alone. */
.consent-line {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.75rem;
  align-items: center;
  min-height: var(--tap);
  margin-top: 1rem;
  padding: 0.625rem 0.875rem;
  background: var(--sand);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  cursor: pointer;
}

.consent-line:hover { background: var(--forest-pale); }

.consent-line:has(input:checked) {
  background: var(--forest-pale);
  border-color: var(--forest);
  font-weight: 500;
}

/* Radios get the same box as the checkboxes: /trusted/ asks one question with
   three answers where /secure/ asks three questions with one each, and the row
   is the same row either way. */
.consent-line input[type='checkbox'],
.consent-line input[type='radio'] {
  width: 1.5rem;
  height: 1.5rem;
  accent-color: var(--forest);
  cursor: pointer;
}

/* The trusted-contact trigger group. Unlike the consents, its legend is
   visible — there it would only have repeated the heading above it, here it is
   the question the three answers answer. */
.trigger-group { margin-top: 1.5rem; padding: 0; }

.trigger-group .help {
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);      /* 6.93:1, same floor as the field help */
}

/* The row's own margin already sets it off from the help line above. */
.trigger-group .consents { margin-top: 0.75rem; }

/* The promise under the questions: what no answer above it can switch off. */
form .section-note { margin-top: 1.5rem; font-size: 0.9375rem; }


/* ============================================================================
   ROOM CONTENT — Reflections, Wishes, Instructions, Library

   Read-only, and the styling says so. Nothing in here is a field or a control
   you can answer: a prompt opens its own page, and that page says plainly that
   writing on it is the next build. Nothing pretends to be answered, and a
   resource tile does not open — no destinations have been supplied for those,
   and an href of "#" is a promise the page cannot keep.

   A ROOM IS MANY PAGES: an introduction, a page per heading, a page per
   prompt. So a prompt and a category are LINKS now — they were text and a
   same-page anchor when there was nowhere for either to go.

   Rendered by scripts/room_content.py from site/flow/shared/rooms.json.

   CONTRAST, computed not eyeballed — every pair on the Sand ground:

     16.08:1  --text-main on --sand          prompts, headings, tile titles
      7.25:1  --text-muted on --sand         section notes and metadata
      4.50:1  --text-subtle on --sand        "Not yet answered", solved for AA
      6.15:1  --ochre-deep on --sand         the eyebrow above a tile
   ========================================================================== */

/* The client's own introduction to the room, under the one-line description
   that came from her five-rooms table. Larger than body, smaller than .lead —
   it is the second voice on the page, not the first. */
.lead-room { font-size: 1.0625rem; }

.section-note { font-family: var(--sans); color: var(--text-muted); }

/* What a trusted person can and cannot do (/trusted/): two labelled lists in
   the note's own voice. The label sits tight over its list, the lists sit at
   the body floor — a list somebody is deciding by is read, not skimmed. */
.trusted-means {
  margin-top: 1.5rem;
  /* Two columns, will beside can't, on instruction; one under the other on a
     phone — see the max-width rule below. */
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 2rem;
}
@media (max-width: 46rem) { .trusted-means { grid-template-columns: 1fr; } }
.trusted-means p.section-note { margin: 0.75rem 0 0.25rem; }
.trusted-means ul.section-note { margin: 0; padding-left: 1.25rem; font-size: 1rem; }
.trusted-means li { margin: 0.25rem 0; line-height: 1.5; }

/* A tick before what they will do, a cross before what they cannot — on
   instruction, in place of bullets. Forest and Ochre, the product's own two
   accent roles; the words carry the meaning and the mark only echoes it. */
.trusted-means ul.section-note { list-style: none; padding-left: 1.5rem; }
.trusted-means li { position: relative; }
.trusted-means li::before {
  position: absolute;
  left: -1.5rem;
  font-weight: 700;
}
.trusted-can li::before { content: "\2713"; color: var(--forest); }
.trusted-cannot li::before { content: "\2717"; color: var(--ochre); }

/* Where a prompt stands, on its own page: the same words the list it came from
   showed, in the same quiet colour. Never a warning, never red — an unanswered
   prompt is not a failure state. */
.prompt-status {
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-subtle);
}

.prompt-aside { font-family: var(--sans); font-size: 1rem; }

/* Words that are ours where hers are expected. The .drafted mark inside does
   the visual work; this only gives it room to breathe. */
.drafted-note { font-family: var(--sans); }

/* A heading INSIDE a run of copy rather than one opening a section. The h2
   scale is sized to start a page; used mid-paragraph-run it shouts, and this
   one is a signpost between the opening and the bullets under it. Smaller,
   and still a heading — same face, same weight, still an <h2> in the outline
   so the page structure a screen reader announces is unchanged. */
.h2-quiet { font-size: clamp(1.1875rem, 3.4vw, 1.375rem); }

/* --------------------------------------------------------------------------
   A page set wider than the flow's reading measure.

   Prose caps at 62ch everywhere in this product, which is the comfortable end
   of the readable band and right for a screen you are working down. A page
   that is mostly one passage of reading can take more, and the Values intro
   is asked to.

   80ch IS A CEILING, NOT A PREFERENCE. WCAG 1.4.8 asks for no more than 80
   characters a line, and past it the eye starts losing the beginning of the
   next line on the way back. The column these pages sit in is --measure, 64rem,
   which at this size is about 95 characters — so a page set to its own width
   would be over the line, and this is deliberately short of it.

   Cards come along: .points caps at the prose measure so a card's edge lands
   on the paragraph edge, and widening one has to widen the other or the two
   part company. -------------------------------------------------------------- */
.measure-wide p,
.measure-wide .points,
.measure-wide .disclosure { max-width: 80ch; }

/* --------------------------------------------------------------------------
   A card that folds away.

   <details> and <summary>, so it opens with JavaScript off, sits in the tab
   order, answers Enter and Space, and carries its own open/closed state to a
   screen reader. No script, and no aria-expanded of ours to keep in step.

   It hides an EXPLANATION, never a step. "Nothing is a completion gate" is a
   product rule; a fold that puts an argument within reach rather than in front
   of you every time is not one, and nothing behind it has to be opened to get
   on with the room.
   -------------------------------------------------------------------------- */
.disclosure {
  background: var(--forest-pale);
  border-radius: var(--radius-md);
}

/* The whole head is the target, not just the words in it — 1.125rem of padding
   on a two-line heading clears 44px several times over (WCAG 2.5.5), and a
   press anywhere along the row does what pressing the words does. */
.disclosure > summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.125rem 1.25rem;
  cursor: pointer;
  list-style: none;          /* the default triangle, gone in Firefox */
}

.disclosure > summary::-webkit-details-marker { display: none; }

/* Replaced by one of ours, on the right where the eye ends the line rather
   than in front of the heading where it would push her words off the margin
   every other paragraph keeps. Aligned to the first line of a heading that
   may wrap to two. */
.disclosure-caret {
  flex: none;
  width: 0.6rem;
  height: 0.6rem;
  margin-top: 0.45rem;
  border-right: 2px solid var(--forest);
  border-bottom: 2px solid var(--forest);
  transform: rotate(45deg);
  transition: transform 150ms ease;
}

.disclosure[open] .disclosure-caret { transform: rotate(-135deg); }

/* A product about mortality does not move at people who asked it not to. */
@media (prefers-reduced-motion: reduce) {
  .disclosure-caret { transition: none; }
}

.disclosure > summary > h2 { margin: 0; }

/* What is folded away sits under the head, inside the same card, indented to
   the same edge the heading starts on. */
.disclosure > :not(summary) {
  margin: 0;
  padding: 0 1.25rem 1.25rem;
}

/* The points, inside a card rather than each being one: the labels keep their
   block treatment, the green ground goes, because the card is the ground now
   and a card inside a card of the same colour reads as neither. */
.points-flat { gap: 1.25rem; }

.points-flat li {
  padding: 0;
  background: none;
  border-radius: 0;
}

/* The back half of a heading, set under the front half and smaller: a title
   and then an aside in the same sentence, which is how the Values one is
   written ("…Values-Driven Decisions- You'll wish you'd done this years
   ago!"). It stays inside the <h2> — one sentence, one heading, met whole by a
   screen reader.

   Sized in `em` so it tracks whichever heading scale it sits in, with a hard
   1rem floor: it is a sentence someone reads, and the 16px body minimum is a
   floor this product does not go under (WCAG 2.1 AA, and an audience that
   skews older). On .h2-quiet the em would land at ~17px; the max() is there so
   a future trim to the heading cannot quietly take the aside below 16. */
.h2-aside {
  display: block;
  font-size: max(1rem, 0.78em);
  font-weight: 400;
  margin-top: 0.2em;
}

/* --------------------------------------------------------------------------
   The count.

   It orients, it does not gate — so it is a statement of where you are with a
   sentence beside it saying that starting anywhere is fine. There is no bar
   filling up, deliberately: a bar has an end, and an end invites the feeling
   that you are behind.
   -------------------------------------------------------------------------- */
.room-progress {
  max-width: none;
  font-family: var(--sans);
  font-size: 1.0625rem;
}

.room-progress b { font-size: 1.375rem; color: var(--forest); }

.room-progress .help {
  display: block;
  margin-top: 0.25rem;
  font-size: 1rem;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Prompts and sections.

   A list, because that is what it is. The mark is empty and aria-hidden;
   "Not yet answered" carries the state as words, never as a colour or a shape
   alone. Nothing here is warned about and nothing is red — an unanswered
   prompt is not a failure state, it is Tuesday.
   -------------------------------------------------------------------------- */
.prompt-list {
  list-style: none;
  display: grid;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.prompt-item {
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
}

/* The list of HEADINGS on a room's own page, which is not the same shape as a
   list of prompts. A heading is two or three words, so these sit side by side
   while the measure allows it and fall back to a stack when it does not — the
   same auto-fit rule the Instructions category grid uses, at a wider minimum.
   Prompts keep the one-per-row stack: those are whole sentences. */
.prompt-columns { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }

/* The whole row is the target, not the words inside it: a prompt is a long
   line and a 44px minimum met only by the text would leave most of the tile
   dead to a thumb. Padding rather than a fixed height, so a prompt that wraps
   to three lines grows instead of clipping. */
.prompt-open {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.25rem 0.875rem;
  align-items: baseline;
  min-height: 44px;
  padding: 1rem 1.25rem;
  color: inherit;
  text-decoration: none;
}

.prompt-item:hover { border-color: var(--forest); background: var(--forest-pale); }

/* The focus ring belongs to the row, not to the inner grid — the border is
   what a reader sees as the edge of the thing they have landed on. */
.prompt-item:focus-within { border-color: var(--forest); }

.prompt-mark {
  width: 1.125rem;
  height: 1.125rem;
  border: 2px solid var(--border-strong);
  border-radius: 50%;
}

/* --------------------------------------------------------------------------
   The mark on a room front carries a figure (section-counts.js).

   A heading over a list of prompts is a PROGRESS TRACKER: "2/5" inside a ring
   drawn that far round in Forest, the rest in the ring's own grey. A heading
   over a wall — reflections, envelopes — is a plain COUNTER: the number in a
   filled Forest disc, nothing to be out of. Both are the same 2.25rem disc so
   the two rows of a room line up; the plain prompt rows keep the small mark.

   The ring is a conic gradient on ::before, masked to a 3px band, so the
   figure sits on Sand and stays --text-main on --sand (15.31:1). Complete, and
   the counter once it holds anything, go solid: Sand on Forest (5.58:1) —
   the answered treatment already on the prompt rows, one size up.

   It orients, it does not gate. A full ring is a room answered, not a room
   finished; the copy nowhere says "complete".
   -------------------------------------------------------------------------- */
.prompt-mark[data-count-figure] {
  position: relative;
  display: grid;
  place-items: center;
  align-self: center;
  width: 2.25rem;
  height: 2.25rem;
  border-width: 3px;
  font-family: var(--sans);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text-main);
  font-variant-numeric: tabular-nums;
}

/* The ring, drawn as far round as --done says (0 to 1). Sits over the plain
   border so a browser without conic-gradient still shows the grey ring. */
.prompt-mark[data-count-figure][style*="--done"]::before {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  background: conic-gradient(var(--forest) calc(var(--done, 0) * 360deg), var(--border-strong) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), currentColor calc(100% - 3px + 0.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 3px), currentColor calc(100% - 3px + 0.5px));
}

.is-complete .prompt-mark[data-count-figure],
.is-counted .prompt-mark[data-count-figure]:not([style*="--done"]) {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--sand);
}

.is-complete .prompt-mark[data-count-figure]::before { display: none; }

.prompt-q {
  font-family: var(--serif);
  font-size: 1.0625rem;
  color: var(--text-main);
}

.prompt-state,
.prompt-link,
.prompt-desc {
  grid-column: 2;
  font-family: var(--sans);
  font-size: 1rem;
}

/* The line she wrote under a prompt — what it is asking, in her words. On the
   row it sits between the question and the state; on the prompt's own page it
   is the lead under the heading, in the intro's measure. */
.prompt-desc { color: var(--text-muted); }
.prompt-lead { font-size: 1.0625rem; }

.prompt-state { color: var(--text-subtle); }

/* --------------------------------------------------------------------------
   Answered.

   The one state change in the rooms, and it is deliberately quiet: a filled
   mark and the room's own word in Forest, on the row and on the tile alike.
   It is not a tick, not a green, not a "complete" — celebrating engagement
   without implying a finish line is a product rule, and a row of ticks with
   gaps in it reads as a checklist someone is behind on.

     7.12:1  --forest-deep on --sand    the answered wording
   -------------------------------------------------------------------------- */
.is-answered .prompt-state,
.is-answered .category-state,
.prompt-status.is-answered {
  color: var(--forest-deep);
  font-weight: 500;
}

/* Not a mark that carries a figure: a category card is answered at its first
   record, and its ring says how far on its own (.is-complete, .is-counted). */
.is-answered .prompt-mark:not([data-count-figure]) {
  background: var(--forest);
  border-color: var(--forest);
}

/* --------------------------------------------------------------------------
   The answer editor — every prompt page in the product.

   ONE TEMPLATE, rendered by scripts/room_content.py from `answer` in
   rooms.json and revealed by site/flow/shared/answers.js. The field is a
   paragraph box in Reflections and Wishes, a single line in Instructions, and
   Wishes carries the why behind the wish underneath it.

   The saved answer is set in the SERIF, at reading size, on a surface of its
   own: what someone wrote about their own death is the most important text on
   the page, and it should not come back to them looking like form output.
   -------------------------------------------------------------------------- */
.answer { margin-top: 1.75rem; }

/* The two form controls are the sibling of .field's inputs and take the same
   treatment — same border, same radius, same ≥16px that stops iOS zooming the
   viewport when someone taps into it. */
/* A document's page asks two of its three questions in a paragraph box — why a
   document has not been done, and anything else worth saying — and they take
   the same treatment. Not the same HEIGHT: those are a line or two, not the
   page of prose a reflection is, so they keep the rows the build gave them. */
.field-answer textarea,
.field-answer input[type='text'],
.document-record textarea {
  min-height: var(--tap);
  padding: 0.75rem 0.875rem;
  border: 2px solid var(--border-mid);
  border-radius: var(--radius-sm);
  background: #fff;
  font-family: var(--sans);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--text-main);
}

.field-answer textarea {
  /* Resizable, and vertically only: someone writing at length should be able
     to open the box up, and nobody needs to drag it wider than the measure. */
  resize: vertical;
  min-height: 9rem;
}

.field-answer textarea:hover,
.field-answer input[type='text']:hover,
.document-record textarea:hover { border-color: var(--border-strong); }

/* Resizable vertically, like every other paragraph box in the flow. */
.document-record textarea { resize: vertical; }

/* WHERE THE TYPING WENT, under "Which document is this?" when a document that
   already holds something took its place on screen. Body size and body colour,
   not a helper line's: it is about what somebody wrote. Never display:none —
   a status region nobody renders is a status region nobody hears. */
.document-record .field .documents-kept {
  font-size: 1rem;
  color: var(--text-main);
}

.document-record .field .documents-kept:empty { margin: 0; }

/* Wider than the 34rem form column the joining screens use. This is prose
   someone is writing rather than a name or an email, and a paragraph box at
   form width is a column two-thirds the width of the words above it. Still
   inside the 62ch reading measure. */
.field-answer { max-width: 46rem; }

/* ----------------------------------------------------------------------------
   THE QUESTIONS ONE PROMPT ASKS — `extras` in rooms.json, above the paragraph
   box. The radios themselves are the flow's consent lines and need nothing
   here; this is the BOX drawn around a named group of them, which is what the
   client asked for on the Advance Care Directive: her Power of Attorney block,
   set off from the ACD's own question above it.

   An outline rather than a fill. A filled panel inside a form reads as a
   different kind of thing — a notice, something the page is telling you — and
   this is more of the same form, gathered under a name.
   -------------------------------------------------------------------------- */
.answer-group {
  max-width: 46rem;
  margin-top: 2rem;
  padding: 0.25rem 1.25rem 1.25rem;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
}

/* The group's own name carries the rule under it already; a second one from
   .field-section's legend would stack two lines a padding apart. */
.answer-group > legend {
  padding: 0 0.5rem;
  border-bottom: 0;
  width: auto;
}

/* The first question in the box sits under that name, which has set it off. */
.answer-group .trigger-group:first-of-type { margin-top: 0.5rem; }

/* What was answered to them, read back: the question, then the answer under
   it, in the same pair of voices the writing below uses. */
.answer-extras dt {
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text-muted);
}

.answer-extras dd {
  margin: 0.25rem 0 0;
  font-family: var(--serif);
  font-size: 1.0625rem;
  color: var(--text-main);
}

.answer-extras dt + dd + dt { margin-top: 1rem; }

.answer-saved [data-answer-saved-extras]:not([hidden]) + [data-answer-saved-text]:not([hidden]) {
  margin-top: 1.25rem;
}

.answer-saved {
  max-width: 46rem;
  padding: 1.25rem 1.5rem;
  background: var(--forest-pale);
  border-radius: var(--radius-md);
}

.answer-label {
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--text-muted);
}

.answer-when { font-weight: 400; }

/* pre-wrap, because paragraph breaks someone typed are theirs. */
.answer-text {
  margin-top: 0.5rem;
  font-family: var(--serif);
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--text-main);
  white-space: pre-wrap;
}

.answer-saved [data-answer-saved-why] { margin-top: 1.25rem; }

.answer-saved .actions,
.answer-confirm .actions { margin-top: 1.25rem; }

/* Remove is a secondary button like Edit beside it, not a red one. Nothing
   here is destructive in the way a red button promises: the confirmation is
   what carries the weight, and colour-as-warning would put a scolding note in
   a room about someone's own words. */
.answer-remove { color: var(--ochre-deep); }

.answer-confirm { max-width: 46rem; margin-top: 1.25rem; }

.answer-message {
  margin-top: 0.75rem;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--forest-deep);
}

/* Empty until something is saved — and an empty status region must take up no
   room, or every prompt page carries a blank line it never uses. */
.answer-message:empty { margin-top: 0; }

/* --------------------------------------------------------------------------
   THE CHIP WHERE SAVE STOOD. After a save the form gives way to the writing,
   and the first thing in its actions row is not a button but the answer to
   "did that save?" — the same shape as the Save button it replaces, so the
   eye finds it in the same place. Never a control: no pointer, no focus.

   Four colours, one each: Forest for saved, Sun (dashed, an outline) for a
   write waiting on the service, Ochre for one it refused. The words carry
   the state on their own; the tint and the mark before the words agree
   with them. Text is --text-main on every ground, so no new contrast pair.

     12.98:1  --text-main on --forest-pale    saved
     13.17:1  --text-main on --ochre-pale     refused
   -------------------------------------------------------------------------- */
.save-state {
  pointer-events: none;
  color: var(--text-main);
}
.save-state:empty { display: none; }
.save-state::before { content: ''; flex: none; }

.save-state[data-save-chip='saved'] { background: var(--forest-pale); }
.save-state[data-save-chip='saved']::before {
  width: 0.5rem; height: 0.85rem;
  margin: -0.2rem 0.1rem 0 0;
  border: solid var(--forest);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.save-state[data-save-chip='waiting'] {
  background: transparent;
  border-color: var(--sun-deep);
  border-style: dashed;
}
.save-state[data-save-chip='waiting']::before {
  width: 0.6rem; height: 0.6rem;
  border-radius: 50%;
  border: 2px solid var(--sun-deep);
}

.save-state[data-save-chip='refused'] { background: var(--ochre-pale); }
.save-state[data-save-chip='refused']::before {
  width: 0.6rem; height: 0.6rem;
  border-radius: 50%;
  background: var(--ochre);
}

/* --------------------------------------------------------------------------
   THE SAVE LINE. One line in the page head, always there, saying whether the
   writing is safe (save-line.js). It replaced a banner that pushed the page
   down on every load; this holds its place and changes its words.

   The headline is all that shows. The detail — where "saved" means, what a
   waiting change is waiting for — is a screen reader's always and everyone
   else's on hover or focus: the line is focusable for exactly that reason.

   On a room page it sits under the heading, right-aligned, small. On the
   dashboard it is the hero's top-right corner, over the watermark by
   document order alone (it is appended last), and on a phone — where the
   corner is the greeting's — it drops under the words instead.

     6.30:1  --text-muted on --sun-pale    the words and the detail on the hero
     4.50:1  --text-subtle on --sand       the detail under a heading
   -------------------------------------------------------------------------- */
.save-line {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: baseline;
  column-gap: 0.5rem;
  margin: 0.5rem 0 0;
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);
  text-align: right;
}

.save-line:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.save-line [data-save-line-text] { display: inline-flex; align-items: center; gap: 0.45rem; }

/* The mark before the words: a filled dot, or a ring while waiting. */
.save-line [data-save-line-text]::before {
  content: '';
  flex: none;
  width: 0.55rem; height: 0.55rem;
  border-radius: 50%;
  background: var(--forest);
}
.save-line[data-save-state='unsaved'] [data-save-line-text]::before { background: var(--sun); }
.save-line[data-save-state='waiting'] [data-save-line-text]::before {
  background: transparent;
  border: 2px solid var(--sun-deep);
  box-sizing: border-box;
}
.save-line[data-save-state='refused'] [data-save-line-text]::before { background: var(--ochre); }

/* The detail: read always, seen on hover or focus. Kept in the flow as a
   whole second row so it never pushes the headline about; it takes the
   .visually-hidden geometry until then. */
.save-line small {
  flex-basis: 100%;
  font-size: 0.8125rem;
  color: var(--text-subtle);
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.save-line:hover small,
.save-line:focus-within small {
  position: static;
  width: auto; height: auto;
  margin: 0;
  overflow: visible;
  clip-path: none;
  white-space: normal;
}

/* On the dashboard: the hero's top-right corner. The hero is the positioning
   context already (its watermark is absolute inside it). The attribute is in
   the selector for weight alone: the hero makes every child `relative`
   (.dash-hero > *:not(.dash-hero-mark)), and this must outrank that. */
.dash-hero > .save-line[data-save-line] {
  position: absolute;
  top: 1.25rem;
  right: 1.5rem;
  margin: 0;
  max-width: 18rem;
}
.dash-hero .save-line small { color: var(--text-muted); }

@media (max-width: 32rem) {
  .dash-hero > .save-line[data-save-line] {
    position: relative;
    top: auto; right: auto;
    margin-top: 1rem;
    max-width: none;
    justify-content: flex-start;
    text-align: left;
  }
}

/* --------------------------------------------------------------------------
   Reflections — the prompt strip, and the wall of writing under it.

   A PROMPT IS AN INVITATION, NOT A FORM. One at a time, between two arrows,
   in the serif and at reading size — it is a question somebody is being asked,
   and a list of five with radio marks beside them made it a checklist. What is
   underneath is the client's own line about them: starting points, not
   requirements.

   A CARD CARRIES A NAME AND A DATE and nothing else. Same reasoning as the
   envelopes beneath: a wall of these is read in a room another person may walk
   into, so the writing itself is not previewed — only what its writer called
   it, and when they last touched it.

   The blank one is FIRST and always there, and it is a card rather than a
   button because what it makes is a card.

   No motion in here either, so there is nothing for prefers-reduced-motion to
   turn off.
   -------------------------------------------------------------------------- */
/* THE CARD IS THE DASHBOARD'S HERO, one room in: the same Sun-pale ground, the
   same Life Objects behind the words, the same left-aligned block of type.
   That card is how this product invites somebody to do something, and a prompt
   is an invitation — so it is the same card rather than a second design of one.

   WHAT IT REPLACED was a box with centred text, an arrow on either side of it
   and a filled button underneath: four things arranged around one press, none
   of them lined up with the next. The whole card is the press now.

   No motion anywhere in here, so there is nothing for prefers-reduced-motion
   to turn off. */
.prompt-card {
  position: relative;
  overflow: hidden;
  margin-top: 1.75rem;
  padding: 1.75rem 2rem 1.5rem;
  background: var(--sun-pale);
  border-radius: var(--radius-lg);
}

/* The words sit over the watermark — stacked by document order, no z-index,
   exactly as the dashboard hero does it. Named rather than "everything but the
   mark": the two controls place themselves against the card's own edges, and a
   blanket position: relative here would pull them back into the flow. */
.prompt-card-open,
.prompt-card-said,
.prompt-card-note { position: relative; }

.prompt-card-mark {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  border-radius: inherit;
}

/* Two objects rather than the hero's three: this card is half that one's
   height, and a third shape in it is clutter rather than atmosphere. Drawn
   from the bottom right, bleeding past the edge, so the words never meet
   them — the text column is capped below. */
.prompt-card .life-object:nth-child(1) {
  right: -2.5rem;
  bottom: -3.5rem;
  width: 12rem;
  height: 12rem;
}

.prompt-card .life-object:nth-child(2) {
  right: 6.5rem;
  top: -2rem;
  width: 7rem;
  height: 7rem;
  transform: rotate(-8deg);
}

/* The third is the card's own picture rather than another shape at its edge:
   the largest of them, near the middle and a little right of it, so it reads
   behind the question the way the objects read behind the hero's line — the
   text column stops well before its centre, and at a tenth of Forest it is a
   texture rather than something to look at. */
.prompt-card .life-object:nth-child(3) {
  left: 56%;
  top: 50%;
  width: 15rem;
  height: 15rem;
  transform: translate(-50%, -50%) rotate(5deg);
}

/* Room at the top right for the controls, which are pinned there: the label
   and the question stop before them at every width. */
.prompt-card-said { max-width: 34rem; padding-right: 3.5rem; }

/* THE PRESS IS THE WHOLE CARD, not the words in it: one link, laid over the
   card, so anywhere on the Sun-pale opens the editor on the prompt showing —
   the margins, the space beside the objects, the line under the question. One
   link doing it rather than a click handler on a div, so the card stays one
   thing to a screen reader and to the keyboard; it carries no text and takes
   its name from reflections.js. The two round controls sit above it (z-index
   below) and keep their own presses. */
.prompt-card-open {
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* The card is what answers the pointer, and what carries the focus ring — with
   the elevation token for hover, not a colour of its own: the ground is one of
   the six brand tints and there is no darker Sun-pale to invent for it. */
.prompt-card:has(.prompt-card-open):hover { box-shadow: var(--shadow-md); }

.prompt-card:has(.prompt-card-open:focus-visible) {
  outline: 3px solid var(--forest);
  outline-offset: 3px;
}

.prompt-card-label {
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--forest-deep);
}

/* The prompt, at the size the hero's line is: it is the question this page is
   asking, and the largest thing on the card. */
.prompt-card-text {
  margin-top: 0.5rem;
  font-family: var(--serif);
  font-size: clamp(1.375rem, 3vw, 1.75rem);
  line-height: 1.25;
  text-wrap: balance;
  color: var(--text-main);
}

/* Her line about the prompts, under the question, the way the hero's lead sits
   under its heading. */
.prompt-card-note {
  position: relative;
  max-width: 34rem;
  margin-top: 0.75rem;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

/* ONE ARROW, FORWARDS, PINNED TOP RIGHT. Not in the flow of the card: it is a
   control ON the card rather than a step in what the card says — and the top is
   the only edge that does not move. The client's prompts run from six words to
   nineteen, so the card's height changes with every press; an arrow at the
   bottom walked up and down the page under the thumb that was pressing it.
   44x44, like every other target in the flow. */
.prompt-next {
  position: absolute;
  z-index: 1;             /* over the link covering the card */
  top: 1rem;
  right: 1rem;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--forest-deep);
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: 50%;
  cursor: pointer;
}

.prompt-next:hover { border-color: var(--forest); background: var(--forest-pale); }

/* The X, on the editor's card only: it puts the prompt away. Beside the arrow
   rather than opposite it — both controls belong to the prompt, and the card's
   other three corners are where the words and the objects are — and quieter
   than it, because it is not another way to change the prompt, it is the way to
   be done with it. Still 44x44 to hit. */
.prompt-dismiss {
  position: absolute;
  z-index: 1;             /* over the link covering the card, where there is one */
  top: 1rem;
  right: 1rem;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}

.prompt-dismiss:hover { color: var(--text-main); background: var(--sand); }

/* The X takes the corner; the arrow sits inboard of it. Written this way round
   so the arrow needs no second rule on the wall, where there is no X. */
.prompt-card:has(.prompt-dismiss) .prompt-next { right: 4rem; }

/* On a phone there is no width for a control beside the words: the arrow comes
   back into the flow under them, where the card's height moving under it does
   not matter — it moves with the words it belongs to rather than against them.
   The X keeps the corner, and the watermark keeps one object. */
@media (max-width: 34rem) {
  .prompt-card { padding: 1.5rem 1.25rem; }

  /* One object on a phone. The card is the width of the words there, so the
     two that sit beside them have nowhere to be, and the big one would be the
     whole card rather than a wash behind it. */
  .prompt-card .life-object:nth-child(2),
  .prompt-card .life-object:nth-child(3) { display: none; }
  .prompt-card-said { padding-right: 2.75rem; }

  /* Relative, so it is above the link covering the card by document order
     alone, which is what keeps it pressable. */
  .prompt-next,
  .prompt-card:has(.prompt-dismiss) .prompt-next {
    position: relative;
    z-index: 1;
    top: auto;
    right: auto;
    display: flex;
    margin-top: 1rem;
    margin-left: auto;
  }
}

.entries { margin-top: 1.75rem; }

/* A count of what is there, never a target to reach — there is no "of" in this
   line and there never will be. */
.entries-count {
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

.reflections {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 1rem;
  margin-top: 1rem;
}

/* A page rather than an envelope: squarer, with a ruled line at the top where
   an envelope has its flap. It is the same family of card and deliberately not
   the same object — one is something you wrote, the other something you are
   sending. */
.reflection {
  position: relative;
  display: grid;
  align-content: start;
  gap: 0.375rem;
  min-height: 9rem;
  height: 100%;
  padding: 1.5rem 1.25rem 1.125rem;
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: inherit;
}

/* The ruled line, the way a page has one. Decoration, and aria-hidden. */
.reflection-rule {
  position: absolute;
  inset: 0.75rem 1.25rem auto;
  height: 2px;
  background: var(--ochre-pale);
}

.reflection-title {
  font-family: var(--serif);
  font-size: 1.25rem;
  line-height: 1.3;
  color: var(--text-main);
}

/* Unnamed, a card falls back to the prompt it was written from, and says so by
   being quieter rather than by being blank. */
.reflection-title.is-untitled {
  font-style: italic;
  color: var(--text-subtle);
}

.reflection-meta {
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

.reflection:hover { border-color: var(--forest); background: var(--forest-pale); }

/* The blank one, drawn as the same card before anything is on it. */
.reflection-new {
  align-content: center;
  justify-items: center;
  text-align: center;
  background: transparent;
  border: 2px dashed var(--border-strong);
  box-shadow: none;
}

.reflection-new .reflection-title {
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--forest);
}

/* --------------------------------------------------------------------------
   The editor behind that wall.

   The same form furniture as every other editor in this product — one field
   treatment, not two — with the prompt card above it.
   -------------------------------------------------------------------------- */
.entry-editor .field { max-width: 46rem; }

/* In the editor the card keeps its ground and takes the width of the fields
   under it: the prompt and the writing it is for are one column, and a card
   running the full measure over a 46rem box read as page furniture rather than
   as the question being answered. */
.entry-editor .prompt-card {
  margin-top: 0;
  margin-bottom: 1.75rem;
  max-width: 46rem;
}

/* --------------------------------------------------------------------------
   Messages to loved ones — the wall of envelopes.

   A MESSAGE IS ADDRESSED, and that is the whole design. Every other list in
   these rooms is a list of prompts, all of them the client's and all of them
   the same; this is a wall of things somebody wrote to somebody, so the front
   of each one carries the name it is for and nothing else. What is inside is
   not previewed: a wall of these is read in a room another person may walk
   into, and the recipient's name is the only part of a message to a dying
   person's daughter that belongs on a card.

   The blank one is FIRST and always there — starting a message is the one
   thing this page always offers — and it is drawn as an envelope with nobody's
   name on it rather than as a button, because that is what it makes.

   Drawn in CSS from the six brand colours: no new asset, and the flap and the
   seal tint with the theme. The illustration rule (extract, never trace) is
   about the client's artwork — the wordmark, the owl, the Life Objects — and
   this is interface furniture, not illustration.

   No motion anywhere in here, so there is nothing for prefers-reduced-motion
   to turn off: an envelope that lifted or tilted under the pointer would be
   playing with something that is not a toy.
   -------------------------------------------------------------------------- */
.messages { margin-top: 1.75rem; }

/* A count of what is there, never a target to reach. There is no "of" in this
   line and there never will be — nobody is owed a number of messages. */
.messages-count {
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

.envelopes {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 1rem;
  margin-top: 1rem;
}

.envelope {
  position: relative;
  display: grid;
  align-content: end;
  justify-items: center;
  gap: 0.25rem;
  overflow: hidden;
  /* Tall enough that the name sits clear of the flap and its seal, at any of
     the widths the grid gives a card. */
  min-height: 11rem;
  height: 100%;
  padding: 1.125rem 1.25rem;
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  text-align: center;
  text-decoration: none;
  color: inherit;
}

/* The flap: the top half of the card, folded to a point. Behind everything
   else on the card, and aria-hidden — it is a drawing of an envelope, and a
   screen reader is told the recipient's name instead. */
.envelope-flap {
  position: absolute;
  inset: 0 0 auto;
  height: 48%;
  background: var(--sand-deep);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

/* The seal, at the point of the flap. Ochre, the accent this product uses for
   a mark rather than a warning. */
.envelope-seal {
  position: absolute;
  top: calc(48% - 0.6875rem);
  width: 1.375rem;
  height: 1.375rem;
  background: var(--ochre-pale);
  border: 2px solid var(--ochre);
  border-radius: 50%;
}

/* The name, in the serif and at reading size: it is a person's name, written
   on an envelope, not a row in a table. */
.envelope-name {
  position: relative;
  font-family: var(--serif);
  font-size: 1.25rem;
  line-height: 1.3;
  color: var(--text-main);
}

/* An envelope may be started before it is addressed, and says so in words
   rather than by being blank. */
.envelope-name.is-unaddressed {
  font-style: italic;
  color: var(--text-subtle);
}

.envelope-meta {
  position: relative;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

.envelope:hover { border-color: var(--forest); background: var(--forest-pale); }

/* The blank one. Dashed, unsealed, and with the same flap as the rest — it is
   the same object before anybody has written on it. */
.envelope-new {
  background: transparent;
  border: 2px dashed var(--border-strong);
  box-shadow: none;
}

.envelope-new .envelope-name {
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--forest);
}

/* --------------------------------------------------------------------------
   The composer behind that wall.

   The same form furniture as the answer editor above — one field treatment in
   this product, not two — with the timing choice underneath. Radios rather
   than a select: three choices, each of them worth reading before choosing.
   -------------------------------------------------------------------------- */
/* One column down the composer: the name, the message and the timing all sit
   to the same measure. .field's own 34rem is the joining form's width, and a
   name box two-thirds the width of the box under it reads as a mistake. */
.message-editor .field { max-width: 46rem; }

.field-timing {
  margin-top: 1.5rem;
  padding: 0;
  border: 0;
  max-width: 46rem;
}

.field-timing legend {
  padding: 0;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  color: var(--text-main);
}

.field-timing .help { margin-top: 0.375rem; }

.timing-options {
  display: grid;
  gap: 0.5rem;
  margin-top: 0.875rem;
}

/* A whole row is the target, not the 20px dot on it. */
.timing-options label {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: var(--tap);
  padding: 0.5rem 0.875rem;
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: 1.0625rem;
}

.timing-options label:hover { border-color: var(--forest); background: var(--forest-pale); }

.timing-options input { width: 1.125rem; height: 1.125rem; accent-color: var(--forest); }

.field-date { margin-top: 1rem; }

.field-date input[type='date'] {
  min-height: var(--tap);
  max-width: 16rem;
  padding: 0.625rem 0.875rem;
  border: 2px solid var(--border-mid);
  border-radius: var(--radius-sm);
  background: var(--sand);
  font-family: var(--sans);
  font-size: 1.0625rem;
  color: var(--text-main);
}

/* --------------------------------------------------------------------------
   The research note in Wishes, and the Willow quote in Reflections.

   Both are the client's, both carry their attribution, and both are set apart
   from her room copy so the source is never ambiguous.
   -------------------------------------------------------------------------- */
.insight {
  display: block;
  padding: 1.25rem 1.5rem;
  background: var(--sun-pale);
  border-radius: var(--radius-md);
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-main);
}

.insight-title { font-weight: 500; margin-bottom: 0.375rem; }

/* A figure carries browser margins of its own, and .insight-quote is specific
   enough to beat `main > * + *`. Both are re-stated rather than left to luck. */
.insight-quote {
  margin: 1.5rem 0 0;
  padding-left: 1.25rem;
  border-left: 3px solid var(--sun-deep);
}

.insight-quote blockquote {
  font-family: var(--serif);
  font-size: 1.0625rem;
  font-style: italic;
  max-width: 62ch;
}

.insight-quote figcaption {
  margin-top: 0.5rem;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Instructions: the icon-led category navigation the brief asks for.

   Links, not tabs, and each one goes to that category's own page — they were
   anchors to a section further down one long page when the room was one page.
   Tiles clear the 44px minimum by a wide margin.
   -------------------------------------------------------------------------- */
.category-nav { margin-top: 1.5rem; }

/* HOW MANY TILES ACROSS, in one place, because TWO grids read it: the tiles
   here and the featured card above them, which is one card in a grid of its own
   and must land at exactly a tile's width. auto-fit cannot do that — it
   collapses the tracks a lone card does not fill, and the card stretches across
   the row. A counted template gives both grids the same track.

   THREE ACROSS, on instruction, which is two even rows of the room's six
   categories. It was six across the measure until Key Contacts became two
   headings; six narrow tiles on one row and then a seventh alone underneath is
   an afterthought rather than one of them. */
:root { --category-columns: 3; }

@media (max-width: 56rem) { :root { --category-columns: 2; } }
@media (max-width: 34rem) { :root { --category-columns: 1; } }

.category-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(var(--category-columns), minmax(0, 1fr));
  gap: 0.75rem;
}

.category {
  display: grid;
  gap: 0.25rem;
  height: 100%;
  padding: 1rem 1.125rem;
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  color: var(--text-main);
  text-decoration: none;
}

.category:hover { border-color: var(--forest); background: var(--forest-pale); }

.category-icon { font-size: 1.5rem; line-height: 1; }
.category-label { font-size: 1.0625rem; font-weight: 500; }
.category-state { font-size: 1rem; color: var(--text-subtle); }

/* How much the category holds, in the card's top corner — the ring or disc the
   room fronts draw (.prompt-mark[data-count-figure], above), so a tracker here
   and a tracker on Wishes are one mark. Assets and Documents are "2/5" in a
   ring; the open lists are a number in a disc. Only the placing is the card's.
   aria-hidden — the state words say the same thing. */
.category { position: relative; }

/* Matched to .prompt-mark[data-count-figure]'s weight, which sets the mark
   relative — the ring's ::before hangs off it, and absolute serves it too. */
.category .category-count[data-count-figure] {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
}

/* --------------------------------------------------------------------------
   The lead category: the first card in the grid, and marked as it.

   Documents Whereabouts, on the client's instruction — where the will, the
   Advance Care Directive and the Power of Attorney are is the first thing a
   family needs, and it was one tile among five. It was lifted out onto a
   section above the grid and came back into it when Key Contacts became two
   headings: six categories are two even rows of three, and a card apart from
   them made it three uneven ones.

   Its place is the emphasis — first, top left — and this is one line of border
   on top of that. Nothing here touches the card's size or its type: a card
   bigger than the five beside it said the same thing a second time and louder.
   -------------------------------------------------------------------------- */
.category-featured { border-color: var(--forest); }

.records-label {
  margin-top: 1rem;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  color: var(--forest);
}

/* -----------------------------------------------------------------------------
   A REGISTER — a heading that holds a list of people rather than a list of
   prompts. Key Contacts, and only Key Contacts so far.

   The card is the client's, from the wireframe's .contacts-grid: an initials
   avatar, the name, how you know them, the role as a badge, and — for the
   people who hold a key — when their access opens. Its colours are not: the
   wireframe's palette is a placeholder (docs/design/04-design-system.md) and
   every value here is a token.

   THE PEOPLE YOU TRUST ARE AT THE TOP, and the order is the whole signal. The
   card is marked as well — a Sun edge and a badge — because an order is not
   announced, and someone reading the page down a screen reader would otherwise
   be told five names with nothing to separate the first two from the rest.
   -------------------------------------------------------------------------- */

.register[hidden] { display: none; }

/* The Add button leads the list rather than trailing it: this is the thing to
   do on an empty page, and on a full one it must not move down the screen
   every time somebody is added. */
.register-actions { margin-top: 1.5rem; }

.register-empty {
  margin-top: 1rem;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-subtle);
}

.register-empty[hidden] { display: none; }

/* ONE PERSON, ONE ROW, full width. A directory is read down the names, so the
   names make a column and everything about somebody sits beside theirs. Tiles
   in three columns stacked six lines each and made four contacts a page and a
   half, with the fifth name below the fold — which is the opposite of "the
   people your loved ones will need to find quickly". */
.contact-grid {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 1rem;
  padding: 0;
}

.contact-grid:empty { margin-top: 0; }

/* Three columns and one row: their initials, everything about them, then Edit
   and Remove. The middle column is the only one that grows, so the buttons sit
   in the same place on every card and the avatars line up down the page. */
.contact-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 1rem;
  row-gap: 0.5rem;
  padding: 0.875rem 1.125rem;
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  color: var(--text-main);
}

/* Somebody who holds a key. Not a colour on its own: the badge inside says so
   in words, for everyone the edge is decoration to. */
.contact-card.is-trusted {
  border-color: var(--sun-deep);
  background: var(--sun-pale);
}

.contact-av {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  background: var(--forest);
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--on-forest);
}

/* ONE WRAPPING LINE, and at most two of them. Who they are, then everything
   else about them beside it; when that will not fit, the detail drops beneath
   the name — and only ever there, because each part of it is a few words. */
.contact-body {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.375rem 1rem;
  min-width: 0;
}

/* The column a reader runs their eye down. Wide enough that the names line up
   whatever follows them, and free to grow past that on a name that needs it.

   The name and how you know them sit SIDE BY SIDE — "Margaret Holt Solicitor" —
   rather than stacked. Stacking them made two lines of every card before any of
   the detail was reached, and a list of four people three screens long. */
.contact-who {
  /* 12rem is what it ASKS for, not what it holds on to. Fixed at 0 0 12rem the
     names lined up beautifully and left the detail beside them exactly 448px
     for 444px of content — four pixels, on a row carrying an email address, an
     access line and a key. It went to two rows on Linux, where the same webfont
     sets a few percent wider, and the gap below is already tuned by two pixels
     for the same reason. Free to shrink, this column gives up the slack it is
     not using — a name needs 110px of its 192 — and the row holds. The names
     still line up; they simply stop doing so before the line breaks instead of
     after. */
  flex: 0 1 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.125rem 0.5rem;
  /* The floor the names line up on, and the only part of the 12rem that is
     actually load-bearing. Anything shorter than this sits on the same left
     edge as every other card; anything longer takes the room it needs and gives
     the rest back. */
  min-width: 7rem;
}

/* A SMALL BASIS ON PURPOSE. Sized to its content, the detail's hypothetical
   width decides where the line breaks, and a contact with a role, a phone, an
   address and a key was wide enough to be thrown onto a line of its own —
   three rows for one person. Asking for 18rem and letting it shrink puts it
   beside the name and wraps it inside itself instead. */
.contact-detail {
  /* Asks for what it actually contains, so that when it cannot have it the
     shortfall is felt by .contact-who above — which can spare it — rather than
     swallowed here as a wrapped line. With a 14rem basis the two columns always
     "fitted", and this one quietly broke its own contents onto a second row
     instead.

     And it does not shrink. A flex container that may shrink never puts its
     parent under pressure — it just wraps, quietly, which is precisely the
     failure being fixed. Refusing to, the shortfall passes up to .contact-who,
     which has room to give. When there is genuinely not enough width for both —
     a phone — the whole detail block drops to the next line as one piece, which
     is the wrap that was always wanted. */
  flex: 1 1 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* 0.75rem between the pieces rather than 0.875: with whether they have been
     asked riding in the access line, the wider gap was the two pixels that put
     a trusted person's key on a row of its own. */
  gap: 0.375rem 0.75rem;
  min-width: 0;
}

/* Ochre-deep rather than ochre: Sand on --ochre measures 4.52:1, which passes
   AA by two hundredths and would fail the moment either colour was nudged.
   --ochre-deep is 5.88:1 and reads as the same accent. */
.is-trusted .contact-av { background: var(--ochre-deep); }

.contact-name {
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.3;
}

/* NOTES ON A PERSON: a line under the numbers, the whole width of the
   detail, in the helper's voice. It wraps; it is the one thing on a card
   allowed to. */
.contact-notes {
  flex: 1 1 100%;
  margin: 0;
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);      /* 6.93:1 */
}

.contact-rel,
.contact-reach {
  font-size: 1rem;
  color: var(--text-muted);
}

/* The role, which is what a loved one is looking for: executor, GP, solicitor.
   Inline-start so it sizes to its words rather than the column. */
.contact-badge {
  padding: 0.1875rem 0.625rem;
  background: var(--forest-pale);
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 500;
  color: var(--forest);
}

.is-trusted .contact-badge {
  background: var(--sand);
}

.contact-trigger {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* Tight, because this row now carries three things — the badge, when their
     key turns, and whether they have been asked — and they belong to one
     another. The pixels bought here are what keep them on one line. */
  gap: 0.375rem;
  font-size: 1rem;
  color: var(--text-muted);
}

.contact-trusted {
  padding: 0.1875rem 0.625rem;
  background: var(--ochre-deep);
  border-radius: 999px;
  /* 16px like everything else. A badge is small by its padding, never by
     dropping under the body-text floor — see the accessibility rules. */
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--sand);
}

/* Said only of somebody who has not been asked yet. A REMINDER TO THE PERSON
   WHO NAMED THEM, not a fault on the card: the muted tone of the other detail
   lines rather than a warning colour, because nothing here is wrong.

   Inside the access row — a card is ONE ROW PER PERSON, and this is one more
   thing said about their key rather than a line of its own. The row's own gap
   separates it and the italic marks it apart; the middot the row uses between
   phrases is the width of the words that would not then fit. */
.contact-permission {
  font-size: 1rem;
  font-style: italic;
  color: var(--text-muted);
}

/* Where the invitation stands — Invited, Accepted, Declined — in the access
   row beside the badge. The service's word (scripts/cms/invitations.py), in
   the same muted tone as the permission reminder: a fact about the key, not
   a state to put right. */
.contact-invite {
  font-size: 1rem;
  color: var(--text-muted);
}

.contact-invite::before {
  content: "·";
  margin-right: 0.375rem;
}

/* The third column, so Edit and Remove sit in the same place on every card
   however much is beside them. The 2rem above every .actions row in the flow
   is the space under a whole page; inside a row of a list it is a hole. */
.contact-actions {
  margin-top: 0;
  white-space: nowrap;
}

/* The confirmation takes the whole width beneath the row it belongs to — it is
   a question about that person, not another thing on their line. */
.contact-card .answer-confirm {
  grid-column: 1 / -1;
  margin-top: 0.25rem;
}

/* A RECORD THAT IS NOT A PERSON — a property, a vehicle, a fund. The same card,
   two columns rather than three: there is no face to put in front of it, and
   initials of "14 Bell St" would be a decoration meaning nothing. */
.record-card { grid-template-columns: minmax(0, 1fr) auto; }

/* An institution's card opens its accounts wherever it is pressed, so it
   says so under the pointer. */
.record-card.has-child { cursor: pointer; }
.record-card.has-child:hover { background: var(--forest-pale); }
.record-card.has-child .register-child { cursor: auto; }

/* And its lines are LABELLED, where a contact's are not. A phone number
   announces itself; "ANZ" does not say whether it is the lender or the bank the
   statements come from. So each line reads "Held with · ANZ", and the label is
   the same words as the box it was typed into. */
.record-detail { flex-basis: 20rem; }

/* NOTHING BUT A NAME ON IT — every other field left blank, which is allowed and
   will be common: nothing here is required but the name. The detail column asks
   for its basis whether or not it holds anything, so without these two rules
   "The shack at Aireys Inlet" wraps inside 12rem with an empty half-card beside
   it. A column with nothing in it takes no width, and the name takes the room
   it gives up — but only then, so the names still line up down a card that has
   something beside them. */
.record-detail:empty { flex: 0 0 0; }
.contact-body:has(.record-detail:empty) .contact-who { flex: 1 1 auto; }

.record-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.375rem;
  font-size: 1rem;
  color: var(--text-main);
}

/* Quiet, and beside the value rather than over it — a stack of label-above-value
   pairs would make four short lines into eight, and a list of four properties
   the length of the page. */
.record-label {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-muted);
}

/* The interpunct is CSS, not text: it separates the two for a reader looking at
   the card and stays out of what one read aloud says. The space either side of
   it is a real one, in the markup — see the card in contacts.js. */
.record-label::after { content: '\00a0·'; font-weight: 400; }

/* A PARAGRAPH — Pet Care's care routine, and its insurance note. It goes under
   its label rather than beside it: three sentences wrap under the label anyway,
   and the separator would be left at the end of the line above with nothing
   after it. The lines somebody typed are the lines they meant, so they are
   kept. */
/* ONE ACCOUNT INSIDE AN INSTITUTION, read on the institution's own card. The
   account's name leads and the rest of it follows, so a trusted person ringing
   the bank can see WHICH account before they pick up the phone — which is the
   whole reason the client asked for any of this. No label in front: four labels
   on a line somebody scans turns a card back into a form. */
.record-child-line { flex-basis: 100%; }
.record-child-line .record-label { font-weight: 500; color: var(--text-main); }
.record-child-line .record-label::after { content: none; }

/* The accounts' own register, while it is sitting inside a card. Set in from
   the card's edge and ruled off above, so it reads as part of the institution
   rather than as a second list that happens to be nearby. */
.register-child {
  grid-column: 1 / -1;           /* the card is a three-column grid; this is a row */
  margin-top: 0.5rem;
  padding-top: 1rem;
  padding-left: 1rem;
  border-top: 1px solid var(--border);
  border-left: 2px solid var(--border);
}

.register-child[hidden] { display: none; }

/* A PARAGRAPH TAKES THE WHOLE ROW. The detail column is a wrapping row of
   short lines — "Carer · Sarah Whitcombe   Vet · Coogee Vet Hospital" — and a
   routine of three sentences left the line after it stranded beside it, out in
   the middle of the card. Breaking the row is what a paragraph does everywhere
   else on the page. */
.record-detail .record-note { flex: 1 0 100%; }

.record-note { display: block; }
.record-note .record-label { display: block; }
.record-note .record-label::after { content: none; }
.record-note .record-value { white-space: pre-wrap; }

/* The form is the page's own work, not a layer over it: it opens where the Add
   button was and the list stays visible behind it. */
.register-form {
  margin-top: 1.5rem;
  padding: 1.25rem;
  background: var(--forest-pale);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
}

.register-form[hidden] { display: none; }
.register-form > * + * { margin-top: 1rem; }
.register-form .field-grid { margin-top: 1rem; }

/* A DOCUMENT HAS NO FACE. The same row as a contact, minus the column that
   holds somebody's initials: the name of the document, what is recorded
   against it, then the way into it. Everything else about the row — the
   surface, the padding, the buttons in their own column — is the contact
   card's, because a list is a list. */
.document-card { grid-template-columns: minmax(0, 1fr) auto; }

/* Recorded is a state, never a score. It is marked by the words inside the row
   and by the faintest lift of the surface, and there is nothing on the rows
   without it — six pale cards under a heading would read as six things left
   undone. */
.document-card.is-recorded { border-color: var(--border-strong); }

@media (max-width: 34rem) {
  /* On a phone there is not room for a third column, so Edit and Remove take
     a row of their own under the name and the detail — still two rows, with
     the second one holding everything but the name. */
  .contact-card { grid-template-columns: auto minmax(0, 1fr); }
  .contact-actions { grid-column: 2; justify-self: start; }

  /* One column fewer to start with, so the buttons drop under the first. */
  .document-card { grid-template-columns: minmax(0, 1fr); }
  .document-card .contact-actions { grid-column: 1; }

  /* A record has no avatar to keep a column for, so its two rows are the name
     and detail, then the buttons under them. After the rule above, not before:
     both are one class deep and the later one wins. */
  .record-card { grid-template-columns: minmax(0, 1fr); }
  .record-card .contact-actions { grid-column: 1; }

  /* And the interpunct goes. These labels are a phrase — "Where the title and
     papers are kept" — so at this width the value wraps below its label of its
     own accord, and the separator is left stranded at the end of the line above
     with nothing after it to separate. The weight already tells them apart. */
  .record-label::after { content: none; }
}

/* --------------------------------------------------------------------------
   Library.

   The room is its six tiles. The wireframe's six category names came off at
   the client's request — they filtered nothing, and filtering belongs to the
   CMS. The tiles do not open either: no destinations have been supplied, and
   an href of "#" is a promise the page cannot keep.

   Editorial principle (brief §5): this must never feel commercial. No prices,
   no urgency, no "shop" language, and the same visual system as every other
   room.
   -------------------------------------------------------------------------- */
.resource-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 0.875rem;
  margin-top: 1.5rem;
}

.resource {
  padding: 1.25rem 1.375rem;
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
}

.resource-tag {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ochre-deep);
}

.resource-title {
  margin-top: 0.5rem;
  font-family: var(--serif);
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--text-main);
}

/* The title of a resource that has somewhere to go.
   ---------------------------------------------------------------------------
   It keeps the title's own colour and is marked by its underline instead. A
   link colour here would be a second accent on a card that already carries
   one in its tag, and the title is the longest text on the card — recolouring
   it is a contrast question in two themes to answer nothing a rule already
   answers. The hover is the tag's Ochre: the one accent the card already has.

   The whole title is the target, so the card's own padding gives it its
   height; the tap floor is met by the row, not by padding on the anchor. */
.resource-link {
  color: inherit;
  text-decoration-thickness: 1px;
}

.resource-link:hover { color: var(--ochre-deep); }

.resource-meta {
  margin-top: 0.5rem;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}


/* --------------------------------------------------------------------------
   The dashboard — /rooms/ once anything has been chosen.

   Two surfaces, both already in the palette: a Sun wash behind the greeting,
   the way the wireframe's hero card is warm against the page, and a Sand-deep
   card for the activity feed. No new colour, no hex literal — see
   docs/design/04-design-system.md.

   CONTRAST, on the two surfaces this section creates:

     13.96:1  --text-main on --sun-pale     the headline and the lead
      5.34:1  --ochre-deep on --sun-pale    the greeting itself
      5.09:1  --forest on --sun-pale        "greatest loves", the emphasis
     13.48:1  --text-main on --sand-deep    each moment
      6.08:1  --text-muted on --sand-deep   which room, and how long ago

   The meta line is 1rem like everything else. The wireframe sets it at
   11.52px, which is one of the 37 styles issue #21 is about; this page is new
   markup rather than a transformed wireframe, so it starts at the floor.
   -------------------------------------------------------------------------- */
.dash > * + * { margin-top: 2.5rem; }

/* The introduction is wrapped so dashboard.js can lift it out whole, and the
   wrap took the page's vertical rhythm away from everything inside it —
   `main > * + *` only reaches main's OWN children, and the wrap is now the
   only one. Restated here, or the eyebrow, the heading and the lead sit
   against each other. */
.dash-tour > * + * { margin-top: 1.5rem; }

/* And more than the rhythm before the table: it is the body of the screen,
   and the sentence above it is an introduction to it, not a caption on it. */
.dash-tour .lead + .table-scroll { margin-top: 2.5rem; }

.dash-hero {
  position: relative;
  overflow: hidden;
  padding: 2rem 2.25rem 2.25rem;
  background: var(--sun-pale);
  border-radius: var(--radius-lg);
}

/* The greeting, the line and the buttons sit over the watermark. Stacked with
   position alone — no z-index anywhere in this block, because the mark is the
   first child and everything after it is painted on top by document order. */
.dash-hero > *:not(.dash-hero-mark) { position: relative; }

/* --- The watermark: three Life Objects, large and faint --------------------

   Style Guide v2 p15, and the first place in the product the objects are used.
   Which three is decided per visit by hero.js; the placement is not — it is
   these three boxes, so the cluster falls the same way whatever is drawn in it.

   MASKED, NOT <img>. A mask takes its colour from this rule, so the objects
   tint with the theme like every other piece of brand artwork, and the SVGs
   stay files on disk rather than two hundred kilobytes inlined into one page.

   The tint is Forest mixed down to a wash, not an opacity on the element: it
   is the same technique the cover card uses, and it keeps what the reader sees
   written in the stylesheet rather than computed off two properties. At 10% it
   measures 1.06:1 against --sun-pale — a texture, not a picture, and nowhere
   near enough to move the 13.96:1 the headline over it is measured at.

   The text column is capped below, so words and objects share the card without
   ever sharing the same space.
   ------------------------------------------------------------------------- */
.dash-hero-mark {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  border-radius: inherit;
}

/* WHAT AN OBJECT IS, wherever it is drawn — the dashboard hero and the prompt
   card on Reflection Notes both draw them, and each places them itself. A mask
   rather than an <img>: it takes its colour from this rule, so the objects tint
   with the theme like every other piece of brand artwork, and the SVGs stay
   files on disk instead of two hundred kilobytes inlined into a page. */
.life-object {
  position: absolute;
  background: color-mix(in srgb, var(--forest) 10%, transparent);
  mask: var(--object) no-repeat center / contain;
  -webkit-mask: var(--object) no-repeat center / contain;
}

/* Three boxes, drawn largest first and bleeding past the right-hand edge —
   a corner of a bigger composition, which is how p15 reads, rather than three
   icons lined up inside a box. The rotations are static: nothing here moves,
   so there is nothing for prefers-reduced-motion to switch off. */
.dash-hero .life-object:nth-child(1) {
  right: -3.5rem;
  bottom: -3rem;
  width: 17rem;
  height: 17rem;
}

.dash-hero .life-object:nth-child(2) {
  right: 9rem;
  top: -2.5rem;
  width: 10rem;
  height: 10rem;
  transform: rotate(-9deg);
}

.dash-hero .life-object:nth-child(3) {
  right: 12.5rem;
  bottom: -1.5rem;
  width: 7.5rem;
  height: 7.5rem;
  transform: rotate(7deg);
}

/* Never over the words. The cluster occupies the right of the card, so the
   copy stops before it — measured against the widest line in
   site/flow/shared/one-liners.json at the h1's own size. */
.dash-hero h1,
.dash-hero .lead { max-width: 34rem; }

/* --- The room backdrop: two Life Objects at the foot of every room page ----

   The same objects and the same mask as the hero watermark, drawn much larger
   and anchored to the BOTTOM OF THE PAGE, not the window — they are part of
   the room, met at the end of it, not furniture that follows you down.

   Which two is the room's choice (flow.json `backdrop`, editable in the owl
   editor); where they fall is this block. The first is the large one, about
   40% of the width and bleeding off the right-hand edge; the second is about
   a fifth of the width, above it and a little to its left.

   Behind everything: z-index -1 against a body that is positioned but not a
   stacking context, so it paints over the page's Sand (which body hands to
   the canvas) and under every card, band and word. The 10% Forest wash is the
   hero's — 1.06:1, a texture, not a picture — so it moves no contrast pair.

   The bleed is clipped by the backdrop's own box, not by body: overflow set
   on body is handed to the viewport, and the page would scroll sideways to
   show the part meant to be off-screen.
   ------------------------------------------------------------------------- */
body:has(> .room-backdrop) { position: relative; }

.room-backdrop {
  --backdrop: max(40vw, 10rem);
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: -1;
  width: 100%;
  height: calc(var(--backdrop) * 1.6);
  overflow: clip;
  pointer-events: none;
}

.room-backdrop .life-object:nth-child(1) {
  right: calc(var(--backdrop) * -0.3);
  bottom: 0;
  width: var(--backdrop);
  height: var(--backdrop);
}

.room-backdrop .life-object:nth-child(2) {
  right: calc(var(--backdrop) * 0.45);
  bottom: calc(var(--backdrop) * 0.85);
  width: calc(var(--backdrop) * 0.55);
  height: calc(var(--backdrop) * 0.55);
  transform: rotate(-8deg);
}

/* h1 is already clamp(1.75rem, 6vw, 2.5rem) and balanced — see the type scale. */
.dash-hero h1 { margin-top: 0.75rem; }

/* The client's lines run from five words to a twenty-word statistic, and one
   size cannot hold both: at the full h1 the long ones take four lines and
   double the height of the card between one visit and the next. One step down
   for those — still well over the 16px floor — set by hero.js off the length
   of the line it is about to write. */
.dash-hero h1[data-length='long'] { font-size: clamp(1.5rem, 4vw, 2rem); }

/* The client's own emphasis, from the wireframe: the second half of the
   question is set in Forest italic, and "much more" is italic in the lead. */
.dash-hero h1 em {
  color: var(--forest);
  font-style: italic;
}

.dash-hero .lead { margin-top: 1rem; }

.dash-section > * + * { margin-top: 1rem; }

.section-label {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Where you are — one row, one dot per thing there is to do.

   One dot per thing there is to do, grouped by room, the whole account's count
   at the head of the row, and the count in words under each group. It replaces what this
   started as — a row of stat cards over a grid of room cards — because those
   were two lines saying one thing.

   NO PERCENTAGES, and that is the design rather than an omission. A percentage
   is a distance from 100, and 100 is a finish line this product does not have;
   a count of dots is a count of things. An unlit dot is the colour of the
   hairline everywhere else in the flow, so a room you have not started looks
   the way the page looked before there were dots at all — nothing is disabled,
   nothing is hidden, and Forest is not a warning colour. The dots are
   aria-hidden; the words under them are the state.

   Five of the six treatments this was chosen from are kept at
   /admin/drafts/progress-options/, so swapping is a rewrite of one function and
   this block, not a rediscovery.

   CONTRAST, on the Sand-deep card this sits on:

     13.48:1  --text-main on --sand-deep    the room names and the figure
      6.08:1  --text-muted on --sand-deep   the count under each group
      4.92:1  --forest on --sand-deep       the lit dots

   The last is a graphical object and clears the 3:1 of WCAG 1.4.11 twice over;
   the others clear AA for normal text. No dot is the only carrier of anything.
   -------------------------------------------------------------------------- */
.dash-row {
  display: flex;
  align-items: flex-start;
  gap: 2rem;
  padding: 1.375rem 1.5rem;
  background: var(--sand-deep);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* The whole account, in the same units as everything beside it: a count, never
   a percentage. Held on one line so the row starts with a figure rather than a
   paragraph. */
.dash-whole {
  flex: none;
  /* Narrow on purpose. Every rem this takes is a rem the groups do not have,
     and the groups wrap a whole line when they run short — the head reading
     over three lines costs nothing, a sixth group dropping to a second row
     costs 87px of the fold. */
  max-width: 9rem;
  padding-right: 2rem;
  border-right: 1px solid var(--border);
  align-self: stretch;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

.dash-whole b {
  font-family: var(--serif);
  font-size: 2rem;
  font-weight: 700;
  color: var(--forest);
}

.dash-whole-sub { display: block; margin-top: 0.25rem; }

.dash-groups {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem 2rem;
  list-style: none;
}

.dash-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* The room name is the link, and it is the only one in the group: the dots are
   decoration and the count is a statement, so neither is a second way in to the
   same place. Live from every room whatever the dots say — with one exception,
   below, which is the same exception the room menu carries.

   44px box on a 34px footprint: the name sits at the top of the box as it
   always did, and the extra height hangs over the 0.5rem gap and the dots
   below — decoration, nothing interactive under it — so the row keeps its
   height and the fold it fits in (tests/dashboard.spec.js, "opens on where
   you are"). The floor is the floor; --tap-compact stays the chips' alone. */
.dash-room {
  display: inline-block;
  min-height: var(--tap);
  margin-bottom: -0.625rem;
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--text-main);
  text-decoration: none;
}

.dash-room:hover { color: var(--forest); text-decoration: underline; }

/* The values-first lock, on the row — the same wordless grey as
   .room-nav-item.is-locked above, for the same four rooms, from the same
   attribute (#42). Applied by room-lock.js; the build renders the row open.
   A measured token, not an opacity, for the reason given on the menu's rule —
   but --text-subtle-deep rather than --text-subtle: the row sits on Sand-deep,
   where the menu's grey falls to 3.96:1, and this one is solved by
   build-tokens.py to be the softest that still holds 4.5:1 there. The hover
   wash says "this will do something"; locked, it will not. */
.dash-group.is-locked > .dash-room {
  color: var(--text-subtle-deep);
  cursor: default;
}

.dash-group.is-locked > .dash-room:hover {
  color: var(--text-subtle-deep);
  text-decoration: none;
}

.dash-room:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 3px;
  border-radius: 3px;
}

/* One dot per thing there is to do, and the Library's count is the client's to
   grow — six tiles became twelve through the CMS, and twelve dots in a line
   pushed the Library group onto a second row and the row past the fold. So
   the dots take no width of their own: the group is as wide as its name and
   its count, and the dots wrap to fill that, however many there are. */
.dash-dots {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3125rem;
  width: 0;
  min-width: 100%;
}

.dash-dot {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: var(--border);
  transition: background-color 0.25s ease;
}

.dash-dot.is-on { background: var(--forest); }

.dash-state {
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

/* The row stops being a row before it starts wrapping badly: the figure moves
   above the groups, and the groups pack two or three to a line. */
@media (max-width: 60rem) {
  .dash-row { flex-direction: column; gap: 1.25rem; }

  .dash-whole {
    max-width: none;
    padding-right: 0;
    padding-bottom: 1.25rem;
    border-right: 0;
    border-bottom: 1px solid var(--border);
    align-self: auto;
  }

  .dash-whole b { font-size: 1.75rem; }
  .dash-whole-sub { display: inline; margin-top: 0; }
}


.activity {
  background: var(--sand-deep);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.activity-title {
  max-width: none;
  padding: 1.125rem 1.5rem;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}

.activity-list { list-style: none; }

.activity-item {
  display: flex;
  gap: 0.875rem;
  align-items: flex-start;
  padding: 1.125rem 1.5rem;
}

.activity-item + .activity-item { border-top: 1px solid var(--border); }

/* Decoration, and announced as such: the dot repeats nothing the two lines
   beside it do not already say. */
.activity-dot {
  flex: none;
  width: 0.625rem;
  height: 0.625rem;
  margin-top: 0.5rem;
  border-radius: 50%;
  background: var(--forest);
}

.activity-dot[data-tone='sun'] { background: var(--sun-deep); }

.activity-text {
  font-family: var(--sans);
  font-size: 1.0625rem;
  color: var(--text-main);
}

.activity-meta {
  margin-top: 0.125rem;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
}

/* The way back to the introduction. Under everything, as a way out is. */
.dash-tour-link { margin-top: 2rem; }

/* Sign out, pushed to the row's far end — the bottom right of the page. The
   owl holds the viewport's own corner, so this sits in the flow instead. On a
   phone the row wraps and it still keeps to the right. */
.dash-signout { margin-left: auto; }
.dash-signout-status:empty { display: none; }

@media (max-width: 32rem) {
  .dash-hero { padding: 1.5rem 1.25rem; }
  .activity-title, .activity-item { padding-left: 1.125rem; padding-right: 1.125rem; }

  /* One object, not three. There is no room beside the words on a phone, so
     the cluster would be three overlapping shapes directly behind them —
     noise rather than atmosphere. The copy takes the width back. */
  .dash-hero .life-object:nth-child(2),
  .dash-hero .life-object:nth-child(3) { display: none; }

  .dash-hero .life-object:nth-child(1) {
    right: -2.5rem;
    bottom: -2.5rem;
    width: 11rem;
    height: 11rem;
  }

  .dash-hero h1,
  .dash-hero .lead { max-width: none; }
}


/* ============================================================================
   THEMES

   The base above is the Sand treatment — Style Guide v2, and staging's default
   (scripts/theme.py). Only the surfaces that must go dark are re-pointed here
   for Forest, which is the client designer's original and stays one switch
   away by setting data-theme on <html>.

   CONTRAST, computed not eyeballed:

     Sand    16.08:1  --text-main on the white card     headline
              7.25:1  --text-muted on the white card    body
              6.15:1  --ochre-deep on the white card    the sign-in line
              9.16:1  --charcoal on --sun               Begin

     Forest   7.62:1  --sand on --forest-deep           the band and the cover
              5.22:1  --sand @86% on the cover card     body
   ========================================================================== */

/* Only one wordmark is ever displayed, so only one is announced. */
.on-sand { display: block; }
.on-forest { display: none; }

:root[data-theme='forest'] .on-sand { display: none; }
:root[data-theme='forest'] .on-forest { display: block; }

:root[data-theme='sand'] .band {
  background: var(--sand);
  border-bottom: 1px solid var(--border-mid);
}

/* --- Forest: the cover becomes the designer's original splash ------------- */

:root[data-theme='forest'] body.cover {
  background: var(--forest-deep);
  color: var(--sand);
}

/* The objects were Forest on Sand; on a Forest ground they are 1.36:1 and
   effectively invisible, so they flip to Sand. The Sun disc behind them is
   unchanged — it reads on either. */
:root[data-theme='forest'] .hero svg { color: var(--sand); }

:root[data-theme='forest'] .cover-wordmark { color: var(--sand); }

:root[data-theme='forest'] .cover-card {
  background: color-mix(in srgb, var(--sand) 7%, transparent);
  border-color: color-mix(in srgb, var(--sand) 16%, transparent);
}

:root[data-theme='forest'] .cover-card h1 { color: color-mix(in srgb, var(--sand) 95%, transparent); }
:root[data-theme='forest'] .cover-card p { color: color-mix(in srgb, var(--sand) 86%, transparent); }
:root[data-theme='forest'] .cover-signin a { color: color-mix(in srgb, var(--sand) 86%, transparent); }
:root[data-theme='forest'] .cover-card .btn-secondary { color: color-mix(in srgb, var(--sand) 86%, transparent); }

/* Below 900px the artwork becomes a wash behind the card (see the cover
   section). Sand's card is solid white and covers it on its own; Forest's is a
   7% sand tint, which the wash reads straight through — the copy ends up
   sitting on a guitar. The card takes the ground's own colour at that width
   instead, which is the surface the 7.62:1 and 5.22:1 figures above are
   measured against.

   It lives here rather than in the media query up there because these two
   selectors have equal specificity: whichever comes last in the file wins, and
   that is this block. */
@media (max-width: 56.25rem) {
  :root[data-theme='forest'] .cover-card {
    background: color-mix(in srgb, var(--forest-deep) 92%, transparent);
  }
}

/* --------------------------------------------------------------------------
   /account/ — your name, your address, and the ways out.
   --------------------------------------------------------------------------
   Deliberately the thinnest section in this file. Everything that could reuse
   a class does: .field, .help, .actions, .btn and .form-status are the sign-in
   page's, so the account page and the door it came from look like one product
   without a second set of rules to keep in step. What is here is only what a
   page of separated, labelled facts needs and no form had — a label for a
   value nobody types into, and the rules between the groups.

   The email address is a .account-value rather than a disabled input. A field
   greyed out is a field that looks broken; a fact printed on the page, with
   the reason it is only a fact beside it, is the honest rendering of something
   that cannot be edited here. */
/* A button that reads as a text link. Sign out and "sign out everywhere" sit
   on one row, and two button-shaped things there would give equal weight to
   two very unequal actions — one you do most days, one you do when something
   has gone wrong. So the second is worded plainly and drawn as text.

   IT KEEPS THE 44px TARGET. Looking like text is the point; being harder to
   hit is not, and the accessibility floor is about the target rather than the
   paint. Muted at regular weight — 6.90:1 --text-muted on --sand — going to
   --text-main on hover and focus. */
.text-button {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 0.25rem;
  background: none;
  border: 0;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.text-button:hover { color: var(--text-main); }

.text-button:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --------------------------------------------------------------------------
   Tabs. The product's first set, on /account/.
   --------------------------------------------------------------------------
   Written generally rather than as .account-something, because the next page
   that wants tabs should take these rather than fork them.

   The selected tab is marked twice over: the Sun rule beneath it and the
   weight of its words, so it is not colour alone doing the work (WCAG 1.4.1).
   IT WRAPS, AND IT HAS NO OVERFLOW OF ITS OWN. Three short tabs never need to
   scroll sideways in a 64rem column, and the one time they did not fit — 375px,
   where the third was clipped off the edge with nothing to say it was there —
   wrapping is the better answer anyway.

   `overflow-x: auto` was here for that narrow case and it cost a scrollbar:
   setting one axis to something other than `visible` computes the OTHER axis
   from `visible` to `auto`, so overflow-y became scrollable, and the -1px
   below is exactly enough content to draw a bar. Measured, not guessed:
   scrollHeight 44 against clientHeight 43 on a strip nothing was overflowing
   horizontally. No overflow property, no bar, and the -1px does its job.

     8.85:1  --text-main on --sand    the selected tab
     6.90:1  --text-muted on --sand   the others */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 1.25rem;
  border-bottom: 1px solid var(--border-mid);
}

.tab {
  flex: none;
  min-height: var(--tap);
  padding: 0.625rem 1rem;
  background: none;
  border: 0;
  border-bottom: 3px solid transparent;
  margin-bottom: -1px;           /* the rule under the strip, met exactly */
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 400;
  color: var(--text-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.tab:hover { color: var(--text-main); }

.tab[aria-selected='true'] {
  color: var(--text-main);
  font-weight: 500;
  border-bottom-color: var(--sun);
}

.tab:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: -2px;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

/* A panel's own children need the rhythm `main > * + *` gives the page, which
   stops at main's direct children and so never reached inside a tab. */
.tabpanel > * + * { margin-top: 1.5rem; }

/* The panel is focusable so the keyboard can reach what a tab opened; it
   should not draw a ring for a mouse that clicked into it. */
.tabpanel:focus { outline: none; }

.tabpanel:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

.account-panel > :first-child { margin-top: 0; }

/* The rule between fields belongs BETWEEN them; the first one in a panel has
   nothing to be separated from — the tab strip is already the line above it. */
.tabpanel > .account-detail:first-child {
  margin-top: 1rem;
  padding-top: 0;
  border-top: 0;
}

/* Wide enough for a name, not the whole column: an input that ran to the
   margin pushed Save and Cancel away from the thing they act on. */
.account-editor input {
  flex: 1 1 14rem;
  max-width: 22rem;
}
.account-editor .btn { flex: 0 0 auto; }


.account-detail {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
}

/* THE HEADING ROW, and the `?` that sits on the end of it.
   ---------------------------------------------------------------------------
   Every field on this page is the same shape — a name for the thing, a `?`
   that opens the sentence explaining it, then the value and the way to change
   it. The row is what makes them look like one family rather than four
   sections that grew separately.

   The `?` is small and the target is not: 44px in both directions around a
   1.6rem circle, because the accessibility floor is about the target rather
   than the paint. Muted at rest so a row of them does not read as four
   warnings, Forest on hover and focus.

     6.90:1  --text-muted on --sand     the resting mark
     5.58:1  --forest on --forest-pale  hover */
.account-head {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.account-label {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  color: var(--text-main);
}

.account-why {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  background: none;
  border: 0;
  font-family: var(--sans);
  font-size: 0.9375rem;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
}

/* The circle is drawn on the span, not the button, so the ring stays small
   while the target stays large. */
.account-why > [aria-hidden] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  border: 1px solid currentColor;
  border-radius: 50%;
}

.account-why:hover {
  color: var(--forest);
  background: var(--forest-pale);
  border-radius: var(--radius-sm);
}

.account-why:focus-visible {
  outline: 2px solid var(--forest);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}

/* The description the `?` opens. One format everywhere: .help, the class the
   product already uses for a sentence under a label, in the same place under
   every heading on the page. */
.account-detail > .help { margin-top: 0.25rem; }

/* ONE ROW PER FIELD: title, value, action.
   ---------------------------------------------------------------------------
   Three cells on one line, so a column of fields can be read down rather than
   picked through. The title column is bounded at both ends — narrow enough
   that "Password" does not strand its value halfway across a 64rem page, wide
   enough that "Email address" does not wrap.

   THE VALUE TAKES ITS CONTENT, NOT THE SLACK. Giving it 1fr pushed every
   button against the right edge, which put a name and the way to change it at
   opposite ends of the page. The button now follows the value it acts on. The
   cost is a ragged right: each row is its own grid, so a long address carries
   its Edit further right than a short name does — the buttons are beside their
   values rather than in a column of their own.

   It STACKS below 40rem rather than compressing: a 44px button and a name do
   not share a phone row, and shrinking either to make them fit would break a
   floor to save a line. */
.account-line {
  display: grid;
  grid-template-columns: minmax(9rem, 13rem) max-content auto;
  align-items: center;
  gap: 0.5rem 1rem;
}

@media (max-width: 40rem) {
  .account-line {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
  }
}

/* Every cell sits on the line the grid gives it; the row owns the spacing. */
.account-line > * { margin-top: 0; }

/* A SETTING'S ROW (the Accessibility tab, #244): a title and one checkbox,
   which takes the value and action cells together — a tick is both the fact
   and the way to change it. The box itself is /secure/'s .consent line, so a
   checkbox looks the same everywhere it is asked. */
.account-line > .account-setting {
  grid-column: 2 / -1;
  margin-top: 0;
}

@media (max-width: 40rem) {
  .account-line > .account-setting { grid-column: auto; }
}

.account-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

/* The editor takes the value and action columns, so the row keeps its title
   while the name is being changed. */
.account-editor {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

@media (max-width: 40rem) {
  .account-editor { grid-column: 1 / -1; }
}

/* The link to each policy's page. Every policy has a page now — one that
   says the document is coming until the client uploads it — so the cell is a
   link rather than the word "pending" it used to be. */
.account-actions a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--forest);
}



.account-value {
  font-family: var(--sans);
  font-size: 1.0625rem;
  margin-top: 0.25rem;
  overflow-wrap: anywhere;       /* a long address wraps rather than widens */
}

/* Earlier agreements to a policy, under the latest (#190) — metadata, so
   the sans face, and muted: the current line is the one that answers. */
.account-history {
  margin: 0.25rem 0 0;
  padding: 0;
  list-style: none;
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);
}

.account-history li + li { margin-top: 0.125rem; }

/* The disclosure in front of them: a line of text at the tap floor, muted
   like the list it opens, at the label size. */
.account-history-toggle {
  font-size: 0.9375rem;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* The second step of "sign out everywhere". Inset behind the Sun edge that
   marks the other things asking for a decision, so a question that appeared
   where a button was is plainly a new thing on the page rather than copy that
   was always there. */
.account-confirm {
  margin-top: 1rem;
  padding: 1rem 1.125rem;
  background: var(--forest-pale);
  border-radius: var(--radius-sm);
  box-shadow: inset 0.25rem 0 0 var(--sun);
}

.account-confirm > :first-child { margin-top: 0; }

/* What "everywhere" is, named (#239): a plain list inside the question, one
   browser a line, at body size — functional text, so DM Sans (Style Guide
   p12). "This browser." is the one thing in it in weight, because it is the
   one line the person can check against the thing in their hand. */
.account-sessions-lead {
  margin: 0.75rem 0 0.25rem;
}

.account-sessions {
  margin: 0 0 0.5rem;
  padding-left: 1.25rem;
  font-family: var(--sans);
}

.account-sessions li {
  margin: 0.25rem 0;
  line-height: 1.5;
}

/* Each row's own Sign out: a text button like the "everywhere" one, kept at
   the tap floor by .text-button, underlined so it reads as the link it is
   beside a sentence that is not one. */
.account-session-end {
  color: var(--forest);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* People who trust you (trusting.js): one row per person, their name in the
   serif because it is a person's name, and the gravest button on the page
   behind it. Shown only when there is somebody on the list. */
.account-trusting {
  margin-top: 3.5rem;
  padding-top: 2.5rem;
  border-top: 1px solid var(--border);
}

/* Its heading is the size of the page's own. This is a second subject on the
   page — somebody else's trust, not your account — and at a field label's size
   it read as one more setting under Signing out. */
.account-trusting-heading {
  margin: 0 0 0.75rem;
  font-family: var(--serif);
  font-size: clamp(1.75rem, 6vw, 2.5rem);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.015em;
}

.trusting-list {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
}

.trusting-row {
  padding: 0.75rem 0;
  border-top: 1px solid var(--border);
}

.trusting-name {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.25rem;
}

.trusting-actions {
  margin-top: 0.5rem;
}

.trusting-done {
  margin: 0.5rem 0 0;
  color: var(--text-muted);
}

/* The return visit after a deletion was asked for (#138): the same inset
   surface as a question, because it is the answer to one — but edged in
   Forest, not Sun, since nothing here is asking for a decision. */
.account-kept {
  margin: 0 0 1.5rem;
  padding: 1rem 1.125rem;
  background: var(--forest-pale);
  border-radius: var(--radius-sm);
  box-shadow: inset 0.25rem 0 0 var(--forest);
  font-family: var(--serif);   /* a short statement, not body copy — Style Guide p12 */
}

.account-kept p { font-family: var(--serif); }   /* the panel's own p rule would win otherwise */
.account-kept > :first-child { margin-top: 0; }
.account-kept > :last-child { margin-bottom: 0; }
.account-kept .text-button { font-family: var(--sans); }


/* --------------------------------------------------------------------------
   The legal documents: /privacy/, /terms/, /cookies/.
   Rendered by scripts/policy_pages.py from the client's upload. The prose is
   narrative content, so it sets in Noto Serif at a reading measure; the
   version line and the PDF controls are functional and stay in DM Sans.
   -------------------------------------------------------------------------- */
.policy-page > * + * { margin-top: 1rem; }

.policy-coming { max-width: 40rem; }

.policy-version {
  font-family: var(--sans);
  font-size: 0.9375rem;
  color: var(--text-muted);
}

.policy-text {
  max-width: 44rem;
  font-family: var(--serif);
  font-size: 1.0625rem;
  line-height: 1.65;
}

.policy-text > * + * { margin-top: 0.875rem; }

.policy-text h2,
.policy-text h3,
.policy-text h4 {
  font-family: var(--serif);
  color: var(--forest);
  margin-top: 1.75rem;
}

.policy-text h2 { font-size: 1.375rem; }
.policy-text h3 { font-size: 1.1875rem; }
.policy-text h4 { font-size: 1.0625rem; }

.policy-text ul,
.policy-text ol { padding-left: 1.5rem; }

.policy-text li + li { margin-top: 0.375rem; }

.policy-open { margin-top: 1.25rem; }

/* The Back above the title (#340): the page opens in a new tab from /secure/,
   and this is the way back — see pdf-viewer.js. */
.policy-back { margin-bottom: 0.5rem; }

/* The PDF viewer (#340): a toolbar, then one page of the document drawn on a
   canvas at the column's width, then the page controls again. The sheet scrolls sideways when zoomed past
   fit-width, so a magnified page never widens the page around it. Replaced the
   <iframe> of the file, which was a small box and blank on most phones. */
.policy-viewer {
  margin-top: 1.25rem;
  border-radius: var(--radius-md);
  /* Where a page turned from the bottom pager scrolls to: a little air above. */
  scroll-margin-top: 1rem;
}
.policy-viewer:focus-visible {
  outline: 3px solid var(--sun);
  outline-offset: 4px;
}
.policy-toolbar {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.policy-toolbar-group {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* Previous / Next again under the page (#367), for the reader at the bottom. */
.policy-toolbar-bottom {
  justify-content: center;
  margin-top: 0.75rem;
  margin-bottom: 0;
}
.policy-toolbar .btn {
  min-width: 44px;
  min-height: 44px;
  padding-inline: 0.875rem;
}
.policy-counter {
  min-width: 7.5rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}
.policy-sheet {
  overflow-x: auto;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
  background: #fff;
}
.policy-page {
  display: block;
  margin: 0 auto;
}
.policy-viewer-failed { max-width: 40rem; }

/* --------------------------------------------------------------------------
   A death declared on this account (declared.js, #389)
   The Ochre callout the admin uses for things that must be read, on the
   Ochre-pale surface — the six colours, no seventh. The heading is a short
   statement, so Noto Serif; everything else is DM Sans. The undo buttons are
   deliberately small — the words matter more than the button — but never
   under the 44px tap floor.
   -------------------------------------------------------------------------- */
.declared {
  margin: 1.5rem 0 2rem;
  padding: 1.25rem 1.5rem;
  background: var(--ochre-pale);
  border: 1px solid var(--border);
  border-left: 0.3rem solid var(--ochre);
  border-radius: var(--radius-sm);
  color: var(--text-main);
  font-family: var(--sans);
}

.declared > :first-child { margin-top: 0; }
.declared p { max-width: 62ch; }

.declared-heading {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 1.35rem;
  color: var(--ochre-deep);
  margin: 0 0 0.5rem;
}

.declared-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.75rem 0 0;
}

.btn.declared-undo {
  padding: 0.3rem 0.8rem;
  font-size: 1rem;
  gap: 0.25rem;
}

.declared-confirm { margin-top: 0.75rem; }
.declared-confirm > p:first-child { margin-top: 0; }
.declared-done p { margin: 0; }
.declared .form-status:empty { display: none; }

/* --------------------------------------------------------------------------
   "Keep this private" (#383) — one box on every editor that has it, and the
   quiet word a saved entry wears when it was kept back. Muted, never a
   warning colour: keeping something to yourself is not a state to put right.
   -------------------------------------------------------------------------- */
/* The answer box's own width (.field-answer): a line under a form, not a banner across the page. */
.private-toggle { margin-top: 1.5rem; max-width: 46rem; }
.private-toggle .help { margin-top: 0; }

.private-marker,
.contact-private {
  font-family: var(--sans);
  font-size: 1rem;
  font-style: italic;
  color: var(--text-muted);
}
.private-marker { margin-top: 0.5rem; }
.reflection .private-marker { display: block; margin-top: 0.25rem; }

/* --------------------------------------------------------------------------
   /trusting/ (#383) — somebody's shared words, read back to a person they
   trusted. A reading page: the room's own answer and record styles, set in
   quiet sections, with nothing to press but the way back.
   -------------------------------------------------------------------------- */
.trusting .trusting-rooms { display: grid; gap: 2.5rem; margin-top: 2rem; }
.trusting-room > h2 { margin-bottom: 1rem; }
.trusting-group { margin-top: 1.5rem; }
.trusting-group > h3 { margin-bottom: 0.75rem; }
.trusting-entries,
.trusting-group { display: grid; gap: 1rem; }
.trusting-entry {
  padding: 1rem 1.25rem;
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-sm);
  background: var(--sand);
}
.trusting-entry > h3,
.trusting-entry > h4 { margin: 0 0 0.5rem; font-family: var(--serif); }
.trusting-see { margin-top: 0.5rem; }

/* The navigable view (#383): the editor's shell, read-only. Until there is
   something to move between — signed out, not yet, nothing here — the page
   is one column and the menu and its button stay out of the way. */
.trusting-shell:not(.is-open) { grid-template-columns: minmax(0, var(--measure)); }
.trusting-shell:not(.is-open) .room-nav { display: none; }
body:has(.trusting-shell:not(.is-open)) .room-nav-toggle { display: none; }

/* Where you are: Home › Wishes › Care Preferences. Quiet, above the eyebrow. */
.trusting-crumbs-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  padding: 0;
  margin: 0 0 1rem;
  list-style: none;
  font-family: var(--sans);
  font-size: 1rem;
}
.trusting-crumbs-list li + li::before { content: "›"; margin-right: 0.5rem; color: var(--text-muted); }
.trusting-crumbs-list a { color: var(--forest); }

.trusting-view { display: grid; gap: 1rem; margin-top: 1.5rem; }
.trusting-view > .trusting-entry > .answer-text:first-child { margin-top: 0; }
/* A card's name on a register page is an h2 for the outline — it is the level
   below the page's h1 — but it reads at a card title's size, not a page's. */
.trusting-view .trusting-entry > h2 { margin: 0 0 0.5rem; font-family: var(--serif); font-size: 1.25rem; }

/* The accounts inside an institution sit under it, indented, titled by whose. */
.trusting-accounts {
  display: grid;
  gap: 0.75rem;
  margin: -0.25rem 0 0.5rem 1.5rem;
  padding-left: 1rem;
  border-left: 2px solid var(--border-mid);
}
.trusting-accounts-title { font-family: var(--sans); font-size: 1rem; color: var(--text-muted); }

.trusting-print-action { margin-top: 2.5rem; }

/* The values chosen: the Values Room's own chips, read-only. */
.trusting-values { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0; margin: 0; list-style: none; }
.trusting-values li {
  padding: 0.5rem 1rem;
  border: 1px solid var(--border-mid);
  border-radius: 999px;
  font-family: var(--sans);
  background: var(--sand);
}

/* ---- The owner's preview (#407) -------------------------------------------
   The banner is the join page's notice, as it says the same kind of thing:
   what you are looking at is not quite what it seems. Above the trail, and
   it stays while the owner moves about. The way to it on Trusted Contacts
   is an outline button beside Add — seen, but not a second primary. */
.trusting-preview {
  margin-bottom: 1.5rem;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  row-gap: 0.25rem;
  column-gap: 1.5rem;
}
.trusting-preview-for { margin: 0; flex: 1 1 auto; }
.trusting-preview-name { font-weight: 500; }
.trusting-preview-choose {
  min-height: 44px;
  margin: 0 0.25rem;
  padding: 0.375rem 0.75rem;
  font: inherit;
  color: var(--text-main);
  background: var(--sand);
  border: 1px solid var(--border-mid);
  border-radius: var(--radius-md);
}
.trusting-preview-back { display: inline-flex; align-items: center; min-height: 44px; white-space: nowrap; }

/* ---- Print / Save as PDF (#383) -------------------------------------------
   The screen is a room at a time; paper is all of it (trusting-print.js).
   Only the print container prints — no band, no menu, no buttons — and a
   card never splits across two pages. Each room starts on a fresh page. */
.trusting-print { display: none; }

@media print {
  body:has(.trusting-print) > :not(.trusting-print) { display: none !important; }
  body:has(.trusting-print) { background: none; }
  .trusting-print { display: block; font-size: 11pt; }
  .trusting-print-head { margin-bottom: 1.5rem; border-bottom: 1px solid var(--border-mid); padding-bottom: 0.75rem; }
  .trusting-print-title { font-family: var(--serif); font-size: 20pt; font-weight: 700; }
  .trusting-print-date { font-family: var(--sans); color: var(--text-muted); }
  .trusting-print-note { font-family: var(--sans); margin-top: 0.5rem; }
  .trusting-print-room { break-before: page; }
  .trusting-print-room:first-of-type,
  .trusting-print-values + .trusting-print-room { break-before: auto; }
  .trusting-print-values { margin-bottom: 1.5rem; }
  .trusting-print-room > h2 { font-size: 16pt; margin-bottom: 0.75rem; }
  .trusting-print .trusting-group > h3 { font-size: 13pt; break-after: avoid; }
  .trusting-print .trusting-entry,
  .trusting-print .trusting-accounts { break-inside: avoid; }
  .trusting-print .trusting-entry { border-color: var(--border-strong); background: none; }
}
