/* =====================================================================
   Design system
   One stylesheet, no build step, no inline styles anywhere so the app runs
   under a CSP with no 'unsafe-inline'.

   Palette notes (WCAG 2.2 AA is a hard requirement, SSDLC §17.1).
   Every ratio below was measured, not assumed:

 --navy-500 #2C4C7C gives 8.6:1 against white - the button token.
 --navy-700 #1B3050 gives 12.4:1 on paper - body text, links, headings.
 --amber #F0A64A is 7.8:1 on ink but only 1.9:1 on paper, so it is NEVER
       text on a light surface - only a fill, or text on navy.
 --text-muted #4A566A 6.9:1 · --text-soft #5E6A7D 5.1:1 · --text 14.6:1.
   ===================================================================== */

/* ---------------------------------------------------------------- */
/* 1. Tokens                                                         */
/* ---------------------------------------------------------------- */

:root {
 --ink-900: #0d1520;
 --ink-800: #152238;
 --ink-700: #1e3050;
 --ink-600: #2a4165;
 --ink-500: #3a5480;

 --navy-100: #e6edf6;
 --navy-200: #c9daec;
 --navy-300: #9bb8d8;
 --navy-500: #2c4c7c;
 --navy-600: #24406a;
 --navy-700: #1b3050;
 --navy-900: #10203a;

 --amber: #f0a64a;
 --amber-dim: #d8913a;
 --amber-soft: #fbeedc;

 /*
   Softer than they were, on purpose.

   --white was #ffffff and --paper #f8f7f4: a pure-white card on a near-white
   page, with near-black text at 15:1 over both. Each is fine alone and the
   three together are a lamp. These sit a step down, which takes body text to
   13:1 - still nearly three times what AA asks - and stops the page being the
   brightest thing on the screen.

   --white keeps its name. It means "the surface a card sits on", not the
   colour white, and it is remapped in dark mode for exactly that reason.
 */
 --paper: #f2f0ea;
 --paper-warm: #e7e4db;
 --white: #fbfaf7;

  /* Ink on a coloured surface (buttons, active chips, the open-FAQ marker).
     Deliberately NOT --white: that token means "card surface" and is remapped
     in dark mode, which turned button text dark-on-navy at 1.84:1. The accent
     colours behind this ink are identical in both themes, so it stays fixed. */
  --on-accent: #ffffff;

  /* Destructive button ground. Fixed in both themes so white ink always clears
     AA on it (6.5:1); --bad is a TEXT colour and is lifted in dark mode. */
  --danger-bg: #b3261e;

 /* Off near-black. 13:1 on paper rather than 15:1. */
 --text: #1d2837;
 --text-muted: #4a566a;
 /*
   Darkened when the grounds came down. On --paper-warm - the ground the
   table-of-contents panel and the callouts sit on - it measured 4.31 against
   the 4.5 a 12px uppercase label needs. 4.79 now, and still a clear step
   lighter than --text-muted at 5.84.
 */
 --text-soft: #576376;
 --on-dark: #f2f6fc;
 --on-dark-muted: rgba(242, 246, 252, 0.84);
 --on-dark-faint: rgba(242, 246, 252, 0.56);

 --line: #e0ddd4;
 --line-strong: #c6c2b6;
 /*
   Ink for outlined display type - the big step numerals. Not --line-strong:
   that is an edge colour at 1.65:1 on paper, which is fine for a rule and not
   fine for a glyph. This clears 3:1 on the surface behind it in both themes,
   the floor for text this size.
 */
 /* Same reason: 3.18 passed for large display type and left nothing over. */
 --outline-ink: #6d7583;
 /*
   Amber that can carry text, the same split the semantic colours already use
   (--bad is a fill, --bad-fg is the ink). Plain --amber is a fill colour and
   sits at 1.9:1 on paper, so anything written in it was unreadable. 4.1:1
   here, and the dark side keeps the bright original because on near-black it
   is already 9:1 and darkening it would only muddy it.
 */
 /* Darkened with the grounds: 3.89 on the new paper was below the 4.5 this
    needs as a 12px pill label. 4.63 now. */
 --amber-fg: #96600f;

 /*
   The footpath. Two strengths of the same print, for the two places it can be.

   --trail-clear is used when the trail runs in the margin, clear of everything,
   and is as strong as it looks. --trail-walked is the fallback for narrow
   screens where the trail has to run down the middle of the page and therefore
   crosses running text: the ceiling there is about 0.11 on the dark sections
   and 0.18 on the light ones, measured, above which text drops below AA.

   Both live in the palette rather than in the component rule so they swap with
   the theme like everything else.
 */
 --trail-rgb: 196 128 44;
 --trail-walked: rgb(var(--trail-rgb) / 0.12);
 --trail-clear: rgb(var(--trail-rgb) / 0.72);
 /* The same hue at zero alpha. NOT the  keyword: that is
    transparent black, and fading to it draws grey footprints on the way. */
 --trail-fade: rgb(var(--trail-rgb) / 0);

 --line-dark: rgba(242, 246, 252, 0.16);
 --line-dark-soft: rgba(242, 246, 252, 0.09);

 /* The tooltip bubble floats over whatever happens to be underneath it, so it
    cannot borrow a surface token: it has to carry its own ground, its own ink
    and its own edge. Written as three tokens rather than a per-theme component
    rule so the dark swap stays a palette swap (see §18). On paper the bubble is
    a dark chip, which reads as "floating above" without a heavy shadow. */
 --tooltip-bg: var(--ink-800);
 --tooltip-fg: var(--on-dark);
 --tooltip-border: var(--ink-700);

 /* Tints behind semantic text. Defined as tokens rather than written inline,
    so they invert with the theme instead of staying light on a dark ground. */
 --good-bg: #e4efe8;
 --danger-tint: #f7e4e2;
 --warn-bg: #fdf1dc;
 --good: #2f6f4f;
 --good-fg: #1f5c3d;
 --warn-fg: #8a5a00;
 --bad: #b3261e;
 --bad-fg: #8c1d18;

 /*
   The focus ring, and the one accent that has to stay visible on every ground
   the app has. --navy-600 is a *fill* colour - it carries white button text -
   and it is deliberately not swapped in dark mode, so using it directly as an
   outline left keyboard focus at 1.6:1 against the dark page. Split out here
   so the ring can be lifted for dark without touching a single button.
 */
 --focus: var(--navy-600);

 /*
   The scrim behind a modal. It sits over the page, not on a surface, so it
   cannot borrow one: it carries its own value and swaps with the theme.
 */
 --scrim: rgba(11, 18, 32, 0.55);

 --r-sm: 8px;
 --r-md: 12px;
 --r-lg: 18px;
 --r-xl: 26px;
 --r-pill: 999px;

 --shadow-sm: 0 1px 2px rgba(15, 25, 40, 0.05), 0 2px 8px rgba(15, 25, 40, 0.04);
 --shadow-md: 0 4px 12px rgba(15, 25, 40, 0.07), 0 16px 40px rgba(15, 25, 40, 0.07);
 --shadow-lg: 0 8px 24px rgba(15, 25, 40, 0.09), 0 32px 72px rgba(15, 25, 40, 0.1);
 --shadow-dark: 0 20px 60px rgba(6, 12, 20, 0.5);

 --font-body: Inter, "Segoe UI", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
 --font-display: "Bricolage Grotesque", "Bricolage Grotesque", Inter, "Segoe UI", system-ui, sans-serif;
 --font-mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;

 --shell: 1180px;
 --gutter: clamp(1.125rem, 4vw, 2.5rem);
 --section-y: clamp(3.5rem, 8vw, 6.5rem);

 --ease: cubic-bezier(0.22, 0.7, 0.24, 1);
 --dur: 0.24s;
}

/* ---------------------------------------------------------------- */
/* 2. Reset & base                                                   */
/* ---------------------------------------------------------------- */

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

html {
 -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;
  /*
    Always reserve the scrollbar track.

    A classic scrollbar takes 15px out of the layout width, so a page that
    scrolls is 15px narrower than one that does not. Moving between, say, the
    dashboard and Settings therefore nudged the whole shell sideways on every
    navigation. Reserving the gutter unconditionally keeps every page the same
    width whether or not it happens to overflow.
  */
  scrollbar-gutter: stable;
}
@supports not (scrollbar-gutter: stable) {
  /* Older engines: keep a permanent track instead, same effect on layout. */
  html {
    overflow-y: scroll;
  }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
 -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 0.55em;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2rem, 4.6vw, 3.4rem);
}
h2 {
  font-size: clamp(1.6rem, 3.2vw, 2.4rem);
}
h3 {
  font-size: clamp(1.05rem, 1.6vw, 1.25rem);
}

p {
  margin: 0 0 1em;
}
p:last-child {
  margin-bottom: 0;
}

a {
  color: var(--navy-700);
  text-underline-offset: 0.2em;
}
a:hover {
  color: var(--navy-900);
}

img,
svg {
  max-width: 100%;
  height: auto;
  display: block;
}

ul,
ol {
  margin: 0;
  padding: 0;
}
li {
  list-style: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--paper-warm);
  padding: 0.1em 0.35em;
  border-radius: 4px;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: 6px;
}
.section--dark :focus-visible,
.app-header :focus-visible,
.site-header :focus-visible,
.site-footer :focus-visible,
.hero :focus-visible {
  outline-color: var(--amber);
}

::selection {
  background: var(--navy-200);
  color: var(--ink-800);
}

/* ---------------------------------------------------------------- */
/* 3. Utilities                                                      */
/* ---------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: 0.75rem;
  top: -100%;
  z-index: 200;
  padding: 0.75rem 1.15rem;
  background: var(--ink-800);
  color: var(--on-dark);
  border-radius: 0 0 var(--r-md) var(--r-md);
  text-decoration: none;
  font-weight: 600;
  transition: top 0.18s var(--ease);
}
.skip-link:focus {
  top: 0;
  color: var(--on-dark);
}

/*
  The hidden attribute means hidden, whatever a class says.

  The browser's own rule for [hidden] is a bare attribute selector, so any
  author rule that sets `display` on a class beats it. That turns progressive
  enhancement inside out: markup written to be revealed by script sits there
  visible and inert when script never runs, which is precisely the case the
  attribute exists to cover. It bit the bulk-action bar exactly that way.

  !important is the right tool here and not a shortcut. `display: none` from an
  attribute the author put on the element is a statement of intent that no
  component rule should be able to argue with.
*/
[hidden] {
  display: none !important;
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.shell--narrow {
  max-width: 780px;
}
/*
  Reference pages - the help index, a help topic, the questions list and help
  search - are read, not scanned. 780px put a topic's prose at roughly 100
  characters a line once the gutter came off, which is past the point where the
  eye loses the start of the next line. 860px is a compromise: the prose blocks
  cap themselves at 68ch well inside it, while the route chips, the plan table
  and the questions list get room to lay out without wrapping every row.
*/
.shell--reading {
  max-width: 860px;
}
/*
  The landing FAQ sits between the two. It is a stack of one-line questions that
  open into a paragraph, so it wants to be visibly narrower than the sections
  above it, but 780px made the longer questions wrap onto a second line and the
  rhythm of the list broke. 940px keeps almost every question on one line.
*/
.shell--faq {
  max-width: 940px;
}
/*
  Legal pages get a wider frame than the rest of the marketing site.
  Measured on the privacy policy: the generated tables were more than half the
  page height because every cell wrapped inside a 621px column. Running text
  stays at a readable measure (see .prose--legal); only the tables and the
  contents list take the extra width.

  Why Us now uses this too: it is a grid of five cards, and at 780px the grid
  collapsed to two columns with a lone card on the last row.
*/
.shell--legal {
  max-width: 1060px;
}

.section {
  padding-block: var(--section-y);
}
.section--mist {
  background: var(--paper-warm);
}
.section--dark {
  background: var(--ink-800);
  color: var(--on-dark);
}
.section--dark h2,
.section--dark h3 {
  color: var(--on-dark);
}
.section--dark p {
  color: var(--on-dark-muted);
}
.section--dark a:not(.btn) {
  color: var(--amber);
}

.section-head {
  max-width: 60ch;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}
.section-head--center {
  margin-inline: auto;
  text-align: center;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--navy-700);
  margin: 0 0 0.9rem;
}
.eyebrow::before {
  content: "";
  width: 1.6rem;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}
.section--dark .eyebrow,
.hero .eyebrow {
  color: var(--amber);
}

.lede {
  font-size: clamp(1rem, 1.4vw, 1.12rem);
  color: var(--text-muted);
  max-width: 58ch;
}
.section--dark .lede {
  color: var(--on-dark-muted);
}
.muted {
  color: var(--text-muted);
  font-size: 0.94rem;
}
.muted-note {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.85rem;
  color: var(--text-soft);
}
.link-quiet {
  font-size: 0.88rem;
  font-weight: 600;
}

.grid {
  display: grid;
  gap: clamp(1rem, 2vw, 1.4rem);
}
.grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.grid--3 {
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.grid--4 {
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}

.reveal-active .reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
.reveal-active .reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* ---------------------------------------------------------------- */
/* 4. Buttons & controls                                             */
/* ---------------------------------------------------------------- */

.btn {
 --btn-bg: var(--navy-500);
 --btn-fg: var(--on-accent);
 --btn-border: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0.65rem 1.35rem;
  border: 1px solid var(--btn-border);
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.94rem;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    background-color var(--dur) var(--ease);
}
.btn:hover {
 --btn-bg: var(--navy-600);
  color: var(--btn-fg);
  transform: translateY(-1px);
  box-shadow: 0 8px 20px rgba(44, 76, 124, 0.28);
}
.btn:active {
  transform: none;
}
.btn--ghost {
 --btn-bg: transparent;
 --btn-fg: var(--text);
 --btn-border: var(--line-strong);
}
.btn--ghost:hover {
 --btn-bg: var(--white);
 --btn-fg: var(--text);
  box-shadow: var(--shadow-sm);
}
.section--dark .btn--ghost,
.hero .btn--ghost {
 --btn-fg: var(--on-dark);
 --btn-border: var(--line-dark);
}
.section--dark .btn--ghost:hover,
.hero .btn--ghost:hover {
 --btn-bg: rgba(242, 246, 252, 0.1);
 --btn-fg: var(--on-dark);
  box-shadow: none;
}
/*
  The accent button. Ink on amber, not white: white on this amber is about 2:1
  and unreadable, whereas --ink-900 on it is 9:1. Both halves are fixed across
  themes, like every other accent pairing here, so the figure holds in both.

  Kept for the single most important action on a page. Used twice it stops
  meaning anything.
*/
.btn--accent {
 --btn-bg: var(--amber);
 --btn-fg: var(--ink-900);
 --btn-border: var(--amber);
}
.btn--accent:hover {
 --btn-bg: var(--amber-dim);
 --btn-fg: var(--ink-900);
  box-shadow: 0 8px 22px rgba(240, 166, 74, 0.3);
}
.btn--danger {
  /* Not var(--bad): that token is lifted in dark mode so it works as text,
     which drops white-on-it to 3.04:1. A destructive button keeps a fixed,
     deep red behind white ink - 6.5:1 in both themes. */
 --btn-bg: var(--danger-bg);
 --btn-fg: var(--on-accent);
}
.btn--danger:hover {
 --btn-bg: #8c1d18;
  box-shadow: 0 8px 20px rgba(179, 38, 30, 0.25);
}
.btn--sm {
  min-height: 2.35rem;
  padding: 0.4rem 1rem;
  font-size: 0.87rem;
}
.btn--lg {
  min-height: 3.1rem;
  padding: 0.75rem 1.8rem;
  font-size: 1.02rem;
}
.btn--wide {
  width: 100%;
}
.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  align-items: center;
}
.btn-row--sticky {
  position: sticky;
  bottom: 0;
  padding: 1rem 0;
  background: linear-gradient(to top, var(--paper) 65%, transparent);
}

.btn-icon {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-soft);
  cursor: pointer;
  font-size: 0.9rem;
}
.btn-icon:hover {
  background: var(--paper-warm);
  color: var(--text);
}
.btn-icon--danger:hover {
  background: #fdecea;
  color: var(--bad-fg);
}

.chip,
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.6rem;
  border-radius: var(--r-pill);
  background: var(--navy-100);
  border: 1px solid var(--navy-200);
  color: var(--navy-900);
  font-size: 0.76rem;
  font-weight: 600;
}
/*
  The chip on a dark section.

  A 16% amber wash under amber text left it at 5.02:1 - above AA, but with so
  little headroom that a footprint passing behind it took the pair to 4.22:1.
  Amber on amber was always going to be tight; the wash was doing the work of
  a tint and paying for it in legibility.

  At 6% the chip still reads as amber-tinted and the pairing sits at 6.11:1,
  which survives anything that can be drawn behind it. The border carries more
  of the identity instead, where contrast is a 3:1 question rather than 4.5:1.
*/
.section--dark .chip {
  background: rgba(240, 166, 74, 0.06);
  border-color: rgba(240, 166, 74, 0.42);
  color: var(--amber);
}
.tag--ok {
  background: var(--amber-soft);
  /* --amber-soft inverts with the theme, so its ink has to invert too.
     A hardcoded olive sat at 2.01:1 once the tint went dark. */
  border-color: var(--amber-dim);
  color: var(--warn-fg);
}
.tag--warn {
  background: #fdf1dc;
  border-color: #f0d9a8;
  color: var(--warn-fg);
}

.pill {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.55rem;
  border-radius: var(--r-pill);
  font-size: 0.74rem;
  font-weight: 700;
  text-transform: capitalize;
  background: var(--paper-warm);
  color: var(--text-muted);
}
.pill--ok,
.pill--today {
  background: var(--navy-100);
  color: var(--navy-900);
}
/*
  The three tinted pills used to carry a literal light tint and, in two cases,
  a literal dark ink. In dark mode the ground inverted with the theme and the
  ink did not, so "done" measured 1.2:1 and "overdue" was light-on-light. Every
  value here is now a token that swaps, which is the rule the rest of this
  file follows; measured light and dark, the ratios are 5.2 / 7.9 (good),
  5.3 / 7.9 (warn) and 7.5 / 8.5 (bad).
*/
.pill--good {
  background: var(--amber-soft);
  color: var(--warn-fg);
}
.pill--warn {
  background: var(--warn-bg);
  color: var(--warn-fg);
}
.pill--bad,
.pill--overdue {
  background: var(--danger-tint);
  color: var(--bad-fg);
}
/*
  The neutral pill, and the only one that is a fact rather than a judgement:
  a size, a distant date, a status with no opinion attached. One rule rather
  than five near-identical ones, so nothing can drift.

  --estimate is "about 45m" on a task row, --muted is "Not done" / "Revoked",
  --soon and --later are dates far enough away not to matter yet.
*/
.pill--soon,
.pill--later,
.pill--muted,
.pill--estimate {
  background: var(--paper-warm);
  color: var(--text-muted);
}

.glyph {
  display: inline-grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  border-radius: var(--r-md);
  background: var(--navy-100);
  border: 1px solid var(--navy-200);
  color: var(--navy-700);
  font-size: 1.05rem;
}
.section--dark .glyph {
  background: rgba(240, 166, 74, 0.14);
  border-color: rgba(240, 166, 74, 0.3);
  color: var(--amber);
}

/* ---------------------------------------------------------------- */
/* 5. Brand & headers                                                */
/* ---------------------------------------------------------------- */

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: inherit;
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: -0.025em;
}
.brand__mark {
  width: 1.75rem;
  height: 1.75rem;
  flex: none;
  color: var(--navy-500);
}
.site-header .brand,
.app-header .brand,
.site-footer .brand,
/* .plain-header was missing here, so the wordmark inherited dark body text
   onto a dark header and sat at 1.03:1 on the sign-in and register pages. */
.plain-header .brand {
  color: var(--on-dark);
}
.site-header .brand__mark,
.app-header .brand__mark,
.site-footer .brand__mark,
.plain-header .brand__mark {
  color: var(--amber);
}

/*
  Opaque at rest, frosted once something is passing under it.

  It was translucent at rest, and a sticky header at rest is not over the hero
  it looks like it belongs to - it is in its own band above it, over the page
  ground. Seven tenths of near-black over paper is grey, so the top of the
  landing page was a grey strip sitting on a navy hero. The blur was doing
  nothing there either, since there was nothing behind it to blur.

  So: the hero's own colour while you are at the top, and the translucency kept
  for the state it was meant for.
*/
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--ink-900);
  border-bottom: 1px solid var(--line-dark-soft);
  color: var(--on-dark);
}
@supports (backdrop-filter: blur(1px)) {
  .site-header.is-scrolled {
    background: rgba(13, 21, 32, 0.82);
    backdrop-filter: saturate(140%) blur(14px);
  }
}
.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 4.25rem;
  position: relative;
}
.site-header .brand {
  margin-right: auto;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 0.1rem;
}
.site-nav__link,
.site-header__login {
  display: inline-flex;
  align-items: center;
  min-height: 2.4rem;
  padding: 0.35rem 0.75rem;
  border-radius: var(--r-pill);
  color: var(--on-dark-muted);
  text-decoration: none;
  font-size: 0.89rem;
  font-weight: 600;
}
.site-nav__link:hover,
.site-header__login:hover {
  color: var(--on-dark);
  background: rgba(242, 246, 252, 0.09);
}
.site-header__actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--r-md);
  border: 1px solid var(--line-dark);
  background: rgba(242, 246, 252, 0.06);
  color: var(--on-dark);
  cursor: pointer;
}
.nav-toggle__bars {
  position: relative;
  display: block;
  width: 1rem;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transition: transform var(--dur) var(--ease);
}
.nav-toggle__bars::before {
  top: -6px;
}
.nav-toggle__bars::after {
  top: 6px;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bars {
  background: transparent;
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before {
  transform: translateY(6px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after {
  transform: translateY(-6px) rotate(-45deg);
}

.plain-header {
  padding: 1.25rem var(--gutter);
  background: var(--ink-800);
}
.plain-footer {
  padding: 1.5rem var(--gutter);
  text-align: center;
  font-size: 0.85rem;
  color: var(--text-muted);
  border-top: 1px solid var(--line);
}

/* ---------------------------------------------------------------- */
/* 6. Hero (marketing)                                               */
/* ---------------------------------------------------------------- */

.hero {
  position: relative;
  background: var(--ink-800);
  color: var(--on-dark);
  padding-block: clamp(3rem, 7vw, 5.5rem) clamp(3.5rem, 8vw, 6rem);
  overflow: hidden;
  isolation: isolate;
}
.hero::before {
  content: "";
  position: absolute;
  z-index: -1;
  width: 42rem;
  height: 42rem;
  top: -20rem;
  right: -12rem;
  border-radius: 50%;
  filter: blur(80px);
  opacity: 0.5;
  background: radial-gradient(circle, rgba(44, 76, 124, 0.75), transparent 68%);
}
.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  width: 30rem;
  height: 30rem;
  bottom: -18rem;
  left: -10rem;
  border-radius: 50%;
  filter: blur(80px);
  opacity: 0.28;
  background: radial-gradient(circle, rgba(240, 166, 74, 0.6), transparent 68%);
}
.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}
.hero__title em {
  font-style: normal;
  background: linear-gradient(96deg, var(--amber), var(--navy-300));
 -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@media (forced-colors: active) {
  .hero__title em {
    color: CanvasText;
    background: none;
  }
}
.hero__lede {
  font-size: clamp(1rem, 1.5vw, 1.15rem);
  color: var(--on-dark-muted);
  max-width: 46ch;
  margin-bottom: 1.75rem;
}
.hero__actions {
  margin-bottom: 2rem;
}
.hero__proof {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line-dark-soft);
}
.hero__proof-item {
  display: flex;
  flex-direction: column;
}
.hero__proof-value {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 800;
  color: var(--amber);
}
.hero__proof-label {
  font-size: 0.8rem;
  color: var(--on-dark-faint);
}

.hero__stage {
  display: grid;
  gap: 0.9rem;
}
.mock {
  border-radius: var(--r-lg);
  border: 1px solid var(--line-dark);
  background: linear-gradient(165deg, rgba(242, 246, 252, 0.09), rgba(242, 246, 252, 0.03));
  box-shadow: var(--shadow-dark);
  padding: 1rem 1.1rem;
}
.mock__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.75rem;
}
.mock__glyph {
  display: inline-grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: var(--r-sm);
  background: rgba(240, 166, 74, 0.16);
  color: var(--amber);
  font-size: 0.85rem;
}
.mock__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--on-dark);
}
.mock__tag {
  margin-left: auto;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--amber);
}
.mock__line {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-left: 0.6rem;
  border-left: 2px solid rgba(240, 166, 74, 0.35);
}
.mock__node {
  font-size: 0.82rem;
  color: var(--on-dark-muted);
  position: relative;
}
.mock__node::before {
  content: "";
  position: absolute;
  left: -1.03rem;
  top: 0.45rem;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--ink-700);
  border: 2px solid rgba(240, 166, 74, 0.5);
}
.mock__node.is-done::before {
  background: var(--amber);
  border-color: var(--amber);
}
.mock-task {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 0.35rem;
  font-size: 0.83rem;
  color: var(--on-dark-muted);
}
.mock-task__box {
  display: inline-grid;
  place-items: center;
  width: 1rem;
  height: 1rem;
  flex: none;
  border-radius: 4px;
  border: 1.5px solid rgba(242, 246, 252, 0.4);
  font-size: 0.65rem;
  color: var(--ink-800);
}
.mock-task--done .mock-task__box {
  background: var(--amber);
  border-color: var(--amber);
}
.mock--score {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1rem;
  align-items: center;
}
.mock-dial {
  position: relative;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: conic-gradient(var(--amber) 0 74%, rgba(242, 246, 252, 0.12) 74% 100%);
}
.mock-dial::after {
  content: "";
  position: absolute;
  inset: 0.5rem;
  border-radius: 50%;
  background: #1b2a42;
}
.mock-dial__value {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 800;
  color: var(--amber);
}
.mock-bars {
  display: grid;
  gap: 0.45rem;
}
.mock-bar {
  display: grid;
  grid-template-columns: 6rem minmax(0, 1fr);
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.72rem;
  color: var(--on-dark-faint);
}
.mock-bar__track {
  height: 5px;
  border-radius: 3px;
  background: rgba(242, 246, 252, 0.12);
  overflow: hidden;
}
.mock-bar__fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--navy-500), var(--amber));
}
.mock-bar__fill[data-fill="82"] { width: 82%; }
.mock-bar__fill[data-fill="70"] { width: 70%; }
.mock-bar__fill[data-fill="64"] { width: 64%; }

/* ---------------------------------------------------------------- */
/* 7. Marketing cards                                                */
/* ---------------------------------------------------------------- */

.card,
.feature {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  padding: clamp(1.25rem, 2vw, 1.6rem);
  border-radius: var(--r-lg);
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  height: 100%;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.card:hover,
.feature:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-md);
}
.card h3,
.feature h3 {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.card p,
.feature p {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.93rem;
}
.card__index {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--navy-700);
  margin: 0;
}
/*
  The tags on an audience card sit on one row.

  Two of them measured 209px inside a 205px card and wrapped, which read as a
  mistake rather than as a list. The chips are tightened here rather than
  globally - a chip in a dense table wants the roomier default - and the four
  pixels of slack this leaves are what the test in test/landing-layout.test.js
  is guarding.
*/
.card__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: auto;
  padding-top: 0.3rem;
}
.card__list .chip {
  padding: 0.1rem 0.4rem;
  font-size: 0.72rem;
}
.card__foot {
  margin-top: auto;
  padding-top: 0.5rem;
  font-size: 0.85rem !important;
  color: var(--text-soft) !important;
  border-top: 1px solid var(--line);
}
.card--dark {
  background: rgba(242, 246, 252, 0.05);
  border-color: var(--line-dark);
  box-shadow: none;
  color: var(--on-dark);
}
.card--dark p {
  color: var(--on-dark-muted);
}
.card--dark .card__foot {
  color: var(--on-dark-faint) !important;
  border-top-color: var(--line-dark-soft);
}
.feature__glyph {
  margin-bottom: 0.2rem;
}

.ai-promise {
  margin-top: clamp(2rem, 4vw, 3rem);
  padding: clamp(1.5rem, 3vw, 2.25rem);
  border-radius: var(--r-xl);
  background: rgba(240, 166, 74, 0.08);
  border: 1px solid rgba(240, 166, 74, 0.25);
}
.ai-promise h3 {
  margin-top: 0;
  color: var(--amber);
}
.ai-promise ul {
  display: grid;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}
.ai-promise li {
  position: relative;
  padding-left: 1.4rem;
  color: var(--on-dark-muted);
  font-size: 0.94rem;
}
.ai-promise li::before {
  content: "✓";
  position: absolute;
  left: 0;
  color: var(--amber);
  font-weight: 700;
}

.plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: 1.25rem;
  max-width: 900px;
  margin-inline: auto;
}
.plans--single {
  max-width: 460px;
}
.plan {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  padding: clamp(1.5rem, 3vw, 2rem);
  border-radius: var(--r-xl);
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-md);
}
.plan--featured {
  border-color: var(--navy-300);
}
.plan__name {
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--navy-700);
  margin: 0;
}
.plan__price {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 2.6rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  margin: 0;
}
.plan__desc {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.94rem;
}
.plan__features {
  display: grid;
  gap: 0.55rem;
  padding: 1rem 0 0;
  border-top: 1px solid var(--line);
}
.plan__features li {
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr);
  gap: 0.55rem;
  align-items: start;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.plan__tick {
  display: inline-grid;
  place-items: center;
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.18rem;
  border-radius: 50%;
  background: var(--amber-soft);
  /* --amber-soft inverts with the theme, so its ink has to invert too. The
     hardcoded brown that was here measured 2.01:1 once the tint went dark -
     which only became visible when the marketing pages started following the
     theme preference. */
  color: var(--warn-fg);
  font-size: 0.62rem;
  font-weight: 800;
}

/* ---------------------------------------------------------------- */
/* 8. FAQ / prose / tables                                           */
/* ---------------------------------------------------------------- */

.faq__panel {
  display: grid;
  gap: 0.65rem;
}
.qa {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--white);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.qa__q {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 3.1rem;
  padding: 0.8rem 1.1rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.96rem;
  cursor: pointer;
  list-style: none;
}
.qa__q::-webkit-details-marker {
  display: none;
}
.qa__q::after {
  content: "+";
  margin-left: auto;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  border-radius: 50%;
  background: var(--navy-100);
  color: var(--navy-700);
  font-size: 1rem;
  font-weight: 700;
}
.qa[open] .qa__q::after {
  content: "\2212";
  background: var(--navy-500);
  color: var(--on-accent);
}
.qa__a {
  padding: 0 1.1rem 1rem;
  color: var(--text-muted);
  font-size: 0.94rem;
}
.faq__cta {
  margin-top: 1.5rem;
  padding: 1.2rem 1.35rem;
  border-radius: var(--r-lg);
  background: var(--ink-800);
  color: var(--on-dark);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
}
.faq__cta p {
  margin: 0;
  color: var(--on-dark-muted);
}
.faq__cta .btn {
  margin-left: auto;
}

.page-hero {
  background: var(--ink-800);
  color: var(--on-dark);
  padding-block: clamp(2.5rem, 6vw, 4rem);
}
.page-hero h1 {
  margin-bottom: 0.5rem;
}
.page-hero p {
  color: var(--on-dark-muted);
  max-width: 58ch;
  margin: 0;
}
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1rem;
  font-size: 0.84rem;
  color: var(--on-dark-faint);
}
.breadcrumb a {
  color: var(--on-dark-muted);
  text-decoration: none;
}
.breadcrumb a:hover {
  color: var(--amber);
  text-decoration: underline;
}
.breadcrumb li::after {
  content: "/";
  margin-left: 0.4rem;
  opacity: 0.4;
}
.breadcrumb li:last-child::after {
  content: none;
}
.breadcrumb--app {
  color: var(--text-soft);
  margin-bottom: 0.75rem;
}
.breadcrumb--app a {
  color: var(--navy-700);
}

.prose {
  max-width: 72ch;
}

/*
  The wide variant used by the legal pages. The container itself is unbounded so
  tables can use the full frame, and the running-text elements carry the measure
  instead. 74ch is a touch wider than the standard prose column, which is what
  actually shortens these pages without pushing lines past what is comfortable
  to read.
*/
.prose--legal {
  max-width: none;
}
.prose--legal > p,
.prose--legal > h2,
.prose--legal > h3,
.prose--legal > ul,
.prose--legal > ol,
.prose--legal > .callout {
  max-width: 82ch;
}
.prose--legal .table-scroll,
.prose--legal .toc {
  max-width: none;
}
.prose h2 {
  font-size: clamp(1.25rem, 2.2vw, 1.55rem);
  margin-top: 2.25rem;
}
.prose h3 {
  font-size: 1.02rem;
  margin-top: 1.4rem;
}
.prose p,
.prose li {
  color: var(--text-muted);
}
.prose ul {
  display: grid;
  gap: 0.45rem;
  margin: 0 0 1em;
  padding-left: 1.2rem;
}
.prose ul li {
  list-style: disc;
}
.updated {
  display: inline-block;
  padding: 0.25rem 0.7rem;
  border-radius: var(--r-pill);
  background: var(--navy-100);
  border: 1px solid var(--navy-200);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--navy-900);
}
.generated-note {
  padding: 0.85rem 1rem;
  border-radius: var(--r-md);
  background: var(--amber-soft);
  border: 1px solid #f2ddb8;
  font-size: 0.88rem;
  /* --warn-fg, not a literal: the --amber-soft ground under this swaps to a
     dark brown, and a fixed dark ink on it came out at 2.01:1. */
  color: var(--warn-fg);
}
.toc {
  margin: 1.5rem 0 2rem;
  padding: 1rem 1.25rem;
  border-radius: var(--r-md);
  background: var(--paper-warm);
  border: 1px solid var(--line);
}
.toc h2 {
  margin: 0 0 0.5rem;
  font-size: 0.76rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.toc ol {
  display: grid;
  gap: 0.25rem;
  padding-left: 1.1rem;
}
.toc li {
  list-style: decimal;
  font-size: 0.9rem;
}
.callout {
  margin: 1rem 0;
  padding: 1.1rem 1.25rem;
  border-radius: var(--r-md);
  background: var(--white);
  border: 1px solid var(--line);
  border-left: 3px solid var(--navy-500);
}
.callout h3 {
  margin-top: 0;
}
.callout p {
  margin-bottom: 0.5rem;
  font-size: 0.92rem;
}
.principles {
  display: grid;
  gap: 0.6rem;
  padding: 0;
}
.principles li {
  padding: 0.7rem 1rem;
  border-radius: var(--r-md);
  background: var(--white);
  border: 1px solid var(--line);
  font-size: 0.94rem;
  color: var(--text-muted);
}

.table-scroll {
  overflow-x: auto;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  margin-bottom: 1.25rem;
}
.data-table {
  width: 100%;
  min-width: 32rem;
  border-collapse: collapse;
  font-size: 0.9rem;
  background: var(--white);
}
.data-table th,
.data-table td {
  padding: 0.7rem 0.9rem;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.data-table thead th {
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-soft);
  background: var(--paper-warm);
}
.data-table tbody th {
  font-weight: 600;
  color: var(--text);
}
.data-table td {
  color: var(--text-muted);
}
.data-table tr:last-child th,
.data-table tr:last-child td {
  border-bottom: 0;
}

/* ---------------------------------------------------------------- */
/* 9. Footer                                                         */
/* ---------------------------------------------------------------- */

.site-footer {
  background: var(--ink-900);
  color: var(--on-dark);
  padding-block: clamp(2.5rem, 5vw, 4rem) 1.75rem;
}
.site-footer a {
  color: var(--on-dark-muted);
  text-decoration: none;
  font-size: 0.9rem;
}
.site-footer a:hover {
  color: var(--amber);
  text-decoration: underline;
}
.footer__top {
  display: grid;
  /*
    Four link columns, not three. A Learn column was added and the grid was
    left at three, so Legal fell to a second row and sat on its own under the
    wordmark - which read as an afterthought rather than as one of the four.
  */
  grid-template-columns: minmax(0, 1.4fr) repeat(4, minmax(0, 1fr));
  gap: clamp(1.5rem, 4vw, 3rem);
  padding-bottom: 2rem;
  border-bottom: 1px solid var(--line-dark-soft);
}
.footer__blurb {
  margin-top: 0.9rem;
  color: var(--on-dark-faint);
  font-size: 0.9rem;
  max-width: 30ch;
}
.footer__col h2 {
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--on-dark-faint);
  margin: 0 0 0.75rem;
}
.footer__list {
  display: grid;
  gap: 0.1rem;
}
.footer__list a {
  display: inline-flex;
  /* WCAG 2.2 target size (2.5.8): these are discrete navigation links, not
     links inside a sentence, so the inline exception does not apply and both
     dimensions have to clear 24px. "AI" is the short one that forces min-width. */
  min-height: 1.9rem;
  min-width: 1.9rem;
  align-items: center;
}
.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: center;
  padding-top: 1.5rem;
  font-size: 0.84rem;
  color: var(--on-dark-faint);
}
.footer__legal-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  margin-left: auto;
}
.footer__legal-links a {
  display: inline-flex;
  align-items: center;
  min-height: 1.9rem;
  min-width: 1.9rem;
}

/* ---------------------------------------------------------------- */
/* 10. App shell                                                     */
/* ---------------------------------------------------------------- */

.app-body {
  background: var(--paper);
}
.app-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--ink-800);
  color: var(--on-dark);
  border-bottom: 1px solid var(--line-dark-soft);
}
/*
  The same two tracks as .app-shell, with the same gap, width and gutter.

  That is the whole trick: the second column of the header starts at exactly the
  same x as the second column of the page, so the search field and the page
  title share an edge. Any other arrangement lines the field up with wherever
  the wordmark happens to end, which is a different place at every text size and
  in every language.
*/
.app-header__inner {
  display: grid;
  grid-template-columns: 14rem minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.25rem);
  align-items: center;
  min-height: 3.75rem;
  padding-inline: var(--gutter);
  max-width: 1400px;
  margin-inline: auto;
}
.app-header__lead {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}
.app-header__bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}
/*
  Search takes the room, and everything else is pushed to the far end. The
  Commands button stays beside the field it belongs to rather than drifting off
  with the account controls.
*/
.app-header__bar .app-header__links {
  margin-left: auto;
}
.app-header__user {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.app-header__name {
  font-size: 0.88rem;
  color: var(--on-dark-muted);
}
.app-header__logout {
  display: inline-flex;
}
.app-header .btn--ghost {
 --btn-fg: var(--on-dark);
 --btn-border: var(--line-dark);
}

.app-shell {
  display: grid;
  grid-template-columns: 14rem minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.25rem);
  max-width: 1400px;
  margin-inline: auto;
  padding: clamp(1.25rem, 3vw, 2rem) var(--gutter) 4rem;
  align-items: start;
}
.app-nav {
  position: sticky;
  top: 4.75rem;
}
.app-nav ul {
  display: grid;
  gap: 0.15rem;
}
.app-nav__link {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  min-height: 2.6rem;
  padding: 0.5rem 0.85rem;
  border-radius: var(--r-md);
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.93rem;
}
.app-nav__link:hover {
  background: var(--paper-warm);
  color: var(--text);
}
.app-nav__link.is-active {
  background: var(--navy-100);
  color: var(--navy-900);
}
.app-nav__icon {
  width: 1.25rem;
  text-align: center;
  opacity: 0.8;
}
.app-nav__note {
  margin-top: 1rem;
  padding: 0.7rem 0.85rem;
  border-radius: var(--r-md);
  background: var(--paper-warm);
  border: 1px solid var(--line);
  font-size: 0.82rem;
  color: var(--text-muted);
}
.app-main {
  min-width: 0;
}

.page-header {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 1.75rem;
}
.page-header h1 {
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  margin-bottom: 0.3rem;
}
.page-header .lede {
  margin: 0;
  font-size: 0.98rem;
}

.subnav {
  display: flex;
  gap: 0.4rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--line);
}
.subnav__link {
  padding: 0.6rem 0.9rem;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.92rem;
}
.subnav__link.is-active {
  border-bottom-color: var(--navy-500);
  color: var(--navy-900);
}

.panel {
  padding: clamp(1.15rem, 2.2vw, 1.6rem);
  border-radius: var(--r-lg);
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  margin-bottom: 1.25rem;
}
.panel__head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.panel__title {
  margin: 0;
  font-size: 1.05rem;
}
.panel__note {
  margin-top: 0.85rem;
  font-size: 0.83rem;
  color: var(--text-soft);
}
.panel--danger {
  border-color: #f0c6c2;
  background: #fff8f7;
}
.panel--danger .panel__title {
  color: var(--bad-fg);
}
.panel--master {
  border-color: var(--navy-300);
  background: linear-gradient(160deg, var(--navy-100), var(--white));
}
.panel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.25rem;
  margin-bottom: 1.25rem;
}
.panel-grid > .panel {
  margin-bottom: 0;
}

.flash {
  padding: 0.75rem 1rem;
  border-radius: var(--r-md);
  font-size: 0.92rem;
  font-weight: 600;
  margin-bottom: 1.25rem;
}
.flash--good {
  background: var(--amber-soft);
  border: 1px solid #f2ddb8;
  color: #7a4d0c;
}
.flash--warn {
  background: #fdf1dc;
  border: 1px solid #f0d9a8;
  color: var(--warn-fg);
}
.flash--bad {
  background: #fdecea;
  border: 1px solid #f5c6c2;
  color: var(--bad-fg);
}

.empty {
  text-align: center;
  padding: clamp(2rem, 5vw, 3.5rem) 1.5rem;
  border-radius: var(--r-lg);
  background: var(--white);
  border: 1px dashed var(--line-strong);
}
.empty__icon {
  display: block;
  font-size: 2rem;
  /* navy-300 measured 2.05:1 on white, below the 3:1 floor for a large
     graphical element. --navy-700 is used because it INVERTS with the theme
     (dark ink on light, light blue on dark), so it clears the floor in both:
     12.4:1 on paper and 7.8:1 on the dark surface. A fixed colour passes one
     and fails the other. */
  color: var(--navy-700);
  margin-bottom: 0.5rem;
}
.empty h2 {
  font-size: 1.15rem;
  margin-bottom: 0.4rem;
}
.empty p {
  color: var(--text-muted);
  max-width: 44ch;
  margin-inline: auto;
}
.empty__action {
  margin-top: 1.25rem;
}

/* ---------------------------------------------------------------- */
/* 11. Score, stats, lists                                           */
/* ---------------------------------------------------------------- */

.score {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.25rem;
}
.score__dial {
  position: relative;
  width: 5.5rem;
  height: 5.5rem;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: conic-gradient(var(--navy-500) calc(var(--pct, 0) * 1%), var(--paper-warm) 0);
}
.score__dial::after {
  content: "";
  position: absolute;
  inset: 0.55rem;
  border-radius: 50%;
  background: var(--white);
}
.score__value {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--navy-900);
}
.score__meta {
  display: flex;
  flex-direction: column;
}
.score__band {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 800;
}
.score__band--ok { color: var(--navy-900); }
.score__band--warn { color: var(--warn-fg); }
.score__band--bad { color: var(--bad-fg); }
.score__label {
  font-size: 0.82rem;
  color: var(--text-soft);
}

.score-bars {
  display: grid;
  gap: 0.6rem;
}
.score-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 5rem 2rem;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.85rem;
}
.score-bar__label {
  color: var(--text-muted);
  display: flex;
  gap: 0.4rem;
  align-items: baseline;
}
.score-bar__weight {
  font-size: 0.72rem;
  color: var(--text-soft);
}
.score-bar__track {
  height: 6px;
  border-radius: 3px;
  background: var(--paper-warm);
  overflow: hidden;
}
.score-bar__fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--navy-500);
  width: calc(var(--w, 0) * 1%);
}
.score-bar__value {
  text-align: right;
  font-weight: 700;
  color: var(--text);
}

/*
  Sized so four tiles land as two rows of two in the dashboard's half-width
  panel and as a single row of four in the full-width one on Trends. At 6rem
  the dashboard fitted three and left the fourth on a line of its own, which
  reads as something having gone wrong rather than as a set of four.
*/
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 0.75rem;
}
/*
  Each tile is a link now: every one of them counts something that has its own
  page, so a tile that could not be followed was a dead end.
*/
.stat {
  display: block;
  padding: 0.85rem;
  border-radius: var(--r-md);
  background: var(--paper-warm);
  text-align: center;
  text-decoration: none;
  color: inherit;
}
.stat-grid > li {
  display: flex;
}
.stat-grid > li > .stat {
  flex: 1;
}
/*
  The overdue tile.

  Not a literal pink: --danger-tint is the same colour on paper and swaps to a
  deep red-brown on a dark page. Hardcoded, this tile stayed a pale pink block
  on the dark dashboard with dark-mode ink on it - the label came out at
  1.51:1 and the count at 1.76:1, both of them unreadable.
*/
.stat--alert {
  background: var(--danger-tint);
}
.stat__value {
  display: block;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 800;
}
.stat--alert .stat__value {
  color: var(--bad-fg);
}
.stat__label {
  display: block;
  font-size: 0.78rem;
  color: var(--text-muted);
}

.progress {
  height: 7px;
  border-radius: 4px;
  background: var(--paper-warm);
  overflow: hidden;
  margin: 0.5rem 0;
}
.progress__fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--navy-500), var(--amber));
  width: calc(var(--w, 0) * 1%);
}

.card-list {
  display: grid;
  gap: 0.85rem;
}

.task-list {
  display: grid;
  gap: 0.35rem;
}
.task {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--r-md);
  background: var(--paper);
  border: 1px solid transparent;
}
.task:hover {
  border-color: var(--line);
  background: var(--white);
}
.task.is-done .task__title {
  text-decoration: line-through;
  color: var(--text-soft);
}
.task__check {
  display: inline-flex;
  flex: none;
}
.checkbox-btn {
  display: grid;
  place-items: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 6px;
  border: 1.5px solid var(--line-strong);
  background: var(--white);
  color: var(--on-accent);
  cursor: pointer;
  font-size: 0.75rem;
  font-weight: 800;
  padding: 0;
}
.checkbox-btn:hover {
  border-color: var(--navy-500);
}
.task.is-done .checkbox-btn,
.milestone.is-done .checkbox-btn {
  background: var(--navy-500);
  border-color: var(--navy-500);
}
.task__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}
.task__title {
  font-size: 0.94rem;
}
.task__goal {
  font-size: 0.78rem;
  color: var(--text-soft);
  text-decoration: none;
}
.task__meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: none;
}

.goal-card,
.habit-card,
.note-card {
  padding: 1.1rem 1.25rem;
  border-radius: var(--r-lg);
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
}
.goal-card__head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: baseline;
  justify-content: space-between;
}
/*
  The whole goal card is the link.

  Not by wrapping it in an anchor: that would make the accessible name the
  entire card - title, status, due date, description and the milestone counts -
  read out in full every time. The title keeps the link and the link grows an
  overlay to the edges of the card instead, so the name stays the title and
  the target is the card.

  Anything interactive that ever lands on this card has to sit above the
  overlay, or the overlay swallows it. There is nothing today; the delete
  control lives on the detail page.
*/
.goal-card {
  position: relative;
}
.goal-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}
.goal-card :where(a:not(.goal-card__title a), button, form, [role="progressbar"]) {
  position: relative;
  z-index: 1;
}
.goal-card__title {
  margin: 0;
  font-size: 1.05rem;
}
.goal-card__title a {
  text-decoration: none;
  color: var(--text);
}
.goal-card__title a:hover {
  color: var(--navy-700);
}
.goal-card__meta {
  display: flex;
  gap: 0.4rem;
}
.goal-card__desc {
  margin: 0.4rem 0;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.goal-card__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin: 0.5rem 0 0;
  font-size: 0.82rem;
  color: var(--text-soft);
}
.goal-summary {
  margin-bottom: 1.5rem;
}

.milestone-list {
  display: grid;
  gap: 0.35rem;
  margin-bottom: 1rem;
}
.milestone {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.7rem;
  border-radius: var(--r-md);
  background: var(--paper);
}
.milestone.is-done .milestone__title {
  text-decoration: line-through;
  color: var(--text-soft);
}
.milestone__title {
  flex: 1;
  font-size: 0.93rem;
}

.add-row {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.75rem;
}
.add-row input[type="text"] {
  flex: 1;
  min-height: 2.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  background: var(--white);
}
.inline-form {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.select-inline {
  min-height: 2.35rem;
  padding: 0.35rem 0.65rem;
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  background: var(--white);
  text-transform: capitalize;
}

.habit-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.habit-chip__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.5rem;
  padding: 0.4rem 0.85rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--white);
  cursor: pointer;
  font-size: 0.88rem;
}
.habit-chip.is-done .habit-chip__btn {
  background: var(--amber-soft);
  border-color: var(--amber-dim);
}
.habit-chip__tick {
  display: inline-grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  border: 1.5px solid var(--line-strong);
  font-size: 0.7rem;
  font-weight: 800;
  /* The ground here is --white, which swaps to a dark card. */
  color: var(--text-soft);
}
.habit-chip.is-done .habit-chip__tick {
  background: var(--amber);
  border-color: var(--amber);
  /* Fixed ink on a fixed ground, the same pairing as .btn--accent: 8.96:1.
     The inherited colour sat at 3.54:1 on this amber, failing in BOTH themes. */
  color: var(--ink-900);
}
.habit-chip__count {
  font-size: 0.78rem;
  color: var(--text-soft);
}
.habit-card {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
}
.habit-card__main {
  flex: 1;
  min-width: 12rem;
}
.habit-card__title {
  margin: 0 0 0.25rem;
  font-size: 1rem;
}
.habit-card__stats {
  display: flex;
  gap: 1rem;
  margin: 0;
  font-size: 0.83rem;
  color: var(--text-muted);
}
.habit-card__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.note-card__head {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  justify-content: space-between;
}
.note-card__title {
  margin: 0;
  font-size: 1rem;
}
.note-card__date {
  font-size: 0.8rem;
  color: var(--text-soft);
}
.note-card__body {
  margin: 0.5rem 0;
  font-size: 0.92rem;
  color: var(--text-muted);
  white-space: pre-wrap;
}
/*
  The foot of a note: when it was written on the left, what you can do with it
  on the right. Delete moved up to the corner, so this row is no longer empty
  on a plain note - it carries the date.
*/
.note-card__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.filter-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1.25rem;
}
.filter-chip {
  display: inline-flex;
  align-items: center;
  min-height: 2.35rem;
  padding: 0.4rem 0.9rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--white);
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.88rem;
  font-weight: 600;
}
.filter-chip:hover {
  border-color: var(--navy-300);
  color: var(--navy-900);
}
.filter-chip.is-active {
  background: var(--navy-500);
  border-color: var(--navy-500);
  color: var(--on-accent);
}

/* ---------------------------------------------------------------- */
/* 12. Forms                                                         */
/* ---------------------------------------------------------------- */

.field {
  display: grid;
  gap: 0.3rem;
  margin-bottom: 1rem;
}
.field--grow {
  flex: 1;
}
.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.75rem;
}
.field > label {
  font-weight: 600;
  font-size: 0.88rem;
}
.field__hint {
  font-size: 0.82rem;
  color: var(--text-soft);
  margin: 0;
}
.field input,
.field select,
.field textarea,
.add-row input {
  width: 100%;
  min-height: 2.75rem;
  padding: 0.6rem 0.8rem;
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  background: var(--white);
  font-size: 0.95rem;
}
.field textarea {
  min-height: 6rem;
  resize: vertical;
  line-height: 1.55;
}
.field input:hover,
.field textarea:hover {
  border-color: var(--navy-300);
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--navy-500);
  box-shadow: 0 0 0 3px rgba(44, 76, 124, 0.22);
}
.field input:disabled {
  background: var(--paper-warm);
  color: var(--text-soft);
}
.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--bad);
}
.field__error {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--bad-fg);
}

.checkbox {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.6rem;
  align-items: start;
  margin-bottom: 1rem;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.checkbox input {
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.18rem;
  accent-color: var(--navy-500);
}
/*
  The consent block is a decision, not a checkbox at the bottom of a form, so it
  is given the room a decision deserves. Its own margin is zero because the
  `.auth-form` grid gap now owns the space around it; leaving both in place
  double-spaced it away from the fields it belongs with.

  Everything inside it takes --text-muted, never --text-soft: the ground here is
  --navy-100, where muted measures 6.3:1 and soft only 4.6:1.
*/
.consent-block {
  margin: 0;
  padding: 1.5rem 1.5rem 1.25rem;
  border-radius: var(--r-md);
  border: 1px solid var(--navy-200);
  background: var(--navy-100);
}
.consent-block legend {
  padding: 0 0.4rem;
  font-weight: 700;
  font-size: 0.88rem;
}
.consent-block__lede {
  margin: 0 0 1.1rem;
  max-width: 46ch;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--text-muted);
}
/* Boxed, so the choice reads as the thing being decided rather than as one more
   line of the paragraph above it. */
.consent-block__choice {
  margin: 0;
  padding: 0.9rem 1rem;
  gap: 0.75rem;
  align-items: center;
  border-radius: var(--r-md);
  border: 1px solid var(--navy-200);
  background: var(--white);
}
/* The checkbox keeps its own ring; this only tells the eye which box owns it. */
.consent-block__choice:focus-within {
  border-color: var(--navy-500);
  box-shadow: 0 0 0 3px rgba(44, 76, 124, 0.22);
}
.consent-block__choice-text {
  display: grid;
  gap: 0.2rem;
}
.consent-block__choice-title {
  font-weight: 650;
  font-size: 0.95rem;
  color: var(--text);
}
.consent-block__choice-note {
  font-size: 0.85rem;
  line-height: 1.55;
  color: var(--text-muted);
}
.consent-block__foot {
  margin: 1rem 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Accessible switch built on a real checkbox. */
.switch-control {
  position: relative;
  display: inline-flex;
  flex: none;
  cursor: pointer;
}
.switch-control input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.switch-control__track {
  display: block;
  width: 3rem;
  height: 1.7rem;
  border-radius: var(--r-pill);
  background: var(--line-strong);
  transition: background-color var(--dur) var(--ease);
  position: relative;
}
.switch-control__track::after {
  content: "";
  position: absolute;
  top: 0.2rem;
  left: 0.2rem;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  background: var(--white);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease);
}
.switch-control input:checked + .switch-control__track {
  background: var(--navy-500);
}
.switch-control input:checked + .switch-control__track::after {
  transform: translateX(1.3rem);
}
.switch-control input:focus-visible + .switch-control__track {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

/*
  One setting per row, and each row is its own decision.

  This had no vertical padding and no separator, so two consecutive settings -
  Tooltips and Reduce motion - rendered as a single four-line block and the eye
  had to work out which switch belonged to which paragraph. The padding and the
  hairline are on the class rather than on the Accessibility panel because the
  same row is used for the AI master switch, and fixing it here fixes every
  place it appears at once.

  The divider is `.toggle-row + .toggle-row` rather than a rule on the list,
  so it works whether the rows are list items or loose siblings.
*/
.toggle-row {
  display: flex;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 0;
}
.toggle-row + .toggle-row {
  border-top: 1px solid var(--line);
}

/*
  The list is a wrapper, not a surface. Its first and last rows give their outer
  padding back so the panel's own padding is not doubled at the top and bottom.
*/
.toggle-list {
  display: block;
  margin: 0.25rem 0 0;
  padding: 0;
  list-style: none;
}
.toggle-list > li:first-child {
  padding-top: 0.25rem;
}
.toggle-list > li:last-child {
  padding-bottom: 0;
}
.feature-toggles {
  display: grid;
  gap: 0.75rem;
}
.feature-toggle {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
  justify-content: space-between;
  padding: 1rem 1.1rem;
  border-radius: var(--r-md);
  background: var(--paper);
  border: 1px solid var(--line);
}
.feature-toggle.is-muted {
  opacity: 0.62;
}
/*
  The pill and the switch travel together, at the right-hand end of the row.

  The pill used to sit inside the heading, next to the feature's name, where it
  read as part of the name rather than as a statement about the control. Beside
  the switch it is doing the job it was written for: saying that this particular
  switch starts off.

  flex-start rather than centre so it lines up with the top of the row on a
  narrow screen, where the description wraps to several lines and a
  vertically-centred cluster would drift away from the heading it belongs to.
*/
.feature-toggle__control {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  flex: none;
}
.feature-toggle h3 {
  margin: 0 0 0.3rem;
  font-size: 0.98rem;
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}
.feature-toggle p {
  margin: 0 0 0.35rem;
  font-size: 0.88rem;
  color: var(--text-muted);
}
.feature-toggle__sends,
.feature-toggle__review {
  font-size: 0.82rem !important;
  color: var(--text-soft) !important;
}

/* ---------------------------------------------------------------- */
/* 13. AI affordances                                                */
/* ---------------------------------------------------------------- */

.ai-button {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 2.35rem;
  padding: 0.4rem 0.9rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--navy-300);
  background: var(--navy-100);
  color: var(--navy-900);
  font-size: 0.86rem;
  font-weight: 700;
  cursor: pointer;
}
.ai-button:hover {
  background: var(--navy-200);
}
/*
  currentColor, not --navy-500.

  --navy-500 is a FILL colour - it is the ground under button text in three
  dozen rules - and like --navy-600 it deliberately does not swap with the
  theme. Used as ink on a pill whose ground DOES swap, it went dark-on-dark:
  1.46:1 in dark mode, which is an invisible sparkle rather than a subtle one.

  Inheriting means it cannot come apart from its ground again, in either theme
  or in any variant of the button.
*/
.ai-button__spark {
  color: currentColor;
}
.ai-button--off {
  background: var(--paper-warm);
  border-color: var(--line);
  color: var(--text-soft);
  cursor: default;
  flex-wrap: wrap;
}

.ai-button__why {
  font-weight: 500;
  font-size: 0.78rem;
}
.ai-button[aria-busy="true"] {
  opacity: 0.7;
  cursor: progress;
}

.ai-slot {
  margin-top: 1rem;
  padding: 0.9rem 1rem;
  border-radius: var(--r-md);
  background: var(--navy-100);
  border: 1px solid var(--navy-200);
}
.ai-slot__status {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--navy-900);
}
.ai-slot__status:empty {
  display: none;
}
.ai-suggestions {
  display: grid;
  gap: 0.4rem;
  margin-bottom: 0.75rem;
}
.ai-suggestion {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.6rem;
  align-items: start;
  padding: 0.6rem 0.75rem;
  border-radius: var(--r-sm);
  background: var(--white);
  border: 1px solid var(--navy-200);
  font-size: 0.9rem;
}
.ai-suggestion input {
  margin-top: 0.2rem;
  width: 1.1rem;
  height: 1.1rem;
  accent-color: var(--navy-500);
}
.ai-note {
  font-size: 0.78rem;
  color: var(--navy-900);
  opacity: 0.85;
  margin: 0.5rem 0 0;
}
.ai-error {
  color: var(--bad-fg);
  font-weight: 600;
  font-size: 0.86rem;
}

.autocomplete-box {
  position: relative;
}
.autocomplete-list {
  position: absolute;
  z-index: 40;
  left: 0;
  right: 0;
  top: calc(100% + 0.25rem);
  background: var(--white);
  border: 1px solid var(--navy-200);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.autocomplete-list li {
  padding: 0;
}
.autocomplete-list button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.55rem 0.8rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 0.9rem;
}
.autocomplete-list button:hover,
.autocomplete-list button:focus {
  background: var(--navy-100);
}

/* ---------------------------------------------------------------- */
/* 14. Auth & error pages                                            */
/* ---------------------------------------------------------------- */

.plain-body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.plain-body main {
  flex: 1;
}
.auth-shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: center;
  max-width: 1060px;
  margin-inline: auto;
  padding: clamp(2rem, 6vw, 4rem) var(--gutter);
}
.auth-card {
  padding: clamp(2rem, 4vw, 3rem);
  border-radius: var(--r-xl);
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-md);
}
.auth-card h1 {
  font-size: clamp(1.5rem, 3vw, 2rem);
  margin-bottom: 0.35rem;
}
.auth-card__head {
  margin-bottom: 2rem;
}
.auth-card__head h1 {
  margin-bottom: 0.5rem;
}
.auth-card__lede {
  margin: 0;
  max-width: 36ch;
  font-size: 0.98rem;
  line-height: 1.6;
  color: var(--text-muted);
}

/*
  The form is a grid so its spacing is two numbers rather than a margin on each
  child that collapses differently depending on which children a given page
  renders. Two scales, deliberately:

    1.25rem  between fields, which belong to each other
    2rem     between regions - the fields, the consent decision, the submit

  Sign-in puts its fields directly in the form and register wraps them in a
  fieldset, so the field margin is cleared for both and the extra 0.75rem is
  added to the regions that need to stand apart.
*/
.auth-form {
  display: grid;
  gap: 1.25rem;
}
.auth-form .field {
  margin-bottom: 0;
}
.auth-form > .consent-block,
.auth-form > .auth-form__submit {
  margin-top: 0.75rem;
}
.auth-form__group {
  display: grid;
  gap: 1.25rem;
  margin: 0;
  padding: 0;
  border: 0;
}
/* A legend ignores margin in several engines, so the space under it is padding. */
.auth-form__legend {
  padding: 0 0 1rem;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.auth-form__submit {
  display: grid;
  gap: 1rem;
  margin: 0;
}
.auth-terms {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.6;
  color: var(--text-muted);
}

.auth-alt {
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
  font-size: 0.9rem;
  color: var(--text-muted);
}
.auth-aside {
  display: grid;
  gap: 1.25rem;
  align-content: start;
}
.auth-aside h2 {
  font-size: clamp(1.2rem, 2.2vw, 1.5rem);
  margin: 0;
}
.auth-aside__lede {
  margin: 0;
  max-width: 40ch;
  font-size: 0.98rem;
  line-height: 1.65;
  color: var(--text-muted);
}
.auth-benefits {
  display: grid;
  gap: 1.4rem;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
}
.auth-benefit {
  position: relative;
  display: grid;
  gap: 0.3rem;
  padding-left: 1.6rem;
}
.auth-benefit::before {
  content: "✓";
  position: absolute;
  left: 0;
  /* Inverts with the theme, for the same reason as .auth-points li::before. */
  color: var(--navy-700);
  font-weight: 800;
}
.auth-benefit__title {
  margin: 0;
  font-size: 1rem;
  font-weight: 650;
  line-height: 1.35;
  color: var(--text);
}
.auth-benefit__body {
  margin: 0;
  max-width: 42ch;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--text-muted);
}
.auth-points {
  display: grid;
  gap: 0.55rem;
  margin-top: 1rem;
}
.auth-points li {
  position: relative;
  padding-left: 1.5rem;
  color: var(--text-muted);
  font-size: 0.93rem;
}
.auth-points li::before {
  content: "✓";
  position: absolute;
  left: 0;
  /* --navy-500 is fixed in both themes, so on the dark sign-in page it sat at
     roughly 1.8:1 and the ticks were effectively gone. --navy-700 inverts to a
     pale blue, which clears the floor on paper (12.4:1) and on ink (9.1:1). */
  color: var(--navy-700);
  font-weight: 800;
}

.error-page {
  text-align: center;
  padding: clamp(3rem, 10vw, 6rem) var(--gutter);
}
.error-page__code {
  font-family: var(--font-mono);
  font-size: clamp(3rem, 12vw, 6rem);
  font-weight: 700;
  line-height: 1;
  color: var(--navy-300);
  margin: 0 0 0.5rem;
}
.error-page .btn-row {
  justify-content: center;
  margin-top: 1.5rem;
}

/* ---------------------------------------------------------------- */
/* 15. Responsive & motion                                           */
/* ---------------------------------------------------------------- */

@media (max-width: 1040px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .hero__stage {
    max-width: 32rem;
  }
  .footer__top {
    /*
      The wordmark takes the full width and the four link columns pair up
      below it, so nothing is left stranded on a row of its own.
    */
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .footer__top > :first-child {
    grid-column: 1 / -1;
  }
}

@media (max-width: 900px) {
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
  }
  .app-nav {
    position: static;
    display: none;
  }
  .app-nav.is-open {
    display: block;
  }
  .app-nav ul {
    grid-auto-flow: row;
  }
  .nav-toggle--app {
    display: inline-flex;
  }
  html:not(.js) .app-nav {
    display: block;
  }
  html:not(.js) .nav-toggle--app {
    display: none;
  }
  .auth-shell {
    grid-template-columns: minmax(0, 1fr);
  }
  .auth-aside {
    order: -1;
  }
}

@media (max-width: 820px) {
  .nav-toggle {
    display: inline-flex;
  }
  .site-header__nav {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    top: calc(100% + 0.4rem);
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
    padding: 0.7rem;
    border-radius: var(--r-lg);
    background: rgba(13, 21, 32, 0.98);
    border: 1px solid var(--line-dark);
    box-shadow: var(--shadow-dark);
  }
  .site-header__nav.is-open {
    display: flex;
  }
  .site-header__nav .site-nav__link,
  .site-header__nav .site-header__login {
    min-height: 2.75rem;
    padding-inline: 1rem;
  }
  html:not(.js) .site-header__nav {
    display: flex;
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0 0 0.75rem;
  }
  html:not(.js) .nav-toggle {
    display: none;
  }
  html:not(.js) .site-header__inner {
    flex-wrap: wrap;
  }
}

@media (max-width: 560px) {
  .footer__top {
    grid-template-columns: minmax(0, 1fr);
  }
  .footer__legal-links {
    margin-left: 0;
  }
  .btn-row .btn {
    width: 100%;
  }
  .faq__cta .btn {
    margin-left: 0;
    width: 100%;
  }
  .score-bar {
    grid-template-columns: minmax(0, 1fr) 3.5rem 2rem;
  }
  .feature-toggle {
    flex-direction: column;
  }
  .toggle-row {
    flex-direction: column;
    align-items: flex-start;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal-active .reveal {
    opacity: 1;
    transform: none;
  }
}

@media (forced-colors: active) {
  .card,
  .panel,
  .plan,
  .qa,
  .task,
  .goal-card,
  .feature {
    border: 1px solid CanvasText;
  }
  .btn {
    border: 1px solid ButtonText;
  }
  .switch-control__track {
    border: 1px solid ButtonText;
  }
}

@media print {
  .site-header,
  .app-header,
  .app-nav,
  .site-footer,
  .nav-toggle,
  .hero__stage,
  .btn-row {
    display: none !important;
  }
  body {
    background: #fff;
    color: #000;
  }
}

/* ---------------------------------------------------------------- */
/* 16. OAuth sign-in                                                 */
/* ---------------------------------------------------------------- */

.oauth-block {
  margin-bottom: 1.25rem;
}
.oauth-list {
  display: grid;
  gap: 0.5rem;
}
.oauth-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  min-height: 2.85rem;
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--white);
  color: var(--text);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.92rem;
  text-decoration: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.oauth-btn:hover {
  border-color: var(--navy-500);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}
.oauth-btn__glyph {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
  color: var(--navy-500);
}
.oauth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.1rem 0 0;
  font-size: 0.82rem;
  color: var(--text-soft);
}
.oauth-divider::before,
.oauth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.identity-list {
  display: grid;
  gap: 0.5rem;
}
.identity-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 0.9rem;
  border-radius: var(--r-md);
  background: var(--paper);
  border: 1px solid var(--line);
}
.identity-row__meta {
  flex: 1;
  min-width: 10rem;
}
.identity-row__name {
  display: block;
  font-weight: 700;
  font-size: 0.92rem;
}
.identity-row__email {
  display: block;
  font-size: 0.82rem;
  color: var(--text-soft);
}

/* ---------------------------------------------------------------- */
/* 17. Admin                                                         */
/* ---------------------------------------------------------------- */

.admin-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  padding: 0.8rem 1rem;
  margin-bottom: 1.25rem;
  border-radius: var(--r-md);
  background: var(--navy-100);
  border: 1px solid var(--navy-200);
  font-size: 0.88rem;
  color: var(--navy-900);
}
.admin-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}
.admin-metric {
  padding: 0.95rem;
  border-radius: var(--r-md);
  background: var(--white);
  border: 1px solid var(--line);
  text-align: center;
}
.admin-metric__value {
  display: block;
  font-family: var(--font-display);
  font-size: 1.6rem;
  font-weight: 800;
  color: var(--navy-700);
}
.admin-metric__label {
  display: block;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.admin-note {
  padding: 0.85rem 1rem;
  border-radius: var(--r-md);
  background: var(--amber-soft);
  border: 1px solid #f2ddb8;
  font-size: 0.85rem;
  color: #7a4d0c;
}

.prose-list {
  display: grid;
  gap: 0.4rem;
  margin: 0 0 1rem;
  padding-left: 1.2rem;
}
.prose-list li {
  list-style: disc;
  color: var(--text-muted);
  font-size: 0.92rem;
}


/* ---------------------------------------------------------------- */
/* 18. Preferences: theme and text size                              */
/* ---------------------------------------------------------------- */

/*
  Applied as attributes on <html> by the server, so the correct theme is in the
  first painted frame. Doing this in client script would flash the wrong theme
  on every load, which is worst for the people who deliberately chose dark.
*/

:root[data-text-size='large'] { font-size: 120%; }
:root[data-text-size='larger'] { font-size: 140%; }

/*
  Dark mode is a palette swap, not a second stylesheet.

  Two triggers, one set of values: an explicit choice, and the system setting
  when the user has expressed no choice. Both point at the same token block so
  they cannot drift apart.
*/
:root[data-theme='dark'] {
  color-scheme: dark;
  /* Surfaces, from furthest back to nearest front. */
  --paper: #0d1520;
  --paper-warm: #1b2942;
  --white: #16223a;

  /* Text. */
  --text: #f2f6fc;
  --text-muted: #b9c6da;
  --text-soft: #94a3ba;

  /* Lines read as light-on-dark now. */
  --line: rgba(242, 246, 252, 0.14);
  --line-strong: rgba(242, 246, 252, 0.26);
  /* Paper is #0d1520 here, so the outline has to lighten, not darken. */
  --outline-ink: #77839a;
  --amber-fg: #f0a64a;
  /* Navy prints vanish on a near-black paper, so the unwalked ones lift. */
  --trail-rgb: 240 166 74;
  --trail-walked: rgb(var(--trail-rgb) / 0.15);
  --trail-clear: rgb(var(--trail-rgb) / 0.7);
  --trail-fade: rgb(var(--trail-rgb) / 0);

  /* Tints that carried dark text on a light chip have to invert together,
     or the chip stays pale and its text disappears into it. */
  --navy-100: #223354;
  --navy-200: #2b4068;
  --navy-700: #9bb8d8;
  --navy-900: #cfe0f2;
  --amber-soft: #33270f;

  /* A near-black bubble vanishes into a near-black page, so the tooltip lifts
     to the card surface and leans on an explicitly strong edge instead. That
     edge composites to about 4.1:1 against the page ground; --line-strong
     measures 2.6:1 here and would leave the bubble with no visible shape. */
  --tooltip-bg: var(--white);
  --tooltip-border: rgba(242, 246, 252, 0.4);

  /* Semantic colours need lifting off a dark ground to stay legible. */
  --good-bg: #12301f;
  --danger-tint: #2a1618;
  --warn-bg: #33270f;
  --good-fg: #7fd6a0;
  --warn-fg: #e6b866;
  --bad: #ef6a60;
  --bad-fg: #ff9c94;

  /* 10.1:1 on the dark page and 3:1 or better on every surface above it.
     --navy-600 measures 1.6:1 here, which is a focus ring you cannot find. */
  --focus: #9bc4f5;
  --scrim: rgba(2, 6, 14, 0.72);
}

@media (prefers-color-scheme: dark) {
  :root[data-theme='system'] {
    color-scheme: dark;
  /* Surfaces, from furthest back to nearest front. */
  --paper: #0d1520;
  --paper-warm: #1b2942;
  --white: #16223a;

  /* Text. */
  --text: #f2f6fc;
  --text-muted: #b9c6da;
  --text-soft: #94a3ba;

  /* Lines read as light-on-dark now. */
  --line: rgba(242, 246, 252, 0.14);
  --line-strong: rgba(242, 246, 252, 0.26);
  /* Paper is #0d1520 here, so the outline has to lighten, not darken. */
  --outline-ink: #77839a;
  --amber-fg: #f0a64a;
  /* Navy prints vanish on a near-black paper, so the unwalked ones lift. */
  --trail-rgb: 240 166 74;
  --trail-walked: rgb(var(--trail-rgb) / 0.15);
  --trail-clear: rgb(var(--trail-rgb) / 0.7);
  --trail-fade: rgb(var(--trail-rgb) / 0);

  /* Tints that carried dark text on a light chip have to invert together,
     or the chip stays pale and its text disappears into it. */
  --navy-100: #223354;
  --navy-200: #2b4068;
  --navy-700: #9bb8d8;
  --navy-900: #cfe0f2;
  --amber-soft: #33270f;

  /* Same reasoning as the explicit dark block above. */
  --tooltip-bg: var(--white);
  --tooltip-border: rgba(242, 246, 252, 0.4);

  /* Semantic colours need lifting off a dark ground to stay legible. */
  --good-bg: #12301f;
  --danger-tint: #2a1618;
  --warn-bg: #33270f;
  --good-fg: #7fd6a0;
  --warn-fg: #e6b866;
  --bad: #ef6a60;
  --bad-fg: #ff9c94;

  /* Same reasoning as the explicit dark block above. */
  --focus: #9bc4f5;
  --scrim: rgba(2, 6, 14, 0.72);
  }
}

/*
  The few places that hardcoded a light tint rather than using a token. Kept
  deliberately short - anything added here is a hint that the component should
  be using a token instead.
*/
/*
  'system' must only ever appear inside the prefers-color-scheme block below.
  Outside it this rule painted the delete-account panel near-black on a light
  device while its text stayed dark - the confirmation label measured 1.04:1.
*/
:root[data-theme='dark'] .panel--danger {
  background: #2a1618;
  border-color: #6b2f2c;
}
:root[data-theme='dark'] .flash--bad,
:root[data-theme='dark'] .btn-icon--danger:hover {
  background: #2a1618;
  border-color: #6b2f2c;
}
:root[data-theme='dark'] .flash--good {
  background: #12301f;
  border-color: #2f6b47;
}
:root[data-theme='dark'] .flash--warn,
:root[data-theme='dark'] .notice--warn,
:root[data-theme='dark'] .admin-note,
:root[data-theme='dark'] .tag--warn {
  background: #33270f;
  border-color: #6b5424;
  color: var(--warn-fg);
}

@media (prefers-color-scheme: dark) {
  :root[data-theme='system'] .panel--danger,
  :root[data-theme='system'] .btn-icon--danger:hover { background: #2a1618; border-color: #6b2f2c; }
  :root[data-theme='system'] .flash--bad { background: #2a1618; border-color: #6b2f2c; }
  :root[data-theme='system'] .flash--good { background: #12301f; border-color: #2f6b47; }
  :root[data-theme='system'] .flash--warn,
  :root[data-theme='system'] .notice--warn,
  :root[data-theme='system'] .admin-note,
  :root[data-theme='system'] .tag--warn {
    background: #33270f;
    border-color: #6b5424;
    color: var(--warn-fg);
  }
}

/*
  Form controls are 0.95rem on a large screen, which is 15.2px at the default
  root size. Mobile Safari zooms the page whenever a focused field is under
  16px, so on small screens they go up to a full 1rem. Expressed in rem, not
  px, so the text-size preference still scales them.
*/
@media (max-width: 640px) {
  .field input,
  .field select,
  .field textarea,
  .add-row input {
    font-size: 1rem;
  }
}

/*
  Tooltips are opt-out. The script is the real enforcement - it reads the
  preference at show time and never builds a bubble - so this is a backstop for
  the one frame between a bubble existing and the observer tearing it down, not
  the mechanism. The two rules that used to live here did nothing: `[title]`
  was set to its own initial value, and `.tooltip-hint` was emitted nowhere.
*/
:root[data-tooltips='off'] .tooltip { display: none; }

/*
  A preference for reduced motion is honoured the same as the OS setting.

  "The same as" is the whole requirement, and it was not being met: the OS
  block also stops smooth scrolling and settles the scroll-reveal animations,
  and neither of those was mirrored here. Somebody who turned the preference on
  inside the app still got the page gliding under them and every section fading
  in as they scrolled - the two most obvious motions on the page - which is a
  fair reason to say the setting does nothing.

  test/stylesheet.test.js now compares the two blocks rather than trusting
  that whoever edits one remembers the other.
*/
:root[data-reduced-motion='on'] {
  scroll-behavior: auto;
}
:root[data-reduced-motion='on'] *,
:root[data-reduced-motion='on'] *::before,
:root[data-reduced-motion='on'] *::after {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
}
:root[data-reduced-motion='on'] .reveal-active .reveal {
  opacity: 1;
  transform: none;
}

/* ---------------------------------------------------------------- */
/* 19. Plan, referrals, memory                                       */
/* ---------------------------------------------------------------- */

.plan-facts {
  display: grid;
  gap: 0.5rem;
  margin: 1rem 0;
}
.plan-facts li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0.85rem;
  border-radius: var(--r-md);
  background: var(--paper-warm);
  font-size: 0.92rem;
}
.plan-facts__label { color: var(--text-muted); }

.plan-choices {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.85rem;
  margin: 1rem 0;
}
/*
  A column, so the action sits on the floor of the card rather than wherever
  the blurb above it happens to end. Four plans with four different blurb
  lengths put four buttons at four heights.
*/
.plan-choice {
  display: flex;
  flex-direction: column;
  padding: 1.1rem;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--white);
}
/*
  The flex item is the FORM, not the button inside it.

  `margin-top: auto` on the button did nothing, because the button is not a
  child of the card - it is wrapped in the form that submits the change. Two
  plans happened to have blurbs of the same height, which is the only reason
  this ever looked aligned. A plan with a one-line blurb put its button halfway
  up the card while the one beside it sat on the floor.
*/
.plan-choice > form {
  margin-top: auto;
}
.plan-choice.is-current { border-color: var(--navy-500); box-shadow: 0 0 0 1px var(--navy-500); }
.plan-choice h3 { margin: 0 0 0.35rem; font-size: 1rem; }
.plan-choice__price {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 800;
  color: var(--navy-700);
}
.plan-choice__save { margin: 0.15rem 0 0.5rem; font-size: 0.78rem; color: var(--text-soft); }
.plan-choice__blurb { margin: 0 0 0.85rem; font-size: 0.88rem; color: var(--text-muted); }
/*
  `margin: auto 0 0`, not `margin: 0`.

  This is the line the current plan shows instead of a button, so it has to land
  where the buttons land. It used to be given `margin-top: auto` by the rule
  above and then have it reset by this shorthand two rules later - same
  specificity, later wins, and nothing said so.
*/
.plan-choice__current {
  margin: auto 0 0;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--navy-700);
}

.notice {
  padding: 1rem 1.15rem;
  border-radius: var(--r-md);
  margin: 1rem 0;
}
.notice p { margin: 0 0 0.75rem; }
.notice--warn { background: var(--amber-soft); border: 1px solid #f2ddb8; color: #7a4d0c; }

.copy-field {
  display: flex;
  gap: 0.5rem;
  margin: 1rem 0 0.5rem;
}
.copy-field input {
  flex: 1;
  min-height: 2.75rem;
  padding: 0.6rem 0.8rem;
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  background: var(--paper-warm);
  font-family: var(--font-mono);
  font-size: 0.85rem;
}

.choice-set {
  border: 0;
  padding: 0;
  margin: 0 0 1.5rem;
}
.choice-set legend {
  padding: 0;
  font-weight: 700;
  font-size: 0.88rem;
  margin-bottom: 0.5rem;
}
.choice {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0.45rem 0.9rem;
  margin: 0 0.4rem 0.4rem 0;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--white);
  cursor: pointer;
  font-size: 0.9rem;
}
.choice input { accent-color: var(--navy-500); }
.choice:has(input:checked) {
  border-color: var(--navy-500);
  background: var(--navy-100);
  color: var(--navy-900);
  font-weight: 600;
}

.memory-list { display: grid; gap: 0.5rem; margin-top: 0.75rem; }
/*
  A stand-in row shares the item's box metrics by being listed here, rather than
  by having them copied into a rule of its own. Copied numbers are how a reserve
  drifts out of step with the thing it is reserving space for.
*/
.memory-item,
.memory-reserve__row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  padding: 0.8rem 0.95rem;
  border-radius: var(--r-md);
  background: var(--paper);
  border: 1px solid var(--line);
}
.memory-item__body { flex: 1; min-width: 14rem; }
.memory-item__content { margin: 0; font-size: 0.94rem; }
.memory-item__meta { margin: 0.2rem 0 0; font-size: 0.78rem; color: var(--text-soft); }

/*
  Height held for the rows a short last page does not have.

  Same reason as the review screen's tick reserve, and one consequence more.
  This pager sits near the bottom of its document, so when the panel collapsed
  the document became shorter than the scroll position and the browser clamped
  it - the reader had not scrolled, and the page moved anyway. Reserving the
  height means the document keeps its length and there is nothing to clamp.

  The stand-in is built from the row's own parts rather than from a measured
  number: it borrows the item's box above, and the two lines inside it are the
  real content and meta classes, so it is exactly as tall as a one-line item and
  stays that way if either is restyled. Borrowed metrics, invisible ink.
*/
.memory-reserve {
  display: grid;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.memory-reserve__row {
  border-color: transparent;
  background: transparent;
}
.memory-reserve__row > span {
  display: block;
}


/* ---------------------------------------------------------------- */
/* 20. Interactive hero                                              */
/* ---------------------------------------------------------------- */

.hero__layout {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  position: relative;
  z-index: 1;
}

/* The decorative field sits behind everything and never intercepts input. */
.hero__field {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.hero__glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  pointer-events: none;
}
.hero__glow--a {
  width: 40rem;
  height: 40rem;
  top: -18rem;
  right: -12rem;
  opacity: 0.42;
  background: radial-gradient(circle, rgba(44, 76, 124, 0.9), transparent 68%);
}
.hero__glow--b {
  width: 30rem;
  height: 30rem;
  bottom: -16rem;
  left: -10rem;
  opacity: 0.2;
  background: radial-gradient(circle, rgba(240, 166, 74, 0.75), transparent 68%);
}

/* ---- The working miniature ---- */

.demo {
  border-radius: var(--r-xl);
  border: 1px solid var(--line-dark);
  background: linear-gradient(165deg, rgba(242, 246, 252, 0.1), rgba(242, 246, 252, 0.035));
  box-shadow: var(--shadow-dark);
  overflow: hidden;
  backdrop-filter: blur(6px);
}
.demo__chrome {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--line-dark-soft);
}
.demo__dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: rgba(242, 246, 252, 0.24);
  flex: none;
}
.demo__title {
  margin: 0 0 0 0.5rem;
  flex: 1;
  font-family: var(--font-display);
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--on-dark);
}
.demo__ring {
  position: relative;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  flex: none;
  border-radius: 50%;
  background: conic-gradient(var(--amber) calc(var(--pct, 40) * 1%), rgba(242, 246, 252, 0.14) 0);
  transition: background 0.5s var(--ease);
}
.demo__ring::after {
  content: "";
  position: absolute;
  inset: 0.32rem;
  border-radius: 50%;
  background: #16223a;
}
.demo__ring-value {
  position: relative;
  z-index: 1;
  font-size: 0.68rem;
  font-weight: 800;
  color: var(--amber);
}

.demo__tabs {
  display: flex;
  gap: 0.25rem;
  padding: 0.6rem 0.75rem 0;
}
.demo__tab {
  min-height: 2.2rem;
  padding: 0.35rem 0.85rem;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--on-dark-muted);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.82rem;
  cursor: pointer;
}
.demo__tab:hover { background: rgba(242, 246, 252, 0.08); color: var(--on-dark); }
.demo__tab[aria-selected='true'] { background: var(--amber); color: var(--ink-900); }

/*
  All three panels share one grid cell, so the frame is as tall as the tallest
  of them whichever tab is showing. Switching tab used to resize the panel and
  shove everything below it up and down.
*/
.demo__body { display: grid; padding: 0.9rem 1.1rem 1.2rem; min-height: 15rem; }
.demo__panel { grid-area: 1 / 1; display: grid; gap: 0.55rem; align-content: start; }
/*
  visibility, not display: the box has to keep its size or the stacking above
  achieves nothing. It still takes the panel out of the accessibility tree and
  makes everything inside it unreachable by keyboard, which is what the hidden
  attribute was doing.
*/
.demo__panel.is-hidden { visibility: hidden; }

.demo__milestones { display: grid; gap: 0.35rem; }
/*
  The row box moved onto the button. The li is now a plain wrapper: leaving the
  background here as well would paint a second panel behind the control and the
  hover state would only cover part of what looks like the row.
*/
.demo__milestones li { display: block; }

/*
  A milestone is a real toggle, so it is built from the same parts as
  .demo__check below - same ground, same hover, same pressed tick. Deliberately
  no line-through on the label: it works for three tasks and turns five stacked
  milestones into a wall of crossed-out text.
*/
.demo__milestone {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  min-height: 2rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: rgba(242, 246, 252, 0.05);
  color: var(--on-dark-muted);
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}
.demo__milestone:hover {
  background: rgba(242, 246, 252, 0.12);
  border-color: rgba(242, 246, 252, 0.22);
  color: var(--on-dark);
}
.demo__milestone[aria-pressed='true'] { color: var(--on-dark-faint); }
.demo__milestone[aria-pressed='true'] .demo__tick {
  background: var(--amber);
  border-color: var(--amber);
}
.demo__milestone[aria-pressed='true'] .demo__tick::after { content: "✓"; }
.demo__tick {
  display: grid;
  place-items: center;
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  border-radius: 4px;
  border: 1.5px solid rgba(242, 246, 252, 0.28);
  font-size: 0.62rem;
  color: var(--ink-900);
}
.demo__tasks { display: grid; gap: 0.35rem; }
/*
  These are buttons that read as list rows, which is the point of the
  miniature - but the previous hover moved the ground by 5% alpha and nothing
  else, which is under the threshold where a visitor notices it is a control at
  all. The transparent resting border reserves the space, so the edge appearing
  on hover does not shift the row. Focus is the shared amber ring from
  `.hero :focus-visible`; do not add `outline: none` here.
*/
.demo__check {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  min-height: 2rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: rgba(242, 246, 252, 0.05);
  color: var(--on-dark-muted);
  font-size: 0.85rem;
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}
.demo__check:hover {
  background: rgba(242, 246, 252, 0.12);
  border-color: rgba(242, 246, 252, 0.22);
  color: var(--on-dark);
}
.demo__box {
  display: grid;
  place-items: center;
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  border-radius: 4px;
  border: 1.5px solid rgba(242, 246, 252, 0.32);
  font-size: 0.62rem;
  color: var(--ink-900);
}
.demo__check[aria-pressed='true'] .demo__box {
  background: var(--amber);
  border-color: var(--amber);
}
.demo__check[aria-pressed='true'] .demo__box::after { content: "✓"; }
.demo__check[aria-pressed='true'] span:last-child {
  text-decoration: line-through;
  color: var(--on-dark-faint);
}

.demo__score {
  margin: 0.2rem 0 0.5rem;
  font-family: var(--font-display);
  font-size: 2.4rem;
  font-weight: 800;
  color: var(--amber);
  line-height: 1;
}
.demo__score-max { font-size: 1rem; color: var(--on-dark-faint); font-weight: 600; }
.demo__bars { display: grid; gap: 0.45rem; }
.demo__bars li {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  align-items: center;
  gap: 0.6rem;
  font-size: 0.76rem;
  color: var(--on-dark-faint);
}
.demo__bar {
  height: 6px;
  border-radius: 3px;
  background: rgba(242, 246, 252, 0.12);
  overflow: hidden;
}
.demo__bar > span {
  display: block;
  height: 100%;
  border-radius: 3px;
  width: calc(var(--w, 0) * 1%);
  background: linear-gradient(90deg, var(--navy-300), var(--amber));
  transition: width 0.55s var(--ease);
}
.demo__hint {
  margin: 0.4rem 0 0;
  font-size: 0.78rem;
  color: var(--on-dark-faint);
}

/* ---------------------------------------------------------------- */
/* 21. Pricing cards                                                 */
/* ---------------------------------------------------------------- */

.pricing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16.5rem, 1fr));
  gap: 1.1rem;
  max-width: 62rem;
  margin-inline: auto;
  align-items: start;
}
.price-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.6rem 1.4rem;
  border-radius: var(--r-xl);
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  height: 100%;
}
.price-card--featured {
  border-color: var(--navy-500);
  box-shadow: var(--shadow-md);
}
.price-card__badge { position: absolute; top: -0.7rem; right: 1.3rem; }
.price-card__name {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--navy-700);
}
.price-card__amount {
  display: flex;
  align-items: baseline;
  gap: 0.15rem;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--text);
}
.price-card__currency { font-size: 1.2rem; }
.price-card__figure { font-size: 2.8rem; line-height: 1; }
.price-card__free { font-size: 2.4rem; line-height: 1; }
.price-card__period { font-size: 0.95rem; font-weight: 600; color: var(--text-soft); }
.price-card__annual { margin: 0; font-size: 0.8rem; color: var(--text-soft); }
.price-card__blurb { margin: 0.3rem 0 0; font-size: 0.9rem; color: var(--text-muted); }
.price-card__features {
  display: grid;
  gap: 0.5rem;
  margin: 0;
  padding: 1rem 0;
  border-top: 1px solid var(--line);
}
.price-card__features li {
  display: grid;
  grid-template-columns: 1.1rem minmax(0, 1fr);
  gap: 0.55rem;
  align-items: start;
  font-size: 0.88rem;
  color: var(--text-muted);
}
.price-card .btn { margin-top: auto; }
.pricing-foot {
  max-width: 46rem;
  margin: 1.5rem auto 0;
  text-align: center;
  font-size: 0.9rem;
  color: var(--text-muted);
}

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

/* Every moving part stops when the visitor has asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .demo__bar > span,
  .demo__ring { transition: none; }
}

/* ---------------------------------------------------------------- */
/* 22. Admin dashboard                                               */
/* ---------------------------------------------------------------- */

.admin-section-title {
  font-size: 1.05rem;
  margin: 1.75rem 0 0.75rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--line);
}
.admin-metric__hint {
  display: block;
  font-size: 0.7rem;
  color: var(--text-soft);
  margin-top: 0.1rem;
}
.data-table caption {
  caption-side: top;
  padding: 0.6rem 0.9rem;
  text-align: left;
  font-size: 0.8rem;
  color: var(--text-soft);
  background: var(--paper-warm);
}

/* Signup sparkline */
.spark {
  display: flex;
  align-items: flex-end;
  gap: 0.4rem;
  height: 8rem;
  margin-top: 0.75rem;
}
.spark__col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 0.25rem;
  height: 100%;
}
.spark__bar {
  width: 100%;
  min-height: 2px;
  height: calc(var(--w, 0) * 1%);
  border-radius: 3px 3px 0 0;
  background: linear-gradient(180deg, var(--navy-500), var(--navy-300));
}
.spark__value { font-size: 0.78rem; font-weight: 700; }
.spark__label { font-size: 0.68rem; color: var(--text-soft); }

/* Activation funnel */
.funnel { display: grid; gap: 0.6rem; margin-top: 0.75rem; }
.funnel__step {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr) 5.5rem;
  align-items: center;
  gap: 0.75rem;
}
.funnel__label { font-size: 0.88rem; font-weight: 600; }
.funnel__note {
  display: block;
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--text-soft);
}
.funnel__track {
  height: 0.9rem;
  border-radius: var(--r-pill);
  background: var(--paper-warm);
  overflow: hidden;
}
.funnel__fill {
  display: block;
  height: 100%;
  width: calc(var(--w, 0) * 1%);
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--navy-500), var(--amber));
}
.funnel__value { text-align: right; font-weight: 700; font-size: 0.9rem; }
.funnel__value small { font-weight: 400; color: var(--text-soft); }

@media (max-width: 640px) {
  .funnel__step { grid-template-columns: minmax(0, 1fr) 4.5rem; }
  .funnel__track { grid-column: 1 / -1; }
}


/* ==================================================================
   23. Refinement layer
   ------------------------------------------------------------------
   A pass over the signed-in application. Everything below works from
   the existing tokens, so it holds in both themes automatically, and
   every motion rule is switched off under reduced motion at the end.
   ================================================================== */

/* ---- Wordmark -------------------------------------------------- */

/*
  The name of the product should be the most confident thing in the header.

  The mark is kept at roughly 1.6x the cap height of the wordmark: much larger
  and the compass starts to read as the logo with the name as a caption, much
  smaller and the two stop looking like one lockup. Both headers reserve at
  least 3.75rem of height, so 2.15rem of mark still leaves the row breathing.
*/
.brand {
  font-size: 1.34rem;
  letter-spacing: -0.03em;
  gap: 0.6rem;
}
.brand__mark {
  width: 2.15rem;
  height: 2.15rem;
}
.app-header .brand {
  font-size: 1.4rem;
}
/*
  On a phone the signed-in header carries the wordmark, the person's name, a
  sign-out control and the nav toggle on one line. The larger lockup is the
  first thing to give way there, because the toggle has a fixed 2.75rem target
  that must not be squeezed.
*/
@media (max-width: 560px) {
  .brand,
  .app-header .brand {
    font-size: 1.2rem;
  }
  .brand__mark {
    width: 1.9rem;
    height: 1.9rem;
  }
}
/* The mark leads slightly on hover, which suits a compass needle. */
.brand__mark {
  transition: transform 0.5s var(--ease);
}
.brand:hover .brand__mark {
  transform: rotate(-12deg);
}

/* ---- Elevation --------------------------------------------------
   A hairline ring plus a soft shadow reads cleaner than a 1px border,
   and survives the theme swap because both are built from tokens. */

.panel,
.card,
.feature,
.goal-card,
.habit-card,
.note-card,
.price-card,
.auth-card,
.plan-choice,
.admin-metric {
  border-radius: var(--r-lg);
  box-shadow:
    0 1px 2px rgba(13, 21, 32, 0.04),
    0 8px 24px -12px rgba(13, 21, 32, 0.16);
  transition:
    box-shadow 0.25s var(--ease),
    transform 0.25s var(--ease),
    border-color 0.25s var(--ease);
}

.panel {
  padding: clamp(1.25rem, 2.4vw, 1.75rem);
}

/* Panels that head a section get a faint tint so the eye finds them. */
.panel--score {
  background:
    radial-gradient(120% 140% at 0% 0%, var(--navy-100) 0%, transparent 58%),
    var(--white);
}

/* ---- Page heading ----------------------------------------------- */

.page-header {
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--line);
}
.page-header h1 {
  font-size: clamp(1.7rem, 3.2vw, 2.25rem);
  letter-spacing: -0.035em;
  margin-bottom: 0.35rem;
}
/*
  No measure cap on a page lede.

  56ch is the right width for a paragraph and the wrong one for a single line
  of orientation under a title: it forced every one of these onto two or three
  lines while the space beside it sat empty. These are one sentence, and they
  read as a subtitle rather than as prose.
*/
.page-header .lede {
  font-size: 1rem;
  max-width: none;
}

/* ---- Sidebar ----------------------------------------------------- */

.app-nav__link {
  position: relative;
  border-radius: var(--r-md);
  padding-left: 1rem;
  transition:
    background-color 0.18s var(--ease),
    color 0.18s var(--ease),
    padding-left 0.18s var(--ease);
}
/* A marker that grows from nothing rather than a block that snaps in. */
.app-nav__link::before {
  content: "";
  position: absolute;
  left: 0.35rem;
  top: 50%;
  width: 3px;
  height: 0;
  border-radius: 3px;
  background: var(--amber);
  transform: translateY(-50%);
  transition: height 0.22s var(--ease);
}
.app-nav__link:hover {
  padding-left: 1.15rem;
}
.app-nav__link.is-active::before {
  height: 1.15rem;
}
.app-nav__icon {
  opacity: 0.7;
  transition: opacity 0.18s var(--ease);
}
.app-nav__link.is-active .app-nav__icon,
.app-nav__link:hover .app-nav__icon {
  opacity: 1;
}

/* ---- The score --------------------------------------------------- */

.score {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  margin-bottom: 1.5rem;
}
.score__ring {
  position: relative;
  width: 8.5rem;
  height: 8.5rem;
  flex: none;
  display: grid;
  place-items: center;
}
.score__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Start the arc at twelve o'clock rather than three. */
  transform: rotate(-90deg);
  overflow: visible;
}
.score__track,
.score__arc {
  fill: none;
  stroke-width: 9;
  stroke-linecap: round;
}
.score__track {
  stroke: var(--paper-warm);
}
.score__arc {
  stroke: url(#score-arc-gradient);
  /* 2 * pi * r, with r = 52. */
  stroke-dasharray: 326.73;
  stroke-dashoffset: calc(326.73 - (326.73 * var(--pct, 0) / 100));
  transition: stroke-dashoffset 1.1s cubic-bezier(0.22, 0.7, 0.24, 1);
}
.score__center {
  position: relative;
  display: grid;
  justify-items: center;
  line-height: 1;
}
.score__value {
  font-family: var(--font-display);
  font-size: 2.5rem;
  font-weight: 800;
  letter-spacing: -0.045em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.score__outof {
  margin-top: 0.3rem;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.score__meta {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
}
.score__band {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.score__band--good { color: var(--good-fg); }
.score__band--ok { color: var(--navy-700); }
.score__band--warn { color: var(--warn-fg); }
.score__band--bad { color: var(--bad-fg); }
.score__label {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text-muted);
}
.score__hint {
  font-size: 0.8rem;
  color: var(--text-soft);
}

/* ---- Component bars ---------------------------------------------- */

.score-bars {
  display: grid;
  gap: 0.7rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line);
}
.score-bar__track {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--paper-warm);
}
.score-bar__fill {
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--navy-500), var(--navy-300));
  transition: width 0.9s var(--ease);
}
.score-bar__value {
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* ---- Stats ------------------------------------------------------- */

.stat {
  border-radius: var(--r-md);
  border: 1px solid transparent;
  transition: border-color 0.2s var(--ease), transform 0.2s var(--ease);
}
.stat:hover {
  border-color: var(--line);
  transform: translateY(-2px);
}
.stat__value {
  font-size: 1.7rem;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

/* ---- Tasks ------------------------------------------------------- */

.task {
  position: relative;
  border-radius: var(--r-md);
  padding-left: 0.85rem;
  transition:
    background-color 0.18s var(--ease),
    border-color 0.18s var(--ease),
    transform 0.18s var(--ease);
}
/* Priority as a quiet edge marker, so the list is scannable without
   another badge competing for attention. */
.task::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55rem;
  bottom: 0.55rem;
  width: 3px;
  border-radius: 3px;
  background: transparent;
  transition: background-color 0.18s var(--ease);
}
.task:has(.pill--high)::before,
.task:has(.pill--bad)::before {
  background: var(--bad);
}
.task:hover {
  transform: translateX(2px);
}
.task.is-done {
  opacity: 0.62;
}
.task__title {
  transition: color 0.18s var(--ease);
}

.checkbox-btn {
  position: relative;
  border-radius: 7px;
  transition:
    border-color 0.18s var(--ease),
    background-color 0.18s var(--ease),
    transform 0.18s var(--ease);
}
.checkbox-btn:hover {
  transform: scale(1.08);
}
.checkbox-btn:active {
  transform: scale(0.94);
}

/* ---- Goals -------------------------------------------------------- */

.goal-card:hover,
.habit-card:hover,
.note-card:hover {
  transform: translateY(-3px);
  box-shadow:
    0 2px 4px rgba(13, 21, 32, 0.05),
    0 18px 40px -18px rgba(13, 21, 32, 0.28);
  border-color: var(--navy-300);
}
.goal-card__title a {
  text-decoration: none;
  transition: color 0.18s var(--ease);
}
.progress {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--paper-warm);
}
.progress__fill {
  border-radius: var(--r-pill);
  transition: width 0.8s var(--ease);
}

/* ---- Milestones --------------------------------------------------- */

.milestone {
  border-radius: var(--r-md);
  transition: background-color 0.18s var(--ease);
}
.milestone.is-done {
  opacity: 0.6;
}

/* ---- Empty states -------------------------------------------------- */

.empty {
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-xl);
  background:
    radial-gradient(90% 120% at 50% 0%, var(--navy-100) 0%, transparent 62%),
    var(--white);
}
.empty__icon {
  font-size: 2.4rem;
  opacity: 0.85;
}
.empty h2 {
  font-size: 1.25rem;
  letter-spacing: -0.025em;
}

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

.btn {
  letter-spacing: -0.008em;
  transition:
    transform 0.18s var(--ease),
    box-shadow 0.18s var(--ease),
    background-color 0.18s var(--ease);
}
.btn:active {
  transform: translateY(1px);
}

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

.data-table tbody tr {
  transition: background-color 0.15s var(--ease);
}
.data-table tbody tr:hover {
  background: var(--paper-warm);
}
.data-table td,
.data-table th {
  font-variant-numeric: tabular-nums;
}

/* ---- Settings navigation -------------------------------------------- */

.subnav {
  gap: 0.15rem;
  overflow-x: auto;
  scrollbar-width: thin;
}
.subnav__link {
  white-space: nowrap;
  border-radius: var(--r-md) var(--r-md) 0 0;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}
.subnav__link:hover {
  background: var(--paper-warm);
}

/* ---- Motion opt-out -------------------------------------------------
   Everything above degrades to a static, fully legible interface. */

@media (prefers-reduced-motion: reduce) {
  .score__arc,
  .score-bar__fill,
  .progress__fill,
  .brand__mark,
  .app-nav__link,
  .app-nav__link::before,
  .task,
  .checkbox-btn,
  .goal-card,
  .habit-card,
  .note-card,
  .stat,
  .btn {
    transition: none !important;
  }
  .app-nav__link:hover,
  .task:hover,
  .goal-card:hover,
  .habit-card:hover,
  .note-card:hover,
  .stat:hover,
  .checkbox-btn:hover,
  .brand:hover .brand__mark {
    transform: none;
  }
}
:root[data-reduced-motion='on'] .score__arc,
:root[data-reduced-motion='on'] .score-bar__fill,
:root[data-reduced-motion='on'] .progress__fill {
  transition: none;
}

/* ==================================================================
   24. Sidebar
   ------------------------------------------------------------------
   Grouped, with drawn icons and live counts. Overrides the earlier
   flat-list rules.
   ================================================================== */

.app-nav {
  position: sticky;
  top: 4.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  max-height: calc(100vh - 6rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 0.25rem;
}

.app-nav__group + .app-nav__group {
  margin-top: 0.9rem;
}
.app-nav__group ul {
  display: grid;
  gap: 0.1rem;
}

/* Admin sits above everything and is fenced off, so it reads as a different
   kind of destination rather than another feature tab. */
.app-nav__group--admin {
  padding-bottom: 0.9rem;
  margin-bottom: 0.3rem;
  border-bottom: 1px solid var(--line);
}
.app-nav__group--admin .app-nav__link {
  color: var(--navy-700);
  font-weight: 700;
}
.app-nav__group--admin .app-nav__link.is-active {
  background: var(--navy-100);
}

.app-nav__heading {
  margin: 0 0 0.3rem;
  padding-inline: 0.85rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.app-nav__link {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  /* Comfortably past the 24px WCAG 2.5.8 target floor, and a size that does
     not demand precision aiming on a laptop trackpad. */
  min-height: 2.6rem;
  padding: 0.5rem 0.85rem 0.5rem 1rem;
  border-radius: var(--r-md);
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.93rem;
  line-height: 1.2;
}
.app-nav__link:hover {
  background: var(--paper-warm);
  color: var(--text);
}
.app-nav__link.is-active {
  background: var(--navy-100);
  color: var(--navy-900);
}

.app-nav__icon {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
}
.app-nav__label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Counts. A number is information; an overdue number is a prompt, so the two
   are styled differently rather than relying on the reader to notice. */
.app-nav__badge {
  flex: none;
  min-width: 1.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: var(--r-pill);
  background: var(--paper-warm);
  color: var(--text-soft);
  font-size: 0.74rem;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.app-nav__link.is-active .app-nav__badge {
  background: var(--navy-200);
  color: var(--navy-900);
}
.app-nav__badge--alert {
  background: var(--danger-bg);
  color: var(--on-accent);
}

.app-nav__note {
  margin-top: 1rem;
}

@media (max-width: 900px) {
  .app-nav {
    position: static;
    /* Must be repeated here: the unconditional .app-nav rule above appears
       later in the file than the original collapse rule, so it would otherwise
       win and leave the drawer permanently open. */
    display: none;
    max-height: none;
    overflow: visible;
    padding: 0.5rem 0 1rem;
    border-bottom: 1px solid var(--line);
    margin-bottom: 1rem;
  }
  .app-nav.is-open {
    display: flex;
  }
  /* Without scripting there is no toggle, so the menu stays on the page. */
  html:not(.js) .app-nav {
    display: flex;
  }
  /* Full-width rows on a phone: easier to hit, and the grouping still reads. */
  .app-nav__link {
    min-height: 2.9rem;
  }
}

/* ---------------------------------------------------------------- */
/* 25. Search, review, trends, archive, templates, tags, workspace,  */
/*     security, import and calendar                                 */
/* ---------------------------------------------------------------- */

/* --- shared bits reused by several of the new screens ------------ */

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  margin-top: 1rem;
}
.panel__subtitle {
  margin: 1.4rem 0 0.5rem;
  font-size: 0.92rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--navy-700);
}
.split-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
  margin-bottom: 1rem;
}
.split-list__head {
  margin: 0 0 0.5rem;
  font-size: 0.86rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-soft);
}
.copy-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin: 0.75rem 0 0.25rem;
}
.copy-row input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: 0.85rem;
}
.danger-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  padding-top: 1.1rem;
  margin-top: 1.1rem;
  border-top: 1px solid var(--line);
}
.danger-row h3 {
  margin: 0 0 0.2rem;
  font-size: 0.98rem;
}
.danger-row p {
  margin: 0;
}
.stat-line {
  font-size: 1.02rem;
}

/* --- search ------------------------------------------------------ */

/*
  Two names for one thing: `.search-form` is written by hand on the help pages,
  `.list-search` is what the shared component emits for a page that holds more
  than one list. They are the same control, so they share one rule rather than
  drifting into two search boxes that look slightly different.

  `.list-search` sits inside a flex bar with a count beside it, so it takes no
  bottom margin of its own there.
*/
.search-form,
.list-search {
  display: flex;
  gap: 0.6rem;
  align-items: flex-end;
  margin-bottom: 1.25rem;
}
.list-search {
  flex: 1 1 22rem;
  margin-bottom: 0;
}
.search-form .field,
.list-search .field {
  flex: 1;
  margin: 0;
}
.filter-chip__count {
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: var(--r-pill);
  background: var(--navy-100);
  color: var(--navy-700);
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
}
.filter-chip.is-active .filter-chip__count {
  background: rgba(242, 246, 252, 0.22);
  color: var(--on-accent);
}

.result-list {
  display: grid;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.result__link {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0.2rem 0.9rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--white);
  text-decoration: none;
  color: inherit;
}
.result__link:hover {
  border-color: var(--navy-300);
  background: var(--paper-warm);
}
.result__kind {
  grid-row: span 2;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-soft);
  padding-top: 0.15rem;
}
.result__title {
  font-weight: 600;
  color: var(--navy-700);
}
.result__excerpt {
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* --- tags -------------------------------------------------------- */

.tag-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.tag-cloud a {
  text-decoration: none;
}
.tag__count {
  margin-left: 0.35rem;
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}
.tag--navy {
  background: var(--navy-100);
  border-color: var(--navy-200);
  color: var(--navy-700);
}
.tag--amber {
  background: var(--amber-soft);
  border-color: var(--amber-dim);
  color: var(--warn-fg);
}
.tag--green {
  background: var(--good-bg);
  border-color: var(--good);
  color: var(--good-fg);
}
.tag--red {
  background: var(--danger-tint);
  border-color: var(--bad);
  color: var(--bad-fg);
}
.tag--grey {
  background: var(--paper-warm);
  border-color: var(--line-strong);
  color: var(--text-muted);
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--white);
}
.tag-row__edit {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex: 1;
  min-width: 16rem;
}
.tag-row__edit .field {
  margin: 0;
}
.tag-row__actions {
  display: flex;
  gap: 0.4rem;
  margin-left: auto;
}

/* --- weekly review ----------------------------------------------- */

.review-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.25rem;
  margin-bottom: 1.25rem;
}
.tick-list {
  display: grid;
  gap: 0.4rem;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
}
.tick-list li {
  display: flex;
  gap: 0.6rem;
  align-items: baseline;
  font-size: 0.95rem;
}
.tick-list .muted {
  margin-left: auto;
  font-size: 0.82rem;
  white-space: nowrap;
}
.tick {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--good-bg);
  color: var(--good-fg);
  font-size: 0.72rem;
  font-weight: 700;
}
.tick--warn {
  background: var(--amber-soft);
  color: var(--warn-fg);
}
.tick--soft {
  background: var(--navy-100);
  color: var(--navy-700);
}

.habit-review {
  display: grid;
  gap: 0.9rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.habit-review__row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.2rem 0.75rem;
}
.habit-review__row .progress {
  grid-column: 1 / -1;
}
.habit-review__title {
  font-weight: 600;
}
.habit-review__count {
  color: var(--text-soft);
  font-size: 0.88rem;
  font-variant-numeric: tabular-nums;
}
.habit-review__row.is-kept .habit-review__count {
  color: var(--good-fg);
}

.review-history {
  display: grid;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.review-history li {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
}
.review-history .muted {
  font-size: 0.86rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- trends ------------------------------------------------------ */

.spark {
  display: block;
  width: 100%;
  height: 7.5rem;
  overflow: visible;
}
.spark__area {
  fill: var(--navy-100);
}
.spark__line {
  fill: none;
  stroke: var(--navy-500);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.spark__end {
  fill: var(--amber);
  stroke: var(--white);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}
.spark-axis {
  display: flex;
  justify-content: space-between;
  margin: 0.4rem 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.7rem;
  color: var(--text-soft);
}
.spark-axis li {
  flex: 1;
  text-align: center;
}
.spark-axis li.is-last {
  color: var(--navy-700);
  font-weight: 600;
}

.trend {
  font-variant-numeric: tabular-nums;
}
.trend--up {
  color: var(--good-fg);
}
.trend--down {
  color: var(--bad-fg);
}

.bar-rows {
  display: grid;
  gap: 0.4rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}
.bar-row {
  display: grid;
  grid-template-columns: minmax(2.5rem, auto) 1fr 3rem;
  gap: 0.6rem;
  align-items: center;
  font-size: 0.88rem;
}
.bar-row__label {
  color: var(--text-soft);
}
.bar-row__track {
  height: 0.6rem;
  border-radius: var(--r-pill);
  background: var(--paper-warm);
  overflow: hidden;
}
.bar-row__fill {
  display: block;
  height: 100%;
  /*
    Reads the width the script set, which every other bar in the app already
    does. It was `width: 0` with nothing overriding it, so the day-of-week bars
    drew empty however large the number beside them was - the markup was right,
    the script was right, and the one rule that had to consume the value did
    not. `0` is the fallback for a bar rendered before the script runs, or with
    scripting off entirely, which is the same starting point the others use.
  */
  width: calc(var(--w, 0) * 1%);
  border-radius: var(--r-pill);
  background: var(--navy-500);
}
.bar-row__value {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.fact-list {
  display: grid;
  gap: 0.5rem;
  margin: 0.75rem 0 0;
}
.fact-list > div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--line);
}
.fact-list dt {
  color: var(--text-muted);
  font-size: 0.9rem;
}
.fact-list dd {
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.habit-trend {
  display: grid;
  gap: 1rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}
.habit-trend__head {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.35rem;
  font-size: 0.92rem;
}
.habit-trend__weeks {
  display: flex;
  gap: 3px;
}
.habit-trend__week {
  flex: 1;
  height: 1.4rem;
  border-radius: 3px;
  background: var(--paper-warm);
}
.habit-trend__week[data-level='1'] {
  background: var(--navy-200);
}
.habit-trend__week[data-level='2'] {
  background: var(--navy-300);
}
.habit-trend__week[data-level='3'] {
  background: var(--navy-500);
}

/* --- archive ----------------------------------------------------- */

.archive-list {
  display: grid;
  gap: 0.5rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}
.archive-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
}
.archive-row:last-child {
  border-bottom: 0;
}
.archive-row__body {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: baseline;
}
.archive-row__kind {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-soft);
}
.archive-row__title {
  font-weight: 600;
}
.archive-row__actions {
  display: flex;
  gap: 0.4rem;
}

/* --- templates --------------------------------------------------- */

.template-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.template-card {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.25rem;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--white);
}
.template-card__title {
  margin: 0;
  font-size: 1.08rem;
}
.template-card__summary {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.92rem;
}
.template-card__contents {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.78rem;
  color: var(--text-soft);
}
.template-card__contents li {
  padding: 0.15rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--paper-warm);
}
.template-card form {
  margin-top: auto;
}

/* --- workspace --------------------------------------------------- */

/*
  One divided list, five uses. Passkeys and share links arrived later and are
  the same object as a session row - a name, a line of history, a control that
  ends it - so they join the group rather than getting a near-identical copy
  each. Everything here is token-driven, so dark needs no override.
*/
.member-list,
.session-list,
.connection-list,
.event-list,
.passkey-list,
.share-links {
  display: grid;
  gap: 0.5rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}
.member,
.session,
.connection,
.passkey,
.share-link {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  padding: 0.8rem 0;
  border-bottom: 1px solid var(--line);
}
.member:last-child,
.session:last-child,
.connection:last-child,
.passkey:last-child,
.share-link:last-child {
  border-bottom: 0;
}
.member__who,
.session__body,
.connection__body,
.passkey__body,
.share-link__main {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  flex: 1;
}
.member__name,
.session__device,
.connection__name,
.passkey__name,
.share-link__label {
  font-weight: 600;
}
.member__meta {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
/* Controls at the end of a divided row. Wrapping matters here: a rename field
   and two buttons do not fit beside a device name on a phone, and they belong
   under it rather than off the side of it. */
.member__actions,
.connection__actions,
.passkey__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-left: auto;
}
.passkey__actions {
  align-items: flex-end;
}
.session.is-current {
  /* Marked with a rule as well as a colour, so it is not colour alone. */
  border-left: 3px solid var(--good);
  padding-left: 0.7rem;
}
.panel--share .panel__head {
  margin-bottom: 0.35rem;
}

/* --- security ---------------------------------------------------- */

.enrol {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  align-items: flex-start;
  margin: 1rem 0 1.5rem;
}
.enrol__qr {
  flex: none;
  padding: 0.75rem;
  border-radius: var(--r-md);
  background: #ffffff;
  border: 1px solid var(--line);
  line-height: 0;
}
.enrol__manual {
  flex: 1;
  min-width: 15rem;
}
.secret code {
  display: inline-block;
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-md);
  background: var(--paper-warm);
  font-size: 1.02rem;
  letter-spacing: 0.08em;
  word-break: break-all;
}
.code-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.45rem;
  margin: 1rem 0;
  padding: 0;
  list-style: none;
}
.code-grid code {
  display: block;
  padding: 0.5rem;
  border-radius: var(--r-md);
  background: var(--white);
  border: 1px solid var(--line);
  text-align: center;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}

/* --- calendar ---------------------------------------------------- */

.event {
  display: flex;
  gap: 0.85rem;
  align-items: baseline;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--line);
}
.event:last-child {
  border-bottom: 0;
}
.event__time {
  flex: none;
  width: 4rem;
  color: var(--text-soft);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.event__title {
  font-weight: 500;
}

/* --- recurrence and assignment on task rows ---------------------- */

.repeat-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.1rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--navy-100);
  border: 1px solid var(--navy-200);
  color: var(--navy-700);
  font-size: 0.74rem;
  white-space: nowrap;
}
.assignee-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--paper-warm);
  border: 1px solid var(--line-strong);
  color: var(--text-muted);
  font-size: 0.74rem;
  white-space: nowrap;
}
.weekday-set {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.4rem;
}
.weekday-set label {
  position: relative;
}
.weekday-set input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.weekday-set span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.75rem;
  min-height: 2.4rem;
  padding: 0 0.5rem;
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  background: var(--white);
  font-size: 0.82rem;
}
.weekday-set input:checked + span {
  background: var(--navy-500);
  border-color: var(--navy-500);
  color: var(--on-accent);
}
.weekday-set input:focus-visible + span {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* --- the optional question at cancellation ----------------------- */

.cancel-reason summary {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0.5rem 1.1rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--white);
  color: var(--navy-700);
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
}
.cancel-reason[open] summary {
  margin-bottom: 0.75rem;
}
.cancel-reason[open] > .field,
.cancel-reason[open] > p {
  max-width: 34rem;
}

/* --- admin cohort grid ------------------------------------------- */

.cohort-cell {
  display: inline-block;
  min-width: 3rem;
  padding: 0.15rem 0.4rem;
  border-radius: var(--r-md);
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-size: 0.82rem;
  background: var(--paper-warm);
  color: var(--text-muted);
}
.cohort-cell[data-level='1'] { background: var(--navy-100); color: var(--navy-700); }
.cohort-cell[data-level='2'] { background: var(--navy-200); color: var(--navy-700); }
.cohort-cell[data-level='3'] { background: var(--navy-300); color: var(--ink-800); }
.cohort-cell[data-level='4'] { background: var(--navy-500); color: var(--on-accent); }

.bar-row__label--wide {
  /* Feature names are longer than three-letter weekday labels. */
  min-width: 11rem;
}
.bar-rows .bar-row__label--wide + .bar-row__track {
  min-width: 4rem;
}

/* --- the weekly review nudge on the dashboard --------------------- */

.notice--nudge {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
}
.notice--nudge span {
  display: block;
  color: var(--text-muted);
  font-size: 0.92rem;
}

/* --- the repeat block on the add-task form ------------------------ */

.repeat-details {
  margin: 0.5rem 0 1rem;
  padding: 0.75rem 0 0;
  border-top: 1px solid var(--line);
}
.repeat-details summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--navy-700);
  padding: 0.35rem 0;
  min-height: 2.2rem;
  display: flex;
  align-items: center;
}
.repeat-details[open] summary {
  margin-bottom: 0.5rem;
}

/* --- the sidebar nudge dot --------------------------------------- */

.app-nav__dot {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-left: auto;
  border-radius: 50%;
  background: var(--amber);
}

/* --- narrow screens ---------------------------------------------- */

@media (max-width: 640px) {
  .search-form,
  .list-search {
    flex-wrap: wrap;
  }
  .search-form .field,
  .list-search .field {
    flex-basis: 100%;
  }
  .result__link {
    grid-template-columns: 1fr;
  }
  .result__kind {
    grid-row: auto;
  }
  .enrol {
    justify-content: center;
  }
  .tag-row__edit {
    min-width: 100%;
  }
  .tag-row__actions {
    margin-left: 0;
  }
}

/* ---------------------------------------------------------------- */
/* 27. Vertical rhythm inside panels                                 */
/*                                                                   */
/* .panel__title had margin: 0 and nothing after it supplied space,  */
/* so a heading sat directly on top of the first form label and a    */
/* submit button sat directly under the last line of hint text. This */
/* is one scale applied consistently rather than a margin added to   */
/* whichever section somebody complained about.                      */
/* ---------------------------------------------------------------- */

.panel__title {
  margin: 0 0 0.9rem;
}
/* The head lays its own children out, so the title must not add to it. */
.panel__head .panel__title {
  margin-bottom: 0;
}

.panel > p,
.panel > .muted,
.panel > ul,
.panel > ol,
.panel form > p {
  margin-top: 0;
  margin-bottom: 1.05rem;
}
.panel > p:last-child,
.panel form > p:last-child {
  margin-bottom: 0;
}

.panel h3 {
  margin: 1.6rem 0 0.5rem;
  font-size: 0.98rem;
}
.panel h3:first-child {
  margin-top: 0;
}
/*
  A toggle row lays its own label out and supplies its own padding, so the
  panel scale above must not reach inside it: 1.6rem of margin-top on a heading
  that is already vertically centred in a flex row just makes the row tall and
  pushes the label off centre from the switch beside it.
*/
.toggle-row h3,
.toggle-row .panel__title {
  margin: 0 0 0.2rem;
}
.toggle-row p {
  margin: 0;
}

.field {
  gap: 0.4rem;
  margin-bottom: 1.25rem;
  /*
    Pack to the top, so a control sits under its own label.

    A field is a grid, and inside a .field-row it is also a grid ITEM, which
    stretches to the height of the tallest field in the row. Its own rows then
    share out that extra height - so one field carrying a hint made every other
    field in the row grow a gap between its label and its control, and a row of
    four inputs came out at three different heights. Packing to the start leaves
    the spare height at the bottom, where it is invisible, instead of in the
    middle where it is not.
  */
  align-content: start;
}
/* A field's own hint belongs to it, so it sits close. */
.field .field__hint {
  margin-top: 0.1rem;
}

/* A submit control never sits directly on the text above it. */
.panel form > .btn,
.panel form > button,
.panel form > .btn-row,
.panel form > .button-row {
  margin-top: 1.15rem;
}
/* Inline forms are laid out in a row and supply their own gap. */
.panel .inline-form > .btn,
.panel .inline-form > button {
  margin-top: 0;
}

.panel hr {
  margin: 1.75rem 0 1.5rem;
  border: 0;
  border-top: 1px solid var(--line);
}

.identity-list,
.btn-row {
  margin-bottom: 1.05rem;
}
.btn-row:last-child {
  margin-bottom: 0;
}

/* --- the same scale, one level out ------------------------------- */
/*
  The complaint that panels feel cramped came back four times, and each time it
  was reported against a different screen. It is one cause: the gap BETWEEN
  panels was smaller than the gap between two paragraphs inside one, so a
  settings page read as a single ruled sheet rather than as a stack of separate
  decisions. Raising the outer gap above the inner one is what makes each panel
  look like its own thing, and it costs nothing horizontally.
*/
.panel {
  margin-bottom: 1.5rem;
}
.panel-grid {
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}
.panel-grid > .panel {
  margin-bottom: 0;
}

/* A head can wrap to two rows on a narrow screen; row-gap keeps the second row
   off the first instead of relying on the column gap alone. */
.panel__head {
  gap: 0.6rem 0.75rem;
  margin-bottom: 1.15rem;
}

/*
  Rows in a list of records are separate records. 0.5rem read as one ruled block
  when every row has a border of its own.
*/
.memory-list,
.memory-reserve,
.identity-list {
  gap: 0.65rem;
}
/* The stand-in row rides along, or it reserves the wrong height at this size. */
.memory-item,
.memory-reserve__row {
  padding: 0.95rem 1.05rem;
}

/* --- administrator tier override --------------------------------- */

.panel--admin {
  border-color: var(--amber-dim);
  background: var(--amber-soft);
}
.tier-switch {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
}
.tier-switch form {
  margin: 0;
}

/* ---------------------------------------------------------------- */
/* 28. Toasts                                                        */
/* ---------------------------------------------------------------- */

.toasts {
  position: fixed;
  z-index: 200;
  bottom: 1.25rem;
  right: 1.25rem;
  display: grid;
  gap: 0.5rem;
  justify-items: end;
  /* The container spans the corner but must not intercept clicks on the page
     behind it; the toasts themselves take pointer events back. */
  pointer-events: none;
  max-width: min(24rem, calc(100vw - 2.5rem));
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.7rem 0.75rem 0.7rem 1rem;
  border-radius: var(--r-md);
  border: 1px solid var(--navy-200);
  background: var(--white);
  color: var(--navy-700);
  box-shadow:
    0 1px 2px rgba(13, 21, 32, 0.06),
    0 12px 28px -14px rgba(13, 21, 32, 0.35);
  font-size: 0.92rem;
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity 0.22s var(--ease),
    transform 0.22s var(--ease);
}
.toast.is-leaving {
  opacity: 0;
  transform: translateY(0.4rem);
}
.toast--good {
  border-left: 3px solid var(--good);
}
.toast--bad {
  border-left: 3px solid var(--bad);
  color: var(--bad-fg);
}
.toast__text {
  flex: 1;
}
.toast__close {
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-soft);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.toast__close:hover {
  background: var(--paper-warm);
  color: var(--navy-700);
}

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

@media (max-width: 640px) {
  .toasts {
    left: 1rem;
    right: 1rem;
    bottom: 1rem;
    justify-items: stretch;
    max-width: none;
  }
}

/* --- the sidebar AI notice, held to a single line ----------------- */

.app-nav__note {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  /* A status line that wraps to three lines in a narrow sidebar reads as an
     error. The full sentence is in the title attribute and in the help. */
  white-space: nowrap;
}
.app-nav__note-text {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.app-nav__note a {
  flex: none;
}

/* ---------------------------------------------------------------- */
/* 29. Help, reworked so a list of topics is scannable               */
/*                                                                   */
/* The first version was a stack of identically weighted bordered    */
/* cards, which reads as one grey mass: nothing told the eye where   */
/* one topic ended and the next began, and the plan tag sat inline   */
/* with the title where it competed with it.                         */
/* ---------------------------------------------------------------- */

.help-list {
  display: block;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}
.help-list > li {
  border-bottom: 1px solid var(--line);
}

.help-link {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 1rem;
  padding: 0.95rem 0.6rem;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  text-decoration: none;
  color: inherit;
}
.help-link:hover,
.help-link:focus-visible {
  background: var(--paper-warm);
}

.help-link__body {
  display: grid;
  gap: 0.2rem;
  min-width: 0;
}
.help-link__title {
  font-weight: 650;
  font-size: 1rem;
  color: var(--navy-700);
}
.help-link__summary {
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.help-link__aside {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: none;
}
.help-link__chevron {
  /* --navy-300 came out at 2.05:1 on the card, which is below AA and close
     enough to invisible that the affordance was not doing its job. */
  color: var(--text-muted);
  font-size: 1.3rem;
  line-height: 1;
  transition: transform 0.18s var(--ease);
}
.help-link:hover .help-link__chevron {
  transform: translateX(2px);
  color: var(--navy-500);
}

/* The plan tag is a fact about the topic, not part of its name. */
.help-plan {
  flex: none;
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 0.15rem 0.5rem;
}

/* --- questions --------------------------------------------------- */

.faq-list {
  border-top: 1px solid var(--line);
  gap: 0;
}
.faq {
  border-bottom: 1px solid var(--line);
}
.faq details {
  border: 0;
  border-radius: 0;
  background: transparent;
}
.faq details[open] {
  background: var(--paper-warm);
  border-radius: var(--r-md);
}
.faq summary {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.85rem 0.75rem;
  min-height: 0;
  list-style: none;
  font-weight: 600;
  line-height: 1.45;
}
/* Replace the platform triangle with a marker that has two clear states. */
.faq summary::-webkit-details-marker {
  display: none;
}
.faq summary::marker {
  content: '';
}
.faq__marker {
  position: relative;
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.1rem;
  border-radius: 50%;
  background: var(--navy-100);
}
.faq__marker::before,
.faq__marker::after {
  content: '';
  position: absolute;
  inset: 50% 0.28rem auto;
  height: 2px;
  border-radius: 1px;
  background: var(--navy-700);
  transform: translateY(-50%);
}
.faq__marker::after {
  transform: translateY(-50%) rotate(90deg);
  transition: transform 0.18s var(--ease);
}
.faq details[open] .faq__marker::after {
  transform: translateY(-50%) rotate(0deg);
}
.faq__question {
  min-width: 0;
}
.faq details > p {
  padding: 0 0.75rem 1rem 2.6rem;
  max-width: 66ch;
  line-height: 1.6;
}

@media (prefers-reduced-motion: reduce) {
  .help-link__chevron,
  .faq__marker::after {
    transition: none;
  }
}

/* --- where to find this ------------------------------------------ */

/* Now a <ul> inside a titled region, so the list resets are needed and the old
   bottom margin is gone: the region's own grid gap supplies that space. */
.help-routes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
/*
  The arrow inherits the chip's own ink rather than taking --text-soft. Soft on
  --navy-100 measures 4.58:1 on paper and 4.85:1 on ink, which passes but sits
  close enough to the floor that a rounding difference in the audit would fail
  the build. The body font is what makes it read as quieter than the mono path
  beside it, so nothing is lost.
*/
.help-route__arrow {
  flex: none;
  font-family: var(--font-body);
}
.help-route__label {
  /* Monospace when this was a URL. It is a sentence now, so it reads in the
     same face as everything else around it. */
  min-width: 0;
  font-weight: 550;
}
.help-route {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 2.2rem;
  padding: 0.25rem 0.7rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--navy-200);
  background: var(--navy-100);
  color: var(--navy-700);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  text-decoration: none;
}
.help-route:hover {
  border-color: var(--navy-300);
}

/* --- how we use AI ----------------------------------------------- */

.ai-features {
  margin: 1.75rem 0;
}
/*
  The topic around this is capped at 44rem, which is the right measure for the
  prose it usually holds and the wrong one for a grid of cards: five of them in
  a 44rem column is a page you scroll rather than read.

  Widened only where that grid is present, so every other help topic keeps its
  reading measure. Where :has() is missing this simply does not apply and the
  section stays as it was, which is a fair place to land.
*/
.help-topic:has(.ai-features) {
  max-width: 64rem;
}
/*
  Across, not down.

  Five features in one column made a page you scrolled through rather than read,
  with the right two thirds of the panel empty the whole way. Each card is short
  - a name, a sentence, a row of what it sends - so they sit beside each other
  comfortably.

  auto-fit rather than a column count, so this needs no breakpoint of its own:
  the cards fall back to one column when there is no room for two.
*/
/*
  One card per row.

  Two columns fitted five cards into less height, and made each of them 384px
  wide - narrow enough that the name and its "off by default" badge wrapped onto
  separate lines and the "sends" chips wrapped under their label. A list of five
  things is not long enough for the height to be worth that.
*/
.ai-feature-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
  margin: 1.1rem 0 0;
  padding: 0;
  list-style: none;
}
.ai-feature {
  padding: 1.05rem 1.15rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--navy-300);
  border-radius: var(--r-md);
  background: var(--white);
}
.ai-feature__head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 0.45rem;
}
.ai-feature__name {
  margin: 0;
  font-size: 1rem;
  color: var(--navy-700);
}
.ai-feature__default {
  flex: none;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 0.15rem 0.5rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
}
.ai-feature__default--on {
  background: var(--good-bg);
  border-color: var(--good);
  color: var(--good-fg);
}
.ai-feature__default--off {
  background: var(--paper-warm);
  color: var(--text-muted);
}
.ai-feature__what {
  margin: 0 0 0.7rem;
  color: var(--text-muted);
  line-height: 1.55;
}
.ai-feature__sends {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
}
.ai-feature__label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-soft);
}
.chip {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.55rem;
  border-radius: var(--r-pill);
  background: var(--navy-100);
  border: 1px solid var(--navy-200);
  color: var(--navy-700);
  font-size: 0.76rem;
}

/* --- reference links in the app header ---------------------------- */

.app-header__links {
  display: flex;
  gap: 0.25rem;
  margin-left: auto;
  margin-right: 0.75rem;
}
.app-header__links a {
  display: inline-flex;
  align-items: center;
  min-height: 2.4rem;
  padding: 0.3rem 0.7rem;
  border-radius: var(--r-pill);
  color: var(--on-dark-muted);
  text-decoration: none;
  font-size: 0.88rem;
}
.app-header__links a:hover {
  color: var(--on-dark);
  background: rgba(242, 246, 252, 0.09);
}

@media (max-width: 820px) {
  .app-header__links {
    display: none;
  }
}

@media (max-width: 640px) {
  .help-link {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .help-link__chevron {
    display: none;
  }
  .faq details > p {
    padding-left: 0.75rem;
  }
}

/* ---------------------------------------------------------------- */
/* 30. Landing page: bordered cards that line up                     */
/*                                                                   */
/* Three separate causes of the ragged look:                         */
/*                                                                   */
/*   1. .pricing-grid had align-items: start, which overrode the     */
/*      height: 100% on the cards, so every card in a row was a      */
/*      different height.                                            */
/*   2. There are four plans now, and 4 x 16.5rem did not fit inside */
/*      the 62rem container, so the row broke to 3 + 1.              */
/*   3. auto-fit picks its own column count, so a row of four cards  */
/*      could become 3 + 1 at some widths. Deliberate breakpoints    */
/*      wrap to 2 + 2, which is symmetrical.                         */
/* ---------------------------------------------------------------- */

.pricing-grid {
  /* Stretch, so height: 100% on the card actually resolves. */
  align-items: stretch;
  max-width: 74rem;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 68rem) {
  .pricing-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 34rem) {
  .pricing-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A featured card is raised, so give the row room rather than letting it
   overlap the card beside it. */
.price-card--featured {
  align-self: stretch;
}

/* Audience cards: four across, then two, then one. Never three plus one. */
.grid--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: stretch;
}
@media (max-width: 62rem) {
  .grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 34rem) {
  .grid--4 {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Feature cards: three across, then two, then one. */
.grid--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: stretch;
}
@media (max-width: 60rem) {
  .grid--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 40rem) {
  .grid--3 {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Every bordered card fills its row, so the borders line up top and bottom
   even when one card holds two more lines of copy than its neighbour. */
.grid > .card,
.grid > .feature,
.pricing-grid > .price-card {
  height: 100%;
  margin: 0;
}

/* The AI section's list of features sits in the same rhythm. */
.section--dark .grid {
  align-items: stretch;
}

/* ==================================================================
   31. Reference pages, long lists and the tooltip
   ------------------------------------------------------------------
   Seven separate changes landed across the app at once and every one
   of them needed styling that did not exist yet. They are collected
   here rather than scattered through the sections above, because they
   share one idea: a block of content owns the space around itself,
   through a gap on its container, instead of every child carrying a
   margin that then collapses or doubles depending on which siblings
   happen to render.

   Everything below is built from tokens, so it holds in both themes
   without a second set of component rules.
   ================================================================== */

/* ---- The public reference shell ---------------------------------- */

/*
  `.section`'s padding is tuned for a marketing hero and is far too tall above
  a help heading. This is the same section, stepped down: enough that the h1 is
  not flush under the site header, less than a landing page.
*/
.section--reference {
  padding-block: clamp(2rem, 4vw, 3rem) clamp(3rem, 6vw, 4.5rem);
}

/*
  Inside the signed-in app the reference pages nest a `.shell` inside
  `.app-shell`, which has already applied the gutter. Written with both classes
  so it wins over `.shell` regardless of source order.
*/
.shell.reference-shell {
  padding-inline: 0;
}

/* ---- The name in the app header is a link ------------------------ */

/*
  Was a span, is now the way into account settings, so it has to look and
  behave like the reference links beside it. The 2.4rem box is load-bearing:
  it is what keeps the control over the 24x24 target minimum.
*/
.app-header__name {
  display: inline-flex;
  align-items: center;
  min-height: 2.4rem;
  padding: 0.3rem 0.7rem;
  border-radius: var(--r-pill);
  font-size: 0.88rem;
  color: var(--on-dark-muted);
  text-decoration: none;
  cursor: pointer;
}
.app-header__name:hover,
.app-header__name:focus-visible {
  color: var(--on-dark);
  background: rgba(242, 246, 252, 0.09);
}

/* ---- Pagers ------------------------------------------------------ */

/*
  Rendered by the shared component for every long list. It has to wrap rather
  than overflow: on a phone the status line and the three controls do not fit
  on one line, and a pager that scrolls the page sideways is worse than none.
*/
.pager {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 1rem;
  align-items: center;
  justify-content: space-between;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}
.pager__status {
  margin: 0;
  font-size: 0.86rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.pager__controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  align-items: center;
}
.pager__link {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  min-height: 2.35rem;
  padding: 0.4rem 0.85rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--white);
  color: var(--navy-700);
  text-decoration: none;
  font-size: 0.87rem;
  font-weight: 600;
  white-space: nowrap;
}
.pager__link:hover {
  border-color: var(--navy-300);
  background: var(--paper-warm);
}
/*
  The disabled end of the range is a span, not an anchor, so it has to look
  inert without becoming unreadable: --text-soft is the quietest token that
  still clears 4.5:1 on a panel (5.5:1 on paper, 6.1:1 on ink). Opacity was the
  obvious alternative and was rejected - it would have taken the text under
  the floor.
*/
.pager__link.is-disabled {
  border-color: var(--line);
  background: transparent;
  color: var(--text-soft);
  cursor: default;
}
.pager__page {
  padding-inline: 0.35rem;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---- The bar above a long list ----------------------------------- */

.admin-list__controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
/*
  This is the answer to "how much is there", so it is quiet but never faint:
  the same treatment as .muted, which clears 4.5:1 on a panel in both themes.
  It wraps rather than pushing the row wide, because it can end in a search
  term of any length.
*/
.admin-list__count {
  margin: 0;
  min-width: 0;
  font-size: 0.88rem;
  color: var(--text-muted);
  white-space: normal;
}

@media (max-width: 640px) {
  .admin-list__controls {
    align-items: stretch;
  }
  /* The count drops under the form rather than beside it. */
  .admin-list__count {
    order: 1;
  }
}

/* ---- A help topic reads as separate blocks ----------------------- */

/*
  One container owns the whole vertical rhythm of a topic. Every block inside
  is optional, so spacing carried on the blocks themselves came out uneven: a
  topic with no prose but three steps was spaced differently from one with
  prose and no steps. A gap does not care which blocks rendered.
*/
/*
  A topic reads as one column rather than as a wide band.

  The prose inside it was already capped at a readable measure, but nothing else
  was: the heading, the lede, the rule under it, the route chips and the
  questions all ran the full 860px, so the page looked like a wide layout with a
  narrow paragraph sitting in it. Capping the whole region puts every part of it
  on the same left and right edge, which is what makes it read as a document.

  Set on the region, not on .shell--reading, because the help index and the
  questions page are lists rather than documents and genuinely want the width.
*/
.help-topic {
  max-width: 44rem;
  display: grid;
  gap: clamp(1.5rem, 3.5vw, 2.25rem);
}
/* The panel, the notice and the AI block each carry their own margin, which
   would stack on top of the gap. The gap is the only spacing here. */
.help-topic > * {
  margin-block: 0;
}

/*
  The prose fills its column.

  It used to cap itself at 68ch - about 590px inside a 780px column - which is a
  good measure for a long read and the wrong one here: these are three or four
  short paragraphs, and holding them to two thirds of the width made the block
  taller than it needed to be while the space beside it sat empty. The column
  itself is what keeps the line length honest.
*/
.help-body {
  display: grid;
  gap: 0.9rem;
  line-height: 1.65;
}
.help-body > p {
  margin: 0;
}

.help-section {
  display: grid;
  gap: 0.85rem;
}
/* Same reasoning as .help-topic: the region's gap is the spacing, so its
   children do not also carry margins. */
.help-section > * {
  margin-block: 0;
}
.help-section__title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--navy-700);
}
.help-section__lede {
  margin: 0;
  font-size: 0.92rem;
  color: var(--text-muted);
}

/*
  A tinted, bordered region, so "where to find this" reads as a group of links
  rather than a heading sitting on a row of loose chips. The hairline is under
  3:1 against paper, which is correct here: this is a grouping container with
  no state, not a control boundary, and the tint is what marks the region off.
  Same recipe as the facts strip on an asset page below.
*/
.help-section--routes,
.asset-facts {
  padding: clamp(1rem, 2.2vw, 1.35rem);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--paper-warm);
}

/*
  Numbered, so the markers have to be put back: the base stylesheet clears
  list-style on every li. Block layout rather than grid, because a marker on a
  grid item is not reliably drawn across engines; adjacent-sibling margins are
  safe here because nothing above them can collapse into the list.
*/
.help-steps {
  margin: 0;
  padding-left: 1.4rem;
  max-width: 68ch;
  line-height: 1.6;
}
.help-steps > li {
  list-style: decimal;
}
.help-steps > li + li {
  margin-top: 0.6rem;
}
/* --text-muted, not --text-soft: a marker is text and this one is small. */
.help-steps > li::marker {
  color: var(--text-muted);
  font-weight: 700;
}

/* Two shortcuts under the help search. These existed in markup with no rule at
   all, so the buttons ran together with no gap between them. */
.help-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1.75rem;
}

/* ---- Why Us ------------------------------------------------------ */

/*
  auto-fill rather than auto-fit: five cards in three tracks keeps every card
  the same width instead of stretching the last row's two across the page.
  The track floor is min()-guarded so a viewport narrower than the track
  cannot force the grid wider than its container.
*/
.asset-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: clamp(1rem, 2.2vw, 1.5rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

/*
  Four rows, with the facts taking the slack, so the action sits on the bottom
  edge of every card and the buttons line up across a row whatever the length
  of the copy above them.

  No stretched overlay on the title link: the card also holds a real submit
  button, and an overlay would swallow it.
*/
.asset-card {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  gap: 0.85rem;
  padding: clamp(1.15rem, 2.2vw, 1.5rem);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--white);
  box-shadow:
    0 1px 2px rgba(13, 21, 32, 0.04),
    0 8px 24px -12px rgba(13, 21, 32, 0.16);
  transition:
    box-shadow 0.25s var(--ease),
    border-color 0.25s var(--ease);
}
.asset-card:hover,
.asset-card:focus-within {
  border-color: var(--navy-300);
  box-shadow:
    0 2px 4px rgba(13, 21, 32, 0.06),
    0 16px 36px -14px rgba(13, 21, 32, 0.24);
}

.asset-card__stage {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
/* The deal-order digit. letter-spacing is cancelled or the tracking inherited
   from the parent pushes a single character off centre in its circle. */
.asset-card__order {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  border: 1px solid var(--navy-200);
  background: var(--navy-100);
  color: var(--navy-700);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
}
.asset-card__stage-name {
  min-width: 0;
}

.asset-card__title {
  margin: 0;
  font-size: 1.12rem;
  line-height: 1.3;
  letter-spacing: -0.02em;
}
.asset-card__link {
  color: var(--navy-700);
  text-decoration: none;
}
.asset-card__link:hover {
  text-decoration: underline;
}

.asset-card__facts {
  display: grid;
  gap: 0.7rem;
  margin: 0;
}
.asset-card__fact,
.asset-facts__item {
  display: grid;
  gap: 0.15rem;
}
/* --text-muted rather than --text-soft: this type is around 11.5px, so it
   takes the 6.9:1 rather than the 5.1:1. */
.asset-card__fact-label,
.asset-facts__label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
}
/* A dd carries 40px of UA margin-inline-start. */
.asset-card__fact-value,
.asset-facts__value {
  margin: 0;
  color: var(--text);
  font-size: 0.94rem;
  line-height: 1.55;
}

/* The hairline is what gives the card its internal rhythm: it separates the
   thing being described from the thing you can do about it. */
.asset-card__action {
  display: flex;
  align-items: center;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}
.asset-card__action form {
  margin: 0;
}

/* The facts strip on a single asset page. Shares the tinted-region recipe with
   .help-section--routes above; only the layout is its own. */
.asset-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 1rem;
  margin: 0 0 1.5rem;
}

/* ---- AI memory --------------------------------------------------- */

/*
  These selectors carry .panel because §27's `.panel > p` is more specific than
  a bare class and would otherwise win on the margin.
*/
.panel > .memory-total {
  margin: 0 0 0.35rem;
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.02em;
  /* Full-strength ink. This is a statistic, not a heading and not a quiet
     aside: it is the headline answer to "what do you know about me". */
  color: var(--text);
}
.panel > .memory-empty {
  margin: 0.75rem 0 0;
  font-size: 0.94rem;
  color: var(--text-soft);
}

/*
  A quiet badge naming the kind, shown only in the unfiltered list. It sits at
  the head of a line of meta text and has to wrap sensibly with the date that
  follows it, so it is inline-block rather than a flex child.

  It takes --text-muted rather than inheriting the softer meta colour, because
  the badge carries information the rest of the line does not repeat.
*/
.memory-item__kind {
  display: inline-block;
  margin-right: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--paper-warm);
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
}

/* ---- Tooltips ---------------------------------------------------- */

/*
  The script owns position, left, top and pointer-events and writes them on the
  element; nothing here may set them, or the two would disagree about geometry.
  Everything below is appearance.

  Colour comes from the three --tooltip-* tokens (§1, swapped in §18), so the
  bubble is a dark chip on paper and a raised card on ink, and neither theme
  needs a component override. Text clears 14.7:1 in both.

  No arrow, deliberately: the script clamps the bubble inside the viewport, so
  a centred arrow would point at nothing on a narrow screen.
*/
.tooltip {
  max-width: min(22rem, calc(100vw - 2rem));
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-md);
  border: 1px solid var(--tooltip-border);
  background: var(--tooltip-bg);
  color: var(--tooltip-fg);
  font-size: 0.84rem;
  font-weight: 500;
  line-height: 1.45;
  text-align: left;
  /* Over the sticky headers (100) and the autocomplete list (40); under the
     skip link and the toasts (200). */
  z-index: 150;
  box-shadow: var(--shadow-lg);
  animation: tooltip-in 0.14s var(--ease);
}
.tooltip--above {
  animation-name: tooltip-in-above;
}

@keyframes tooltip-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}
@keyframes tooltip-in-above {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}

/* ==================================================================
   32. Eight features, one stylesheet
   ------------------------------------------------------------------
   Seven authors built these in parallel and none of them could touch
   this file, so their requests are reconciled here rather than shipped
   as seven private looks. Three rules held throughout:

     1. Colour comes from tokens (§1, swapped in §18). There is not one
        hardcoded hex below, so dark mode needed no component override.
     2. Where two authors described the same object, it became one rule
        with both selectors on it. A near-duplicate is how two lists
        drift apart six months later.
     3. Anything that would have failed the contrast or target-size
        audit was built differently, and the note above it says why.
   ================================================================== */

/* --- the search box in the app header ----------------------------- */

/*
  A search field on a dark bar.

  It cannot borrow --white or --line: those are surface tokens that invert
  with the theme, and this header is ink in both. So the field is drawn from
  the on-dark family, which is fixed: an 8% white wash for the ground, the
  hairline the header already uses for its own edges, and --on-dark for typed
  text (11.7:1) with --on-dark-muted for the placeholder (9.0:1).

  min-width: 0 is load-bearing. A text input's automatic minimum size is its
  intrinsic ~20-character width, so without it the field refuses to shrink and
  the header pushes the sign-out control off a narrow screen instead.
*/
.app-header__search {
  position: relative;
  display: flex;
  align-items: center;
  /*
    Grows into the column rather than sitting at a fixed 24rem. Capped so that
    on a very wide screen it does not become a search field the width of a
    desk, which reads as a mistake rather than as generosity.
  */
  flex: 1 1 auto;
  min-width: 0;
  max-width: 38rem;
}
.app-header__search-icon {
  position: absolute;
  left: 0.75rem;
  display: inline-flex;
  color: var(--on-dark-muted);
  /* The icon sits over the field, so it must not swallow the click that is
     meant to put the caret in it. */
  pointer-events: none;
}
/*
  The svg in the header carries a viewBox and no dimensions, which makes it a
  replaced element with a 300x150 default. Sized here rather than left to the
  markup, because `img, svg { height: auto }` in §2 turns that default into a
  very large icon rather than a broken one, which is harder to notice.
*/
.app-header__search-icon svg {
  width: 1.05rem;
  height: 1.05rem;
}
.app-header__search input {
  width: 100%;
  min-width: 0;
  min-height: 2.4rem;
  padding: 0.35rem 0.85rem 0.35rem 2.4rem;
  border: 1px solid var(--line-dark);
  border-radius: var(--r-pill);
  background: rgba(242, 246, 252, 0.08);
  color: var(--on-dark);
  /*
    A full 1rem, not the 0.88rem the rest of the header runs at. Mobile Safari
    zooms the whole page when a focused field is under 16px, and this field is
    on every signed-in screen, so it would zoom on every screen.
  */
  font-size: 1rem;
  /* Safari draws a searchfield with its own metrics and ignores the padding
     above unless the appearance is reset. */
  appearance: none;
  -webkit-appearance: none;
}
.app-header__search input::placeholder {
  color: var(--on-dark-muted);
  /* Firefox dims placeholders to 54% on top of the colour, which would put
     this under AA. */
  opacity: 1;
}
.app-header__search input:hover {
  border-color: rgba(242, 246, 252, 0.32);
}
/*
  Focus is stated twice on purpose. :focus-visible gives the amber ring the
  rest of the header uses; the lighter ground is what makes the field itself
  look active while you type, including after a click, where :focus-visible
  does not fire.
*/
.app-header__search input:focus {
  background: rgba(242, 246, 252, 0.16);
  border-color: rgba(242, 246, 252, 0.4);
}
/*
  The menu button is a fixed 2.75rem square target and must not be shrunk to
  pay for anything else on the row. With a flexible field now sitting beside
  it, the default flex-shrink would let it fall to about 18px on the narrowest
  screens, which is under the WCAG 2.2 AA floor for a target.
*/
.nav-toggle {
  flex: none;
}
/* Same reasoning: sign out is the control you need when the header is
   crowded, so it keeps its width and the name gives way instead. */
.app-header__logout .btn {
  flex: none;
  white-space: nowrap;
}

/* --- the command palette ------------------------------------------ */

/*
  The trigger lives on the header bar, which is ink in both themes, so it is
  the same control in both. It never becomes a light chip.
*/
.palette-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  flex: 0 0 auto;
  min-height: 2.4rem;
  min-width: 2.4rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--line-dark);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--on-dark-muted);
  font-size: 0.88rem;
  cursor: pointer;
}
.palette-trigger:hover {
  color: var(--on-dark);
  background: rgba(242, 246, 252, 0.09);
}
.palette-trigger__icon {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
}
.palette-trigger__key {
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--line-dark);
  border-radius: var(--r-sm);
  font-family: var(--font-body);
  font-size: 0.72rem;
  /* The same ink as the label beside it. --on-dark-faint would still clear
     AA at 5.5:1, but a keyboard hint is not a lesser kind of text and there
     is nothing to gain from making 11.5px type any fainter than it is. */
  color: var(--on-dark-muted);
  line-height: 1.5;
}

/*
  The dialog.

  No `display` is declared on .palette itself, deliberately. The browser hides
  a closed <dialog> and honours the `hidden` attribute the script sets, and an
  author `display` would beat both and leave an invisible modal permanently
  over the page. Everything visual is on .palette__panel instead.

  Stacking needs no argument: showModal() puts the element in the top layer,
  which paints above the toasts (z-index 200) and tooltips (150) whatever
  their z-index. The value below is only there so a future reader does not
  have to know that.
*/
.palette {
  width: 100%;
  max-width: min(40rem, 92vw);
  max-height: calc(100vh - 16vh);
  /* Top-aligned, not centred: the list grows downwards as you type, and a
     centred dialog would move the row under your eye while you read it. */
  margin: 12vh auto auto;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: visible;
  z-index: 300;
}
/*
  ::backdrop inherits custom properties from the dialog in current engines, but
  it did not until recently, and an unresolvable var() would leave the modal
  with no scrim at all. The fallback is the light value, which still darkens a
  dark page; it is a worse scrim, not a missing one.
*/
.palette::backdrop {
  background: var(--scrim, rgba(11, 18, 32, 0.55));
}
.palette__panel {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--white);
  box-shadow: var(--shadow-lg);
}
.palette__search {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--line);
}
.palette__search-icon {
  width: 1.1rem;
  height: 1.1rem;
  flex: none;
  color: var(--text-soft);
}
.palette__input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 1.05rem;
}
/*
  The ring stays on the input rather than moving to the panel. A ring drawn
  around the whole card says "this card has focus somewhere in it", which is
  exactly the ambiguity a keyboard user cannot afford once arrow keys start
  moving a highlight that is not focus. The offset is pulled in because the
  input is already inside a padded row.
*/
.palette__input:focus-visible {
  outline-offset: 1px;
  border-radius: var(--r-sm);
}
.palette__close {
  flex: none;
  display: inline-grid;
  place-items: center;
  min-width: 2.25rem;
  min-height: 2.25rem;
  padding: 0;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-soft);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
.palette__close:hover {
  background: var(--paper-warm);
  color: var(--text);
}
.palette__list {
  max-height: min(50vh, 24rem);
  overflow-y: auto;
  padding: 0.35rem;
}
.palette__list[aria-busy="true"] {
  opacity: 0.65;
  transition: opacity 0.15s var(--ease);
}
.palette__group {
  padding-block: 0.25rem;
}
.palette__group-title {
  margin: 0;
  padding: 0.4rem 0.7rem 0.2rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.palette__option {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.75rem;
  padding: 0.5rem 0.7rem;
  border-radius: var(--r-md);
  cursor: pointer;
}
.palette__option:hover {
  background: var(--paper-warm);
}
/*
  The highlight is not focus - focus never leaves the input - so it has to be
  legible as a place marker on its own. Tint plus a solid left edge, because a
  tint alone disappears under forced colours and for anyone who cannot pick
  --navy-100 out of --white. --focus is the one accent guaranteed to clear 3:1
  on both grounds, which is why the bar borrows it.

  The row's own text colours are left alone: --text and --text-muted measure
  13.9:1 and 6.3:1 on this tint in light, 11.6:1 and 7.3:1 in dark, so nothing
  needs restating per theme.
*/
.palette__option[aria-selected="true"] {
  background: var(--navy-100);
  box-shadow: inset 3px 0 0 var(--focus);
}
.palette__option-body {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  flex: 1 1 auto;
}
.palette__option-title,
.palette__option-detail {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.palette__option-title {
  color: var(--text);
  font-size: 0.95rem;
}
.palette__option-detail {
  color: var(--text-muted);
  font-size: 0.8rem;
}
.palette__kind {
  flex: none;
  padding: 0.1rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--navy-100);
  color: var(--navy-700);
  font-size: 0.7rem;
  font-weight: 700;
}
/*
  Never display: none. It is the live region that reports how many rows there
  are, and a region that is not rendered announces nothing.
*/
.palette__status {
  margin: 0;
  padding: 0.45rem 0.9rem;
  border-top: 1px solid var(--line);
  font-size: 0.78rem;
  color: var(--text-soft);
}
.palette__hint {
  margin: 0;
  padding: 0 0.9rem 0.6rem;
  font-size: 0.74rem;
  color: var(--text-soft);
}
/* The page behind a modal must not scroll under it. */
html.is-palette-open {
  overflow: hidden;
}

/* --- bulk selection on the task list ------------------------------ */

/*
  The bar is always in the markup, because the form has to work with scripting
  blocked: you tick rows, then you press a button, and both have to be there.
  What changes with a selection is emphasis, and it changes without any
  script at all - :has() on the shared ancestor can see the ticked boxes even
  though they sit in the list *after* the bar.

  Quiet state: the page's own ground, a hairline, no shadow. It reads as part
  of the page furniture.
  Selected state: it lifts to a card and follows you down a long list.
*/
.bulk-bar {
  display: grid;
  gap: 0.7rem;
  padding: 0.85rem 0.9rem;
  margin-bottom: 0.85rem;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--paper);
  transition:
    background-color 0.18s var(--ease),
    border-color 0.18s var(--ease),
    box-shadow 0.18s var(--ease);
}
.app-main:has(.task__select-box:checked) .bulk-bar {
  background: var(--white);
  border-color: var(--navy-300);
  box-shadow: var(--shadow-sm);
}
.bulk-bar__lead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem 1rem;
}
.bulk-bar__hint {
  margin: 0;
  font-size: 0.85rem;
  /* --text-muted rather than the --text-soft the footnote below uses. Both
     clear AA, but this line is the instruction for the whole control and the
     soft grey sits at 5.1:1 with nothing to spare. */
  color: var(--text-muted);
}
.bulk-bar__selection {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}
.bulk-bar__all {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 2.25rem;
  font-size: 0.85rem;
  cursor: pointer;
}
/*
  24px square, which is WCAG 2.2 AA's floor for a target and roughly twice a
  native checkbox. Not dimmed at rest, though the list would certainly look
  calmer if it were: a native unchecked box at 60% opacity composites to about
  2.3:1 against the row, and 1.4.11 wants 3:1 for the boundary of a control.
  The list is kept from reading as a spreadsheet by giving the column no
  chrome of its own and letting the *row* carry the selection instead.
*/
.bulk-bar__all-box,
.task__select-box {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  margin: 0;
  accent-color: var(--navy-500);
  cursor: pointer;
}
.bulk-bar__count {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted);
  /* Reserved and tabular, so the row does not twitch as the number changes. */
  min-width: 6.5rem;
  font-variant-numeric: tabular-nums;
}
.bulk-bar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
/*
  Delete goes to the far end of the row.

  It is the one action here you cannot undo with the button beside it, so it
  does not sit shoulder to shoulder with "Set to Med". When the row is wide
  enough it lands at the right edge with a gap in front of it; when it wraps it
  lands at the right edge of the second line rather than orphaned at the left.
*/
.bulk-bar__actions > .btn--danger {
  margin-left: auto;
}
.bulk-bar__group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.5rem;
  border-radius: var(--r-md);
  background: var(--paper-warm);
}
.bulk-bar__group-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.bulk-bar__field-label {
  font-size: 0.88rem;
  font-weight: 600;
}
/*
  Deliberately not `.field input`: that rule is 0.95rem and only reaches 1rem
  under 640px. This control is on the same page as the phone form audit, so it
  simply inherits the body's 1rem and never triggers zoom-on-focus.
*/
.bulk-bar__date {
  min-width: 9.5rem;
  min-height: 2.35rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--white);
}
.bulk-bar__note {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-soft);
}

/* The checkbox column, and the spacer standing in for it on the rows this
   account does not own. Both must occupy the same width, or half the list
   starts a sixth of an inch to the left of the other half. */
.task__select,
.task__select--none {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.75rem;
  min-height: 1.5rem;
}
/*
  Specificity note: :has() takes the weight of its argument, so this is (0,3,0)
  and beats .task:hover. Without that, hovering a selected row would wash the
  selection out.

  --text-soft on this tint measures 4.65:1, which passes and should not: the
  goal line is 12.5px. It steps up to --text-muted here (6.3:1) rather than
  living one rounding error above the line.
*/
.task:has(.task__select-box:checked) {
  background: var(--navy-100);
  border-color: var(--navy-200);
}
.task:has(.task__select-box:checked) .task__goal {
  color: var(--text-muted);
}

/* --- estimates and the day line ----------------------------------- */

/*
  One sentence stating what today holds. Body size, body colour, no bar, and
  never red or green: the whole point of the line is that it reports a number
  without having an opinion about it. Anything that tinted with the numbers
  would turn a bad morning into a scolding.
*/
.day-line {
  margin: 0 0 0.6rem;
  font-size: 0.98rem;
  color: var(--text);
}
.day-capacity {
  margin-top: 1rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line);
}
.day-capacity__summary {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 1.75rem;
  padding: 0.25rem 0;
  font-size: 0.85rem;
  color: var(--text-muted);
  cursor: pointer;
  /* The default triangle goes, replaced below by one that rotates. Two
     properties for it because older WebKit puts the marker in a pseudo
     element of its own rather than honouring list-style. */
  list-style: none;
}
.day-capacity__summary::-webkit-details-marker {
  display: none;
}
.day-capacity__summary::before {
  content: "\203A";
  display: inline-block;
  font-size: 1.1em;
  line-height: 1;
  transition: transform 0.18s var(--ease);
}
.day-capacity[open] .day-capacity__summary::before {
  transform: rotate(90deg);
}
.day-capacity__form {
  padding-top: 0.75rem;
}
.day-capacity__form .field {
  margin-bottom: 0.75rem;
}

/* --- last week's commitments -------------------------------------- */

/*
  Nothing in this block may look like a verdict.

  "Not done" is the same weight, the same size and the same colour as "Done".
  The only visual difference between the four outcomes is a 2px rule down the
  left of the ones that happened, and even that is a neutral hairline rather
  than a green one. Someone who had a bad week has to want to open this page
  again next Monday, and a wall of red is how you make sure they do not.
*/
.commitment-report__summary {
  margin: 0 0 0.9rem;
  font-size: 1.05rem;
  font-weight: 600;
  /* Full --text in every outcome. This is the most important line on the
     page and it is not a status message. */
  color: var(--text);
}
.commitment-report__list,
.commitment-slots,
.shared-list,
.capture-held__list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.commitment-report__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.65rem 0 0.65rem 0.65rem;
  /* Transparent on every row so the 2px below cannot shift the text of the
     rows that do not have it. */
  border-left: 2px solid transparent;
}
.commitment-report__item + .commitment-report__item {
  border-top: 1px solid var(--line);
}
.commitment-report__item--done {
  border-left-color: var(--line-strong);
}
.commitment-report__text {
  flex: 1 1 12rem;
  min-width: 0;
  color: var(--text);
}
.commitment-report__answer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
}
.commitment-report__ask {
  font-size: 0.85rem;
  color: var(--text-soft);
}
.commitment-report__quote {
  margin: 0;
  padding-left: 0.9rem;
  border-left: 3px solid var(--line-strong);
}
.commitment-report__quote blockquote {
  margin: 0;
  color: var(--text);
}
.commitment-report__quote figcaption {
  margin-top: 0.35rem;
  font-size: 0.85rem;
}
.commitment-slots {
  display: grid;
  gap: 1.1rem;
}
.commitment-slot {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 0.75rem;
  align-items: end;
}
/* The last .field in a slot would otherwise add a margin the grid gap has
   already paid for. */
.commitment-slot .field {
  margin-bottom: 0;
}

/* --- the guided first run ----------------------------------------- */

.onboarding {
  max-width: 52rem;
  margin-inline: auto;
}
.onboarding__steps {
  margin-bottom: 1.5rem;
}
.onboarding__steps ol {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.onboarding__step {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  font-size: 0.85rem;
}
/*
  Three states, and none of them is only a colour: the circle is outlined,
  solid, or solid-and-tinted, and the current step is the only one at weight
  600. The step that is done also says ", done" in visually hidden text, which
  is the part that is actually announced.
*/
.onboarding__step-number {
  display: inline-grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  flex: none;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
.onboarding__step--todo .onboarding__step-number {
  color: var(--text-soft);
}
/*
  --navy-500 is one of the fixed accents: it does not swap, so --on-accent
  stays at 8.6:1 on it in both themes and this needs no dark counterpart.
*/
.onboarding__step--current .onboarding__step-number {
  border-color: var(--navy-500);
  background: var(--navy-500);
  color: var(--on-accent);
}
.onboarding__step--done .onboarding__step-number {
  border-color: var(--navy-200);
  background: var(--navy-100);
  color: var(--navy-900);
}
.onboarding__step-label {
  color: var(--text-soft);
}
.onboarding__step--current .onboarding__step-label {
  color: var(--text);
  font-weight: 600;
}
.onboarding__panel {
  padding: clamp(1.4rem, 3vw, 2rem);
}
.onboarding__summary {
  margin: 0 0 0.35rem;
  font-size: 0.95rem;
  color: var(--text-muted);
}
.onboarding__contents {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.onboarding__subhead {
  margin: 1.5rem 0 0.75rem;
  font-size: 0.95rem;
  font-weight: 700;
}
/*
  The skip control stays a full-size button. Shrinking the way out to link
  text is how a flow stops being skippable in practice, and it would drop the
  target under the 24px floor on a phone.
*/
.onboarding__skip {
  font-weight: 600;
}
.onboarding__alt {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.onboarding__dismiss {
  margin-top: 2rem;
  text-align: center;
}
.onboarding__dismiss-note {
  margin: 0.6rem auto 0;
  max-width: 34rem;
  font-size: 0.82rem;
  color: var(--text-muted);
}

.shape-choice {
  border: 0;
  margin: 0;
  padding: 0;
}
.shape-choice__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.shape-choice__list > li {
  display: flex;
}
.shape-choice__option {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  width: 100%;
  padding: 1rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--white);
  cursor: pointer;
}
.shape-choice__option:hover {
  border-color: var(--navy-300);
}
/*
  Selection is a tint, a stronger border and a solid left edge. The edge is
  the part that survives forced colours and a monochrome print, so it is not
  decoration.
*/
.shape-choice__option:has(input:checked) {
  border-color: var(--navy-500);
  background: var(--navy-100);
  box-shadow: inset 4px 0 0 var(--focus);
}
/*
  The radio stays a real, visible, focusable radio. The ring is added to the
  card as well so the whole target reads as focused; the input's own outline
  is never removed, which is what the :focus-visible rule in §2 supplies.
*/
.shape-choice__option:has(input:focus-visible) {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
.shape-choice__input {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  margin: 0.1rem 0 0;
  accent-color: var(--navy-500);
}
.shape-choice__body {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex: 1;
  min-width: 0;
}
.shape-choice__title {
  font-size: 0.98rem;
  font-weight: 700;
  color: var(--text);
}
/*
  --text-muted rather than the --text-soft that was asked for. On a checked
  card's tint the soft grey measures 4.65:1, which passes 4.5:1 by fifteen
  hundredths, and this is the line the author flagged as the one most likely
  to fall over in dark mode. The muted grey is 6.3:1 on the tint and 6.9:1 off
  it, so the card is safe checked or not, in either theme, with room to spare.
*/
.shape-choice__summary {
  font-size: 0.87rem;
  line-height: 1.45;
  color: var(--text-muted);
}
.shape-choice__meta {
  font-size: 0.78rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.first-steps__row {
  margin: 0 0 0.75rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.first-steps__legend {
  padding: 0 0.35rem;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text-muted);
}

/* --- sharing a plan, and the page the reader sees ------------------ */

/*
  The link is shown once and never again, so the callout has to look like an
  event rather than a field. --amber-soft is the one tint that already swaps
  (#fbeedc on paper, #33270f on ink), so --text sits on it at 13:1 or better
  either way and the block needs no per-theme value.
*/
.share-once {
  margin-bottom: 1rem;
  padding: 1rem;
  border: 1px solid var(--amber-dim);
  border-radius: var(--r-md);
  background: var(--amber-soft);
}
.share-once__title {
  margin: 0 0 0.6rem;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
}
/*
  Only the family is set. Size is left to `.field input`, which already steps
  up to 1rem under 640px; overriding it here would put a 13.6px field on a
  phone and zoom the page on focus. A link is long and an input cannot wrap,
  so it scrolls inside itself, which is the honest behaviour.
*/
.share-once__value {
  font-family: var(--font-mono);
}
.share-link__meta {
  font-size: 0.8rem;
  color: var(--text-soft);
}
/*
  A revoked or expired link keeps its ink. Dimming it was the request, but the
  label is already at the small end and dropping it to a wash is how a list
  becomes unreadable for the person most likely to be checking it. The state
  is carried by the pill next to it and by a rule through the label, neither
  of which is a contrast change.
*/
.share-link--dead .share-link__label {
  text-decoration: line-through;
  text-decoration-color: var(--line-strong);
}

/*
  The reader's page has no shell and no sidebar around it: it is rendered
  through the plain layout, so it supplies its own measure.
*/
.shared-goal {
  max-width: 780px;
  margin-inline: auto;
  padding: clamp(2rem, 6vw, 3.5rem) var(--gutter);
}
.shared-goal__kicker {
  margin: 0 0 0.35rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.shared-goal__title {
  margin: 0;
  font-size: clamp(1.6rem, 4vw, 2.3rem);
  color: var(--text);
}
.shared-goal__lede {
  margin: 0.5rem 0 0;
  max-width: 60ch;
  font-size: 1.05rem;
  color: var(--text-muted);
}
.shared-goal__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin: 0.9rem 0 1.5rem;
}
.shared-list {
  margin-top: 0.75rem;
}
.shared-list__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
}
.shared-list__item:last-child {
  border-bottom: 0;
}
.shared-list__title {
  min-width: 0;
  font-size: 0.98rem;
  color: var(--text);
}
/* Muted, and no strike. The row already carries a status pill, so the state
   is stated rather than drawn twice. */
.shared-list__item.is-done .shared-list__title {
  color: var(--text-muted);
}
.shared-list__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  flex-shrink: 0;
}
.shared-goal__note {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
  color: var(--text-soft);
}

/* --- passkeys ------------------------------------------------------ */

/*
  The rows themselves are in §25 with the sessions and connections they are
  the same shape as. Only the controls are new.
*/
.passkey-rename {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
}
.passkey-rename .field {
  margin-bottom: 0;
}
/* A long device name must not push Save and Remove off the row. */
.passkey-rename .field input {
  min-width: 8rem;
  max-width: 14rem;
}
.passkey-add {
  display: grid;
  gap: 0.75rem;
  justify-items: start;
}
/* The grid gap already spaces the rows; the field's own margin would double
   it. */
.passkey-add .field {
  margin-bottom: 0;
}
.passkey-add__status {
  margin: 0;
  font-size: 0.9rem;
  color: var(--text-soft);
}
/* Empty until the device says something. Collapsed so it does not leave a
   hole under the button, but never display:none in the general case: it is a
   live region and has to stay in the tree to be announced. */
.passkey-add__status:empty {
  display: none;
}
/*
  The explanation of why the button cannot work is removed the moment it can.
  Nothing else about this notice is ours; it is a .notice--warn and inherits
  both themes from §12 and §18.
*/
.js .passkey-add__no-js {
  display: none;
}

/* --- capture, and the queue behind it ------------------------------ */

.capture-form__hint {
  margin: 0;
  font-size: 0.82rem;
  color: var(--text-muted);
}

/*
  The held panel is deliberately one step quieter than the form above it: a
  bordered area on the page ground rather than a second card competing with
  the thing you are typing into.
*/
.capture-held {
  padding: clamp(1rem, 2vw, 1.35rem);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--paper);
}
/*
  Nothing waiting: the heading goes and the border with it, leaving one line
  of text. The section and its status line are never hidden - the status line
  is a live region, and an unrendered live region says nothing at the moment
  something arrives in the queue.
*/
.capture-held--empty {
  padding: 0;
  border: 0;
  background: none;
}
.capture-held--empty .capture-held__title {
  display: none;
}
.capture-held__title {
  margin: 0 0 0.75rem;
  font-size: 1.05rem;
}
.capture-held__status {
  margin: 0;
  font-size: 0.9rem;
  color: var(--text-muted);
}
/* Something of this account's is actually waiting. Full ink and a little
   weight, so "2 captures are waiting" is not styled like a footnote. */
.capture-held__status--waiting {
  color: var(--text);
  font-weight: 600;
}
.capture-held__list {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.85rem;
}
.capture-held__item {
  display: grid;
  gap: 0.35rem;
  padding: 0.75rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--white);
}
.capture-held__text {
  margin: 0;
  font-weight: 600;
  color: var(--text);
  /* Somebody will paste a 300-character address into this box. */
  overflow-wrap: anywhere;
}
.capture-held__note {
  margin: 0;
  font-size: 0.88rem;
  color: var(--text-muted);
  /* The line breaks somebody typed are part of what they wrote. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/*
  Where the capture stands, in one line. The dot is a shape as well as a
  colour, and the sentence next to it says the same thing in words, so the
  colour is never the only carrier.
*/
.capture-held__state {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.capture-held__state::before {
  content: "\25CF";
  font-size: 0.7em;
  line-height: inherit;
}
.capture-held__state--sending {
  color: var(--good-fg);
}
/* Held is normal, not an error. Amber, and the calm end of it. */
.capture-held__state--held {
  color: var(--warn-fg);
}
.capture-held__state--rejected,
.capture-held__state--wrong-account {
  color: var(--bad-fg);
}

/*
  A third toast tone. Without it "held on this device" would arrive looking
  exactly like "saved", which is the one lie this whole feature exists to
  avoid. Built the same way as --good and --bad: an edge and an ink, both
  from tokens that swap.
*/
.toast--held {
  border-left: 3px solid var(--warn-fg);
  color: var(--warn-fg);
}

/* --- rows of controls, unified ------------------------------------- */

/*
  Five authors independently described the same object: a wrapping row of
  controls at the foot of a card or a form. One rule, five selectors.
  (.passkey__actions is the sixth of these but sits at the end of a divided
  row rather than the foot of a card, so it joins the row-actions group in
  §25 instead.)

  .template-card form already sets margin-top: auto in §25 so buttons line up
  across cards of different heights; inside an actions wrapper that margin
  would push the forms apart instead, so it is cancelled there and moved to
  the wrapper.
*/
.bulk-bar__actions,
.capture-form__actions,
.capture-held__actions,
.onboarding__actions,
.template-card__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
.capture-form__actions,
.onboarding__actions {
  margin-top: 1.25rem;
  gap: 0.75rem;
}
.template-card__actions {
  margin-top: auto;
}
.template-card__actions form {
  margin: 0;
}

/* --- narrow screens ------------------------------------------------ */

/*
  The header now carries a wordmark, a search field, a command trigger, a
  reference link, a name and a sign-out control. At 375px that is more than
  one row can hold honestly, so the search takes a row of its own and the
  trigger goes with it.

  The break is forced by a zero-height flex item rather than left to chance.
  Ordering alone would not do it: whether the search happened to fit beside
  the sign-out button would depend on how long somebody's name is, and the
  header would rearrange itself between two accounts.
*/
/* No keyboard, no point promising a keystroke. */
@media (max-width: 1024px) {
  .palette-trigger__key {
    display: none;
  }
}

@media (max-width: 820px) {
  /*
    One column below this width, because there is no sidebar to line up with:
    the two-track grid only earns its keep while the shell has two tracks.
    The wordmark and the menu button take the first row, everything else wraps
    beneath.
  */
  .app-header__inner {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0.55rem;
    padding-block: 0.55rem;
  }
  .app-header__lead {
    justify-content: space-between;
  }
  .app-header__bar {
    flex-wrap: wrap;
    row-gap: 0.55rem;
  }
  /* Search takes a line of its own, after the account controls rather than
     above them: signing out must never be the thing that scrolls away. */
  .app-header__search {
    order: 9;
    flex: 1 1 100%;
    max-width: none;
  }
  .palette-trigger {
    order: 10;
  }
  .app-header__bar .app-header__links {
    margin-left: 0;
  }
  .app-header__user {
    margin-left: auto;
  }
  /* Icon only. The button keeps its name from aria-label, and 2.4rem square
     stays well over the 24px floor. */
  .palette-trigger__label {
    display: none;
  }
  /*
    A name is somebody's own and should not be truncated lightly, but the
    alternative here is the sign-out control leaving the screen.

    min-width: 0 is the part that does the work. A flex item refuses by
    default to shrink below its longest word, so a person called Fitzwilliam
    pushed the header wider than the phone whatever the max-width said. The
    display switches from inline-flex to inline-block because text-overflow
    has no effect on a flex container, and the line-height puts back the
    vertical centring that align-items was providing: 1.8rem of text plus the
    0.3rem padding either side is exactly the 2.4rem the row is.
  */
  .app-header__user {
    min-width: 0;
  }
  .app-header__name {
    display: inline-block;
    min-width: 0;
    max-width: 8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: 1.8rem;
    vertical-align: middle;
  }
}

@media (max-width: 640px) {
  .commitment-slot {
    grid-template-columns: minmax(0, 1fr);
  }
  .palette {
    max-width: 96vw;
    margin-top: 6vh;
  }
  .palette__hint {
    display: none;
  }
  /* The answer buttons drop under the commitment rather than being squeezed
     beside it. */
  .commitment-report__answer {
    margin-left: 0;
  }
  .passkey-rename {
    flex-wrap: wrap;
  }
  .passkey-rename .field input {
    max-width: none;
  }
}

@media (max-width: 560px) {
  .shape-choice__list {
    grid-template-columns: minmax(0, 1fr);
  }
  /* The last of the headroom on the narrowest phones, taken out of the space
     between header items rather than out of any control on the row. */
  .app-header__inner {
    column-gap: 0.75rem;
  }
}

/*
  A toolbar that follows a long list is worth having on a big screen and is in
  the way on a small one, so it only sticks where the sidebar does, and only
  once there is something selected to act on.
*/
@media (min-width: 900px) {
  .app-main:has(.task__select-box:checked) .bulk-bar {
    position: sticky;
    top: 4.75rem;
    z-index: 5;
  }
}

/*
  Everything above that moves, switched off by name. §15 already does this
  globally with !important, so this block changes no behaviour; it is here so
  that a component added to this section later is written by somebody who has
  just read a list of the ones that had to be accounted for.
*/
@media (prefers-reduced-motion: reduce) {
  .bulk-bar,
  .day-capacity__summary::before,
  .palette,
  .palette::backdrop,
  .palette__list,
  .palette__option {
    transition: none;
    animation: none;
  }
}

/*
  Forced colours flatten every tint in this section to the same system canvas.
  Anything that was distinguishing itself with a background alone therefore
  needs an edge, and the palette's highlight needs the system's own.
*/
@media (forced-colors: active) {
  .bulk-bar,
  .capture-held__item,
  .shape-choice__option,
  .palette__panel,
  .share-once {
    border: 1px solid CanvasText;
  }
  .palette__option[aria-selected="true"] {
    outline: 2px solid Highlight;
    outline-offset: -2px;
  }
}

/* ---------------------------------------------------------------- */
/* 33. How it works: steps that follow the scroll                    */
/* ---------------------------------------------------------------- */

/*
  Two halves. The rail sticks and reads out where you are; the steps do the
  talking. Everything that depends on the script hangs off `.is-enhanced`, so
  with scripting blocked this is a numbered list beside a set of in-page links
  and nothing is hidden waiting for a class that will never arrive.
*/

.steps-section {
  background: var(--paper);
}

.steps {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

/* ---- the rail ---- */

.steps__rail {
  position: relative;
  /*
    Where the rail's line runs, measured from the left edge of the rail. The
    markers and the track both derive from it - written as two independent
    offsets they drifted apart immediately, and a line beside its own dots
    reads as a rendering fault rather than a rail.
  */
 --rail-x: 1.75rem;
}
.steps.is-enhanced .steps__rail {
  position: sticky;
  /* Clears the fixed marketing header with a little air under it. */
  top: 7rem;
}
.steps__rail-list {
  list-style: none;
  margin: 0;
  padding: 0 0 0 1.6rem;
  display: grid;
  gap: 0.35rem;
  position: relative;
  /* Above the track, so a marker covers the line rather than the reverse. */
  z-index: 1;
}
.steps__rail-list li {
  position: relative;
}
.steps__pip {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--r-sm);
  text-decoration: none;
  color: var(--text-soft);
  transition: color 0.2s ease, background-color 0.2s ease;
}
.steps__pip:hover {
  color: var(--text);
  background: var(--paper-warm);
}
.steps__pip-num {
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  color: var(--text-soft);
  transition: color 0.2s ease;
}
.steps__pip-label {
  font-size: 0.93rem;
  font-weight: 600;
}
/*
  The marker on the rail. Drawn on the link rather than as another element so
  there is one thing to keep aligned with the text, not two.
*/
.steps__pip::before {
  content: "";
  position: absolute;
  /* The li starts at the list's padding edge, so this is measured back from
     there to land the marker's centre on --rail-x. */
  left: calc(var(--rail-x) - 1.6rem - 0.37rem);
  width: 0.74rem;
  height: 0.74rem;
  margin-top: 0.5rem;
  border-radius: 50%;
  border: 2px solid var(--line-strong);
  background: var(--paper);
  transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.2s ease;
}
.steps__pip.is-current {
  color: var(--text);
  font-weight: 700;
}
.steps__pip.is-current .steps__pip-num {
  color: var(--amber-fg);
}
.steps__pip.is-current::before {
  border-color: var(--amber);
  background: var(--amber);
  transform: scale(1.2);
}
.steps__pip.is-done::before {
  border-color: var(--amber);
  background: var(--amber-soft);
}

/*
  The track the markers sit on, plus the fill that grows down it. Only shown
  once the script is driving the fill - an empty groove with nothing in it
  reads as a loading state that never finishes.
*/
.steps__progress {
  display: none;
}
.steps.is-enhanced .steps__progress {
  display: block;
  position: absolute;
  left: calc(var(--rail-x) - 1px);
  top: 0.95rem;
  bottom: 0.95rem;
  width: 2px;
  border-radius: 2px;
  background: var(--line);
  z-index: 0;
}
.steps__progress > span {
  display: block;
  width: 100%;
  height: 0%;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--amber), var(--amber-dim));
  transition: height 0.25s ease-out;
}

/* ---- the steps ---- */

.steps__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(2.5rem, 6vw, 5rem);
}

.step {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1rem, 2.5vw, 1.8rem);
  align-items: start;
  /* Anchored jumps from the rail must not land under the fixed header. */
  scroll-margin-top: 7rem;
}
/*
  Steps arrive as they are scrolled to. Only under `.is-enhanced`, because the
  script is the only thing that adds it - written the other way round, a script
  that fails to run would leave the whole section faded out permanently.
*/
.steps.is-enhanced .step {
  opacity: 0.32;
  transform: translateY(0.9rem);
  transition: opacity 0.45s ease, transform 0.45s ease;
}
.steps.is-enhanced .step.is-seen {
  opacity: 1;
  transform: none;
}

.step__index {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 5vw, 3.6rem);
  font-weight: 800;
  line-height: 0.85;
  font-variant-numeric: tabular-nums;
  margin: 0;
  color: transparent;
 -webkit-text-stroke: 1.5px var(--outline-ink);
  transition: -webkit-text-stroke-color 0.35s ease;
}
.steps.is-enhanced .step.is-current .step__index {
 -webkit-text-stroke-color: var(--amber-fg);
}
/*
  Outlined numerals depend on -webkit-text-stroke, which is not universal.
  Where it is missing the transparent fill above would render the number
  invisible, so it is painted back in for anyone whose engine lacks the stroke.
*/
@supports not (-webkit-text-stroke: 1px red) {
  .step__index { color: var(--outline-ink); }
}
@media (forced-colors: active) {
  .step__index {
    color: CanvasText;
   -webkit-text-stroke: 0;
  }
}

.step__title {
  margin: 0.2rem 0 0.6rem;
  font-size: clamp(1.15rem, 2vw, 1.45rem);
  text-wrap: balance;
}
.step__text {
  margin: 0;
  max-width: 54ch;
  color: var(--text-muted);
}

/* The aside: the pace motif, filling one more mark per step. */
.step__aside {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1rem 0 0;
  padding: 0.5rem 0.85rem;
  width: fit-content;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--white);
  box-shadow: var(--shadow-sm);
}
.step__ticks {
  display: inline-flex;
  align-items: flex-end;
  gap: 0.28rem;
}
.step__tick {
  width: 2px;
  height: 0.7rem;
  border-radius: 1px;
  background: var(--line-strong);
}
.step__tick.is-lit {
  background: var(--amber);
}
.step__aside-text {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 900px) {
  .steps {
    grid-template-columns: minmax(0, 1fr);
  }
  /*
    Sticking a horizontal rail to the top of a narrow screen eats the room the
    steps need, so on small screens the rail is simply the list of links it
    already is.
  */
  .steps.is-enhanced .steps__rail {
    position: static;
  }
  .steps__rail-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    padding-left: 0;
  }
  .steps__pip::before {
    display: none;
  }
  .steps__pip {
    border: 1px solid var(--line);
    background: var(--white);
  }
  .steps__pip.is-current {
    border-color: var(--amber);
    background: var(--amber-soft);
  }
  .steps.is-enhanced .steps__progress {
    display: none;
  }
}

/*
  Reduced motion. Written at this specificity on purpose: the resting rule is
  .steps.is-enhanced .step, three classes, so a bare .step override loses the
  cascade and leaves every step but the current one sitting at a third opacity
  permanently - worse than the animation it was meant to remove.

  The rail keeps working either way. Where you are in a list is information,
  and reduced motion is a request for less movement, not less of the page.
*/
@media (prefers-reduced-motion: reduce) {
  .steps.is-enhanced .step {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .steps__progress > span,
  .steps__pip,
  .steps__pip::before {
    transition: none;
  }
}
:root[data-reduced-motion='on'] .steps.is-enhanced .step {
  opacity: 1;
  transform: none;
  transition: none;
}
:root[data-reduced-motion='on'] .steps__progress > span,
:root[data-reduced-motion='on'] .steps__pip,
:root[data-reduced-motion='on'] .steps__pip::before {
  transition: none;
}

/* ---------------------------------------------------------------- */
/* 34. Closing call to action                                        */
/* ---------------------------------------------------------------- */

.closer {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink-800);
  color: var(--on-dark);
  padding-block: clamp(3.5rem, 8vw, 6rem);
  text-align: center;
}
.closer__field {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.closer__inner {
  display: grid;
  justify-items: center;
}
.closer .eyebrow {
  color: var(--amber);
}
.closer__title {
  margin: 0 0 0.9rem;
  font-size: clamp(1.6rem, 4vw, 2.6rem);
  max-width: 30ch;
  text-wrap: balance;
  color: var(--on-dark);
}
.closer__lede {
  margin: 0 0 1.9rem;
  max-width: 58ch;
  color: var(--on-dark-muted);
}
.closer__actions {
  justify-content: center;
  margin-bottom: 2rem;
}
.closer .btn--ghost {
  border-color: var(--line-dark);
  color: var(--on-dark);
}
.closer .btn--ghost:hover {
  border-color: var(--amber);
  color: var(--amber);
}
.closer__points {
  list-style: none;
  margin: 0;
  padding: 1.6rem 0 0;
  border-top: 1px solid var(--line-dark-soft);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.7rem 2.2rem;
  font-size: 0.88rem;
  color: var(--on-dark-faint);
}
.closer__points li {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}
.closer__points li::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--amber);
}

/* ---------------------------------------------------------------- */
/* 35. The footpath                                                  */
/* ---------------------------------------------------------------- */

/*
  One trail of footprints from the top of the page to the bottom, with the
  part behind the reader painted in the accent and the part ahead left as a
  faint press. Scrolling moves the boundary, so the trail fills in as the page
  is walked and completes exactly when the bottom is reached.

  How the layer gets between a section's background and its content:

  A section paints its own background first, then its in-flow content. An
  absolutely positioned child normally paints above BOTH, which would put
  footprints on top of the text. The way through is `isolation: isolate` plus
  `z-index: -1` - the section becomes a stacking context, and a negative child
  is painted after the background and before the content. `.closer__field`
  already worked this way, so this is the house pattern rather than a new one.

  The prints themselves are a mask, not an image: only the alpha of
  /assets/trail.svg is read, and the colour comes from the gradient underneath.
  That is what lets one file serve the light sections and the dark ones, and
  it is why the two ends of the gradient can be theme tokens.
*/

[data-trail],
.hero,
.closer {
  /*
    Rendered tile size. The file is 94x320, so this keeps its aspect: change
    one and change the other, or the prints distort. Read by the script too, so
    the two cannot drift.
  */
 --trail-w: 76px;
 --trail-h: 259px;
}

[data-trail] {
  position: relative;
  isolation: isolate;
}

[data-trail]::before {
  content: "";
}

[data-trail]::before,
.hero__trail,
.closer__trail {
  /*
    Where the walked part ends, in this layer's own coordinates.

    --scroll-line is a document Y set once per frame on :root; --trail-top is
    this layer's document offset, set once per layout. Subtracting gives a
    local offset without the script having to touch every section every frame.

    Declared HERE and not on the section, which is a distinction that cost an
    hour. A custom property is substituted where it is declared, so a --trail-cut
    written on .hero would resolve var(--trail-top) against .hero - which has
    none - and then hand the already-substituted result down to .hero__trail,
    silently ignoring the value the script had just set on it. The section case
    happened to work because there the property and the layer share an element.

    The fallback is a line far BELOW the page, not above it: with no script, and
    under reduced motion where the script stops driving, every print is behind
    the reader and the whole path shows at once. A still, complete trail is the
    right thing to fall back to - falling back to an empty one would mean the
    decoration simply vanishes for anyone whose script did not run.
  */
 --trail-cut: calc(var(--scroll-line, 99999px) - var(--trail-top, 0px));

  position: absolute;
  top: 0;
  bottom: 0;
  width: var(--trail-w);
  pointer-events: none;

  /*
    The walk deepens as it goes: barely there where the reader started, full by
    the time they reach the bottom. --trail-depth is this layer's position down
    the document, and the fallback of 1 means the trail is at full weight
    wherever nothing is driving it.
  */
  opacity: calc(0.16 + 0.74 * var(--trail-depth, 1));

  /*
    Centred, which is the narrow-screen position. Above the breakpoint below,
    the trail moves out into the margin instead.

    One tile wide either way, never the full page: the prints only ever occupy
    this column, and a full-width layer meant a whole viewport being rastered
    through the mask on every scroll frame - about 17ms, the entire 60Hz budget
    for a decoration.
  */
  left: 50%;
  margin-left: calc(var(--trail-w) / -2);

 -webkit-mask-image: url("/assets/trail.svg");
  mask-image: url("/assets/trail.svg");
 -webkit-mask-repeat: repeat-y;
  mask-repeat: repeat-y;
 -webkit-mask-size: var(--trail-w) var(--trail-h);
  mask-size: var(--trail-w) var(--trail-h);
  /*
    --trail-phase carries this section's offset into the tile, so the gait
    continues across a section boundary instead of restarting at every one.
  */
 -webkit-mask-position: 50% var(--trail-phase, 0px);
  mask-position: 50% var(--trail-phase, 0px);

  /*
    The whole effect, in one gradient: prints behind the reader, nothing ahead.

    Nothing ahead is the point. An unwalked print drawn faintly meant the path
    was already there and merely changed colour as you passed it; leaving the
    ground empty until you reach it is what makes the prints read as being made
    rather than revealed.

    A gradient's first colour already fills everything above its first stop, so
    no stop at 0 is needed - and pinning one there was actively wrong. Colour
    stops may not decrease, so when --trail-cut goes negative, which it does for
    every layer the reader has not reached yet, a pinned 0 clamped the cut up to
    0 and painted the top of an entirely unwalked section as walked.

    The fade is short - about half a stride - so a print arrives over roughly
    its own height rather than ghosting in over several.
  */
  background: linear-gradient(
    180deg,
    var(--trail-walked) var(--trail-cut),
    var(--trail-fade) calc(var(--trail-cut) + 2rem)
  );
}

[data-trail]::before {
  z-index: -1;
}

/*
  The hero and the closer already own a decorative layer, so the path goes
  inside it rather than on a pseudo-element both have already spent. Above the
  corner glows, which are blurred washes the prints should read over.
*/
.hero__trail,
.closer__trail {
  z-index: 1;
}

/*
  Sections that are ink in BOTH themes. Their ground never swaps, so neither
  should the prints on it - the theme tokens would darken these to invisible
  on the light side.
*/
.hero,
.closer,
.section--dark[data-trail] {
 --trail-rgb: 240 166 74;
 --trail-walked: rgb(var(--trail-rgb) / 0.10);
 --trail-clear: rgb(var(--trail-rgb) / 0.66);
 --trail-fade: rgb(var(--trail-rgb) / 0);
}

/*
  Out of the reading column, on any screen with room beside it.

  This is what lets the prints be dark enough to actually see. Down the middle
  of the page they cross running text, and the strongest print that keeps every
  crossing at AA is far too faint to read as a footprint at all - which is what
  the first version of this was, and why it looked like nothing. Out here there
  is no text to be careful of.

  The breakpoint is where the margin stops fitting a tile: below it the trail
  goes back to the middle of the page, and back to the alpha that is safe over
  text. tests/e2e/footpath.spec.js checks both halves of that.
*/
@media (min-width: 1280px) {
  [data-trail]::before,
  .hero__trail,
  .closer__trail {
    /*
      Out past the content edge, and clamped: the max() keeps the column on
      screen at the narrow end of this breakpoint, where the margin is only just
      wide enough to hold it.

      The last term is the clearance between the trail column and the edge of
      the text, so it is the one to change to move the prints sideways - larger
      means further left. Note the max() already pins the column at 0.5rem from
      about 1380px of layout width downwards, so a change here is invisible on a
      1366px laptop: there the trail is as far left as the layout allows.
    */
    left: max(0.5rem, calc(50% - var(--shell) / 2 + var(--gutter) - var(--trail-w) - 4.5rem));
    margin-left: 0;

    /*
      Over the top of everything, rather than behind it.

      Safe only out here. Down the middle of the page this would put prints on
      top of running text; in the margin there is nothing to cover, and the
      "touches no text at all" test in tests/e2e/footpath.spec.js is what keeps
      that true. Below this breakpoint the layer stays at z-index -1.
    */
    z-index: 2;
  }

  /*
    .section--dark[data-trail] has to be named here too.

    It sets --trail-walked itself, at two selectors' worth of specificity, so a
    bare [data-trail] in this block loses to it no matter where the block sits.
    The dark sections were left at the over-text strength - 0.10 - while every
    light section around them switched to 0.66, which read as the dark sections
    swallowing the trail.
  */
  [data-trail],
  .hero,
  .closer,
  .section--dark[data-trail] {
   --trail-walked: var(--trail-clear);
  }
}

/*
  Under a mask, `background` is the only thing painting, so there is no
  forced-colors fallback worth having: the whole layer is decoration and
  disappearing entirely is the right outcome.
*/
@media (forced-colors: active) {
  [data-trail]::before,
  .hero__trail,
  .closer__trail {
    display: none;
  }
}

/* ---- The mark, walking the same path ---- */

/*
  The wordmark is the trail in miniature: three prints of the same foot the page
  draws, walking down, newest at the bottom.

  That newest one fades in with how far down the page the reader has come, so
  the mark takes a step as the page is walked. One property, set on :root, so
  the header mark and the footer mark stay in step with each other.

  The fallback is 1, not 0. Nothing drives this inside the app or with scripting
  blocked, and the mark has to look finished wherever it appears - three prints,
  not two and a gap.
*/
.brand__next {
  /* Never fully absent. A lone footprint reads as a broken mark, so the
     newer one only deepens - from a third to full - as the page is walked. */
  opacity: calc(0.35 + 0.65 * var(--brand-step, 1));
  transition: opacity 320ms ease-out;
}

/*
  Reduced motion.

  The footpath needs no rule here: its script stops driving, the cut keeps its
  fallback and the trail is a still path. The mark does need one - the script
  leaves the property alone, but a transition already declared would still
  animate if anything else changed it.
*/
@media (prefers-reduced-motion: reduce) {
  .brand__next {
    transition: none;
  }
}
:root[data-reduced-motion='on'] .brand__next {
  transition: none;
}

/* ---------------------------------------------------------------- */
/* 36. Reduced motion, asked for inside the app                      */
/* ---------------------------------------------------------------- */

/*
  Everything the device setting stops, the preference stops too.

  These are the targeted rules - the ones that settle a transform rather than
  shorten a duration. The global rule a few thousand lines up already caps
  every animation and transition duration for both sources, but it cannot
  undo a hover lift, so with the in-app switch on the cards still rose under
  the pointer while the device setting held them still.

  Written out rather than shared because a media query cannot set an
  attribute and an attribute cannot open a media query, so the two sources
  genuinely need two selector lists. test/stylesheet.test.js compares them.
*/
:root[data-reduced-motion='on'] .demo__bar > span,
:root[data-reduced-motion='on'] .demo__ring {
  transition: none;
}

:root[data-reduced-motion='on'] .score__arc,
:root[data-reduced-motion='on'] .score-bar__fill,
:root[data-reduced-motion='on'] .progress__fill,
:root[data-reduced-motion='on'] .brand__mark,
:root[data-reduced-motion='on'] .app-nav__link,
:root[data-reduced-motion='on'] .app-nav__link::before,
:root[data-reduced-motion='on'] .task,
:root[data-reduced-motion='on'] .checkbox-btn,
:root[data-reduced-motion='on'] .goal-card,
:root[data-reduced-motion='on'] .habit-card,
:root[data-reduced-motion='on'] .note-card,
:root[data-reduced-motion='on'] .stat,
:root[data-reduced-motion='on'] .btn {
  transition: none !important;
}

:root[data-reduced-motion='on'] .app-nav__link:hover,
:root[data-reduced-motion='on'] .task:hover,
:root[data-reduced-motion='on'] .goal-card:hover,
:root[data-reduced-motion='on'] .habit-card:hover,
:root[data-reduced-motion='on'] .note-card:hover,
:root[data-reduced-motion='on'] .stat:hover,
:root[data-reduced-motion='on'] .checkbox-btn:hover,
:root[data-reduced-motion='on'] .brand:hover .brand__mark {
  transform: none;
}

:root[data-reduced-motion='on'] .toast {
  transition: none;
}

:root[data-reduced-motion='on'] .help-link__chevron,
:root[data-reduced-motion='on'] .faq__marker::after {
  transition: none;
}

:root[data-reduced-motion='on'] .bulk-bar,
:root[data-reduced-motion='on'] .day-capacity__summary::before,
:root[data-reduced-motion='on'] .palette,
:root[data-reduced-motion='on'] .palette::backdrop,
:root[data-reduced-motion='on'] .palette__list,
:root[data-reduced-motion='on'] .palette__option {
  transition: none;
  animation: none;
}

/* ---------------------------------------------------------------- */
/* 37. Stepping between review periods                               */
/* ---------------------------------------------------------------- */

/*
  The same strip on all three reviews. The period being read sits between the
  two arrows rather than above them, so the row reads as one control and the
  answer to "which one am I looking at" is in the middle of it.
*/
.period-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1.25rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper-warm);
}
.period-nav__now {
  flex: 1;
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/*
  Next is spent once you are looking at the period you are living in. Left as a
  link it invites a click into a week nobody has lived, which renders an empty
  form that reads as a fault rather than as the end of the road.
*/
.period-nav .is-disabled {
  opacity: 0.45;
  cursor: default;
  pointer-events: none;
}

@media (max-width: 560px) {
  .period-nav__now {
    /* Its own line rather than squeezed between two arrows. */
    order: -1;
    flex-basis: 100%;
  }
}

/* ---------------------------------------------------------------- */
/* 38. Task rows that line up                                        */
/* ---------------------------------------------------------------- */

/*
  The right-hand side of a task row is a set of columns, not a huddle.

  It was a flex row of whatever that task happened to have - an assignee, a
  priority, a due date, a repeat marker - so nothing lined up with the row above
  or below it and the eye had to re-find each field on every line. A fixed
  column per field means a due date is always in the same place, and a task
  with no priority leaves a gap where the priority would be rather than pulling
  everything left.

  A lane is a wrapper in the markup rather than a width on the pill, because a
  column only lines up with the one above it if it is there on both rows. A
  pill that renders to nothing takes its column with it, and everything after
  it slides left by exactly the width of the thing that is missing - which is
  the bug this section exists to fix, not a smaller version of it.

  Which fields exist still varies by view - the goal detail page has an
  assignee select and the task list does not - so the flow is still by column
  and only the two fields that appear on every row are given a fixed width.
*/
.task__meta {
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  justify-content: end;
  gap: 0.5rem;
  flex: none;
}

/*
  The two lanes that are always present, sized to their longest ordinary value:
  "Normal" and "5 days overdue". Fixed rather than min-width, so a row with a
  short value does not narrow its own column out of alignment.
*/
.task__lane {
  display: flex;
  justify-content: center;
}
.task__lane--priority {
  width: 4.75rem;
}
.task__lane--due {
  width: 8.25rem;
}

/* Each field gets a lane wide enough for its longest ordinary value. */
.task__meta > .inline-form:has(.select-inline) {
  width: 8.5rem;
}
.task__meta .select-inline {
  width: 100%;
}
.task__meta > .pill {
  min-width: 4.5rem;
  justify-content: center;
}
/*
  The estimate pill is reached through its lane as well as directly. Section 42
  wraps it on the task list and leaves it a direct child on the dashboard and
  the goal page, and a pill that is 5.5rem wide on one screen and 2.6rem wide on
  another is the same pill drawn two ways.
*/
.task__meta > .pill--estimate,
.task__lane--estimate > .pill--estimate,
.task__meta > .repeat-pill,
.task__meta > .assignee-pill {
  min-width: 5.5rem;
  justify-content: center;
}
/*
  The delete control keeps its own lane at the end, so it sits under the one
  above it whether or not the row has a priority.
*/
.task__meta > form:not(:has(.select-inline)) {
  display: flex;
  justify-content: flex-end;
  min-width: 2rem;
}

@media (max-width: 640px) {
  /*
    Columns stop paying for themselves once there is no width to give them.
    Back to a wrapping row, which is what a narrow screen wants.
  */
  .task__meta {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
  }
  .task__meta > .inline-form:has(.select-inline),
  .task__meta > .pill,
  .task__meta > .pill--estimate,
  /* Reached through its lane too, or the reset above misses it entirely and
     the pill keeps a 5.5rem floor on the screen with the least room for it. */
  .task__lane--estimate > .pill--estimate,
  .task__meta > .repeat-pill,
  .task__meta > .assignee-pill,
  .task__lane {
    width: auto;
    min-width: 0;
  }
  /* An empty lane on a narrow screen is a gap for nothing. */
  .task__lane:empty {
    display: none;
  }
}

/* ---------------------------------------------------------------- */
/* 39. Room under a form's own action                                */
/* ---------------------------------------------------------------- */

/*
  Whatever follows a form inside a panel gets a gap from its submit button.

  The sharing panel put "See the links you have made" and "No links yet"
  directly under Create a link with nothing between them, so the sentence read
  as a caption belonging to the button rather than as the state of the panel.

  Keyed on the form rather than on the paragraph, because the two things that
  land there are a link and a whole list, and both want the same air.
*/
.panel > form + * {
  margin-top: 1.1rem;
}

/* ---------------------------------------------------------------- */
/* 40. The date field                                                */
/* ---------------------------------------------------------------- */

/*
  A native date input renders its value from the operating system's locale and
  no page can change that, so on an en-CA machine it reads yyyy-mm-dd while
  every other date in the app reads "Aug 24, 2026".

  The native control is still the field that submits. It is moved out of sight
  and out of the tab order, never disabled and never removed, and a text input
  showing the app's format sits in front of it. With the script blocked, none
  of this applies and the native picker is simply visible.
*/
.datefield {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.35rem;
}
.datefield__native {
  /*
    Not display:none and not disabled - both would stop it submitting. Clipped
    to nothing, which leaves it in the form and out of the way.
  */
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  border: 0;
}
.datefield__text {
  flex: 1;
  min-width: 0;
}
.datefield__open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 2.5rem square: the 24px target floor the phone suite asserts, with room. */
  width: 2.5rem;
  min-height: 2.5rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--white);
  cursor: pointer;
}
.datefield__open:hover {
  border-color: var(--navy-500);
}

.datefield__pop {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 0;
  z-index: 30;
  width: 17rem;
  padding: 0.6rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--white);
  box-shadow: var(--shadow-md);
}
.datefield__head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
}
.datefield__month {
  flex: 1;
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.92rem;
}
.datefield__step {
  width: 2rem;
  min-height: 2rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  cursor: pointer;
  color: var(--text);
}
.datefield__grid {
  width: 100%;
  border-collapse: collapse;
}
.datefield__grid th {
  padding: 0.2rem 0;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-soft);
}
.datefield__grid abbr {
  text-decoration: none;
}
.datefield__day {
  width: 100%;
  min-height: 2rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  cursor: pointer;
}
.datefield__day:hover {
  background: var(--paper-warm);
}
.datefield__day.is-today {
  border-color: var(--line-strong);
}
.datefield__day.is-selected {
  background: var(--navy-500);
  border-color: var(--navy-500);
  color: var(--on-accent);
}
.datefield__foot {
  display: flex;
  justify-content: space-between;
  gap: 0.4rem;
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--line);
}
.datefield__quick {
  border: 0;
  background: transparent;
  padding: 0.25rem 0.4rem;
  font-size: 0.82rem;
  color: var(--navy-700);
  cursor: pointer;
  text-decoration: underline;
}

/* A date that could not be read is left as typed and said so. */
.datefield__text[aria-invalid='true'] {
  border-color: var(--bad);
}

/* ---------------------------------------------------------------- */
/* 41. The bulk bar arrives with the first tick                      */
/* ---------------------------------------------------------------- */

/*
  A row of buttons that can only act on a selection is furniture until there is
  one, and it was taking a block of vertical space above every task list to say
  so.

  Done with :has() rather than script, because a bar that appeared only for
  people running JavaScript would be a bar that half the definition of "works"
  in this codebase cannot reach. The list and the bar share .task-board, so the
  container is what gets asked whether anything inside it is checked.

  Wrapped in @supports on purpose. Without the guard, a browser with no :has()
  would apply the `display: none` and never find the rule that takes it back -
  the feature would not degrade, it would disappear. Inside the guard, such a
  browser gets neither rule and keeps the always-visible bar it had before.
*/
@supports selector(:has(*)) {
  .task-board > .bulk-bar {
    display: none;
  }
  .task-board:has(.task__select-box:checked) > .bulk-bar {
    display: grid;
  }
}

/* ---------------------------------------------------------------- */
/* 42. Headings over the columns a task row already had             */
/* ---------------------------------------------------------------- */

/*
  Section 38 put the right-hand fields of a task row into fixed lanes so they
  line up down the page. This is the line that says what they are.

  Two lanes are added here to make that possible.

  The estimate had no lane. It did not shift the fields after it - .task__meta
  is justify-content: end and is anchored right by .task__body's flex: 1, so an
  omitted column moves nothing to its right - but a heading needs a box to sit
  over on EVERY row, present or not, and estimate pills need a column of their
  own to line up in.

  The "stop repeating" button and the delete button did have a column each, so
  a repeating task carried one more column than the row above it and every lane
  in that row shifted left by the width of a button. That is the defect section
  38 was written for, arriving from the end it did not cover.

  The heading row is not part of the list - it is a sibling, so the list stays a
  list of tasks - and it mirrors a row's own right-hand inset, a transparent
  border and the same padding, rather than guessing at the offset.
*/

/*
  Both are items of one grid track.

  .task-list is itself a grid whose single implicit track is sized by the widest
  row's min-content, so when a row is wider than the column - a repeating task
  assigned to a teammate is, at several ordinary window widths - every row
  renders at that width. The heading row is a block, so it stayed at the
  column's width and every heading fell short of its column by exactly the
  overflow. Sharing a track means the headings go wherever the rows go.
*/
.task-board {
  display: grid;
}
.task-list__head {
  display: grid;
  grid-auto-flow: column;
  justify-content: end;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.75rem 0.35rem;
  /* What a row's own border contributes to the inset. Transparent, so it
     reserves the width without drawing a line. */
  border-right: 1px solid transparent;
}
.task-list__head-lane {
  display: flex;
  justify-content: center;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.task__lane--estimate {
  /* The width the estimate pill used to reserve for itself. */
  width: 5.5rem;
}
/*
  Wide enough for the two icon buttons a repeating task carries, and packed to
  the right so a row with only the delete button leaves it exactly where it has
  always been rather than centred in a gap.
*/
.task__lane--actions {
  width: 4rem;
  justify-content: flex-end;
}

/*
  A column nobody on this list uses costs 5.5rem of width it cannot give back.

  The lanes are fixed and .task__meta does not shrink, so every lane is width
  the row cannot do without - and a list where nothing has an estimate was
  paying for that column anyway, which is what pushed rows past the content
  column at ordinary window widths. Asking whether the board holds an estimate
  at all takes the column away when it holds none, heading included: the rule
  matches the heading's lane and the rows' lanes alike, so they cannot disagree
  about how many columns there are.

  Wrapped in @supports for the reason section 41 is: without :has() a browser
  would find the rule that hides the column and never the condition that was
  meant to gate it. Inside the guard, such a browser simply keeps the column.
*/
@supports selector(:has(*)) {
  .task-board:not(:has(.pill--estimate)) .task__lane--estimate {
    display: none;
  }
}

@media (max-width: 640px) {
  /*
    The lanes collapse to a wrapping row at this width - section 38 - so there
    are no columns left for a heading to sit over.
  */
  .task-list__head {
    display: none;
  }
  .task__lane--estimate,
  .task__lane--actions {
    width: auto;
  }
}

/* ---------------------------------------------------------------- */
/* 43. A paged list keeps its height on the last page               */
/* ---------------------------------------------------------------- */

/*
  The last page of a paged list is almost always short, and the panel was
  shrinking to fit it: turning to a final page of one moved everything below it
  up the screen, which reads as the page having lost something.

  The blank rows are a sibling of the list rather than empty items inside it,
  so the list still holds only the work that was actually finished. Each one is
  an empty line of text at a row's own font size, which is a row high by
  construction - there is no measurement here to fall out of step with the row
  it is standing in for.
*/
.tick-reserve {
  display: grid;
  /* The gap the list uses between its own rows, and one more above the first
     stand-in to replace the gap that would have preceded it. */
  gap: 0.4rem;
  margin-top: 0.4rem;
}
.tick-reserve__row {
  font-size: 0.95rem;
}

/* ---------------------------------------------------------------- */
/* 44. The task board                                                */
/* ---------------------------------------------------------------- */

/*
  Three columns at the unfiltered address; the three filter chips still render
  the single-column list, which is where the lane headings from section 42 live.

  The bulk bar stays a DIRECT child of .task-board and the columns sit beside
  it, not around it. Section 41's :has() reveal selects `.task-board > .bulk-bar`
  and test/stylesheet.test.js asserts that exact selector, so wrapping the bar
  would break the no-script reveal and the test that guards it.
*/
.task-board__columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.9rem;
  align-items: start;
}
.task-board__column {
  min-width: 0;
  padding: 0.75rem;
  border-radius: var(--r-md);
  background: var(--paper-warm);
  border: 1px solid var(--line);
}
.task-board__column-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0 0 0.6rem;
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.task-board__column-title {
  /* Its own element so the count can sit apart from it rather than being read
     as part of the column's name. */
  min-width: 0;
}
.task-board__count {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.task-board__empty {
  margin: 0;
  padding: 0.75rem 0.25rem;
  font-size: 0.86rem;
  color: var(--text-soft);
}
.task-board__more {
  display: inline-block;
  margin-top: 0.6rem;
  font-size: 0.82rem;
  color: var(--text-soft);
}

/*
  A card on the board is the list row, rearranged.

  The row is a single flex line because the list has room for one. A column is a
  third as wide, so the same content wraps: the body takes the full width and
  the meta lanes fall underneath it rather than being squeezed to nothing.
*/
.task-board__column .task {
  flex-wrap: wrap;
  align-items: flex-start;
}
.task-board__column .task__body {
  flex-basis: 100%;
  order: -1;
  margin-bottom: 0.4rem;
}
.task-board__column .task__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 0.35rem;
}
/* The fixed lanes are a list idea. In a column there is nothing to line up
   with, and a lane holding its width would push the row sideways. */
.task-board__column .task__lane {
  width: auto;
  min-width: 0;
}
.task-board__column .task__lane:empty {
  display: none;
}

.task-card__move {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  flex-basis: 100%;
  margin-top: 0.4rem;
}
.task-card__move-btn {
  border: 1px solid var(--line);
  background: var(--white);
  color: var(--text-soft);
  border-radius: var(--r-pill);
  padding: 0.15rem 0.6rem;
  font-size: 0.75rem;
  cursor: pointer;
}
.task-card__move-btn:hover {
  border-color: var(--navy-500);
  color: var(--text);
}

@media (max-width: 900px) {
  /* Three columns stop being columns well before the page does. Stacked, each
     one keeps its heading and its count, which is the part that was doing the
     work. */
  .task-board__columns {
    grid-template-columns: minmax(0, 1fr);
  }
}
