/* Visual system.

   The look is coss.com/ui - the design system behind Cal.com - rebuilt in
   plain CSS. Coss ships as React components and this app has no build step
   and no React, so what is ported here is the system underneath it: the token
   names and values, the neutral ramp, the 10px radius, hairline borders drawn
   in transparent black, a barely-there shadow on anything raised, and one
   focus ring used everywhere.

   The rest of the brief still holds: very light neutral ground, white cards,
   generous radius, one sans. The only saturated colors on screen are the
   goal colors, the status colors and the current-time line.

   Every rule below is written against the semantic names, so changing the
   palette is an edit in one place. */

/* The greeting's face. Wild Pines Park is the heavy capitals the OROS
   wordmark is drawn in. It has no lowercase at all, so anything set in it
   comes out as capitals - which is why it is used on the greeting and
   nowhere else in the app. */
@font-face {
  font-family: "Wild Pines Park";
  src: url("fonts/WildPinesPark-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

:root {
  /* ---------------------------------------------- the neutral ramp
     Coss builds almost everything out of one gray ramp. So does this. */
  --neutral-50:  #fafafa;
  --neutral-100: #f5f5f5;
  --neutral-200: #e5e5e5;
  --neutral-300: #d4d4d4;
  --neutral-400: #a3a3a3;
  --neutral-500: #737373;
  --neutral-600: #525252;
  --neutral-700: #404040;
  --neutral-800: #262626;
  --neutral-900: #171717;
  --neutral-950: #0a0a0a;

  /* ---------------------------------------------- surfaces and ink */
  --background: #f6f6f6;
  --foreground: #18181b;
  --card-bg: #ffffff;
  --popover: #ffffff;

  --primary: var(--neutral-800);
  --primary-foreground: var(--neutral-50);
  /* Where the primary button goes when you point at it and when you press it.
     These used to be written into the button as --neutral-900 and -950, which
     is a step darker only while the button itself is dark. In the dark theme
     the primary button is nearly white, so pointing at it painted it the same
     color as its own label and the word disappeared. */
  --primary-hover: var(--neutral-900);
  --primary-active: var(--neutral-950);

  /* Coss's secondary, muted and accent are all one thing: black at 4%. Tinted
     glass over whatever it sits on, so it works on any surface.

     It is also what every hover, press and quiet panel fill in this file is
     painted in. Those used to be drawn in --neutral-50, a flat #fafafa that
     stays #fafafa in the dark theme too - so hovering a row on a near-black
     page lit it up white, and a panel meant to sit back came forward. A
     transparent wash cannot make that mistake: it darkens a light surface and
     lightens a dark one, because it is only ever a percentage of whatever is
     already underneath it. */
  --secondary: rgba(0, 0, 0, .04);
  --secondary-hover: rgba(0, 0, 0, .07);
  --accent: rgba(0, 0, 0, .04);
  --muted-fg: #71717a;
  --subtle-fg: #a1a1aa;

  /* Borders are transparent black too, which is why nothing in here looks
     like a gray box pasted onto a gray box. */
  --border: #e8e8ec;
  --border-strong: #e4e4e7;
  --input: rgba(0, 0, 0, .10);
  --ring: var(--neutral-400);
  --ring-shadow: 0 0 0 3px rgba(163, 163, 163, .24);

  /* Pressing something darkens it here and lightens it in the dark theme, so
     the three wash strengths and the two surfaces they are painted over are
     named rather than written out wherever they are used. */
  --press-soft: rgba(0, 0, 0, .02);
  --press: rgba(0, 0, 0, .05);
  --press-strong: rgba(0, 0, 0, .07);
  --inset-top: rgba(0, 0, 0, .04);
  --scrim: rgba(0, 0, 0, .32);
  --knob: #ffffff;
  /* What the soft status fills are mixed into: the surface they sit on. */
  --surface-mix: #ffffff;

  /* ---------------------------------------------- status
     Each comes as a fill, a darker text tone, a soft background and an edge,
     so a warning strip and a warning word are visibly the same color. */
  --destructive: #ef4444;
  --destructive-foreground: #b91c1c;
  --destructive-soft: color-mix(in srgb, var(--destructive) 5%, var(--surface-mix));
  --destructive-edge: color-mix(in srgb, var(--destructive) 32%, transparent);
  --success: #10b981;
  --success-foreground: #047857;
  --success-soft: color-mix(in srgb, var(--success) 5%, var(--surface-mix));
  --success-edge: color-mix(in srgb, var(--success) 32%, transparent);
  --warning: #f59e0b;
  --warning-foreground: #b45309;
  --warning-soft: color-mix(in srgb, var(--warning) 5%, var(--surface-mix));
  --warning-edge: color-mix(in srgb, var(--warning) 32%, transparent);
  --info: #3b82f6;
  --info-foreground: #1d4ed8;
  --info-soft: color-mix(in srgb, var(--info) 5%, var(--surface-mix));
  --info-edge: color-mix(in srgb, var(--info) 32%, transparent);

  /* Time that has already been and gone, on any calendar grid. Deliberately
     faint: the past is context, not a warning. */
  --gone: color-mix(in srgb, var(--foreground) 5%, transparent);

  /* ---------------------------------------------- icon tiles
     How much of a color goes into the little rounded square a symbol sits
     in. It is a percentage rather than a color because the color itself
     comes from the goal, and the goal comes from the data.

     Two strengths, because they are saying two different things. A goal tile
     is a label - it tells you which of eight things this row belongs to, and
     wants to be quiet enough that you read the words first. A tile on
     something the app is *asking* you has to be noticed, so it carries half
     again as much color.

     Both go up in the dark theme: a tenth of a hue over white is a visible
     tint, and a tenth of the same hue over near-black is near-black. */
  --tile-wash: 10%;
  --ai-wash: 15%;

  /* ---------------------------------------------- radius
     Coss sets --radius: 0.625rem and derives the rest from it. */
  --radius: 10px;
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: var(--radius);
  --radius-xl: 16px;
  --radius-2xl: 18px;
  --radius-full: 999px;

  /* ---------------------------------------------- elevation
     Four steps, and the first is nearly invisible on purpose: it is what
     separates a control you can press from a shape that is only drawn. */
  --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, .05);
  --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, .08), 0 1px 2px -1px rgba(0, 0, 0, .08);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .08), 0 2px 4px -2px rgba(0, 0, 0, .06);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .10), 0 4px 6px -4px rgba(0, 0, 0, .08);
  --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .12), 0 8px 10px -6px rgba(0, 0, 0, .08);

  /* ---------------------------------------------- control heights
     Every button, input and select picks one of these. It is the single
     biggest reason a row of mixed controls now lines up. */
  --h-xs: 26px;
  --h-sm: 34px;
  --h-md: 34px;
  --h-lg: 40px;

  /* Two things stick underneath the top bar - the week's day headings and the
     168-hour remainder strip - and both need to know where it ends. They used
     to carry a hardcoded 57px, which had drifted out of date. */
  --topbar-h: 66px;

  --ease: cubic-bezier(.2, 0, .2, 1);
  --dur: 130ms;

  --font: "Inter", ui-sans-serif, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
  --font-heading: "Cal Sans", "Inter", ui-sans-serif, system-ui, sans-serif;
  /* The greeting, and nothing else. See the @font-face above. */
  --font-display: "Wild Pines Park", var(--font-heading);
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --font-serif: ui-serif, Georgia, "Iowan Old Style", "Times New Roman", serif;

  /* ---------------------------------------------- the old names
     Kept as aliases so no screen had to be rewritten to move onto the new
     palette. New rules should use the semantic names above. */
  --bg: var(--background);
  --card: var(--card-bg);
  --line: var(--border);
  --line-strong: var(--border-strong);
  --ink: var(--foreground);
  --ink-2: var(--neutral-600);
  --muted: var(--muted-fg);
  --faint: var(--subtle-fg);
  --now: var(--destructive);

  --r-card: var(--radius-xl);
  --r-block: var(--radius-md);
  --card-pad: 20px;
  --pane-l: 320px;
  --pane-r: 300px;
  --hour: 62px;
}

/* ---------------------------------------------------------------- the dark

   Every rule in this file is written against the names above, so the dark
   theme is those names given different values and nothing else. No rule
   below this block knows which theme it is in.

   It follows the machine: whatever the phone or the laptop is set to, which
   is the setting people actually have an opinion about. There is no switch in
   the app, because a second place to say the same thing is a second place for
   it to be wrong.

   Three things are genuinely different rather than inverted:

     - Cards are LIGHTER than the ground, not white on gray. Painting a white
       card on a dark page is how a dark theme ends up glowing at you.
     - Borders and press washes are transparent WHITE, so they still read as a
       hairline over any surface, the way transparent black does in the light.
     - The status text tones go up the ramp, not down. #b91c1c on near-black
       is a color you can see is red and cannot read. */
/* The dark theme is now opt-in rather than automatic.
   The app is aimed at a light, near-white product, and following the system
   meant that on a Mac set to dark you never saw it - you got a dark app that
   had been designed light, which is why panels glowed and hovers vanished.
   So: light unless somebody asks otherwise. `data-theme` is stamped on the
   root by app.js from settings.theme; "system" is the one value that lets the
   query below take over again. */
@media (prefers-color-scheme: dark) {
  :root[data-theme="system"] {
    --background: #0e0e10;
    --foreground: var(--neutral-200);
    --card-bg: #17171a;
    --popover: #1c1c20;

    --primary: var(--neutral-100);
    --primary-foreground: var(--neutral-900);
    --primary-hover: var(--neutral-50);
    --primary-active: #ffffff;

    --secondary: rgba(255, 255, 255, .06);
    --secondary-hover: rgba(255, 255, 255, .10);
    --accent: rgba(255, 255, 255, .06);
    --muted-fg: #9a9aa2;
    --subtle-fg: #6e6e77;

    --border: rgba(255, 255, 255, .10);
    --border-strong: rgba(255, 255, 255, .18);
    --input: rgba(255, 255, 255, .14);
    --ring: var(--neutral-400);
    --ring-shadow: 0 0 0 3px rgba(163, 163, 163, .28);

    --press-soft: rgba(255, 255, 255, .03);
    --press: rgba(255, 255, 255, .07);
    --press-strong: rgba(255, 255, 255, .11);
    --inset-top: rgba(255, 255, 255, .05);
    --scrim: rgba(0, 0, 0, .58);
    --knob: var(--neutral-100);
    --surface-mix: #17171a;

    /* Readable on a dark ground, and still obviously the same four colors. */
    --destructive: #f87171;
    --destructive-foreground: #fca5a5;
    --success: #34d399;
    --success-foreground: #6ee7b7;
    --warning: #fbbf24;
    --warning-foreground: #fcd34d;
    --info: #60a5fa;
    --info-foreground: #93c5fd;

    /* The soft fills need more of the color in them here: 5% of a hue over
       near-black is indistinguishable from near-black. */
    --destructive-soft: color-mix(in srgb, var(--destructive) 12%, var(--surface-mix));
    --success-soft:     color-mix(in srgb, var(--success) 12%, var(--surface-mix));
    --warning-soft:     color-mix(in srgb, var(--warning) 12%, var(--surface-mix));
    --info-soft:        color-mix(in srgb, var(--info) 12%, var(--surface-mix));

    /* And the tile washes, for the same reason. */
    --tile-wash: 24%;
    --ai-wash: 30%;

    /* The past recedes rather than lightens: cards are lighter than the page
       here, so a black wash is what reads as "already behind you". */
    --gone: color-mix(in srgb, #000 22%, transparent);

    /* Shadows do almost no work on a dark ground - depth comes from the card
       being lighter than the page - so they are kept only as a soft anchor
       under the things that genuinely float. */
    --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, .40);
    --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, .45), 0 1px 2px -1px rgba(0, 0, 0, .45);
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .50), 0 2px 4px -2px rgba(0, 0, 0, .40);
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .55), 0 4px 6px -4px rgba(0, 0, 0, .45);
    --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .60), 0 8px 10px -6px rgba(0, 0, 0, .50);

    --ink-2: var(--neutral-300);
  }

  /* The goal colors are the one thing not set from here: they are mixed in
     JavaScript, because the color itself comes from the data. `goalStyle` in
     util.js deepens the fill for this theme - 16% of a hue over white reads,
     and the same 16% over near-black does not. */

  ::selection { background: rgba(255, 255, 255, .16); }
}

/* The same tokens again, for somebody who has actually chosen dark. It is a
   copy rather than a shared block because a media query and an attribute
   cannot be combined into one selector without one of them winning when it
   should not - and a wrong answer here paints one theme's text on the
   other theme's ground. Keep the two in step. */
:root[data-theme="dark"] {
    --background: #0e0e10;
    --foreground: var(--neutral-200);
    --card-bg: #17171a;
    --popover: #1c1c20;

    --primary: var(--neutral-100);
    --primary-foreground: var(--neutral-900);
    --primary-hover: var(--neutral-50);
    --primary-active: #ffffff;

    --secondary: rgba(255, 255, 255, .06);
    --secondary-hover: rgba(255, 255, 255, .10);
    --accent: rgba(255, 255, 255, .06);
    --muted-fg: #9a9aa2;
    --subtle-fg: #6e6e77;

    --border: rgba(255, 255, 255, .10);
    --border-strong: rgba(255, 255, 255, .18);
    --input: rgba(255, 255, 255, .14);
    --ring: var(--neutral-400);
    --ring-shadow: 0 0 0 3px rgba(163, 163, 163, .28);

    --press-soft: rgba(255, 255, 255, .03);
    --press: rgba(255, 255, 255, .07);
    --press-strong: rgba(255, 255, 255, .11);
    --inset-top: rgba(255, 255, 255, .05);
    --scrim: rgba(0, 0, 0, .58);
    --knob: var(--neutral-100);
    --surface-mix: #17171a;

    /* Readable on a dark ground, and still obviously the same four colors. */
    --destructive: #f87171;
    --destructive-foreground: #fca5a5;
    --success: #34d399;
    --success-foreground: #6ee7b7;
    --warning: #fbbf24;
    --warning-foreground: #fcd34d;
    --info: #60a5fa;
    --info-foreground: #93c5fd;

    /* The soft fills need more of the color in them here: 5% of a hue over
       near-black is indistinguishable from near-black. */
    --destructive-soft: color-mix(in srgb, var(--destructive) 12%, var(--surface-mix));
    --success-soft:     color-mix(in srgb, var(--success) 12%, var(--surface-mix));
    --warning-soft:     color-mix(in srgb, var(--warning) 12%, var(--surface-mix));
    --info-soft:        color-mix(in srgb, var(--info) 12%, var(--surface-mix));

    /* And the tile washes, for the same reason. */
    --tile-wash: 24%;
    --ai-wash: 30%;

    /* The past recedes rather than lightens: cards are lighter than the page
       here, so a black wash is what reads as "already behind you". */
    --gone: color-mix(in srgb, #000 22%, transparent);

    /* Shadows do almost no work on a dark ground - depth comes from the card
       being lighter than the page - so they are kept only as a soft anchor
       under the things that genuinely float. */
    --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, .40);
    --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, .45), 0 1px 2px -1px rgba(0, 0, 0, .45);
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .50), 0 2px 4px -2px rgba(0, 0, 0, .40);
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .55), 0 4px 6px -4px rgba(0, 0, 0, .45);
    --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .60), 0 8px 10px -6px rgba(0, 0, 0, .50);

    --ink-2: var(--neutral-300);
  }

  /* The goal colors are the one thing not set from here: they are mixed in
     JavaScript, because the color itself comes from the data. `goalStyle` in
     util.js deepens the fill for this theme - 16% of a hue over white reads,
     and the same 16% over near-black does not. */

  ::selection { background: rgba(255, 255, 255, .16); }


* { box-sizing: border-box; }

html, body {
  /* The parts the browser draws itself - time pickers, dropdown menus, the
     scrollbars, the box a form control opens - take their look from this and
     nothing else. Without it a dark page opens a white clock face. */
  color-scheme: light dark;
  margin: 0;
  height: 100%;
  /* Nothing in this app is meant to be reached by scrolling the page sideways.
     Everything that is genuinely wider than the window - the year planner, the
     week grid - carries its own scroller, and the page itself moving takes the
     top bar and the columns off the left edge instead.

     What made it move was a tooltip. They are drawn as a ::after centered on
     their own button and are laid out even while hidden, so a long one on a
     button near the right edge pushed the page a hundred pixels wider before
     anybody hovered anything. tip.js already swings a tooltip in from the edge
     before it is shown, so clipping here costs nothing.

     `clip` rather than `hidden`: hidden would make this a scroll container and
     the sticky top bar would stop sticking. */
  overflow-x: clip;
  background: var(--background);
  color: var(--foreground);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

::selection { background: rgba(38, 38, 38, .12); }

/* ------------------------------------------------------------- headings

   Several screens drop a bare <h1> straight into the page and were getting
   the browser's own 32px default, which is why the app had two different
   heading sizes depending on which screen you were on. One scale now. */

h1 { font-size: 22px; font-weight: 700; letter-spacing: -.015em; margin: 0 0 12px; }
h2 { font-size: 16px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 8px; }
h3 { font-size: 14px; font-weight: 600; margin: 0 0 6px; }

/* Cal Sans - cal.com's own typeface - carries the words at the top of a
   screen, and Inter does everything else. Cal Sans ships in a single weight,
   so font-synthesis is turned off here: without it the browser smears a fake
   bold over a face that never had one. */
h1,
.wizard h1,
.cal-title,
.day-head h2,
.modal h2,
.empty-state h1,
.finding-head {
  font-family: var(--font-heading);
  font-weight: 400;
  font-synthesis-weight: none;
  letter-spacing: -.005em;
}

/* One focus treatment for the whole app, and only for the keyboard. Clicking
   a button no longer flashes a ring at you; tabbing to it still does. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }

/* Things you can press settle rather than snap. */
button, a, input, select, textarea, .card, summary, [role="button"] {
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease),
    opacity var(--dur) var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* Native checkboxes and radios take the app's ink instead of the OS blue. */
input[type="radio"], progress { accent-color: var(--primary); }

/* Coss draws its own checkbox rather than tinting the OS one, so a tick on
   the reading list and a tick on a task are the same object. Circular, not
   the rounded square this used to be - every checkbox in the reference's own
   screenshots is a circle. */
input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 16px; height: 16px; flex: none;
  margin: 0; padding: 0;
  border: 1px solid var(--input);
  border-radius: var(--radius-full);
  background: var(--card-bg);
  box-shadow: var(--shadow-xs);
  cursor: pointer;
  display: inline-grid; place-content: center;
}
input[type="checkbox"]:hover { border-color: var(--border-strong); }
input[type="checkbox"]::before {
  content: ""; width: 9px; height: 9px;
  transform: scale(0); transition: transform var(--dur) var(--ease);
  background: var(--primary-foreground);
  clip-path: polygon(14% 44%, 0 65%, 40% 100%, 100% 16%, 84% 0, 39% 66%);
}
input[type="checkbox"]:checked {
  background: var(--primary); border-color: var(--primary); box-shadow: none;
}
input[type="checkbox"]:checked::before { transform: scale(1); }

/* Slim scrollbars. There are a lot of scrolling panels in here, and the
   default chunky gray track cut lines through the layout. */
* { scrollbar-width: thin; scrollbar-color: var(--neutral-300) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--neutral-300);
  background-clip: content-box;
  border: 3px solid transparent;
  border-radius: var(--radius-full);
}
::-webkit-scrollbar-thumb:hover { background: var(--neutral-400); background-clip: content-box; }

/* ------------------------------------------------------------- primitives

   The small shared pieces Coss ships as components. Written once here so a
   badge on the targets screen and a badge in the peek panel are the same
   badge, rather than two things that happen to look alike. */

.badge, .badge-recommended {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--secondary);
  color: var(--muted-fg);
  font-size: 11px; font-weight: 600;
  letter-spacing: .01em; white-space: nowrap;
}
/* Coss badges are a flat 8% tint of the status color with no outline. */
.badge-success { background: color-mix(in srgb, var(--success) 10%, transparent); border-color: transparent; color: var(--success-foreground); }

/* The one the app puts against the option it thinks you should take. It is
   the success badge rounded all the way off, because a recommendation is an
   opinion offered alongside the choice rather than a status stamped on it,
   and a rounded pill says that where a rectangle does not. */
.badge-recommended {
  background: color-mix(in srgb, var(--success) 10%, transparent);
  border-color: transparent;
  color: var(--success-foreground);
  border-radius: var(--radius-full);
  padding: 1px 9px;
  letter-spacing: 0;
}

/* A switch, for a setting that is on or off. Coss keeps the checkbox for
   ticking things off a list and uses this for anything that is a preference -
   the shape tells you which kind of question you are being asked. */
input[type="checkbox"].switch {
  appearance: none; -webkit-appearance: none;
  position: relative; flex: none;
  width: 34px; height: 20px; padding: 0; margin: 0;
  border: 0; border-radius: var(--radius-full);
  background: var(--input);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .08);
  cursor: pointer;
  transition: background-color var(--dur) var(--ease);
}
input[type="checkbox"].switch::before { content: none; }
input[type="checkbox"].switch::after {
  content: "";
  position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: var(--radius-full);
  background: var(--knob); box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease);
}
input[type="checkbox"].switch:checked { background: var(--primary); box-shadow: none; }
input[type="checkbox"].switch:checked::after { transform: translateX(14px); }
input[type="checkbox"].switch:disabled { opacity: .64; pointer-events: none; }

/* A radio, for a question with exactly one answer. The app has never had one,
   which is why every single-choice question in here is a row of things that
   look pressable and give you no warning that picking one un-picks another. A
   circle is the only shape that says that before you touch it.

   Chosen is a thick ring of ink with the surface still showing through the
   middle, not a filled disc: an empty center reads as "this one, out of
   these", where a solid dot reads as a bullet. The ring is grown with an
   inset shadow rather than a fatter border so the outer size never changes
   and the whole thing can settle into place instead of jumping. */
.radio {
  flex: none; appearance: none; -webkit-appearance: none;
  display: inline-block;
  width: 20px; height: 20px; padding: 0; margin: 0;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--card-bg);
  box-shadow: inset 0 0 0 0 var(--primary);
  cursor: pointer;
  transition:
    border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}
.radio:hover { border-color: var(--foreground); }
.radio[aria-checked="true"] {
  border-color: var(--primary);
  box-shadow: inset 0 0 0 4.5px var(--primary);
}
.radio:disabled, .radio[aria-disabled="true"] { opacity: .5; pointer-events: none; }

/* A tooltip the app draws itself. The icon-only controls on the top bar were
   relying on the Mac's own, which takes about a second to show up and looks
   like it belongs to a different program.

   Two separate properties do two separate jobs: `translate` centers the
   tooltip on its button, `transform` does the small slide as it opens. They
   compose, so keeping them apart is what lets the edge rules further down
   move the tooltip without having to restate the slide four more times. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute; top: calc(100% + 7px); left: 50%;
  translate: -50% 0;
  transform: translateY(-2px);
  padding: 3px 8px;
  border-radius: var(--radius-md);
  background: var(--neutral-800); color: var(--neutral-50);
  box-shadow: var(--shadow-md);
  font-size: 12px; font-weight: 500; line-height: 1.4;
  white-space: nowrap; pointer-events: none;
  opacity: 0; visibility: hidden;
  transition: opacity 120ms var(--ease), transform 120ms var(--ease), visibility 120ms;
  z-index: 200;
}
[data-tip]:hover::after, [data-tip]:focus-visible::after {
  opacity: 1; visibility: visible; transform: translateY(0);
  transition-delay: 500ms;
}
/* Below the fold there is no room underneath, so it flips above. */
[data-tip][data-tip-above]::after {
  top: auto; bottom: calc(100% + 7px);
  transform: translateY(2px);
}
[data-tip][data-tip-above]:hover::after,
[data-tip][data-tip-above]:focus-visible::after {
  transform: translateY(0);
}
/* At either edge of the window a centered tooltip hangs half off the screen
   and gets cut down the middle, with nothing to scroll to reach the rest of
   it. So when it will not fit centered it hangs from the near edge of its
   button instead. tip.js measures and picks; these two are the results. */
[data-tip][data-tip-align="start"]::after { left: 0; right: auto; translate: none; }
[data-tip][data-tip-align="end"]::after { left: auto; right: 0; translate: none; }
/* A touch screen has no hover, and a tooltip that sticks after a tap is
   worse than none. */
@media (hover: none) { [data-tip]::after { content: none; } }

/* Shown while something is still loading, so a panel has a shape before it
   has content and the page stops jumping when the answer arrives. */
.skeleton {
  border-radius: var(--radius-sm);
  /* Washes rather than the flat grays off the ramp. The ramp does not flip
     with the theme, so this used to be a pair of near-white bars laid across
     a near-black card. */
  background: linear-gradient(90deg, var(--press-strong) 25%, var(--secondary) 37%, var(--press-strong) 63%);
  background-size: 400% 100%;
  animation: skeleton 1.6s ease-in-out infinite;
}
@keyframes skeleton { from { background-position: 100% 50%; } to { background-position: 0 50%; } }

/* The other kind of waiting. A skeleton is for content that is about to have
   a known shape; this is for a card that cannot say anything about what is
   coming and would rather admit it. It sits inside the card it stands in for,
   so the page does not resize twice.

   The arc is open at the top because a broken ring is the only way a circle
   can show it is turning. */
.loading {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 32px 20px;
  text-align: center; color: var(--muted-fg);
}
.loading p { margin: 0; }
.spinner {
  display: block;
  width: 22px; height: 22px; flex: none;
  border: 2px solid var(--border-strong);
  border-top-color: transparent;
  border-radius: var(--radius-full);
  animation: spinner-turn .7s linear infinite;
}
@keyframes spinner-turn { to { transform: rotate(360deg); } }
/* Motion off, and a three-quarter arc that never moves just looks like
   something failed to draw. Closed up, it is a plain ring holding the space. */
@media (prefers-reduced-motion: reduce) {
  .spinner { border-top-color: var(--border-strong); opacity: .55; }
}

/* The app's own shape, drawn before it has anything to put in it. One column
   on a phone: the three-pane grid it borrows is a laptop layout, and three
   columns squeezed into 375px is a worse lie about what is coming than one. */
.boot-skeleton { opacity: .7; }
@media (max-width: 760px) {
  .boot-skeleton { display: block; }
  .boot-skeleton > .card + .card { display: none; }
}

/* ------------------------------------------------------------------ top bar */

.topbar {
  min-height: var(--topbar-h);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  /* Frosted rather than solid: the calendar scrolling underneath stays
     faintly visible, which is what stops the bar reading as a lid. */
  background: color-mix(in srgb, var(--background) 78%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  position: sticky;
  top: 0;
  z-index: 40;
}

.brand {
  width: 20px; height: 20px;
  border-radius: var(--radius-sm);
  background: linear-gradient(160deg, var(--neutral-700), var(--neutral-950));
  box-shadow: var(--shadow-xs);
}

.topbar-left { display: flex; align-items: center; gap: 11px; min-width: 0; }

/* The greeting uses the app's own sans, like everything else. A serif here
   read as if it had been pasted in from a different product.

   The two halves are weighted differently on purpose: "Good evening" is the
   same six days out of seven, so it sits back in the muted gray, and the name
   carries the weight and the full ink. */
.greeting {
  font-family: var(--font);
  font-size: 16px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.greeting-time { color: var(--muted); font-weight: 400; }
.greeting-name { color: var(--foreground); font-weight: 700; }
@media (max-width: 760px) { .greeting { display: none; } }

/* The greeting at the top of the screen, which arrives once, when the app is
   opened, and then goes.

   The name drops in from above. The date opens under it half a second later
   and closes again after three and a half seconds. Then, at five, the whole
   row floats up and shuts, and the app slides into the space: by that point
   you are working, and a hello you have already read is a row you are
   scrolling past.

   The row closes by going from one row of grid to none, which is how a
   height that nobody has measured can be animated to nothing - the greeting
   can wrap to two lines on a narrow window and this still closes exactly. */
.hello-row {
  display: grid;
  grid-template-rows: 1fr;
}
.hello-inner {
  min-height: 0;
  padding: 2px 2px 12px;
}
.hello-row:not(.asking) {
  animation: hello-away .8s cubic-bezier(.4, 0, .5, 1) 5s both;
}
@keyframes hello-away {
  from { grid-template-rows: 1fr; opacity: 1; transform: none; }
  to   { grid-template-rows: 0fr; opacity: 0; transform: translateY(-10px); }
}
/* The closing row has to clip what is inside it or the words simply hang
   below it while it shuts. It cannot be clipped the whole time, though: that
   would cut the top off the name dropping in. So the clipping is switched on
   at the moment the closing starts, by an animation with no duration and the
   same delay - the only way one element's CSS can wait for another's. */
.hello-row:not(.asking) .hello-inner {
  animation: hello-clip 0s linear 5s both;
}
@keyframes hello-clip {
  to { overflow: hidden; }
}
.hello-row h2 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-synthesis-weight: none;
  font-size: 30px;
  line-height: 1.1;
  letter-spacing: .01em;
  color: var(--foreground);
  animation: hello-in .9s cubic-bezier(.2, .7, .3, 1) both;
}
.hello-row p {
  margin: 4px 0 0;
  font-size: 13px;
  color: var(--muted);
  overflow: hidden;
  animation: hello-line-in .7s cubic-bezier(.2, .7, .3, 1) .5s both,
             hello-line-out .8s ease 3.5s both;
}
@keyframes hello-in {
  from { opacity: 0; transform: translateY(-26px) scale(.94); }
  to   { opacity: 1; transform: none; }
}
@keyframes hello-line-in {
  from { opacity: 0; transform: translateY(-14px); max-height: 0; margin-top: 0; }
  to   { opacity: 1; transform: none; max-height: 22px; margin-top: 4px; }
}
@keyframes hello-line-out {
  from { opacity: 1; max-height: 22px; margin-top: 4px; }
  to   { opacity: 0; max-height: 0; margin-top: 0; }
}
/* The same row, before the app has been told a name: the question, the field
   beside it, and the line underneath that stays put rather than closing - it
   is explaining what the field is for, and an explanation that removes itself
   before you have answered is no use. */
.hello-ask-line { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.hello-ask { max-width: 12rem; }
.hello-row.asking p {
  overflow: visible;
  animation: hello-ask-in .7s cubic-bezier(.2, .7, .3, 1) .5s both;
}
/* No height in this one, unlike the date's: this sentence is long enough to
   wrap on a phone, and an animation that ends on a fixed height would cut the
   second line off. */
@keyframes hello-ask-in {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: none; }
}

/* Nothing moves for anyone who has asked for that. The date and the greeting
   both stay where they are rather than taking themselves away - someone who
   turned motion off still wants the words, and a row that vanishes is motion
   whether or not it slides. */
@media (prefers-reduced-motion: reduce) {
  .hello-row,
  .hello-inner,
  .hello-row h2,
  .hello-row p,
  .hello-row.asking p { animation: none; }
}
@media (max-width: 760px) {
  .hello-inner { padding-bottom: 8px; }
  .hello-row h2 { font-size: 24px; }
}

/* Coss's segmented control: a sunken track, and the selected item raised out
   of it on a white card with a hairline and the smallest shadow there is. */
.navpills {
  justify-self: center;
  display: flex;
  gap: 2px;
  background: var(--secondary);
  border-radius: var(--radius-lg);
  padding: 2px;
}

.pill {
  border: 1px solid transparent;
  background: transparent;
  padding: 5px 14px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--muted-fg);
  font-weight: 500;
}
.pill:hover { color: var(--foreground); background: var(--secondary); }
.pill.active {
  background: var(--card-bg); color: var(--foreground); font-weight: 600;
  border-color: transparent; box-shadow: var(--shadow-sm);
}
.pill.active:hover { background: var(--card-bg); }

/* Four controls, not ten tabs. Today keeps its label; the rest are icons with
   a tooltip, and each opens a hub of nested screens. */
.pill { display: flex; align-items: center; gap: 7px; }
.pill svg {
  width: 15px; height: 15px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}
.pill-icon { padding: 6px 11px; }

.avatar-btn {
  border: 1px solid var(--input); background: var(--card-bg);
  width: 30px; height: 30px; border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--muted-fg); padding: 0;
  box-shadow: var(--shadow-xs);
}
.avatar-btn:hover { color: var(--foreground); border-color: var(--border-strong); background: var(--secondary); }
.avatar-btn.active { border-color: var(--primary); color: var(--foreground); }
.avatar-btn svg {
  width: 16px; height: 16px;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Sub-tabs inside a hub. Quieter than the top bar on purpose - they are a
   second level, and making them shout would undo the point of nesting. */
.subnav {
  display: flex; gap: 2px; flex-wrap: wrap;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.subtab {
  border: 0; background: transparent; cursor: pointer;
  padding: 7px 12px 9px; color: var(--muted-fg); font-weight: 500;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.subtab:hover { color: var(--foreground); }
.subtab.active { color: var(--foreground); font-weight: 600; border-bottom-color: var(--primary); }

.topbar-right { display: flex; justify-content: flex-end; align-items: center; gap: 10px; }

/* The middle of the bar: which view of the calendar, and then the two places
   that are not the calendar. The gap is wider than the one inside the pill on
   purpose - it is what says these are a different kind of thing. */
.topbar-center { display: flex; align-items: center; gap: 18px; }

.topjump { display: flex; align-items: center; gap: 2px; }
.topjump button {
  border: 0; background: transparent; cursor: pointer;
  min-height: 26px; padding: 0 10px;
  border-radius: var(--radius-full);
  font: inherit; font-size: 13px; font-weight: 500;
  color: var(--muted-fg);
}
.topjump button:hover { color: var(--foreground); background: var(--secondary); }
/* The screen you are on, said the same way the calendar pill says it. */
.topjump button.active { color: var(--foreground); background: var(--secondary); font-weight: 600; }
.topjump button:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }

/* The menu button with its word showing. Round dot becomes a pill; everything
   else about it is unchanged. */
.avatar-btn--labeled {
  width: auto; padding: 0 12px; gap: 6px;
  border-radius: var(--radius-full);
  font: inherit; font-size: 13px; font-weight: 500;
}
.date-label { color: var(--muted); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ layout */

main { padding: 16px 20px 40px; }

/* Arriving on a screen.

   Only when you have actually gone somewhere - not every time the screen is
   redrawn, which is most of the time. Small on purpose: enough to say "this is
   a different place" and short enough that pressing two things in a row never
   feels like waiting for an animation. */
@keyframes screen-in {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}
main.screen-in { animation: screen-in 170ms var(--ease); }

/* A hairline across the top while something is being saved.

   Held back for 400ms by the code that turns it on, so an ordinary save - which
   is over in a few milliseconds - never flashes anything at all. It is only for
   the connection that has gone slow, where the alternative is a screen that
   looks like it stopped listening. */
.saving::after {
  content: "";
  position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 300;
  background: linear-gradient(90deg, transparent, var(--primary), transparent);
  background-size: 40% 100%; background-repeat: no-repeat;
  animation: saving-sweep 1.1s linear infinite;
  pointer-events: none;
}
@keyframes saving-sweep {
  from { background-position: -40% 0; }
  to   { background-position: 140% 0; }
}

.three-pane {
  display: grid;
  grid-template-columns: var(--pane-l) minmax(420px, 1fr) var(--pane-r);
  gap: 16px;
  align-items: start;
}
/* The calendar panels take the width off the rails rather than off the grid.
   The rails are the same two cards on every screen and they read fine a little
   narrower; the middle, on the week, is seven day columns, and at 588px of a
   1280px window each one got 72px and every block title wrapped to four lines.
   Folding the goals away already fixed it, which is the tell that the room was
   there all along - it was just being handed to the wrong column by default. */
.three-pane:has(> .cal-middle) { --pane-l: 250px; --pane-r: 240px; }

.three-pane.left-collapsed { grid-template-columns: 44px minmax(420px, 1fr) var(--pane-r); }
.three-pane.right-collapsed { grid-template-columns: var(--pane-l) minmax(420px, 1fr) 44px; }
.three-pane.left-collapsed.right-collapsed { grid-template-columns: 44px minmax(420px, 1fr) 44px; }

@media (max-width: 1180px) {
  .three-pane, .three-pane.left-collapsed, .three-pane.right-collapsed { grid-template-columns: 1fr; }
}

/* ---- three columns, three scrollbars ----

   Side by side, these are columns, not a stack, and they are almost never the
   same height. Letting the window scroll instead meant that going to look at
   your evening took the goals list and the day's own heading off the top of
   the screen with it - so you scrolled back up to find out which day you were
   even looking at.

   So on a wide screen the shell is exactly the window: nothing outside it
   scrolls, and each column scrolls inside itself with its heading pinned. On a
   narrow screen the three stack into one column and the page scrolls as one
   page, which is the right behavior there and is left alone. */

@media (min-width: 1181px) {
  /* Only the three-column day gets locked to the window. Every other screen is
     one tall column and has to scroll normally - applying this to `main` itself
     clipped the compass, settings, targets and the reviews at the fold with no
     way to reach the rest. */
  main:has(.three-pane) {
    height: calc(100dvh - var(--topbar-h));
    padding-bottom: 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
  /* flex, not height:100% - height:100% is 100% of main's box, and when the
     demo banner sits above the three-pane as a sibling inside that same box,
     100% overshoots by exactly the banner's height. The excess is clipped by
     main's own overflow:hidden, which reads as the right column stopping
     short of its true bottom no matter how far you scroll. flex:1 auto asks
     for "whatever is left after my siblings" instead of a fixed share, so it
     is correct whether the banner is there or not. */
  .three-pane { flex: 1 1 auto; min-height: 0; align-items: stretch; }

  /* Today puts the three panes straight into `main`. The calendar puts them
     inside a hub wrapper first, and a plain block wrapper does not pass a
     height down - so the panes measured themselves against their own content,
     grew past the window, and `main` clipped whatever did not fit with no way
     to scroll to it. On the year that meant the time-off and heavy-stretch
     cards were simply unreachable on a wide screen. Every wrapper between
     `main` and the panes now hands the height through. */
  main:has(.three-pane) > *:has(.three-pane),
  main:has(.three-pane) .hub-body:has(.three-pane) {
    display: flex; flex-direction: column;
    flex: 1 1 auto; min-height: 0;
  }

  /* Every column, not just the two that happen to be a single card. The right
     one is a plain wrapper holding several cards stacked, so a rule written
     against `.card` skipped it entirely and it could not scroll at all.
     min-height:0 is what lets a grid item be shorter than its content; without
     it the column grows to fit and the overflow never triggers. */
  .three-pane > * { min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

  /* The heading spans the card's padding so nothing shows through beside it
     while the content slides underneath.

     The top edge is NOT done with the same negative-margin bleed as the
     left/right edges, and that is deliberate, not an oversight. A sticky
     element with a negative TOP margin reserves less space in normal flow
     than it actually occupies - confirmed by hand, in this browser, on this
     page - so the sibling right after it gets laid out too high and the
     sticky header paints over the top of it. On the day header that showed
     as the opening sentence looking garbled; on the goals pane it did the
     same to the first line under the tabs. Two different headers, same
     wrong number, so it is the mechanism at fault, not either header.

     The top edge still has to be covered, though, and moving the padding does
     it without any negative margin. `top: 0` pins the header to the card's
     content edge, which is 20px below where the column actually starts
     scrolling - so that 20px was a strip of live scrollport with nothing over
     it, and a line that had just slid under the title came back into view
     above it. Handing that padding to the header instead costs nothing in
     height and leaves no gap. */
  .three-pane > .card:has(> .card-head:first-child, > .day-head:first-child, > .pane-head:first-child),
  .three-pane > .side-rail > .card:has(> .card-head:first-child) {
    padding-top: 0;
  }

  .three-pane > .card > .card-head,
  .three-pane > .card > .day-head,
  .three-pane > .card > .pane-head,
  .three-pane > .side-rail > .card > .card-head {
    position: sticky; top: 0; z-index: 3;
    /* Frosted, like the top bar. Solid white read as a lid dropped on the
       column; frosted keeps whatever is passing underneath faintly there as a
       blur, so the list still feels continuous and the title stays legible. */
    background: color-mix(in srgb, var(--card-bg) 78%, transparent);
    backdrop-filter: saturate(180%) blur(16px);
    -webkit-backdrop-filter: saturate(180%) blur(16px);
    margin: 0 calc(var(--card-pad) * -1) 12px;
    padding: var(--card-pad) var(--card-pad) 10px;
  }

  /* Inside the pinned block the heading has already had its own spacing
     applied by the wrapper, so it must not add a second lot of it. */
  .three-pane > .card > .pane-head > .card-head { margin: 0 0 10px; padding: 0; }
  .three-pane > .card > .pane-head > .goal-views { margin-bottom: 0; }

  /* ---- the right column: a stack that scrolls, of boxes that also scroll ----

     The column scrolls as one, the way it always has - a long week is read by
     scrolling the column, not by squinting at it through a slot. What is new
     is a ceiling on each box: none may stand taller than the column itself, so
     a list that keeps growing becomes a box with its own scroll and its own
     pinned title rather than a card a page and a half long. The 40px is what
     leaves the top of the next box showing whenever one hits that ceiling,
     which is the thing that tells you the column has more below it.

     No overscroll containment on these, unlike the two columns beside them:
     reaching the end of a box has to carry straight on into the column, or the
     wheel stops dead halfway down the rail with more clearly left to read.

     Folded away, none of this applies: a 44px rail is a button, not a stack. */
  .three-pane:not(.right-collapsed) > .side-rail > .card {
    max-height: calc(100% - 40px);
    overflow-y: auto;
  }

  /* A collapsed pane is a 44px rail with nothing to scroll. */
  .three-pane > .card.pane-collapsed { overflow: visible; }
}

/* ---- how tall an hour is ----

   Shorter and taller, and nothing between them. There was a percentage there
   once and then a menu of named sizes; both answered a question nobody asks.
   On a phone there are no buttons at all - you pinch the day. See zoom.js. */

.zoom-control {
  display: inline-flex; align-items: center; gap: 1px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--card-bg);
  padding: 1px;
}
.zoom-control .zoom-step {
  width: 24px; height: 24px;
  padding: 0;
  border-radius: var(--radius-full);
  font-size: 14px; line-height: 1;
}
.zoom-control .zoom-step:disabled { opacity: .32; cursor: default; }

/* ---- today's score, part by part ----

   Each row is a part of the score with its own bar, and its second line is
   the instruction for earning it while it is still unearned. A number on its
   own says how you are doing; this says what to do next. */

.focus-total { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }

.focus-bar {
  height: 6px; border-radius: var(--radius-full);
  background: var(--secondary);
  overflow: hidden; margin-bottom: 14px;
}
.focus-bar > span {
  display: block; height: 100%;
  background: var(--success);
  border-radius: inherit;
  transition: width 240ms var(--ease);
}

.focus-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; }
.focus-row + .focus-row { border-top: 1px solid var(--border); }
.focus-row-body { flex: 1; min-width: 0; }
.focus-row-label { font-size: 13px; font-weight: 500; }
.focus-row.earned .focus-row-label { color: var(--muted); }
.focus-row-note {
  font-size: 11.5px; color: var(--muted-fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.focus-row-bar {
  flex: none; width: 54px; height: 5px;
  border-radius: var(--radius-full);
  background: var(--secondary);
  overflow: hidden;
}
.focus-row-bar > span {
  display: block; height: 100%;
  background: var(--success);
  border-radius: inherit;
  transition: width 240ms var(--ease);
}

/* How much of the day's list got done, under the three scored rows. A count,
   not a score, so it gets a number rather than a bar - and the number stays
   gray until the list is actually finished. A green "0 / 9" at nine in the
   morning would be praise for a day that has not started. */
.focus-tally {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 8px 0 0; margin-top: 4px;
  border-top: 1px solid var(--border);
}
.focus-tally-label { font-size: 12px; color: var(--foreground); }
.focus-tally-value {
  font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums;
  color: var(--muted-fg); white-space: nowrap;
}
.focus-tally-value.good { color: var(--success-foreground); }

/* Room to breathe. 16px was tight enough that every panel read as a dense
   little box; the reference runs closer to 20 and the difference across three
   columns is most of why one feels calm and the other feels busy. */
.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--card-pad);
  box-shadow: var(--shadow-xs), inset 0 1px 0 var(--inset-top);
}
.card + .card { margin-top: 16px; }

/* Side by side in the three-pane grid, that stacking margin pushes the middle
   and right panes 16px lower than the left. They are columns, not a stack. */
.three-pane > .card + .card { margin-top: 0; }

/* The three headers are different sizes - a small uppercase label on the
   sides, a real heading in the middle - so they are given a common height and
   centered in it. Otherwise every pane starts on its own line. */
.three-pane > .card > .card-head,
.three-pane > .card > .day-head {
  min-height: 34px;
  align-items: center;
  margin-bottom: 12px;
}

.card-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 12px;
}
/* Sentence case, at the size the words deserve.
   This used to be 13px, bold, UPPERCASE and letter-spaced, which is the
   convention for a label on a form field - a thing you scan past. But these
   are the names of the three things the screen is made of, and a panel that
   shouts GOALS at you reads like a filing cabinet. Superfocus writes the same
   headings as "Goals" and "Daily focus" and the whole screen calms down. */
.card-head h2 {
  margin: 0; font-size: 14px; font-weight: 600;
  letter-spacing: 0; text-transform: none; color: var(--foreground);
}

.pane-collapsed { padding: 12px 8px; text-align: center; }
.pane-collapsed button { writing-mode: vertical-rl; }

/* ------------------------------------------------------------------ buttons */

/* Coss buttons. A rounded rectangle, a hairline border, and the smallest
   shadow in the set, so a button looks pressable without shouting.

   Five variants and four heights, and nothing else in the app is allowed to
   invent its own - which is why a row of mixed controls now lines up. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: var(--h-md);
  padding: 0 12px;
  border: 1px solid var(--input);
  background: var(--card-bg);
  color: var(--foreground);
  border-radius: var(--radius-lg);
  /* Coss etches a hairline along the inside of the top edge of everything
     raised. It is what stops a pale button looking like a flat rectangle. */
  box-shadow: var(--shadow-xs), inset 0 1px 0 var(--inset-top);
  cursor: pointer;
  font-weight: 500;
  white-space: nowrap;
  user-select: none;
  position: relative;
  /* Some of these are links rather than buttons - "Set up your year", the
     export link in Settings. Without this they keep the underline every
     anchor gets by default, which is the one thing that gives away that a
     button is not a button. .btn-link sets its own underline back on. */
  text-decoration: none;
}
.btn:hover { background: var(--press-soft); }
.btn:active { background: var(--press); box-shadow: none; }
.btn:disabled { opacity: .64; pointer-events: none; box-shadow: none; }

/* Flat. The primary used to carry a lit top edge and a drop shadow, which is
   the treatment for a pale button that would otherwise read as a painted
   rectangle. A near-black button does not have that problem, and the two
   shadows only made it look embossed next to everything around it. */
.btn-primary {
  background: var(--primary); color: var(--primary-foreground);
  border-color: var(--primary); font-weight: 600;
  box-shadow: none;
}
.btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
.btn-primary:active { background: var(--primary-active); border-color: var(--primary-active); box-shadow: none; }
/* A disabled button has to look like nothing is going to happen. A solid gray
   fill with white text on it is a perfectly legible button that happens not
   to respond, which reads as the app being broken rather than as you not
   having finished. Graying the label as hard as the fill says "not yet". */
.btn-primary:disabled {
  background: var(--secondary); border-color: transparent;
  color: var(--subtle-fg); opacity: 1; box-shadow: none; pointer-events: none;
}

.btn-secondary { background: var(--secondary); border-color: transparent; box-shadow: none; }
.btn-secondary:hover { background: var(--secondary-hover); border-color: transparent; }

.btn-sm { height: var(--h-sm); padding: 0 10px; font-size: 13px; }
.btn-xs { height: var(--h-xs); padding: 0 8px; font-size: 12px; border-radius: var(--radius-md); }
.btn-icon { width: var(--h-md); padding: 0; }
.btn-sm.btn-icon { width: var(--h-sm); }

.btn-ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: var(--h-sm); padding: 0 8px;
  border: 1px solid transparent; background: transparent; box-shadow: none;
  border-radius: var(--radius-sm);
  color: var(--muted-fg); cursor: pointer; font-weight: 500;
  white-space: nowrap;
}
.btn-ghost:hover { background: var(--secondary); color: var(--foreground); }
.btn-ghost:active, .btn-secondary:active, .pill:active, .subtab:active,
.choice-row:active, .pill-sm:active { background: var(--press-strong); }
.btn-destructive:active, .btn-danger:active { box-shadow: none; }

/* Ready for a slow save: set data-busy on the button and the label swaps for
   a spinner without the button changing width. */
@keyframes btn-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
.btn[data-busy] { color: transparent; pointer-events: none; }
.btn[data-busy]::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 14px; height: 14px; margin: 0;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: var(--radius-full);
  animation: btn-spin .6s linear infinite;
}
/* The spinner is drawn in currentColor, and a busy button's text is
   transparent - so on the primary button it has to be repainted in whatever
   that button's label was, which is not white in both themes. */
.btn-primary[data-busy]::after { border-color: var(--primary-foreground); border-top-color: transparent; }

.btn-danger {
  color: var(--destructive-foreground);
  border-color: var(--destructive-edge);
  background: var(--card-bg);
}
.btn-danger:hover { background: var(--destructive-soft); border-color: var(--destructive); }

.btn-destructive {
  background: var(--destructive); border-color: var(--destructive);
  color: var(--on-destructive, #fff); font-weight: 600;
}
.btn-destructive:hover {
  background: var(--destructive-foreground); border-color: var(--destructive-foreground);
}

.btn-link {
  border: 0; background: none; box-shadow: none; padding: 0; height: auto;
  color: var(--foreground); text-decoration: underline; text-underline-offset: 3px;
}
.btn-link:hover { background: none; color: var(--muted-fg); }

/* ------------------------------------------------------------------ goals pane */

.goal-block { padding: 10px 0; border-bottom: 1px solid var(--line); }
.goal-block:last-child { border-bottom: 0; }

/* min-width:0 for the same reason .goal-title has one, a rung further up: this
   row is itself a flex child, and without it the row refuses to shrink below
   its own contents - so the title inside never reaches the point where it
   would ellipsize, and a long goal name pushed the tier pill out past the edge
   of the card and had it clipped. Only showed on a narrow screen, which is
   where the tier is hardest to guess from anything else. */
.goal-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* The goal's color, and the first thing you see on the row. A soft halo in
   the same color makes it a tile rather than a speck. */
.goal-dot {
  width: 11px; height: 11px; border-radius: var(--radius-xs); flex: none;
  box-shadow: 0 0 0 3px var(--goal-fill, transparent);
}

/* A goal's symbol, washed in the goal's own color. Big enough to recognize
   at a glance down a column, quiet enough not to shout over the words.

   The wash is mixed here rather than taken from --goal-fill, which JavaScript
   sets at 16% for the calendar blocks. A block is a solid object you are
   meant to see the shape of from across the screen; a tile is a label on a
   line of text, and at 16% it competed with the words beside it. */
.goal-icon {
  width: 22px; height: 22px; flex: none;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--goal, var(--muted-fg)) var(--tile-wash), var(--surface-mix));
  color: var(--goal);
  display: flex; align-items: center; justify-content: center;
}
.goal-icon svg {
  width: 13px; height: 13px;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.target-main .goal-icon { margin-top: 1px; }

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

   The same tile at the three sizes the app needs it: beside a line of text,
   at the head of a row, and standing alone above a heading. The corner is
   kept at roughly 28% of the side at every size, because a fixed radius on a
   48px square looks square and on a 22px square looks like a circle.

   Color comes from whatever the tile is standing for. Left alone it takes
   the goal's, the way .goal-icon does; the two tone classes below override it
   where there is no goal to take it from. */
.icon-tile {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  background: color-mix(in srgb, var(--goal, var(--muted-fg)) var(--tile-wash), var(--surface-mix));
  color: var(--goal, var(--muted-fg));
}
.icon-tile svg {
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Forgetting the size class should give you the middle one, not a tile with
   no size at all - which collapses to nothing and looks like a missing icon
   rather than a missing class. */
.icon-tile { width: 36px; height: 36px; border-radius: var(--radius-lg); }
.icon-tile svg { width: 19px; height: 19px; }
.icon-tile-sm { width: 22px; height: 22px; border-radius: var(--radius-sm); }
.icon-tile-md { width: 36px; height: 36px; border-radius: var(--radius-lg); }
.icon-tile-lg { width: 48px; height: 48px; border-radius: 14px; }
.icon-tile-sm svg { width: 13px; height: 13px; }
.icon-tile-md svg { width: 19px; height: 19px; }
.icon-tile-lg svg { width: 25px; height: 25px; }

/* Nothing in particular: a tile for a symbol that stands for the app itself
   rather than for one of your goals. */
.tone-neutral { background: var(--secondary); color: var(--muted-fg); }
/* And the one that means the app is asking rather than telling. Blue is the
   only color in here that never means good or bad, which is exactly what a
   question needs - and it carries more of its hue than a goal tile does,
   because a question you do not notice is a question you do not answer. */
.tone-ai {
  background: color-mix(in srgb, var(--info) var(--ai-wash), var(--surface-mix));
  color: var(--info);
}

/* ------------------------------------------------- picking symbol and color

   One control, because they are one decision: how you pick this target out of
   a list of eight without reading any of them. */

.look-picker { display: flex; flex-direction: column; gap: 14px; }
.look-section { display: flex; flex-direction: column; gap: 7px; }
.look-label {
  font-size: 11px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted-fg);
}

.symbol-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(38px, 1fr)); gap: 6px; }
.symbol-pick {
  height: 38px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--input); border-radius: var(--radius-md);
  background: var(--card-bg); color: var(--muted-fg);
  box-shadow: var(--shadow-xs), inset 0 1px 0 var(--inset-top);
  cursor: pointer; padding: 0;
}
.symbol-pick:hover { background: var(--secondary); color: var(--foreground); }
.symbol-pick[aria-pressed="true"] {
  background: var(--goal-fill); color: var(--goal);
  border-color: var(--goal); box-shadow: 0 0 0 1px var(--goal);
}
.symbol-pick svg {
  width: 17px; height: 17px;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}

.swatch-grid { display: flex; flex-wrap: wrap; gap: 8px; }
/* Each swatch is the tile itself, in that color, carrying the chosen symbol:
   what you are picking is what you will see, not a paint chip of it. */
.swatch {
  width: 38px; height: 38px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--goal, var(--muted-fg)) var(--tile-wash), var(--surface-mix));
  color: var(--goal);
  cursor: pointer; box-shadow: var(--shadow-xs);
}
.swatch svg {
  width: 18px; height: 18px;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.swatch:hover { border-color: var(--goal); }
.swatch.taken { opacity: .45; }
.swatch[aria-pressed="true"] {
  opacity: 1;
  border-color: var(--goal);
  box-shadow: 0 0 0 1px var(--goal);
}

@media (max-width: 760px) {
  .symbol-grid { grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); }
  .symbol-pick { height: 44px; }
  .swatch { width: 44px; height: 44px; }
}
.goal-title {
  font-weight: 600; flex: 1; color: var(--foreground);
  /* min-width:0 is the part that matters: without it a flex child refuses to
     shrink below its own text, so it never ellipsizes and the title wraps to
     a second line instead - which is what breaks the column's rhythm. */
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* How much of this season a goal is. It used to be set in spaced capitals in
   a small rectangle, which is the treatment for a heading - and it read as
   one, shouting CORE down a list of quiet sentences. It is a pill now, set
   the way the word is actually spelled, and the tier is carried by color
   instead: blue for the one thing this season is about, green for the ones
   that are real but second in line, and no color at all for what is only
   ticking over. */
.tier-tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 600;
  color: var(--ink-2); background: var(--secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-full); padding: 1px 9px;
  white-space: nowrap;
}
/* Only the three tier words get capitalized. The same pill is also used for a
   duration and a date range, and "3h" and "This week" are already spelled the
   way they should be read. */
.tier-tag.core, .tier-tag.secondary, .tier-tag.support { text-transform: capitalize; }
.tier-tag.core {
  color: var(--info-foreground);
  background: color-mix(in srgb, var(--info) 10%, transparent);
  border-color: transparent;
}
.tier-tag.secondary {
  color: var(--success-foreground);
  background: color-mix(in srgb, var(--success) 10%, transparent);
  border-color: transparent;
}
.tier-tag.support {
  color: var(--muted-fg); background: var(--secondary); border-color: transparent;
}
/* The chevron belongs only on the pill that actually opens the list of tiers.
   Drawn on a plain label it would be a promise the row cannot keep. */
button.tier-tag::after, .tier-tag[aria-haspopup]::after {
  content: ""; flex: none;
  width: 5px; height: 5px; margin: -3px 0 0 -1px;
  border: 1.5px solid currentColor; border-left: 0; border-top: 0;
  transform: rotate(45deg);
}
/* Everything that is not one of the three tiers keeps taking the goal's own
   color, because in those places the pill is qualifying a goal rather than
   naming a rank. */
.goal-block .tier-tag:not(.core):not(.secondary):not(.support),
.target-row .tier-tag:not(.core):not(.secondary):not(.support) {
  color: var(--goal, var(--ink-2));
  background: var(--goal-fill, var(--secondary));
  border-color: var(--goal-edge, var(--border));
}

.badge-unmeasurable {
  display: inline-block; margin-top: 4px;
  font-size: 11px; font-weight: 600;
  color: var(--warning-foreground); background: var(--warning-soft);
  border: 1px solid var(--warning-edge);
  border-radius: var(--radius-sm); padding: 1px 8px;
}

.milestone-row {
  display: flex; align-items: center; gap: 8px;
  margin: 8px 0 6px; padding-left: 2px;
}

/* A goal opened on its own card wears its color along the top edge, the way
   the blocks on the calendar do. */
.card[style*="--goal"] {
  border-top: 3px solid var(--goal);
  border-top-left-radius: var(--r-card);
  border-top-right-radius: var(--r-card);
}
.milestone-diamond { flex: none; margin: 0 1px; }
.milestone-title { flex: 1; font-size: 13px; }
/* The milestone actually in play carries the goal's color, so the line you
   are working toward is the one that catches your eye. */
.milestone-row[data-active="true"] .milestone-title {
  color: var(--goal); font-weight: 600;
}
.milestone-date {
  color: var(--ink-2); font-size: 11px; font-weight: 500; white-space: nowrap;
  background: var(--secondary); border-radius: var(--radius-sm); padding: 1px 7px;
}

.task-row {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0 4px 18px; font-size: 13px;
}
/* The closeout's hit/missed lists - a ✓ and a — differ by one stroke at a
   glance; a filled green ring and a plain gray one don't. */
.row-icon {
  flex: none; width: 15px; height: 15px;
}
.row-icon svg {
  width: 100%; height: 100%;
  fill: none; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.row-icon--hit svg { stroke: var(--success); }
.row-icon--missed svg { stroke: var(--muted-fg); }
/* The same box wherever a task is listed - on the day, and on the plan. */
.task-row .checkbox, .plan-task .checkbox {
  width: 16px; height: 16px; border-radius: var(--radius-full);
  border: 1px solid var(--input); background: var(--card-bg);
  box-shadow: var(--shadow-xs);
  cursor: pointer; flex: none; padding: 0;
  display: flex; align-items: center; justify-content: center;
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}
.task-row .checkbox:hover, .plan-task .checkbox:hover { border-color: var(--border-strong); }
/* The tick is always there, sitting at nothing, so ticking a task draws it in
   rather than replacing an empty box with a full one. It is a pseudo-element
   rather than a character in the markup for the same reason: it has to be the
   same tick before and after, or there is nothing to animate. */
.task-row .checkbox::after, .plan-task .checkbox::after {
  content: "✓";
  color: var(--on-primary, #fff); font-size: 11px; line-height: 1;
  transform: scale(0); opacity: 0;
  transition: transform 160ms cubic-bezier(.2, 1.5, .5, 1), opacity 90ms var(--ease);
}
.task-row .checkbox[data-done="true"], .plan-task .checkbox[data-done="true"] {
  background: var(--goal, var(--primary));
  border-color: var(--goal, var(--primary));
  color: var(--on-primary, #fff); box-shadow: none;
}
.task-row .checkbox[data-done="true"]::after, .plan-task .checkbox[data-done="true"]::after { transform: scale(1); opacity: 1; }
.task-title { flex: 1; cursor: pointer; }
/* Settled rather than snapped, so a task crossing itself off reads as one
   movement instead of the row being swapped for a different row. */
.task-row .task-title { transition: color var(--dur) var(--ease); }
.task-row[data-done="true"] .task-title { color: var(--muted); text-decoration: line-through; }
/* Two chips that qualify a row with one small word - how long a thing takes,
   and how far off it is - and were two slightly different rectangles for no
   reason anybody could have named. One shape now, the same pill as the tier
   tag, so a row carrying both of them does not look like it borrowed one of
   them from somewhere else. Only the fill differs, because that is the part
   that is actually saying something. */
.time-chip, .horizon-chip {
  display: inline-flex; align-items: center;
  font-size: 11px; font-weight: 600; white-space: nowrap;
  padding: 1px 9px; border-radius: var(--radius-full);
  border: 1px solid transparent;
}
/* Tinted in the goal's own color. Reading down the pane, the color says
   which goal an hour belongs to before you have read a word of it. */
.time-chip {
  background: color-mix(in srgb, var(--goal, var(--neutral-500)) 13%, transparent);
  color: var(--goal, var(--muted-fg));
}
/* The same chip, with no hour in it yet. It must NOT take the goal tint: the
   whole point of the pair is that one glance down the column separates what
   has been given a time from what has not. */
.time-chip.unbooked {
  background: var(--secondary);
  color: var(--muted-fg);
  border-color: var(--border);
  gap: 4px;
}
.chip-clock { width: 10px; height: 10px; flex: none; }

.horizon-chip {
  color: var(--ink-2);
  border-color: var(--border); background: var(--secondary);
}
.horizon-chip[data-h="next"] { color: var(--foreground); border-color: var(--border-strong); }
.key-star { color: var(--goal, var(--foreground)); font-size: 12px; }

/* The star on a task you called important yourself. A different color from
   the key-task star in front of the name, because they mean different things
   and a row can carry both. */
.task-important { color: var(--warning-foreground); font-size: 11px; flex: none; }

/* The ghost line and the field it turns into have to be the same row - same
   height, same left edge, same size of type - or the column jumps when you
   click it and jumps back when you press Escape. The 18px matches .task-row,
   so the + lands directly under the checkboxes above it. */
.add-task, .add-task-open {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 4px 0 4px 18px;
  border: 0; background: transparent; color: var(--muted);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.add-task:hover, .add-task:hover .add-task-plus { color: var(--ink); }

.add-task-plus {
  width: 16px; height: 16px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-xs);
  background: var(--secondary); color: var(--muted-fg);
  font-size: 12px; line-height: 1;
}

/* No box and no border: the only thing that should change when you click the
   ghost line is that a caret appears in it. */
.add-task-field {
  flex: 1; min-width: 0;
  border: 0; background: transparent; padding: 0;
  font: inherit; color: var(--foreground); outline: none;
}
.add-task-field::placeholder { color: var(--muted); }
/* "When do you want this done?", asked on the row itself. Quiet until you
   touch it — it arrives already answered, so it should read as a date you can
   change rather than a second field to fill in. */
.add-task-due {
  flex: 0 0 auto; width: 7.5rem;
  border: 0; background: transparent; padding: 0;
  font: inherit; font-size: 12px; color: var(--muted); outline: none;
  text-align: right;
}
.add-task-due:hover, .add-task-due:focus { color: var(--foreground); }

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

.day-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 10px;
}
.day-head h2 { margin: 0; font-size: 18px; }
/* "Today" and the date it stands for, read as one line. Baselines rather than
   centers, so the small gray date sits on the same line the heading is
   written on instead of floating half a letter above it. */
.day-head-left { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.day-date-beside { white-space: nowrap; }
.day-nav { display: flex; gap: 4px; align-items: center; }

/* The hour labels straddle the line they name, which means the first one sits
   above the top of the grid - and the grid clips what overflows it, so the
   first hour of every day was drawn with its top half sliced off. The whole
   grid is nudged down by half a label to make room for it, and the two things
   positioned against the grid rather than flowing inside it - the blocks and
   the now-line - are nudged with it. */
:root { --grid-top: 9px; }

.calendar {
  position: relative;
  padding-top: var(--grid-top);
  border-top: 1px solid var(--line);
  overflow: hidden;
}
/* The hour line starts where the numbers stop.
   It used to be a border on the row itself, which runs the full width - so
   every line was drawn straight through the middle of the hour it was naming.
   Apple's and Google's both stop at the gutter, and the reason is legibility:
   a number with a rule through it is a number you read twice. */
.hour-row {
  position: relative;
  height: var(--hour);
}
.hour-row::after {
  content: ""; position: absolute; left: 56px; right: 0; bottom: 0;
  height: 0; border-top: 1px solid var(--line);
}
.hour-label {
  position: absolute; left: 0; top: -8px;
  width: 46px; text-align: right;
  color: var(--muted-fg); font-size: 11px; font-variant-numeric: tabular-nums;
}

/* Time already spent, washed out. The grid rows sit on top of it, so hovering
   an hour in the past still lights up the way any other hour does - the wash
   says "gone", it does not say "off limits". */
.time-gone {
  position: absolute; left: 56px; right: 0; top: var(--grid-top);
  background: var(--gone); pointer-events: none;
}
.hour-hit {
  position: absolute; left: 56px; right: 0; top: 0; bottom: 0;
  cursor: pointer;
}
/* No fill on hover. Every hour you passed the mouse over lit up, which made
   the day flicker as you read it. The small + is enough to say the hour can
   be clicked. */
.hour-hit::after {
  content: "+";
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  /* Was --faint, which against a white hour is very nearly nothing. Clicking
     an hour is the quickest way to put something on the day and this mark is
     the only thing that says so, so it is worth being able to see. */
  color: var(--muted-fg); opacity: 0; font-size: 15px; line-height: 1;
}
.hour-hit:hover::after { opacity: 1; }
/* Dragging a task from the goals column onto the day. */
.hour-hit.drop-target { background: var(--info-soft); }
/* Not only tasks: a goal's own name row and its milestone drag onto the day
   as well, and each of those books an hour of that goal. Named row by row
   rather than as a bare [data-dragging] - the body carries that same mark
   while a drag is in the air, and a bare selector would fade the whole page
   to 40% every time you picked something up. */
.task-row[data-draggable="true"],
.goal-row[data-draggable="true"],
.milestone-row[data-draggable="true"] { cursor: grab; }
.task-row[data-dragging="true"],
.goal-row[data-dragging="true"],
.milestone-row[data-dragging="true"] { cursor: grabbing; opacity: .4; }

/* While a task is in the air the calendar is one big drop surface. Without
   this, blocks and events already on the grid intercept the pointer and the
   hour underneath never hears about it - which is why the preview only showed
   up over empty stretches of the day. */
body[data-dragging="true"] .block,
body[data-dragging="true"] .blocks-layer,
body[data-dragging="true"] .week-blocks { pointer-events: none; }
/* And nothing gets highlighted on the way past. Dragging a goal's name is
   dragging a line of text, and without this the browser selects half the
   column behind it. */
body[data-dragging="true"] { user-select: none; }

/* The block you are about to make. Dashed and see-through, so it never reads
   as something already booked - and never in the way of the pointer. */
.drag-ghost {
  pointer-events: none;
  border-style: dashed !important;
  z-index: 20;
  /* Its own fill, because the colored backgrounds are keyed off data-kind and
     the ghost has no kind - it is not a block yet. Left transparent it was
     technically on screen and invisible on top of anything already booked. */
  background: var(--goal-fill, var(--neutral-100)) !important;
  border-color: var(--goal-edge, var(--faint)) !important;
  border-width: 2px !important;
  box-shadow: 0 2px 8px rgb(0 0 0 / .18);
}
.drag-ghost .block-title { color: var(--goal, var(--ink)); font-weight: 600; }
.drag-ghost .block-time { color: var(--goal, var(--faint)); opacity: .8; }

/* Follows the cursor from the first pixel a drag actually starts - every
   moment a real hour slot's own grid-snapped ghost (above) isn't the one
   doing the job yet. Same colored-fill-plus-edge language as a block itself,
   just pill-shaped and small, since there's no shape to preview until
   something is actually under the pointer. */
.drag-chip {
  position: fixed; top: 0; left: 0; z-index: 30;
  display: flex; align-items: center; gap: 7px;
  padding: 6px 12px 6px 8px;
  border-radius: var(--radius-full);
  background: var(--goal-fill, var(--secondary));
  border: 1.5px solid var(--goal-edge, var(--border-strong));
  box-shadow: 0 3px 10px rgb(0 0 0 / .16);
  font-size: 13px; font-weight: 600; color: var(--goal, var(--foreground));
  pointer-events: none; white-space: nowrap;
}
.drag-chip-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--goal, var(--muted-fg));
}
.drag-chip-title { max-width: 220px; overflow: hidden; text-overflow: ellipsis; }

.blocks-layer { position: absolute; left: 56px; right: 0; top: var(--grid-top); bottom: 0; pointer-events: none; }

.block {
  position: absolute; left: 0; right: 8px;
  border-radius: var(--r-block);
  padding: 6px 9px;
  overflow: hidden;
  pointer-events: auto;
  cursor: pointer;
  border: 1px solid transparent;
}
.block .block-title { font-weight: 600; font-size: 13px; line-height: 1.2; }
.block .block-time { font-size: 12px; color: var(--muted); margin-top: 1px; }

/* Every other clickable surface on the grid (.hour-hit, .week-slot,
   .month-cell) answers a hover before you commit to the click; the block
   itself was the one thing on the page that didn't, keyboard focus included -
   it wasn't even in the tab order. */
.block:hover { border-color: var(--goal, var(--border-strong)); }
.block:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }

/* A strip along the bottom edge, not a grip that's always visible - the
   cursor and the grip line only show up once you're actually over the edge,
   the way a spreadsheet's column border does. Drag it to change how long the
   block is instead of opening the block to type a new end time. */
/* A strip at each end of a block, for dragging that end up or down. The body
   between them drags the whole block instead - see armBlockDrag in today.js. */
.block-grab {
  position: absolute; left: 0; right: 0;
  height: 10px; cursor: ns-resize; touch-action: none;
}
.block-grab-top { top: 0; }
.block-grab-bottom { bottom: 0; }
.block-grab::after {
  content: ""; position: absolute; left: 50%;
  width: 26px; height: 3px; border-radius: var(--radius-full);
  background: currentColor; opacity: 0;
  transform: translateX(-50%);
  transition: opacity var(--dur) var(--ease);
}
.block-grab-top::after { top: 1px; }
.block-grab-bottom::after { bottom: 1px; }
/* Both grips show whenever the pointer is anywhere on the block. You used to
   have to find the strip before you could see there was one to find. */
.block:hover .block-grab::after { opacity: .35; }
.block-grab:hover::after { opacity: .7; }

/* The middle of a block is a handle too. */
.block[data-dragging="true"] { cursor: grabbing; z-index: 6; }
.block[data-dragging="true"] .block-grab::after { opacity: .7; }

/* A short block cannot hold two lines. On a phone, where the whole day is
   squeezed to fit one screen, a half-hour meeting is about twenty pixels tall
   and was showing its title and then half of its start time underneath - which
   reads as a rendering fault rather than as a small block. The time goes; the
   title is the part you need, and the block's own position already says when
   it is. Measured against the block itself, so it happens at whatever size the
   grid has ended up at rather than at a guessed breakpoint. */
.block { container-type: size; }
@container (max-height: 38px) {
  .block .block-time { display: none; }
  .block { padding: 3px 9px; }
}
@container (max-height: 24px) {
  .block .block-title { font-size: 11.5px; }
  .block { padding: 2px 7px; }
}
/* A block too short to hold a line of text AND the padding above and below it.
   On a phone a 45-minute block is 19 pixels tall; 12 of those were padding and
   2 were border, leaving 5 for text that needs 12 - so the words came out
   sliced across the middle, showing the top half of the letters.

   The padding has to come off, and it cannot be taken off here: the two
   @container rules above try to, and an element is not allowed to query its
   own container, so every `.block { ... }` inside them has silently never
   applied. Only the title's own rules were ever doing anything.

   So the title takes the padding back, from the inside, with a negative margin
   the same size. Same result, and it is reachable from where the query works. */
@container (max-height: 22px) {
  .block .block-title {
    font-size: 11px;
    line-height: 1.1;
    margin: -6px -2px;
  }
}

/* Goal-linked: solid muted fill in the goal color, colored title. */
.block[data-kind="goal"] .block-title { color: var(--goal); }
.block[data-kind="goal"] { background: var(--goal-fill); border-color: var(--goal-edge); }
.block[data-kind="goal"] .block-time { color: var(--goal); opacity: .75; }

/* Everything else: dashed border, no fill, gray text. Stark on purpose - a day
   of gray dashes should read as a day where nothing moved a goal forward. */
.block[data-kind="other"], .block[data-kind="external"] {
  background: transparent;
  border: 1px dashed var(--line-strong);
  color: var(--muted);
}
.block[data-kind="other"] .block-title, .block[data-kind="external"] .block-title {
  color: var(--muted); font-weight: 500;
}
.block[data-done="true"] { opacity: .55; }
.block[data-done="true"] .block-title { text-decoration: line-through; }
.block .key-star { position: absolute; right: 8px; top: 6px; }

/* The wrap-up's answer, drawn on the day itself. Missed fades back and is
   struck through, so a week of what was written and a week of what happened
   can never be mistaken for each other at a glance. */
.block[data-actual="missed"] { opacity: .55; }
.block[data-actual="missed"] .block-title { text-decoration: line-through; }
.block[data-actual="missed"] .block-instead {
  font-size: 12px; font-style: italic; color: var(--muted);
  text-decoration: none; margin-top: 1px;
}
/* Partly done: the booking stays exactly where it was, with the honest number
   under it. Nothing fades - the hour was used, just not all of it. */
.block[data-actual="partial"] .block-instead {
  font-size: 12px; color: var(--muted); margin-top: 1px;
}
.block[data-actual="did"]::after {
  content: "✓"; position: absolute; right: 8px; bottom: 5px;
  font-size: 11px; color: var(--muted);
}

.now-line { position: absolute; left: 46px; right: 0; height: 0; z-index: 5; pointer-events: none; margin-top: var(--grid-top); }
.now-line::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0;
  border-top: 1.5px solid var(--now);
}
.now-line::after {
  content: ""; position: absolute; left: -3px; top: -3.5px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--now);
}
/* The dot said "here" but not "when" - the reference's own now-line carries
   the literal current time. Anchored left, overlapping the hour-label gutter
   the same way theirs does. */
.now-line-time {
  position: absolute; left: -46px; top: -8px;
  min-width: 40px; padding: 1px 5px;
  border-radius: var(--radius-full);
  background: var(--now); color: var(--on-now, #fff);
  font-size: 10px; font-weight: 600; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ right pane */

.stat-line { display: flex; justify-content: space-between; align-items: baseline; padding: 3px 0; }
.stat-line .label { color: var(--ink-2); }
.stat-line .value { font-weight: 600; font-variant-numeric: tabular-nums; }

.big-number { font-size: 30px; font-weight: 700; letter-spacing: -.02em; line-height: 1.1; }
.big-number small { font-size: 13px; font-weight: 500; color: var(--muted); margin-left: 4px; }

.sentence {
  font-size: 14px; line-height: 1.5; color: var(--ink);
  border-left: 2px solid var(--ink); padding-left: 10px; margin: 10px 0 0;
}
.sentence.warn { border-left-color: var(--now); }

.divider { height: 1px; background: var(--line); margin: 12px 0; }

.goal-hours { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 13px; }
.goal-hours .name {
  flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: color-mix(in srgb, var(--goal, var(--foreground)) 72%, var(--foreground));
  font-weight: 500;
}
.goal-hours .nums { font-variant-numeric: tabular-nums; color: var(--ink-2); }
.goal-hours .short { color: var(--now); font-size: 16px; line-height: 1; }

.meter {
  height: 5px; border-radius: var(--radius-full); overflow: hidden; margin-top: 3px;
  background: var(--goal-fill, var(--neutral-200));
}
.meter > span { display: block; height: 100%; border-radius: 999px; }

.progress-squares { display: flex; gap: 3px; margin-top: 6px; flex-wrap: wrap; }
.progress-squares i { width: 12px; height: 12px; border-radius: var(--radius-xs); background: var(--goal-fill, var(--neutral-200)); }
.progress-squares i.on { background: var(--goal, var(--primary)); }

.quiet-note { color: var(--muted); font-size: 13px; }
.quiet-note.flag { color: var(--now); }

/* ------------------------------------------------------------------ week grid */

.week-grid {
  display: grid;
  grid-template-columns: 52px repeat(7, minmax(0, 1fr));
  border-top: 1px solid var(--line);
  position: relative;
  /* Seven days below about 88px each stop being readable - "Prayer and
     reading" comes out as four stacked words - so the grid holds this width
     and .week-scroll takes over. There was already a rule saying exactly this
     under a narrow-viewport media query, which never fired on a laptop: the
     squeeze comes from the middle column being 588px of a 1280px window, not
     from the window. A wide screen is past this number and never notices it. */
  min-width: 620px;
}
.week-daycol { border-left: 1px solid var(--line); position: relative; }

/* Where you are in the week, said with tone rather than with a label.
   Days already behind you are washed flat; today keeps the full white ground
   it always had, which is what makes it the one column that stands out. */
.week-daycol.past .week-slots { background: var(--gone); }

/* A fixed height, not whatever the contents come to. The eight headings - the
   gutter's blank one and the seven days - have to be exactly the same height
   or the columns underneath them start at different places, and everything
   positioned down the grid is out by the difference. Today's date sits in a
   filled circle, which is two pixels taller than plain text, and that was
   enough to put its whole column out of step with the hour labels. */
.week-dayhead {
  display: flex; align-items: center; gap: 5px;
  height: 31px; padding: 0 8px; font-size: 12px; font-weight: 600;
  white-space: nowrap; overflow: hidden;
  /* Pinned to the top of the grid's own scroll box, not to the top bar. It
     used to be offset by the top bar's height, which put it 66px down the
     scroll box and left it covering the first half hour of every day - which
     nobody noticed until the anchors put breakfast and the gym down there. */
  border-bottom: 1px solid var(--border); position: sticky; top: 0;
  /* Above the now line and its time pill as well as the blocks: a heading you
     can see through to a red line underneath reads as a mistake. */
  background: var(--card-bg); z-index: 8;
}
.week-dayhead span { color: var(--muted); font-weight: 500; }

/* Today's date in a filled red circle, the way every calendar you have ever
   used marks it. The weekday beside it goes to full strength; a day already
   gone goes quiet. */
.week-dayhead.today { color: var(--ink); }
.week-dayhead.today span {
  display: inline-block; min-width: 18px; padding: 1px 5px;
  border-radius: var(--radius-full);
  background: var(--now); color: var(--on-now, #fff); font-weight: 600; text-align: center;
}
.week-dayhead.past { color: var(--muted); }
.week-dayhead.past span { color: var(--faint); }

.week-hourcol { border-right: 1px solid var(--line); }
/* Half a label of headroom, so the first hour is not sliced off by the day
   headings above it - the same nudge the day grid makes. */
.week-hours { position: relative; padding-top: var(--grid-top); }
.week-slots { position: relative; padding-top: var(--grid-top); }
.week-slot { height: var(--hour); border-bottom: 1px solid var(--line); cursor: pointer; position: relative; }

/* One label per hour, exactly as tall as the hour it names, sitting astride
   the line rather than in the gap between two of them. It used to be a flat
   34px while the slots beside it were 62 - which meant the labels and the
   lines drifted further apart down the page until "5p" was pointing at two in
   the afternoon. */
.week-hour-label {
  height: var(--hour);
  font-size: 11px; line-height: 1; color: var(--muted-fg);
  text-align: right; padding-right: 8px;
  font-variant-numeric: tabular-nums;
  /* Transform, not margin: it moves the text onto the line without moving the
     box, so the label below it stays exactly one hour further down. */
  transform: translateY(-5px);
}

/* Now, across the week. A hairline everywhere so the hour reads straight
   across all seven columns, solid with a dot on today. */
.week-now {
  position: absolute; left: 0; right: 0; height: 0; z-index: 4;
  pointer-events: none;
  border-top: 1px solid color-mix(in srgb, var(--now) 30%, transparent);
}
.week-now.here { border-top: 1.5px solid var(--now); z-index: 6; }
.week-now.here::after {
  content: ""; position: absolute; left: -3px; top: -4px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--now);
}
/* The time itself, in the gutter, in the same red pill the day grid uses. */
.week-now-time {
  position: absolute; right: 4px; z-index: 6;
  transform: translateY(-50%);
  padding: 1px 5px; border-radius: var(--radius-full);
  background: var(--now); color: var(--on-now, #fff);
  font-size: 10px; font-weight: 600; font-variant-numeric: tabular-nums;
  pointer-events: none;
}
/* Today's hours already spent. Behind the slots, so a hover still shows. */
.week-gone {
  position: absolute; left: 0; right: 0; top: var(--grid-top);
  background: var(--gone); pointer-events: none;
}

.week-blocks { position: absolute; left: 0; right: 0; bottom: 0; top: var(--grid-top); pointer-events: none; }
.week-blocks .block { right: 2px; left: 2px; padding: 3px 5px; }
.week-blocks .block .block-title { font-size: 11px; }
.week-blocks .block .block-time { display: none; }
/* A task is picked out and waiting for a slot: every click has to reach the
   grid underneath, or the hours an anchor covers become the one part of the
   week you cannot drop anything on. */
.week-blocks.placing .block { pointer-events: none; }

/* Energy tags, carried from the wrap-up into the week view. Shape as well as
   color, so they read without it. */
/* Pushed to the far end of the heading. Not floated: the heading is a flex row
   now, so it needs a margin rather than a float to get over there. */
.energy-mark { margin-left: auto; font-size: 10px; font-style: normal; }
.energy-mark i { font-style: normal; margin-left: 3px; }
.energy-mark .up { color: var(--success-foreground); }
.energy-mark .down { color: var(--destructive-foreground); }

.energy-dot {
  position: absolute; right: 3px; bottom: 3px;
  width: 6px; height: 6px; border-radius: 50%;
}
.energy-dot.energising { background: var(--success-foreground); }
.energy-dot.draining { background: var(--destructive-foreground); }

.backlog-item {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px; border: 1px solid var(--border); border-radius: var(--radius-md);
  margin-bottom: 6px; cursor: grab; background: var(--card-bg);
  box-shadow: var(--shadow-xs);
}
.backlog-item:hover { border-color: var(--border-strong); }
.backlog-item[data-dragging="true"] { opacity: .4; }
.backlog-item .est { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.backlog-item.selected { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.week-slot.drop-target { background: var(--info-soft); }

.sparkline {
  display: flex; align-items: flex-end; gap: 3px; height: 34px;
  padding: 2px 0; border-bottom: 1px solid var(--line);
}
.sparkline span { flex: 1; max-width: 14px; background: var(--ink); border-radius: 2px 2px 0 0; }

/* ------------------------------------------------------------------ month */

.month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.month-dow {
  font-size: 11px; color: var(--muted); text-transform: uppercase;
  letter-spacing: .05em; padding-bottom: 4px;
}
.month-cell {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card-bg);
  min-height: 88px; padding: 6px 7px; cursor: pointer;
  display: flex; flex-direction: column; gap: 4px; position: relative;
  /* A day with many blocks still describes itself in two lines - the rest is
     a count, not a taller box, so the grid stays one shape across a month. */
  overflow: hidden;
}
.month-cell:hover { border-color: var(--border-strong); background: var(--secondary); }
.month-cell.empty { border: 0; cursor: default; }
.month-cell.today { border-color: var(--ink); }
.month-cell.selected { box-shadow: 0 0 0 1px var(--primary); }

/* Days already behind you, washed the same faint gray the week grid uses for
   the same reason: where you are in the month should be something you see, not
   something you count out. */
.month-cell.past { background: color-mix(in srgb, var(--gone), var(--card-bg)); }
.month-cell.past:hover { background: var(--secondary); }
.month-cell.past .month-daynum { color: var(--muted); }

.month-daynum { font-size: 12px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
/* Today's date in a filled red circle, the same mark the week grid puts on it.
   `align-self` so the circle is only as wide as the number, not the cell. */
.month-cell.today .month-daynum {
  align-self: flex-start;
  min-width: 19px; padding: 1px 5px;
  border-radius: var(--radius-full);
  background: var(--now); color: var(--on-now, #fff); font-weight: 600; text-align: center;
}
/* The top item or two, agenda-style: title only, tinted by the goal it
   serves, same --goal/--goal-fill convention the day peek uses. */
.month-items { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.month-item {
  font-size: 10.5px; line-height: 1.4;
  padding: 1px 5px; border-radius: 3px;
  background: var(--goal-fill, var(--neutral-100));
  border-left: 2px solid var(--goal, var(--line-strong));
  color: var(--goal, var(--ink-2));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.month-more { font-size: 10px; color: var(--muted); padding-left: 3px; }
/* How claimed the day is. Pinned to the foot of the cell so the bars line up
   across a row and the month reads as a shape at a glance, which is the one
   thing a month is for. Quiet on purpose: it is the background against which
   the named work is the figure, not a second thing competing to be read. */
.month-load {
  margin-top: auto; height: 4px; border-radius: 2px;
  background: var(--line); overflow: hidden;
}
/* --faint, not --line-strong: a track and a fill one step apart on the same
   gray is a bar you cannot see is a bar. */
.month-load > span { display: block; height: 100%; background: var(--faint); }
.month-wrap {
  position: absolute; right: 6px; top: 6px;
  width: 7px; height: 7px; border-radius: 50%; background: var(--faint);
}
.month-wrap.strong { background: var(--ink); }
.month-wrap.normal { background: var(--faint); }
.month-wrap.hard { background: transparent; border: 1px solid var(--faint); }

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

.wizard { max-width: 720px; margin: 0 auto; }
.wizard-steps { display: flex; gap: 6px; margin-bottom: 20px; }
.wizard-steps i {
  height: 3px; flex: 1; border-radius: 999px; background: var(--line);
}
.wizard-steps i.done { background: var(--ink); }
/* 27px, not the 23px this used to be - checked against the reference's own
   onboarding headlines and confirmed genuinely smaller, not just a different
   crop. Scoped to .wizard specifically: the shared .card padding stays at
   --card-pad everywhere else, since that same padding is also what a dense
   3-pane panel uses and has no room to loosen up. */
.wizard h1 { font-size: 27px; margin: 0 0 8px; }
.wizard .lede { color: var(--muted); margin: 0 0 18px; }
/* Wrap and a gap, because the row is not always two buttons. The sermon
   process card puts three along here and the check-in two long ones, and with
   nowrap they simply ran off the right of a phone - the Save among them. */
.wizard-actions {
  display: flex; justify-content: space-between; margin-top: 22px;
  flex-wrap: wrap; gap: 8px;
}
.wizard > .card { padding: 28px; }
/* The reference's own "Continue" is a taller, roomier button than the rest
   of the app's compact controls - but only here. Widening --h-lg itself
   would also grow every compact in-panel button that already reads right
   next to the reference's own equally-compact in-app buttons. */
.wizard-actions .btn { height: 44px; padding: 0 20px; font-size: 15px; }

/* ---- settings page: jump nav + group headers ----

   Same idea as the gear menu's group labels - small, uppercase, out of the
   way - but laid out for a page you scroll instead of a dropdown you close.
   The nav stays pinned under the topbar so reaching a section never means
   scrolling back up past everything else first. */
.settings-jumpnav {
  position: sticky; top: var(--topbar-h); z-index: 20;
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 0 0 18px; padding: 8px 0;
  background: var(--background);
}
.settings-group-label {
  margin: 28px 2px 4px;
  padding: 0;
  font-size: 10.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--subtle-fg);
  scroll-margin-top: calc(var(--topbar-h) + 48px);
}
.settings-group-label-first { margin-top: 0; }

.field { display: block; margin-bottom: 14px; }
.field > span { display: block; font-weight: 600; margin-bottom: 5px; }
.field > small { display: block; color: var(--muted-fg); font-weight: 400; margin-bottom: 5px; }
/* A hint nested inside a checkbox's label still needs its own line. */
label.field span > small {
  display: block; color: var(--muted); font-weight: 400; margin-top: 3px; line-height: 1.4;
}

/* Coss fields: same height as a button, same radius, same hairline. A field
   and the button next to it are two halves of one control strip. */
input[type="text"], input[type="number"], input[type="date"],
input[type="datetime-local"], input[type="time"], input[type="password"],
input[type="email"], input[type="search"], input[type="url"], textarea, select {
  width: 100%;
  height: var(--h-md);
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  padding: 0 11px;
  background: var(--card-bg);
  color: var(--foreground);
  box-shadow: var(--shadow-xs), inset 0 1px 0 var(--inset-top);
}
input::placeholder, textarea::placeholder { color: var(--subtle-fg); }
input:hover, textarea:hover, select:hover { border-color: var(--border-strong); }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}
input:disabled, textarea:disabled, select:disabled { pointer-events: none; }
input:disabled, textarea:disabled, select:disabled {
  background: var(--secondary); color: var(--muted-fg);
  cursor: not-allowed; box-shadow: none;
}
textarea {
  resize: vertical; min-height: 90px; height: auto;
  padding: 9px 11px; line-height: 1.5;
}
input[type="number"] { width: 88px; }

/* The OS draws its own dropdown arrow and it never matches anything. Ours
   is the same chevron the rest of the app uses. */
select {
  appearance: none; -webkit-appearance: none;
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23737373' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 14px;
}

.tier-columns { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.tier-col {
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 10px; min-height: 160px; background: var(--secondary);
}
.tier-col.over { border-color: var(--info); background: var(--info-soft); }
.tier-col h3 { margin: 0 0 2px; font-size: 13px; }
.tier-col .cap { font-size: 11px; color: var(--muted); margin-bottom: 8px; }
.tier-col.full .cap { color: var(--now); }
.candidate {
  background: var(--card-bg); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: 7px 9px; margin-bottom: 6px; cursor: grab;
  display: flex; align-items: center; gap: 7px;
  box-shadow: var(--shadow-xs);
}

.dump-pane {
  background: var(--secondary); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 12px; white-space: pre-wrap; color: var(--ink-2);
  max-height: 340px; overflow: auto;
}

.manifesto {
  white-space: pre-wrap;
  background: var(--secondary); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 16px; line-height: 1.6;
}

/* ------------------------------------------------------------------ 168 hours */

.remainder-bar {
  position: sticky; top: var(--topbar-h); z-index: 30;
  background: var(--primary); color: var(--primary-foreground);
  border-radius: var(--radius-lg); padding: 12px 16px;
  display: flex; align-items: baseline; justify-content: space-between;
  box-shadow: var(--shadow-md);
}
/* The intake is a card inside Settings now rather than a screen of its own, so
   this number stays with the questions it belongs to. Pinned under the top
   bar it would ride along over the Google and Todoist cards below, still
   announcing a figure about laundry. */
.remainder-bar.remainder-inline { position: static; box-shadow: var(--shadow-xs); }
.remainder-bar .n { font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; }
.remainder-bar .n.negative { color: color-mix(in srgb, var(--destructive) 50%, #fff); }
.remainder-bar .cap { opacity: .7; font-size: 13px; }

.section-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 18px; }
@media (max-width: 700px) { .section-grid { grid-template-columns: 1fr; } }

.mini-field { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.mini-field span { color: var(--ink-2); }

.delta-table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
.delta-table th, .delta-table td { text-align: right; padding: 7px 8px; border-bottom: 1px solid var(--border); }
.delta-table th {
  font-size: 11px; font-weight: 600; color: var(--muted-fg);
  text-transform: uppercase; letter-spacing: .05em;
}
.delta-table tbody tr:hover { background: var(--press-soft); }
.delta-table th:first-child, .delta-table td:first-child { text-align: left; text-transform: capitalize; }
.delta-table td.pos { color: var(--success-foreground); }
.delta-table td.neg { color: var(--destructive-foreground); }

/* ------------------------------------------------------------------ modal */

.modal-backdrop {
  position: fixed; inset: 0;
  background: var(--scrim);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; z-index: 90; padding: 20px;
  animation: dialog-fade 140ms var(--ease);
}
.modal {
  position: relative;
  background: var(--popover);
  border: 1px solid var(--border);
  border-radius: var(--radius-2xl);
  padding: 20px;
  width: min(520px, 100%); max-height: 85vh; overflow: auto;
  box-shadow: var(--shadow-xl);
  animation: dialog-in 200ms cubic-bezier(.4, 0, .2, 1);
}
.modal h2 { margin: 0 0 4px; font-size: 19px; }

/* The way out, in the corner every dialog on a computer keeps it. Ghosted
   until you point at it: a dialog is opened to do something, and a close
   button drawn as hard as the button that finishes the job invites you to
   take the exit by mistake. */
.modal-close {
  position: absolute; top: 14px; right: 14px;
  width: 24px; height: 24px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-sm);
  background: none; box-shadow: none;
  color: var(--muted-fg); cursor: pointer; line-height: 1;
}
.modal-close:hover { background: var(--secondary); color: var(--foreground); }
.modal-close:active { background: var(--press-strong); }
.modal-close svg {
  width: 14px; height: 14px;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
/* A long title would otherwise run underneath it. */
.modal:has(.modal-close) > h2 { padding-right: 30px; }
.modal .lede { color: var(--muted-fg); margin: 0 0 16px; }
/* A rule above the buttons, so the row that commits or cancels is visibly
   separate from the thing you were filling in. */
.modal-actions {
  display: flex; gap: 8px; justify-content: flex-end;
  margin-top: 20px;
}
@keyframes dialog-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes dialog-in {
  from { opacity: 0; transform: scale(.98); }
  to { opacity: 1; transform: none; }
}

.choice-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  border: 1px solid var(--input); background: var(--card-bg);
  border-radius: var(--radius-lg); padding: 10px 12px; margin-bottom: 8px;
  cursor: pointer; text-align: left; box-shadow: var(--shadow-xs);
}
.choice-row:hover { border-color: var(--border-strong); background: var(--secondary); }
.choice-row .dot { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.choice-row .sub { color: var(--muted); font-size: 12px; }

/* The single-answer version: one line, a radio in front of it, and no shadow.
   The plain choice row is a card you press and something happens; this is one
   of a set where picking any of them drops the others, and it has to look
   like a list rather than like five separate buttons stacked up. Flat, thin,
   evenly spaced, and the circle in front does the explaining.

   The height is a minimum rather than a fixed size, so an answer too long for
   one line grows its row instead of hiding its own second half. */
.choice-row--compact {
  gap: 12px;
  min-height: 49px;
  padding: 12px 14px;
  /* --border reads as borderless at normal size - --input is the same
     darker, alpha-based line the plain .choice-row uses, just without the
     shadow this variant deliberately drops. */
  border-color: var(--input);
  box-shadow: none;
  font-size: 14px;
}
.choice-row--compact:hover { background: var(--secondary); }
/* Chosen is said by the radio, so the row only has to stop looking optional. */
.choice-row--compact:has(.radio[aria-checked="true"]) { border-color: var(--primary); }

/* The last row of the set, where the answer is one you type. The field inside
   is stripped back to plain text so the row stays one object: a bordered box
   sitting inside another bordered box is two questions where there is one. */
.choice-row--other { cursor: text; }
.choice-row--other input[type="text"],
.choice-row--other input[type="search"] {
  flex: 1; min-width: 0;
  height: auto; padding: 0;
  border: 0; background: none; box-shadow: none;
  font-size: 14px;
}
.choice-row--other input:focus-visible { outline: none; }
/* The row carries the focus instead, because the row is what you are on. */
.choice-row--other:focus-within { border-color: var(--primary); }

/* ------------------------------------------------------------------ timer */

/* A running session used to be a modal with a 54px centered clock, big
   enough to cover the calendar it was reporting on. It lives in the right
   column now, the same width as the inspector beside it, so the clock is
   sized and left-aligned for that column instead of for a page to itself -
   and takes the same goal-less "this is active now" accent (--now, the
   now-line's own red) the inspector takes from the goal it is showing. */
.timer-card { border-left: 3px solid var(--now); animation: inspector-in var(--dur) var(--ease); }
.timer-face { text-align: left; padding: 6px 0 2px; }
.timer-face .clock {
  font-size: 36px; font-weight: 700; font-variant-numeric: tabular-nums;
  letter-spacing: -.02em; line-height: 1;
}
.timer-face .paused { color: var(--muted); }
.timer-actions { display: flex; gap: 8px; margin-top: 14px; }
.timer-actions .btn { flex: 1; }

.timer-strip {
  display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--primary); border-radius: var(--radius-lg);
  padding: 10px 14px; margin-bottom: 14px;
  background: var(--card-bg);
  box-shadow: var(--shadow-sm);
}
.timer-strip .clock { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.timer-strip .what { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ------------------------------------------------------------------ wrap-up */

/* The reality check: one row per thing the calendar said, one key each. */
.check-row { padding: 8px 0; border-bottom: 1px solid var(--line); }
.check-row[data-cursor="true"] { background: var(--secondary); }
.check-row .line { display: flex; align-items: center; gap: 10px; }
.check-row .when {
  color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums;
  min-width: 46px; flex: none;
}
.check-row .what { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.check-row .keys { display: flex; gap: 6px; flex: none; }
.check-row .keys button[aria-pressed="true"] {
  background: var(--primary); color: var(--primary-foreground); border-color: var(--primary);
}
.check-row[data-outcome="missed"] .what { text-decoration: line-through; color: var(--muted); }
/* The "what happened instead" line only exists once something was missed. */
.check-row .instead { display: none; width: 100%; margin: 6px 0 2px; }
.check-row[data-outcome="missed"] .instead { display: block; }

/* Yesterday, offered in the morning. One line until it is asked for. */
.morning-strip {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--input); border-radius: var(--radius-lg);
  padding: 8px 10px 8px 14px; margin-bottom: 12px;
  background: var(--card-bg); box-shadow: var(--shadow-xs);
}
.morning-strip .what { flex: 1; font-size: 14px; }

/* Last night, at the top of the review. */
.bed-line {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.bed-line .what { flex: 1; }
.bed-line .bed-time { width: 120px; flex: none; }

/* Naming the week's two or three big things. */
.pick-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; margin-bottom: 6px; cursor: pointer;
  border: 1px solid var(--input); border-radius: var(--radius-lg);
  background: var(--card-bg);
}
.pick-row .what { flex: 1; }
.pick-row[data-chosen="true"] { border-color: var(--primary); background: var(--secondary); }
.pick-row[data-chosen="true"] .what { font-weight: 600; }
.pick-check {
  flex: none; width: 16px; height: 16px; opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.pick-check svg {
  width: 100%; height: 100%;
  fill: none; stroke: var(--primary); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.pick-row[data-chosen="true"] .pick-check { opacity: 1; }

/* Booked against spent, one row per thing the hours were for. */
.spent-row { margin-bottom: 10px; }
.spent-row .goal-dot { margin-right: 7px; }

/* How long it actually took. */
.actual-line { display: inline-flex; align-items: center; gap: 6px; }
.actual-mins { width: 76px; }

/* Where a missed thing goes now. */
.check-row .tail {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 6px 0 2px;
}
.check-row .tail:empty { display: none; }

.energy-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.energy-row .what { flex: 1; }
.energy-row .keys { display: flex; gap: 6px; }
.energy-row .keys button[aria-pressed="true"] {
  background: var(--primary); color: var(--primary-foreground); border-color: var(--primary);
}
/* Coss's is a flat chip, not a molded keycap. */
.kbd {
  display: inline-block; min-width: 20px; height: 20px; line-height: 20px;
  text-align: center; padding: 0 5px;
  border: 0; border-radius: var(--radius-sm);
  background: var(--secondary); color: var(--muted-fg);
  font-family: var(--font); font-size: 11px; font-weight: 500;
}

/* ------------------------------------------------------------------ toast */

.toast-host { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 120; }
.toast {
  display: flex; align-items: center; gap: 9px;
  background: var(--popover); color: var(--foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 10px 14px; margin-top: 8px;
  box-shadow: var(--shadow-lg); max-width: 460px;
  animation: toast-in 160ms var(--ease);
}
/* A colored dot rather than a colored panel: the words stay readable and
   the app keeps one toast shape whatever it is telling you. */
.toast::before {
  content: ""; flex: none;
  width: 7px; height: 7px; border-radius: var(--radius-full);
  background: var(--success);
}
.toast.error { border-color: var(--destructive-edge); }
.toast.error::before { background: var(--destructive); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* Coss's Empty is a centered stack with real air around it, not a heading
   with a paragraph under it. */
.empty-state {
  display: flex; flex-direction: column; align-items: center;
  gap: 10px; text-align: center; padding: 56px 24px;
}
.empty-state h1 { font-size: 20px; margin: 0; }
.empty-state p { color: var(--muted-fg); max-width: 24rem; margin: 0 0 8px; }

/* The smaller cousin, for a hole inside a screen rather than a whole screen
   with nothing on it. A dashed edge, because the point is that something is
   meant to go here and has not yet - a solid border draws a finished box and
   makes an empty list look like a deliberately empty list. Two lines only:
   what is missing, and the one thing to do about it. */
.empty-slot {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; text-align: center;
  padding: 32px 24px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--secondary);
}
.empty-slot > :first-child { font-weight: 600; color: var(--foreground); }
.empty-slot p, .empty-slot .hint { margin: 0; color: var(--muted-fg); }

/* ---- the interview ---- */
.day-toggles { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
/* Loose in the page rather than in a track, so these get a real border. */
.day-toggles .pill {
  border: 1px solid var(--input); background: var(--card-bg);
  box-shadow: var(--shadow-xs); border-radius: var(--radius-md);
}
.day-toggles .pill:hover { background: var(--secondary); border-color: var(--border-strong); }
.day-toggles .pill.active {
  background: var(--primary); border-color: var(--primary); color: var(--primary-foreground);
}

.rhythm-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
.rhythm-table th, .rhythm-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); text-align: left; }
.rhythm-table th {
  font-size: 11px; color: var(--muted-fg); font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
}
.rhythm-table tbody tr:hover { background: var(--press-soft); }
.rhythm-table td.num, .rhythm-table th:nth-child(4) { text-align: right; font-variant-numeric: tabular-nums; }
.rhythm-table td:first-child { width: 45%; }

/* An invisible border makes a field look like plain text, and then nobody
   knows what they are allowed to type in. Editable things look editable. */
.flat-input {
  width: 100%; height: auto;
  border: 1px solid var(--input); background: var(--card-bg);
  padding: 4px 7px; border-radius: var(--radius-sm);
  font: inherit; color: var(--foreground);
  box-shadow: none;
}
.flat-input:hover { border-color: var(--border-strong); }
.flat-input:focus { border-color: var(--ring); outline: none; box-shadow: var(--ring-shadow); }
.flat-input.num { text-align: right; }

/* Calculated, not typed. Reads as output so it is not mistaken for a field. */
.rhythm-table td.num:not(:has(.flat-input)) { color: var(--muted); }

/* Hours, day by day */
.day-hours-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.day-hours-row:last-child { border-bottom: 0; }
.day-hours-label { width: 104px; flex: none; padding-top: 5px; font-weight: 600; }
.day-hours-label .quiet-note { font-weight: 400; font-size: 11px; }
.window-row { display: flex; align-items: center; gap: 6px; margin-bottom: 5px; }
.window-row input[type="time"] { width: auto; height: var(--h-sm); padding: 0 8px; }

.plain-list { list-style: none; padding: 0; margin: 8px 0 0; }
.plain-list li { padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 14px; }

/* ---- the year ---- */
.year-chart {
  display: flex; align-items: flex-end; gap: 3px;
  height: 96px; margin: 14px 0 10px; overflow-x: auto; padding-bottom: 10px;
}
.year-col { display: flex; flex-direction: column; align-items: center; gap: 3px; flex: none; }
.year-bar {
  width: 14px; height: 76px;
  display: flex; align-items: flex-end;
}
.year-bar > span {
  width: 100%; border-radius: 3px 3px 0 0;
  background: var(--line-strong);
}
.year-bar.heavy > span { background: #d8a24a; }
.year-bar.brutal > span { background: var(--now); }
.year-bar.off > span { background: #bcd9c4; }
.year-bar.now > span { outline: 2px solid var(--ink); outline-offset: 1px; }
.year-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ink); }

.demo-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap;
  background: var(--warning-soft); border: 1px solid var(--warning-edge);
  border-radius: var(--r-card);
  padding: 9px 14px; margin-bottom: 14px;
  font-size: 13px; color: var(--warning-foreground);
}
.demo-banner strong { color: var(--warning-foreground); }

/* ---- the linear planner: months down the side, days across ---- */
.planner-scroll { overflow-x: auto; margin: 12px 0 10px; padding-bottom: 6px; }
/* Thirty-seven day columns, each one already free to shrink. This floor is the
   point below which the day numbers stop being readable - about 18px a column
   plus the hairlines between them - and nothing more.

   It was 900px, which is wider than the column the year is drawn in on a
   1440px laptop by 56 pixels. So the one screen that exists to show a whole
   year at once could not show a whole month without being dragged sideways,
   and it missed by less than an inch. */
.planner { min-width: 700px; }

/* Each month is one banded row, so a bar is unmistakably attached to the
   month above it rather than floating between two. */
.planner-row { display: flex; align-items: flex-start; gap: 4px; padding: 3px 0; }
.planner-row + .planner-row { border-top: 1px solid var(--line); }
.planner-row:nth-child(even) { background: var(--secondary); }
.planner-head { background: transparent !important; padding-bottom: 0; }

/* A button, because it opens that month - but it must not look like one, or
   twelve buttons down the side would shout over the planner itself. */
.planner-monthlabel {
  flex: none; width: 46px;
  display: flex; align-items: baseline; gap: 4px;
  padding: 2px 0 0; margin: 0;
  border: 0; background: none; font: inherit; color: inherit;
  text-align: left; cursor: pointer;
}
.planner-monthlabel:hover strong { text-decoration: underline; }
.planner-monthlabel strong { font-size: 12px; }

/* Days and bars are two stacked 37-column grids, so a bar lines up with its
   days without ever covering the numbers. */
.planner-body { flex: 1; min-width: 0; }
.planner-days {
  display: grid;
  grid-template-columns: repeat(37, minmax(0, 1fr));
  gap: 1px;
}

/* Every dated cell is a button - all 365 of them open that day. They must not
   look like buttons, or the planner stops being a picture of the year. */
.planner-cell {
  position: relative;
  height: 17px;
  border-radius: 2px;
  background: var(--card);
  border: 1px solid transparent;
  font-size: 9px;
  font-variant-numeric: tabular-nums;
  font-family: inherit;
  color: inherit;
  padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
}
button.planner-cell { cursor: pointer; }
button.planner-cell:hover { border-color: var(--ink); }
.planner-cell.blank { background: transparent; }
.planner-cell.head {
  height: 13px; background: transparent; color: var(--muted-fg); font-size: 9px;
}
.planner-cell.head.weekend { color: var(--ink-2); font-weight: 600; }
.planner-cell.weekend { background: var(--neutral-200); }
.planner-cell.dayoff { background: var(--neutral-300); }
.planner-cell.dayoff .planner-num { color: var(--ink-2); }
.planner-cell.today { border-color: var(--ink); font-weight: 700; }
.planner-num { color: var(--ink-2); }

.planner-sermon {
  position: absolute; bottom: 1px; left: 50%; transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--ink);
}
.planner-sermon.ready { background: var(--success-foreground); }

/* Bars sit under the day strip, one grid row per lane. */
.planner-bars {
  display: grid;
  grid-template-columns: repeat(37, minmax(0, 1fr));
  grid-auto-rows: 14px;
  gap: 1px;
  margin-top: 1px;
}
.planner-bar {
  height: 13px;
  border-radius: 3px;
  display: flex; align-items: center;
  padding: 0 5px;
  font-size: 9px; font-weight: 600;
  white-space: nowrap; overflow: hidden;
  opacity: .92;
}
.planner-bar.continues { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.planner-bar span { overflow: hidden; text-overflow: ellipsis; }
.planner-bar.off { background: #bfe0c8; color: #14532a; }
.planner-bar.leadup { background: #f3e2bd; color: #6b5300; }
.planner-bar.season { background: #e6bb76; color: #4d3300; }
.planner-bar.brutal { background: #e5a098; color: #5c1a13; }

.planner-key { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-top: 6px; }
.planner-key-item { display: flex; align-items: center; gap: 5px; }
.planner-swatch { width: 11px; height: 11px; border-radius: 3px; display: inline-block; }
.planner-swatch.off { background: #bfe0c8; }
.planner-swatch.leadup { background: #f3e2bd; }
.planner-swatch.season { background: #e6bb76; }
.planner-swatch.dayoff { background: var(--neutral-300); }
.planner-swatch.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ink); }

/* Wraps, because a row with a run-up on it does not fit. Six controls in a
   546px column left the "how heavy is this week" menu crushed to a 16px
   sliver with none of its words showing, and pushed the × off the end of the
   panel entirely - so the one span you most wanted to delete was the one you
   could not reach. */
.span-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 5px 0; border-bottom: 1px solid var(--line);
}
.span-row > input[type="text"] { flex: 1; min-width: 120px; }
.span-row > input[type="date"] { width: 140px; }
/* Enough to read the longest of the three answers, and no more: .flat-input
   is width:100%, so left alone on a wrapped line it takes the whole row. */
.span-row > select { flex: 0 1 auto; width: auto; min-width: 170px; }
.lead-field { display: flex; align-items: center; gap: 4px; color: var(--muted); font-size: 12px; }
.lead-field input { width: 48px; }

/* ---- annual targets ---- */
/* The top rung of the ladder, with the rungs below it nested inside. The
   indent is the whole point: it is what makes "this target has nothing under
   it" visible at a glance instead of something you have to count. */
.target-row {
  padding: 12px 0; border-bottom: 1px solid var(--line);
}
.target-row:last-of-type { border-bottom: none; }
.target-head { display: flex; align-items: center; gap: 8px; }
.target-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto;
  background: var(--neutral-300);
}
.target-title { flex: 1; min-width: 120px; font-weight: 600; }
.target-measure { margin-top: 6px; font-size: 13px; color: var(--muted); }
.target-goal {
  margin: 8px 0 0 18px; padding-left: 12px;
  border-left: 2px solid var(--line);
}
.target-goal-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.target-project {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; padding: 2px 0 2px 14px; font-size: 13px;
}

/* ---- the four quarters ---- */
/* Four columns on a laptop, one under another on a phone. auto-fit rather
   than a fixed four, because the middle column of the three-pane shell is
   546px on a laptop and four columns of goal titles in that is unreadable. */
.quarter-board {
  display: grid; gap: 10px; margin-top: 10px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.quarter-col {
  border: 1px solid var(--line); border-radius: 8px; padding: 10px;
  background: var(--surface, transparent);
}
.quarter-col.now { border-color: var(--primary); }
.quarter-col.past { opacity: 0.55; }
.quarter-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.quarter-note { margin: 6px 0 8px; }
.quarter-goal {
  display: flex; align-items: flex-start; gap: 6px;
  padding: 6px 0; border-top: 1px solid var(--line);
}
.quarter-goal-title { font-size: 13px; font-weight: 600; }
.quarter-picker { width: auto; flex: 0 0 auto; font-size: 12px; padding: 2px 4px; }
.quarter-holidays { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--line); }
.quarter-unplaced { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); }
.quarter-loose { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.quarter-loose-title { flex: 1; min-width: 0; font-size: 13px; }

/* ---- on track, or not ---- */
/* Words, not a color on its own: "3 weeks late" says what to do about it and
   a red dot does not. The color is the second signal, never the only one. */
.track-tag {
  flex: 0 0 auto; font-size: 11px; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--line-strong); color: var(--muted);
  white-space: nowrap;
}
.track-tag.ok { color: var(--success-foreground); border-color: currentColor; }
.track-tag.late { color: var(--warning-foreground); border-color: currentColor; }

/* ---- the nesting bubble ---- */
/* One dot, in the annual target's color, on every row that belongs to one.
   Small on purpose: it is a thing you match across screens out of the corner
   of your eye, not a thing you read. Hovering says the name. */
.target-bubble {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  display: inline-block; vertical-align: middle;
  background: var(--neutral-300);
}
/* A rung the thing does not reach, drawn hollow so the gap is what you see. */
.target-bubble.empty {
  background: transparent; border: 1px solid var(--line-strong);
}
.target-tag { display: inline-flex; align-items: center; gap: 5px; }
.target-tag-name { font-size: 12px; color: var(--muted); }
.chain-bubbles { display: inline-flex; align-items: center; gap: 3px; }
.proj-title { display: flex; align-items: center; gap: 6px; font-weight: 600; min-width: 0; }
.proj-title > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ---- the guided pass, in three columns ---- */
/* It used to be one centered column, which made it the only screen in the app
   that did not look like the app. Left is the steps, middle is the step you
   are on, right is the arithmetic and the help button. */
.setup-rail, .setup-mid, .setup-right { min-width: 0; }
.setup-rail { display: flex; flex-direction: column; gap: 2px; }
/* Every step draws its real editor inside the question now, rather than
   sending you off to another screen to answer it. One gap between the pieces,
   so editors written separately still read as one screen. */
.setup-editor { display: flex; flex-direction: column; gap: 12px; }
.setup-editor .field, .setup-editor .mini-field { margin-bottom: 0; }
.setup-step {
  display: flex; align-items: flex-start; gap: 10px; width: 100%;
  padding: 8px; border: 1px solid transparent; border-radius: 8px;
  background: none; text-align: left; font: inherit; color: inherit;
  cursor: pointer;
}
.setup-step:hover { background: var(--neutral-100, rgba(0, 0, 0, 0.03)); }
.setup-step.open { border-color: var(--primary); }
.setup-step.done .sitdown-title { opacity: 0.6; }
/* A step you have answered says so with a green check, and that is the only
   place the pass reports itself. The number it replaces is not missed. */
.setup-step.done .sitdown-mark {
  background: var(--success); border-color: var(--success); color: #fff;
}

/* ---- federal holidays ---- */
.holiday-list { margin-top: 8px; }
.holiday-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  width: 100%; padding: 4px 0; background: none; border: none; cursor: pointer;
  border-bottom: 1px solid var(--line); text-align: left; font: inherit;
  color: inherit;
}
.holiday-row:last-child { border-bottom: none; }
.holiday-row:hover { background: var(--neutral-100, rgba(0, 0, 0, 0.03)); }
.holiday-name { font-size: 13px; }
/* A dot in the corner of the day, not a fill: a holiday is a fact about the
   day, not a claim on it, and filling the cell would read as time booked. */
.planner-cell.holiday::after {
  content: ''; position: absolute; top: 2px; right: 2px;
  width: 4px; height: 4px; border-radius: 50%; background: var(--primary);
}

/* ---- sermons ---- */
.buffer-dots { display: flex; align-items: center; gap: 6px; margin: 12px 0 4px; }
.buffer-dot {
  width: 13px; height: 13px; border-radius: 50%;
  border: 1.5px solid var(--line-strong); background: transparent;
}
.buffer-dot.filled { background: var(--success-foreground); border-color: var(--success-foreground); }

/* The last track is minmax(0, auto), not auto: an `auto` track sizes to its
   contents and will not give any of it back, so a long step name on the Done
   button ran the whole action group - the delete included - off the right of
   the card. The middle column gives up a little first, then the button
   truncates. */
.sermon-row {
  display: grid;
  grid-template-columns: 92px minmax(120px, 1fr) auto 130px minmax(0, auto);
  align-items: center; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.sermon-actions { min-width: 0; }
.sermon-advance {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: block;
}
.sermon-row.behind {
  background: var(--destructive-soft); margin: 0 -8px; padding: 8px;
  border-radius: var(--radius-md);
}
.sermon-when { display: flex; flex-direction: column; line-height: 1.25; }
.sermon-main { display: flex; flex-direction: column; gap: 2px; }
.flat-input.small { font-size: 12px; color: var(--muted); }
.sermon-actions { display: flex; align-items: center; gap: 2px; }

.stage-track { display: flex; gap: 3px; }
.stage-pip {
  width: 15px; height: 6px; border-radius: 3px;
  background: var(--line);
}
.stage-pip.done { background: var(--ink); }
.stage-pip.due { outline: 1.5px solid var(--now); outline-offset: 1px; }

@media (max-width: 900px) {
  .sermon-row { grid-template-columns: 1fr; gap: 6px; }
  .span-row { flex-wrap: wrap; }
}

/* ------------------------------------------------------------------ reading */

.reading-row { border-bottom: 1px solid var(--line); }
.reading-row:last-of-type { border-bottom: 0; }
.reading-head { display: flex; align-items: center; gap: 9px; }
.reading-tick { width: 16px; height: 16px; flex: none; cursor: pointer; }
.reading-ref {
  display: flex; align-items: center; justify-content: space-between; gap: 7px;
  flex: 1; border: 0; background: transparent; cursor: pointer;
  padding: 8px 0; text-align: left; font-weight: 600; color: var(--ink);
}
.reading-ref:hover { color: var(--ink-2); }
.reading-ref.done { color: var(--muted); text-decoration: line-through; }
.reading-caret { color: var(--faint); font-size: 11px; flex: none; }

.reading-body { padding: 2px 0 10px 25px; }
/* A serif for scripture: it is the one place in the app that is being read
   rather than scanned. */
.reading-text {
  font-family: var(--font-serif);
  font-size: 14.5px; line-height: 1.6; color: var(--ink-2);
  white-space: pre-wrap; margin: 0 0 8px;
  max-height: 320px; overflow-y: auto;
}
.reading-open { display: inline-block; text-decoration: none; }
.reading-open:hover { color: var(--ink); }

.reading-check {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0; cursor: pointer;
}
.reading-check input { width: 16px; height: 16px; }

.card-head select.flat-input {
  width: auto; font-size: 12px; padding: 3px 22px 3px 7px;
  background-position: right 5px center; background-size: 12px;
}

/* ------------------------------------------------------------------ targets */

.target-row { padding: 10px 0; border-bottom: 1px solid var(--line); }
.target-row:last-child { border-bottom: 0; }
.target-row.closed { opacity: .55; }
.target-row.closed .target-title { text-decoration: line-through; }

.target-main { display: flex; align-items: flex-start; gap: 9px; }
.target-title { font-weight: 600; }
.target-right { display: flex; align-items: center; gap: 8px; flex: none; }
.target-row .meter { margin-top: 7px; margin-left: 19px; }

@media (max-width: 760px) {
  .target-main { flex-wrap: wrap; }
  .target-right { width: 100%; justify-content: space-between; padding-left: 19px; }
}

/* ------------------------------------------------------------------ phone

   Saved to a home screen this runs without browser chrome, so the layout has
   to stand on its own: panes stack, the calendar gets more room per hour
   because fingers are not cursors, and content clears the notch. */

@supports (padding: max(0px)) {
  .topbar {
    padding-left: max(20px, env(safe-area-inset-left));
    padding-right: max(20px, env(safe-area-inset-right));
  }
  main {
    padding-left: max(20px, env(safe-area-inset-left));
    padding-right: max(20px, env(safe-area-inset-right));
    padding-bottom: max(40px, env(safe-area-inset-bottom));
  }
}

@media (max-width: 760px) {
  :root { --hour: 74px; }

  main { padding: 12px 14px 40px; }
  .card { padding: 13px; border-radius: var(--radius-lg); }
  .card + .card { margin-top: 12px; }

  /* The date goes: it is on the day header anyway. */
  .topbar { grid-template-columns: auto 1fr auto; gap: 8px; padding: 10px 14px; }
  .date-label { display: none; }
  .navpills { padding: 2px; }
  .pill { padding: 8px 12px; min-height: var(--h-md); }
  .pill-icon { padding: 8px 11px; }
  .avatar-btn, .capture-btn, .talk-btn { width: var(--h-md); height: var(--h-md); }

  /* Touch targets. A 15px checkbox is a miss waiting to happen. */
  .btn { height: var(--h-lg); padding: 0 16px; }
  .btn-sm { height: var(--h-md); padding: 0 12px; }
  .btn-xs { height: var(--h-md); padding: 0 10px; }
  .btn-icon { width: var(--h-lg); }
  .btn-sm.btn-icon { width: var(--h-md); }
  .task-row { padding: 8px 0 8px 14px; }
  /* The box stays 22px so the row does not balloon; the padding around it
     is what your thumb actually hits. */
  .task-row .checkbox { width: 22px; height: 22px; }
  .task-row .checkbox, .reading-tick { padding: 5px; box-sizing: content-box; }
  .btn-ghost { height: var(--h-md); padding: 0 10px; }
  input[type="text"], input[type="number"], input[type="date"],
  input[type="datetime-local"], input[type="time"], input[type="password"],
  input[type="email"], input[type="search"], input[type="url"],
  select { height: var(--h-lg); }
  /* Beats .window-row input[type="time"], which otherwise keeps it at 30px. */
  .window-row input[type="time"] { height: var(--h-lg); }

  /* One column, and 16px inputs so iOS stops zooming the page on focus -
     that zoom is the most disorienting thing on a phone. */
  .section-grid, .tier-columns { grid-template-columns: 1fr; }
  input[type="text"], input[type="number"], input[type="date"],
  input[type="datetime-local"], input[type="time"], input[type="password"],
  input[type="email"], input[type="search"], input[type="url"],
  textarea, select { font-size: 16px; }
  input[type="number"] { width: 100%; }

  .wizard h1 { font-size: 20px; }
  .big-number { font-size: 26px; }

  /* The week's own minimum is set with the grid itself now, unconditionally -
     it was here, where it could only ever fire on a narrow window, and the
     squeeze that actually happens is a middle column too narrow inside a wide
     one. One column always fits, so the day panel is exempt: making it scroll
     would be a bug, not a compromise. */

  /* Hours, day by day: label above its rows rather than beside them. */
  .day-hours-row { flex-direction: column; gap: 6px; }
  .day-hours-label { width: auto; padding-top: 0; }

  .modal { padding: 16px; border-radius: var(--radius-xl); }
  .modal-backdrop { padding: 12px; align-items: flex-end; }
  /* The X stays 24px so it does not grow into the title; the padding around
     it is what a thumb actually lands on. Same trick as the task checkbox. */
  .modal-close { top: 8px; right: 8px; padding: 6px; box-sizing: content-box; }
  /* A row you tap rather than click, with room for a second line of answer. */
  .choice-row--compact { min-height: 52px; }
  /* Stripped of its border it is still a field, so it still needs the 16px
     that stops iOS zooming the page the moment it is tapped. */
  .choice-row--other input[type="text"],
  .choice-row--other input[type="search"] { font-size: 16px; }
  .radio { width: 22px; height: 22px; }
  .radio[aria-checked="true"] { box-shadow: inset 0 0 0 5px var(--primary); }
  .toast-host { bottom: max(16px, env(safe-area-inset-bottom)); width: calc(100% - 28px); }
  .toast { max-width: none; }

  .delta-table th, .delta-table td { padding: 5px 4px; font-size: 13px; }
  /* Still two lines, just smaller ones - a phone screen has no room to
     spare, but dropping the item text back to bare color would undo the
     point of it here. */
  .month-cell { min-height: 64px; padding: 4px; }
  .month-item { font-size: 9.5px; padding: 1px 4px; }
}

/* ------------------------------------------------------- the calendar window

   One window, three panels. The bar never moves: range on the left, the same
   back / today / forward in the middle, the Week-Month-Year switch on the
   right. Only the panel underneath changes when you switch, which is the whole
   reason it reads as one thing rather than three screens. */

.cal-shell {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  margin-bottom: 16px;
  box-shadow: var(--shadow-xs);
}

.cal-bar {
  display: flex; align-items: center; gap: 16px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
/* The bar wraps; the date does not.

   `min-width: 0` let this shrink below its own text, so on the week - which
   carries a zoom control the month and year do not - the title was squeezed
   to 55px and "24 – 30 August 2026" came out as four stacked words, seventy
   pixels of dead height above the grid. Wrapping is what the bar is set up
   to do (flex-wrap, above); it just never got the chance, because the title
   gave way first. Holding the title at its own width makes the controls drop
   to a second row instead, which is the right thing to move. */
.cal-title {
  margin: 0; font-size: 18px;
  flex: 1 1 auto; min-width: max-content;
}
.cal-nav { display: flex; align-items: center; gap: 6px; }
.cal-right { display: flex; align-items: center; gap: 10px; }

.cal-arrow {
  border: 1px solid var(--input); background: var(--card-bg);
  width: 28px; height: 28px; border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--muted-fg); padding: 0; font-size: 15px; line-height: 1;
  box-shadow: var(--shadow-xs);
}
.cal-arrow:hover {
  border-color: var(--border-strong); background: var(--secondary); color: var(--foreground);
}

/* Week / Month / Year, on a phone only.
   On a wide screen this was a second copy of the switch at the top of the
   window, an inch below it, both lit - so it has gone and the top one is the
   answer. A phone has no top switch to duplicate: its Calendar is one tab on
   the bottom bar, and without this there is no way to reach the month or the
   year at all. Plain text, no track of its own - the bar it sits on is busy
   enough. */
.cal-modes { display: flex; gap: 14px; }
.cal-mode {
  border: 0; background: transparent; cursor: pointer;
  padding: 4px 8px; border-radius: var(--radius-sm);
  color: var(--muted-fg); font-weight: 500; font-size: 13px;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.cal-mode.active { color: var(--foreground); font-weight: 600; background: var(--secondary); }
/* Touched, or hovered where there is a pointer to hover with - a softer
   version of what the one you are on already looks like. */
.cal-mode:not(.active):active {
  background: color-mix(in srgb, var(--secondary) 55%, transparent);
  color: var(--foreground);
}
@media (hover: hover) {
  .cal-mode:not(.active):hover {
    background: color-mix(in srgb, var(--secondary) 55%, transparent);
    color: var(--foreground);
  }
}

.cal-body { padding: 14px 16px 16px; }
.cal-empty { padding: 40px 0; text-align: center; }

/* Week keeps the backlog beside the grid: it is there to be dragged from, and
   putting it below the calendar would be a worse week for a tidier rule. */
.cal-split {
  display: grid; grid-template-columns: minmax(0, 1fr) 280px;
  gap: 16px; align-items: start;
}
.cal-rail {
  border-left: 1px solid var(--line);
  padding-left: 16px;
  min-width: 0;
}
.week-scroll { overflow-x: auto; }

/* One day, in the same grid as the week - same hour rows, same block drawing,
   just the one column, taking the room the seven would have shared. */

/* A day heading you can click to open just that day. Underlined on hover only:
   seven permanently-underlined headings would shout over the grid. */
.week-dayhead.clickable { cursor: pointer; }
.week-dayhead.clickable:hover { text-decoration: underline; }

@media (max-width: 1000px) {
  .cal-split { grid-template-columns: 1fr; }
  .cal-rail { border-left: 0; border-top: 1px solid var(--line); padding-left: 0; padding-top: 14px; }
}
@media (max-width: 700px) {
  .cal-bar { gap: 10px; }
  .cal-title { flex-basis: 100%; }
}

/* ------------------------------------------------------------- the hover peek

   Hovering a day on the month or year panel shows the day itself. Same color
   code as everywhere else - the goal's color down the left edge of each
   entry - so a glance here and a glance at the calendar mean the same thing
   without having to learn a second language. */

/* The host ignores the mouse so the rest of the page stays usable; the panel
   itself takes it back, because you can move into the panel, scroll it and
   click a line in it. */
.peek-host { position: fixed; inset: 0; pointer-events: none; z-index: 130; }

.peek {
  position: fixed;
  width: 276px;
  background: var(--popover);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 10px 12px;
  box-shadow: var(--shadow-lg);
  pointer-events: auto;
  /* Placed before it is measured, so it must not flash in the corner first. */
  opacity: 0; transition: opacity .1s ease;
}
.peek.shown { opacity: 1; }

/* Only the list scrolls. The date and the day's totals stay put at the top,
   which is what you are checking against while you scroll. */
.peek-list {
  max-height: 46vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0 -4px;
  padding: 0 4px;
}

.peek-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding-bottom: 6px; margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
}
.peek-date { font-weight: 700; font-size: 13px; }
.peek-total { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }

/* The year context: holiday, a heavy stretch, a Sunday you are preaching. On
   the planner this is usually the reason you hovered in the first place. */
.peek-context { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }
.peek-chip {
  font-size: 11px; font-weight: 600; padding: 2px 7px; border-radius: var(--radius-sm);
}
.peek-chip.off { background: #bfe0c8; color: #14532a; }
.peek-chip.season { background: #e6bb76; color: #4d3300; }
.peek-chip.brutal { background: #e5a098; color: #5c1a13; }
.peek-chip.preach { background: var(--secondary); color: var(--foreground); }

/* A button, because clicking a line opens it - but drawn as a row, because a
   list of buttons would drown the color code it exists to show. */
.peek-row {
  display: flex; align-items: flex-start; gap: 8px;
  width: 100%; text-align: left;
  padding: 5px 7px; margin-bottom: 3px;
  border-radius: var(--radius-sm);
  background: var(--goal-fill, var(--neutral-100));
  border: 0;
  border-left: 3px solid var(--goal, var(--line-strong));
  font: inherit; color: inherit; cursor: pointer;
}
.peek-row:hover { background: var(--goal-edge, var(--neutral-200)); }
/* Anything from your calendar rather than your plan stays gray - the colors
   belong to goals, and lending one to a dentist appointment would make the
   whole code meaningless. */
.peek-row.external { background: var(--neutral-100); border-left-color: var(--faint); }

.peek-when {
  flex: none; width: 42px;
  font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
  padding-top: 1px;
}
.peek-what { flex: 1; min-width: 0; }
.peek-title { font-size: 12.5px; }
.peek-goal { display: block; font-size: 11px; color: var(--muted); }

.peek-empty { color: var(--muted); font-size: 12.5px; padding: 4px 0; }

.peek-foot {
  display: flex; gap: 10px; align-items: center;
  margin-top: 7px; padding-top: 6px;
  border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--muted);
}
.peek-foot .up { color: var(--success-foreground); }
.peek-foot .down { color: var(--now); }

/* The planner's own strip of controls, now that it has no card heading. */
.planner-head {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  margin-bottom: 4px;
}
/* The line above the year says to scroll sideways, and only appears when there
   is somewhere to scroll to. It is switched off with the `hidden` attribute,
   which the browser draws as display:none - and the rule directly above beats
   that, so without this it stayed on screen while claiming to be off. */
.planner-head[hidden] { display: none; }

/* ------------------------------------------------------------------ the box

   The capture button lives on the top bar, not inside a screen, because the
   whole point is getting something out of your head without navigating first.
   The count only appears when there is something in it. */

.capture-btn, .talk-btn {
  position: relative;
  border: 1px solid var(--input); background: var(--card-bg);
  width: 30px; height: 30px; border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; color: var(--muted-fg); padding: 0;
  box-shadow: var(--shadow-xs);
}
.capture-btn:hover, .talk-btn:hover {
  color: var(--foreground); border-color: var(--border-strong); background: var(--secondary);
}
.capture-btn svg, .talk-btn svg {
  width: 15px; height: 15px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
/* The button is in the markup from the start and revealed once the app knows
   the microphone leads somewhere, so `hidden` has to beat the display above. */
.talk-btn[hidden] { display: none; }
.capture-count {
  position: absolute; top: -4px; right: -4px;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: var(--radius-full); background: var(--primary); color: var(--primary-foreground);
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
  box-shadow: 0 0 0 2px var(--background);
}
.capture-count.stale { background: var(--destructive); color: var(--on-destructive, #fff); }

.capture-input { min-height: 72px; }

/* ---------------------------------------------------------------- the + sheet

   One sheet, every noun in the app on it. The box is at the top because it is
   what you reach for most; the rest is grouped below a rule so the two halves
   read as two different offers rather than one long list. See public/js/add.js.

   The group labels are the gear menu's own, deliberately: both are lists of
   everywhere you can go, and giving them two different looks would make one of
   them feel like a different app. */
.add-sheet .gear-group-label { padding: 14px 0 4px; }
.add-sheet .gear-group-label:first-of-type { padding-top: 4px; }
.add-sheet .choice-row { margin-bottom: 6px; }
.add-capture .capture-input { min-height: 56px; }
.add-capture-actions {
  display: flex; align-items: center; gap: 9px;
  margin-top: 10px;
}
.add-capture-actions .btn-ghost { padding: 0; font-size: 12.5px; }
.add-capture-actions .btn-ghost[hidden] { display: none; }

/* "or add something in particular" — a rule with the words sitting in the gap,
   rather than a heading, because it is a change of subject and not a section.*/
.add-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 18px 0 2px;
  color: var(--subtle-fg); font-size: 11.5px;
}
.add-divider::before, .add-divider::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}

.capture-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.capture-row:last-child { border-bottom: 0; }
.capture-text { flex: 1; min-width: 0; }
.capture-actions { display: flex; gap: 5px; flex-wrap: wrap; flex: none; }

/* The five sorting answers. Small, fixed, never reordered - sorting has to
   become muscle memory or the box stops being emptied. */
.pill-sm {
  border: 1px solid var(--input); background: var(--card-bg);
  padding: 3px 9px; font-size: 12px; font-weight: 500;
  border-radius: var(--radius-sm); cursor: pointer;
  box-shadow: var(--shadow-xs);
}
.pill-sm:hover {
  border-color: var(--border-strong); background: var(--secondary); color: var(--foreground);
}
.pill.muted, .pill-sm.muted { color: var(--muted); }

/* ------------------------------------------------------------------ people

   Ordered by neglect, so the rows carry the standing rather than a date. */

.person-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
  border-left: 2px solid transparent; padding-left: 8px; margin-left: -8px;
}
.person-row:last-child { border-bottom: 0; }
.person-row.overdue { border-left-color: var(--now); }
.person-row.due { border-left-color: var(--faint); }
.person-main { flex: 1; min-width: 0; }
.person-name { font-weight: 600; }
.person-actions { display: flex; gap: 6px; flex: none; }

.linkish {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; color: inherit; font-weight: inherit; text-align: left;
}
.linkish:hover { text-decoration: underline; }

.contact-history { margin-top: 6px; }
.contact-line {
  display: flex; align-items: center; gap: 10px;
  padding: 4px 0; border-bottom: 1px solid var(--line);
}
.contact-line > span:first-child { flex: 1; }

/* ------------------------------------------------------------------ shelf */

.shelf-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.shelf-row:last-child { border-bottom: 0; }
.shelf-row.quiet { opacity: .6; }
.shelf-main { flex: 1; min-width: 0; }
.shelf-actions { display: flex; gap: 6px; flex: none; }

/* ------------------------------------------------------------------ patterns */

.finding-head {
  margin: 0 0 6px; font-size: 17px;
  text-transform: none; color: var(--foreground);
}

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

.review-line {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0; border-bottom: 1px solid var(--line);
}
.review-line:last-child { border-bottom: 0; }
.review-past { padding: 8px 0; border-bottom: 1px solid var(--line); }
.review-past:last-child { border-bottom: 0; }

/* Next week as it already stands. Seven bars, no numbers - the question is
   the shape, and a table of minutes answers a different one. */
.ahead-strip {
  display: flex; gap: 8px; align-items: flex-end;
  margin-top: 14px; padding-top: 8px;
}
.ahead-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px; }
.ahead-bar { display: flex; align-items: flex-end; height: 46px; width: 100%; }
.ahead-bar > span { width: 100%; background: var(--ink); border-radius: 3px; display: block; }
.ahead-bar.empty > span { background: var(--line-strong); }

/* ------------------------------------------------------------ the week strip

   The seven days around the one you are looking at, above the day itself. The
   row an iPhone calendar puts there: where you are in the week, and any other
   day of it one tap away. */

.day-strip {
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 2px; margin: 8px 0 4px;
}
.day-chip {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  border: 0; background: transparent; cursor: pointer;
  padding: 5px 0 4px; border-radius: var(--radius-md);
  font: inherit; color: var(--ink-2);
  transition: background var(--dur) var(--ease);
}
.day-chip:hover { background: var(--secondary); }
.day-chip:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
.day-chip.past { color: var(--muted); }
.day-chip-dow {
  font-size: 10px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.day-chip-num {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--radius-full);
  font-size: 14px; font-variant-numeric: tabular-nums;
}
/* Today is the red circle every calendar draws; the day you are looking at is
   filled in the app's own ink. When they are the same day, red wins - being on
   today is the more useful of the two facts. */
.day-chip.chosen .day-chip-num { background: var(--foreground); color: var(--background); font-weight: 600; }
.day-chip.today .day-chip-num { background: var(--now); color: var(--on-now, #fff); font-weight: 600; }
.day-chip.today .day-chip-dow { color: var(--now); }

/* How much is booked, up to three. Past three the honest answer is "a lot". */
.day-chip-dots { display: flex; gap: 2px; height: 4px; align-items: center; }
.day-chip-dots i { width: 3px; height: 3px; border-radius: 50%; background: var(--muted-fg); }
.day-chip.chosen .day-chip-dots i, .day-chip.today .day-chip-dots i { background: var(--ink-2); }

/* Past five days a column is too narrow to hold a word, so it stops trying.
   The blocks stay - their color and their place on the day are the whole
   point at that width - and the day heads shed everything but the date. */
.week-grid.dense .block-title,
.week-grid.dense .energy-mark { display: none; }
.week-grid.dense .week-dayhead { padding: 0 3px; font-size: 10px; gap: 2px; }
.week-grid.dense .week-dayhead.today span { min-width: 15px; padding: 1px 3px; }
.week-grid.dense .week-blocks .block { left: 1px; right: 1px; padding: 0; }

/* Out to the week, then the month, then the year. */
.cal-out { display: inline-flex; align-items: center; gap: 6px; }
.cal-out-mark svg {
  width: 13px; height: 13px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ------------------------------------------------------------------ the plan

   The four rungs as one list: annual target, quarterly goal, monthly project,
   task. Depth is said with an indent and a hairline down the left rather than
   with boxes inside boxes - four nested cards on a 375px screen leaves about
   two hundred pixels for the words. */

.plan-screen { display: flex; flex-direction: column; gap: 16px; }
.plan-title { margin: 0; font-size: 20px; }

.plan-line {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 9px 0; min-width: 0;
  border: 0; background: transparent; font: inherit; color: inherit;
  text-align: left;
}
.plan-line-title {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.plan-head { cursor: pointer; border-radius: var(--radius-sm); }
.plan-head:hover { background: var(--secondary); }
.plan-head:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
.plan-caret { flex: none; width: 12px; color: var(--muted-fg); font-size: 10px; }

/* The top rung reads as a heading; everything under it reads as a list. */
.plan-top .plan-line-title { font-weight: 650; font-size: 15px; }
.plan-target + .plan-target { border-top: 1px solid var(--line); }
.plan-goal .plan-line-title { font-weight: 600; }

.plan-count, .plan-when, .plan-est {
  flex: none; color: var(--muted); font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.plan-kind {
  flex: none; font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--faint); min-width: 52px;
}

/* One hairline down the left of everything belonging to the row above it. It
   takes the color of the target or goal it hangs off, so a long list still
   says at a glance which of two or three things you are inside. */
.plan-children {
  margin-left: 6px; padding-left: 12px;
  border-left: 2px solid color-mix(in srgb, var(--goal, var(--border-strong)) 45%, transparent);
}
.plan-project { padding: 2px 0; }
.plan-project > .plan-line { padding-bottom: 4px; }

.plan-task { display: flex; align-items: center; gap: 8px; padding: 6px 0; min-width: 0; }
.plan-task-title {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13px;
}
.plan-loose { padding: 7px 0; border-top: 1px solid var(--line); }

/* Where the chain runs out. The one thing this screen is actually for, so it
   is a sentence rather than a count. */
.plan-stops {
  margin: 0 0 8px 20px; padding-left: 10px;
  border-left: 2px solid var(--warning);
  color: var(--muted); font-size: 13px; line-height: 1.45;
}

/* Add a rung. The ghost line becomes the field when you press it, the same as
   the add row at the foot of every block on the day. */
.plan-add {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 8px 0;
  border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--faint); text-align: left;
}
.plan-add:hover { color: var(--foreground); }
.plan-add-plus { flex: none; width: 14px; text-align: center; font-weight: 600; }
.plan-add-open { color: var(--foreground); cursor: default; }
.plan-add-field {
  flex: 1; min-width: 0;
  border: 0; background: transparent; padding: 0;
  font: inherit; font-size: 13px; color: var(--foreground); outline: none;
}
.plan-add-field::placeholder { color: var(--muted); }

/* ------------------------------------------------------------------ the sit-down */

/* Each step is a button that takes you to where that step is actually done -
   the three cards below it on this page, or the Season screen. It was a plain
   row of text for a long time, which made the whole card a poster: four things
   to do and nothing to press. */
.sitdown-step {
  display: flex; gap: 12px; align-items: flex-start; width: 100%;
  padding: 10px 4px 10px 0; border-bottom: 1px solid var(--line);
  border-top: 0; border-left: 0; border-right: 0;
  background: transparent; font: inherit; color: inherit; text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.sitdown-step:last-of-type { border-bottom: 0; }
.sitdown-step:hover { background: var(--secondary); }
.sitdown-step:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
.sitdown-go {
  flex: none; align-self: center; color: var(--faint); font-size: 16px;
}
.sitdown-step:hover .sitdown-go { color: var(--foreground); }
.sitdown-mark {
  flex: none; width: 22px; height: 22px; border-radius: var(--radius-full);
  border: 1px solid var(--border-strong); color: var(--muted-fg);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700;
}
.sitdown-step.done .sitdown-mark { background: var(--primary); border-color: var(--primary); color: var(--primary-foreground); }
.sitdown-body { flex: 1; min-width: 0; }
.sitdown-title { font-weight: 600; }
.sitdown-step.done .sitdown-title { color: var(--muted); }

/* "It is this one." A card that has just been scrolled to says so for a second
   and then stops - without it, jumping down a long page is indistinguishable
   from the click having done nothing. */
.just-here { animation: just-here 1.4s var(--ease); }
@keyframes just-here {
  0%   { box-shadow: 0 0 0 3px var(--info-soft); border-color: var(--primary); }
  70%  { box-shadow: 0 0 0 3px var(--info-soft); border-color: var(--primary); }
  100% { box-shadow: 0 0 0 3px transparent; }
}

/* ------------------------------------------------------------------ the ant

   The bug report, in the one corner nothing else uses. It sits at a third of
   full strength so that a thing which is on every single screen never asks for
   attention, and comes up to full the moment you point at it. See js/bug.js. */

.bug-btn {
  position: fixed;
  right: 14px; bottom: 12px;
  z-index: 90;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; border: 0; background: none;
  color: var(--muted-fg); opacity: .32;
  cursor: pointer;
  transition: opacity 140ms var(--ease), color 140ms var(--ease);
}
.bug-btn:hover, .bug-btn:focus-visible { opacity: 1; color: var(--foreground); }
.bug-btn svg {
  width: 19px; height: 19px;
  fill: none; stroke: currentColor; stroke-width: 1.1;
  stroke-linecap: round; stroke-linejoin: round;
}
/* An ant that has been noticed does what an ant does. Two degrees, twice -
   any more and it is a cartoon. */
.bug-btn:hover svg { animation: ant-scuttle 900ms var(--ease) infinite; }
@keyframes ant-scuttle {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: translateY(-.5px) rotate(2deg); }
  75% { transform: translateY(.5px) rotate(-2deg); }
}
/* The ant's own tooltip used to be hand-placed here, because it sits in the
   corner where a centered one hangs off the edge. Every tooltip is measured
   now, so it gets the same answer without the special case. */

.bug-input { min-height: 96px; }

/* One report, in Settings. */
.bug-row { padding: 10px 0; border-bottom: 1px solid var(--line); }
.bug-row:last-child { border-bottom: 0; }
.bug-text { white-space: pre-wrap; margin-bottom: 3px; }

/* ------------------------------------------------------------------ event dialog */

.field-row { display: flex; gap: 12px; }
.field-row > label.field { flex: 1; }

.check-field {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 14px; cursor: pointer;
}
.check-field input { width: auto; }

/* A calendar an event cannot be moved off is shown, not offered - Google has
   no move, and faking one would lose the invitations. */
.static-field {
  border: 1px dashed var(--border-strong); border-radius: var(--radius-md);
  padding: 8px 11px; min-height: var(--h-md); color: var(--muted-fg);
  background: var(--secondary);
  display: flex; align-items: center;
}

/* ------------------------------------------------------------------ printing

   The wall planner is the one screen worth putting on paper. A linear calendar
   works because it lives where you can see it - pinned up, not behind an app.

   The Print button sets `.printing-planner` on <body>; everything below then
   strips the app back to that one card, landscape, on a single sheet. A plain
   Cmd+P is left alone on purpose and still prints the page as it looks. */

.card-head-right { display: flex; align-items: center; gap: 10px; }
.print-only { display: none; }

@page { size: letter landscape; margin: 0.4in; }

@media print {
  html, body { height: auto; background: #fff; }
  main { padding: 0; }

  /* Color is the whole point of the sheet. Without this browsers drop the bar
     fills and the year comes out as a grid of empty boxes. */
  * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  .no-print, .toast-host, #modalHost, .bug-btn { display: none !important; }

  body.printing-planner .topbar,
  body.printing-planner .subnav,
  body.printing-planner .demo-banner,
  body.printing-planner .wizard > .card-head,
  body.printing-planner .cal-bar,
  body.printing-planner .card { display: none !important; }

  /* The strapline replaces the heading and the date label in the corner: on
     paper nothing else says which twelve months this is. */
  body.printing-planner .print-only { display: block; }
  body.printing-planner .print-strap {
    margin: 0 0 8px;
    font-size: 13px; font-weight: 700;
    letter-spacing: .04em; text-transform: uppercase;
  }

  body.printing-planner .wizard { max-width: none; margin: 0; }
  /* The window's own frame goes too — on paper the sheet is the whole page,
     not a panel sitting inside an app. */
  body.printing-planner .cal-shell {
    border: 0; border-radius: 0; margin: 0; overflow: visible;
  }
  body.printing-planner .cal-body { padding: 0; }
  body.printing-planner .print-sheet {
    border: 0; padding: 0; margin: 0; break-inside: avoid;
  }

  /* On screen the planner scrolls sideways at a fixed minimum width. On paper
     there is nowhere to scroll, so it folds down to the page instead. */
  body.printing-planner .planner-scroll { overflow: visible; margin: 0; padding: 0; }
  body.printing-planner .planner { min-width: 0; width: 100%; }

  /* Tighter rows so all twelve months clear one sheet, and no page break
     through the middle of a month. */
  body.printing-planner .planner-row { padding: 1.5px 0; break-inside: avoid; }
  body.printing-planner .planner-row:nth-child(even) { background: var(--neutral-100); }
  body.printing-planner .planner-monthlabel { width: 40px; }
  body.printing-planner .planner-cell { height: 14px; font-size: 7.5px; }
  body.printing-planner .planner-cell.head { height: 11px; font-size: 7.5px; }
  body.printing-planner .planner-bars { grid-auto-rows: 11px; }
  body.printing-planner .planner-bar { height: 10px; font-size: 7.5px; padding: 0 3px; }
  body.printing-planner .planner-key { margin-top: 10px; }
}

/* --------------------------------------------------- setup, and the ranking

   The pass that sets the app up, and the priority list it produces. Two things
   run through all of it: a coach's voice above every question, and a ranking
   you put in order by hand. Both are built out of the pieces already in this
   file - the same hairline, the same 10px family of radii, the same one gray
   ramp - so a setup step and an ordinary screen are visibly the same app. */

/* The coach. Bigger than a quiet note and set to a readable measure, because
   this is a sentence you are meant to read rather than a label you scan. */
.coach {
  color: var(--muted);
  font-size: 14px;
  line-height: 1.6;
  max-width: 60ch;
  margin: 0 0 14px;
}
/* Inside a row there is no room for the full measure, and no room to breathe
   above and below it either. */
.coach.small {
  font-size: 13px;
  line-height: 1.5;
  max-width: none;
  margin: 2px 0 8px;
}

/* ---- the step strip ----

   The whole pass on one line above the card. It wraps rather than scrolls: on
   a narrow window a second row of chips is readable, and a strip that scrolls
   sideways hides the steps it exists to show. */

.step-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 16px;
}

.step-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--h-sm);
  padding: 4px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--card-bg);
  color: var(--muted-fg);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.3;
  text-align: left;
  cursor: pointer;
  box-shadow: var(--shadow-xs);
}
.step-chip:hover { border-color: var(--border-strong); color: var(--foreground); background: var(--secondary); }

/* The small round mark before the label: empty while a step is still to do,
   filled with a check once it is done. Same mark as the sit-down list. */
.step-mark {
  flex: none;
  width: 15px; height: 15px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-strong);
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 700; line-height: 1;
}

.step-chip.done { color: var(--muted); }
.step-chip.done .step-mark {
  background: var(--primary); border-color: var(--primary); color: var(--primary-foreground);
}

/* Where you are now. Border and weight carry it, not a fill, so the strip
   stays a row of chips rather than one loud button among quiet ones. */
.step-chip.current {
  border-color: var(--primary);
  color: var(--foreground);
  font-weight: 600;
  box-shadow: 0 0 0 1px var(--primary);
}
.step-chip.current .step-mark { border-color: var(--primary); }

/* ---- the rank list ----

   Order is the data here, so a row has to read as a thing you can pick up: a
   grip you can grab, a number that says where it sits, and the actions kept
   over on the right where they cannot be hit by accident. */

.rank-list { display: flex; flex-direction: column; gap: 6px; margin: 10px 0 0; }

/* The left border is 3px on every row, not only the top one - a border that
   appears when a row becomes first would shift every other row sideways. */
.rank-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card-bg);
  box-shadow: var(--shadow-xs);
}
.rank-row:hover { border-color: var(--border-strong); border-left-color: var(--border-strong); }

.rank-grip {
  flex: none;
  color: var(--faint);
  font-size: 12px; line-height: 1;
  letter-spacing: -2px;
  cursor: grab;
  user-select: none;
}
.rank-grip:active { cursor: grabbing; }

.rank-num {
  flex: none;
  width: 22px; height: 22px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-strong);
  background: var(--card-bg);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--muted-fg);
}

.rank-body { flex: 1; min-width: 0; }
.rank-title { font-weight: 600; }
.rank-body .sub { color: var(--muted); font-size: 12px; }

.rank-actions { display: flex; align-items: center; gap: 2px; flex: none; }
/* Single glyphs, so square rather than the ghost button's default padding. */
.rank-actions .btn-ghost { width: var(--h-sm); padding: 0; }

/* Number one. A heavier left edge and a filled number, so the row that gets
   the first share of every week is the one you see first. */
.rank-row.top { border-left-color: var(--primary); }
.rank-row.top .rank-num {
  background: var(--primary); border-color: var(--primary); color: var(--primary-foreground);
}

/* Lifted, not resized: the row you are dragging is inserted between the others
   as you move, and a row that changed size mid-drag would move the very rows
   you are aiming at. Only opacity and shadow change here, on purpose. */
.rank-row.dragging {
  opacity: .55;
  box-shadow: var(--shadow-md);
  cursor: grabbing;
}

/* Only shown once the order has actually changed. Note on the left, the save
   on the right, ruled off from the list above it. */
.save-order {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--line);
}
/* It is built hidden, and a display of flex would otherwise win over that. */
.save-order[hidden] { display: none; }

/* ---- anchors ----

   One bordered block per thing that has to hold - sleep, the family, the run.
   Headline you can switch off, the times underneath, the days along the
   bottom. Used again for the plan's checkpoints, which have the same shape. */

.anchor-row {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  padding: 12px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-xs);
}

.anchor-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
}
/* The checkbox label carries its own bottom margin everywhere else; in here
   the block's padding is already doing that job. */
.anchor-head .check-field { margin-bottom: 0; }
.anchor-head .check-field span { font-weight: 600; }

/* Flex rather than grid: one of these fields is given flex: 3 in the markup,
   and the number of fields changes from row to row. They wrap to their own
   line before they squash. */
.anchor-grid {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: 10px;
  margin-top: 10px;
}
.anchor-grid > .mini-field {
  flex: 1 1 150px;
  min-width: 0;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 4px;
}
.anchor-grid > .mini-field > span {
  color: var(--muted-fg); font-size: 12px; font-weight: 500;
}
/* Numbers are 88px wide by default, which leaves a labeled column half
   empty. In here they fill the column they were given. */
.anchor-grid > .mini-field input[type="number"] { width: 100%; }
/* The remove button sits on the same baseline as the fields beside it. */
.anchor-grid > .btn-ghost { flex: none; align-self: flex-end; height: var(--h-md); }

.anchor-row .day-toggles { margin: 10px 0 0; }
.anchor-row .coach.small { margin-bottom: 0; }

/* Switched off. Dimmed, but the headline stays at full strength - you have to
   be able to read what you just turned off, and turn it back on. */
.anchor-row.off { background: var(--secondary); }
.anchor-row.off .anchor-grid,
.anchor-row.off .day-toggles,
.anchor-row.off .coach { opacity: .5; }

/* ---- small shared rows ----

   A field and its button as one strip, and the suggestion chips underneath. */

.add-row { display: flex; align-items: center; gap: 8px; }
.add-row > input { flex: 1; min-width: 0; }
.add-row > .btn { flex: none; }

.chip-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }

/* ---- anchors on the calendar ----

   Background time, already spoken for. Hatched rather than filled, because a
   solid fill would compete with the goal colors, and the hatch says at a
   glance that the hour is held rather than booked. Muted throughout, and no
   grab cursor: these are not hours you move around your week.

   Keyed on both the class and the data attribute, so it works whichever way
   the block ends up being marked. */

.block.anchor,
.block[data-kind="anchor"] {
  background-color: var(--neutral-100);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(0, 0, 0, .05) 0 5px,
    rgba(0, 0, 0, 0) 5px 11px
  );
  border: 1px solid var(--border);
  color: var(--muted);
  cursor: default;
}
.block.anchor .block-title,
.block[data-kind="anchor"] .block-title {
  color: var(--muted-fg); font-weight: 500;
}
.block.anchor .block-time,
.block[data-kind="anchor"] .block-time { color: var(--faint); }

/* ---- narrow windows ----

   Down to a phone the setup pass stays one column of readable blocks: the
   fields stack, the chips stay full size, and nothing scrolls sideways. */

@media (max-width: 700px) {
  .coach { font-size: 13.5px; }
  .anchor-grid > .mini-field { flex: 1 1 100%; }
  .anchor-grid > .btn-ghost { align-self: flex-start; }
  .add-row { flex-wrap: wrap; }
  .add-row > input { flex: 1 1 100%; }
  .rank-row { gap: 8px; padding: 8px; }
  .rank-actions { gap: 0; }
  /* Ghost buttons grow to a thumb-sized height on a phone; these stay square. */
  .rank-actions .btn-ghost { width: var(--h-md); }
  .save-order { flex-wrap: wrap; }
}

/* ---- the opening slides ----

   A wide card split down the middle: the argument on the left, a drawing of
   the app's own parts on the right. The width is the point. When the flow
   stops telling you things and starts asking you something, the card narrows
   back to `.wizard`'s 720px — nobody reads that as a rule and everybody feels
   it, which is exactly the trick Superfocus plays. */

/* ---- the view switch ----

   Three words in a track. It replaced four icon buttons, two of which were
   hubs holding eleven screens between them - which is to say the bar was a
   menu, and it sat above the three-pane day the app exists to show. Now the
   bar says only which view of the calendar you are on. */

.viewswitch {
  display: inline-flex; gap: 2px;
  padding: 2px;
  background: var(--secondary);
  border-radius: var(--radius-full);
}
.viewswitch button {
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; background: transparent; cursor: pointer;
  min-height: 26px; padding: 0 12px;
  border-radius: var(--radius-full);
  font: inherit; font-size: 13px; font-weight: 500;
  color: var(--muted-fg);
}
.viewswitch svg {
  width: 14px; height: 14px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Three states, not two.
   The view you are on is a solid pill; the one under the pointer gets a
   half-strength version of the same pill, so it is obvious the word is a
   button before you press it. It used to only change color, which on a gray
   track is almost nothing - the row read as four labels with one of them
   picked out. */
.viewswitch button { transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }
.viewswitch button:not(.active):hover {
  background: color-mix(in srgb, var(--card-bg) 55%, transparent);
  color: var(--foreground);
}
.viewswitch button.active {
  background: var(--card-bg);
  color: var(--foreground);
  box-shadow: var(--shadow-xs);
}

/* ---- the other views of your goals ----

   Quiet on purpose. They sit under the pane's heading, not on the top bar,
   because they are not peers of "your day" - they are the same goals at four
   different lengths of time. */

/* No negative top margin: the pane's heading is sticky and paints its own
   background over whatever is beneath it, so pulling this row up by 4px slid
   it under the heading and cut it in half. */
.goal-views {
  display: flex; flex-wrap: wrap; gap: 2px;
  margin: 0 0 12px;
}
.goal-view {
  border: 0; background: transparent; cursor: pointer;
  padding: 3px 8px; border-radius: var(--radius-full);
  font: inherit; font-size: 12px; color: var(--muted-fg);
}
.goal-view:hover { background: var(--secondary); color: var(--foreground); }
.goal-view.current { background: var(--secondary); color: var(--foreground); font-weight: 600; }

/* A row in whichever of the four the column is showing. Same height and the
   same left edge as a goal row, so switching between them does not make the
   column jump. */
.sidebar-row {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 8px 0;
  border: 0; background: transparent; cursor: pointer;
  font: inherit; text-align: left; color: var(--foreground);
}
.sidebar-row + .sidebar-row { border-top: 1px solid var(--border); }
.sidebar-row:hover { color: var(--foreground); }
.sidebar-row:hover .sidebar-row-title { text-decoration: underline; }
.sidebar-row-title {
  flex: 1; min-width: 0; font-size: 13.5px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sidebar-row-rank {
  flex: none; width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-full);
  background: var(--secondary); color: var(--muted-fg);
  font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.sidebar-more { margin-top: 10px; padding-left: 0; padding-right: 0; }
.sidebar-empty-action { margin-top: 8px; }

/* ---- the gear ----

   Everything that is not a view of the calendar. Grouped by the moment you
   would reach for it, because "Friday afternoon" is a reason to open a menu
   and "the plan hub" is not. */

.gear-menu-host { position: fixed; inset: 0; z-index: 60; }
.gear-menu {
  position: fixed;
  width: 288px; max-height: 76vh; overflow-y: auto;
  padding: 6px;
  background: var(--popover);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.gear-group-label {
  padding: 10px 10px 4px;
  font-size: 10.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--subtle-fg);
}
.gear-item {
  display: flex; align-items: flex-start; gap: 9px; width: 100%; text-align: left;
  border: 0; background: transparent; cursor: pointer;
  padding: 7px 10px; border-radius: var(--radius-md);
  font: inherit; color: var(--foreground);
}
.gear-item:hover, .gear-item:focus-visible { background: var(--secondary); }
.gear-item.current { background: var(--secondary); font-weight: 600; }
.gear-item-icon {
  flex: none; width: 16px; height: 16px; margin-top: 1px;
  color: var(--muted-fg);
}
.gear-item-icon svg {
  width: 100%; height: 100%;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}
.gear-item-text { min-width: 0; }
.gear-item-label { display: block; font-size: 13.5px; }
.gear-item-hint {
  display: block; margin-top: 1px;
  font-size: 11.5px; color: var(--muted-fg);
}

.wizard-wide { max-width: 1000px; }

/* One of a set. The radio on the left is the whole difference between three
   buttons and three answers to one question, and the badge is what turns a
   default into advice. */
.choice-body { flex: 1; min-width: 0; text-align: left; }
.choice-title { font-weight: 500; }
.choice-row--compact .sub { color: var(--muted); font-size: 12.5px; margin-top: 1px; }
.choice-row[role="radio"] { gap: 12px; align-items: center; }
.choice-row[aria-checked="true"] { border-color: var(--primary); }

.slide {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 28px;
  align-items: center;
  padding: 34px 36px;
}
.slide-words { min-width: 0; }
.slide-words h2 { margin: 16px 0 10px; font-size: 21px; line-height: 1.25; text-wrap: balance; }
.slide-words .coach { max-width: 44ch; margin-bottom: 0; }
.slide-mark { font-size: 12px; font-weight: 600; }
.slide-actions { display: flex; gap: 8px; margin-top: 26px; }
.slide-art { min-width: 0; }

/* A hairline along the very top edge of the window. Full-bleed on purpose:
   inside the card it would read as a component; along the edge it reads as
   how far through you are. */
.setup-progress {
  position: fixed; inset: 0 auto auto 0;
  width: 100%; height: 3px;
  background: var(--secondary);
  z-index: 40;
}
.setup-progress > span {
  display: block; height: 100%;
  background: var(--primary);
  transition: width 320ms var(--ease);
}

@media (max-width: 860px) {
  .slide { grid-template-columns: 1fr; gap: 20px; padding: 24px; }
  /* The drawing comes second on a narrow screen, because the words are what
     you came for and the picture is what confirms them. */
  .slide-art { order: 2; }
  .slide-words { order: 1; }
}

/* ---- the opening screen ----

   What the app is, before it asks its first question. The numbered list is
   the whole pass laid out flat, so nobody starts step one wondering how many
   more of these there are. */

.opening-list { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }

.opening-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 7px 0;
}
.opening-row + .opening-row { border-top: 1px solid var(--border); }

.opening-num {
  flex: none;
  width: 20px; height: 20px;
  margin-top: 1px;
  border-radius: var(--radius-full);
  background: var(--secondary);
  color: var(--muted-fg);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600; line-height: 1;
  font-variant-numeric: tabular-nums;
}

.opening-title { font-size: 14px; font-weight: 500; }

/* ---- what a question is for ----

   Under every setup question: where the answer turns up once setup is over.
   Set apart by a left edge rather than a box, so it reads as an aside to the
   question above it rather than a second question. */

.teaches {
  margin-top: 18px;
  padding: 10px 0 2px 12px;
  border-left: 2px solid var(--border-strong);
}
.teaches p {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.55;
  max-width: 62ch;
}
.teaches-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--subtle-fg);
}

/* ---- the tour ----

   One idea and one drawing per page. The drawing is stroked in currentColor,
   so it follows the text and needs no second version for the dark theme. */

/* ---- the drawing palette ----

   Sampled off Superfocus's own onboarding illustrations rather than guessed:
   a soft, low-saturation set that sits on white without shouting, plus the two
   corner blobs and the blush used for a faded row. The dark theme restates the
   same hues at a lower lightness so a drawing never has to carry a second
   copy of itself. See art.js. */

:root {
  --art-card:      #ffffff;
  --art-ink:       #1c1f24;
  --art-hair:      rgba(0, 0, 0, .10);
  /* Two grays, not one. --art-faint draws THINGS: hairlines, an empty
     checkbox, the shaft of a leader arrow. --art-quiet writes WORDS. They used
     to be the same value, which meant every caption in the tour was set in a
     hairline gray at 8px - technically present and, at that size, not actually
     readable. */
  --art-faint:     #c8cdd4;
  --art-quiet:     #7d848d;
  --art-blush:     #faf4f4;
  --art-blob-cool: #ebf3fe;
  --art-blob-warm: #ffece3;

  --art-blue:      #4aa3f1;
  --art-amber:     #f0b95c;
  --art-purple:    #b49be0;
  --art-coral:     #fe805c;
  --art-teal:      #7fcfc4;
  --art-mint:      #9bddbb;
  --art-pink:      #e8909f;
}

@media (prefers-color-scheme: dark) {
  :root {
    --art-card:      #1e232b;
    --art-ink:       #dfe5ec;
    --art-hair:      rgba(255, 255, 255, .12);
    --art-faint:     #4a535f;
    --art-quiet:     #939cab;
    --art-blush:     #232830;
    --art-blob-cool: #1a2432;
    --art-blob-warm: #2c2320;

    --art-blue:      #5aa9ef;
    --art-amber:     #d9a552;
    --art-purple:    #a58ad6;
    --art-coral:     #e5714f;
    --art-teal:      #6cbdb2;
    --art-mint:      #83c8a4;
    --art-pink:      #d4808f;
  }
}

.tour-card h2 { margin: 18px 0 8px; }

/* The drawing sits directly on the card, with no frame of its own - the two
   soft blobs run off its corners instead, which is what makes it read as a
   window onto something larger rather than a picture in a box. */
.tour-pic {
  border-radius: var(--radius-lg);
  overflow: hidden;
  color: var(--foreground);
}
.tour-pic svg { display: block; }

.tour-dots { display: flex; gap: 6px; margin-bottom: 16px; }

.tour-dot {
  width: 26px; height: 5px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--border-strong);
  cursor: pointer;
}
.tour-dot.seen { background: var(--muted-fg); }
.tour-dot.current { background: var(--primary); }
.tour-dot:hover { background: var(--muted-fg); }

@media (max-width: 700px) {
  .tour-pic { padding: 8px; }
  .tour-dot { width: 18px; }
}

/* ===================================================================== voice

   Saying it into the box instead of typing it. See public/js/voice.js. */

.btn-talk { display: inline-flex; align-items: center; gap: 6px; }
.btn-talk[hidden] { display: none; }
.btn-talk svg {
  width: 15px; height: 15px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}

/* One big target in the middle of the sheet, because the hand holding the
   phone is usually the only hand going spare. */
.ramble-stage {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 18px 0 6px;
}
.ramble-mic {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  width: 92px; height: 92px;
  border: 0; border-radius: var(--radius-full);
  background: var(--primary); color: var(--primary-foreground);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ramble-mic:active { transform: scale(.96); }
.ramble-mic.recording { background: var(--destructive); }
.ramble-mic.asking { opacity: .5; cursor: default; animation: ramble-breathe 1.4s ease-in-out infinite; }
.ramble-mic-icon svg {
  width: 34px; height: 34px;
  fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round;
}

/* The ring is the level meter. It moves with your voice, which is how you
   know it is hearing you before there is anything on screen to read - and
   not knowing that is why people say the whole thing twice. */
.ramble-ring {
  position: absolute; inset: -10px;
  border-radius: var(--radius-full);
  border: 2px solid var(--destructive-edge);
  opacity: 0;
  transition: opacity .2s;
}
.ramble-mic.recording .ramble-ring { opacity: 1; }

.ramble-clock {
  min-height: 18px;
  font-size: 13px; font-variant-numeric: tabular-nums;
  color: var(--muted-fg);
}

.ramble-thinking { display: flex; align-items: center; gap: 12px; padding: 8px 0 4px; }
.ramble-pulse {
  width: 12px; height: 12px; flex: none;
  border-radius: var(--radius-full);
  background: var(--primary);
  animation: ramble-breathe 1.1s ease-in-out infinite;
}
@keyframes ramble-breathe {
  0%, 100% { opacity: .25; transform: scale(.8); }
  50% { opacity: 1; transform: scale(1.15); }
}

.ramble-list { margin-top: 4px; }
.ramble-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.ramble-item:last-child { border-bottom: 0; }
.ramble-item-text { flex: 1; min-width: 0; }

/* What it thought it heard, kept where he can see it. A note that came out
   wrong is much easier to forgive when the mishearing is visible. */
.ramble-heard { margin-top: 12px; font-style: italic; }

/* The day, the time, the length, the name - whatever a spoken note arrived
   already knowing. Beside the words rather than inside them, so which part he
   said and which part was worked out from it never blur together. */
.heard-chips { display: flex; flex-wrap: wrap; gap: 5px; margin: 5px 0 2px; }
.heard-chip {
  padding: 2px 7px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: var(--secondary);
  color: var(--muted-fg);
  font-size: 11px; font-weight: 500; line-height: 16px;
  white-space: nowrap;
}
.heard-chip.when { color: var(--info-foreground); background: var(--info-soft); border-color: var(--info-edge); }
.heard-chip.who { color: var(--success-foreground); background: var(--success-soft); border-color: var(--success-edge); }
.heard-chip.urgent { color: var(--destructive-foreground); background: var(--destructive-soft); border-color: var(--destructive-edge); }

/* The answer the dictation expected. Marked, never pre-pressed - the five
   buttons have to stay in the same places doing the same things. */
.pill-sm.suggested {
  border-color: var(--border-strong);
  background: var(--secondary);
  font-weight: 600;
}

/* ------------------------------------------------------------------ Compass

   How you are actually doing. The order follows the reference: the headline
   assessment, then two columns, then the goals, then the numbers.

   The two columns are a plain two-up grid with no equal-height trick and no
   minimum height on either side. That is deliberate — when one column runs
   much longer than the other, the shape of the card IS the finding, and
   evening them up would hide the one thing worth seeing. */

.compass-strength { display: flex; align-items: flex-start; gap: 18px; }
.compass-strength-text { flex: 1; min-width: 0; }
.compass-strength-text p { margin: 0 0 6px; }
.compass-level { font-size: 19px; font-weight: 650; letter-spacing: -.01em; margin-bottom: 4px; }

/* The five-point scale, drawn as five rising bars with the score underneath —
   the same shape as the icon in the reference, except ours sits next to the
   number it was read off, so it can be checked. */
.compass-dial { flex: none; width: 76px; text-align: center; }
.compass-bars {
  display: flex; align-items: flex-end; justify-content: center;
  gap: 4px; height: 44px;
}
.compass-bars i {
  display: block; width: 9px;
  border-radius: 3px;
  background: var(--secondary-hover);
}
.compass-bars i.lit { background: var(--primary); }
.compass-score {
  display: block; margin-top: 6px;
  font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}

/* How much of the score above is resting on something measured - separate
   from the score itself on purpose, the same way the reference keeps its own
   confidence card apart from the plan-strength number rather than folding
   one into the other. */
.compass-confidence { margin-top: 14px; }
.compass-confidence-head {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 6px;
}
.compass-confidence-label { font-size: 13px; font-weight: 600; }
.compass-confidence-bar { display: flex; gap: 4px; }
.compass-confidence-bar span {
  flex: 1; height: 5px; border-radius: var(--radius-full);
  background: var(--secondary-hover);
}
.compass-confidence-bar span.lit { background: var(--primary); }

.compass-parts {
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line);
  display: grid; grid-template-columns: 1fr 1fr; gap: 4px 20px;
}
/* A part the app could not measure is drawn flat and grayed, so a zero the app
   caused itself never reads as a zero the person earned. */
.compass-part[data-unmeasured="true"] .goal-hours .name,
.compass-part[data-unmeasured="true"] .goal-hours .nums { color: var(--muted); }
.compass-part[data-unmeasured="true"] .meter { opacity: .4; }

.compass-columns {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
  align-items: start; margin-top: 16px;
}
.compass-columns .card { margin-top: 0; }
.compass-findings[data-tone="good"] .finding-head { color: var(--success-foreground); }
.compass-findings[data-tone="watch"] .finding-head { color: var(--warning-foreground); }
.compass-finding { padding: 10px 0; border-bottom: 1px solid var(--line); }
.compass-finding:last-child { border-bottom: 0; padding-bottom: 0; }
.compass-finding p { margin: 4px 0 0; }

.compass-priority { padding: 6px 0 10px; }
.compass-priority .goal-hours .rank {
  flex: none; color: var(--muted); font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.compass-priority[data-met="true"] .goal-hours .name { font-weight: 600; }

.compass-goal {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.compass-goal:last-child { border-bottom: 0; }
.compass-goal-text { flex: 1; min-width: 0; }
.compass-goal-text .name {
  font-size: 14px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.compass-goal-tags { display: flex; align-items: center; gap: 8px; margin-top: 3px; }
.compass-goal .nums { font-size: 13px; font-variant-numeric: tabular-nums; color: var(--ink-2); }
/* A goal that got no hours says so in words. A zero in a column of hours is
   easy to read straight past; "no time" is not. */
.compass-goal[data-covered="false"] .nums { color: var(--warning-foreground); }

.compass-chip {
  padding: 2px 7px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: var(--secondary);
  color: var(--muted-fg);
  font-size: 11px; font-weight: 500; line-height: 16px;
  white-space: nowrap;
}

.compass-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 18px; }
.compass-stat {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--secondary);
}
.compass-stat .big-number { margin: 2px 0 4px; }
.compass-spark { margin: 2px 0 6px; }
.compass-spark svg { display: block; width: 100%; height: 26px; }
.compass-spark-grid { stroke: var(--border); stroke-width: 1; }
.compass-spark-line { fill: none; stroke: var(--primary); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.compass-spark-dot { fill: var(--primary); }

.compass-dist-head { padding-bottom: 6px; }
.compass-dist { padding: 4px 0 9px; }
.compass-dist .goal-icon { flex: none; }

/* On a phone the two-up grids stack. Two columns of findings at 180px wide is
   four words a line, and nobody reads that. */
@media (max-width: 720px) {
  .compass-columns,
  .compass-parts,
  .compass-stats { grid-template-columns: 1fr; }
}


/* ===========================================================================
   The guided setup
   ===========================================================================

   The shape of this flow was taken screen for screen off a walkthrough of
   Superfocus 2.0: five slides, one question per screen, option rows with a
   radio on the right, a segmented wizard bar. That structure is the good idea
   and it is kept.

   What is NOT kept is its skin. This used to be a separate stylesheet with its
   own palette, its own type scale and its own spacing, deliberately inheriting
   nothing - which meant walking out of setup into the app looked like leaving
   one product and arriving at another. Every value below now comes from the
   same tokens the rest of this file uses, so the two cannot drift apart again:
   change --card-bg once and the setup card changes with everything else.

   Sizes came from 1280px-wide stills of the walkthrough. Where a number looked
   odd the frame it was measured in is named. Ratios (radius over height,
   column over window) survive the video's zooming better than absolute pixels,
   so widths are written as percentages wherever the design allows it. */

.replica {
  /* Every one of these is an alias onto the app's own tokens. They are kept as
     names rather than substituted through the rules below because the rules
     say what they mean - "the sunken ground an add-row sits on" - and reading
     var(--secondary) at each of those places would not. */
  --replica-surface: var(--card-bg);
  --replica-surface-sunken: var(--secondary);
  --replica-surface-hover: var(--press-soft);

  --replica-line: var(--border);
  --replica-line-strong: var(--border-strong);
  --replica-line-faint: var(--neutral-100);

  --replica-ink: var(--foreground);
  --replica-ink-muted: var(--muted-fg);
  --replica-ink-faint: var(--subtle-fg);

  /* The accents. Superfocus tints its icon tiles; ours take the same four
     status hues every other panel in the app uses, so a blue tile here and a
     blue tile on the week screen are the same blue. */
  --replica-blue: var(--info-foreground);
  --replica-blue-soft: var(--info-soft);
  --replica-green: var(--success-foreground);
  --replica-green-soft: var(--success-soft);
  --replica-indigo: #6366f1;
  --replica-indigo-soft: color-mix(in srgb, #6366f1 12%, var(--surface-mix));
  --replica-sky: var(--info);
  --replica-sky-soft: color-mix(in srgb, var(--info) 12%, var(--surface-mix));

  /* Disabled controls, matching .btn-primary:disabled: a real fill and a real
     gray label rather than an opacity fade, so they stay crisp. */
  --replica-disabled-bg: var(--secondary);
  --replica-disabled-ink: var(--subtle-fg);

  --replica-radius: var(--radius-lg);
  --replica-radius-card: var(--r-card);
  --replica-radius-pill: var(--radius-full);

  /* Control heights come from the app's scale. The primary button here is the
     same height as every button on every other screen, which is most of why a
     wizard step now reads as this app rather than a page inside it. */
  --replica-control-h: var(--h-md);
  /* The reference's option rows carry more air than this did - two lines of
     text with room around them rather than a row sized to its content. */
  --replica-option-h: 68px;
  /* The follow-ups' answer rows. 49px left them reading as thin bars with
     more gap than row between them; the reference's are chunkier than the
     space around them, not thinner. */
  --replica-option-h-compact: 60px;
  --replica-option-gap: 16px;

  /* The split wizard's left panel measured 306px across the browser window in
     d02_0605.jpg. It is written as a clamp so it keeps roughly the same quarter
     of the window on a wide display instead of stranding the calendar. */
  --replica-panel-w: clamp(272px, 26%, 400px);

  --replica-ease: var(--dur) var(--ease);

  box-sizing: border-box;
  min-height: 100%;
  text-align: left;
}

/* Type, color and the font stack are all inherited now. They used to be
   restated here - 15px on 1.55 in a different fallback chain - and that one
   difference was doing more damage than the palette: every word in setup was
   set a size larger and a little looser than every word in the app. */

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

/* A margin reset, not a type system. The job is only to stop the app's own
   heading and paragraph margins from leaking in and breaking the vertical
   rhythm of a screen that centers one question. */
.replica h1,
.replica h2,
.replica h3,
.replica p,
.replica figure,
.replica blockquote {
  margin: 0;
}

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

/* NOTE: there is deliberately no `.replica button { font: inherit }` here.
   The app already sets that globally, and restating it under `.replica` made
   it two selectors deep - which beat `.replica-btn` and quietly handed every
   button in the flow the body's 14px/400 instead of its own 13px/600. The
   buttons looked like a different product because they were a different size.

   Same trap with color: `.replica button { color: inherit }` painted the
   primary button's label the same near-black as its own fill and the word
   vanished. Leave the reset where it is and let the classes win. */

.replica a {
  color: inherit;
}

/* ---------------------------------------------------------------------------
   Top progress hairline
   ------------------------------------------------------------------------ */

/* The slide sequence marks its progress with a single bar welded to the very
   top edge of the window — no container, no padding, running the full width
   (measured x=8 to x=1272, the whole viewport, in a03_0026.jpg). It is only a
   few pixels tall, so it reads as part of the browser rather than part of the
   page, which is exactly the effect. */
.replica-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 60;
  height: 4px;
  background: var(--replica-line-strong);
  overflow: hidden;
}

/* Drive it with --fill, e.g. style="--fill: 42%". The same custom property
   name is used by .replica-steps and .replica-qbar so that whoever wires this
   up only has to remember one word. */
.replica-progress::after {
  content: "";
  display: block;
  width: var(--fill, 0%);
  height: 100%;
  background: var(--replica-ink);
  transition: width var(--replica-ease);
}

/* ---------------------------------------------------------------------------
   The two-column slide
   ------------------------------------------------------------------------ */

/* The five intro slides use one wide card holding a column of words and a
   column of artwork. The card took 64% of the window and the words 56% of the
   card (a03_0026.jpg: card 232–1046 in a 1264px viewport, artwork starting at
   692). The artwork column has no padding at all — the illustration runs off
   all three of its outer edges and is clipped by the card's own corner radius,
   which is what makes the card feel like a window rather than a slide. */
.replica-slide {
  display: grid;
  /* A little more to the drawing than the reference gives it. Ours carries
     labels rather than abstract shapes, and at 44% of the card they were
     rendering at seven pixels. */
  grid-template-columns: 48% 52%;
  /* Near-full-bleed, the way the reference's own intro slides are: measured
     off a12_0116.jpg at about 89% of the window, against the 64% this used
     to be. At 64% the slide read as a dialog sitting on a page; theirs reads
     as the page. */
  width: clamp(320px, 88%, 1320px);
  min-height: 560px;
  margin: 0 auto;
  background: var(--replica-surface);
  border: 1px solid var(--replica-line);
  border-radius: var(--replica-radius-card);
  box-shadow: var(--shadow-xs), inset 0 1px 0 var(--inset-top);
  overflow: hidden;
}

/* Words are pushed well inside and vertically centered, leaving a lot of air
   above and below — the slides are mostly empty on purpose. */
.replica-slide__words {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 18px;
  padding: 56px clamp(24px, 5%, 56px);
}

/* 30 on 700, against the 22 this was. The reference's intro headlines are
   the largest type anywhere in its product and carry the slide on their own;
   at 22px ours read like a card heading inside a screen rather than the
   screen's one statement. */
.replica-slide__title {
  font-size: 30px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--replica-ink);
}

.replica-slide__body {
  max-width: 38ch;
  font-size: 17px;
  line-height: 1.55;
  color: var(--replica-ink-muted);
}

/* The artwork bleeds, so it is given no padding and told to cover. A tinted
   ground sits behind it in case the image is slow or missing, because a
   transparent half of the card would show the page gray through the corner. */
/* The drawing sits on the card, not inside a tinted panel. The soft blue was
   standing in for artwork that had not arrived yet; with a real drawing in
   there it only fought the drawing's own two corner blobs. */
.replica-slide__art {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  padding: 20px 20px 20px 0;
}

/* The drawing arrives wrapped in the tour's own .tour-pic div, so this has to
   reach through it. Written as `> svg` it matched nothing: the SVG fell back
   to the 300px every browser gives an unsized one, and the slide showed a
   drawing two-thirds the width of the space it had, with 6px labels nobody
   could read. object-fit is gone with it - it does nothing to an inline SVG,
   which scales by its own viewBox. */
.replica-slide__art > * {
  flex: 1 1 auto;
  min-width: 0;
}

.replica-slide__art img {
  display: block;
  width: 100%;
  height: auto;
}

/* The slide drawings are taller than they are wide (360x400 - see
   slideart.js), so scaling them to the full WIDTH of this half would push
   them past the bottom of the card and grow it. Fitting them to the box in
   both directions instead lets the SVG's own preserveAspectRatio letterbox
   it - it fills the half properly and never distorts. */
.replica-slide__art { align-items: stretch; }
.replica-slide__art svg {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 0;
}

/* ---------------------------------------------------------------------------
   The narrow question card
   ------------------------------------------------------------------------ */

/* Every screen that asks something uses the same centered card at roughly 44%
   of the window (a14_0136.jpg: 355–921 in a 1264px viewport) with generous
   padding and centered text. Content inside it is centered too, which is what
   makes a single question feel like the only thing on the screen. */
.replica-card {
  width: clamp(320px, 44%, 820px);
  margin: 0 auto;
  padding: 40px clamp(20px, 6%, 48px);
  background: var(--replica-surface);
  border: 1px solid var(--replica-line);
  border-radius: var(--replica-radius-card);
  box-shadow: var(--shadow-xs), inset 0 1px 0 var(--inset-top);
  text-align: center;
}

/* 25, not the 16 this was. The absolute size is not the thing to match here -
   a screenshot can be at any zoom - the RATIO is: in the reference a question
   headline is about 1.8x the text inside the option rows below it, and at
   16px against a 14px row title ours was 1.1x, so the question read as
   another label on the page rather than as the thing being asked. */
.replica-card__title {
  font-size: 25px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: var(--replica-ink);
}

/* The one big free-text question. In the reference it is the largest type in
   the whole flow, centered, sitting straight on the page with no card behind
   it and a tall box underneath - so it gets its own modifier rather than the
   bordered card the option screens use. */
.replica-card--ask {
  width: clamp(320px, 74%, 1180px);
  background: none;
  border: 0;
  box-shadow: none;
  padding: 8px 0 0;
}

/* Left, because the follow-ups are long generated questions and at that
   length centered text is hard to read - which is what the reference does
   with them too. The one big free-text question IS centered, and says so
   itself under .replica-card--ask below. */
.replica-card__question {
  font-size: 30px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--replica-ink);
  text-align: left;
}

.replica-card--ask .replica-card__question { text-align: center; }

/* The setup owns the window while it is running, so its card sits in the
   middle of it. Left to normal flow the card stacked against the top with a
   third of the screen empty underneath, which is the "weirdly spaced" of it -
   the reference centers every one of these. */
main:has(> .replica) {
  display: flex;
  /* Column, not the default row: main also holds the demo banner, and as a
     row that banner became a sibling COLUMN beside the wizard and squeezed
     it into the right-hand third. */
  flex-direction: column;
  min-height: calc(100dvh - var(--topbar-h) - 32px);
}
/* The banner keeps its own height at the top; the wizard takes what is left
   and centers itself inside it. */
main:has(> .replica) > .replica {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* The follow-ups. Same card, more of it - see followUpScreen(). */
.replica-card--wide { width: clamp(320px, 68%, 1040px); }

/* The box you answer into: tall enough to hold a real answer without
   scrolling, which is most of what makes the question feel askable. */
.replica-card--ask .replica-textarea {
  margin-top: 34px;
  min-height: 260px;
  font-size: 16px;
  line-height: 1.6;
}

.replica-card--ask .replica-btn--primary { margin-top: 20px; }

/* The box the mic is positioned against. .replica-mic itself was already
   built and measured further down this file - only the wiring was missing -
   so nothing here restates its size or its 8px inset. */
.replica-askbox { position: relative; }
.replica-askbox .replica-textarea { padding-right: 48px; }

/* Removing a goal from the drafted list. A circled trash rather than the ×
   this was: × on a row that also carries a dropdown reads as "close this
   control", and the reference draws it as a trash for that reason. */
.replica-trash {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--replica-line);
  border-radius: 50%;
  background: var(--replica-surface);
  color: var(--replica-ink-muted);
  cursor: pointer;
  transition: color var(--replica-ease), border-color var(--replica-ease);
}
.replica-trash:hover { color: var(--replica-ink); border-color: var(--neutral-300); }
.replica-trash svg {
  width: 15px; height: 15px;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}

.replica-card__sub {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--replica-ink-muted);
}

/* A vertical rhythm helper, so a screen can stack tile / title / rows / button
   without every caller inventing its own margins. */
.replica-card__stack {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 22px;
}

/* Lists and fields fill the card; a button keeps its own width and sits in the
   middle, which is how every "Continue" in the video is placed. */
.replica-card__stack > .replica-btn,
.replica-card__stack > .replica-tile {
  align-self: center;
}

/* ---------------------------------------------------------------------------
   Option rows
   ------------------------------------------------------------------------ */

/* The list that every choice screen is built from. Two heights: the tall one
   carries an icon tile, a title and a description; the compact one is a single
   line of text next to a radio. Both keep the same 10px corner and the same
   hairline, so a screen can mix them without looking assembled. */
.replica-option {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--replica-option-h);
  padding: 0 14px;
  background: var(--replica-surface);
  border: 1px solid var(--replica-line);
  border-radius: var(--replica-radius);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--replica-ease),
    border-color var(--replica-ease);
}

/* A list of them. The 16px gutter is what stops the rows from reading as one
   table with rules in it. */
.replica-option + .replica-option,
.replica-option + .replica-add {
  margin-top: var(--replica-option-gap);
}

.replica-option--compact {
  min-height: var(--replica-option-h-compact);
}

.replica-option:hover {
  background: var(--replica-surface-hover);
}

/* Selection is carried almost entirely by the radio. The border darkens by one
   step and nothing else changes — no fill, no ring — which is why a selected
   list still looks calm. */
.replica-option.is-selected {
  border-color: var(--neutral-300);
}

.replica-option:focus-visible,
.replica-option:has(:focus-visible) {
  outline: 2px solid var(--replica-ink);
  outline-offset: 2px;
}

/* The row inside the row. Everything an option shows - icon, text, badge,
   radio - is wrapped in this, and it only ever had a layout in the expanded
   ("Set your own") case, where the rule further down makes it a flex row.
   Everywhere else it fell back to a plain block, which stacked the icon above
   the text and left the radio as an inline span whose width and height are
   ignored outright - so a 20px circle rendered as a 3px-wide slit. It is the
   same row in both cases and now says so once, here. */
.replica-option__head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1 1 auto;
  min-width: 0;
}

.replica-option__body {
  flex: 1 1 auto;
  min-width: 0;
}

/* 14 on 600, the same as a heading row anywhere else in the app. It was 15px
   here, which is a size the app uses nowhere - close enough to look like a
   mistake rather than a decision, and one of the things that made a wizard
   step read as a page borrowed from somewhere else. */
.replica-option__title {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--replica-ink);
}

.replica-option__desc {
  margin-top: 2px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--replica-ink-muted);
}

/* A compact row's label is the whole of its content, so it sits on the row's
   own baseline rather than in a titled block. */
.replica-option--compact .replica-option__title {
  font-weight: 500;
}

/* Slot for the Recommended pill and anything else that trails the text. */
.replica-option__meta {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
  flex: 0 0 auto;
}

/* The radio. A selected one is a thick black ring around a small white hole
   rather than a dot inside a circle — measured in a12_0116.jpg, where the ring
   ate about a third of the 20px diameter from each side. Drawing it as a wide
   border on a white circle reproduces that exactly and needs no pseudo. */
.replica-radio {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border: 1.5px solid var(--neutral-300);
  border-radius: 50%;
  background: var(--replica-surface);
  transition: border-color var(--replica-ease), border-width var(--replica-ease);
}

/* The `input:checked +` form is kept inside `.replica` on purpose: an unscoped
   attribute selector like that would reach every radio in the host app. */
.replica .replica-radio.is-selected,
.replica .replica-option.is-selected .replica-radio,
.replica input[type="radio"]:checked + .replica-radio {
  border: 6px solid var(--replica-ink);
}

/* ---------------------------------------------------------------------------
   The row that opens
   ------------------------------------------------------------------------ */

/* Choosing "Set your own" does not push a new screen — the row itself grows and
   reveals a date field and a line of derived text under a full-bleed divider
   (a13_0126.jpg: the rule runs the entire inner width of the row, corner to
   corner, with no inset). Padding moves off the row and onto its two halves so
   that the divider can reach the edges. */
.replica-option.is-expanded {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 0;
  cursor: default;
}

.replica-option.is-expanded .replica-option__head {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--replica-option-h);
  padding: 0 14px;
  cursor: pointer;
}

.replica-option__more {
  padding: 14px;
  border-top: 1px solid var(--replica-line-faint);
}

/* The derived line — "Approximately 13 weeks" — is centered under the field and
   set in the faint gray, because it is a consequence of the choice above rather
   than another thing to read. */
.replica-option__hint {
  margin-top: 10px;
  font-size: 13px;
  text-align: center;
  color: var(--replica-ink-faint);
}

/* ---------------------------------------------------------------------------
   Badges and category pills
   ------------------------------------------------------------------------ */

.replica-badge {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 9px;
  border-radius: var(--replica-radius-pill);
  background: var(--replica-green-soft);
  color: var(--replica-green);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  white-space: nowrap;
}

/* The goal list tags each goal with its weight in the season. Core and
   Secondary are colored; Support is deliberately left neutral so that the eye
   sorts the list by importance without reading a word of it. */
.replica-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 20px;
  padding: 0 8px;
  border-radius: var(--replica-radius-pill);
  background: var(--secondary);
  color: var(--replica-ink-muted);
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}

.replica-pill--core {
  background: var(--replica-indigo-soft);
  color: var(--replica-indigo);
}

.replica-pill--secondary {
  background: var(--replica-sky-soft);
  color: var(--replica-sky);
}

.replica-pill--support {
  background: var(--secondary);
  color: var(--replica-ink-muted);
}

/* The tier picker: a pill you press, and a menu that names what each tier
   MEANS. See tierPicker() in replica.js for why this is not a <select>. */
.replica-tierwrap { position: relative; display: inline-block; margin-top: 6px; }

button.replica-pill {
  border: 0;
  cursor: pointer;
  font-family: inherit;
}
.replica-pill__chev {
  display: inline-flex;
  width: 10px; height: 10px;
  opacity: .8;
}
.replica-pill__chev svg { width: 100%; height: 100%; }

.replica-tiermenu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 40;
  width: 248px;
  padding: 6px;
  background: var(--replica-surface);
  border: 1px solid var(--replica-line);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
}

.replica-tieritem {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.replica-tieritem:hover { background: var(--replica-surface-hover); }
.replica-tieritem__label {
  display: block;
  font-size: 13px; font-weight: 600;
  color: var(--replica-ink);
}
.replica-tieritem__def {
  display: block;
  margin-top: 1px;
  font-size: 12px;
  color: var(--replica-ink-muted);
}
.replica-tieritem__check {
  margin-left: auto;
  flex: none;
  width: 15px; height: 15px;
  color: var(--replica-ink);
}
.replica-tieritem__check svg { width: 100%; height: 100%; }

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

/* Both field shapes are containers rather than bare controls, because the
   microphone button has to live inside the box and a lone <input> cannot hold
   an absolutely positioned child. Drop an <input> or <textarea> inside and it
   is stripped back to nothing so the wrapper is the only thing you see. */
.replica-field,
.replica-textarea {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 40px;
  padding: 0 12px;
  background: var(--replica-surface);
  border: 1px solid var(--replica-line-strong);
  border-radius: var(--replica-radius);
  font-size: 14px;
  color: var(--replica-ink);
  cursor: text;
  transition: border-color var(--replica-ease), box-shadow var(--replica-ease);
}

/* The free-text box is tall, top-aligned, and keeps a lane clear on the right
   so the microphone never lands on top of a word. */
.replica-textarea {
  align-items: flex-start;
  min-height: 86px;
  padding: 11px 44px 11px 12px;
}

.replica-field > input,
.replica-field > textarea,
.replica-textarea > input,
.replica-textarea > textarea {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  font-size: inherit;
  line-height: 1.5;
  outline: none;
  resize: none;
}

.replica-textarea > textarea {
  min-height: 62px;
  resize: vertical;
}

.replica-field ::placeholder,
.replica-textarea ::placeholder {
  color: var(--replica-ink-faint);
  opacity: 1; /* Firefox fades placeholders by default; Superfocus does not. */
}

.replica-field:focus-within,
.replica-textarea:focus-within {
  border-color: var(--ring);
  box-shadow: var(--ring-shadow);
}

/* The date field inside an expanded row centers its content, because it reads
   as a value the app worked out rather than a blank waiting to be filled. */
.replica-field--center {
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  font-weight: 600;
}

/* The microphone. It sits against the top-right inside corner — about 8px in
   from both edges in a14_0136.jpg — so on the tall box it stays level with the
   first line of typing rather than floating in the middle of the field. */
.replica-mic {
  position: absolute;
  top: 8px;
  right: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--replica-line-strong);
  border-radius: 50%;
  background: var(--replica-surface);
  color: var(--replica-ink-muted);
  cursor: pointer;
  transition: background-color var(--replica-ease), color var(--replica-ease);
}

.replica-mic:hover {
  background: var(--replica-surface-hover);
  color: var(--replica-ink);
}

.replica-mic > svg {
  width: 14px;
  height: 14px;
}

/* On a single-line field the microphone would overhang a 40px box, so it is
   centered vertically there instead of pinned to the top. */
.replica-field > .replica-mic {
  top: 50%;
  transform: translateY(-50%);
}

/* The free-text row at the bottom of every follow-up. The row is already a
   flex line of radio + input, so the mic only has to stop the input from
   eating the space and sit at the end of it. */
.replica-option--other { position: relative; }
.replica-option--other .replica-field { flex: 1 1 auto; min-width: 0; }
.replica-option--other > .replica-mic {
  position: static;
  flex: 0 0 auto;
  margin-left: 8px;
}

/* Hidden outright where the server has no transcription key: a microphone
   that cannot hear anything is worse than no microphone. See voiceAvailable()
   and wireMic() in replica.js. */
.replica-mic[hidden] { display: none; }
.replica-mic:disabled { opacity: .5; cursor: default; }

/* Recording says so the one way nobody has to be taught. */
.replica-mic.recording {
  background: var(--destructive-soft);
  border-color: var(--destructive);
  color: var(--destructive);
  animation: replica-mic-pulse 1.5s ease-in-out infinite;
}
@keyframes replica-mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, .32); }
  50%      { box-shadow: 0 0 0 6px rgba(220, 38, 38, 0); }
}

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

.replica-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: var(--replica-control-h);
  padding: 0 16px;
  border: 1px solid transparent;
  border-radius: var(--replica-radius);
  background: transparent;
  color: var(--replica-ink);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.005em;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition: background-color var(--replica-ease),
    border-color var(--replica-ease), color var(--replica-ease);
}

/* Near-black, white label, and completely flat. There is no shadow on any
   button in the video, which is the single detail that most separates this look
   from the shadowed-button house style of most web apps. Note the corner: it is
   a rounded rectangle, not a pill — a pill here reads as a different product. */
.replica-btn--primary {
  background: var(--primary);
  color: var(--primary-foreground);
}

.replica-btn--primary:hover {
  background: var(--primary-hover);
}

/* Disabled is a real gray fill with a real gray label rather than a faded copy
   of the enabled state, so it stays sharp instead of going milky. */
.replica-btn--primary:disabled,
.replica-btn--primary.is-disabled {
  background: var(--replica-disabled-bg);
  color: var(--replica-disabled-ink);
  cursor: not-allowed;
}

/* The secondary button is the one place the flow uses a full pill, which is how
   it stays clearly subordinate to the squared-off primary next to it. */
.replica-btn--ghost {
  background: var(--replica-surface);
  border-color: var(--replica-line-strong);
  border-radius: var(--replica-radius-pill);
  color: var(--replica-ink);
}

.replica-btn--ghost:hover {
  background: var(--replica-surface-hover);
}

.replica-btn--ghost:disabled,
.replica-btn--ghost.is-disabled {
  background: var(--replica-surface);
  border-color: var(--replica-line-strong);
  color: var(--replica-disabled-ink);
  cursor: not-allowed;
}

.replica-btn:focus-visible {
  outline: 2px solid var(--replica-ink);
  outline-offset: 2px;
}

/* The full-width "+ Add goal" row that closes a list. It is a button wearing a
   row's clothes — same corner as the rows above it, but a filled gray ground
   and no border, so it reads as an action rather than another choice. */
.replica-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 40px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--replica-radius);
  background: var(--replica-surface-sunken);
  color: var(--replica-ink);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--replica-ease);
}

.replica-add:hover {
  background: var(--secondary-hover);
}

/* ---------------------------------------------------------------------------
   Loading
   ------------------------------------------------------------------------ */

/* While the model works, the card's whole contents are replaced by a small
   spinner and one sentence, centered in the space the content had (c19_0582.jpg
   keeps the card at full height and simply empties it). Nothing else moves, so
   the wait feels like part of the screen rather than an interruption. */
.replica-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-height: 180px;
  padding: 32px 0;
  text-align: center;
}

/* A thin open arc, not a thick ring: the stroke is 2px on an 18px circle and
   three of the four sides are drawn, which leaves the small gap the video
   shows chasing itself around. */
.replica-loading__spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--replica-ink-faint);
  border-right-color: transparent;
  border-radius: 50%;
  animation: replica-spin 700ms linear infinite;
}

.replica-loading__text {
  font-size: 14px;
  color: var(--replica-ink-muted);
}

@keyframes replica-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---------------------------------------------------------------------------
   Wizard progress — six segments
   ------------------------------------------------------------------------ */

/* Not one bar but six separate ones, so the reader can count how much is left
   instead of estimating it. Segments share the space equally rather than taking
   a fixed width, because the same component appears in a 305px side panel and
   in a much wider card, and a fixed 66px segment would overflow the panel.

   The gap is written as a clamp for the same reason: d02_0605.jpg measures
   about 5px between segments in the narrow panel, while the wide placement
   wants nearer 14px. A percentage in between keeps both looking right. */
.replica-steps {
  display: flex;
  align-items: center;
  gap: clamp(5px, 3%, 14px);
  width: 100%;
}

.replica-steps__seg {
  flex: 1 1 0;
  position: relative;
  height: 4px;
  border-radius: var(--replica-radius-pill);
  background: var(--replica-line-strong);
  overflow: hidden;
}

.replica-steps__seg.is-done {
  background: var(--replica-ink);
}

/* The step you are on fills partway rather than waiting to flip at the end, so
   answering a question inside a step still moves something. Set it per element:
   style="--fill: 62%". */
.replica-steps__seg.is-current::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--fill, 0%);
  background: var(--replica-ink);
  border-radius: inherit;
  transition: width var(--replica-ease);
}

/* The bar usually sits to the right of a back chevron in the panel header. */
.replica-steps-row {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
}

.replica-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--replica-ink-muted);
  cursor: pointer;
}

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

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

/* A different progress idea for a different problem: the follow-up questions
   have no fixed number, so instead of segments there is one continuous bar
   across the top of the card with a caption underneath — what you are in on the
   left, how far through on the right (b20_0384.jpg). */
.replica-qbar {
  width: 100%;
}

.replica-qbar__track {
  height: 4px;
  border-radius: var(--replica-radius-pill);
  background: var(--replica-line-strong);
  overflow: hidden;
}

.replica-qbar__fill {
  width: var(--fill, 0%);
  height: 100%;
  border-radius: inherit;
  background: var(--replica-ink);
  transition: width var(--replica-ease);
}

.replica-qbar__caption {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--replica-ink-muted);
}

/* The percentage is the only number on the screen, so it gets the weight. */
.replica-qbar__value {
  font-weight: 600;
  color: var(--replica-ink);
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
   The split wizard
   ------------------------------------------------------------------------ */

/* Planning steps drop the centered card and take the whole window: questions on
   the left, a live calendar on the right that redraws as you answer. Both sides
   are cards floating on the page gray with a small gutter between them, which
   is what keeps a full-bleed screen from feeling like a different application.
   Each side scrolls on its own so the calendar never drags the questions away. */
.replica-split {
  display: flex;
  gap: 12px;
  align-items: stretch;
  width: 100%;
  height: 100%;
  min-height: 100vh;
  min-height: 100dvh; /* iOS shrinks 100vh behind its toolbar; dvh does not. */
  padding: 12px;
  background: var(--replica-ground);
}

.replica-split__panel {
  flex: 0 0 var(--replica-panel-w);
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px;
  overflow-y: auto;
  background: var(--replica-surface);
  border: 1px solid var(--replica-line);
  border-radius: 12px;
  box-shadow: var(--shadow-xs), inset 0 1px 0 var(--inset-top);
}

.replica-split__main {
  flex: 1 1 auto;
  min-width: 0;
  overflow: auto;
  background: var(--replica-surface);
  border: 1px solid var(--replica-line);
  border-radius: 12px;
}

/* In the panel the type steps down a notch — it is a column a third the width
   of the card layouts, and the card's sizes would only wrap. */
.replica-split__panel .replica-card__title {
  font-size: 16px;
  text-align: left;
}

.replica-split__panel .replica-card__sub {
  font-size: 12px;
  text-align: left;
}

.replica-split__panel .replica-option {
  min-height: var(--replica-option-h-compact);
}

/* A small all-caps-ish label that groups rows inside the panel. */
.replica-split__label {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--replica-line-faint);
  font-size: 12px;
  font-weight: 600;
  color: var(--replica-ink-muted);
}

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

/* The rounded square behind every glyph. Three sizes: small inside a row,
   medium as a row's own marker, large standing alone above a question. The
   radius grows with the tile so that all three keep the same softness. */
.replica-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 35px;
  height: 35px;
  border-radius: var(--replica-radius);
  background: var(--secondary);
  color: var(--replica-ink);
}

.replica-tile > svg {
  width: 58%;
  height: 58%;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}

.replica-tile--sm {
  width: 28px;
  height: 28px;
  border-radius: 8px;
}

.replica-tile--md {
  width: 35px;
  height: 35px;
  border-radius: 10px;
}

.replica-tile--lg {
  width: 44px;
  height: 44px;
  border-radius: 12px;
}

.replica-tile--neutral {
  background: var(--secondary);
  color: var(--replica-ink);
}

/* The blue tone is not decoration: it marks the screens where the app is asking
   you something or showing you something it generated, as opposed to the plain
   gray of screens that are only presenting your own choices back to you. */
.replica-tile--ai {
  background: var(--replica-blue-soft);
  color: var(--replica-blue);
}

/* ---------------------------------------------------------------------------
   Modal
   ------------------------------------------------------------------------ */

/* The overlay is a white wash rather than the usual dark scrim, which keeps the
   whole product feeling lit from the same side and stops a modal from turning
   the screen into a lightbox.

   NOTE for whoever reviews this: the raw pixels disagree. Sampling
   c16_0561.jpg, c17_0568.jpg and c18_0575.jpg against the un-modaled
   c14_0547.jpg, page white (#FFFFFF) becomes #3A3B40 behind the dialog while
   the browser chrome outside the page is untouched — that is a black scrim at
   roughly 75% opacity, not a white wash. The white wash is used here because it
   was handed down as confirmed; if the scrim turns out to be right, this one
   declaration is the only thing that has to change. */
.replica-veil {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  /* A judgment call, and worth knowing why it is one.
     The frames cannot settle this. Every frame with a dialog open is dimmed
     to a flat near-black - and so is the BROWSER'S OWN TOOLBAR, outside the
     web page entirely, which no stylesheet can reach. That is Loom dimming
     the whole recording, not the app dimming its page, so the real veil is
     hidden underneath an effect we are not looking at.
     So: the ordinary convention, at a weight that still lets you see where
     you were. One declaration to change if better evidence turns up. */
  background: var(--scrim);
}

.replica-modal {
  position: relative;
  width: min(var(--replica-modal-w, 560px), 100%);
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  padding: 32px;
  background: var(--replica-surface);
  border: 1px solid var(--replica-line);
  border-radius: var(--replica-radius-card);
  text-align: center;
  /* The heaviest shadow the app has. A dialog is the one thing here that
     genuinely floats above the page, and against a scrim a border alone would
     lose its edge. */
  box-shadow: var(--shadow-xl);
}

/* Wide variant — the three-card explainer runs about half the window
   (c17_0568.jpg) where the calendar-connect dialog runs under a third. */
.replica-modal--wide {
  --replica-modal-w: 900px;
}

.replica-modal__close {
  position: absolute;
  top: 14px;
  right: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--replica-ink-muted);
  cursor: pointer;
  transition: background-color var(--replica-ease), color var(--replica-ease);
}

.replica-modal__close:hover {
  background: var(--replica-surface-hover);
  color: var(--replica-ink);
}

.replica-modal__title {
  margin-top: 14px;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.02em;
}

.replica-modal__sub {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--replica-ink-muted);
}

/* ---------------------------------------------------------------------------
   Manifesto signature
   ------------------------------------------------------------------------ */

/* The season manifesto ends by asking you to sign it, and the field answers in
   handwriting the moment you type — that switch from typeface to handwriting is
   the whole point of the screen, so the face has to read as a signature rather
   than as a novelty. Caveat is monoline and slightly slanted, which is close to
   the marker hand in the video; the fallbacks are the nearest thing each
   platform ships so an offline render still looks written rather than typed. */
.replica-signature {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 6px auto 0;
  padding: 4px 8px;
  border: 0;
  border-bottom: 1px solid transparent;
  background: transparent;
  font-family: "Caveat", "Segoe Script", "Bradley Hand", "Snell Roundhand",
    cursive;
  font-size: 26px;
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  color: var(--replica-ink);
  outline: none;
  transition: border-color var(--replica-ease);
}

/* No rule under a signed name — the video shows the name sitting on nothing —
   but an empty field needs somewhere to aim, so the line appears while the
   placeholder is showing and while you are writing. */
.replica-signature:placeholder-shown,
.replica-signature:focus {
  border-bottom-color: var(--replica-line-strong);
}

.replica-signature::placeholder {
  color: var(--replica-ink-faint);
  opacity: 1;
}

/* The lines that frame it: a small serifed line above, the date below. */
.replica-signature-label {
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  color: var(--replica-ink);
}

.replica-signature-date {
  margin-top: 6px;
  font-size: 13px;
  text-align: center;
  color: var(--replica-ink-muted);
}

/* ---------------------------------------------------------------------------
   Narrow screens
   ------------------------------------------------------------------------ */

/* Below 860px there is no honest way to keep two columns: the words get an
   unreadable measure and the calendar loses too many days. Both layouts stack
   instead, and the slide's artwork keeps a fixed slice at the bottom so it
   stays an illustration rather than becoming the page. */
@media (max-width: 860px) {
  .replica-slide {
    grid-template-columns: 1fr;
    width: min(100%, 640px);
    min-height: 0;
  }

  .replica-slide__words {
    padding: 32px 24px;
  }

  .replica-slide__art {
    order: 2;
    height: 240px;
  }

  .replica-card {
    width: min(100%, 640px);
    padding: 28px 20px;
  }

  .replica-split {
    flex-direction: column;
    height: auto;
  }

  .replica-split__panel {
    flex: 0 0 auto;
    width: 100%;
  }

  .replica-split__main {
    min-height: 60vh;
  }

  .replica-modal {
    padding: 24px 20px;
  }
}

/* ---------------------------------------------------------------------------
   Reduced motion
   ------------------------------------------------------------------------ */

/* A spinner that never stops is the worst offender for anyone who has asked the
   system to calm things down, so it holds still as a plain ring and every
   transition collapses to nothing. The interface still works — it just stops
   sliding. */
@media (prefers-reduced-motion: reduce) {
  .replica *,
  .replica *::before,
  .replica *::after {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
  }

  .replica-loading__spinner {
    animation: none;
    border-right-color: var(--replica-line-strong);
  }
}


/* ===========================================================================
   The rail toggle, the inspector, and the block menu
   ===========================================================================

   Three pieces of one idea: the columns move, the page does not. Fold the
   goals out of the way, select a block so its detail arrives in the right
   column beside the day rather than on top of it, and act on that block where
   your hand already is. */

.rail-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--muted-fg);
  cursor: pointer;
}
.rail-toggle svg {
  width: 16px; height: 16px;
  fill: none; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round;
}
.rail-toggle:hover { background: var(--secondary); color: var(--foreground); }
.rail-toggle.folded { color: var(--foreground); background: var(--secondary); }

/* The calendar, now that it is the middle column rather than the whole page. */
.cal-middle { display: flex; flex-direction: column; gap: 16px; min-height: 0; }
/* Each card is exactly as tall as it is, and the column scrolls. Left to the
   flex default they all shrink to share whatever height the column has been
   given - which on a wide screen, where the column is pinned to the window,
   squashed the calendar itself down to two pixels. */
.cal-middle > * { flex: none; }

/* ---------------------------------------------------------------- inspector

   It takes the goal's own color on its left edge, the way the block on the
   calendar does. The two are the same piece of work seen twice, and the color
   is what says so without a word. */

.inspector {
  border-left: 3px solid var(--goal, var(--border-strong));
  animation: inspector-in var(--dur) var(--ease);
}
@keyframes inspector-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

.inspector-actions { display: flex; gap: 8px; margin: 10px 0 14px; }
.inspector-actions .btn { flex: 1; }
.inspector-focus { color: var(--on-primary, #fff); }

.inspector-rows { display: flex; flex-direction: column; gap: 9px; }
.inspector-row {
  display: flex; align-items: center; gap: 9px;
  font-size: 13px; color: var(--foreground);
}
.inspector-row.quiet { color: var(--muted-fg); }
.inspector-glyph {
  width: 16px; flex: none; text-align: center;
  color: var(--subtle-fg); font-size: 12px;
}
.inspector-label {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.inspector-notes {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--border);
  white-space: pre-wrap;
}

/* A selected block is ringed rather than recolored: it is still the same piece
   of work and still has to read as its own goal's color. */
.block[data-selected="true"] {
  outline: 2px solid var(--goal, var(--primary));
  outline-offset: -1px;
}

/* ---------------------------------------------------------------- the menu */

.blockmenu-host { position: fixed; inset: 0; z-index: 70; }
.blockmenu {
  position: fixed;
  min-width: 210px;
  padding: 5px;
  background: var(--popover);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.blockmenu-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 7px 9px;
  border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--foreground);
  font-size: 13px; text-align: left; cursor: pointer;
}
.blockmenu-item:hover { background: var(--secondary); }
.blockmenu-label { flex: 1; white-space: nowrap; }
/* A menu is the only place most people ever read a keyboard shortcut, so the
   shortcut rides along with the thing it does. */
.blockmenu-keys, .blockmenu-hint { color: var(--subtle-fg); font-size: 11px; flex: none; }
/* Delete stands apart, both spatially and in color, from everything above it
   that can be undone. */
.blockmenu-divider { height: 1px; margin: 5px 4px; background: var(--line); }
.blockmenu-item.danger { color: var(--destructive-foreground); }
.blockmenu-item.danger:hover { background: var(--destructive-soft); }

/* The greeting, now that it lives on the day's header instead of the top bar. */


/* ---------------------------------------------------------------- the panel

   A flow running in the left column, where the goals rail normally is. The
   wrap-up is the reason it exists: it asks what happened to each block of your
   day, and as a dialog it covered the day it was asking about. See panel.js. */

.panel-card { animation: panel-in var(--dur) var(--ease); }
@keyframes panel-in {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: none; }
}

.panel-head {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 14px;
}
.panel-title { font-size: 13px; font-weight: 600; color: var(--muted-fg); flex: 1; }
.panel-back { margin-left: auto; flex: none; }

/* Five dashes with two filled says how far through you are in a way that
   "step 2 of 5" never quite does. */
.panel-steps { display: flex; gap: 5px; flex: 1; }
.panel-step {
  flex: 1; height: 3px; border-radius: 2px;
  background: var(--border-strong);
  transition: background var(--dur) var(--ease);
}
.panel-step.done, .panel-step.here { background: var(--primary); }

/* The flows inside were written for a dialog, so they still use .modal-actions
   for their button rows. In a 320px column those have to wrap rather than run
   off the edge. */
.panel-body .modal-actions { flex-wrap: wrap; gap: 8px; }
.panel-body .modal-actions .btn { flex: 1 1 auto; }
.panel-body h2 { font-size: 16px; font-weight: 700; margin: 0 0 8px; }
.panel-body .check-row .line { flex-wrap: wrap; }


/* ------------------------------------------------ a whole screen in a column

   Every screen that is not the calendar and not Settings now opens in one of
   the calendar's own columns rather than in place of it. Each was written as a
   page in the middle of a window, so three things have to be undone: the page
   width they center themselves at, the grids that assume a page is wide, and
   the headings sized for one.

   The screen keeps its own title, so the bar above it carries only the way
   out. */
.panel-head-slim { margin-bottom: 4px; min-height: 22px; }
.panel-head-slim .panel-back { margin-left: auto; }

/* Several of these set their own max-width inline, which no class can beat.
   This is the one place in the stylesheet that needs !important, and it needs
   it for that reason rather than to win an argument with another rule. */
.panel-screen .wizard,
.panel-screen .plan-screen,
.panel-screen .compass {
  max-width: none !important;
  margin: 0;
}

/* One column, whatever the screen thought it was going to get. */
.panel-screen .rv-cal3,
.panel-screen .load-split,
.panel-screen .two-up,
.panel-screen .card-grid { grid-template-columns: 1fr; }

/* A page heading is 27px. In a column beside the calendar that is shouting,
   and the screen's name is already the only thing at the top of it. */
.panel-screen h1 { font-size: 19px; line-height: 1.25; margin: 0; }
.panel-screen h2 { font-size: 15px; }
.panel-screen > * > .card,
.panel-screen > .card { padding: 14px; }

/* Nothing inside may push the column wider than it is - a table or a row of
   buttons that does not fit scrolls inside its own box instead. */
.panel-screen { min-width: 0; }
.panel-screen table,
.panel-screen .subnav { max-width: 100%; overflow-x: auto; }


/* ---------------------------------------------------------------- setup, embedded

   Season length, the big question, the follow-ups, the goal list and the
   commitment run in the left column now, with Today's calendar live beside
   them - see replica.js. Only the five opening slides stay a full-screen
   takeover; everything past them used to float as a centered card sized for
   the whole page, and that has to become normal block content inside a
   320px column instead. */

.replica--embedded { width: auto; }

/* The hairline is fixed to the viewport for the full-screen slides, which is
   right there - welded to the very top edge like the browser's own loading
   bar. Inside a column it would draw across the whole app, over the top bar
   included, so here it becomes an ordinary bar at the top of the panel. */
.replica--embedded .replica-progress {
  position: static;
  margin: 0 0 16px;
  border-radius: var(--replica-radius-pill);
}

/* Full width and left-aligned everywhere embedded runs - both inside the
   desktop panel and, on phone, as its own full-width screen. */
.replica--embedded .replica-card {
  width: 100%;
  text-align: left;
}

/* The type steps down a notch and options compact, same numbers as
   .replica-split__panel (measured off the reference's own sidebar wizard,
   d02_0605.jpg) - this is that same narrow column, just reached by a
   different route (a left-column panel instead of a fixed split layout), so
   it needs the same compaction. Left unapplied, a card's option rows keep
   their full-width sizing meant for a ~500px centered card and visibly wrap
   inside the actual ~305px column. */
.replica--embedded .replica-card__title { font-size: 16px; }
.replica--embedded .replica-card__sub { font-size: 12px; }
.replica--embedded .replica-option { min-height: var(--replica-option-h-compact); }

/* In a column this narrow the "Recommended" pill cannot sit on the same line
   as the text and the radio: between the icon, the pill and the radio there
   were about 86px left for the description, which wrapped it to six lines and
   made the one recommended row twice the height of the two below it. Wrapping
   the pill onto its own line under the text gives that description back the
   ~90px the pill was holding, and the three rows match again. */
.replica--embedded .replica-option__head { flex-wrap: wrap; }
.replica--embedded .replica-option__head .replica-radio { order: 2; }
.replica--embedded .replica-option__head .replica-badge {
  order: 3;
  flex-basis: 100%;
  margin: 2px 0 0 40px;
}

/* Inside the panel body specifically: the panel's own .card already
   supplies the surface, the border and the padding, so strip the card's
   own rather than nesting two of them. On phone there is no panel.card
   ancestor - embeddedScreen() is mounted directly - so .replica-card's
   base surface (background, border, shadow, padding) is left alone there
   and supplies the card look phone was otherwise missing entirely. */
/* Flat, not raised - the wrap-up borrows .choice-row from the modal dialogs,
   which draw a shadow because a dialog floats over the page. A panel doesn't
   float over anything; it sits in the column like everything else in it, so
   the shadow read as a mismatch against every other row nearby. */
.panel-body .choice-row { box-shadow: none; }

.panel-body .replica--embedded .replica-card {
  margin: 0;
  padding: 0;
  border: 0;
  box-shadow: none;
  background: transparent;
}

/* Content-width and left-aligned, not full-width - checked against the
   reference's own sidebar-wizard "Continue" buttons (9.16.39, 9.17.10,
   9.17.44, 9.18.38 PM), every one of which sits as a compact pill at the
   panel's left edge with empty space beside it, not stretched across the
   column. The comment this replaced guessed at that and guessed wrong. */
.replica--embedded .replica-btn--primary {
  align-self: flex-start;
}


/* ==================================================================
   The review

   Its own file's worth of rules, kept here because everything else is
   here. Two ideas run through all of it:

   1. A number carries its own verdict. Green while there is room, red
      once there is not, and gray when there is no limit to be over. The
      arithmetic happens in lib/reviews.js and is never redone here.
   2. Anything that needs a sentence to explain it is on the hover
      instead of the page.
   ================================================================== */

/* The notice on Today. The only colored card in that column, because it is
   the only one that goes away by being dealt with. */
.rv-notice {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 15px 16px; border-radius: var(--radius-lg);
  background: var(--accent-soft, color-mix(in srgb, #2f6fed 7%, var(--card-bg)));
  border: 1px solid color-mix(in srgb, var(--accent, #2f6fed) 30%, transparent);
  box-shadow: var(--shadow-sm, 0 1px 2px rgba(0, 0, 0, .05));
}
.rv-notice-text { flex: 1; min-width: 140px; font-size: 17px; font-weight: 600; }
.rv-notice-buttons { display: flex; gap: 8px; }
.rv-notice .btn { height: 38px; padding: 0 14px; font-size: 14px; }
.side-rail > .rv-notice { margin-bottom: 14px; }

/* A count against its limit. */
.num {
  font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13px;
  padding: 2px 7px; border-radius: 999px; flex: none;
  background: var(--secondary); color: var(--muted);
}
.num.ok { background: var(--success-soft); color: var(--success-foreground); }
.num.done { background: var(--success-soft); color: var(--success-foreground); }
.num.over { background: var(--destructive-soft); color: var(--destructive-foreground); }

.rv-meter { width: 70px; flex: none; }

/* The three months, and the goals under whichever one is showing. */
.rv-tabs { display: flex; gap: 6px; margin-bottom: 14px; }
.rv-tab {
  font: inherit; font-size: 12px; padding: 5px 14px; border: 0; cursor: pointer;
  border-radius: 999px; background: var(--secondary); color: var(--muted);
}
.rv-tab:hover { background: var(--secondary-hover); color: var(--foreground); }
.rv-tab.on { background: var(--primary); color: var(--primary-foreground); font-weight: 600; }
.rv-empty { padding: 14px 0; }

.rv-goal { border-bottom: 1px solid var(--border); }
.rv-goal:last-of-type { border-bottom: 0; }
.rv-goal > summary {
  display: flex; align-items: center; gap: 10px; padding: 11px 0;
  cursor: pointer; list-style: none; flex-wrap: wrap;
}
.rv-goal > summary::-webkit-details-marker { display: none; }
.rv-goal > summary::before {
  content: '\25B8'; color: var(--subtle-fg); font-size: 10px; width: 9px; flex: none;
}
.rv-goal[open] > summary::before { content: '\25BE'; }
.rv-name { font-weight: 600; flex: none; }
.rv-target { color: var(--muted); font-size: 13px; flex: 1; min-width: 60px; }

.rv-nest { margin: 0 0 12px 30px; }
.rv-project > summary {
  display: flex; align-items: center; gap: 10px; padding: 6px 0;
  cursor: pointer; list-style: none; font-size: 13px;
}
.rv-project > summary::-webkit-details-marker { display: none; }
.rv-project > summary::before {
  content: '\25B8'; color: var(--subtle-fg); font-size: 9px; width: 9px; flex: none;
}
.rv-project[open] > summary::before { content: '\25BE'; }
.rv-pname { font-weight: 600; flex: 1; min-width: 0; }

.rv-tasks { margin: 0 0 8px 18px; padding: 0; list-style: none; font-size: 13px; color: var(--muted); }
.rv-tasks li { padding: 2px 0; }
.rv-tasks li::before { content: '\00B7\00A0'; color: var(--subtle-fg); }
.rv-tasks li.done::before { content: '\2713\00A0'; color: var(--success); }

.rv-hours { display: flex; align-items: center; gap: 10px; padding-top: 6px; }
.rv-hours-label { font-size: 13px; color: var(--muted); }

.rv-counts { margin-top: 14px; }
.rv-count { border-top: 1px solid var(--border); }
.rv-count > summary {
  display: flex; align-items: center; gap: 10px; padding: 10px 0;
  cursor: pointer; list-style: none;
}
.rv-count > summary::-webkit-details-marker { display: none; }
.rv-count > summary::before {
  content: '\25B8'; color: var(--subtle-fg); font-size: 10px; width: 9px; flex: none;
}
.rv-count[open] > summary::before { content: '\25BE'; }
.rv-count .grow { flex: 1; min-width: 0; }

.rv-miss {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 0 8px 20px; border-top: 1px solid var(--border); font-size: 14px;
}
.rv-miss .grow { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.rv-line {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--border);
}
.rv-line:last-of-type { border-bottom: 0; }

.rv-bring { margin-top: 14px; }
.rv-bring > summary {
  display: flex; align-items: center; justify-content: space-between;
  height: 38px; padding: 0 12px; cursor: pointer; list-style: none;
  border: 1px solid var(--input); border-radius: var(--radius-md);
  color: var(--muted); font-size: 14px;
}
.rv-bring > summary::-webkit-details-marker { display: none; }
.rv-bring > summary::after { content: '\25BE'; color: var(--subtle-fg); font-size: 11px; }
.rv-bring-list {
  margin-top: 6px; border: 1px solid var(--border);
  border-radius: var(--radius-md); overflow: hidden;
}
.rv-bring-row {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 12px; background: none; border: 0; border-top: 1px solid var(--border);
  font: inherit; font-weight: 600; color: var(--foreground); text-align: left; cursor: pointer;
}
.rv-bring-row:first-child { border-top: 0; }
.rv-bring-row:hover { background: var(--secondary); }

/* The quarter, nested under the goal for the year it serves. */
.rv-target-block { padding-top: 14px; border-top: 1px solid var(--border); }
.rv-target-block:first-of-type { border-top: 0; padding-top: 2px; }
.rv-target-label { font-size: 12px; color: var(--subtle-fg); }
.rv-target-name { font-size: 14px; font-weight: 600; margin-bottom: 2px; }

/* The three months ahead, and what is already on them. */
.rv-cal3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-bottom: 12px; }
.rv-monthname {
  font: inherit; font-weight: 600; font-size: 14px; margin-bottom: 8px;
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--foreground);
}
.rv-monthname:hover { text-decoration: underline; }
.rv-bighead { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.rv-bigname { font-weight: 600; font-size: 16px; }

.rv-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.rv-grid > span {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  position: relative; font-size: 11px; border-radius: 4px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.rv-dow { color: var(--subtle-fg); font-size: 10px; }
.rv-pad { visibility: hidden; }
.rv-day { cursor: pointer; }
.rv-day:hover { box-shadow: inset 0 0 0 1.5px var(--foreground); }
.rv-day.off { background: var(--accent-soft, color-mix(in srgb, #0f7d8c 16%, var(--card-bg))); color: var(--accent-text, #0b5f6b); }
.rv-day.hol { box-shadow: inset 0 0 0 1.5px var(--foreground); font-weight: 600; color: var(--foreground); }
.rv-dots {
  position: absolute; bottom: 2px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 2px;
}
.rv-dots i { display: block; width: 3px; height: 3px; border-radius: 50%; }

.rv-grid.big { gap: 4px; }
.rv-grid.big > span { aspect-ratio: auto; height: 66px; }
.rv-grid.big .rv-day {
  flex-direction: column; align-items: flex-start; justify-content: flex-start;
  gap: 2px; padding: 5px 6px; border: 1px solid var(--border); font-size: 12px;
}
.rv-grid.big .rv-lbl { font-size: 10px; line-height: 1.2; color: var(--muted); }
.rv-grid.big .rv-dots { bottom: 4px; left: 6px; transform: none; }

/* What is on a day. A panel rather than a tooltip, because a day holds a
   list and each line carries its own color the same as everywhere else. */
.rv-pop {
  position: absolute; top: calc(100% + 6px); left: 50%;
  opacity: 0; visibility: hidden;
  transition: opacity 120ms var(--ease), visibility 120ms;
  translate: -50% 0; z-index: 300; text-align: left;
  background: var(--popover); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  padding: 9px 11px; min-width: 190px;
}
.rv-day:hover .rv-pop {
  opacity: 1; visibility: visible; transition-delay: 500ms;
}
.rv-day.pop-left .rv-pop { left: 0; translate: none; }
.rv-day.pop-right .rv-pop { left: auto; right: 0; translate: none; }
.rv-pop-date { display: block; font-size: 11px; color: var(--muted); margin-bottom: 7px; white-space: nowrap; }
.rv-pop-row {
  display: flex; align-items: center; gap: 8px; padding: 3px 0;
  font-size: 12.5px; font-weight: 600; color: var(--foreground); white-space: nowrap;
}
.rv-pop-row .goal-icon { width: 18px; height: 18px; }
.rv-pop-row .goal-icon svg { width: 11px; height: 11px; }
.rv-pop-none { font-size: 12.5px; color: var(--subtle-fg); }

.rv-legend { display: flex; gap: 16px; flex-wrap: wrap; color: var(--muted); font-size: 13px; }
.rv-legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; }
.rv-legend .k-off { background: var(--accent-soft, color-mix(in srgb, #0f7d8c 16%, var(--card-bg))); }
.rv-legend .k-dot { width: 8px; height: 8px; border-radius: 50%; }
.rv-legend .k-hol { box-shadow: inset 0 0 0 1.5px var(--foreground); }

.rv-facts { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 14px; color: var(--muted); font-size: 13px; }
.rv-facts > span { display: flex; align-items: center; gap: 7px; }

.rv-pickhead { margin: 20px 0 6px; }
.rv-pickhead h3 { margin: 0; font-size: 15px; }
.rv-pick {
  display: flex; align-items: center; gap: 10px; padding: 10px 0;
  border-bottom: 1px solid var(--border); cursor: pointer;
}
.rv-pick:last-of-type { border-bottom: 0; }
.rv-pick input { width: 16px; height: 16px; flex: none; accent-color: var(--primary); }
.rv-pick-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.rv-pick-name { font-weight: 600; }
.rv-pick:not(.on) .rv-pick-name { color: var(--muted); }
.rv-pick-from { font-size: 12px; color: var(--subtle-fg); }

.rv-fit { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.rv-fit-label { font-size: 13px; color: var(--muted); }
.rv-fit .rv-meter { flex: 1; width: auto; }

/* The year, month by month. */
.rv-bars { display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px; align-items: end; height: 120px; }
.rv-bar { background: var(--neutral-300); border-radius: 4px 4px 0 0; }
.rv-bar.off { background: var(--accent, color-mix(in srgb, #0f7d8c 55%, var(--card-bg))); }
.rv-barlabels {
  display: grid; grid-template-columns: repeat(12, 1fr); gap: 6px;
  margin-top: 6px; color: var(--muted); font-size: 11px; text-align: center;
}
.rv-barlabels span.off { color: var(--accent-text, #0f7d8c); font-weight: 700; }

.rv-runs { display: grid; gap: 8px; margin-top: 16px; }
.rv-run { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rv-run-when { font-weight: 600; font-size: 14px; }

.rv-said { border-color: var(--border-strong); }
.rv-said-when { font-size: 12px; color: var(--subtle-fg); }

@media (max-width: 720px) {
  .rv-cal3 { grid-template-columns: 1fr; }
  .rv-target { min-width: 100%; order: 3; }
}

/* Adding a goal without leaving the review. Closed it is a quiet button; open
   it is one field. Nothing in between, and no dialog over the top of the work
   you were in the middle of. */
.rv-add {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin-top: 12px; padding: 9px 10px; cursor: pointer;
  background: none; border: 1px dashed var(--input); border-radius: var(--radius-md);
  font: inherit; font-size: 14px; color: var(--muted); text-align: left;
}
.rv-add:hover { background: var(--secondary); color: var(--foreground); }
.rv-add-plus { font-size: 15px; line-height: 1; }
.rv-add-open { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.rv-add-field {
  flex: 1; min-width: 0; height: 38px; padding: 0 12px;
  border: 1px solid var(--input); border-radius: var(--radius-md);
  background: var(--card-bg); color: var(--foreground); font: inherit; font-size: 14px;
}
.rv-add-field:focus { outline: none; border-color: var(--ring); box-shadow: var(--ring-shadow); }

/* The deadline a newly added goal will inherit from its quarter. */
.rv-add-due { font-size: 12px; color: var(--muted); flex: none; white-space: nowrap; }

/* A panel in the right column needs more room than the two cards that usually
   live there, so the column widens. It is still the same column and the same
   window - the middle keeps the calendar the review is asking about. */
.three-pane.right-panel { grid-template-columns: var(--pane-l) minmax(400px, 1fr) 460px; }
.three-pane.right-panel.left-collapsed { grid-template-columns: 44px minmax(400px, 1fr) 520px; }

/* The same thing on the other side. The plan, the season, your projects and
   your people take the left column, where the goals rail normally is - they
   are longer versions of that same list, so they belong in its place rather
   than opposite it. */
.three-pane.left-panel { grid-template-columns: 460px minmax(400px, 1fr) var(--pane-r); }
.three-pane.left-panel.right-collapsed { grid-template-columns: 520px minmax(400px, 1fr) 44px; }

/* Except when the middle is the week, the month or the year.
   Those are grids of columns, and a panel of a fixed 460px takes its width out
   of the wrong place: on a 1280px window the seven days of a week were left
   72px each and the weekday names ran into one another. A panel is a list and
   reads perfectly well narrower, so the panel is what gives way. It still
   never goes below the width at which it stops being a list. */
.three-pane.left-panel:has(> .cal-middle) {
  grid-template-columns: clamp(320px, 28vw, 460px) minmax(400px, 1fr) var(--pane-r);
}
.three-pane.right-panel:has(> .cal-middle) {
  grid-template-columns: var(--pane-l) minmax(400px, 1fr) clamp(320px, 28vw, 460px);
}

/* One in each column at once: the list of your people on the left, the person
   you clicked on the right, the day still between them. Neither gets the 460px
   a panel on its own does - two of those would leave the calendar a strip, and
   the calendar being visible is the entire reason these are columns and not
   dialogs. */
.three-pane.left-panel.right-panel {
  grid-template-columns: minmax(330px, 1fr) minmax(300px, 1.05fr) minmax(330px, 1fr);
}

/* Below this width the three columns have already become one - see the stack
   rule beside .three-pane itself. A panel stacks with them.

   This used to force three narrow columns back on, which squeezed the goals
   rail into 44px while it was still drawing its full contents: every goal came
   out one letter per line. */
@media (max-width: 1180px) {
  .three-pane.left-panel,
  .three-pane.right-panel { grid-template-columns: 1fr; }
  /* Whatever you just opened goes to the top of the stack. A left-column panel
     is already the first thing in the markup; a right-column one is last. */
  .three-pane.right-panel > .side-rail { order: -1; }
}

/* Inside the panel the review is one column of cards rather than a centered
   page, so its own wizard width comes off. */
.rv-panel-body > .card { padding: 14px; }
.rv-panel-body > .card + .card { margin-top: 12px; }
.rv-panel-body .rv-cal3 { grid-template-columns: 1fr; gap: 14px; }
.rv-panel-body .rv-bars { height: 90px; }
.rv-panel-body .rv-goal > summary { gap: 8px; }
.rv-panel-body .rv-meter { width: 54px; }

/* ------------------------------------------------------------------ the genie

   A dialog flies up out of the thing you clicked and settles. The offset from
   the pointer to where the dialog landed is measured in modal.js and handed
   over as two custom properties, so the animation itself is one rule shared by
   every dialog in the app.

   Deliberately a touch springy at the end. A linear scale reads as a box
   appearing; this reads as the thing you pressed opening out. */
@keyframes genie-in {
  from {
    opacity: 0;
    transform: translate(var(--genie-x, 0px), var(--genie-y, 0px)) scale(.16);
  }
  55% { opacity: 1; }
  to { opacity: 1; transform: translate(0, 0) scale(1); }
}
.modal.genie {
  animation: genie-in 320ms cubic-bezier(.16, 1, .3, 1);
  will-change: transform, opacity;
}
@media (prefers-reduced-motion: reduce) {
  .modal.genie { animation: dialog-fade 140ms var(--ease); }
}

/* A dialog holding a screen's worth of work rather than one question. The
   columns behind stay visible and blurred, which is the whole point of it
   being a dialog and not a page. */
.modal.modal-wide { width: min(960px, 100%); max-height: 88vh; padding: 0; }
.modal-wide .modal-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 20px; border-bottom: 1px solid var(--border);
  background: var(--popover); border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
}
.modal-wide .modal-head h2 { margin: 0; font-size: 18px; }
.modal-wide .modal-x { font-size: 20px; line-height: 1; padding: 0 8px; flex: none; }
.modal-wide .modal-body { padding: 18px 20px 22px; }

/* Two columns of cards where there is room. The review is long, and one
   960px column of short cards is mostly whitespace. */
@media (min-width: 900px) {
  .modal-wide .rv-panel-body { columns: 2; column-gap: 16px; }
  .modal-wide .rv-panel-body > * { break-inside: avoid; margin: 0 0 16px; }
}

/* A category's color, beside the thing it belongs to. Small enough to sit in a
   table cell without pushing the field around, big enough to match by eye
   against the same color on another screen. See lib/hues.js. */
.cat-dot {
  width: 9px; height: 9px; border-radius: 3px; flex: none;
  display: inline-block; vertical-align: middle;
}
.rhythm-what { display: flex; align-items: center; gap: 8px; }
.rhythm-what .flat-input { flex: 1; min-width: 0; }


/* ------------------------------------------------------ a panel on a phone

   A phone has no columns to open a panel into, so the panel takes the screen
   instead - see renderToday in today.js and renderCalendar in calendar.js.

   Until this rule existed that wrapper was a bare div with no styling at all:
   the card came out only as tall as its own contents, with the page showing
   underneath it, which reads as a fragment left on the screen rather than as
   the screen. It is the calendar column's own shape, so a panel arrives where
   the day was and scrolls the same way. */
.phone-panel-screen {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.phone-panel-screen > .panel-card {
  flex: 1;
  min-height: 0;
}

/* The sideways slide is a panel arriving BESIDE the calendar. On a phone it
   arrives instead of the calendar, so it comes up rather than in from the
   edge - sliding in from the side would say it had opened next to something. */
.phone-panel-screen > .panel-card { animation: phone-panel-in var(--dur) var(--ease); }
@keyframes phone-panel-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* ---------------------------------------------- names wrap, they are not cut

   Everything named here holds something a person wrote themselves: a goal, a
   task, an annual target. All of them were one line ending in an ellipsis,
   which on a phone left you rating "Pray..." and "Hosp..." in the morning
   review and reading "Preach the whole of R..." on the plan. A goal is a
   sentence somebody chose their words for, and cutting it off makes the plan
   look like a database.

   Two lines rather than as many as it likes, so one very long title still
   cannot push a row to four lines and break the rhythm of the column - which
   is what the ellipsis was protecting in the first place.

   This block is at the end of the file because it has to come after the rules
   it overrides. Cutting off is still right in a few places and those are left
   alone: a chip inside a month-grid cell, the ghost that follows a drag, the
   running timer strip. Each of those has a fixed height and nowhere to put a
   second line. */
.goal-title,
.goal-hours .name,
.plan-line-title,
.plan-task-title,
.sidebar-row-title,
.compass-goal-text .name {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The morning review asks about one block per row: when it was, what it was,
   and the three answers. The three buttons are a fixed width, so on a phone
   they took every pixel the name needed. Below 700px the name gets the whole
   width and the buttons drop underneath it, lined up under the name rather
   than under the time. */
.check-row .what {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  min-width: 0;
}

@media (max-width: 700px) {
  .check-row .line { flex-wrap: wrap; row-gap: 8px; }
  /* The buttons are what drops, not the name: the time and what happened stay
     on one line together, the way you would say it out loud. */
  .check-row .keys { flex-basis: 100%; margin-left: 56px; }
}

/* --------------------------------------------- important against urgent

   The four boxes of the Eisenhower matrix, drawn in the goals column. See
   public/js/matrix.js.

   That column is about 280px wide once the card's padding is off it, so the
   grid is auto-fit rather than a hard 2x2: two columns while there is room
   for two, one column when there is not. A media query cannot do this job -
   the column is narrow inside a window that is wide, and a media query only
   ever knows how wide the window is. */
/* Clipped sideways, and this is load-bearing rather than tidiness. The column
   this sits in scrolls on both axes, and a grid reports the width its content
   WOULD want if nothing wrapped - about 550px here - so the column gained a
   horizontal scroll range it never needed. Clicking a tab then scrolled the
   column sideways to bring it into view and cut the letter off the front of
   the heading above it. Nothing in here is meant to be seen outside the
   column, so clipping the axis costs nothing and the scroll range goes away.
   `clip` rather than `hidden`, which would force the vertical axis to scroll
   inside the pane instead of with the column. */
.matrix-pane { overflow-x: clip; }

.matrix-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(124px, 1fr));
  gap: 8px;
}

.matrix-box {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 10px;
  min-width: 0;
}

/* Important, with nothing forcing it. This is the box the other three exist
   to frame, so it is the one that is outlined and filled in - enough to find
   without reading anything, and not so much that it becomes a heading. */
.matrix-box-focus {
  border-color: var(--border-strong);
  background: var(--secondary);
}

.matrix-box-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 6px;
}
.matrix-box-title {
  min-width: 0;
  font-size: 12px; font-weight: 600; color: var(--foreground);
}
/* Gray, always. Four things to do now is neither praise nor a warning, and a
   colored number would claim a verdict this screen has not made. */
.matrix-box-count {
  flex: none;
  font-size: 12px; font-weight: 600; color: var(--muted-fg);
  font-variant-numeric: tabular-nums;
}

.matrix-advice,
.matrix-empty {
  margin: 4px 0 0;
  font-size: 11px; line-height: 1.4; color: var(--muted-fg);
}

.matrix-list {
  margin-top: 8px;
  display: flex; flex-direction: column; gap: 2px;
}

/* The symbol, the name and the deadline - stacked rather than strung along
   one line, because at 124px a name and a date side by side leaves about four
   characters for the name. The symbol spans both rows, so the two lines still
   read as one row. */
.matrix-task {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  gap: 1px 6px;
  align-items: start;
  width: 100%;
  padding: 4px;
  border: 0; background: none; border-radius: var(--radius-sm);
  font: inherit; text-align: left; cursor: pointer;
  transition: background-color var(--dur) var(--ease);
}
.matrix-task:hover { background: var(--press-soft); }
.matrix-task .goal-icon {
  grid-row: 1 / span 2;
  width: 16px; height: 16px; margin-top: 1px;
}
.matrix-task .goal-icon svg { width: 10px; height: 10px; }

/* Three lines, not two. A box this narrow fits about eleven characters to a
   line, and two lines cut most real task names in half - "Read the
   commentari…" is not a task anybody can recognize. Three gets the usual ones
   whole and still stops a paragraph from taking over the box. */
.matrix-task-title {
  grid-column: 2;
  font-size: 12px; line-height: 1.35; color: var(--foreground);
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.matrix-when {
  grid-column: 2; justify-self: start;
  margin-top: 2px;
  padding: 0 6px; border-radius: var(--radius-full);
  font-size: 10px; font-weight: 600; white-space: nowrap;
  background: var(--secondary); color: var(--muted-fg);
}
/* The one number on this screen that is genuinely bad news, so it is the one
   that gets a color. */
.matrix-when.late {
  background: var(--destructive-soft);
  color: var(--destructive);
}

/* The star on a task somebody marked important by hand. Colored, because it
   is the one thing on this screen that is your answer rather than the app's,
   and a gray star would read as decoration. */
.matrix-marked { color: var(--warning-foreground); font-size: 11px; }

.matrix-note { margin: 10px 0 0; }

/* The row under the capture box gains one more button once the assistant is
   on — "Ask where this goes" — and on a phone four controls in one rigid row
   squeeze each other down to nothing. Let it wrap instead. See placeit.js. */
.add-capture-actions { flex-wrap: wrap; row-gap: 8px; }
