/* ===========================================================================
   The landing page's own palette — the dark galaxy look, deliberately kept.

   It used to live on :root, which meant it also painted the app and, because
   this file loads after tokens.css, silently overrode every design token. It
   is now scoped to the state where the landing is actually on screen, so the
   marketing page looks exactly as it did while the app gets tokens.css.

   Everything inside the app should come from tokens.css. tools/lint-tokens.js
   enforces that.
   =========================================================================== */
:root:has(#auth:not([hidden])) {
  color-scheme: dark;
  --bg: #06050d;          /* near-black with a faint purple cast */
  --panel: #100e20;       /* deep purple-navy panel */
  --panel-2: #17142e;
  --line: #2a2552;        /* purple-tinted border */
  --text: #ecebf9;
  --muted: #9c98ca;
  --accent: #6b8cff;      /* blue */
  --accent-2: #a26cff;    /* purple */
  --shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
  --radius: 14px;
}
:root[data-theme="light"]:has(#auth:not([hidden])) {
  color-scheme: light;
  --bg: #f3f5ff;          /* white with a blue tint */
  --panel: #ffffff;
  --panel-2: #eef1ff;
  --line: #dde2fb;
  --text: #1b1a30;
  --muted: #5c6091;
  --accent: #5a6cff;      /* blue */
  --accent-2: #8b5cff;    /* purple */
  --shadow: 0 12px 34px rgba(90, 90, 170, 0.16);
}
* { box-sizing: border-box; }
html, body { margin: 0; }
/* Ensure the [hidden] attribute always wins over display:flex/grid rules below */
[hidden] { display: none !important; }
body {
  font-family: var(--font-sans);
  /* The app gets a flat surface. The decorative washes that used to live here
     are the landing page's, and they now sit on the landing page — accent is
     for the action to take now, not for painting the room. */
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  font-size: var(--font-base);
  font-weight: var(--weight-body);
  line-height: var(--leading-base);
}
.wrap { max-width: 820px; margin: 0 auto; padding: var(--space-6) var(--space-5) calc(112px + env(safe-area-inset-bottom)); }

/* top bar + nav */
header.app { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-4); }
.logo {
  width: 40px; height: 40px; border-radius: var(--radius-md);
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text);
  display: grid; place-items: center; font-size: var(--font-lg);
}
h1 { font-size: var(--font-lg); font-weight: var(--weight-emphasis); margin: 0; letter-spacing: -0.2px; line-height: var(--leading-tight); }

/* ---- floating bottom dock ----------------------------------------------
   Fixed, glassy pill centred at the bottom of the screen. A gradient
   indicator slides between the four tabs; the buttons themselves stay
   transparent so the pill reads as the selection. */
nav.tabs {
  position: fixed; z-index: 60;
  left: 50%; bottom: calc(var(--space-4) + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  width: min(430px, calc(100vw - 28px));
  display: flex; gap: 0; padding: var(--space-1);
  border-radius: var(--radius-full);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
}
/* The sliding selection pill (one quarter of the inner width). Neutral, not
   accent: which tab you're on is way-finding, not the action to take now. */
nav.tabs::before {
  content: ""; position: absolute; top: var(--space-1); bottom: var(--space-1); left: var(--space-1);
  width: calc((100% - var(--space-2)) / 4);
  border-radius: var(--radius-full); pointer-events: none;
  background: var(--surface-2);
  transition: left var(--duration-slow) var(--ease-out), opacity var(--duration-fast) var(--ease-out);
}
/* Slide with `left`, not a percentage transform: a percentage translate resolves
   against the pseudo-element's own box, which does not match the button pitch,
   and the pill drifted off the active tab. These calcs share the exact
   expression used for the width, so pill and button always line up. */
nav.tabs:has(button:nth-child(2).active)::before { left: calc(var(--space-1) + (100% - var(--space-2)) / 4); }
nav.tabs:has(button:nth-child(3).active)::before { left: calc(var(--space-1) + (100% - var(--space-2)) / 2); }
nav.tabs:has(button:nth-child(4).active)::before { left: calc(var(--space-1) + (100% - var(--space-2)) * 0.75); }
nav.tabs:not(:has(button.active))::before { opacity: 0; }

nav.tabs button {
  position: relative; z-index: 1; flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-1);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-1); border: none; border-radius: var(--radius-full); cursor: pointer;
  background: transparent; color: var(--text-muted);
  font-size: var(--font-sm); font-weight: var(--weight-body);
  transition: color var(--duration-fast) var(--ease-out);
}
nav.tabs .tabicon {
  width: 20px; height: 20px; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
nav.tabs button:hover { color: var(--text-secondary); }
nav.tabs button.active { color: var(--text); font-weight: var(--weight-emphasis); }
nav.tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* shared day bar */
.daybar { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-4) 0 var(--space-1); }
.daybar .lbl { flex: 1; text-align: center; font-weight: var(--weight-emphasis); font-size: var(--font-base); }
.daybar .lbl small { color: var(--text-secondary); font-size: var(--font-sm); font-weight: var(--weight-body); margin-left: var(--space-2); }
.navbtn {
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  border-radius: var(--radius-sm); cursor: pointer;
  min-height: var(--tap-min); min-width: var(--tap-min);
  padding: var(--space-2) var(--space-3); font-size: var(--font-base);
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.navbtn:hover { border-color: var(--border-strong); background: var(--surface-2); }
.navbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.navbtn.today { font-size: var(--font-sm); font-weight: var(--weight-emphasis); padding: var(--space-2) var(--space-3); }
/* hidden but still occupying its row, so pages don't jump when you switch tabs */
.daybar.ghost { visibility: hidden; }

section.view { display: none; margin-top: var(--space-3); }
section.view.active { display: block; }

.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--space-5); box-shadow: var(--shadow-sm); }
.card + .card { margin-top: var(--space-4); }
.card h2 { font-size: var(--font-base); font-weight: var(--weight-emphasis); margin: 0 0 var(--space-4); letter-spacing: 0; text-transform: none; color: var(--text); }

label { display: block; font-size: var(--font-sm); color: var(--text-secondary); margin: 0 0 var(--space-2); }
/* the parenthetical aside inside a field label */
.lbl-note { color: var(--text-muted); }
input, select, textarea {
  width: 100%; min-height: var(--tap-min); padding: var(--space-3); border-radius: var(--radius-sm);
  border: 1px solid var(--border-control); background: var(--surface-2); color: var(--text);
  font-size: var(--font-base); outline: none;
  transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
  font-family: inherit;
}
textarea { resize: vertical; min-height: 74px; }
/* A tick box is not a text field, and the rule above was never meant for it:
   width:100% + min-height:44px + 12px of padding turns a checkbox into a 90px
   square that shoves its own label off the end of the row — which is exactly
   what had happened to the reminder settings. Two components had already
   patched around it locally; this fixes it where it starts.
   Accent is correct here: a ticked box is the state of a control you operate. */
input[type="checkbox"], input[type="radio"] {
  width: 18px; height: 18px; min-height: 0; min-width: 0; padding: 0;
  flex: none; accent-color: var(--accent); cursor: pointer;
}
input[type="checkbox"] { border-radius: var(--radius-sm); }
input[type="radio"] { border-radius: 50%; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-quiet); }
.row { display: flex; gap: var(--space-3); align-items: stretch; }
.row > div { flex: 1; }
/* In a row, let labels grow so inputs bottom-align even when one label wraps. */
.row > .field { display: flex; flex-direction: column; }
.row > .field > label { flex: 1 0 auto; }
.row > .field > input, .row > .field > select { margin-top: auto; }
.field { margin-bottom: var(--space-4); }

.seg { display: flex; gap: var(--space-2); }
.seg button {
  flex: 1; min-height: var(--tap-min); padding: var(--space-3) var(--space-2);
  border-radius: var(--radius-sm); border: 1px solid var(--border-control);
  background: var(--surface-2); color: var(--text-secondary); cursor: pointer;
  font-size: var(--font-sm); font-weight: var(--weight-body);
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.seg button.active { background: var(--surface); border-color: var(--border-strong); color: var(--text); font-weight: var(--weight-emphasis); }
.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Difficulty is ordinal, so the three states are one hue getting stronger, not
   three unrelated hues. The dots inside the button carry the same information
   without relying on colour at all. */
.seg button[data-diff].active { color: var(--text); }
.seg button[data-diff="easy"].active { border-color: var(--difficulty-1); }
.seg button[data-diff="medium"].active { border-color: var(--difficulty-2); }
.seg button[data-diff="hard"].active { border-color: var(--difficulty-3); }

/* ---- difficulty dots: the non-colour channel -------------------------------
   Three dots, one/two/three filled. This is the primary encoding — the hue is
   reinforcement, so difficulty survives greyscale, colour blindness and a
   washed-out phone screen in daylight. */
.diffdots { display: inline-flex; align-items: center; gap: 3px; flex: none; }
.diffdots i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--difficulty-track); display: block;
}
.diffdots[data-diff="easy"] i:nth-child(-n+1),
.diffdots[data-diff="medium"] i:nth-child(-n+2),
.diffdots[data-diff="hard"] i:nth-child(-n+3) { background: var(--difficulty-2); }
.diffdots[data-diff="easy"] i:nth-child(-n+1) { background: var(--difficulty-1); }
.diffdots[data-diff="hard"] i:nth-child(-n+3) { background: var(--difficulty-3); }

.btn {
  width: 100%; min-height: var(--tap-min); padding: var(--space-3) var(--space-4);
  border: 1px solid transparent; border-radius: var(--radius-sm); cursor: pointer;
  background: var(--accent); color: var(--text-on-accent);
  font-size: var(--font-base); font-weight: var(--weight-emphasis); margin-top: var(--space-2);
  transition: background var(--duration-fast) var(--ease-out);
}
.btn:hover { background: var(--accent-hover); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:disabled { opacity: .5; cursor: default; }
.btn.secondary { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.btn.secondary:hover { background: var(--surface); border-color: var(--border-strong); }
/* Red lives here and nowhere else: an action that destroys something. */
.btn.danger { background: var(--danger); color: var(--on-danger); border-color: transparent; }
.btn.danger:hover { background: var(--danger-hover); }
.btn.small { width: auto; min-height: var(--tap-min); padding: var(--space-2) var(--space-4); font-size: var(--font-sm); margin: 0; }

/* list header + progress bar */
.listhead { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-3); }
.listhead h2 { margin: 0; }
.taskcount { font-size: var(--font-sm); color: var(--text-muted); white-space: nowrap; }
.progressbar { height: 6px; border-radius: var(--radius-full); background: var(--surface-sunken); border: 1px solid var(--border); overflow: hidden; margin-bottom: var(--space-4); }
.progressbar > span { display: block; height: 100%; width: 0; border-radius: var(--radius-full);
  background: var(--text-muted); transition: width var(--duration-slow) var(--ease-out); }

.task-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.task-item { display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-1); background: none;
  border: none; border-top: 1px solid var(--border); border-radius: 0; }
.task-item:first-child { border-top: none; padding-top: var(--space-1); }
/* break-word split "dissertation" mid-syllable when the column was tight.
   With the row wider that is no longer needed, and normal wrapping reads far
   better; overflow-wrap still catches a genuinely unbreakable string. */
.task-item .name { word-break: normal; overflow-wrap: anywhere; }
.chk {
  position: relative;
  width: 24px; height: 24px; flex: none; border-radius: var(--radius-sm); border: 2px solid var(--border-control);
  background: transparent; cursor: pointer; display: grid; place-items: center; padding: 0; color: var(--text-muted);
  font-size: var(--font-sm); font-weight: var(--weight-emphasis); line-height: 1;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
/* The box stays 24px because a 44px square would dominate the row, but the
   thing you actually hit with a thumb is 44px. */
.chk::after {
  content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: var(--tap-min); height: var(--tap-min);
}
/* The tick box is an affordance, not the action to take now — accent shows up
   on hover, where the pointer already is, and stays off the resting state so
   the accent budget isn't spent once per row. */
.chk:hover { border-color: var(--accent); }
.chk:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chk.on { background: var(--surface-sunken); border-color: var(--border); color: var(--text-muted); }
.ti-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.task-item .name { font-weight: var(--weight-emphasis); font-size: var(--font-base); min-width: 0; word-break: break-word; }
.task-item.done { opacity: .6; }
.task-item.done .name { text-decoration: line-through; color: var(--text-muted); }
.ti-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.chip { font-size: var(--font-sm); font-weight: var(--weight-body); color: var(--text-muted); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-full); padding: 2px var(--space-2); white-space: nowrap; }
/* Duration and difficulty are read constantly and never tapped, so they lose
   the pill and become text. Three bordered capsules per row was the single
   biggest source of visual weight in the list. */
.ti-meta .chip.dur, .ti-meta .chip.diff { background: none; border: none; padding: 0; }
.ti-meta .chip.dur + .chip.diff::before { content: "·"; margin-right: var(--space-2); color: var(--border-strong); }
.chip.dur { font-variant-numeric: tabular-nums; }
.chip.diff { display: inline-flex; align-items: center; gap: var(--space-2); }
.ti-actions { display: flex; gap: var(--space-1); flex: none; align-self: flex-start; }
.iconbtn {
  position: relative;
  cursor: pointer; color: var(--text-muted); border: none; background: none;
  font-size: var(--font-base); min-width: var(--tap-min); min-height: var(--tap-min);
  display: grid; place-items: center; border-radius: var(--radius-sm); line-height: 1;
  transition: color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
/* In a task row the visible box shrinks to 32px but the thing a thumb hits
   stays 44px, so the row gets ~40px of its width back and task names stop
   wrapping after three words. */
.ti-actions .iconbtn { min-width: 32px; min-height: 32px; }
.ti-actions .iconbtn::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: var(--tap-min); height: var(--tap-min);
}
.iconbtn:hover { color: var(--text); background: var(--surface-sunken); }
.iconbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.iconbtn.del:hover { color: var(--danger); background: var(--danger-quiet); }
.empty { color: var(--text-muted); font-size: var(--font-base); text-align: center; padding: var(--space-6) 0; }
/* a hint that sits above content rather than standing in for it */
.empty.small { padding: var(--space-3) var(--space-4); margin-top: var(--space-3); font-size: var(--font-sm);
  line-height: var(--leading-base); border: 1px dashed var(--border); border-radius: var(--radius-md); }

details.ai { margin-top: var(--space-4); border-top: 1px solid var(--border); padding-top: var(--space-4); }
details.ai > summary { cursor: pointer; font-size: var(--font-sm); color: var(--text-secondary); font-weight: var(--weight-emphasis); list-style: none; min-height: var(--tap-min); display: flex; align-items: center; }
details.ai > summary::-webkit-details-marker { display: none; }
.hint { font-size: var(--font-sm); color: var(--text-muted); margin: var(--space-2) 0; }
.typehint { font-size: var(--font-sm); color: var(--text-secondary); margin-top: var(--space-2); display: none; }
.slot.fixed .slot-card { box-shadow: inset 0 0 0 1px var(--border-strong); }
.status { font-size: var(--font-sm); margin-top: var(--space-2); min-height: 18px; color: var(--text-secondary); }
/* A request that genuinely failed. Not workload, not a judgement about the
   user's day — the one other place the danger hue is allowed. */
.status.err { color: var(--danger); }
.status.ok { color: var(--text-secondary); }
/* A degraded parse is not an error — the task was still created. It reads as
   information, not as something the user has to fix. */
.status.warn { color: var(--text-secondary); }
.quotanote { font-size: var(--font-sm); color: var(--muted); margin-top: var(--space-2); }

.lead { color: var(--text-secondary); font-size: var(--font-base); margin: 0 0 var(--space-1); }
.steps { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.steps li { display: flex; gap: var(--space-3); align-items: flex-start; }
.steps .n { flex: none; width: 26px; height: 26px; border-radius: 50%; background: var(--surface-2); border: 1px solid var(--border); display: grid; place-items: center; font-weight: var(--weight-emphasis); font-size: var(--font-sm); color: var(--text); }
.steps .txt b { color: var(--text); }
.steps .txt { color: var(--text-secondary); font-size: var(--font-base); }

.stats { display: flex; gap: var(--space-3); flex-wrap: wrap; }
.stat { flex: 1; min-width: 120px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-4); }
.stat .v { font-size: var(--font-lg); font-weight: var(--weight-emphasis); }
.stat .k { font-size: var(--font-sm); color: var(--text-muted); margin-top: 2px; }
.glancebtns { display: flex; gap: 10px; margin-top: 14px; }
.glancebtns .btn { margin-top: 0; }

/* ---- "Add to your plan" tiles (Tasks tab) ----
   One quiet grid instead of a stack of competing forms. Every tile is the same
   shape so nothing shouts; the accent only appears on hover/focus. */
.addgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); }
.addtile {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); cursor: pointer; text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text);
  font: inherit; line-height: var(--leading-tight);
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.addtile:hover { border-color: var(--border-strong); background: var(--surface); }
.addtile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.addtile .at-ic { font-size: var(--font-lg); line-height: 1.1; margin-bottom: var(--space-1); }
.addtile b { font-size: var(--font-base); font-weight: var(--weight-emphasis); }
.addtile .at-sub { font-size: var(--font-sm); color: var(--text-muted); }
@media (max-width: 560px) {
  .addgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); }
  .addtile { padding: var(--space-3); }
}

/* Brief highlight on the Settings card a tile jumps you to, so it's obvious
   where you landed. */
.jumped { animation: jumpFlash 1.6s ease-out 1; }
@keyframes jumpFlash {
  0%, 12% { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
  100% { border-color: var(--line); box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) { .jumped { animation: none; } }

.glance { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; }
.ring { position: relative; width: 136px; height: 136px; flex: none; }
.ring svg { width: 136px; height: 136px; display: block; }
.ring .ringc { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.ring .big { font-size: var(--font-xl); font-weight: var(--weight-emphasis); }
.ring .sub { font-size: var(--font-sm); color: var(--text-muted); margin-top: var(--space-1); }
.ginfo { flex: 1; min-width: 190px; display: flex; flex-direction: column; gap: 12px; }
.streak { display: flex; align-items: center; gap: 12px; }
.streak .flame { font-size: var(--font-xl); line-height: 1; filter: grayscale(1) opacity(0.5); }
.streak.on .flame { filter: none; }
.streak .sv { font-size: var(--font-xl); font-weight: 800; letter-spacing: -.5px; }
.streak .sk { font-size: var(--font-sm); color: var(--muted); text-transform: uppercase; letter-spacing: .6px; }
.legend { display: flex; gap: 16px; font-size: var(--font-sm); color: var(--muted); }
.legend i { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
.giline { display: flex; justify-content: space-between; font-size: 14px; color: var(--muted); border-top: 1px solid var(--line); padding-top: 10px; }
.giline b { color: var(--text); }

/* work-hours presets + summary */
.presetrow { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 16px; }
.preset { min-height: var(--tap-min); padding: var(--space-2) var(--space-4); border-radius: var(--radius-full); border: 1px solid var(--border); background: var(--surface-2);
  color: var(--text-secondary); font-size: var(--font-sm); cursor: pointer; transition: border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out); }
.preset:hover { color: var(--text); border-color: var(--border-strong); }
.preset.active { color: var(--text); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--panel-2)); }
.worksum { color: var(--text); font-weight: 600; }

/* working-days selector */
.daysel { display: flex; flex-wrap: wrap; gap: 6px; }
.daysel button { flex: 1; min-width: 42px; min-height: var(--tap-min); padding: var(--space-2) var(--space-1); border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-muted); font-size: var(--font-sm); cursor: pointer; transition: border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out); }
.daysel button:hover { color: var(--text); border-color: var(--border-strong); }
.daysel button.on { background: var(--surface); border-color: var(--border-strong); color: var(--text); font-weight: var(--weight-emphasis); }

.curvewrap { margin: 4px 0 18px; background: var(--panel-2); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; }
.curvewrap .cap { font-size: var(--font-sm); color: var(--text-muted); margin-bottom: var(--space-2); }
svg.curve { width: 100%; height: 52px; display: block; }

.banner { padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); font-size: var(--font-base); margin: 0 0 var(--space-4); display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.banner.ok { background: var(--surface-2); border: 1px solid var(--border); color: var(--text-secondary); }
.banner.warn { background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--text); }
.banner .grow { flex: 1; min-width: 160px; }

/* ---- Proportional day grid: hour ruler + duration-sized blocks ---- */
/* The grid scrolls in its own pane so the full 24 hours are always reachable
   without stretching the page to a thousand-pixel column. --pane-h is shared with
   the week so every zoom level opens at the same size and nothing jumps. */
/* The day grid IS the day view, so it takes what is left of the viewport
   rather than sitting in a fixed porthole with empty page under it. The
   subtrahend is the app header, day bar, zoom control and hours row; the floor
   stops it collapsing on a short window. */
:root { --pane-h: max(420px, min(calc(100dvh - 330px), 900px)); }
@supports not (height: 100dvh) { :root { --pane-h: max(420px, min(calc(100vh - 330px), 900px)); } }
#schedule { min-height: var(--pane-h); }
.dg-scroll { height: var(--pane-h); overflow-y: auto; overscroll-behavior: contain;
  margin-top: 10px; padding: 8px 12px 8px 2px; border: 1px solid var(--border); border-radius: 14px;
  background: color-mix(in srgb, var(--surface-2) 35%, transparent); }
.daygrid { position: relative; --gutter: 60px; }
/* the vertical rail between ruler and blocks */
/* The spine of the day, shaped by the energy curve. It replaced a flat 2px
   rule, so it occupies exactly the space that rule did — centred on the same
   axis, just wider where the curve peaks. No colour: form is the whole signal,
   which keeps it a report rather than a judgement about a time of day. */
.dg-rail { position: absolute; top: 0; bottom: 0;
  left: calc(var(--gutter) + 6px - 5px); width: 12px; overflow: visible; }
/* --border-control, not --line. As a flat rule this was decoration and 1.58:1
   was the right weight for it; now that its shape carries the energy curve it
   is non-text information, and the house rule puts that at 3:1 or better. */
.dg-rail path { fill: var(--border-control); opacity: .5; }
/* hour ruler: label on the left, a faint line across the grid */
.dg-hour { position: absolute; left: 0; right: 0; height: 0; }
.dg-hour::after { content: ""; position: absolute; left: calc(var(--gutter) + 7px); right: 0; top: 0; border-top: 1px solid var(--border); }
.dg-hl { position: absolute; left: 0; top: -7px; width: var(--gutter); text-align: right; font-size: var(--font-sm);
  color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* live "now" line (today only) */
.dg-now { position: absolute; left: 0; right: 0; height: 0; z-index: 4; pointer-events: none; }
.dg-now::after { content: ""; position: absolute; left: calc(var(--gutter) + 7px); right: 0; top: 0; height: 2px;
  background: var(--text); border-radius: 2px; transform: translateY(-1px); }
.dg-now-dot { position: absolute; left: calc(var(--gutter) + 7px); top: 0; width: 10px; height: 10px; border-radius: 50%;
  background: var(--text); transform: translate(-50%, -50%); }
.dg-now-lbl { position: absolute; left: calc(var(--gutter) + 16px); top: 0; transform: translateY(-50%);
  font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--bg); background: var(--text);
  padding: 1px 7px; border-radius: 999px; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* the blocks live to the right of the rail */
.dg-body { position: absolute; top: 0; bottom: 0; left: calc(var(--gutter) + 20px); right: 0; }
/* working hours, so an empty day still reads as a day */
.dg-window { position: absolute; left: -14px; right: 0; border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: inset 2px 0 0 color-mix(in srgb, var(--accent) 32%, transparent); }
.dg-block { position: absolute; left: 0; right: 0; overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: var(--space-3); display: flex; align-items: flex-start; gap: var(--space-3); }
.dg-block.tight { padding-top: 4px; padding-bottom: 4px; align-items: center; }
/* short slots: one line only — the subtitle can't fit and would be sliced */
.dg-block.tight .info .s { display: none; }
.dg-block.micro { padding-top: 2px; padding-bottom: 2px; }
.dg-block.micro .info .t { font-size: var(--font-sm); line-height: var(--leading-tight); }
.dg-block.micro .badge { display: none; }
.dg-block.micro .brk-label { font-size: var(--font-sm); }
/* A colour-coded leading edge reads the day's shape faster than reading every
   badge. Difficulty is the ordinal ramp; everything else is structural. */
.dg-block.task, .dg-block.commit, .dg-block.rest.sleep { border-left-width: 3px; }
.dg-block.task.easy { border-left-color: var(--difficulty-1); }
.dg-block.task.medium { border-left-color: var(--difficulty-2); }
.dg-block.task.hard { border-left-color: var(--difficulty-3); }
.dg-block.goal { border-left-color: var(--text-secondary); }
.dg-block.commit { border-left-color: var(--border-strong); }
.dg-block.rest.sleep { border-left-color: var(--border-strong); }
/* A half-hour slot is only ~22px tall and a quarter-hour one ~11px. Those
   cannot host the body scale, so they use --font-micro, which exists for
   this grid alone — see tokens.css. */
.dg-block.micro { padding: 0 var(--space-3); border-radius: var(--radius-sm); }
.dg-block.micro .info .t { font-size: var(--font-micro); line-height: 1; }
.dg-block.micro .badge { display: none; }
.dg-block.micro .brk-label { font-size: var(--font-micro); line-height: 1; }
.dg-block.micro .gchk { width: 16px; height: 16px; margin: 0; border-width: 1.5px; }
.dg-block.micro .gchk::before { width: 16px; height: 16px; margin: -8px 0 0 -8px; border-width: 1.5px; }
.dg-block.micro .dg-when { font-size: var(--font-micro); }
.dg-block.nano { padding: 0 var(--space-3); border-radius: var(--radius-sm); }
.dg-block.nano .info .t { font-size: var(--font-micro); line-height: 1; }
.dg-block.nano .brk-label { font-size: var(--font-micro); line-height: 1; }
.dg-block.nano .gchk { display: none; }
.dg-block.nano .dg-when { display: none; }
/* A short block is ~270px wide once the hour axis has taken its column, which
   leaves the title about 120px — and the clock was taking 60 of them, so
   "Past paper practice" became "Past paper prac…". The block is already
   POSITIONED at its time and the axis is right beside it, so on a block too
   short to wrap, the clock is the thing that goes. Taller blocks keep it. */
.dg-block.tight .dg-when { display: none; }
.dg-block.nano::before { display: none; }
/* start time on the right of each block */
.dg-when { flex: none; margin-left: auto; align-self: flex-start; padding-left: var(--space-2); font-size: var(--font-sm);
  color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* a node where the block meets the rail */
.dg-block::before { content: ""; position: absolute; left: -14px; top: 13px; width: 11px; height: 11px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--text-muted); }
.dg-block.tight::before { top: 50%; transform: translateY(-50%); }
.dg-block.task::before { border-color: var(--text-secondary); }
/* task blocks open their detail page on click */
.dg-block.task[data-task] { cursor: pointer; transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out); }
.dg-block.task[data-task]:hover { border-color: var(--border-strong); background: var(--surface); }
.dg-block.fixed { box-shadow: inset 0 0 0 1px var(--border-strong); }
.dg-block.break, .dg-block.free { background: transparent; border-style: dashed; color: var(--text-muted); font-size: var(--font-sm); }
.dg-block.free { opacity: .82; }
.dg-block.break::before, .dg-block.free::before { border-color: var(--border); background: var(--border); }
.dg-block.lunch { border-style: solid; }
.brk-label { flex: 1; font-weight: 600; }
.brk-dur { font-variant-numeric: tabular-nums; font-size: var(--font-sm); }
/* commitments */
.dg-block.commit { background: var(--surface-2); border-color: var(--border-strong); }
.dg-block.commit::before { border-color: var(--border-strong); background: var(--text-muted); }
/* goals */
.dg-block.goal { background: var(--surface-2); border-color: var(--border-strong); }
.dg-block.goal::before { border-color: var(--border-strong); background: var(--text-secondary); }
.goaltag { font-size: var(--font-sm); color: var(--text-muted);
  border: 1px solid var(--border); border-radius: var(--radius-full); padding: 1px var(--space-2); margin-left: var(--space-1); vertical-align: middle; }
.gchk { position: relative; flex: none; width: var(--tap-min); height: var(--tap-min); margin: -10px; border: none;
  background: transparent; cursor: pointer; }
.gchk::before { content: ""; position: absolute; left: 50%; top: 50%; margin: -12px 0 0 -12px;
  width: 24px; height: 24px; border-radius: var(--radius-sm); border: 2px solid var(--border-strong);
  transition: border-color var(--duration-fast) var(--ease-out); }
.gchk:hover::before { border-color: var(--accent); }
.gchk:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Short blocks cannot host a 44px control without distorting the grid, so the
   tick shrinks back to its drawn size there rather than overflowing. */
.dg-block.tight .gchk, .dg-block.micro .gchk { width: 24px; height: 24px; margin: 0; }
.dg-block .info { flex: 1; min-width: 0; }
.dg-block .info .t { font-weight: var(--weight-emphasis); font-size: var(--font-base);
  overflow-wrap: anywhere; }
/* A 15-minute block is about 20px tall — there is only room for one line there,
   so those still clip. Anything taller can wrap. */
.dg-block.tight .info .t, .dg-block.micro .info .t, .dg-block.nano .info .t {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dg-block .info .t .pinicon { font-size: var(--font-sm); }
.dg-block .info .s { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); color: var(--text-muted); font-size: var(--font-sm); margin-top: var(--space-1); }
.dg-block .info .s .dchip { font-weight: var(--weight-emphasis); color: var(--text); font-variant-numeric: tabular-nums; }
.dg-block .info .s .tsep::before { content: "· "; color: var(--text-muted); }
.badge { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--font-sm); font-weight: var(--weight-body);
  letter-spacing: .5px; padding: 3px 9px; border-radius: 999px; white-space: nowrap; align-self: flex-start;
  color: var(--muted); background: var(--panel); border: 1px solid var(--line); }
.badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--difficulty-2); }
.badge.hard::before { background: var(--difficulty-3); }
.badge.medium::before { background: var(--difficulty-2); }
.badge.easy::before { background: var(--difficulty-1); }

/* "Now / Next" glance line */
.nownext { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.nn { flex: 1 1 180px; min-width: 0; display: block; font-size: var(--font-base); padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--surface-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nn b { display: inline-block; font-size: var(--font-sm); font-weight: var(--weight-emphasis); margin-right: var(--space-2); }
.nn small { color: var(--text-muted); }
.nn.now { border-color: var(--border-strong); background: var(--surface); }
.nn.now b, .nn.next b { color: var(--text-secondary); }

/* task check-off circle (neutral accent, vs the goal's green) */
.taskchk::before { border-radius: 50%; }

/* time-of-day states: past fades, the current block glows */
.dg-block.past { opacity: .5; }
.dg-block.past .badge { opacity: .7; }
.dg-block.live { border-color: var(--text-secondary); box-shadow: inset 0 0 0 1px var(--text-secondary); }
.dg-block.live::before { border-color: var(--text); background: var(--text); }
.nowtag { display: inline-block; margin-left: var(--space-2); vertical-align: middle; font-size: var(--font-sm);
  font-weight: var(--weight-emphasis); color: var(--bg); background: var(--text); padding: 1px var(--space-2); border-radius: var(--radius-full); }

/* rest bands (sleep / off-hours) — dashed, dimmer, shaded */
.dg-block.rest { border-style: dashed; color: var(--text-muted); background: var(--surface-sunken); }
.dg-block.rest .info .t { font-weight: var(--weight-emphasis); color: var(--text); }
.dg-block.rest::before { border-color: var(--border); background: var(--border); }
.dg-block.rest.sleep { background: var(--surface-sunken); border-color: var(--border);
  border-left-style: solid; }
.dg-block.rest.sleep::before { border-color: var(--border); background: var(--surface-2); }
.dg-block.rest .btn.small { margin: 0; }
/* being asleep isn't an alarm — the "live" ring goes calm on a rest block */
.dg-block.rest.live { border-color: var(--border-strong);
  box-shadow: inset 0 0 0 1px var(--border-strong); }
.dg-block.rest.live::before { border-color: var(--border-strong); background: var(--border-strong); box-shadow: none; }
.dg-block.rest .nowtag { background: var(--border-strong); }
.rest-ic { font-size: var(--font-base); line-height: var(--leading-tight); flex: none; }
@media (max-width: 520px) {
  /* one shorter pane height for both the day and the week — the fixed tab bar
     eats the bottom of the viewport, so leave it room */
  :root { --pane-h: max(380px, calc(100dvh - 300px)); }
  .daygrid { --gutter: 46px; }
  .dg-scroll { padding: 6px 8px 6px 2px; }
  .dg-block { padding: 7px 10px; gap: 8px; border-radius: 10px; }
  .dg-block .info .t { font-size: var(--font-base); }
  .dg-when { font-size: var(--font-sm); }
  /* the name earns the width here — difficulty keeps its dot, drops its word */
  .dg-block .badge { font-size: 0; gap: 0; padding: 4px; letter-spacing: 0; }
}

.why { margin-top: var(--space-5); font-size: var(--font-sm); color: var(--text-secondary); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-4); }
.why b { color: var(--text); }
details.why > summary { cursor: pointer; list-style: none; font-weight: var(--weight-emphasis); color: var(--text);
  min-height: var(--tap-min); display: flex; align-items: center; }
details.why > summary::-webkit-details-marker { display: none; }
details.why > summary::after { content: " \25be"; color: var(--text-muted); margin-left: var(--space-2); }
details.why[open] > summary::after { content: " \25b4"; }
details.why > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
details.why p { margin: var(--space-3) 0 0; }
.why ul { margin: var(--space-3) 0 0; padding-left: var(--space-5); }
.why li { margin-bottom: var(--space-1); }

/* ---- Task detail page ---- */
.td-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.td-top .btn { margin: 0; }
.td-card { border-left: 4px solid var(--border-strong); }
.td-card.hard { border-left-color: var(--difficulty-3); }
.td-card.medium { border-left-color: var(--difficulty-2); }
.td-card.easy { border-left-color: var(--difficulty-1); }
.td-card.done { opacity: .82; }
.td-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.td-donetag { font-size: var(--font-sm); color: var(--text-secondary); }
.td-name { width: 100%; font-size: var(--font-lg); font-weight: var(--weight-emphasis); color: var(--text);
  background: transparent; border: none; border-bottom: 1px dashed transparent; border-radius: 0; padding: 2px 0 6px; margin: 0 0 14px; }
.td-name:hover { border-bottom-color: var(--border); }
/* An editable field needs a focus indicator that does not depend on noticing a
   1px underline changed colour, so the ring is real. */
.td-name:focus { outline: none; border-bottom-color: var(--accent); box-shadow: none; }
.td-name:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.td-card.done .td-name { text-decoration: line-through; color: var(--text-muted); }
.td-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.td-row { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-3) var(--space-4); background: var(--surface); }
.td-k { font-size: var(--font-sm); color: var(--text-muted); }
.td-v { font-size: var(--font-base); font-weight: var(--weight-emphasis); color: var(--text); }
.td-v.warn { color: var(--text-secondary); }
.td-insight { margin-top: var(--space-4); font-size: var(--font-sm); color: var(--text-secondary); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--space-3); }
.td-insight b { color: var(--text); }
.td-label { display: block; font-size: var(--font-sm); color: var(--text-muted); margin: var(--space-5) 0 var(--space-2); }
/* sub-steps checklist */
.td-check { margin-top: 18px; }
.tc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.tc-count { font-size: var(--font-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.tc-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.tc-item { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface-2); }
.tc-chk { position: relative; flex: none; width: 24px; height: 24px; border-radius: var(--radius-sm); border: 2px solid var(--border-strong);
  background: transparent; color: var(--text-muted); font-size: var(--font-sm); line-height: 1; cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out); }
.tc-chk::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: var(--tap-min); height: var(--tap-min); }
.tc-chk:hover { border-color: var(--accent); }
.tc-chk.on { background: var(--surface-sunken); border-color: var(--border); }
.tc-text { flex: 1; min-width: 0; font-size: var(--font-base); word-break: break-word; }
.tc-item.done .tc-text { text-decoration: line-through; color: var(--text-muted); }
.tc-del { flex: none; width: var(--tap-min); height: var(--tap-min); border-radius: var(--radius-sm); border: 1px solid transparent; background: transparent;
  color: var(--text-muted); font-size: var(--font-base); line-height: 1; cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out); }
.tc-del:hover { color: var(--danger); border-color: var(--danger); }
.tc-add { display: flex; gap: var(--space-2); }
.tc-add .tc-input { flex: 1; margin: 0; padding: var(--space-2) var(--space-3); font-size: var(--font-base); }
.tc-add .btn.small { margin: 0; flex: none; }
.td-notes { width: 100%; min-height: 120px; resize: vertical; font-size: var(--font-base); line-height: var(--leading-base); }
.td-foot { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-5); }
.td-foot .btn { width: auto; margin: 0; flex: 0 1 auto; }
@media (max-width: 480px) { .td-grid { grid-template-columns: 1fr; } .td-foot .btn { flex: 1 1 100%; } }

@media (max-width: 560px) {
  .slot { grid-template-columns: 58px 1fr; column-gap: 18px; }
  .timeline::before { left: 66px; }
  .slot::before { left: 60px; }
  .slot-time .st { font-size: 12px; }
}
footer { text-align: center; color: var(--muted); font-size: 12.5px; margin-top: 40px; }

/* trademark superscript on brand wordmarks */
.tm { font-size: .5em; font-weight: 700; vertical-align: super; letter-spacing: 0; opacity: .7; margin-left: 1px; }

/* in-app footer — legal links present on every app screen */
.appfooter { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 40px; }
.appfooter .af-note { margin: 0; }
.appfooter .af-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 16px; }
.appfooter .af-links a { color: var(--muted); text-decoration: none; font-size: 12.5px; transition: color .18s ease; }
.appfooter .af-links a:hover { color: var(--text); }
.appfooter .af-copy { margin: 0; font-size: 11.5px; }

/* Visible only to assistive technology. Used where an icon carries meaning a
   sighted reader gets from position — "Starts at" before a pinned time, "Part
   of" before an outcome — so the chip reads as a sentence rather than as a
   bare value. clip-path over `display:none` because hidden text is not
   announced at all. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Skip link. Off-screen until focused, then pinned top-left above everything —
   the landing's authbar sits at z-index 25 and the form overlay at 30, so this
   has to clear both or it focuses invisibly. */
.skiplink {
  position: fixed; top: -100px; left: var(--space-4); z-index: 60;
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  background: var(--accent); color: var(--text-on-accent);
  font-size: var(--font-base); font-weight: var(--weight-emphasis); text-decoration: none;
  transition: top var(--duration-fast) var(--ease-out);
}
.skiplink:focus { top: var(--space-4); }

/* The line under a sign-in form saying what the address is used for. */
.authconsent { font-size: var(--font-sm); color: var(--muted); line-height: 1.5; margin: var(--space-3) 0 0; }
.authconsent a { color: var(--text); }

/* ===================== AUTH / LANDING ===================== */
.auth {
  position: fixed; inset: 0; overflow-y: auto; overflow-x: hidden;
  background: radial-gradient(1000px 700px at 15% 10%, rgba(108,140,255,.25), transparent 55%),
              radial-gradient(900px 600px at 90% 90%, rgba(138,108,255,.22), transparent 55%), var(--bg);
  /* The landing is one continuous dark cinematic stage — the hero, the phone mock
     and every section below share it. It deliberately does NOT follow the app's
     light/dark toggle (that governs the signed-in app), so the dark palette is
     pinned here. Without this, light mode left a hard-coded dark hero sitting on
     white sections, with washed-out buttons. Re-declaring the custom properties on
     .auth makes every var()-driven surface and control inside the landing dark,
     whatever data-theme is on <html>. */
  color-scheme: dark;
  --bg: #06050d; --panel: #100e20; --panel-2: #17142e; --line: #2a2552;
  --text: #ecebf9; --muted: #9c98ca; --accent: #6b8cff; --accent-2: #a26cff;
  --hard: #ff6b6b; --medium: #ffb454; --easy: #4ecb8d;
  --shadow: 0 14px 40px rgba(0, 0, 0, 0.55);

  /* The block above pins the LEGACY aliases, which is what the landing's own
     sections read. It did not pin the Phase 1 tokens, and the sign-in card is
     built from app components that read those — so in LIGHT theme the email
     input rendered --text (#ecebf9, pinned light) on --surface-2 (#F0F1F5,
     resolved from :root): 1.04:1, invisible. Its label was 2.56:1. The landing
     claims to be theme-independent, so it has to pin the whole neutral set, not
     half of it. Ratios against --panel #100e20:
       --text-secondary 11.09  --text-muted 7.02  --border-control 3.88
       --text on --surface-2 15.15   --text-on-accent on --accent 6.30 */
  --surface: #100e20; --surface-2: #17142e; --surface-sunken: #06050d;
  --border: #2a2552; --border-strong: #3f3f6b; --border-control: #6F6AA6;
  --text-secondary: #C6C2E6; --text-muted: #9c98ca;
  --accent-hover: #9DAFFF; --accent-quiet: #1f2547;
  /* White on the landing's lighter accent is 3.08:1. The accent is the brand,
     so the ink flips instead of the button: 6.30:1. */
  --text-on-accent: #0B0D14;
  --danger: #ff6b6b; --danger-hover: #ff8f8f; --danger-quiet: #2a1717;
  --on-danger: #0B0D14;
}

/* persistent top bar — brand left, Log in + theme right — stays pinned while scrolling */
.authbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 25;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 18px;
}
.authbar-brand { display: flex; align-items: center; gap: 10px; }
.authbar-actions { display: flex; align-items: center; gap: 10px; }
/* The landing is always dark, so the theme toggle here would be a control that
   does nothing visible. Hide it — the app carries its own toggle (header + settings). */
.authbar .theme-toggle { display: none; }
.blogo.sm { width: 34px; height: 34px; border-radius: 10px; font-size: 19px; }
.brandname.sm { font-size: 19px; letter-spacing: -.4px; }
.barlogin {
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--text);
  border-radius: 10px; padding: 8px 16px; font-size: 13.5px; font-weight: 700; cursor: pointer;
  transition: border-color .15s ease, background .15s ease, transform .1s ease;
}
.barlogin:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 22%, transparent); }
.barlogin:active { transform: scale(.96); }

/* back-to-top — fixed to the viewport (no transformed ancestor between it and
   the root, so `fixed` really is the viewport here), hidden until you're a screen
   in. Living inside #auth means it disappears with the landing on sign-in. */
.totop {
  position: fixed; right: 18px; bottom: 18px; z-index: 20;
  width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center;
  font-size: 19px; line-height: 1; cursor: pointer; color: var(--text);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
  /* No backdrop-filter: this is fixed over the scrolling content, so a blurred
     backdrop has to be re-rasterised every single scrolled frame. An opaque
     background costs nothing and looks the same on a dark page. */
  background: color-mix(in srgb, var(--panel-2) 97%, transparent);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .45);
  opacity: 0; transform: translateY(12px); pointer-events: none;
  transition: opacity .25s ease, transform .25s ease, border-color .15s ease, background .15s ease;
}
.totop.show { opacity: 1; transform: none; pointer-events: auto; }
.totop:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, var(--panel-2)); }
.totop.show:active { transform: scale(.94); }
@media (max-width: 640px) { .totop { right: 14px; bottom: 14px; width: 44px; height: 44px; } }
@media (prefers-reduced-motion: reduce) { .totop { transition: opacity .25s ease; transform: none; } }

/* choice = first full-height screen (in flow); form = fixed overlay revealed on choice */
.authpanel {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 26px; padding: 72px 24px 40px; text-align: center;
}
.authpanel.choice { position: relative; min-height: 100vh; }
.authpanel.form {
  position: fixed; inset: 0; z-index: 30; overflow-y: auto;
  background: radial-gradient(1000px 700px at 15% 10%, rgba(108,140,255,.25), transparent 55%),
              radial-gradient(900px 600px at 90% 90%, rgba(138,108,255,.22), transparent 55%), var(--bg);
}
.authpanel > * { position: relative; z-index: 1; }   /* above the star canvas */

.authhero { max-width: 620px; width: 100%; }
.authhero .blogo { width: 54px; height: 54px; border-radius: 16px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); display: grid; place-items: center; font-size: 30px; box-shadow: var(--shadow); }
.authhero h1 { font-size: 31px; font-weight: 700; margin: 8px auto 12px; letter-spacing: -.5px; line-height: 1.12; max-width: 460px; }
.authhero p { color: var(--muted); font-size: 16px; margin: 0 auto; max-width: 430px; line-height: 1.55; }

/* choice screen — two big buttons */
.choicebtns { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.choicebtns .btn { min-width: 180px; font-size: 15px; padding: 13px 22px; }

/* =============== CINEMATIC HERO (easemize port, re-themed) =============== */
/* Tall wrapper: its height is the scroll distance over which the hero stays
   stuck. The hero sticks for (wrapper height − 100vh), so this is 100vh of
   scrolling for the dive. It was 360vh (260vh of dive), which put the hero at
   roughly half the whole landing page — you were still diving when you expected
   to be reading. One screen of scroll is enough to read as cinematic. */
.hero-scroll { position: relative; height: 200vh; z-index: 3; }
.authpanel.choice.cinematic {
  background: radial-gradient(120% 80% at 50% -10%, #162C6D 0%, #0A101D 46%, #050914 100%);
  overflow: hidden; padding: 96px 24px 60px; justify-content: center;
  /* Native compositor pinning — no GSAP transform pin, so no wheel jitter. */
  position: sticky; top: 0; height: 100vh;
}
/* faint grid */
.cine-grid {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: .5;
  background-image:
    linear-gradient(color-mix(in srgb, #3B82F6 22%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, #3B82F6 22%, transparent) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: radial-gradient(120% 90% at 50% 30%, #000 30%, transparent 78%);
          mask-image: radial-gradient(120% 90% at 50% 30%, #000 30%, transparent 78%);
}
/* film grain */
.film-grain { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; opacity: .10; mix-blend-mode: overlay; }
/* The glow is a soft radial. It used to get there with `filter: blur(70px)` over a
   780x520 box — and the dive animates this element's opacity three separate times,
   so Chrome re-rasterised a 70px blur across half the viewport on every scrolled
   frame. Profiling the dive, that single declaration owned the stutter: dropping it
   took p90 frame time 14.0ms -> 7.2ms and removed every frame over 33ms.
   A gradient is already soft, so the blur was buying nothing a longer stop ramp
   can't do for free. Box is enlarged to cover the spread the blur used to add. */
.cine-glow {
  position: absolute; left: 50%; top: 4%; width: 940px; height: 660px; transform: translateX(-50%);
  z-index: 0; pointer-events: none; opacity: .55;
  background: radial-gradient(50% 50% at 50% 50%,
    rgba(59, 130, 246, .40) 0%,
    rgba(59, 130, 246, .27) 30%,
    rgba(59, 130, 246, .14) 52%,
    rgba(59, 130, 246, .05) 72%,
    rgba(59, 130, 246, 0) 88%);
}
.cine-inner {
  position: relative; z-index: 2; width: 100%; max-width: 1080px;
  display: grid; grid-template-columns: 1.05fr .95fr; gap: 40px; align-items: center;
}
.hero-text-wrapper { text-align: left; }
.cine-kicker { display: inline-flex; align-items: center; gap: 9px; font-size: 13px; font-weight: 800; letter-spacing: .5px; color: #E2E8F0; margin-bottom: 20px; }
.cine-kicker .blogo.xs { width: 30px; height: 30px; border-radius: 9px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); display: grid; place-items: center; font-size: 17px; }
.cine-title { font-size: clamp(34px, 5.4vw, 68px); font-weight: 800; line-height: 1.02; letter-spacing: -1.6px; margin: 0 0 20px; }
.cine-title span { display: block; }
/* The bottom stop used to be #A1A1AA, which dropped the headline's second line to
   a mid grey — over the star canvas and the grid it stopped reading as bright text.
   It still ramps (that's the "matte silver" look) but now bottoms out light enough
   to stay clearly legible against anything behind it.
   NB a dark text-shadow is NOT an option here: with background-clip:text the
   background paints first and the shadow paints over it, so a dark halo would
   darken the glyphs themselves. Separation comes from .cine-copy's scrim instead. */
.text-silver-matte {
  background: linear-gradient(180deg, #FFFFFF 0%, #F1F5F9 46%, #CBD5E1 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  text-shadow: 0 1px 0 rgba(255,255,255,.04);
}
/* plain text, so a dark halo is safe here and lifts it off the constellations */
.cine-sub {
  color: #CBD5E1; font-size: clamp(15px, 1.5vw, 19px); font-weight: 300; line-height: 1.6;
  max-width: 460px; margin: 0 0 30px; text-shadow: 0 1px 12px rgba(5, 9, 20, .85);
}
.cta-wrapper { display: flex; gap: 14px; flex-wrap: wrap; }
.cta-wrapper .btn { min-width: 168px; font-size: 15px; padding: 14px 24px; }

/* phone mockup */
.phone-widget {
  position: relative; justify-self: center; width: 290px; height: 590px;
  border-radius: 42px; padding: 12px;
  background: linear-gradient(160deg, #1b2540, #0b1120);
  border: 1px solid rgba(148,163,255,.18);
  box-shadow: 0 40px 90px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.08), 0 0 0 6px rgba(10,16,29,.6);
  /* preserve-3d lets the ::after back-plate sit behind the face in real depth so
     the slanted phone shows a chunky side edge (see below). will-change is left
     off deliberately — in Chromium it flattens preserve-3d, collapsing that edge. */
  transform-style: preserve-3d; backface-visibility: hidden; transform: translateZ(0);
}
/* Real thickness: a dark back-plate parented into the phone's 3D space. When the
   phone is slanted, its receding edges reveal this plate as the device's side,
   so it reads as a solid object instead of a paper-thin sheet. As the dive
   straightens the phone to flat, the plate hides directly behind the face. */
.phone-widget::after {
  content: ""; position: absolute; inset: -1px; border-radius: 43px;
  /* a lit top-left → dark bottom-right ramp so the exposed side catches light and
     the thickness reads against the dark page instead of vanishing into it */
  background: linear-gradient(125deg, #33477a 0%, #1a2442 26%, #0a1020 58%, #04060c 100%);
  transform: translateZ(-38px);
  box-shadow: 0 0 0 2px #05070d, 0 34px 60px rgba(0,0,0,.5);
}
/* thin machined highlight along the rim where the face meets the side */
.phone-widget::before {
  content: ""; position: absolute; inset: -1px; border-radius: 43px; z-index: 4;
  pointer-events: none; border: 1px solid rgba(150,170,255,.22);
  transform: translateZ(-6px);
}
.phone-notch { position: absolute; top: 14px; left: 50%; transform: translateX(-50%); width: 116px; height: 22px; background: #050914; border-radius: 0 0 16px 16px; z-index: 3; }
.phone-screen { position: relative; width: 100%; height: 100%; border-radius: 32px; overflow: hidden; background: radial-gradient(120% 60% at 50% 0%, #16204a, #0A101D 70%); }
/* discrete full-screen pages, swapped via translateY */
.phone-pages { position: absolute; inset: 0; display: flex; flex-direction: column; will-change: transform; }
.phone-page { flex: 0 0 100%; height: 100%; padding: 40px 17px 20px; display: flex; flex-direction: column; justify-content: center; gap: 13px; }
.pp-title { font-size: 15px; font-weight: 800; color: #F1F5F9; letter-spacing: -.2px; }
.pp-title.big { font-size: 19px; margin-top: 4px; }
.pp-cap { font-size: 11.5px; color: #A1A1AA; line-height: 1.5; margin: 2px 0 0; }
.pp-step { display: flex; align-items: center; gap: 10px; background: rgba(148,163,255,.06); border: 1px solid rgba(148,163,255,.12); border-radius: 12px; padding: 11px; }
.pp-step .pp-num { width: 22px; height: 22px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 12px; font-weight: 800; color: #fff; background: linear-gradient(135deg, var(--accent), var(--accent-2)); }
.pp-step div { display: flex; flex-direction: column; text-align: left; }
.pp-step b { font-size: 12.5px; color: #E2E8F0; }
.pp-step span { font-size: 10.5px; color: #A1A1AA; }
/* PAGE 1 home dashboard inside the phone — mirrors the real app Home:
   greeting + streak, a small·big·small ring scoreboard, an up-next card and a
   mini momentum strip. */
.pp-home { justify-content: flex-start; gap: 11px; padding-top: 30px; }
.ph-tabs { display: flex; gap: 15px; justify-content: center; font-size: 11px; font-weight: 700; color: #7c86b8; }
.ph-tabs .on { color: #F1F5F9; position: relative; }
.ph-tabs .on::after { content: ""; position: absolute; left: 0; right: 0; bottom: -5px; height: 2px; border-radius: 2px; background: var(--accent); }

/* greeting + streak header */
.ph-hero { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 6px; }
.ph-greet { display: flex; flex-direction: column; gap: 2px; text-align: left; font-size: 15px; font-weight: 800; color: #F1F5F9; letter-spacing: -.3px; }
.ph-greet .ph-date { font-size: 9.5px; font-weight: 600; color: #94a3d6; letter-spacing: .2px; }
.ph-greet .ph-date b { color: var(--accent); font-weight: 700; }
.ph-streak { display: inline-flex; align-items: center; gap: 6px; padding: 5px 9px; border-radius: 999px; background: rgba(148,163,255,.06); border: 1px solid rgba(255,180,84,.35); }
.ph-streak .flame { font-size: 15px; line-height: 1; }
.ph-stk-b { display: flex; flex-direction: column; line-height: 1.1; text-align: left; }
.ph-stk-b b { font-size: 11px; font-weight: 800; color: #fff; }
.ph-stk-b span { font-size: 8.5px; color: #A1A1AA; }

/* scoreboard: small · big lead · small, like the real Home */
.ph-score { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; justify-items: center; gap: 4px; margin: 2px 0; }
.ph-metric { display: flex; flex-direction: column; align-items: center; gap: 5px; min-width: 0; }
.ph-metric em { font-size: 8.5px; font-style: normal; font-weight: 700; letter-spacing: .3px; text-transform: uppercase; color: #94a3d6; }
.ph-metric.lead em { color: #E2E8F0; }
.ph-rank { font-size: 8.5px; font-weight: 800; background: linear-gradient(120deg, var(--accent), var(--accent-2)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.ph-ring { position: relative; display: grid; place-items: center; }
.ph-ring svg { display: block; width: 100%; height: 100%; }
.ph-ring.sm { width: 52px; height: 52px; }
.ph-ring.big { width: 96px; height: 96px; }
/* soft rotating halo behind the lead ring, echoing the real Home */
.ph-ring.big::before { content: ""; position: absolute; inset: -8px; border-radius: 50%; z-index: -1; opacity: .4; filter: blur(13px); background: conic-gradient(from 0deg, var(--accent), var(--accent-2), var(--accent)); }
@media (prefers-reduced-motion: no-preference) {
  .ph-ring.big::before { animation: phHalo 9s linear infinite; }
  @keyframes phHalo { to { transform: rotate(360deg); } }
}
.ph-trk { fill: none; stroke: rgba(148,163,255,.14); stroke-width: 6; }
.ph-arc { fill: none; stroke-width: 6; stroke-linecap: round; stroke-dasharray: 157.1; stroke-dashoffset: var(--o); }
.ph-rc { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.ph-rc b { font-weight: 800; color: #fff; line-height: 1; }
.ph-ring.sm .ph-rc b { font-size: 13px; }
.ph-ring.big .ph-rc b { font-size: 30px; letter-spacing: -1px; }
.ph-rc span { font-size: 8px; color: #A1A1AA; margin-top: 2px; }

/* up-next card */
.ph-next { display: flex; align-items: center; gap: 9px; background: rgba(148,163,255,.06); border: 1px solid rgba(148,163,255,.14); border-radius: 12px; padding: 9px 11px; }
.ph-nic { font-size: 13px; line-height: 1; flex: none; }
.ph-nbody { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; text-align: left; }
.ph-nwhen { font-size: 8px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; color: var(--accent); }
.ph-nbody b { font-size: 12px; font-weight: 700; color: #E2E8F0; }
.ph-narr { font-size: 15px; color: #7c86b8; font-weight: 700; flex: none; }

/* mini momentum strip */
.ph-mo { display: flex; flex-direction: column; gap: 6px; margin-top: 1px; }
.ph-mo-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 9px; font-weight: 800; letter-spacing: .5px; text-transform: uppercase; color: #94a3d6; }
.ph-mo-head .up { color: #4ecb8d; }
.ph-bars { display: flex; align-items: flex-end; gap: 3px; height: 34px; }
.ph-bars i { flex: 1; height: var(--h); min-height: 3px; border-radius: 3px; background: rgba(148,163,255,.16); }
.ph-bars i.on { background: linear-gradient(180deg, #4ecb8d, rgba(78,203,141,.4)); }

/* home screen cards (📅 Day planned / 🔥 streak / ✅ Task complete) */
.pp-cards { justify-content: flex-start; gap: 11px; }
.pp-card {
  display: flex; align-items: center; gap: 11px; text-align: left;
  background: rgba(148,163,255,.06); border: 1px solid rgba(148,163,255,.12);
  border-radius: 13px; padding: 12px 13px;
}
.pp-card .pc-ico { font-size: 22px; line-height: 1; flex: none; }
.pp-card div { display: flex; flex-direction: column; }
.pp-card b { font-size: 13px; color: #E2E8F0; font-weight: 700; line-height: 1.25; }
.pp-card div span { font-size: 11px; color: #A1A1AA; }

.pp-start { align-items: center; justify-content: center; text-align: center; gap: 12px; }
.pp-logo { width: 54px; height: 54px; border-radius: 16px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); display: grid; place-items: center; box-shadow: 0 12px 28px rgba(59,130,246,.5); }
.pp-cta { width: 100%; margin-top: 6px; font-size: 14px; padding: 12px; }
.pw-head { display: flex; align-items: center; justify-content: space-between; }
.pw-label { font-size: 12px; font-weight: 700; letter-spacing: .4px; color: #E2E8F0; text-transform: uppercase; }
.pw-live { font-size: 10px; font-weight: 700; color: #3B82F6; }
.pw-metric { position: relative; display: grid; place-items: center; height: 150px; }
.progress-ring { width: 150px; height: 150px; }
.pr-track { stroke: rgba(148,163,255,.14); }
.pr-arc { stroke: #3B82F6; stroke-dasharray: 327; stroke-dashoffset: 327; filter: drop-shadow(0 0 8px rgba(59,130,246,.6)); }
.pw-counter { position: absolute; display: flex; flex-direction: column; align-items: center; }
.counter-val { font-size: 46px; font-weight: 800; color: #fff; line-height: 1; letter-spacing: -1.5px; }
.pw-unit { font-size: 12px; color: #A1A1AA; margin-top: 2px; }
.mockup-scroll-wrapper { flex: 1; overflow: hidden; -webkit-mask-image: linear-gradient(#000 78%, transparent); mask-image: linear-gradient(#000 78%, transparent); }
.text-track { display: flex; flex-direction: column; gap: 9px; }
.pw-row { display: flex; align-items: center; gap: 9px; background: rgba(148,163,255,.06); border: 1px solid rgba(148,163,255,.12); border-radius: 11px; padding: 9px 11px; font-size: 12px; font-weight: 600; color: #E2E8F0; }
.pw-row .pw-t { color: #94a3d6; font-size: 10.5px; font-weight: 700; min-width: 30px; }
.pw-row .pw-bar { width: 3px; align-self: stretch; min-height: 14px; border-radius: 2px; background: var(--accent); }
.pw-bar.hard { background: #ff6b6b; } .pw-bar.med { background: #ffb454; } .pw-bar.easy { background: #4ecb8d; } .pw-bar.brk { background: #3f3f6b; }
.pw-brk { color: #94a3d6; font-style: italic; font-weight: 500; }
.floating-badge {
  position: absolute; display: flex; align-items: center; gap: 9px; z-index: 5;
  background: rgba(17,24,45,.92); border: 1px solid rgba(148,163,255,.2); border-radius: 13px;
  padding: 9px 12px; box-shadow: 0 18px 40px rgba(0,0,0,.5);
  font-size: 12px;
}
.floating-badge div { display: flex; flex-direction: column; text-align: left; }
.floating-badge b { font-size: 12px; color: #fff; font-weight: 700; line-height: 1.2; }
.floating-badge span { font-size: 10.5px; color: #A1A1AA; }
.floating-badge.fb1 { top: 40px; left: -66px; }
.floating-badge.fb2 { top: 300px; right: -58px; }
.floating-badge.fb3 { bottom: 66px; left: -54px; }

@media (max-width: 900px) {
  .cine-inner { grid-template-columns: 1fr; gap: 44px; justify-items: center; text-align: center; }
  .hero-text-wrapper { text-align: center; }
  .cine-sub { margin-left: auto; margin-right: auto; }
  .cta-wrapper { justify-content: center; }
  .floating-badge.fb1 { left: -20px; } .floating-badge.fb2 { right: -18px; } .floating-badge.fb3 { left: -14px; }
}
/* ---------------------------------------------------------------------------
   PHONE LAYOUT — the hero becomes two discrete "pages" instead of one crowded
   stage.

   The old layout stacked the headline block AND the tall phone mock inside a
   single 100vh sticky panel with overflow:hidden. On a phone that meant the mock
   sat entirely below the clip and was never visible at all — the dive zoomed
   something you couldn't see — and the "scroll to learn more" cue, laid out after
   the (viewport-tall) text block, was clipped away too.

   Now the two occupy the SAME stage and are shown in sequence by the scroll
   scene in fx.js:
     page 1 — motto + buttons, spread evenly down the screen, cue pinned bottom
     page 2 — text parts away, the phone rises into the centre
   The phone is absolutely centred rather than laid out in flow, so it is always
   inside the 100vh clip. Centring uses left/top + negative margins, NOT a
   translate — GSAP owns `transform` on the mock and would overwrite it.
   --------------------------------------------------------------------------- */
@media (max-width: 640px) {
  /* svh, not vh: on a phone 100vh is the height with the browser chrome HIDDEN,
     so a 100vh stage hangs under the address/tab bars and anything pinned to its
     bottom edge (the cue) sits behind them. svh is the with-chrome height, which
     is what's actually on screen at rest. */
  .authpanel.choice.cinematic { justify-content: flex-start; padding: 0 18px; height: 100svh; }
  /* the stage's full height, so the phone's top:50% is the viewport's centre */
  .cine-inner { display: block; gap: 0; min-height: 100svh; }

  /* page 1: three rows — brand, copy, buttons — spread down the screen. Padding
     clears the fixed authbar at the top and the pinned cue at the bottom. */
  .hero-text-wrapper {
    min-height: 100svh;
    padding: 76px 0 104px;
    display: flex; flex-direction: column; justify-content: space-between; align-items: center;
  }
  /* The authbar's brand and the hero kicker are the same wordmark ~60px apart on
     a phone, which reads as a duplication bug. The kicker is the bigger, nicer one
     and belongs to page 1's rhythm, so the bar drops its copy and keeps just the
     Log in button. */
  .authbar-brand { display: none; }
  /* flex-end because space-between would shove Log in to the left once the brand
     is gone. The scrim matters too: the bar floats over every section, and without
     it section headings scroll straight through the Log in button. Gradient rather
     than a solid bar so there's no hard edge where it ends. */
  .authbar {
    justify-content: flex-end; padding-bottom: 28px;
    background: linear-gradient(180deg, rgba(6, 5, 13, .92) 42%, rgba(6, 5, 13, 0));
  }
  .cine-kicker { margin: 0; font-size: 14px; text-shadow: 0 1px 10px rgba(5, 9, 20, .9); }
  .cine-copy { position: relative; display: flex; flex-direction: column; justify-content: center; gap: 14px; }
  /* Legibility scrim. The star canvas, the grid and the film grain all sit at
     z-index 0-1 inside the panel; .cine-inner is z-index 2, so a -1 layer here
     lands above all three but still behind the words — the constellations stop
     running through the headline. Soft-edged radial so there's no visible box.
     It rides .cine-copy, which the dive fades as one unit, so it leaves with the
     text instead of lingering as a dark smudge. */
  .cine-copy::before {
    content: ""; position: absolute; z-index: -1; inset: -16% -22%; pointer-events: none;
    background: radial-gradient(58% 54% at 50% 50%, rgba(5, 9, 20, .86), rgba(5, 9, 20, .55) 56%, rgba(5, 9, 20, 0) 80%);
  }
  .cine-title { font-size: clamp(30px, 9vw, 44px); letter-spacing: -1px; margin: 0; }
  /* Phone: drop the silver ramp entirely and go solid white.
     The gradient runs 180deg across the WHOLE h1, so with the motto on two lines
     the first line gets the white end and the second gets the #CBD5E1 end — which
     is why "praiseach dheth." read as greyed out. Narrow screens always wrap it to
     two lines, so on a phone the effect is guaranteed, not incidental. Solid fill
     also frees up a real dark halo: with background-clip:text the shadow paints
     over the glyphs, but plain colour takes it behind them, so the words sit off
     the star map without the scrim having to do all the work. */
  .cine-title.text-silver-matte {
    background: none;
    -webkit-background-clip: border-box; background-clip: border-box;
    color: #FFFFFF; -webkit-text-fill-color: #FFFFFF;
    text-shadow: 0 2px 16px rgba(5, 9, 20, .92), 0 1px 3px rgba(5, 9, 20, .7);
  }
  .cine-sub { font-size: 15px; line-height: 1.55; margin: 0 auto; max-width: 20rem; }
  .cta-wrapper { width: 100%; flex-direction: column; align-items: stretch; gap: 11px; }
  .cta-wrapper .btn { width: 100%; padding: 14px 20px; }

  /* the cue is pinned to the stage, not laid out after the viewport-tall text */
  .scrollcue { position: absolute; z-index: 6; left: 0; right: 0; bottom: 26px; width: max-content; margin: 0 auto; }

  /* page 2: the phone, dead centre of the same stage (see note above) */
  .phone-widget {
    position: absolute; left: 50%; top: 50%;
    width: 250px; height: 508px; margin: -254px 0 0 -125px;
    z-index: 3; pointer-events: none;
  }
  .phone-page { padding: 34px 14px 16px; }
  .pp-home { padding-top: 26px; }

  .floating-badge { display: none; }
}
@media (max-width: 380px) {
  .phone-widget { width: 224px; height: 456px; margin: -228px 0 0 -112px; }
}

/* Full-width normal pages the phone dissolves into on dive-in. Positioned over
   the pinned hero (inset:0), cross-faded in by GSAP. The `.cinematic` prefix
   lifts specificity above `.authpanel > *`, which would otherwise force these
   back to position:relative. Typography is sized for a full screen, not a phone. */
.cinematic .appscreens {
  position: absolute; inset: 0; z-index: 4; overflow: hidden;
  opacity: 0; pointer-events: none; will-change: opacity, transform;
  background: radial-gradient(120% 80% at 50% 0%, #16204a 0%, #0A101D 55%, #050914 100%);
}
.appscreen-track { position: absolute; inset: 0; display: flex; flex-direction: column; will-change: transform; }
.appscreen { flex: 0 0 100%; height: 100%; display: flex; align-items: center; justify-content: center; padding: 8vh 6vw; }
.as-inner { width: 100%; max-width: 840px; text-align: left; }
.as-title {
  font-size: clamp(30px, 5vw, 52px); font-weight: 800; letter-spacing: -1.4px; margin: 0 0 30px; line-height: 1.02;
  background: linear-gradient(180deg, #FFFFFF 0%, #E2E8F0 42%, #A1A1AA 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* Overview cards */
.as-cards { display: flex; flex-direction: column; gap: 16px; }
.as-card {
  display: flex; align-items: center; gap: 18px;
  background: rgba(148,163,255,.06); border: 1px solid rgba(148,163,255,.14); border-radius: 18px; padding: 20px 22px;
}
.as-card .as-ico { font-size: 32px; line-height: 1; flex: none; }
.as-card > div { display: flex; flex-direction: column; gap: 3px; }
.as-card b { font-size: clamp(17px, 2vw, 21px); color: #F1F5F9; font-weight: 700; }
.as-card > div span { font-size: clamp(13px, 1.4vw, 15px); color: #A1A1AA; }
/* Today schedule rows */
.as-rows { display: flex; flex-direction: column; }
.as-row {
  display: flex; align-items: center; gap: 18px; padding: 16px 4px;
  border-bottom: 1px solid rgba(148,163,255,.12); font-size: clamp(16px, 1.8vw, 20px); color: #E2E8F0; font-weight: 600;
}
.as-row .as-t { color: #94a3d6; font-weight: 700; min-width: 74px; font-variant-numeric: tabular-nums; }
.as-row .as-bar { width: 4px; align-self: stretch; min-height: 22px; border-radius: 2px; background: var(--accent); }
.as-bar.hard { background: #ff6b6b; } .as-bar.med { background: #ffb454; } .as-bar.easy { background: #4ecb8d; } .as-bar.brk { background: #3f3f6b; }
.as-name { flex: 1; } .as-brk { color: #94a3d6; font-style: italic; font-weight: 500; }
/* How-it-works steps */
.as-steps { display: flex; flex-direction: column; gap: 16px; }
.as-step {
  display: flex; align-items: center; gap: 18px;
  background: rgba(148,163,255,.06); border: 1px solid rgba(148,163,255,.14); border-radius: 18px; padding: 20px 22px;
}
.as-step .as-num {
  width: 44px; height: 44px; flex: none; border-radius: 50%; display: grid; place-items: center;
  font-size: 18px; font-weight: 800; color: #fff; background: linear-gradient(135deg, var(--accent), var(--accent-2));
}
.as-step > div { display: flex; flex-direction: column; gap: 3px; }
.as-step b { font-size: clamp(17px, 2vw, 21px); color: #E2E8F0; }
.as-step > div span { font-size: clamp(13px, 1.4vw, 15px); color: #A1A1AA; }

/* Static fallback (reduced-motion / no GSAP): the scroll scene never arms, so
   there is no pinned spacer to scroll through and the 100vh hero would be frozen.
   Let the phone grow to its natural height with all pages stacked, giving the
   #auth scroller real content height so the page scrolls normally. */
/* Collapse the tall sticky wrapper and un-stick the hero in the static fallback. */
.hero-scroll:has(.static-hero) { height: auto; }
/* Selector has to out-specify `.authpanel.choice.cinematic` (three classes), which
   otherwise pins this to a clipped 100vh box no matter how tall the stack gets. */
.authpanel.choice.cinematic.static-hero { position: static; height: auto; min-height: 100vh; overflow: visible; }
/* height:auto lets the stacked pages grow; the position/margin resets undo the
   phone-width rule that absolutely centres the mock in the scroll scene. */
.static-hero .phone-widget { position: static; left: auto; top: auto; margin: 0 auto; height: auto; }
.static-hero .hero-text-wrapper { min-height: 0; padding-bottom: 40px; }
.static-hero .scrollcue { position: static; margin: 0 auto; }
.static-hero .phone-screen { height: auto; overflow: visible; }
.static-hero .phone-pages { position: static; }
.static-hero .phone-page {
  flex: none; height: auto; min-height: 260px; justify-content: flex-start;
  border-top: 1px solid rgba(148,163,255,.10);
}
.static-hero .phone-page:first-child { border-top: none; }
/* badges are pinned to fixed offsets on the (now tall) phone — hide them here */
.static-hero .floating-badge { display: none; }

/* Normal closing screen — scrolled into after the pinned zoom sequence releases.
   A plain full-height CTA in normal flow (no phone / no zoom). */
.authpanel.outro {
  position: relative; min-height: 100vh; gap: 0;
  background: radial-gradient(120% 80% at 50% 115%, #162C6D 0%, #0A101D 46%, #050914 100%);
}
.outro-inner { max-width: 440px; display: flex; flex-direction: column; align-items: center; gap: 14px; }
.outro-inner .blogo {
  width: 62px; height: 62px; border-radius: 18px; display: grid; place-items: center;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: 0 14px 34px rgba(59,130,246,.5);
}
.outro-inner h2 { font-size: clamp(26px, 4vw, 42px); font-weight: 800; letter-spacing: -1px; line-height: 1.05; margin: 6px 0 0; }
.outro-inner p { color: #A1A1AA; font-size: 16px; line-height: 1.55; margin: 0; }
.outro .cta-wrapper { margin-top: 12px; justify-content: center; }
.outro .cta-wrapper .btn { min-width: 168px; font-size: 15px; padding: 14px 24px; }
.outro-foot { position: absolute; bottom: 22px; left: 0; right: 0; text-align: center; color: var(--muted); font-size: 12.5px; }

/* form screen — the login/signup card, revealed with an iris clip on choice */
.authcard { width: 100%; max-width: 400px; flex: none; text-align: left;
  background: var(--panel); border: 1px solid var(--line); border-radius: 18px; padding: 24px; box-shadow: var(--shadow); }
.authback {
  border: none; background: none; color: var(--muted); cursor: pointer;
  font-size: 13px; font-weight: 600; padding: 0 0 12px; margin: 0;
  transition: color .15s ease;
}
.authback:hover { color: var(--text); }
/* iris reveal: a circle clip-path grows from the click point */
@media (prefers-reduced-motion: no-preference) {
  .authpanel.form.revealing { animation: irisReveal .55s cubic-bezier(.3, .7, .2, 1) both; }
  @keyframes irisReveal {
    from { clip-path: circle(0 at var(--ox, 50%) var(--oy, 50%)); opacity: .4; }
    to   { clip-path: circle(150% at var(--ox, 50%) var(--oy, 50%)); opacity: 1; }
  }
  .authpanel.form.revealing .authcard { animation: cardRise .5s cubic-bezier(.2, .8, .3, 1.1) .12s both; }
  @keyframes cardRise { from { opacity: 0; transform: translateY(22px) scale(.97); } to { opacity: 1; transform: none; } }
}

@media (max-width: 820px) {
  .authpanel { gap: 22px; padding: 68px 20px 32px; }
  .brandname { font-size: 38px; letter-spacing: -1px; }
  .authhero .blogo { width: 50px; height: 50px; font-size: 29px; }
  .authhero h1 { font-size: 23px; }
  .authhero p { font-size: 15.5px; }
  .choicebtns .btn { min-width: 150px; }
}

.authtabs { display: flex; gap: 6px; background: var(--panel-2); border: 1px solid var(--line); border-radius: 11px; padding: 5px; margin-bottom: 18px; }
.authtabs button { flex: 1; padding: 9px; border: none; border-radius: 8px; background: transparent; color: var(--muted); font-weight: 600; font-size: 14px; cursor: pointer; transition: all .12s; }
.authtabs button.active { background: var(--panel); color: var(--text); box-shadow: 0 1px 3px rgba(0,0,0,.2); }
.autherr { color: var(--hard); font-size: 13px; min-height: 16px; margin-top: 8px; }
.authnote { font-size: 11px; color: var(--muted); margin-top: 16px; text-align: center; line-height: 1.5; }

/* Passwordless sign-in. One button per provider, stacked, full width: on a
   phone these are the only targets on the screen and there is no reason to make
   them share a row and shrink. */
.authproviders { display: grid; gap: var(--space-2); }
.authproviders .provider { display: flex; align-items: center; justify-content: center; gap: var(--space-2); margin-top: 0; }
.authdivider { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-3) 0; color: var(--muted); font-size: var(--font-sm); }
.authdivider::before, .authdivider::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* Account deletion. Separated from Close/Log out by a rule so a mis-tap on the
   way out of the sheet cannot land on it. */
.acctdanger { margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--border); display: grid; gap: var(--space-2); }
.acctdanger .btn { margin-top: 0; }
.acctconfirm { margin-top: var(--space-3); padding: var(--space-3); border: 1px solid var(--danger); border-radius: var(--radius-sm); background: var(--danger-quiet); }
.acctconfirm p { margin: 0 0 var(--space-2); }
.acctconfirm .hint { font-size: var(--font-sm); color: var(--muted); }

/* account button + modal */
.accountbtn { margin-left: 8px; border: 1px solid var(--line); background: var(--panel-2); color: var(--text); border-radius: 10px; padding: 8px 12px; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap; }
.accountbtn:hover { border-color: var(--accent); }

/* light/dark theme toggle */
.theme-toggle {
  border: 1px solid var(--line); background: var(--panel-2); color: var(--text);
  width: 38px; height: 38px; border-radius: 10px; cursor: pointer; font-size: 16px;
  display: grid; place-items: center; flex: none; line-height: 1;
  transition: border-color .15s ease, transform .1s ease;
}
.theme-toggle:hover { border-color: var(--accent); }
.theme-toggle:active { transform: scale(.92); }
/* The gear takes the auto margin so both it and the account button sit right,
   in that order — settings is a place you go, the account is who you are. */
header.app .headerbtn { margin-left: auto; }
.headerbtn {
  flex: none; width: 38px; height: 38px; padding: 0; cursor: pointer;
  display: grid; place-items: center; border-radius: var(--radius-md);
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.headerbtn svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }
.headerbtn:hover { color: var(--text); border-color: var(--border-strong); }
.headerbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.brandrow .theme-toggle { margin-left: auto; }

/* daily motivational quote */
.quote {
  position: relative; margin-top: 22px; padding: 14px 16px 14px 40px;
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--surface-2);
  color: var(--text-secondary); font-size: var(--font-base); font-style: italic; line-height: var(--leading-base); max-width: 440px;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.quote .qmark {
  position: absolute; left: 12px; top: 2px; font-size: var(--font-xl); line-height: 1;
  font-style: normal; color: var(--text-muted);
  font-family: Georgia, "Times New Roman", serif;
}
.quote.home { margin: var(--space-1) auto 0; text-align: center; padding: var(--space-4); border-left: none;
  border-top: 3px solid var(--border-strong); max-width: 520px; }
.quote.home .qmark { position: static; display: block; margin-bottom: -8px; }
@media (max-width: 820px) { .quote { margin-left: auto; margin-right: auto; } }

/* dancing skeleton logo (original art; limbs animated via SMIL in the SVG) */
.skelly {
  width: 82%; height: 82%; display: block; overflow: visible;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .35));
}
.modal { position: fixed; inset: 0; background: rgba(0,0,0,.55); display: flex; align-items: center; justify-content: center; padding: 20px; z-index: 50; overflow-y: auto; }
/* Zoomed in (or on a short window) the box can outgrow the viewport. A centred
   flex child would then be clipped at BOTH ends with no way to reach the close
   button, so cap its height and let it scroll inside instead. */
.modalbox { background: var(--panel); border: 1px solid var(--line); border-radius: 16px; padding: 22px; width: 100%; max-width: 420px; box-shadow: var(--shadow);
  max-height: 100%; overflow-y: auto; }
.modalbox h2 { font-size: var(--font-lg); font-weight: var(--weight-emphasis); margin: 0 0 var(--space-4); }
.acctavatar { width: 52px; height: 52px; border-radius: var(--radius-md); background: var(--surface-2); border: 1px solid var(--border); display: grid; place-items: center; font-size: var(--font-lg); font-weight: var(--weight-emphasis); color: var(--text); margin-bottom: var(--space-4); }
.acctrow { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-3) 0; border-top: 1px solid var(--border); font-size: var(--font-base); }
.acctrow:first-child { border-top: none; padding-top: 0; }
.acctrow .k { color: var(--text-muted); }
.acctrow .v { font-weight: 600; text-align: right; word-break: break-word; }
.modalbtns { display: flex; gap: 10px; margin-top: 18px; }


/* ===================== ONBOARDING WIZARD ===================== */
.onboard { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(3,2,10,.72); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.onboard-card { background: var(--panel); border: 1px solid var(--line); border-radius: 18px; padding: 26px 24px 20px;
  width: 100%; max-width: 460px; box-shadow: var(--shadow); animation: obin .28s cubic-bezier(.2,.7,.3,1) both;
  /* Fixed size across all steps so the box never jumps between questions. */
  height: min(86vh, 600px); display: flex; flex-direction: column; }
@keyframes obin { from { opacity: 0; transform: translateY(14px) scale(.98); } to { opacity: 1; transform: none; } }
.ob-head { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
/* Scrollable body: short steps centre (no top-loaded white space), tall steps scroll. */
.ob-body { flex: 1 1 auto; overflow-y: auto; min-height: 0; margin: 0 -6px; padding: 0 6px; }
.ob-body-inner { min-height: 100%; display: flex; flex-direction: column; justify-content: flex-start; }
/* Each step fills the body so its illustration can occupy the space below the question. */
.ob-body { scroll-behavior: smooth; overscroll-behavior: contain; }
.ob-body .ob-step { flex: 1 1 auto; display: flex; flex-direction: column; }
.ob-body::-webkit-scrollbar { width: 8px; }
.ob-body::-webkit-scrollbar-thumb { background: var(--line); border-radius: 8px; }
.ob-progress { display: flex; gap: 6px; }
.ob-dot { width: 26px; height: 5px; border-radius: 999px; background: var(--line); transition: background .25s; }
.ob-dot.on { background: var(--accent); }
.ob-count { font-size: 11px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
.ob-step h2 { font-size: 21px; margin: 0 0 6px; letter-spacing: -.3px; }
.ob-sub { font-size: 13.5px; color: var(--muted); margin: 0 0 18px; line-height: 1.5; }
/* Per-question illustration filling the space beneath the inputs */
.ob-art { margin-top: auto; padding-top: 16px; display: flex; align-items: flex-end; justify-content: center; min-height: 0; }
.ob-art svg { width: 100%; max-width: 280px; height: auto; max-height: 200px; overflow: visible; }
.ob-art.art-compact svg { max-height: 104px; max-width: 240px; }
/* All animations use transform/opacity only (compositor-friendly) to stay smooth. */
.art-wave, .art-peak, .art-spark, .art-halo, .art-steam, .art-bar { will-change: transform, opacity; }
@keyframes artWaveFlow { from { transform: translate3d(0,0,0); } to { transform: translate3d(-110px,0,0); } }
.art-wave { animation: artWaveFlow 7s linear infinite; }
.art-peak, .art-spark { transform-box: fill-box; transform-origin: center; }
.art-peak { animation: artPulse 2.6s ease-in-out infinite; }
.art-spark { animation: artPulse 2.1s ease-in-out infinite; }
@keyframes artPulse { 0%,100% { transform: scale(.82); opacity: .6; } 50% { transform: scale(1.2); opacity: 1; } }
.art-halo { animation: artHalo 3.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes artHalo { 0%,100% { transform: scale(1); opacity: .16; } 50% { transform: scale(1.28); opacity: .26; } }
.art-steam { opacity: 0; animation: artSteam 3.6s ease-in-out infinite; }
.art-steam.s2 { animation-delay: 1.2s; } .art-steam.s3 { animation-delay: 2.4s; }
@keyframes artSteam { 0% { opacity: 0; transform: translateY(6px); } 30% { opacity: .5; } 100% { opacity: 0; transform: translateY(-12px); } }
.art-bar { transform-box: fill-box; transform-origin: 50% 100%; animation: artGrow 3.4s ease-in-out infinite alternate; }
.art-bar.b2 { animation-delay: .3s; } .art-bar.b3 { animation-delay: .6s; } .art-bar.b4 { animation-delay: .9s; }
@keyframes artGrow { from { transform: scaleY(.55); } to { transform: scaleY(1); } }
@media (prefers-reduced-motion: reduce) { .ob-art *, .art-wave { animation: none !important; } }

.ob-choices, .ob-yesno { display: flex; flex-direction: column; gap: 10px; }
.ob-choices button, .ob-yesno button { display: grid; grid-template-columns: 34px 1fr; grid-auto-rows: auto; column-gap: 12px; align-items: center;
  text-align: left; padding: 13px 15px; border-radius: 13px; border: 1px solid var(--line); background: var(--panel-2);
  color: var(--text); cursor: pointer; transition: border-color .12s, background .12s; }
.ob-choices button:hover, .ob-yesno button:hover { border-color: color-mix(in srgb, var(--muted) 40%, var(--line)); }
.ob-choices button.on, .ob-yesno button.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--panel-2)); }
.ob-choices .obc-ic, .ob-yesno .obc-ic { grid-row: 1 / 3; font-size: 24px; }
.ob-choices b, .ob-yesno b { font-size: 15px; font-weight: 700; }
/* --text-muted clears 4.5:1 on every plain surface, but the SELECTED state
   mixes 12% accent into the card behind it (#dbdef0 light, #292e41 dark), which
   closes the gap from both directions — 4.03 and 3.89. The descriptor moves up
   one step to --text-secondary: 5.56 and 5.73 selected, 6.58 and 6.95 not. It
   is still visibly the quieter half of the pair under the bold label. */
.ob-choices span:last-child, .ob-yesno span:last-child { font-size: 12.5px; color: var(--text-secondary); }

/* Pick-exactly-two (onboarding pillar focus). Same card language as
   .ob-choices, minus the icon column — no icon slot needed here. */
.ob-picks { display: flex; flex-direction: column; gap: 8px; }
.ob-picks button {
  display: flex; flex-direction: column; gap: 2px; text-align: left; padding: 12px 14px;
  border-radius: 13px; border: 1px solid var(--line); background: var(--panel-2);
  color: var(--text); cursor: pointer; transition: border-color .12s, background .12s, opacity .12s;
}
.ob-picks button:hover { border-color: color-mix(in srgb, var(--muted) 40%, var(--line)); }
.ob-picks button.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--panel-2)); }
.ob-picks button.disabled { opacity: .45; cursor: default; }
.ob-picks b { font-size: 14.5px; font-weight: 700; }
.ob-picks span:last-child { font-size: 12.5px; color: var(--text-secondary); }

/* Weekly-pattern editor (Settings + onboarding) */
.weekeditor { display: flex; flex-direction: column; gap: 7px; }
.we-flex { display: flex; align-items: center; gap: 11px; font-size: 13px; color: var(--text); cursor: pointer;
  padding: 12px 14px; border: 1px solid var(--line); border-radius: 12px;
  background: color-mix(in srgb, var(--panel-2) 55%, transparent); margin-bottom: 6px; transition: border-color .15s; }
.we-flex:hover { border-color: color-mix(in srgb, var(--muted) 35%, var(--line)); }
.we-flex input { width: 17px; height: 17px; flex: none; accent-color: var(--accent); margin: 0; cursor: pointer; }
.weekrow, .we-anyday {
  display: grid; grid-template-columns: 44px 46px 1fr; gap: 12px; align-items: center;
  padding: 9px 13px; border-radius: 12px; border: 1px solid transparent;
  background: color-mix(in srgb, var(--panel-2) 55%, transparent); transition: border-color .15s, opacity .15s; }
.we-anyday { grid-template-columns: 46px 1fr; }
.weekrow:hover { border-color: color-mix(in srgb, var(--muted) 30%, var(--line)); }
.weekrow.off { opacity: .5; }
.weekrow .wd-name, .we-anyday .wd-name { font-weight: 600; font-size: 13px; letter-spacing: .2px; }
/* toggle switch */
.wd-toggle { position: relative; width: var(--tap-min); height: var(--tap-min); padding: 0; border: none;
  background: transparent; cursor: pointer; }
.wd-toggle::before { content: ""; position: absolute; left: 50%; top: 50%; margin: -12px 0 0 -21px;
  width: 42px; height: 24px; border-radius: var(--radius-full);
  border: 1px solid var(--border); background: var(--surface);
  transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out); }
.wd-toggle .wd-knob { position: absolute; top: 50%; left: 50%; margin: -9px 0 0 -19px; width: 18px; height: 18px; border-radius: 50%;
  background: var(--text-muted); transition: transform var(--duration-base) var(--ease-out), background var(--duration-fast) var(--ease-out); }
.wd-toggle.on::before { border-color: transparent; background: var(--accent); }
.wd-toggle.on .wd-knob { transform: translateX(18px); background: var(--text-on-accent); }
.wd-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* time range */
.wd-times { display: grid; grid-template-columns: 1fr auto 1fr; gap: 8px; align-items: center; }
.wd-time { width: 100%; padding: 8px 10px; margin: 0; font-size: var(--font-base); }
.wd-sep { color: var(--muted); font-size: var(--font-base); text-align: center; }
.we-hint { font-size: 12px; color: var(--muted); margin: 8px 2px 0; line-height: 1.5; }

/* End-of-day check-in (feedback loop) */
/* Flexes inside the fixed-height wizard card rather than claiming a fixed 52vh,
   which overflowed the card once the viewport got short (high zoom). */
.ci-list { display: flex; flex-direction: column; gap: 7px; flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.ci-item { display: flex; align-items: center; gap: var(--space-3); min-height: var(--tap-min); padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--surface-2); cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out); }
.ci-item.on { border-color: var(--border-strong); background: var(--surface-sunken); }
.ci-item input { width: 20px; height: 20px; flex: none; accent-color: var(--accent); margin: 0; }
.ci-item .ci-name { flex: 1; font-size: var(--font-base); }
.ci-item.on .ci-name { text-decoration: line-through; color: var(--muted); }
.ci-item .ci-meta { font-size: var(--font-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.hs-wrap { margin-top: var(--space-4); }


/* Person-model editors: commitments, goals, priorities */
.planeditor { display: flex; flex-direction: column; gap: var(--space-2); }
.pe-empty { font-size: var(--font-sm); color: var(--text-muted); margin: var(--space-1) var(--space-1) var(--space-2); line-height: var(--leading-base); }
.pe-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--surface-2); }
.pe-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pe-main b { font-size: var(--font-base); font-weight: var(--weight-emphasis); }
.pe-main span { font-size: var(--font-sm); color: var(--text-muted); }
.pe-del { flex: none; width: var(--tap-min); height: var(--tap-min); border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-muted); font-size: var(--font-lg); line-height: 1; cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out); }
.pe-del:hover { color: var(--danger); border-color: var(--danger); }
.pe-add { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px dashed var(--border); background: var(--surface-2); margin-top: var(--space-1); }
.pe-add .pe-name { width: 100%; }
.pe-days { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pe-day { flex: 1; min-width: 42px; min-height: var(--tap-min); padding: var(--space-2) 0; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text-muted); font-size: var(--font-sm); cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out); }
.pe-day.on { border-color: var(--border-strong); background: var(--surface); color: var(--text); font-weight: var(--weight-emphasis); }
.pe-times { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--space-2); align-items: center; }
.pe-times input, .pe-add input, .pe-add select { margin: 0; }
.pe-grid { display: grid; grid-template-columns: 1.2fr .8fr .9fr 1fr; gap: var(--space-2); }
.pe-grid input, .pe-grid select { padding: var(--space-2); font-size: var(--font-sm); }
.pe-add .btn.small { align-self: flex-start; background: var(--surface-2); color: var(--text); border-color: var(--border); }
.pe-add .btn.small:hover { background: var(--surface); border-color: var(--border-strong); }
/* priority sliders */
.weighteditor { display: flex; flex-direction: column; gap: 13px; }
.pe-weight { display: grid; grid-template-columns: 1fr 150px; gap: 12px 16px; align-items: center; }
.pw-head b { font-size: var(--font-base); font-weight: 700; display: block; margin-bottom: 2px; }
.pw-head span { font-size: var(--font-sm); color: var(--muted); display: block; line-height: 1.35; }
/* Custom filled slider with a percentage bubble that follows the thumb */
.pw-slide { position: relative; padding-top: 4px; }
.pe-weight input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; margin: 0; padding: 0;
  border: none; border-radius: 999px; cursor: pointer;
  background: linear-gradient(to right, var(--text-muted) 0%, var(--text-muted) var(--p,50%), var(--border) var(--p,50%), var(--border) 100%); }
/* This used to be `outline: none; box-shadow: none`, which removed the only
   focus indicator the slider had — a keyboard user could not see where they
   were. The ring goes on the track (the element that actually takes focus);
   :focus-visible keeps it off pointer interaction, which is what the original
   rule was really trying to avoid. */
.pe-weight input[type=range]:focus { outline: none; }
.pe-weight input[type=range]:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.pe-weight input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--accent); box-shadow: var(--shadow-sm); cursor: pointer; }
.pe-weight input[type=range]:active::-webkit-slider-thumb { transform: scale(1.18); }
.pe-weight input[type=range]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--surface); border: 2px solid var(--accent); cursor: pointer; }
.pw-bubble { position: absolute; top: -16px; transform: translateX(-50%); font-size: var(--font-sm); font-weight: var(--weight-emphasis);
  color: var(--text-secondary); background: var(--surface-2);
  border: 1px solid var(--border-strong); padding: 1px var(--space-2); border-radius: var(--radius-full);
  pointer-events: none; white-space: nowrap; transition: left var(--duration-fast) var(--ease-out); }
.pe-check { display: flex; align-items: flex-start; gap: var(--space-3); min-height: var(--tap-min); font-size: var(--font-sm); color: var(--text-secondary); cursor: pointer; line-height: var(--leading-base); }
.pe-check input { width: 20px; height: 20px; flex: none; margin-top: 1px; accent-color: var(--accent); }
@media (max-width: 560px) { .pe-grid { grid-template-columns: 1fr 1fr; } .pe-weight { grid-template-columns: 1fr; gap: 6px; } }

/* Per-day hours override panel (Schedule) */
.dayhours { background: color-mix(in srgb, var(--panel-2) 60%, transparent); border: 1px solid var(--line);
  border-radius: 13px; padding: 13px 15px; margin-bottom: 16px; }
.dh-row {
  padding: var(--space-2) var(--space-3); display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.dh-label { font-size: var(--font-sm); color: var(--text-secondary); }
.dh-label b { color: var(--text); font-weight: var(--weight-emphasis); }
.dh-tag { font-size: var(--font-sm); font-weight: 700; letter-spacing: .3px; text-transform: uppercase; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line));
  border-radius: 999px; padding: 2px 8px; margin-left: 6px; white-space: nowrap; }
.dh-edit { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 13px;
  padding-top: 13px; border-top: 1px solid var(--line); }
.dh-edit input[type=time] { width: auto; margin: 0; padding: 8px 10px; font-size: var(--font-base); }
.ob-actions { flex: none; display: flex; gap: 10px; margin-top: 18px; }
.ob-actions .btn { margin-top: 0; }
.ob-actions #obBack { flex: 0 0 auto; width: auto; padding-left: 18px; padding-right: 18px; }
.ob-actions #obNext { flex: 1; }
.ob-skip { flex: none; display: block; width: 100%; margin-top: 12px; background: none; border: none; color: var(--muted);
  font-size: 12.5px; cursor: pointer; padding: 6px; }
.ob-skip:hover { color: var(--text); }
#obOneThing { width: 100%; resize: vertical; font: inherit; }

/* Payoff screen (SPEC.md §4) — replaces the wizard body in place once Finish
   is pressed. Same card shell, same fixed height, so nothing jumps. */
.ob-payoff { flex: 1 1 auto; display: flex; flex-direction: column; gap: 16px; overflow-y: auto; text-align: center; justify-content: center; }
.ob-daybar { display: flex; height: 26px; border-radius: 999px; overflow: hidden; border: 1px solid var(--line); flex: none; }
.ob-daybar span { display: block; height: 100%; }
.ob-daybar .seg-recovery { background: linear-gradient(90deg, var(--accent), var(--accent-2)); }
.ob-daybar .seg-work { background: var(--muted); }
.ob-daybar .seg-upkeep { background: color-mix(in srgb, var(--accent) 45%, var(--muted)); }
.ob-daybar .seg-life { background: var(--line); }
.ob-daylegend { display: flex; flex-wrap: wrap; gap: 8px 16px; justify-content: center; font-size: 12.5px; color: var(--muted); }
.ob-daylegend b { color: var(--text); font-weight: 700; }
.ob-daylegend i { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 5px; vertical-align: middle; }
.ob-daylegend .seg-recovery { background: linear-gradient(90deg, var(--accent), var(--accent-2)); }
.ob-daylegend .seg-work { background: var(--muted); }
.ob-daylegend .seg-upkeep { background: color-mix(in srgb, var(--accent) 45%, var(--muted)); }
.ob-daylegend .seg-life { background: var(--line); }
.ob-scores { display: flex; justify-content: center; gap: 32px; }
.ob-scores b { display: block; font-size: 28px; font-weight: 800; line-height: 1; }
.ob-scores span { font-size: 12px; color: var(--muted); }

/* ===================================================================
   Visual polish — aurora bg, glass panels, gradient text, micro-motion
   =================================================================== */

/* Animated aurora — landing page only. It used to sit on body, which put a
   moving coloured wash behind the app as well; the app is a calm flat surface
   now, so this is scoped to the state where the landing is on screen. */
body:has(#auth:not([hidden]))::before {
  content: ""; position: fixed; inset: -25%; z-index: -1; pointer-events: none;
  background:
    radial-gradient(38% 38% at 20% 20%, rgba(108,140,255,.30), transparent 62%),
    radial-gradient(34% 34% at 82% 14%, rgba(150,100,255,.30), transparent 62%),
    radial-gradient(44% 44% at 72% 82%, rgba(90,110,255,.20), transparent 62%),
    radial-gradient(40% 40% at 24% 86%, rgba(170,120,255,.18), transparent 62%);
  filter: blur(28px);
}
@media (prefers-reduced-motion: no-preference) {
  body::before { animation: aurora 24s ease-in-out infinite alternate; }
  @keyframes aurora {
    0%   { transform: translate3d(0, 0, 0) scale(1); }
    50%  { transform: translate3d(2%, -2.5%, 0) scale(1.09); }
    100% { transform: translate3d(-2%, 2%, 0) scale(1.05); }
  }
}

/* Frosted glass is a landing-page treatment. App surfaces are opaque and flat
   so text sits on a predictable background at any scroll position. */
.authcard {
  background: color-mix(in srgb, var(--panel) 80%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(1.25);
  backdrop-filter: blur(16px) saturate(1.25);
  border-color: color-mix(in srgb, var(--line) 75%, transparent);
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,.06);
}
.card, .modalbox {
  background: var(--surface);
  border-color: var(--border);
  box-shadow: var(--shadow-sm);
}
.card, .now-card, .hs-panel, .pg-sec { transition: border-color var(--duration-fast) var(--ease-out); }
.card:hover, .now-card:hover, .hs-panel:hover, .pg-sec:hover { border-color: var(--border-strong); }

/* The landing keeps its gradient wordmark. The app's own heading is text. */
.authhero h1 {
  background: linear-gradient(120deg, var(--text), var(--accent) 85%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

@media (prefers-reduced-motion: no-preference) {
  .authhero .blogo { animation: floaty 5.5s ease-in-out infinite; }
  @keyframes floaty { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
  /* The dock settles in. Ease-out, no overshoot. */
  nav.tabs { animation: dockin var(--duration-slow) var(--ease-out) both; }
  @keyframes dockin {
    from { opacity: 0; transform: translate(-50%, 10px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
  }
}

/* view + modal entrance transitions */
@media (prefers-reduced-motion: no-preference) {
  section.view.active { animation: viewin var(--duration-base) var(--ease-out) both; }
  @keyframes viewin { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
  .authcard { animation: pop .42s ease both; }
  .authhero { animation: viewin .5s ease both; }
  .modal { animation: fadein var(--duration-fast) var(--ease-out) both; }
  .modalbox { animation: viewin var(--duration-base) var(--ease-out) both; }
  @keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
  @keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.97); } to { opacity: 1; transform: none; } }
}

.task-item { transition: background var(--duration-fast) var(--ease-out); }
.task-item:hover { background: var(--surface-2); border-radius: var(--radius-sm); }
.stat, .slot-card { transition: border-color var(--duration-fast) var(--ease-out); }

/* nicer scrollbars (WebKit) */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: var(--radius-sm); }
::-webkit-scrollbar-thumb:hover { background: var(--border-strong); }
::-webkit-scrollbar-track { background: transparent; }

/* ----- landing page refinements ----- */
.brandrow { display: flex; align-items: center; justify-content: center; gap: 14px; margin-bottom: 4px; }
.authhero .blogo { width: 62px; height: 62px; font-size: 35px; border-radius: 18px; }
.brandname { font-weight: 800; font-size: 56px; letter-spacing: -1.5px; line-height: 1; background: linear-gradient(120deg, var(--accent), var(--accent-2)); -webkit-background-clip: text; background-clip: text; color: transparent; }
.feat { gap: 11px; }
.feat .chip {
  display: flex; align-items: center; gap: 12px;
  background: color-mix(in srgb, var(--panel) 62%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  border-radius: 12px; padding: 11px 13px; color: var(--text); font-size: 13.5px;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  transition: border-color .15s ease, transform .15s ease;
}
.feat .chip:hover { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); transform: translateX(2px); }
.feat .ci { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; font-size: 16px; flex: none; }
.feat .ci.c1, .hpill .ci.c1 { background: rgba(108,140,255,.18); }
.feat .ci.c2, .hpill .ci.c2 { background: rgba(138,108,255,.18); }
.feat .ci.c3, .hpill .ci.c3 { background: rgba(255,180,84,.18); }

/* ---- compact value pills (replace the old text chips) ---- */
.heropills { display: flex; flex-wrap: wrap; justify-content: center; gap: 9px; }
.hpill {
  display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600;
  color: var(--text); padding: 7px 13px 7px 8px; border-radius: 999px;
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  transition: border-color .15s ease, transform .15s ease;
}
.hpill:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); transform: translateY(-2px); }
.hpill .ci { width: 24px; height: 24px; border-radius: 7px; display: grid; place-items: center; font-size: 13px; flex: none; }
.authcard { position: relative; overflow: hidden; }
.authcard::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px; z-index: 4;
  background: linear-gradient(90deg, var(--accent), var(--accent-2), var(--easy), var(--accent-2), var(--accent));
  background-size: 200% 100%; }
@media (prefers-reduced-motion: no-preference) {
  .authcard::before { animation: barsheen 6s linear infinite; }
  @keyframes barsheen { to { background-position: -200% 0; } }
}
.authcardhead { margin-bottom: 16px; text-align: center; }
.authcardhead h3 { margin: 0 0 4px; font-size: 19px; letter-spacing: -.3px; }
.authcardhead p { margin: 0; color: var(--muted); font-size: 13.5px; }
@media (max-width: 820px) {
  .feat { display: flex; }
  .brandrow { justify-content: center; }
}

/* ===================================================================
   FX layer (fx.js) — particles, spotlight cards, tilt, shiny text,
   count-up glow. Inspired by reactbits.dev / 21st.dev, done in vanilla.
   =================================================================== */

/* particle constellation canvas sits behind the landing content */
#auth .fx-canvas { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.authwrap { position: relative; z-index: 1; }

/* galaxy canvas behind the dashboard — runs in both themes (stars recolour per theme) */
.dashfx { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
/* soften the aurora a touch in light mode so it reads cleanly on white */
:root[data-theme="light"] body::before { opacity: .6; filter: blur(36px); }

/* cursor-following spotlight glow on cards (fed --spot-x/--spot-y by fx.js).
   Same treatment on .now-card / .hs-panel / .pg-sec as the original .card —
   those three didn't exist yet when this was written; they're the same kind
   of surface and were the one inconsistency between how Home/Progress panels
   felt and how everything else in the app already did. */
.card, .authcard, .now-card, .hs-panel, .pg-sec { position: relative; }
.card::after, .authcard::after, .now-card::after, .hs-panel::after, .pg-sec::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; z-index: 2; opacity: 0; transition: opacity .3s ease;
  background: radial-gradient(90px circle at var(--spot-x, 50%) var(--spot-y, 50%),
              color-mix(in srgb, var(--accent) 13%, transparent), transparent 70%);
}
.card.spot::after, .authcard.spot::after,
.now-card.spot::after, .hs-panel.spot::after, .pg-sec.spot::after { opacity: 1; }
/* keep the glow behind the form controls so it never washes over what you're typing in */
.card label, .card input, .card select, .card textarea, .card .seg, .card .btn,
.authcard label, .authcard input, .authcard select, .authcard textarea, .authcard .btn,
.now-card label, .now-card input, .now-card select, .now-card textarea, .now-card .seg, .now-card .btn,
.hs-panel label, .hs-panel input, .hs-panel select, .hs-panel textarea, .hs-panel .btn,
.pg-sec label, .pg-sec input, .pg-sec select, .pg-sec textarea, .pg-sec .btn { position: relative; z-index: 3; }

/* login card leans toward the cursor (fx.js sets inline transform) */
.authcard { transition: transform .2s ease, box-shadow .2s ease; transform-style: preserve-3d; }

/* shiny sweep across the big headings */
@media (prefers-reduced-motion: no-preference) {
  .authhero h1, .brandname, header.app h1 { background-size: 220% auto; animation: shine 7s linear infinite; }
  @keyframes shine { to { background-position: 220% center; } }
}

/* rotating aurora glow behind the completion ring */
.ring::before {
  content: ""; position: absolute; inset: -7px; border-radius: 50%; z-index: -1;
  background: none; filter: none; opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .ring::before { animation: spin 9s linear infinite; }
  @keyframes spin { to { transform: rotate(360deg); } }
}

/* count-up numbers get a tabular figure so width doesn't jitter mid-animation */
.ring .big, .streak .sv { font-variant-numeric: tabular-nums; }

/* ===================================================================
   Dashboard "daily challenge" — three progress rings
   =================================================================== */
/* Home dashboard: hero, rings, an actionable "up next" card, quick stat tiles */
/* ===========================================================================
   HOME

   Home answers one question: what do I do right now. The answer is the only
   loud thing on the screen. Greeting and date are one quiet line above it;
   streak and day score are one quiet line below it; everything that used to
   be here on arrival is inside a single <details>, one tap away.
   =========================================================================== */
.home-stage { position: relative; z-index: 1; display: flex; flex-direction: column; gap: var(--space-5); padding: var(--space-5) 2px var(--space-10); }

.hs-head p { margin: 0; }
.hs-greet { font-size: var(--font-lg); font-weight: var(--weight-emphasis); line-height: var(--leading-tight); }
.hs-date { font-size: var(--font-sm); color: var(--text-muted); margin-top: var(--space-1) !important; }

/* ---- the one thing ---------------------------------------------------- */
.now-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--space-6); box-shadow: var(--shadow-md);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.now-card p { margin: 0; }
.now-label { font-size: var(--font-sm); color: var(--text-muted); }
.now-title {
  font-size: var(--font-xl); font-weight: var(--weight-emphasis);
  line-height: var(--leading-tight); letter-spacing: -0.4px;
  margin: 0; text-transform: none; color: var(--text); word-break: break-word;
}
.now-meta {
  font-size: var(--font-sm); color: var(--text-secondary);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  font-variant-numeric: tabular-nums;
}
.now-diff { display: inline-flex; align-items: center; gap: var(--space-2); }
.now-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
.now-actions .btn { margin-top: 0; flex: 1; }
.now-card.is-done .now-title, .now-card.is-empty .now-title { font-size: var(--font-lg); }

/* A running task. The border says "this one is live" without a colour that
   means anything else. */
.now-card.is-running { border-color: var(--border-strong); }

/* ---- the plan line ----------------------------------------------------
   Accepted, drifted, or not yet agreed to. Deliberately quiet: it sits under
   the one thing Home is for, and its button is secondary because accepting a
   plan is not the action to take right now — doing the task is. */
.plan-note {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  font-size: var(--font-sm); color: var(--text-secondary);
}
.plan-note > span { flex: 1; min-width: 180px; }

/* ---- the quiet line, which is now three rings ------------------------- */
/* This row stands in the slot that used to read "3 left · 4 day streak · 62
   day score", so it has to keep reading as a line and not become a panel:
   34px rings, label beside rather than under, one row at every width.

   No hue. Category colours belong to the balance chart and to the 3px edge on
   a task card — never to Home — so all three rings share one neutral stroke.
   And nothing here moves: a ring that has not closed is information, not a
   verdict, so it does not tint, pulse or sweep in to be noticed. */
.hs-rings {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2); align-items: center;
}
.hs-ring { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.hs-ring svg { width: 34px; height: 34px; flex: none; }
.hs-rprog { transition: none; }
.hs-rt { display: flex; flex-direction: column; min-width: 0; line-height: var(--leading-tight); }
.hs-rt b {
  font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hs-rt i {
  font-style: normal; font-size: var(--font-sm); color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Below about 360px "Movement" and its ring stop fitting side by side. Stack
   them rather than truncating the word — a label you cannot read is worse than
   a row that is 16px taller. */
@media (max-width: 359px) {
  .hs-ring { flex-direction: column; gap: var(--space-1); text-align: center; }
  .hs-rt { align-items: center; }
}

/* A plan you accepted and have not changed needs no decision, so it is a
   footnote rather than a row with a button in it. */
.plan-note.is-settled { margin: 0; color: var(--text-muted); }
/* Whether the plan is agreed is a fact about the card above it, not a fifth
   thing on the page, so it sits closer to the card than the page gap allows. */
.now-card + .plan-note { margin-top: -8px; }

/* ---- everything else, behind one tap ---------------------------------- */
.hs-more { border-top: 1px solid var(--border); padding-top: var(--space-2); }
.hs-more > summary {
  list-style: none; cursor: pointer; min-height: var(--tap-min);
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--font-sm); color: var(--text-secondary);
}
.hs-more > summary::-webkit-details-marker { display: none; }
.hs-more > summary::after {
  content: "›"; margin-left: auto; font-size: var(--font-lg); line-height: 1;
  color: var(--text-muted); transition: transform var(--duration-fast) var(--ease-out);
}
.hs-more[open] > summary::after { transform: rotate(90deg); }
.hs-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hs-more-body { display: flex; flex-direction: column; gap: var(--space-5); padding-top: var(--space-4); }

.hs-carry {
  font-size: var(--font-sm); color: var(--text-secondary);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
}

/* ---- scoreboard: deep work · day score · consistency ------------------- */
.ringdefs { position: absolute; width: 0; height: 0; overflow: hidden; }
.scoreboard {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; justify-items: center;
  gap: 10px; width: 100%; max-width: 580px; margin: 2px auto 0;
}
.metric { display: flex; flex-direction: column; align-items: center; gap: 9px; min-width: 0; }
.metric .mlabel { font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--text-secondary); }
.metric .mnote { font-size: var(--font-sm); color: var(--text-muted); text-align: center; line-height: var(--leading-tight); }
.ring.sm { width: 116px; height: 116px; }
.ring.sm svg { width: 100%; height: 100%; filter: none; }
.ring.sm::before { display: none; }            /* no rotating conic glow on the flanking rings */
.ring.sm .big { font-size: var(--font-lg); font-weight: var(--weight-emphasis); }
.ring.sm .sub { font-size: var(--font-sm); }
.ring.sm circle.prog { filter: none; }

/* the lead ring carries the headline score — bigger, lit, with a soft halo */
.metric.lead { gap: 11px; }
.metric.lead .ring.sm { width: 168px; height: 168px; }
.metric.lead .ring.sm::before { display: none; }
/* qualified with .ring.sm so it always outranks the flanking-ring type sizes,
   whichever order the two rules end up in */
.metric.lead .ring.sm .big { font-size: var(--font-xl); font-weight: var(--weight-emphasis); line-height: 1; }
.metric.lead .ring.sm .sub { font-size: var(--font-sm); margin-top: var(--space-1); }
.metric.lead .mlabel { color: var(--text); }
.metric.lead .mnote { display: flex; flex-direction: column; align-items: center; gap: 3px; margin-top: -5px; }
.sc-rank { font-size: var(--font-base); font-weight: var(--weight-emphasis); color: var(--text-secondary); }
/* the deadline ring carries a "done / total" pair rather than a single figure */
.metric.wide .ring.sm .big { font-size: var(--font-base); font-weight: var(--weight-emphasis); }
/* a ring centre must never wrap — it would spill outside the circle */
.ring .ringc { padding: 0 9px; }
.ring .big, .ring .sub { white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.due-verdict { font-weight: var(--weight-emphasis); font-size: var(--font-sm); color: var(--text-secondary); }
.due-verdict.ok, .due-verdict.warn, .due-verdict.bad { color: var(--text-secondary); }
.sc-delta, .hp-trend { font-size: var(--font-sm); font-weight: var(--weight-body); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.sc-delta.up, .hp-trend.up, .sc-delta.down, .hp-trend.down { color: var(--text-secondary); }
/* score-band tint: the halo warms up as the day gets better */
.scoreboard.low .metric.lead .ring.sm::before,
.scoreboard.none .metric.lead .ring.sm::before { opacity: .1; }
.scoreboard.elite .metric.lead .ring.sm::before { opacity: .45; }

/* ---- panels shared by momentum + goals --------------------------------- */
.hs-panel { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px 14px; box-shadow: var(--shadow); }
.hp-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.hp-head h3 { margin: 0; font-size: var(--font-base); font-weight: var(--weight-emphasis); color: var(--text); }
.hp-sub { font-size: var(--font-sm); color: var(--text-muted); }

/* momentum — 14 days of day-scores as bars */
.mo-bars { display: flex; align-items: flex-end; gap: 5px; height: 74px; }
/* every day keeps a faint column, so a day with nothing planned reads as
   "no data" rather than as a missing bar */
.mo-bar { flex: 1; height: 100%; display: flex; align-items: flex-end; border-radius: 6px;
  background: color-mix(in srgb, var(--line) 32%, transparent); }
:root[data-theme="light"] .mo-bar { background: color-mix(in srgb, var(--line) 70%, transparent); }
.mo-bar i {
  display: block; width: 100%; height: var(--h, 0%); border-radius: 6px;
  background: var(--text-muted);
  transition: height var(--duration-slow) var(--ease-out);
}
.mo-bar.on i { background: var(--text-secondary); }
/* after .on, so the selected day with nothing planned still reads as "no data" */
.mo-bar.empty i { height: 5px; box-shadow: none;
  background: color-mix(in srgb, var(--muted) 45%, transparent); }
.mo-foot { display: flex; justify-content: space-between; margin-top: var(--space-2); font-size: var(--font-sm); color: var(--text-muted); }
.mo-foot b { color: var(--text); font-size: var(--font-sm); font-weight: var(--weight-emphasis); font-variant-numeric: tabular-nums; }

/* weekly goals — target vs done, worst-first */
.gl-list { display: flex; flex-direction: column; gap: 10px; }
.gl-row { display: grid; grid-template-columns: minmax(80px, 1fr) 2.1fr auto; align-items: center; gap: 12px; }
.gl-name { font-size: var(--font-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gl-bar { height: 8px; border-radius: 999px; background: var(--panel-2); border: 1px solid var(--line); overflow: hidden; }
.gl-bar i { display: block; height: 100%; border-radius: 999px;
  background: var(--text-muted); transition: width var(--duration-slow) var(--ease-out); }
.gl-row.hit .gl-bar i { background: var(--text-secondary); }
.gl-num { font-size: var(--font-sm); font-weight: var(--weight-emphasis); font-variant-numeric: tabular-nums; min-width: 34px; text-align: right; }
.gl-num span { color: var(--text-muted); font-weight: var(--weight-body); }
.gl-row.hit .gl-num { color: var(--text); }
.gl-empty { font-size: var(--font-sm); color: var(--text-muted); line-height: var(--leading-base); cursor: pointer; }

/* Focus panel (adaptive personalisation, SPEC.md) — same hs-panel shell as
   Deadlines/Momentum/Weekly goals above it, so it reads as one more thing
   inside "Today in detail" rather than a different kind of screen. */
.pf-cards { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-2); }
.pf-card { display: flex; flex-direction: column; gap: 6px; }
.pf-card b { font-size: var(--font-base); font-weight: var(--weight-emphasis); }
.pf-card span { font-size: var(--font-sm); color: var(--text-muted); }
.pf-proposal + .pf-proposal { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--border); }
.pf-note { margin: var(--space-2) 0 0; font-size: var(--font-sm); color: var(--text-secondary); line-height: var(--leading-base); }
.pf-actions { display: flex; gap: var(--space-2); margin-top: var(--space-2); }
.pf-actions .btn { margin-top: 0; flex: 1; }
.pf-learning { font-size: var(--font-sm); color: var(--text-muted); font-style: italic; }
.pf-explain-btn {
  background: none; border: none; padding: 0; margin-bottom: var(--space-3); cursor: pointer;
  font-size: var(--font-sm); color: var(--text-muted); font: inherit; text-decoration: underline dotted;
}
.pf-explain-btn:hover { color: var(--text); }
.pf-explain { margin-bottom: var(--space-3); padding: var(--space-3); background: var(--surface-2); border-radius: var(--radius-sm); }
.pf-explain p { margin: 0 0 var(--space-2); font-size: var(--font-sm); color: var(--text-secondary); line-height: var(--leading-base); }
.pf-explain p:last-child { margin-bottom: 0; }
/* "Your day", the same bar the payoff screen shows once — kept small here. */
.pf-daybar { height: 10px; margin-bottom: var(--space-3); }
/* Trend — same quiet-bars language as .mo-bars (Momentum), reused rather than
   re-invented: a faint column per week, no animation, no colour judgment. */
.pf-trend { display: flex; align-items: flex-end; gap: 3px; height: 28px; margin-bottom: var(--space-3); }
.pf-trend i { flex: 1; display: block; height: var(--h); min-height: 3px; border-radius: 2px; background: var(--surface-sunken); border: 1px solid var(--border); }
.pf-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
.pf-chip { font-size: var(--font-sm); color: var(--text-muted); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-full); padding: 3px var(--space-2); }
.pf-checkin { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--border); }
.pf-checkin p { margin: 0 0 var(--space-2); font-size: var(--font-sm); color: var(--text); }
.pf-checkin .btn { margin-top: 0; width: auto; }
.gl-empty b { color: var(--accent); white-space: nowrap; }

/* ---- outcomes editor (Settings) --------------------------------------- */
.oc-row .pe-main { flex: 1; min-width: 0; }
.oc-verdict { font-style: normal; font-weight: 700; }
.oc-verdict.ok, .oc-verdict.warn, .oc-verdict.bad { color: var(--text-secondary); }
.oc-slide { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.oc-slide input[type=range] { flex: 1; min-width: 0; }
.oc-pct { font-size: var(--font-sm); font-weight: var(--weight-emphasis); font-variant-numeric: tabular-nums; min-width: 38px; text-align: right; }
.oc-lbl { font-size: var(--font-sm); color: var(--text-muted); margin-right: var(--space-2); }
.chip.outcome { background: var(--surface-2); border-color: var(--border-strong); }
/* Visually distinct but not accented — this reports provenance, not an
   action to take, so it stays inside the neutral palette (CLAUDE.md rule 1). */
.chip.ai-chip { border-style: dashed; }

/* Per-pillar filter chips on Tasks (SPEC.md §7). "on" reuses the same
   accent-on-select treatment as any other control the user operates
   (CLAUDE.md rule 1 — this is a control, not a readout). */
.pillar-filters { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }
.pf-filter {
  font-size: var(--font-sm); padding: var(--space-1) var(--space-3); border-radius: var(--radius-full);
  border: 1px solid var(--border); background: var(--surface); color: var(--text-secondary); cursor: pointer;
  min-height: 32px; transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.pf-filter:hover { border-color: var(--border-strong); }
.pf-filter.on { border-color: var(--accent); background: var(--accent-quiet); color: var(--text); }

/* "Today's budget" — Schedule (SPEC.md §7). */
.sc-budget { margin-top: var(--space-4); }
.sc-budget h2 { margin: 0 0 var(--space-3); font-size: var(--font-base); font-weight: var(--weight-emphasis); }
.sc-budget-row { display: grid; grid-template-columns: 90px 1fr auto; align-items: center; gap: var(--space-3); }
.sc-budget-row + .sc-budget-row { margin-top: var(--space-2); }
.sc-budget-name { font-size: var(--font-sm); color: var(--text); }
.sc-budget-v { font-size: var(--font-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Settings: the direct profile editor + its change history (SPEC.md §5/§9). */
.profile-editor .row { margin-bottom: var(--space-2); }
.profile-log { margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--border); }
.profile-log h3 { margin: 0 0 var(--space-2); font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
.profile-log-row { display: flex; flex-direction: column; gap: 1px; padding: var(--space-1) 0; font-size: var(--font-sm); }
.profile-log-row b { font-weight: var(--weight-emphasis); color: var(--text); }
.profile-log-row span { color: var(--text-muted); }

/* ---- horizon: zoom bar + week / month / year --------------------------- */
.zoombar { display: flex; gap: var(--space-1); margin: 0 0 var(--space-1); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-1); }
.zoombar button { flex: 1; min-height: var(--tap-min); padding: var(--space-2); border: none; border-radius: var(--radius-sm); cursor: pointer;
  background: transparent; color: var(--text-muted); font-size: var(--font-sm); transition: color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out); }
.zoombar button:hover { color: var(--text); }
.zoombar button.active { color: var(--text); font-weight: var(--weight-emphasis); background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--border); }

/* the horizon views sit directly in the Schedule card — a second card inside it
   just wasted a border and 36px of the grid's width */
.hz-card { margin-top: 4px; }
.hz-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.hz-head h3 { margin: 0; font-size: var(--font-lg); font-weight: var(--weight-emphasis); }
.hz-head span { font-size: var(--font-sm); color: var(--text-muted); font-weight: var(--weight-emphasis); font-variant-numeric: tabular-nums; }
.hz-empty { font-size: var(--font-base); color: var(--text-muted); margin-top: 14px; line-height: 1.5; }

/* score ramp shared by week / month / year */
/* The day-score ramp. A score is a readout, not somewhere you act, so it is a
   neutral density ramp rather than an accent one — five steps of grey getting
   more present, which is also what makes the year heatmap legible. */
.s-none { background: var(--surface-2); }
.s1 { background: color-mix(in srgb, var(--text-muted) 20%, transparent); }
.s2 { background: color-mix(in srgb, var(--text-muted) 40%, transparent); }
.s3 { background: color-mix(in srgb, var(--text-muted) 62%, transparent); }
.s4 { background: color-mix(in srgb, var(--text-secondary) 80%, transparent); }
.s5 { background: var(--text-secondary); }

/* week — seven day columns on the same full 0–24h scale as the day grid */
.wk-cal { --wk-gutter: 46px; --wk-hour: 44px; height: var(--pane-h); overflow: auto;
  border: 1px solid var(--line); border-radius: 14px; background: var(--panel-2); overscroll-behavior: contain; }
.wk-inner { min-width: 520px; }
.wk-hrow, .wk-body { display: grid; grid-template-columns: var(--wk-gutter) repeat(7, minmax(62px, 1fr)); }
.wk-hrow { position: sticky; top: 0; z-index: 6; background: var(--surface); border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-md); }
.wk-corner { position: sticky; left: 0; z-index: 2; background: var(--surface); }
.wk-dh { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 7px 4px 8px;
  border-left: 1px solid var(--border); cursor: pointer; transition: background var(--duration-fast); }
.wk-dh:hover { background: var(--surface-2); }
.wk-dh b { font-size: var(--font-micro); font-weight: var(--weight-emphasis); letter-spacing: .4px; text-transform: uppercase; color: var(--text-muted); }
.wk-dn { font-size: var(--font-base); font-weight: var(--weight-emphasis); line-height: 1.1; color: var(--text); font-variant-numeric: tabular-nums; }
.wk-pill { width: 27px; height: 18px; border-radius: 6px; display: grid; place-items: center; font-style: normal;
  font-size: var(--font-micro); font-weight: var(--weight-emphasis); color: var(--text); font-variant-numeric: tabular-nums; }
.wk-dh em { max-width: 100%; font-style: normal; font-size: var(--font-micro); color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wk-dh.today { background: var(--surface-2); }
.wk-dh.today .wk-dn { color: var(--text-secondary); }
.wk-dh.off { opacity: .6; }

.wk-body { position: relative; }
.wk-rail { position: sticky; left: 0; z-index: 3; background: var(--surface-2); }
.wk-h { position: absolute; left: 0; width: var(--wk-gutter); margin-top: -6px; padding-right: 7px; text-align: right;
  font-size: var(--font-micro); font-weight: var(--weight-emphasis); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.wk-dc { position: relative; border-left: 1px solid var(--border);
  background-image: repeating-linear-gradient(to bottom,
    color-mix(in srgb, var(--border) 55%, transparent) 0 1px, transparent 1px var(--wk-hour)); }
.wk-win { position: absolute; left: 0; right: 0; background: var(--surface-2);
  box-shadow: inset 2px 0 0 var(--surface-2); }
.wk-dc.today { background-color: var(--surface-2); }
.wk-dc.off { background-color: color-mix(in srgb, var(--border) 16%, transparent); }
.wk-blk { position: absolute; left: 2px; right: 2px; overflow: hidden; display: flex; flex-direction: column; gap: 1px;
  padding: 3px 5px; border: 1px solid var(--border); border-left: 3px solid var(--border);
  border-radius: 6px; background: var(--surface); cursor: pointer; }
.wk-blk:hover { z-index: 2; box-shadow: 0 3px 12px var(--border-strong); }
.wk-blk .wb-t { font-size: var(--font-sm); font-weight: var(--weight-emphasis); line-height: 1.25; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wb-ic { font-style: normal; }
.wk-blk .wb-w { font-size: var(--font-micro); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.wk-blk.tight { padding: 1px 5px; justify-content: center; }
.wk-blk.tight .wb-t { font-size: var(--font-micro); }
.wk-blk.micro { padding: 0 4px; border-radius: 5px; }
.wk-blk.micro .wb-t { font-size: var(--font-micro); line-height: 1; }
.wk-blk.task.hard { border-left-color: var(--text); background: color-mix(in srgb, var(--text) 11%, var(--surface)); }
.wk-blk.task.medium { border-left-color: var(--text-secondary); background: color-mix(in srgb, var(--text-secondary) 11%, var(--surface)); }
.wk-blk.task.easy { border-left-color: var(--text-secondary); background: color-mix(in srgb, var(--text-secondary) 10%, var(--surface)); }
.wk-blk.goal { border-left-color: var(--text-secondary); background: color-mix(in srgb, var(--text-secondary) 10%, var(--surface)); }
.wk-blk.commit { border-left-color: var(--border-strong); background: var(--surface-2); }
.wk-blk.rest { border-left-color: var(--border-strong);
  background: var(--border-strong); }
.wk-blk.rest .wb-t { color: var(--text-muted); }
.wk-blk.brk { border-style: dashed; background: transparent; }
.wk-blk.brk .wb-t { color: var(--text-muted); font-weight: var(--weight-emphasis); }
.wk-blk.past { opacity: .5; }
.wk-blk.live { border-color: var(--text); box-shadow: 0 0 0 1px var(--text); }
.wk-blk.rest.live { border-color: var(--border-strong);
  box-shadow: 0 0 0 1px var(--border-strong); }
.wk-now { position: absolute; left: 0; right: 0; height: 2px; z-index: 4; background: var(--text); pointer-events: none; }
.wk-now::before { content: ""; position: absolute; left: -3px; top: -3px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--text); }

/* month — only the big events: outcomes landing, commitments, pinned/hard/long work */
.mo-wrap { overflow-x: auto; overscroll-behavior-x: contain; }
.mo-inner { min-width: 620px; }
.mo-dow { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; margin-bottom: 6px; }
.mo-dow span { text-align: center; font-size: var(--font-sm); color: var(--text-muted); }
.mo-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.mo-cell { position: relative; display: flex; flex-direction: column; gap: 3px; min-height: 86px; overflow: hidden;
  padding: 5px; border-radius: 9px; border: 1px solid var(--border); background: var(--surface-2);
  cursor: pointer; transition: border-color var(--duration-fast), background var(--duration-fast); }
.mo-cell:hover { border-color: var(--accent); background: var(--surface); }
.mo-cell.blank { background: none; border: none; cursor: default; }
.mo-cell.blank:hover { background: none; border: none; }
.mo-cell.off { background: color-mix(in srgb, var(--border) 16%, transparent); }
.mo-cell.today { border-color: var(--text-secondary); box-shadow: 0 0 0 1px var(--text-secondary); }
.mo-n { font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.mo-wd { display: none; }
.mo-none { display: none; font-size: var(--font-base); color: var(--text-muted); line-height: 1.5; }
.mo-cell.today .mo-n { color: var(--text-secondary); }
.mo-evs { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.mo-ev { padding: 2px 5px; border-radius: 5px; border: 1px solid var(--border); background: var(--surface); color: var(--text);
  font-size: var(--font-micro); font-weight: var(--weight-emphasis); line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mo-ev.oc { font-weight: var(--weight-emphasis); border-color: var(--border-strong); background: color-mix(in srgb, var(--accent) 15%, var(--surface)); }
.mo-ev.commit { border-color: var(--border-strong); background: var(--surface-2); }
.mo-ev.hard { border-color: var(--border-strong); background: color-mix(in srgb, var(--text) 10%, var(--surface)); }
.mo-ev.medium { border-color: var(--border-strong); background: color-mix(in srgb, var(--text-secondary) 10%, var(--surface)); }
.mo-ev.easy { border-color: var(--border-strong); background: color-mix(in srgb, var(--text-secondary) 9%, var(--surface)); }
.mo-ev.done { opacity: .55; text-decoration: line-through; }
.mo-more { padding-left: 3px; font-size: var(--font-micro); font-weight: var(--weight-emphasis); color: var(--text-muted); }

/* year */
.yr-grid { display: flex; flex-direction: column; gap: 5px; }
.yr-row { display: grid; grid-template-columns: 34px 1fr 28px; align-items: center; gap: 8px; cursor: pointer; }
.yr-row:hover .yr-m { color: var(--accent); }
.yr-m { font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--text-muted); }
.yr-days { display: flex; gap: 2px; }
.yr-days i { flex: 1; height: 15px; border-radius: 3px; min-width: 0; }
.yr-days i.today { box-shadow: 0 0 0 1.5px var(--text); }
.yr-avg { font-size: var(--font-sm); font-weight: var(--weight-emphasis); text-align: right; font-variant-numeric: tabular-nums; color: var(--text-muted); }

.hz-legend { display: flex; align-items: center; gap: 4px; margin-top: 12px; font-size: var(--font-sm); color: var(--text-muted); }
.hz-legend i { width: 14px; height: 14px; border-radius: 3px; }
.hz-legend span:last-child { margin-left: 2px; }

/* outcomes strip inside a horizon view */
.hz-oc { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 14px; }
.hz-oc h4 { margin: 0 0 10px; font-size: var(--font-sm); font-weight: var(--weight-emphasis); letter-spacing: .7px; text-transform: uppercase; color: var(--text-muted); }
.hz-ocrow { display: flex; flex-direction: column; gap: 6px; padding: 11px 12px; border-radius: 11px;
  background: var(--surface-2); border: 1px solid var(--border); margin-bottom: 8px; }
.hz-ocrow.warn, .hz-ocrow.bad { border-color: var(--border-strong); }
.hz-octop { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.hz-octop b { font-size: var(--font-base); font-weight: var(--weight-emphasis); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hz-octop span { font-size: var(--font-sm); color: var(--text-muted); white-space: nowrap; }
.hz-ocfoot { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: var(--font-sm); color: var(--text-muted); }
.hz-ocrow.warn .gl-bar i { background: var(--text-secondary); }
.hz-ocrow.bad .gl-bar i { background: var(--text-secondary); }

@media (max-width: 560px) {
  /* the week keeps all seven columns — a week you have to scroll sideways
     through isn't a week — so it bleeds to the card edges and the type shrinks */
  .wk-cal { --wk-gutter: 30px;
    margin: 0 -20px; border-left: 0; border-right: 0; border-radius: 0; }
  .wk-inner { min-width: 0; }
  .wk-hrow, .wk-body { grid-template-columns: var(--wk-gutter) repeat(7, minmax(0, 1fr)); }
  .wk-dh { padding: 6px 2px 7px; gap: 1px; }
  .wk-dh b { font-size: var(--font-micro); letter-spacing: 0; }
  .wk-dn { font-size: var(--font-base); }
  .wk-pill { width: 22px; height: 15px; font-size: var(--font-micro); }
  .wk-dh em { display: none; }
  .wk-h { font-size: var(--font-micro); padding-right: 4px; }
  .wk-blk { left: 1px; right: 1px; padding: 2px 3px; border-radius: 5px; }
  /* every pixel of a 47px column goes to the name — the row position already
     says when it is, and the colour already says what kind of thing it is */
  .wk-blk .wb-w, .wb-ic { display: none; }
  .wk-blk .wb-t { font-size: var(--font-micro); letter-spacing: -.2px; }
  .wk-blk.tight .wb-t { font-size: var(--font-micro); }
  .wk-blk.micro .wb-t { font-size: var(--font-micro); }
  /* Seven columns become seven rows. The time grid is unreadable at 60px a
     day — every title rendered as "Lunch …", "Email …" — and what you want from
     a week on a phone is "what is each day like", which is a list. */
  .wk-cal { display: none; }
  .wk-agenda { display: grid; gap: var(--space-2); }

  /* And the month becomes an actual month again. Hiding every empty day left
     one row on screen and no sense of the month at all, which is the one thing
     a month view is for. Seven columns at ~48px is plenty for a date and a load
     bar; the names live in the list underneath, where there is room for them. */
  .mo-wrap { overflow-x: visible; }
  .mo-inner { min-width: 0; }
  .mo-grid { grid-template-columns: repeat(7, 1fr); gap: 3px; }
  .mo-cell { min-height: 46px; padding: var(--space-1) 2px; gap: 2px; }
  .mo-wd { display: none; }
  .mo-n { font-size: var(--font-sm); justify-content: center; }
  .mo-evs { display: flex; flex-direction: column; gap: 2px; width: 100%; }
  /* A title cannot fit in a 48px cell, so an event becomes a load bar. */
  .mo-ev { width: 100%; height: 3px; min-height: 3px; padding: 0; border-radius: 2px;
    overflow: hidden; text-indent: -999em; white-space: nowrap; }
  .mo-more { display: none; }
  .mo-none { display: block; }
  .mo-list { display: block; }
  .yr-row { grid-template-columns: 28px 1fr 24px; gap: 5px; }
  .yr-days i { height: 12px; }
}

/* deadlines — one block per due date, carrying task completion and pace */
.dl-row { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-3); border-radius: var(--radius-md);
  background: var(--surface-2); border: 1px solid var(--border); }
.dl-row.warn, .dl-row.bad { border-color: var(--border-strong); }
.dl-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.dl-top b { font-size: var(--font-base); font-weight: var(--weight-emphasis); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dl-when { font-size: var(--font-sm); color: var(--text-muted); white-space: nowrap; }
.dl-row.bad .dl-when { color: var(--text-secondary); }
.dl-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: var(--font-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.dl-more { font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--text-secondary); cursor: pointer; padding: var(--space-1) 0 0; min-height: var(--tap-min); display: flex; align-items: center; }
.dl-more:hover { text-decoration: underline; }
.dl-row.warn .gl-bar i { background: var(--text-secondary); }
.dl-row.bad .gl-bar i { background: var(--text-secondary); }


/* quick stat tiles */
.hs-tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); }
.hs-tile { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); text-align: center; }
.hs-tile b { display: block; font-size: var(--font-lg); font-weight: var(--weight-emphasis); }
.hs-tile span { display: block; font-size: var(--font-sm); color: var(--text-muted); margin-top: var(--space-1); }


@media (max-width: 560px) {
  .home-stage { padding: 18px 2px 32px; gap: 18px; }
  /* One row of three, small-big-small — the shape it always had on a wide
     screen. It used to put the lead on its own row with the other two beneath,
     which made the block about 350px tall; on a phone that pushed the two
     supporting rings under the floating dock, so the thing you came to see was
     off screen. Three across is 96+118+96 plus gaps, which fits 375px. */
  .scoreboard { grid-template-columns: 1fr auto 1fr; gap: var(--space-2); }
  .metric.lead { grid-column: auto; order: 0; }
  .metric.lead .ring.sm { width: 118px; height: 118px; }
  .metric.lead .ring.sm .big { font-size: var(--font-lg); }
  .ring.sm { width: 26vw; height: 26vw; max-width: 96px; max-height: 96px; }
  .ring.sm .big { font-size: var(--font-lg); }
  .ring.sm .sub { font-size: var(--font-sm); }
  .metric .mlabel { font-size: var(--font-sm); }
  .metric .mnote { font-size: var(--font-sm); }
  .hs-panel { padding: 14px 15px 12px; }
  .mo-bars { height: 62px; gap: 4px; }
  .gl-row { grid-template-columns: minmax(70px, 1fr) 1.5fr auto; gap: 9px; }
  .hs-tiles { grid-template-columns: repeat(2, 1fr); }
  .hs-tile b { font-size: var(--font-lg); }
  .dl-foot { flex-direction: column; gap: 2px; }
}


/* ===================== LANDING — visual screens below the choice ===================== */
/* "scroll to learn more" — the control that moves you off page 1. Styled as a
   real pill button (it is the only affordance on the phone's first screen), but
   it stays an <a> so it still navigates with JS off. */
.scrollcue {
  display: inline-flex; flex-direction: column; align-items: center; gap: 3px;
  color: var(--muted); text-decoration: none; font-size: 12.5px; font-weight: 600; letter-spacing: .3px;
  padding: 9px 18px 7px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  /* sits on the opaque hero, so a blur buys nothing and costs a frame */
  background: color-mix(in srgb, var(--accent) 12%, var(--panel));
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.scrollcue:hover { color: var(--text); border-color: color-mix(in srgb, var(--accent) 55%, transparent); background: color-mix(in srgb, var(--accent) 15%, transparent); }
.scrollcue:active { transform: scale(.97); }
.scrollcue .cue-arrow { font-size: 18px; line-height: 1; }

/* NOTE: page 3 used to carry `margin-top: -100vh`, meant to close a gap after the
   dive. It did the opposite. The hero panel is sticky inside a taller wrapper, so
   once it un-sticks it comes to rest over the wrapper's LAST viewport — exactly
   the 100vh the negative margin pulled page 3 into. The panel is opaque and sits
   at z-index 3 above .visualscreen's z-index 1, so page 3 was parked underneath it
   and never became visible at any scroll position; you went from the dive straight
   into "How it works". Without the margin the two are simply adjacent: the panel
   slides up and page 3 rises in behind it, which is the intended hand-off and
   leaves no gap, because there is nothing between them to leave one. */

/* Screens 2-4 — full-height centred visual sections */
.visualscreen {
  position: relative; z-index: 1; min-height: 100vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px; padding: 80px 24px; max-width: 760px; margin: 0 auto; text-align: center;
}
.vs-kicker { font-size: 12.5px; font-weight: 800; letter-spacing: 1.6px; text-transform: uppercase; color: var(--accent); }
.vs-title { font-size: 46px; line-height: 1.08; letter-spacing: -1px; margin: 0; max-width: 680px; }
.vs-sub { color: var(--muted); font-size: 16px; margin: 0 auto 6px; max-width: 470px; }
.visualscreen .herofx { margin: 12px auto 8px; }
.visualscreen .snapsched { margin: 0 auto; max-width: 380px; }
.visualscreen .heropills { justify-content: center; }
.visualscreen.cta .blogo.big {
  width: 74px; height: 74px; border-radius: 20px; margin-bottom: 4px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  display: grid; place-items: center; box-shadow: 0 14px 34px rgba(108,140,255,.45);
}
.ctabtn { width: 100%; max-width: 320px; margin-top: 8px; font-size: 15px; padding: 13px; }

/* ---- PAGE 3 — the phone's home screen, blown up to full screen ----
   The dive ends by dissolving the phone; this is what you land on, so it shows
   the SAME content. It reuses the mock's .ph-* markup verbatim and only scales
   the type/rings up, so the two stay in sync by construction. */
.fullapp { max-width: 620px; }
.fa-board {
  width: 100%; margin-top: 10px; display: flex; flex-direction: column; gap: 20px;
  background: linear-gradient(180deg, rgba(148,163,255,.08), rgba(148,163,255,.02));
  border: 1px solid rgba(148,163,255,.16); border-radius: 26px;
  padding: 24px 22px 26px; box-shadow: 0 30px 70px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.05);
}
.fa-board .ph-hero { margin-top: 0; }
.fa-board .ph-greet { font-size: 23px; }
.fa-board .ph-greet .ph-date { font-size: 12px; }
.fa-board .ph-streak { padding: 8px 14px; gap: 9px; }
.fa-board .ph-streak .flame { font-size: 21px; }
.fa-board .ph-stk-b b { font-size: 15px; }
.fa-board .ph-stk-b span { font-size: 11px; }
.fa-board .ph-score { gap: 10px; margin: 2px 0; }
.fa-board .ph-metric { gap: 8px; }
.fa-board .ph-metric em { font-size: 11px; }
.fa-board .ph-rank { font-size: 12px; }
.fa-board .ph-ring.sm { width: 82px; height: 82px; }
.fa-board .ph-ring.big { width: 152px; height: 152px; }
.fa-board .ph-ring.big::before { inset: -14px; filter: blur(20px); }
.fa-board .ph-ring.sm .ph-rc b { font-size: 20px; }
.fa-board .ph-ring.big .ph-rc b { font-size: 48px; letter-spacing: -1.6px; }
.fa-board .ph-rc span { font-size: 11px; margin-top: 4px; }
.fa-board .ph-next { padding: 14px 16px; gap: 13px; border-radius: 16px; }
.fa-board .ph-nic { font-size: 20px; }
.fa-board .ph-nwhen { font-size: 10.5px; }
.fa-board .ph-nbody b { font-size: 16px; }
.fa-board .ph-narr { font-size: 22px; }
.fa-board .ph-mo { gap: 10px; }
.fa-board .ph-mo-head { font-size: 11.5px; }
.fa-board .ph-bars { height: 62px; gap: 5px; }

/* fa-board flanked by two small mock-ups of Progress and Challenge, resting
   just past its edges like the back cards of a stacked deck. Rest pose only
   moves `transform` (cheap, compositor-only) — see the reveal block below for
   the fold-in from a wider fan, which is the only place `left`/`right`
   themselves would need to animate, and they don't: the fan is also done with
   `transform: translateX(...)` on top of this same fixed offset, so nothing
   here ever triggers layout. Desktop/tablet only — seeing both slivers on a
   360px phone would mean overlapping the single mock the "PHONE LAYOUT" rules
   below already tune carefully, so under 900px they simply don't render. */
.fa-stack { position: relative; }
.fa-board { position: relative; z-index: 1; }
.fa-side {
  position: absolute; top: 74px; width: 172px; height: 352px; padding: 9px;
  border-radius: 30px; background: linear-gradient(160deg, #1b2540, #0b1120);
  border: 1px solid rgba(148,163,255,.18); box-shadow: 0 26px 60px rgba(0,0,0,.5);
  z-index: 0; filter: brightness(.82) saturate(.85);
}
/* fa-board renders far narrower than its 620px max-width (it's a shrink-wrapped
   flex item sized by its own content, not by the section) — around 380-420px in
   practice. -46px put more than half of each side card, ring included, directly
   underneath it. -152px leaves only the card's own edge (inside its blank
   padding, not its ring/text) touching fa-board — verified against a real
   render, not assumed from the max-width. */
.fa-side.l { left: -152px; transform: rotate(-4deg); }
.fa-side.r { right: -152px; transform: rotate(4deg); }
.fs-notch { position: absolute; top: 9px; left: 50%; transform: translateX(-50%); width: 64px; height: 12px; background: #050914; border-radius: 0 0 9px 9px; z-index: 1; }
.fs-screen {
  position: relative; width: 100%; height: 100%; border-radius: 22px; overflow: hidden;
  background: radial-gradient(120% 60% at 50% 0%, #16204a, #0A101D 70%);
  padding: 26px 11px 12px; display: flex; flex-direction: column; gap: 8px; text-align: left;
}
.fs-head { font-size: 11.5px; font-weight: 800; color: #F1F5F9; }
.fs-row { display: flex; align-items: center; gap: 7px; background: rgba(148,163,255,.06); border: 1px solid rgba(148,163,255,.12); border-radius: 9px; padding: 6px 7px; }
.fs-av { width: 19px; height: 19px; border-radius: 50%; flex: none; display: grid; place-items: center; font-size: 8.5px; font-weight: 800; color: #fff; background: linear-gradient(135deg, var(--accent), var(--accent-2)); }
.fs-nm { font-size: 9.5px; color: #E2E8F0; font-weight: 700; flex: 1; }
.fs-pct { font-size: 9.5px; color: #8ee6b8; font-weight: 800; }
.fs-note { margin-top: auto; font-size: 8.5px; color: #7c86b8; line-height: 1.4; }
.fs-ring { position: relative; width: 84px; height: 84px; margin: 6px auto 0; }
.fs-ring svg { display: block; width: 100%; height: 100%; }
.fs-trk { fill: none; stroke: rgba(148,163,255,.16); stroke-width: 6; }
.fs-arc { fill: none; stroke-width: 6; stroke-linecap: round; stroke-dasharray: 157.1; }
.fs-num { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 21px; font-weight: 800; color: #fff; }
.fs-metric { font-size: 9px; color: #94a3d6; font-weight: 700; text-transform: uppercase; letter-spacing: .3px; margin-top: 4px; }
.fs-lbl { position: absolute; bottom: -24px; left: 50%; transform: translateX(-50%); font-size: 10px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
@media (max-width: 899px) { .fa-side { display: none; } }

/* ---- DEPTH SCREEN — Home, Progress, Challenge and the weekly review as one
   tilted, receding wall. A secondary beat after the Home reveal above, not
   a second hero: no CTA here, smaller type than .vs-title. ---- */
.depthscreen { gap: 10px; max-width: 720px; }
.dp-wrap { position: relative; width: 100%; max-width: 760px; height: 260px; margin-top: 18px; perspective: 1300px; -webkit-mask-image: linear-gradient(rgba(0,0,0,.85), rgba(0,0,0,0) 92%); mask-image: linear-gradient(rgba(0,0,0,.85), rgba(0,0,0,0) 92%); }
.dp-plane { position: absolute; left: 0; right: 0; top: 0; transform: rotateX(30deg); transform-origin: top center; display: flex; flex-direction: column; align-items: center; gap: 20px; }
.dp-row { display: flex; gap: 20px; justify-content: center; }
.dp-row.b { margin-left: 68px; }
.dp-tile {
  width: 128px; height: 88px; border-radius: 13px; flex: none;
  background: color-mix(in srgb, var(--panel-2) 92%, transparent); border: 1px solid rgba(148,163,255,.16);
  box-shadow: 0 16px 32px rgba(0,0,0,.35); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.dp-tile.dim { opacity: .55; }
.dp-tile span { font-size: 9.5px; font-weight: 700; color: #A1A1AA; letter-spacing: .2px; }
@media (max-width: 640px) { .dp-wrap { height: 210px; } .dp-tile { width: 104px; height: 74px; } .dp-row { gap: 14px; } .dp-row.b { margin-left: 44px; } }
/* 4 tiles at the 640px sizes (104px + gaps) run to ~458px — wider than the
   phones this app actually runs on, and clipped mid-tile rather than fitting.
   Shrunk again so the widest row (4 tiles) comfortably clears a 360px screen. */
@media (max-width: 480px) {
  .dp-wrap { height: 178px; }
  .dp-tile { width: 72px; height: 54px; gap: 3px; }
  .dp-tile .fs-ring { width: 24px !important; height: 24px !important; }
  .dp-tile .fs-num { font-size: 7px !important; }
  .dp-tile span { font-size: 8px; }
  .dp-row { gap: 8px; }
  .dp-row.b { margin-left: 28px; }
}

/* ---- FEATURES GRID — replaces the three-tile parallax wall with six,
   in a plain grid; the pitches are adherence-first, matching the app's own
   "never score raw task count" rule. ---- */
.fgscreen { gap: 14px; max-width: 1040px; }
.fg-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; width: 100%; margin-top: 12px; }
.fg-tile {
  display: flex; flex-direction: column; align-items: flex-start; gap: 9px; text-align: left;
  background: color-mix(in srgb, var(--panel-2) 96%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 80%, transparent);
  border-radius: 14px; padding: 16px 17px 18px;
  box-shadow: 0 14px 30px rgba(0, 0, 0, .3), inset 0 1px 0 rgba(255, 255, 255, .06);
  transition: border-color .15s ease;
}
.fg-tile:hover { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
.fg-ic {
  width: 32px; height: 32px; border-radius: 10px; display: grid; place-items: center; font-size: 16px;
  background: linear-gradient(135deg, rgba(107,140,255,.22), rgba(162,108,255,.22)); border: 1px solid rgba(148,163,255,.22);
}
.fg-tile b { font-size: 14px; font-weight: 700; }
.fg-tile span { font-size: 12px; color: var(--muted); line-height: 1.4; }
@media (max-width: 820px) { .fg-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) {
  .fg-grid { grid-template-columns: 1fr; gap: 11px; }
  .fg-tile { padding: 12px; border-radius: 12px; }
  .fg-ic { width: 28px; height: 28px; font-size: 14px; }
  .fg-tile b { font-size: 12.5px; }
  .fg-tile span { font-size: 11px; }
}

/* ---- STAT BAND — a thin trust strip ahead of the closing CTA. Adherence and
   streaks, deliberately never a raw task count (see the rule above). ---- */
.statband { position: relative; z-index: 1; padding: 52px 24px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.sb-wrap { display: flex; flex-direction: column; align-items: center; gap: 20px; max-width: 1000px; margin: 0 auto; }
.sb-kicker { font-size: 12px; font-weight: 800; letter-spacing: 1.6px; text-transform: uppercase; color: var(--accent); }
.sb-row { display: flex; align-items: stretch; gap: 0; flex-wrap: wrap; justify-content: center; }
.sb-stat { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 0 40px; }
.sb-div { width: 1px; background: var(--line); }
.sb-num { font-size: 48px; font-weight: 800; letter-spacing: -1.6px; line-height: 1; color: var(--text); }
.sb-lbl { font-size: 12.5px; color: var(--muted); text-align: center; line-height: 1.4; max-width: 190px; }
@media (max-width: 640px) { .sb-div { display: none; } .sb-stat { padding: 0 10px; } .sb-num { font-size: 40px; } }

@media (prefers-reduced-motion: no-preference) {
  .scrollcue .cue-arrow { animation: cueBounce 1.8s ease-in-out infinite; }
  .visualscreen.cta .blogo.big { animation: floaty 5.5s ease-in-out infinite; }
  @keyframes cueBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5px); } }
}

/* Each screen snaps into place, and fades/​rises in as it enters the viewport,
   fading back out as it leaves — so moving down reads as changing screens rather
   than scrolling. Reveal is gated on `.reveal-ready` (added by fx.js) so the page
   still shows everything if JS is off; disabled entirely under reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
  /* Descendant, not child: .visualscreen/.showcase are inside <main id="landing-main">,
     not direct children of #auth, so a `>` combinator here never matched — the
     reveal fade/rise silently never played for anyone. */
  #auth.reveal-ready .visualscreen,
  #auth.reveal-ready .showcase {
    opacity: 0; transform: translateY(30px);
    transition: opacity .8s ease, transform .8s cubic-bezier(.2, .7, .2, 1); will-change: opacity, transform;
  }
  #auth.reveal-ready .inview { opacity: 1; transform: none; }

  /* The Progress/Challenge side cards start fanned further out and invisible,
     then fold in behind Home as #screen-idea reveals — same reveal-once
     mechanism as above, just a second beat on top of it (delayed .15s so the
     section's own rise reads first). Resting pose (.fa-side.l/.r above) is
     what they fold TO, so JS-off / reduced-motion users just see that pose
     directly with no animation to miss. */
  #auth.reveal-ready #screen-idea .fa-side {
    opacity: 0; transition: opacity .7s ease .15s, transform 1s cubic-bezier(.2, .7, .2, 1) .15s; will-change: opacity, transform;
  }
  #auth.reveal-ready #screen-idea .fa-side.l { transform: rotate(-15deg) translateX(-90px) scale(1.03); }
  #auth.reveal-ready #screen-idea .fa-side.r { transform: rotate(15deg) translateX(90px) scale(1.03); }
  #auth.reveal-ready #screen-idea.inview .fa-side { opacity: 1; }
  #auth.reveal-ready #screen-idea.inview .fa-side.l { transform: rotate(-4deg); }
  #auth.reveal-ready #screen-idea.inview .fa-side.r { transform: rotate(4deg); }
}
@media (max-width: 820px) { .vs-title { font-size: 31px; } }

/* Phone: the landing reads as six discrete pages, each one screen tall —
   1 motto · 2 phone · 3 the app full screen · 4 how it works · 5 the lineup ·
   6 the closing CTA + footer.

   .hero-scroll's height budgets that: the sticky panel is pinned for the first
   (height − 100svh) = 180svh, which is what fx.js scrubs the dive over — part the
   motto and raise the phone, hold on the phone, then zoom-and-dissolve — and the
   last 100svh is the panel sliding up to hand over to page 3. That final screen
   is structural, not padding: a sticky element always trails its own height. */
@media (max-width: 640px) {
  /* svh throughout, to match the sticky stage — see the note on the hero above */
  .hero-scroll { height: 280svh; }
  .visualscreen { min-height: 100svh; padding: 78px 22px 60px; gap: 13px; }
  .showcase {
    min-height: 100svh;
    display: flex; flex-direction: column; justify-content: center;
    padding: 60px 22px; margin: 0 auto;
  }
  .vs-sub { font-size: 15px; }
  /* page 3 has the tallest content of any section — trim it to fit one screen */
  .fullapp .vs-title { font-size: 26px; }
  .fullapp { gap: 8px; }
  .fa-board { margin-top: 4px; gap: 14px; padding: 16px 14px 18px; border-radius: 20px; }
  .fa-board .ph-greet { font-size: 18px; }
  .fa-board .ph-score { gap: 4px; }
  .fa-board .ph-ring.sm { width: 64px; height: 64px; }
  .fa-board .ph-ring.big { width: 118px; height: 118px; }
  .fa-board .ph-ring.sm .ph-rc b { font-size: 16px; }
  .fa-board .ph-ring.big .ph-rc b { font-size: 37px; }
  .fa-board .ph-next { padding: 11px 13px; }
  .fa-board .ph-nbody b { font-size: 14px; }
  .fa-board .ph-bars { height: 42px; }
}
/* ---- hero animated product display (energy curve + slotted tasks) ---- */
.herofx { position: relative; margin: 24px 0 20px; max-width: 460px; }
/* soft glow pool so the floating cards sit in light */
.herofx::before {
  content: ""; position: absolute; inset: -14% -8%; z-index: -1; pointer-events: none; filter: blur(28px);
  background:
    radial-gradient(52% 56% at 32% 52%, color-mix(in srgb, var(--accent) 24%, transparent), transparent 72%),
    radial-gradient(44% 52% at 66% 40%, color-mix(in srgb, var(--accent-2) 20%, transparent), transparent 72%);
}
.snapsched { display: flex; flex-direction: column; gap: 9px; max-width: 340px; }
.snaprow {
  position: relative; display: flex; align-items: center; gap: 11px; text-align: left;
  background:
    linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,0) 40%),
    color-mix(in srgb, var(--panel-2) 90%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 80%, transparent);
  border-radius: 12px; padding: 10px 14px; font-size: 13px; font-weight: 600; color: var(--text);
  box-shadow: 0 16px 34px rgba(0,0,0,.34), inset 0 1px 0 rgba(255,255,255,.08);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
.snaprow .st { color: var(--muted); font-size: 11.5px; font-weight: 700; min-width: 37px; }
.snaprow .sbar { width: 4px; align-self: stretch; min-height: 16px; border-radius: 3px; background: var(--accent); }
.snaprow .sbar.hard { background: var(--hard); }
.snaprow .sbar.med  { background: var(--medium); }
.snaprow .sbar.easy { background: var(--easy); }
.snaprow .sbar.brk  { background: color-mix(in srgb, var(--line) 90%, transparent); }
.snaprow .sn { flex: 1; }
.snaprow .sd { font-size: 12.5px; }
.snaprow .brkname { color: var(--muted); font-style: italic; font-weight: 500; }
/* jumbled → snapped-into-order loop (see index.html hero) */
@media (prefers-reduced-motion: no-preference) {
  .snaprow { animation: snapIn 7.5s cubic-bezier(.2,.8,.3,1) infinite both; will-change: transform, opacity; }
  .snaprow.r2 { animation-delay: .1s; }
  .snaprow.r3 { animation-delay: .2s; }
  .snaprow.r4 { animation-delay: .3s; }
  @keyframes snapIn {
    0%   { opacity: 0; transform: translate(var(--tx), var(--ty)) rotate(var(--rot)) scale(.9); }
    7%   { opacity: 1; transform: translate(var(--tx), var(--ty)) rotate(var(--rot)) scale(.9); }
    22%  { opacity: 1; transform: translate(var(--tx), var(--ty)) rotate(var(--rot)) scale(.9);
           animation-timing-function: cubic-bezier(.34, 1.5, .4, 1); }
    36%  { opacity: 1; transform: none; }
    90%  { opacity: 1; transform: none; }
    97%  { opacity: 0; transform: none; }
    100% { opacity: 0; transform: translate(var(--tx), var(--ty)) rotate(var(--rot)) scale(.9); }
  }
}


/* ===================================================================
   Feature carousel (fx.js initCarousel) — "How it works" showcase
   =================================================================== */
.showcase { position: relative; z-index: 1; max-width: 1000px; margin: 0 auto; padding: 40px 24px 96px; }
.showcase-head { text-align: center; max-width: 640px; margin: 0 auto 30px; }
.showcase-kicker {
  display: inline-block; font-size: 12px; font-weight: 700; letter-spacing: 1.4px;
  text-transform: uppercase; color: var(--accent); margin-bottom: 10px;
}
.showcase-title { font-size: 27px; line-height: 1.15; letter-spacing: -.6px; margin: 0; }

.carousel { display: flex; flex-direction: column; gap: 26px; }
.carousel-card {
  position: relative; border-radius: 22px; overflow: hidden; outline: none;
  border: 1px solid color-mix(in srgb, var(--line) 80%, transparent);
  background: color-mix(in srgb, var(--panel) 82%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(1.2); backdrop-filter: blur(16px) saturate(1.2);
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,.06);
}
/* cursor-following glow (fed --x/--y by fx.js) */
.carousel-card::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(230px circle at var(--x, 50%) var(--y, 50%),
              color-mix(in srgb, var(--accent) 20%, transparent), transparent 62%);
}
.carousel-inner { position: relative; min-height: 340px; padding: 36px; }
.carousel-text { position: relative; z-index: 3; max-width: 46%; }
.carousel-text .c-step { font-size: 12.5px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--accent); }
.carousel-text .c-title { font-size: 25px; line-height: 1.15; letter-spacing: -.4px; margin: 10px 0 12px; }
.carousel-text .c-desc { color: var(--muted); font-size: 15px; line-height: 1.55; max-width: 440px; margin: 0; }

.carousel-stage {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  display: flex; align-items: center; justify-content: center; padding: 32px 32px 32px 48%;
}

.carousel-nav { list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin: 0; padding: 0; }
.carousel-nav button {
  display: flex; align-items: center; gap: 9px; border: 1px solid var(--line); cursor: pointer;
  border-radius: 999px; padding: 7px 14px 7px 8px; font-size: 13.5px; font-weight: 600;
  background: var(--panel-2); color: var(--muted); transition: background .25s, color .25s, border-color .25s, transform .25s;
}
.carousel-nav button:hover { color: var(--text); border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
.carousel-nav button.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: #fff;
  border-color: transparent; transform: scale(1.04); box-shadow: 0 8px 20px rgba(108,140,255,.35);
}
.carousel-nav .c-num {
  width: 21px; height: 21px; flex: none; border-radius: 50%; display: grid; place-items: center;
  font-size: 12px; font-weight: 700; background: var(--line); color: var(--text);
}
.carousel-nav .c-num.done { background: var(--accent); color: #fff; }
.carousel-nav button.active .c-num { background: rgba(255,255,255,.28); color: #fff; }

/* entrance animations (re-run because fx.js rebuilds the nodes each step) */
@media (prefers-reduced-motion: no-preference) {
  .carousel-text .c-step { animation: cText .45s .04s both; }
  .carousel-text .c-title { animation: cText .45s .1s both; }
  .carousel-text .c-desc { animation: cText .45s .16s both; }
  .carousel-stage .c-img { animation: cImg .55s both; }
  @keyframes cText { from { opacity: 0; transform: translateX(-16px); } to { opacity: 1; transform: none; } }
  @keyframes cImg { from { opacity: 0; transform: scale(.94); } to { opacity: 1; transform: none; } }
}

/* ---- animated mockups shown in the carousel stage ---- */
.mock { width: 100%; max-width: 302px; }
.mock .mset, .mtasks .mai, .mchip, .mslot {
  background: color-mix(in srgb, var(--panel-2) 88%, transparent);
  border: 1px solid color-mix(in srgb, var(--line) 82%, transparent);
  box-shadow: 0 16px 38px rgba(0,0,0,.30);
}

/* step 1 — setup card */
.mset { border-radius: 16px; padding: 15px 16px 13px; }
.mset .mrow { display: flex; justify-content: space-between; align-items: center; padding: 8px 0;
  border-top: 1px solid color-mix(in srgb, var(--line) 55%, transparent); }
.mset .mrow:first-child { border-top: none; padding-top: 2px; }
.mset .mk { color: var(--muted); font-size: 12.5px; }
.mset .mv { font-weight: 700; font-size: 12.5px; }
.mset .mcurve { width: 100%; height: 46px; margin-top: 10px; }
.mset .mc-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; }

/* step 2 — AI parse */
.mai { position: relative; border-radius: 12px; padding: 12px 14px; font-size: 12.5px; line-height: 1.5; color: var(--text); }
.mcaret { display: inline-block; width: 2px; height: 13px; background: var(--accent); vertical-align: -2px; margin-left: 1px; }
.mchips { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.mchip { display: flex; align-items: center; gap: 9px; border-radius: 10px; padding: 9px 11px; font-size: 12.5px; font-weight: 600; }
.mchip em { margin-left: auto; font-style: normal; font-weight: 700; font-size: 11px; color: var(--muted); }
.mchip .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.dot.hard { background: var(--hard); } .dot.med { background: var(--medium); } .dot.easy { background: var(--easy); }

/* step 3 — schedule timeline */
.msched { display: flex; flex-direction: column; gap: 8px; }
.mslot { display: flex; align-items: center; gap: 10px; border-radius: 10px; padding: 10px 12px;
  font-size: 12.5px; font-weight: 600; color: var(--text); border-left: 3px solid var(--accent); }
.mslot em { font-style: normal; color: var(--muted); font-size: 11px; font-weight: 700; min-width: 36px; }
.mslot b { margin-left: auto; font-size: 9.5px; font-weight: 800; letter-spacing: .4px; padding: 2px 8px; border-radius: 999px; text-transform: uppercase; }
.mslot.hard { border-left-color: var(--hard); } .mslot.hard b { background: color-mix(in srgb, var(--hard) 18%, transparent); color: var(--hard); }
.mslot.med  { border-left-color: var(--medium); } .mslot.med b { background: color-mix(in srgb, var(--medium) 20%, transparent); color: var(--medium); }
.mslot.easy { border-left-color: var(--easy); }
.mslot.brk  { border-left-color: color-mix(in srgb, var(--line) 90%, transparent); color: var(--muted); font-style: italic; box-shadow: none; opacity: .8; }

/* step 4 — streak rings + week */
.mstreak { text-align: center; }
.mrings { display: flex; justify-content: center; gap: 16px; }
.mring { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.mring svg { width: 62px; height: 62px; }
.mring-v { font-weight: 800; font-size: 13px; }
.mring-s { font-size: 10.5px; color: var(--muted); }
.mring-p { stroke-dashoffset: var(--o); }
.mweek { display: flex; justify-content: center; gap: 7px; margin-top: 16px; }
.mweek .d { width: 23px; height: 23px; border-radius: 7px; display: grid; place-items: center; font-size: 12px;
  background: var(--panel-2); border: 1px solid var(--line); }
.mweek .d.on { background: color-mix(in srgb, var(--easy) 22%, transparent); border-color: color-mix(in srgb, var(--easy) 40%, var(--line)); }
.mweek .d.today { background: color-mix(in srgb, var(--medium) 22%, transparent); border-color: color-mix(in srgb, var(--medium) 45%, var(--line)); }

/* mockup entrance animations — replay each cycle because fx.js rebuilds the nodes */
@media (prefers-reduced-motion: no-preference) {
  .mset .mrow { animation: mIn .5s ease both; }
  .mset .mrow:nth-child(2) { animation-delay: .08s; }
  .mset .mrow:nth-child(3) { animation-delay: .16s; }
  .mset .mc-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: hfDraw 1.3s cubic-bezier(.4,0,.2,1) .3s forwards; }
  .mcaret { animation: mBlink 1s steps(1) infinite; }
  .mchip { animation: mPop .5s cubic-bezier(.2,.9,.3,1.3) both; }
  .mchip.c1 { animation-delay: .35s; } .mchip.c2 { animation-delay: .5s; } .mchip.c3 { animation-delay: .65s; }
  .mslot { animation: mIn .5s cubic-bezier(.2,.9,.3,1.1) both; }
  .mslot.s1 { animation-delay: .1s; } .mslot.s2 { animation-delay: .22s; }
  .mslot.s3 { animation-delay: .34s; } .mslot.s4 { animation-delay: .46s; }
  .mring-p { stroke-dashoffset: var(--c); animation: mRing 1.1s cubic-bezier(.4,0,.2,1) .25s forwards; }
  .mweek .d { animation: mPop .45s cubic-bezier(.2,.9,.3,1.3) both; }
  .mweek .d:nth-child(1){animation-delay:.1s} .mweek .d:nth-child(2){animation-delay:.17s}
  .mweek .d:nth-child(3){animation-delay:.24s} .mweek .d:nth-child(4){animation-delay:.31s}
  .mweek .d:nth-child(5){animation-delay:.38s} .mweek .d:nth-child(6){animation-delay:.45s}
  .mweek .d:nth-child(7){animation-delay:.55s}
  @keyframes mIn { from { opacity: 0; transform: translateX(-14px); } to { opacity: 1; transform: none; } }
  @keyframes mPop { 0% { opacity: 0; transform: translateY(9px) scale(.85); } 100% { opacity: 1; transform: none; } }
  @keyframes mBlink { 0%,50% { opacity: 1; } 51%,100% { opacity: 0; } }
  @keyframes mRing { from { stroke-dashoffset: var(--c); } to { stroke-dashoffset: var(--o); } }
}

@media (max-width: 720px) {
  .carousel-inner { min-height: 0; padding: 24px; }
  .carousel-text { max-width: 100%; }
  .carousel-stage { position: relative; inset: auto; padding: 0; height: auto; margin-top: 22px; }
  .mock { margin: 0 auto; }
  .showcase-title { font-size: 22px; }
  .carousel-nav .c-name { display: none; }
  .carousel-nav button { padding: 7px; }
}

/* ===================================================================
   Landing site footer — brand, legal links, contact
   =================================================================== */
.sitefooter {
  position: relative; z-index: 1;
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--panel) 60%, transparent);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
.sitefooter-inner {
  max-width: 1000px; margin: 0 auto;
  padding: 30px 24px calc(40px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center;
}
.sitefooter .sf-brand { display: inline-flex; align-items: center; gap: 9px; }
.sitefooter .sf-brand .blogo { width: 30px; height: 30px; border-radius: 9px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); display: grid; place-items: center; box-shadow: var(--shadow); }
.sitefooter .sf-brand .skelly { width: 19px; height: 19px; }
.sitefooter .sf-name { font-weight: 800; letter-spacing: -.3px; font-size: 16px; color: var(--text); }
.sitefooter .sf-name sup { font-size: 9px; font-weight: 700; color: var(--muted); }
.sitefooter .sf-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 22px; }
.sitefooter .sf-links a { color: var(--muted); text-decoration: none; font-size: 14px; font-weight: 500; transition: color .18s ease; }
.sitefooter .sf-links a:hover { color: var(--text); }
.sitefooter .sf-copy { margin: 0; color: var(--muted); font-size: 12.5px; }
.sitefooter .sf-tm { margin: 0; color: var(--muted); font-size: 11.5px; opacity: .85; }
.sitefooter .sf-tm a { color: var(--muted); text-decoration: none; }
.sitefooter .sf-tm a:hover { color: var(--text); }


/* Notification settings. One row per type, each a full-width tap target so the
   whole row toggles rather than only the 20px checkbox. */
.togglelist { display: grid; gap: var(--space-1); margin-top: var(--space-3); }
.togglerow {
  display: flex; align-items: flex-start; gap: var(--space-3);
  min-height: var(--tap-min); padding: var(--space-2);
  border-radius: var(--radius-sm); cursor: pointer;
}
.togglerow:hover { background: var(--surface-2); }
.togglerow input { margin-top: 2px; }
.togglerow > span { display: grid; gap: 2px; min-width: 0; }
.togglerow b { font-size: var(--font-base); font-weight: var(--weight-emphasis); color: var(--text); }
.togglerow small { color: var(--muted); font-size: var(--font-sm); }
.primerlist { margin: var(--space-3) 0; padding-left: var(--space-4); display: grid; gap: var(--space-2); }
.primerlist li { color: var(--text-secondary); font-size: var(--font-sm); }


/* ===========================================================================
   PHASE 2 — categories, time tracking, balance
   =========================================================================== */

/* The six categorical hues, and the only two shapes they are allowed to take:
   a dot beside a label, and a bar in the balance chart. Never a fill on a card,
   never on Home. --category-* appears nowhere else in this stylesheet. */
.catdot { width: 10px; height: 10px; border-radius: 50%; flex: none; display: inline-block; }
.catdot.cat-1 { background: var(--category-1); }
.catdot.cat-2 { background: var(--category-2); }
.catdot.cat-3 { background: var(--category-3); }
.catdot.cat-4 { background: var(--category-4); }
.catdot.cat-5 { background: var(--category-5); }
.catdot.cat-6 { background: var(--category-6); }
.catdot.cat-none { background: var(--border-strong); }

.cat-stroke.cat-1 { stroke: var(--category-1); }
.cat-stroke.cat-2 { stroke: var(--category-2); }
.cat-stroke.cat-3 { stroke: var(--category-3); }
.cat-stroke.cat-4 { stroke: var(--category-4); }
.cat-stroke.cat-5 { stroke: var(--category-5); }
.cat-stroke.cat-6 { stroke: var(--category-6); }

/* ---- Balance ---- */
.bal-range { margin-bottom: var(--space-4); }
.bal-card { display: flex; flex-direction: column; gap: var(--space-5); }
.bal-chart { position: relative; align-self: center; line-height: 0; }
.bal-chart svg { max-width: 100%; height: auto; }
.bal-centre {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; line-height: 1.2; pointer-events: none;
}
.bal-centre b { font-size: var(--font-lg); font-weight: var(--weight-emphasis); color: var(--text); }
.bal-centre span { font-size: var(--font-sm); color: var(--text-muted); }

.bal-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-4); }
.bal-row { display: grid; gap: var(--space-2); }
.bal-top { display: flex; align-items: center; gap: var(--space-2); }
.bal-name { flex: 1; min-width: 0; font-size: var(--font-base); color: var(--text); }
.bal-note { color: var(--text-muted); font-size: var(--font-sm); }
.bal-mins { font-size: var(--font-sm); color: var(--text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The shortfall is the DISTANCE between the bar and the target marker, and
   nothing else. No amber, no red, no "behind" label — a category under target
   is a fact about the week, not a verdict on the person who lived it. Putting
   alarm colour here would rebuild the guilt engine inside the one screen that
   exists to show you your life is out of balance. */
.bal-track {
  position: relative; height: 8px; border-radius: var(--radius-full);
  background: var(--surface-sunken); border: 1px solid var(--border); overflow: visible;
}
.bal-bar { display: block; height: 100%; border-radius: var(--radius-full); min-width: 2px; }
.bal-bar.cat-1 { background: var(--category-1); }
.bal-bar.cat-2 { background: var(--category-2); }
.bal-bar.cat-3 { background: var(--category-3); }
.bal-bar.cat-4 { background: var(--category-4); }
.bal-bar.cat-5 { background: var(--category-5); }
.bal-bar.cat-6 { background: var(--category-6); }
.bal-target {
  position: absolute; top: -3px; bottom: -3px; width: 2px;
  background: var(--text-secondary); border-radius: 1px; transform: translateX(-1px);
}

/* ---- the timer on the task page ---- */
.td-timer { margin: var(--space-4) 0 var(--space-2); display: grid; gap: var(--space-3); }
.tm-head { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.tm-head .btn { margin-top: 0; width: auto; }
.tm-manual { display: flex; gap: var(--space-2); align-items: center; margin-left: auto; }
.tm-manual input { width: 84px; min-height: 40px; padding: var(--space-2); }
.tm-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.tm-row { display: flex; align-items: center; gap: var(--space-3); font-size: var(--font-sm); }
.tm-when { flex: 1; min-width: 0; color: var(--text-muted); }
.tm-mins { color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.tm-del { width: 28px; height: 28px; }

/* A link-shaped button, for an offer inside a sentence. It takes the accent
   because it IS an action — adopting the learned estimate. */
.linkbtn {
  border: none; background: none; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit; font-weight: var(--weight-emphasis); text-decoration: underline;
}
.linkbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- the category editor ---- */
.cat-row { display: flex; align-items: center; gap: var(--space-3); }
.cat-row .cat-name { flex: 1; min-width: 0; }
.cat-row .cat-target { width: 76px; flex: none; }
.cat-unit { font-size: var(--font-sm); color: var(--text-muted); white-space: nowrap; }
.cat-derived {
  font-size: var(--font-sm); color: var(--text-muted); padding: 2px var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius-full); white-space: nowrap;
}

/* Three stacked choices, none of them the safe default — the runaway dialog
   deliberately has no dismiss, because every outcome needs a decision. */
.modalbtns-stack { flex-direction: column; }
.modalbtns-stack .btn { width: 100%; margin-top: 0; }

/* ===========================================================================
   PHASE 3 — modules, work types, document import
   =========================================================================== */

/* ---- the module page ---- */
.md-card { display: grid; gap: var(--space-3); }
.md-head { display: flex; align-items: center; gap: var(--space-3); }
.md-head h2 { margin: 0; font-size: var(--font-lg); }
/* Neutral, and it stays neutral all the way to 100%. The bar reports how much
   of the module is behind you — it is not a score, so it does not change colour
   as it fills and it does not change colour as the target date nears. */
.md-bar { height: 8px; border-radius: var(--radius-full); background: var(--surface-sunken);
  border: 1px solid var(--border); overflow: hidden; }
.md-bar > span { display: block; height: 100%; background: var(--text-secondary);
  border-radius: var(--radius-full); transition: width var(--duration-slow) var(--ease-out); }
.md-line { margin: 0; font-size: var(--font-sm); color: var(--text-secondary); }
.md-sessions { margin-top: var(--space-2); }
.md-sessions h3 { margin: 0 0 var(--space-2); font-size: var(--font-sm); font-weight: var(--weight-emphasis);
  color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
.md-sessions ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.md-sessions li { font-size: var(--font-sm); color: var(--text-secondary); }
.md-group { margin-top: var(--space-4); }

/* ---- the module list in Settings ---- */
.modulelist { display: grid; gap: var(--space-2); }
.md-tile { display: flex; align-items: center; gap: var(--space-3); width: 100%; text-align: left;
  min-height: var(--tap-min); padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text); cursor: pointer; }
.md-tile:hover { border-color: var(--border-strong); }
.md-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.md-tile-main { flex: 1; min-width: 0; display: grid; gap: 2px; }
.md-tile-sub { font-size: var(--font-sm); color: var(--text-muted); }
.md-tile-pct { font-size: var(--font-sm); color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.wt-editor { margin-top: var(--space-4); }

/* ---- the import preview ---- */
.modalbox-wide { max-width: 560px; }
.imp-list { list-style: none; margin: var(--space-4) 0 0; padding: 0; display: grid; gap: var(--space-2);
  max-height: 46vh; overflow-y: auto; overscroll-behavior: contain; }
.imp-row { display: flex; gap: var(--space-3); align-items: flex-start;
  padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); }
/* An unticked row is dimmed, not struck through or recoloured — it is a choice
   not yet made, not a mistake. */
.imp-row.off { opacity: .5; }
.imp-keep { flex: none; display: flex; align-items: center; min-height: 32px; }
.imp-main { flex: 1; min-width: 0; display: grid; gap: var(--space-2); }
.imp-name { min-height: 36px; padding: var(--space-2); }
.imp-meta { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.imp-when { font-size: var(--font-sm); color: var(--text-muted); }
.imp-wt { width: auto; min-height: 34px; padding: var(--space-1) var(--space-2); font-size: var(--font-sm); }

/* ===========================================================================
   PHASE 4 — triage, guided execution, the check-in
   =========================================================================== */

/* Not a banner and deliberately not a warning: a quiet section listing what
   moved and why. No border colour, no icon, no alarm hue — work that did not
   fit was not lost, and the styling has to agree with the wording. */
.nottoday { margin: var(--space-4) 0; padding: var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-2); }
.nt-head { margin: 0 0 var(--space-3); font-size: var(--font-sm); font-weight: var(--weight-emphasis);
  color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
.nt-list { list-style: none; margin: 0 0 var(--space-3); padding: 0; display: grid; gap: var(--space-3); }
.nt-item { display: grid; gap: 2px; }
.nt-name { font-size: var(--font-base); color: var(--text); }
/* One sentence, quieter than the name it explains. */
.nt-why { font-size: var(--font-sm); color: var(--text-muted); }
.nottoday .btn { width: auto; margin-top: 0; }

/* The observation at the end of the check-in. One line, no icon, no emphasis —
   it is something noticed, not something achieved. */
.ci-observe { margin: var(--space-4) 0 0; padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  font-size: var(--font-sm); color: var(--text-secondary); line-height: var(--leading-base); }

/* ===========================================================================
   PHASE 5 — gap filling, deadline back-planning
   =========================================================================== */

/* Collapsed by default so Home keeps answering one question. The trigger is
   secondary: it is a question you ask, not the action to take now. */
.gapfill { margin-top: var(--space-5); }
.gapfill > .btn { width: 100%; margin-top: 0; }
.gappanel { margin-top: var(--space-3); display: grid; gap: var(--space-3);
  padding: var(--space-4); border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--surface-2); }
.gapcustom { display: flex; gap: var(--space-2); align-items: center; }
.gapcustom input { flex: 1; min-height: 40px; padding: var(--space-2) var(--space-3); }
.gapcustom .btn { width: auto; margin-top: 0; flex: none; }
.gapresult:empty { display: none; }
.gap-msg { margin: 0; font-size: var(--font-base); color: var(--text-secondary); line-height: var(--leading-base); }
.gap-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.gap-pick { display: grid; gap: 2px; width: 100%; text-align: left;
  min-height: var(--tap-min); padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); cursor: pointer; }
.gap-pick:hover { border-color: var(--border-strong); }
.gap-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gap-name { font-size: var(--font-base); font-weight: var(--weight-emphasis); }
.gap-meta { font-size: var(--font-sm); color: var(--text-muted); }

/* Proposed sessions. Plain rows, no countdown, no colour that intensifies as
   the date nears — colour reports state, it does not judge. */
.back-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: grid; gap: var(--space-1); }
.back-row { display: flex; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-2) 0; border-top: 1px solid var(--border); font-size: var(--font-base); }
.back-when { color: var(--text); }
.back-mins { color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* The one-line summary that replaced three stacked banners on Schedule.
   Everything merely true about the day, comma-separated and quiet. */
.sched-facts { margin: 0 0 var(--space-4); font-size: var(--font-sm);
  color: var(--text-muted); line-height: var(--leading-base); }

/* ---------------------------------------------------------------------------
   Settings as sections, not a scroll.
   Eleven cards stacked flat made the page 5,600px tall, so finding the one
   setting you came for meant scrolling past ten you didn't. Collapsed, the
   whole of Settings is one screen and the page becomes a menu — which is what
   it always was.
   --------------------------------------------------------------------------- */
.sset { padding: 0; }
.sset-head {
  display: flex; align-items: center; gap: var(--space-3);
  min-height: var(--tap-min); padding: var(--space-4) var(--space-5);
  cursor: pointer; list-style: none; user-select: none;
}
.sset-head::-webkit-details-marker { display: none; }
.sset-title { flex: 1; min-width: 0; margin: 0; font-size: var(--font-lg); font-weight: var(--weight-emphasis); color: var(--text); }
/* A chevron that turns. Neutral: opening a drawer is way-finding, not the
   action to take now, so it stays off the accent. */
.sset-head::after {
  content: ""; flex: none; width: 9px; height: 9px; margin-right: 2px;
  border-right: 2px solid var(--text-muted); border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--duration-fast) var(--ease-out);
}
.sset[open] > .sset-head::after { transform: rotate(225deg) translate(-2px, -2px); }
.sset-head:hover .sset-title { color: var(--text); }
.sset-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-md); }
/* Everything after the summary is the body, inset to match the old card. */
.sset > *:not(.sset-head) { margin-left: var(--space-5); margin-right: var(--space-5); }
.sset > *:last-child { margin-bottom: var(--space-5); }
.sset[open] > .sset-head { border-bottom: 1px solid var(--border); margin-bottom: var(--space-4); }

/* The reminder toggles overflowed their card by ~64px on a phone: the row was
   laid out to fit its text rather than its container. */
.togglerow { min-width: 0; }
.togglerow > span { min-width: 0; }
.togglerow small { overflow-wrap: anywhere; }

/* ===========================================================================
   Add sheet, week agenda, month list
   =========================================================================== */

/* "Add to your plan" was a permanent six-tile block on Tasks, so the page
   mostly showed ways to add work rather than the work. One button; the tiles
   live in a sheet behind it. */
.addopen { margin-top: var(--space-5); }
#addSheet .addgrid { margin-top: var(--space-4); }

/* The week agenda. Rendered always, shown only where the seven-column grid is
   too narrow to read — see the mobile block above. */
.wk-agenda { display: none; }
.wka-day {
  display: grid; gap: var(--space-2); width: 100%; text-align: left;
  padding: var(--space-3) var(--space-4); cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-2); color: var(--text);
}
.wka-day:hover { border-color: var(--border-strong); }
.wka-day:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wka-day.today { border-color: var(--text-secondary); }
.wka-day.off { background: var(--surface-sunken); }
.wka-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.wka-head b { font-size: var(--font-base); font-weight: var(--weight-emphasis); }
.wka-sum { font-size: var(--font-sm); color: var(--text-muted); white-space: nowrap; }
/* How full the day is. Neutral: it reports a load, it does not grade it, so it
   does not change colour as it fills. */
.wka-bar { display: block; height: 4px; border-radius: var(--radius-full); background: var(--surface-sunken); overflow: hidden; }
.wka-bar > i { display: block; height: 100%; background: var(--text-secondary); border-radius: var(--radius-full); }
.wka-items { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.wka-item { font-size: var(--font-sm); color: var(--text-secondary); }
.wka-item + .wka-item::before { content: "·"; margin-right: var(--space-2); color: var(--border-strong); }
.wka-item.done { color: var(--text-muted); text-decoration: line-through; }
.wka-more { font-size: var(--font-sm); color: var(--text-muted); }

/* The month's names, for when the cells are too small to carry them. */
.mo-list { display: none; margin-top: var(--space-4); }
.mol-day {
  display: grid; gap: 2px; width: 100%; text-align: left; cursor: pointer;
  padding: var(--space-3) var(--space-1); background: none; color: var(--text);
  border: none; border-top: 1px solid var(--border);
}
.mol-day:hover { background: var(--surface-2); border-radius: var(--radius-sm); }
.mol-day:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.mol-date { font-size: var(--font-sm); color: var(--text-muted); }
.mol-evs { display: flex; flex-direction: column; gap: 2px; }
.mol-ev { font-size: var(--font-base); color: var(--text); }

/* The two blocks above declare their desktop default (hidden) AFTER the mobile
   overrides at line ~2000, and a later rule wins at equal specificity whatever
   media query it sits in. So the phone case is restated here, last, where it
   actually takes effect. Matches the 560px breakpoint that hides the grids. */
@media (max-width: 560px) {
  .wk-agenda { display: grid; }
  .mo-list { display: block; }
}

/* ===========================================================================
   ASK — the planning assistant
   =========================================================================== */

/* The conversation takes the page, with the composer pinned under it. Same
   arithmetic as the day grid: everything above and the dock below. */
.askbody {
  display: grid; gap: var(--space-4); align-content: start;
  /* The subtrahend is everything that is not the conversation: header, day bar,
     the Ask heading, the composer, and the floating dock. Measured, not guessed
     — at 320px the composer overlapped the dock by 11px. */
  min-height: max(280px, calc(100dvh - 366px));
  max-height: max(280px, calc(100dvh - 366px));
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-1);
}
@supports not (height: 100dvh) {
  .askbody { min-height: max(280px, calc(100vh - 366px)); max-height: max(280px, calc(100vh - 366px)); }
}

.ask-intro { display: grid; gap: var(--space-3); padding: var(--space-4) 0; }
.ask-intro h2 { margin: 0; font-size: var(--font-lg); }
.ask-starters { display: grid; gap: var(--space-2); margin-top: var(--space-2); }
/* Real sentences rather than a menu of features: tapping one teaches what
   typing one would do. */
.ask-starter {
  text-align: left; width: 100%; min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4); cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-2); color: var(--text-secondary); font-size: var(--font-base);
}
.ask-starter:hover { border-color: var(--border-strong); color: var(--text); }
.ask-starter:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.ask-turn { display: grid; gap: var(--space-2); }
.ask-turn p { margin: 0; font-size: var(--font-base); line-height: var(--leading-base); }
.ask-turn.user { justify-items: end; }
.ask-turn.user p {
  max-width: 85%; padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
}
.ask-turn.bot p { color: var(--text); }
.ask-turn.bot.thinking p { color: var(--text-muted); }
.ask-photo { font-size: var(--font-sm); color: var(--text-muted); }

/* The approval list. Every row starts ticked but nothing is written until the
   button is pressed — the same contract the import preview works under. */
.ask-props {
  display: grid; gap: var(--space-3); padding: var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-2);
}
.ask-props-head {
  font-size: var(--font-sm); font-weight: var(--weight-emphasis); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .5px;
}
.ask-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.ask-prop { display: flex; gap: var(--space-3); align-items: flex-start; }
/* Unticked is dimmed, not struck through: a choice not yet made, not a mistake. */
.ask-prop.off { opacity: .5; }
.ask-keep { display: flex; align-items: center; min-height: 24px; }
.ask-prop-main { display: grid; gap: 2px; min-width: 0; }
.ask-prop-t { font-size: var(--font-base); color: var(--text); }
.ask-prop-why { font-size: var(--font-sm); color: var(--text-muted); }
.ask-props .btn { margin-top: 0; }
.ask-done { margin: 0; font-size: var(--font-sm); color: var(--text-secondary); }

.askpending {
  display: flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-2); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-full);
  background: var(--surface-2); font-size: var(--font-sm); color: var(--text-secondary);
}
.askpending-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.askform { display: flex; gap: var(--space-2); align-items: center; margin-top: var(--space-3); }
.askform input#askInput { flex: 1; min-width: 0; border-radius: var(--radius-full); }
.askphoto { flex: none; border: 1px solid var(--border-control); border-radius: var(--radius-full); }
/* Send is the action to take now, so it takes the accent — the one accent on
   this screen. */
.asksend {
  flex: none; width: var(--tap-min); min-height: var(--tap-min); margin-top: 0;
  padding: 0; border-radius: var(--radius-full); font-size: var(--font-lg);
}

/* Said once, under the proposals it applies to — not as a banner on every turn.
   It is a statement of what just happened, not a warning. */
.ask-offline { margin: 0; font-size: var(--font-sm); color: var(--text-muted); }

/* ===========================================================================
   Account dropdown, and completed work on the schedule
   =========================================================================== */

/* Anchored under the header button rather than a full-screen sheet. Fixed
   rather than absolute because the header is inside .wrap, which is centred and
   max-width'd — an absolute panel would hang off the column, not the button. */
.acctmenu {
  position: fixed; z-index: 55;
  top: calc(var(--space-16) + var(--space-2)); right: var(--space-5);
  width: min(300px, calc(100vw - 2 * var(--space-5)));
  display: grid; gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
}
.acctmenu-head { display: flex; align-items: center; gap: var(--space-3); }
.acctmenu-who { min-width: 0; }
.acctmenu-who h2 { margin: 0; font-size: var(--font-base); font-weight: var(--weight-emphasis); }
.acctmenu-who .acctrow { padding: 0; border: none; font-size: var(--font-sm); }
.acctmenu-items { display: grid; gap: 2px; padding-top: var(--space-2); border-top: 1px solid var(--border); }
.acctmenu-item {
  text-align: left; width: 100%; min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3); cursor: pointer;
  border: none; border-radius: var(--radius-sm); background: none;
  color: var(--text); font-size: var(--font-base);
}
.acctmenu-item:hover { background: var(--surface-2); }
.acctmenu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Red only on the one thing that destroys something. */
.acctmenu-item.danger { color: var(--danger); }
.acctmenu-item.danger:hover { background: var(--danger-quiet); }
@media (max-width: 560px) { .acctmenu { top: calc(var(--space-12) + var(--space-4)); } }

/* A finished task keeps its slot on the grid. Dimmed and struck through, not
   removed — the day is a record of what happened as much as a plan for what is
   left, and erasing each block as it is ticked deletes the evidence. Neutral:
   done is a state, not a score, so it takes no colour of its own. */
/* "Protected" (SPEC.md §7 Schedule) — border-style only, no size/content
   change, so it's safe at every tight/micro/nano slot size this grid runs. */
.dg-block.task.protected { border-style: dashed; }
.dg-block.task.done { opacity: .55; }
.dg-block.task.done .info .t { text-decoration: line-through; }
.dg-block.task.done::before { opacity: .5; }

/* ===========================================================================
   Phone pass — measured on iPhone SE / 15 / 15 Pro Max with touch emulation
   =========================================================================== */

/* Both header buttons were under 44px to a thumb. The visible boxes stay as
   they are — a 44px square gear would dominate the header — and the hit area is
   extended with a pseudo-element, the same trick .chk and .ti-actions use. */
.headerbtn, .accountbtn { position: relative; }
.headerbtn::after, .accountbtn::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 100%; min-width: var(--tap-min); height: var(--tap-min);
}

/* Footer legal links were 19px tall. WCAG 2.5.8 wants 24 for anything that is
   not inline in a sentence, and a row of nav links is not a sentence. */
.appfooter .af-links a { display: inline-flex; align-items: center; min-height: 24px; }

/* ---- small phones ----------------------------------------------------------
   On a 375×667 screen the day grid started 397px down — 60% of the way — so the
   schedule itself was almost entirely below the fold, behind a heading, a zoom
   bar, an hours row and two glance cards. Everything here reclaims space that
   was saying something already said elsewhere on the same screen. */
@media (max-width: 400px) {
  /* The zoom bar directly beneath says Day/Week/Month/Year and the day bar above
     says the date, so the heading is decoration. Hidden visually, kept in the
     accessibility tree — a screen reader still needs the section to have a name. */
  #view-schedule > .card > #scheduleTitle {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  /* The date is in the day bar two rows up. Repeating it here is what pushed
     this row onto two lines and wrapped the button to "Edit / day". */
  .dh-date { display: none; }
  .dh-row .btn { white-space: nowrap; }
  /* Now and Next say what is happening and what is next — which is the top of
     the grid directly below, because the grid scrolls itself to now on entry.
     On a screen this size that duplication costs about 60px of the one thing
     the view exists to show. */
  #view-schedule .nownext { display: none; }

  /* The hours card was 88px — a bordered box, its own padding, and the row
     inside it padded again — to show one line and a button. Nested padding is
     the usual reason a small screen runs out of room. */
  .dayhours { padding: 0; margin-bottom: var(--space-3); background: none; border: none; }
  .dayhours .dh-row { padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border); border-radius: var(--radius-sm); }
  .dayhours .dh-row .btn.small { padding: var(--space-1) var(--space-3); }
}

/* ===========================================================================
   Progress — the competitive half of the app, all of it, in one place

   Every bar and dot here is drawn in --text-secondary on a --surface-sunken
   track. That is not laziness: the moment one of these gets a colour, some
   state on this screen becomes a verdict, and the whole point of the four
   mechanics is that none of them can tell you that you failed.
   =========================================================================== */
.pg-body { display: grid; gap: var(--space-4); }

.pg-sec {
  display: grid; gap: var(--space-3);
  padding: var(--space-4); border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--surface); box-shadow: var(--shadow-sm);
}
.pg-sec h3 {
  margin: 0; font-size: var(--font-sm); font-weight: var(--weight-emphasis);
  color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px;
}
.pg-lead { margin: 0; font-size: var(--font-base); color: var(--text); }
.pg-lead b { font-weight: var(--weight-emphasis); font-variant-numeric: tabular-nums; }
.pg-foot { margin: 0; font-size: var(--font-sm); color: var(--text-muted); }
.pg-num {
  font-size: var(--font-xl); font-weight: var(--weight-emphasis);
  font-variant-numeric: tabular-nums; line-height: 1;
}
.pg-numk { margin-left: var(--space-2); font-size: var(--font-sm); color: var(--text-muted); }
/* Comparison is off. The section says so without dressing itself down so far
   that it looks broken. */
.pg-off { background: var(--surface-2); }
.pg-off .btn { justify-self: start; }
.pg-sec > .btn { justify-self: start; }

/* Coming back — three weeks of dots. Showed up, did not, or rest day. The
   "did not" dot is an empty circle and never a red one: the panel it sits in
   is about recovery, and colouring the miss would make it about the miss. */
.pg-dots { display: flex; flex-wrap: wrap; gap: 5px; }
/* An empty dot is a day you did not show up, which is information — so it
   takes --border-control (3.5:1 light, 3.4:1 dark) and not --border-strong,
   which is 1.58:1 and would make the whole row a guess. */
.pg-dot {
  width: 9px; height: 9px; border-radius: var(--radius-full);
  border: 1px solid var(--border-control); background: none;
}
.pg-dot.on { background: var(--text-secondary); border-color: var(--text-secondary); }
.pg-dot.rest { width: 9px; height: 1px; border: none; background: var(--border-control); align-self: center; }

/* Baseline — this week and the average it is measured against, on one scale.
   Neither bar is tinted, because being under your own average is a fact. */
.pg-row {
  display: grid; grid-template-columns: 5.5rem 1fr 4.5rem;
  align-items: center; gap: var(--space-3);
}
.pg-k { font-size: var(--font-sm); color: var(--text-secondary); }
.pg-v {
  font-size: var(--font-sm); color: var(--text); text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pg-track {
  display: block; height: 6px; border-radius: var(--radius-full);
  background: var(--surface-sunken); border: 1px solid var(--border); overflow: hidden;
}
.pg-track i { display: block; height: 100%; min-width: 2px; background: var(--text-secondary); }
/* The average is the reference rather than the achievement, so it is drawn
   quieter — but it is still the number the panel is comparing against, so it
   stays above the 3:1 floor for non-text information. */
.pg-row.is-avg .pg-track i { background: var(--border-control); }
.pg-row.is-avg .pg-k, .pg-row.is-avg .pg-v { color: var(--text-muted); }

/* Consistency — the week's shape. Seven columns whatever the day, so a Tuesday
   is not a wider bar than a Friday. */
.pg-bars {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--space-2);
  align-items: end; height: 48px;
}
.pg-bars i {
  display: block; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--text-secondary);
}
/* A rest day is shown so the week reads as seven days, and drawn as a line so
   it cannot be read as a bar that failed to grow. It is not scored. */
.pg-bars i.rest { height: 1px; background: var(--border-control); border-radius: 0; }

/* Personal bests. A record is set once and cannot be taken away, so nothing
   here has a "lost" state to style — only a running one. */
.pg-pbs { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.pg-pb { display: flex; align-items: baseline; gap: var(--space-3); }
.pg-pbv {
  flex: none; min-width: 3rem; font-size: var(--font-lg);
  font-weight: var(--weight-emphasis); font-variant-numeric: tabular-nums;
}
.pg-pbv i { font-style: normal; font-size: var(--font-sm); color: var(--text-muted); margin-left: 2px; }
.pg-pbl { font-size: var(--font-sm); color: var(--text-secondary); }
.pg-pbnow {
  display: inline-block; margin-left: var(--space-2); font-size: var(--font-sm);
  font-weight: var(--weight-emphasis); color: var(--text-muted);
}
.pg-pb.is-live .pg-pbnow { color: var(--text-secondary); }

/* The group. Alphabetical, adherence only, and your own row marked so you can
   find yourself — marked, not ranked. */
.pg-cmp { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.pg-cmprow {
  display: grid; grid-template-columns: 5.5rem 1fr 3.5rem;
  align-items: center; gap: var(--space-3);
}
.pg-cmpn {
  font-size: var(--font-sm); color: var(--text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pg-cmprow.is-me .pg-cmpn { color: var(--text); font-weight: var(--weight-emphasis); }

/* What the app noticed. One sentence, and it is set slightly larger than the
   panels around it because it is the only thing on this screen that is a claim
   about you rather than a number — it should read like a sentence someone said,
   not like a readout. */
.pg-notice .pg-lead { font-size: var(--font-lg); line-height: var(--leading-tight); }

/* The weekly review dialog. Two boxes and one change; the measure appears only
   once a change exists. */
#reviewModal .field { display: grid; gap: var(--space-2); }
#reviewModal textarea { resize: vertical; min-height: 62px; }
#reviewModal .field .hint { margin: 0; }
#revVerdict { font-weight: var(--weight-emphasis); color: var(--text); }

/* ---- permission to do less -------------------------------------------- */
/* The one place the app asks for less. Deliberately not styled as a warning:
   no accent, no border colour, no icon. It is a question, and "Let it go" is
   as ordinary a button as "Keep it" — a delete dressed as a danger action is
   a delete that says you are giving up. */
.deadrow {
  display: grid; gap: var(--space-3); margin-bottom: var(--space-4);
  padding: var(--space-4); border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--surface-2);
}
.dead-q { margin: 0; font-size: var(--font-base); color: var(--text); }
.dead-q b { font-weight: var(--weight-emphasis); }
.dead-acts { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* ---- the one thing the app cannot derive ------------------------------- */
/* Sits under the carry-forward row in the check-in. Optional, and it looks
   optional — the label says so and an empty field is a valid answer. */
.ci-sleep {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); margin-top: var(--space-3);
}
.ci-sleep label { font-size: var(--font-sm); color: var(--text-secondary); }
.ci-sleep label span { color: var(--text-muted); }
.ci-sleep input { width: 5.5rem; flex: none; text-align: center; }

@media (max-width: 400px) {
  .pg-sec { padding: var(--space-3); }
  .pg-row { grid-template-columns: 4.5rem 1fr 4.25rem; gap: var(--space-2); }
  .pg-cmprow { grid-template-columns: 4.5rem 1fr 3rem; gap: var(--space-2); }
  .pg-notice .pg-lead { font-size: var(--font-base); }
  .dead-acts .btn { flex: 1; }
}

/* The app accounting for its own decision. Quiet by construction: no accent,
   no border, no icon — it is an explanation, not something to act on, and the
   accent rule reserves colour for where you act. The label is set apart so the
   sentence reads as an answer to a question rather than as another fact. */
.td-why {
  margin: var(--space-3) 0 0; font-size: var(--font-sm);
  color: var(--text-secondary); line-height: var(--leading-base);
}
.td-why-k {
  display: block; font-size: var(--font-sm); color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .5px;
  font-weight: var(--weight-emphasis); margin-bottom: 2px;
}

/* ---- the demo bar ------------------------------------------------------ */
/* Pinned to the top of the app, under the header, so it is present without
   being in the way. It carries the accent on its button because 'keep this'
   IS the action to take — the one place on this bar where the accent rule
   points at something real. */
.demobar {
  position: sticky; top: 0; z-index: 15;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  margin-bottom: var(--space-3);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-full);
  background: var(--surface-2);
  font-size: var(--font-sm); color: var(--text-secondary);
}
.demobar > span { min-width: 0; flex: 1; }
.demobar .btn { flex: none; }
@media (max-width: 400px) {
  .demobar { border-radius: var(--radius-md); }
}

/* ---- undo -------------------------------------------------------------- */
/* Sits above the dock, centred, and never covers the dock itself — the way
   out of a screen must not be blocked by an offer about the last thing you
   did. It takes no accent: undo is an escape hatch, not the action to take
   now, and the accent rule reserves colour for the latter. */
.undobar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--space-16) + var(--space-6) + env(safe-area-inset-bottom, 0px));
  z-index: 20; display: flex; align-items: center; gap: var(--space-3);
  max-width: calc(100vw - var(--space-8));
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-full);
  background: var(--surface); box-shadow: var(--shadow-md);
  font-size: var(--font-sm); color: var(--text-secondary);
}
.undobar > span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.undobar .btn { flex: none; }

@media (prefers-reduced-motion: no-preference) {
  /* It arrives; it does not bounce in. 200ms and 6px of travel is enough to
     be noticed at the edge of vision without pulling the eye off the task. */
  .undobar:not([hidden]) { animation: undo-in var(--duration-base) var(--ease-out) both; }
  @keyframes undo-in { from { opacity: 0; transform: translate(-50%, 6px); } }
}

/* ===========================================================================
   Moving through the app

   Native View Transitions. Everything in this block is inside a
   prefers-reduced-motion guard and behind @supports, so a browser without the
   API and a person who has asked for stillness both get the app exactly as it
   was before this section existed — an instant, honest screen swap.

   The motion budget from tokens.css still applies: 250ms, ease-out, no bounce.
   These are translations and opacity only. Nothing here scales a whole screen
   or spins, because a planner that performs at you is a planner you stop
   trusting to be calm.
   =========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  /* The shell — header, day bar, dock, footer — is the part that does NOT
     move. Naming the main region lifts it into its own layer so the shell can
     hold still underneath while only the content travels. That stillness is
     what makes the movement read as navigation rather than as a page reload. */
  #views { view-transition-name: screen; }

  ::view-transition-group(screen) { animation-duration: var(--duration-slow); }
  ::view-transition-old(screen),
  ::view-transition-new(screen) {
    animation-duration: var(--duration-slow);
    animation-timing-function: var(--ease-out);
    animation-fill-mode: both;
    /* The default blend makes two opaque screens muddy where they overlap. */
    mix-blend-mode: normal;
  }

  /* The shell cross-fades fast: it is nearly identical between views, and the
     only things actually changing in it are the dock pill and whether the day
     bar is ghosted. Long enough to smooth those, short enough to be unnoticed. */
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: var(--duration-fast);
    animation-timing-function: var(--ease-out);
  }

  /* --- the screen does not move ------------------------------------------ */
  /* This was a 24px directional slide plus a depth scale. Both are gone, and
     the reason is measurable rather than a matter of taste: #views is 367px
     tall on Tasks and 961px on Schedule, and a transition GROUP interpolates
     its box from the old size to the new one. Every navigation therefore
     dragged the page through ~600px of height change — the content visibly
     resizing under the pointer, which is what "the page shifts" is.

     Killing the group's animation is the fix for that: the new size applies
     at once and only the snapshots animate. Dropping the slide as well is a
     judgement — a planner that moves the page on every tap is not calm, and
     the motion rule in tokens.css exists to prevent exactly this kind of
     decorative movement. What is left is a cross-fade: the screen changes,
     nothing travels. */
  ::view-transition-group(screen) { animation: none; }

  /* A snapshot is a replaced element. Left to itself it is stretched to the
     group's box, so the shorter screen would be distorted for the length of
     the fade; `object-fit: none` keeps each at its natural size and the top
     anchor keeps the part you are actually looking at where it was. */
  ::view-transition-old(screen),
  ::view-transition-new(screen) {
    height: 100%; width: 100%;
    object-fit: none; object-position: top center;
    animation-duration: var(--duration-base);
    animation-timing-function: var(--ease-out);
    animation-fill-mode: both;
    mix-blend-mode: normal;
  }
  html[data-nav]::view-transition-old(screen) { animation-name: vt-out; }
  html[data-nav]::view-transition-new(screen) { animation-name: vt-in; }

  @keyframes vt-out { to { opacity: 0; } }
  @keyframes vt-in  { from { opacity: 0; } }

  /* --- the shared element ------------------------------------------------- */
  /* The card you tapped and the heading you land on carry the same name, so the
     browser interpolates position, size and shape between them. No keyframes:
     the default group animation IS the morph, and overriding it would replace
     the effect with a cross-fade. All this does is match the house curve. */
  ::view-transition-group(morph-subject) {
    animation-duration: var(--duration-slow);
    animation-timing-function: var(--ease-out);
  }
  ::view-transition-old(morph-subject),
  ::view-transition-new(morph-subject) {
    animation-duration: var(--duration-slow);
    animation-timing-function: var(--ease-out);
    height: 100%;
  }

  /* --- lists arrive in order ---------------------------------------------- */
  /* A ladder of 20ms, capped at eight steps by transitions.js, so the longest
     any list takes to settle is 160ms after the screen itself. Below the cap
     items land together — a forty-item list must not take four seconds. */
  .task-item,
  #progressBody .pg-sec {
    animation: vt-item var(--duration-base) var(--ease-out) both;
    animation-delay: calc(var(--stagger, 0) * 20ms);
  }
  @keyframes vt-item { from { opacity: 0; transform: translateY(6px); } }

  /* --- panels reveal as they enter the viewport --------------------------- */
  /* Scroll-driven, so it is tied to the scroll position rather than to a timer
     and reverses correctly when you scroll back up. Behind @supports because
     without a scroll timeline `animation-timeline: view()` silently falls back
     to the document timeline and every panel would animate once on load. */
  @supports (animation-timeline: view()) {
    #progressBody .pg-sec {
      animation: vt-reveal linear both;
      animation-timeline: view();
      /* Complete the reveal over the first 25% of the panel's travel through
         the viewport, and never fade it back out at the top. */
      animation-range: entry 0% cover 25%;
    }
    @keyframes vt-reveal {
      from { opacity: 0; transform: translateY(14px); }
      to   { opacity: 1; transform: none; }
    }
  }
}
