:root {
  --sidebar: #10152b;
  --sidebar-hover: #1b2246;
  --sidebar-active: #2b3568;
  --accent: #4361ee;
  --accent-2: #7b2ff7;
  --bg: #f5f6fb;
  --card: #ffffff;
  --border: #e6e8f2;
  --text: #161a2e;
  --muted: #6b7185;
  --red-bg: #fde8e8;
  --red-fg: #c81e3a;
  --amber-bg: #fef3d6;
  --amber-fg: #a8710a;
  --green-bg: #e3f8ec;
  --green-fg: #0f8a4b;
  --blue-bg: #e8edff;
  --blue-fg: #3348c9;
  --purple-bg: #f1e8ff;
  --purple-fg: #7429c9;
  font-family: var(--font-sans);
}

input[type="password"]::-ms-reveal,
input[type="password"]::-ms-clear {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0b0d17;
    --card: #141726;
    --border: #252a42;
    --text: #eef0fb;
    --muted: #8b90ab;
    --sidebar: #0a0d1c;
    --sidebar-hover: #161c38;
    --sidebar-active: #232c58;
    --red-bg: #3a1520;
    --amber-bg: #3a2e0f;
    --green-bg: #0f2e20;
    --blue-bg: #151d3d;
    --purple-bg: #241536;
  }
}

:root[data-theme="dark"] {
  --bg: #0b0d17;
  --card: #141726;
  --border: #252a42;
  --text: #eef0fb;
  --muted: #8b90ab;
  --sidebar: #0a0d1c;
  --sidebar-hover: #161c38;
  --sidebar-active: #232c58;
  --red-bg: #3a1520;
  --amber-bg: #3a2e0f;
  --green-bg: #0f2e20;
  --blue-bg: #151d3d;
  --purple-bg: #241536;
}

/* ---------- Theme concepts ----------

   Two independent axes, kept separate on purpose:

     data-scheme  light | dark   — what `data-theme` has always meant, untouched
     data-concept default | premium | focus | casual — the visual concept

   The old `data-theme` rules above are left exactly as they were, and `default` adds no CSS at
   all, so the app looks byte-for-byte the same until a person picks a concept.

   `data-scheme` is always set explicitly by script (from the stored preference, or the OS
   setting when the person has not chosen). That is what makes it safe to scope a concept to one
   scheme with a plain attribute selector, instead of fighting specificity with the media query.

   To add a concept: copy one block, give it a name, and add a matching option in
   APP_THEMES (script.js). Nothing else has to change. */
/* ---------- Design tokens ----------

   Everything a concept is allowed to change lives here, as a custom property. The defaults are the
   values the app has always used, written once. `default` has no CSS of its own, so with these
   defaults it keeps rendering exactly as it did when every one of them was a literal sitting in an
   individual rule.

   The point of the families is that a concept overrides a *ramp*, not a hundred rules. Change
   `--radius-*` and every surface goes square together; change `--type-scale` and the whole app's
   typography moves together; change `--density` and the app tightens or breathes as one. */

:root {
  /* --- Typography ----------------------------------------------------
     The size ramp is `role x scale`, so a concept can retune the voice of the app with one number
     and still override an individual step when it wants to. These are the sizes the app actually
     used, kept verbatim, so Default is unchanged. */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  --font-display: var(--font-sans);
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --type-scale: 1;
  --fs-tiny: calc(9px * var(--type-scale));      /* chart axis + legend micro-labels */
  --fs-caption: calc(10px * var(--type-scale));   /* sidebar section caption, chart labels */
  --fs-micro: calc(10.5px * var(--type-scale));  /* calendar event chips */
  --fs-label: calc(11px * var(--type-scale));    /* nav badge, keycap, hour rail */
  --fs-meta: calc(11.5px * var(--type-scale));   /* hints, secondary meta, badges */
  --fs-small: calc(12px * var(--type-scale));    /* checkmarks, flags, small toggles */
  --fs-body-s: calc(12.5px * var(--type-scale)); /* secondary body copy */
  --fs-body: calc(13px * var(--type-scale));     /* tabs, chips, control labels */
  --fs-ui: calc(13.5px * var(--type-scale));     /* buttons, inputs, list rows */
  --fs-lead: calc(14px * var(--type-scale));     /* nav labels, task titles */
  --fs-title: calc(15px * var(--type-scale));    /* brand name, card heading */
  --fs-input: calc(16px * var(--type-scale));    /* text fields; 16px stops iOS zoom-on-focus */
  --fs-stat: calc(18px * var(--type-scale));     /* stat number, compact */
  --fs-h1: calc(20px * var(--type-scale));       /* page title, compact */
  --fs-display: calc(22px * var(--type-scale));  /* page title */

  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --fw-heavy: 800;

  --lh-tight: 1;      /* single-line controls: buttons, chips, icon rows */
  --lh-snug: 1.2;
  --lh-dense: 1.3;
  --lh-dense-2: 1.35;
  --lh-normal: 1.4;
  --lh-base: 1.45;
  --lh-relaxed: 1.5;
  --lh-loose: 1.6;

  --tracking-wide: 0.3px;
  --tracking-caps: 1.4px;

  /* --- Shape ----------------------------------------------------------
     A ramp rather than three one-off corners, so a concept can make the whole app round or
     square coherently. Circles (50%) and pills (999px) stay literal where they are intent — an
     avatar is a circle, not a themed radius. */
  --radius-none: 0;
  --radius-xs: 5px;     /* checkbox, keycap, calendar event */
  --radius-sm: 6px;     /* badge, close button, week event */
  --radius-md: 7px;     /* small buttons, capture-plan rows */
  --radius-row: 8px;    /* tabs, list rows, search hits, avatars */
  --radius-ctl: 9px;    /* inputs, buttons, task rows */
  --radius-tag: 10px;   /* nav badge, calendar cells, ask answer */
  --radius-card: 12px;  /* cards */
  --radius-modal: 14px; /* dialogs, brand mark */
  --radius-lg: 16px;    /* ask panel */
  --radius-sheet: 18px; /* mobile bottom sheet */

  /* --- Density --------------------------------------------------------
     One multiplier for the padded surfaces that carry the rhythm of the app. A concept that wants
     a power-user layout sets 0.86; one that wants to breathe sets 1.08. */
  --density: 1;
  --pad-card: calc(18px * var(--density));
  --pad-ctl-y: calc(9px * var(--density));
  --pad-ctl-x: calc(16px * var(--density));
  --pad-nav-x: calc(10px * var(--density));
  --pad-input-x: calc(14px * var(--density));
  --pad-stat-y: calc(14px * var(--density));
  --pad-row-y: calc(11px * var(--density));
  --pad-row-x: calc(8px * var(--density));
  --pad-modal: calc(22px * var(--density));
  --gap-row: calc(12px * var(--density));

  /* --- Borders --------------------------------------------------------
     Focus rings are whole shadows, not just an outline, so a concept can change the colour of the
     ring without touching every focus rule. */
  --border-w: 1px;
  --border-w-strong: 2px;
  --ring-w: 2px;
  --ring-w-lg: 3px;
  --ring: 0 0 0 var(--ring-w) var(--blue-bg);
  --ring-lg: 0 0 0 var(--ring-w-lg) var(--blue-bg);
  --ring-inset: 0 0 0 var(--ring-w) var(--card);
  /* --- Elevation ------------------------------------------------------ */
  --shadow-xs: 0 1px 2px rgba(16, 21, 43, 0.04);
  --shadow-sm: 0 1px 3px rgba(16, 21, 43, 0.08);
  --shadow-md: 0 8px 24px rgba(16, 21, 43, 0.12);
  --shadow-lg: 0 8px 24px rgba(16, 21, 43, 0.18);
  --shadow-modal: 0 16px 40px rgba(0, 0, 0, 0.18);
  --shadow-drawer: -8px 0 30px rgba(0, 0, 0, 0.15);
  --shadow-none: none;
  --shadow-card: var(--shadow-xs);
  --shadow-pop: var(--shadow-md);

  /* --- Motion ---------------------------------------------------------
     `--motion-scale` is the one lever: 0 stills the app for a focus concept, 1.4 makes it
     springy. The steps are the durations that were already in use. */
  --motion-scale: 1;
  --dur-1: calc(140ms * var(--motion-scale));
  --dur-2: calc(150ms * var(--motion-scale));
  --dur-3: calc(160ms * var(--motion-scale));
  --dur-4: calc(180ms * var(--motion-scale));
  --dur-5: calc(200ms * var(--motion-scale));
  --dur-6: calc(250ms * var(--motion-scale));
  --ease: ease;

  /* --- Surfaces -------------------------------------------------------
     Glass needs somewhere to put the blur, and a gradient needs a home. Both default to off, so a
     theme that wants neither pays nothing. */
  --card-backdrop: none;
  --app-gradient: none;
  --sidebar-bg-image: none;
  /* The hairlines inside the sidebar were white-on-navy literals. A pale sidebar cannot see them. */
  --sidebar-divider: rgba(255, 255, 255, 0.16);
  --sidebar-rule: rgba(255, 255, 255, 0.12);

  /* The sidebar and brand were hard-coded light-on-navy. They are tokens now so a theme with a
     pale sidebar (Deep Work) does not end up with white text on white. */
  --sidebar-fg: #cfd3ec;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #8f97b8;
  --sidebar-caption: #7d86aa;
  --nav-fg: #b8bcda;
  --sidebar-badge-bg: #2b3568;
  --sidebar-badge-fg: #dfe3ff;
  --brand-gradient: linear-gradient(135deg, #17254d 0%, #3157bd 58%, #0e9f97 150%);
  --logo-gradient: linear-gradient(145deg, #e9f0ff, #9fb6ef);
  --logo-fg: #17254d;
  --warning-border: #e6c56a;
}

/* Premium / Elegant — warm neutrals, bronze accent, softer radii and a lifted shadow. */
:root[data-scheme="light"][data-concept="premium"] {
  --sidebar: #1c1917;
  --sidebar-hover: #292524;
  --sidebar-active: #3f3a34;
  --accent: #8a6d3b;
  --accent-2: #b08d57;
  --bg: #faf9f7;
  --card: #ffffff;
  --border: #e8e2d8;
  --text: #1c1917;
  --muted: #7a6f63;
  --red-bg: #fbeceb;
  --red-fg: #9b2c2c;
  --amber-bg: #fbf0dc;
  --amber-fg: #8a5a12;
  --green-bg: #e7f1e7;
  --green-fg: #2f6b3a;
  --blue-bg: #e7edf4;
  --blue-fg: #2f4a6b;
  --purple-bg: #efe9f3;
  --purple-fg: #5b3f73;
  --shadow-card: 0 1px 3px rgba(28, 25, 23, 0.06), 0 8px 24px rgba(28, 25, 23, 0.05);
  --shadow-pop: 0 12px 32px rgba(28, 25, 23, 0.16);
  --brand-gradient: linear-gradient(135deg, #1c1917 0%, #8a6d3b 62%, #b08d57 140%);
  --logo-gradient: linear-gradient(145deg, #e8dcc0, #c9a961);
  --logo-fg: #1c1917;
  --sidebar-fg: #e6ddd0;
  --sidebar-fg-strong: #f7f2e8;
  --sidebar-fg-muted: #a89a88;
  --sidebar-caption: #9c8f80;
  --nav-fg: #cfc3b3;
  --sidebar-badge-bg: #4a4034;
  --sidebar-badge-fg: #f0e6d6;
}

:root[data-scheme="dark"][data-concept="premium"] {
  --sidebar: #100e0c;
  --sidebar-hover: #1f1b17;
  --sidebar-active: #322b23;
  --accent: #c9a961;
  --accent-2: #e0c489;
  --bg: #12100e;
  --card: #1c1917;
  --border: #302a24;
  --text: #f5f1e8;
  --muted: #9c8f80;
  --red-bg: #3a1f1c;
  --amber-bg: #3a2e17;
  --green-bg: #1c3324;
  --blue-bg: #1c2733;
  --purple-bg: #2c2036;
}

/* Deep Work — near-monochrome, flat, square. Nothing competes for attention. */
:root[data-scheme="light"][data-concept="focus"] {
  --sidebar: #ffffff;
  --sidebar-hover: #f2f2f0;
  --sidebar-active: #e8e8e4;
  --accent: #3f4a55;
  --accent-2: #5a6672;
  --bg: #fbfbfa;
  --card: #ffffff;
  --border: #e5e5e1;
  --text: #1c1c1a;
  --muted: #78786f;
  --red-bg: #f0eaea;
  --red-fg: #8f2f2f;
  --amber-bg: #f3efe2;
  --amber-fg: #7a5c1c;
  --green-bg: #e8efe8;
  --green-fg: #33613a;
  --blue-bg: #e8edf0;
  --blue-fg: #3a4a55;
  --purple-bg: #ebe8f0;
  --purple-fg: #4a4258;
  --shadow-card: none;
  --shadow-pop: 0 2px 8px rgba(28, 28, 26, 0.1);
  --brand-gradient: linear-gradient(135deg, #1c1c1a 0%, #3f4a55 60%, #5a6672 140%);
  --logo-gradient: linear-gradient(145deg, #e8e8e4, #93a3ae);
  --logo-fg: #1c1c1a;
  /* A pale sidebar needs dark text. This is the reason these are tokens and not literals. */
  --sidebar-fg: #3a3a36;
  --sidebar-fg-strong: #1c1c1a;
  --sidebar-fg-muted: #6f6f68;
  --sidebar-caption: #787870;
  --nav-fg: #4a4a45;
  --sidebar-badge-bg: #e8e8e4;
  --sidebar-badge-fg: #3a3a36;
  /* A pale sidebar cannot see a white hairline. */
  --sidebar-divider: rgba(28, 28, 26, 0.16);
  --sidebar-rule: rgba(28, 28, 26, 0.12);
}

:root[data-scheme="dark"][data-concept="focus"] {
  --sidebar: #131312;
  --sidebar-hover: #1e1e1c;
  --sidebar-active: #2a2a27;
  --accent: #93a3ae;
  --accent-2: #aebbbf;
  --bg: #0f0f0e;
  --card: #171716;
  --border: #2a2a27;
  --text: #e8e8e4;
  --muted: #8a8a82;
  --red-bg: #331c1a;
  --amber-bg: #332e1c;
  --green-bg: #1e2f20;
  --blue-bg: #1c2429;
  --purple-bg: #26222e;
  --shadow-card: none;
  --shadow-pop: 0 2px 8px rgba(0, 0, 0, 0.5);
}

/* Casual — warm, round and soft. Terracotta and teal instead of the default blues. */
:root[data-scheme="light"][data-concept="casual"] {
  --sidebar: #2d2a3b;
  --sidebar-hover: #3b3750;
  --sidebar-active: #4d4766;
  --accent: #e2725b;
  --accent-2: #2a9d8f;
  --bg: #fdf7f4;
  --card: #ffffff;
  --border: #f0e2db;
  --text: #2d2622;
  --muted: #8a7a70;
  --red-bg: #fdeae6;
  --red-fg: #b5432c;
  --amber-bg: #fdf0d9;
  --amber-fg: #96631a;
  --green-bg: #e2f2ee;
  --green-fg: #1f7a63;
  --blue-bg: #e6f0f5;
  --blue-fg: #2f6b85;
  --purple-bg: #f0e8f6;
  --purple-fg: #74458f;
  --shadow-card: 0 2px 6px rgba(45, 38, 34, 0.05);
  --shadow-pop: 0 10px 28px rgba(45, 38, 34, 0.14);
  --brand-gradient: linear-gradient(135deg, #2d2a3b 0%, #e2725b 60%, #2a9d8f 140%);
  --logo-gradient: linear-gradient(145deg, #ffe0d6, #f2846b);
  --logo-fg: #2d2a3b;
  --sidebar-fg: #e6e0ea;
  --sidebar-fg-strong: #fdf7f4;
  --sidebar-fg-muted: #a89fb0;
  --sidebar-caption: #9d938a;
  --nav-fg: #d8d0dd;
  --sidebar-badge-bg: #4d4766;
  --sidebar-badge-fg: #fdeee9;
}

:root[data-scheme="dark"][data-concept="casual"] {
  --sidebar: #201d2b;
  --sidebar-hover: #2e2a3c;
  --sidebar-active: #3d3852;
  --accent: #f2846b;
  --accent-2: #4fc3b2;
  --bg: #191720;
  --card: #211f2b;
  --border: #322e40;
  --text: #f4f0ea;
  --muted: #9d938a;
  --red-bg: #3d211c;
  --amber-bg: #3a2f19;
  --green-bg: #1b332e;
  --blue-bg: #1d2c33;
  --purple-bg: #2e2438;
  --shadow-card: 0 2px 6px rgba(0, 0, 0, 0.28);
  --shadow-pop: 0 10px 28px rgba(0, 0, 0, 0.42);
}

/* The non-colour half of a concept. `data-concept` on its own, with no `data-scheme`, because shape,
   type, density and motion are the same whether the person is in light or dark — only the palette
   changes. That keeps each concept to one system block plus one palette block per scheme. */

:root[data-concept="premium"] {
  /* Refined means a hair rounder and a hair larger, all the way up the ramp. */
  --radius-xs: 7px;
  --radius-sm: 8px;
  --radius-md: 9px;
  --radius-row: 10px;
  --radius-tag: 12px;
  --radius-ctl: 11px;
  --radius-card: 16px;
  --radius-modal: 16px;
  --radius-lg: 22px;
  --radius-sheet: 20px;
  --type-scale: 1.03;
  --tracking-wide: 0.2px;
  /* A little more air than the default — that is what "refined" usually turns out to mean. */
  --density: 1.05;
  /* The palette is lower contrast than the default, so the border carries more of the structure. */
  --border-w: 1px;
  --ring-w: 2px;
  /* Unhurried. */
  --motion-scale: 1.15;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

:root[data-concept="focus"] {
  /* Square. The whole ramp collapses towards a hairline, not just the cards. */
  --radius-xs: 2px;
  --radius-sm: 2px;
  --radius-md: 2px;
  --radius-row: 3px;
  --radius-ctl: 3px;
  --radius-tag: 3px;
  --radius-card: 4px;
  --radius-modal: 4px;
  --radius-lg: 4px;
  --radius-sheet: 4px;
  /* Slightly smaller, so more fits on a screen that is being used to work. */
  --type-scale: 0.97;
  --lh-normal: 1.35;
  --lh-base: 1.4;
  /* Tighter rows. */
  --density: 0.9;
  /* Still. Nothing moves that does not have to. */
  --motion-scale: 0;
  --border-w: 1px;
  --ring-w: 2px;
}

:root[data-concept="casual"] {
  /* Round, and rounder the bigger the surface — the opposite of the default ramp. */
  --radius-xs: 10px;
  --radius-sm: 11px;
  --radius-md: 12px;
  --radius-row: 13px;
  --radius-tag: 14px;
  --radius-ctl: 14px;
  --radius-card: 18px;
  --radius-modal: 20px;
  --radius-lg: 26px;
  --radius-sheet: 28px;
  --type-scale: 1.04;
  --lh-normal: 1.5;
  --lh-base: 1.55;
  --density: 1.06;
  --motion-scale: 1.3;
  --ease: cubic-bezier(0.34, 1.4, 0.64, 1);
}

/* Aurora — the gradient-and-glass concept. A violet-to-cyan wash behind translucent surfaces, the
   largest corners in the app, and a display face that leans geometric. */

:root[data-concept="aurora"] {
  --radius-xs: 12px;
  --radius-sm: 14px;
  --radius-md: 16px;
  --radius-row: 18px;
  --radius-ctl: 18px;
  --radius-tag: 16px;
  --radius-card: 24px;
  --radius-modal: 24px;
  --radius-lg: 30px;
  --radius-sheet: 30px;
  --font-display: "Avenir Next", "Segoe UI", "Helvetica Neue", var(--font-sans);
  --type-scale: 1.05;
  --lh-normal: 1.45;
  --lh-base: 1.5;
  --tracking-wide: 0.4px;
  --density: 1.08;
  /* Glass: the surfaces stop being solid and start sampling what is behind them. */
  --card-backdrop: blur(18px) saturate(150%);
  --border-w: 1px;
  --ring-w: 3px;
  --motion-scale: 1.35;
  --ease: cubic-bezier(0.32, 0.72, 0.24, 1);
  --brand-gradient: linear-gradient(135deg, #7b3ff2 0%, #4f7dff 48%, #22d3ee 130%);
  --logo-gradient: linear-gradient(145deg, #dcd2ff, #7de8f7);
  --logo-fg: #2a1a5e;
  --sidebar-bg-image: linear-gradient(168deg, #2b1a63 0%, #1b1247 58%, #122046 100%);
}

:root[data-scheme="light"][data-concept="aurora"] {
  --sidebar: #2a1a5e;
  --sidebar-hover: #3a2a74;
  --sidebar-active: #4b3892;
  --accent: #6d3bf5;
  --accent-2: #0ea5c4;
  --bg: #f3f1fd;
  --card: rgba(255, 255, 255, 0.72);
  --border: rgba(109, 59, 245, 0.18);
  --text: #1b1233;
  --muted: #655d86;
  --red-bg: #fde8ec;
  --red-fg: #b3193f;
  --amber-bg: #fdf0d8;
  --amber-fg: #8a5a06;
  --green-bg: #e0f6ef;
  --green-fg: #10795c;
  --blue-bg: #e4ecff;
  --blue-fg: #2f47c4;
  --purple-bg: #efe7ff;
  --purple-fg: #5b21b6;
  --app-gradient: radial-gradient(120% 90% at 8% 0%, #ded3ff 0%, rgba(222, 211, 255, 0) 55%),
    radial-gradient(110% 80% at 100% 12%, #c9f4fb 0%, rgba(201, 244, 251, 0) 52%),
    radial-gradient(120% 100% at 50% 100%, #ffe6f2 0%, rgba(255, 230, 242, 0) 60%);
  --shadow-card: 0 1px 2px rgba(76, 45, 160, 0.06), 0 10px 30px rgba(76, 45, 160, 0.08);
  --shadow-pop: 0 18px 50px rgba(76, 45, 160, 0.18);
  --sidebar-fg: #d6ccf5;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #a99ce0;
  --sidebar-caption: #9a8bd6;
  --nav-fg: #c6b8f0;
  --sidebar-badge-bg: #4b3892;
  --sidebar-badge-fg: #efe8ff;
}

:root[data-scheme="dark"][data-concept="aurora"] {
  --sidebar: #180f3d;
  --sidebar-hover: #241a55;
  --sidebar-active: #332673;
  --accent: #9d7bff;
  --accent-2: #22d3ee;
  --bg: #0a0620;
  --card: rgba(48, 32, 104, 0.52);
  --border: rgba(150, 128, 255, 0.24);
  --text: #f3efff;
  --muted: #a79ed2;
  --red-bg: #3d1630;
  --red-fg: #ff8fa8;
  --amber-bg: #3a2c10;
  --amber-fg: #f0c069;
  --green-bg: #0f3530;
  --green-fg: #4fd6ae;
  --blue-bg: #16205a;
  --blue-fg: #93aaff;
  --purple-bg: #2a1a5c;
  --purple-fg: #c4a8ff;
  --app-gradient: radial-gradient(110% 80% at 10% 0%, #3a1f8a 0%, rgba(58, 31, 138, 0) 55%),
    radial-gradient(100% 80% at 95% 15%, #0d5a75 0%, rgba(13, 90, 117, 0) 55%),
    radial-gradient(130% 110% at 60% 105%, #6b1f6b 0%, rgba(107, 31, 107, 0) 62%);
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 12px 34px rgba(0, 0, 0, 0.38);
  --shadow-pop: 0 20px 60px rgba(0, 0, 0, 0.6);
  --sidebar-fg: #cdc2f2;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #9c8fd0;
  --sidebar-caption: #8b7cc4;
  --nav-fg: #bcaef0;
  --sidebar-badge-bg: #332673;
  --sidebar-badge-fg: #ece4ff;
}

/* Editorial — a serif display face on warm paper, loose leading, hairline rules and no shadow.
   The only concept where nothing is lifted off the page. */

:root[data-concept="editorial"] {
  --radius-xs: 2px;
  --radius-sm: 2px;
  --radius-md: 3px;
  --radius-row: 3px;
  --radius-ctl: 3px;
  --radius-tag: 3px;
  --radius-card: 4px;
  --radius-modal: 4px;
  --radius-lg: 5px;
  --radius-sheet: 8px;
  --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --type-scale: 1.06;
  /* Set, not inherited: a serif face needs the leading to open up or it looks cramped. */
  --lh-tight: 1.15;
  --lh-snug: 1.3;
  --lh-dense: 1.4;
  --lh-dense-2: 1.45;
  --lh-normal: 1.55;
  --lh-base: 1.6;
  --lh-relaxed: 1.65;
  --lh-loose: 1.75;
  --tracking-wide: 0.1px;
  --tracking-caps: 0.16em;
  --density: 1.12;
  --border-w: 1px;
  --ring-w: 2px;
  --motion-scale: 0.7;
  --ease: ease;
  --brand-gradient: linear-gradient(135deg, #211d18 0%, #7a3b24 70%, #a85a2c 140%);
  --logo-gradient: linear-gradient(145deg, #f3e7d3, #c99a63);
  --logo-fg: #211d18;
}

:root[data-scheme="light"][data-concept="editorial"] {
  --sidebar: #f4efe4;
  --sidebar-hover: #eae3d4;
  --sidebar-active: #ded4c0;
  --accent: #9a3f2d;
  --accent-2: #3f5c6b;
  --bg: #f7f4ec;
  --card: #fffdf8;
  --border: #e2dacb;
  --text: #211d18;
  --muted: #6f675c;
  --red-bg: #f6e3de;
  --red-fg: #8f2f1c;
  --amber-bg: #f7ecd0;
  --amber-fg: #7d5a0c;
  --green-bg: #e4eddf;
  --green-fg: #35632f;
  --blue-bg: #e2e8ec;
  --blue-fg: #2f4a5b;
  --purple-bg: #ebe3ee;
  --purple-fg: #5a3a63;
  --shadow-card: none;
  --shadow-pop: 0 6px 18px rgba(33, 29, 24, 0.1);
  --sidebar-fg: #4a443a;
  --sidebar-fg-strong: #1c1813;
  --sidebar-fg-muted: #6d6659;
  --sidebar-caption: #7c7466;
  --nav-fg: #4a443a;
  --sidebar-badge-bg: #e2dac9;
  --sidebar-badge-fg: #3a352c;
  --sidebar-divider: rgba(33, 29, 24, 0.18);
  --sidebar-rule: rgba(33, 29, 24, 0.14);
}

:root[data-scheme="dark"][data-concept="editorial"] {
  --sidebar: #14110d;
  --sidebar-hover: #211d17;
  --sidebar-active: #2f2a22;
  --accent: #d79a72;
  --accent-2: #8fb0c2;
  --bg: #13110e;
  --card: #1b1815;
  --border: #2e2922;
  --text: #f2ece0;
  --muted: #968d7d;
  --red-bg: #3a1f19;
  --red-fg: #e08b74;
  --amber-bg: #372b13;
  --amber-fg: #d8ab5c;
  --green-bg: #1f2f1c;
  --green-fg: #7fb573;
  --blue-bg: #1d262b;
  --blue-fg: #8fb0c2;
  --purple-bg: #2a2130;
  --purple-fg: #bb95c8;
  --shadow-card: none;
  --shadow-pop: 0 8px 22px rgba(0, 0, 0, 0.55);
  --sidebar-fg: #b3a992;
  --sidebar-fg-strong: #f7f2e7;
  --sidebar-fg-muted: #7d7462;
  --sidebar-caption: #6f6757;
  --nav-fg: #b3a992;
  --sidebar-badge-bg: #2f2a22;
  --sidebar-badge-fg: #e8dfcc;
}

/* Dense — built for the screen you actually work in. Monospaced headings, tight rows, square
   corners, hairline rules and motion quick enough to feel like the app is keeping up. */

:root[data-concept="dense"] {
  --radius-xs: 1px;
  --radius-sm: 1px;
  --radius-md: 2px;
  --radius-row: 2px;
  --radius-ctl: 2px;
  --radius-tag: 2px;
  --radius-card: 3px;
  --radius-modal: 3px;
  --radius-lg: 3px;
  --radius-sheet: 3px;
  /* A monospaced display face is the loudest signal a theme can send, and it costs no colour. */
  --font-display: var(--font-mono);
  --type-scale: 0.94;
  --lh-tight: 1;
  --lh-normal: 1.3;
  --lh-base: 1.35;
  --density: 0.82;
  --border-w: 1px;
  --ring-w: 2px;
  --motion-scale: 0.35;
  --ease: ease-out;
  --brand-gradient: linear-gradient(135deg, #0f1720 0%, #0b6bcb 62%, #22d3ee 140%);
  --logo-gradient: linear-gradient(145deg, #dbe9f7, #7fb4e0);
  --logo-fg: #0f1720;
  --shadow-card: none;
}

:root[data-scheme="light"][data-concept="dense"] {
  --sidebar: #101720;
  --sidebar-hover: #1a2430;
  --sidebar-active: #243141;
  --accent: #0b6bcb;
  --accent-2: #c2410c;
  --bg: #f2f4f7;
  --card: #ffffff;
  --border: #dbe0e7;
  --text: #111820;
  --muted: #59626f;
  --red-bg: #fde7e7;
  --red-fg: #b32020;
  --amber-bg: #fdf0d2;
  --amber-fg: #8a5a04;
  --green-bg: #dff5e8;
  --green-fg: #0d7a41;
  --blue-bg: #e2ecf8;
  --blue-fg: #17569c;
  --purple-bg: #ece7f8;
  --purple-fg: #4c2f9e;
  --shadow-pop: 0 6px 16px rgba(17, 24, 32, 0.14);
  --sidebar-fg: #b6c0cd;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #7d8896;
  --sidebar-caption: #6f7a88;
  --nav-fg: #aab4c2;
  --sidebar-badge-bg: #243141;
  --sidebar-badge-fg: #dceaf8;
}

:root[data-scheme="dark"][data-concept="dense"] {
  --sidebar: #0a0e13;
  --sidebar-hover: #141a21;
  --sidebar-active: #1d2530;
  --accent: #4ea3f5;
  --accent-2: #f0883e;
  --bg: #0d1116;
  --card: #14191f;
  --border: #262d36;
  --text: #e4e9ef;
  --muted: #838d99;
  --red-bg: #3a1a1a;
  --red-fg: #f08a8a;
  --amber-bg: #362a12;
  --amber-fg: #e0b45c;
  --green-bg: #12301f;
  --green-fg: #57c98a;
  --blue-bg: #14263c;
  --blue-fg: #7cb6f0;
  --purple-bg: #241d3c;
  --purple-fg: #a992e8;
  --shadow-pop: 0 6px 16px rgba(0, 0, 0, 0.6);
  --sidebar-fg: #9aa5b2;
  --sidebar-fg-strong: #f2f6fa;
  --sidebar-fg-muted: #6b7580;
  --sidebar-caption: #5e6771;
  --nav-fg: #939dab;
  --sidebar-badge-bg: #1d2530;
  --sidebar-badge-fg: #d6e2ef;
}

/* Sage — botanical and calm. A muted green over warm paper, soft but not round, and slow enough
   to feel considered. The only concept built around a natural hue rather than a primary. */

:root[data-concept="sage"] {
  --radius-xs: 6px;
  --radius-sm: 7px;
  --radius-md: 8px;
  --radius-row: 9px;
  --radius-tag: 11px;
  --radius-ctl: 10px;
  --radius-card: 14px;
  --radius-modal: 15px;
  --radius-lg: 20px;
  --radius-sheet: 19px;
  --type-scale: 1.02;
  --tracking-wide: 0.25px;
  --density: 1.04;
  --border-w: 1px;
  --ring-w: 2px;
  --motion-scale: 1.1;
  --ease: cubic-bezier(0.25, 0.6, 0.3, 1);
  --brand-gradient: linear-gradient(135deg, #1e2a22 0%, #4a7c59 60%, #8fb99a 140%);
  --logo-gradient: linear-gradient(145deg, #dceade, #7fae8b);
  --logo-fg: #1e2a22;
}

:root[data-scheme="light"][data-concept="sage"] {
  --sidebar: #1e2a22;
  --sidebar-hover: #29382d;
  --sidebar-active: #3a4d3f;
  --accent: #4a7c59;
  --accent-2: #6b8f71;
  --bg: #f3f6f2;
  --card: #ffffff;
  --border: #dde5dd;
  --text: #1a211b;
  --muted: #5f6b60;
  --red-bg: #fbe9e6;
  --red-fg: #a5372a;
  --amber-bg: #faf0d9;
  --amber-fg: #8a6414;
  --green-bg: #e2f0e6;
  --green-fg: #2e6b41;
  --blue-bg: #e4ecf2;
  --blue-fg: #2f5670;
  --purple-bg: #ebe7f0;
  --purple-fg: #5a4a6b;
  --shadow-card: 0 1px 3px rgba(30, 42, 34, 0.06), 0 8px 24px rgba(30, 42, 34, 0.05);
  --shadow-pop: 0 14px 36px rgba(30, 42, 34, 0.16);
  --sidebar-fg: #c3d4c6;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #93a897;
  --sidebar-caption: #86a08c;
  --nav-fg: #b6c9b9;
  --sidebar-badge-bg: #3a4d3f;
  --sidebar-badge-fg: #e4f0e6;
}

:root[data-scheme="dark"][data-concept="sage"] {
  --sidebar: #0f1712;
  --sidebar-hover: #1a2620;
  --sidebar-active: #27362d;
  --accent: #8fbd9d;
  --accent-2: #b8d4c0;
  --bg: #0e1511;
  --card: #151d18;
  --border: #26322a;
  --text: #edf3ee;
  --muted: #8fa294;
  --red-bg: #3a1f1c;
  --red-fg: #e59a8d;
  --amber-bg: #372d14;
  --amber-fg: #dcb96a;
  --green-bg: #17301f;
  --green-fg: #7cc494;
  --blue-bg: #1a2830;
  --blue-fg: #8fb4c9;
  --purple-bg: #26222f;
  --purple-fg: #b0a0c4;
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.28), 0 10px 28px rgba(0, 0, 0, 0.34);
  --shadow-pop: 0 18px 48px rgba(0, 0, 0, 0.58);
  --sidebar-fg: #a9bdae;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #7d8f80;
  --sidebar-caption: #718675;
  --nav-fg: #9db3a1;
  --sidebar-badge-bg: #27362d;
  --sidebar-badge-fg: #d8e8dc;
}

/* Bordeaux — the deepest and richest of the set. A wine bar rather than a colour scheme: tighter
   corners than Premium, a little more contrast between bar and page, and a warm amber second
   accent so the red never reads as an error colour. */

:root[data-concept="bordeaux"] {
  --radius-xs: 4px;
  --radius-sm: 5px;
  --radius-md: 6px;
  --radius-row: 7px;
  --radius-tag: 9px;
  --radius-ctl: 8px;
  --radius-card: 10px;
  --radius-modal: 12px;
  --radius-lg: 16px;
  --radius-sheet: 16px;
  --type-scale: 1.03;
  --tracking-wide: 0.2px;
  --tracking-caps: 1.2px;
  --density: 1.03;
  --border-w: 1px;
  --ring-w: 2px;
  --motion-scale: 1.05;
  --ease: cubic-bezier(0.3, 0.55, 0.35, 1);
  --brand-gradient: linear-gradient(135deg, #2a1119 0%, #8e2f4a 60%, #c98a9a 140%);
  --logo-gradient: linear-gradient(145deg, #f0d9dd, #c4778c);
  --logo-fg: #2a1119;
}

:root[data-scheme="light"][data-concept="bordeaux"] {
  --sidebar: #2a1119;
  --sidebar-hover: #3a1a24;
  --sidebar-active: #4d2431;
  --accent: #8e2f4a;
  --accent-2: #a8734a;
  --bg: #faf5f4;
  --card: #ffffff;
  --border: #ecdcda;
  --text: #241417;
  --muted: #7a5f62;
  --red-bg: #fbe8e6;
  --red-fg: #a32b28;
  --amber-bg: #f9eed8;
  --amber-fg: #8a5e12;
  --green-bg: #e6efe4;
  --green-fg: #3a6b3f;
  --blue-bg: #e6eaf1;
  --blue-fg: #33455f;
  --purple-bg: #efe4ec;
  --purple-fg: #6b3a5c;
  --shadow-card: 0 1px 3px rgba(42, 17, 25, 0.06), 0 10px 28px rgba(42, 17, 25, 0.06);
  --shadow-pop: 0 16px 42px rgba(42, 17, 25, 0.18);
  --sidebar-fg: #e0c2c9;
  --sidebar-fg-strong: #fff5f7;
  --sidebar-fg-muted: #b08a92;
  --sidebar-caption: #a47c85;
  --nav-fg: #d4b0b8;
  --sidebar-badge-bg: #4d2431;
  --sidebar-badge-fg: #f7e2e6;
}

:root[data-scheme="dark"][data-concept="bordeaux"] {
  --sidebar: #180a0f;
  --sidebar-hover: #26121a;
  --sidebar-active: #361a25;
  --accent: #e08a9e;
  --accent-2: #d9a878;
  --bg: #14090c;
  --card: #1c0f13;
  --border: #2e1a20;
  --text: #f6ecee;
  --muted: #a3868a;
  --red-bg: #3a1717;
  --red-fg: #e8908a;
  --amber-bg: #372814;
  --amber-fg: #dcb06a;
  --green-bg: #1c2f1e;
  --green-fg: #85bf88;
  --blue-bg: #1b1f2c;
  --blue-fg: #93a6c9;
  --purple-bg: #2b1a2a;
  --purple-fg: #c79ac0;
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 10px 30px rgba(0, 0, 0, 0.4);
  --shadow-pop: 0 20px 52px rgba(0, 0, 0, 0.62);
  --sidebar-fg: #c9aab2;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #9a747c;
  --sidebar-caption: #8a6670;
  --nav-fg: #bd9ba4;
  --sidebar-badge-bg: #361a25;
  --sidebar-badge-fg: #f4dbe1;
}

/* Rose — the softest of the set and the one with the most air. A dusty rose over warm white with a
   lavender second accent, generous corners and the loosest leading outside Editorial. */

:root[data-concept="rose"] {
  --radius-xs: 8px;
  --radius-sm: 9px;
  --radius-md: 10px;
  --radius-row: 11px;
  --radius-tag: 13px;
  --radius-ctl: 12px;
  --radius-card: 16px;
  --radius-modal: 18px;
  --radius-lg: 22px;
  --radius-sheet: 20px;
  --type-scale: 1.05;
  --lh-normal: 1.5;
  --lh-base: 1.52;
  --tracking-wide: 0.3px;
  --density: 1.07;
  --border-w: 1px;
  --ring-w: 2px;
  --motion-scale: 1.2;
  --ease: cubic-bezier(0.3, 0.7, 0.4, 1);
  --brand-gradient: linear-gradient(135deg, #2e2430 0%, #b5677f 60%, #d9a8b6 140%);
  --logo-gradient: linear-gradient(145deg, #f7dfe6, #d490a4);
  --logo-fg: #2e2430;
}

:root[data-scheme="light"][data-concept="rose"] {
  --sidebar: #2e2430;
  --sidebar-hover: #3d3141;
  --sidebar-active: #4f4054;
  --accent: #b5677f;
  --accent-2: #7a6a9c;
  --bg: #fbf6f7;
  --card: #ffffff;
  --border: #eddfe3;
  --text: #261a1e;
  --muted: #7d666c;
  --red-bg: #fceaeb;
  --red-fg: #ab3a48;
  --amber-bg: #f9efdc;
  --amber-fg: #8a6118;
  --green-bg: #e7f0e6;
  --green-fg: #3d6b43;
  --blue-bg: #e7ecf3;
  --blue-fg: #384a66;
  --purple-bg: #f0e6f0;
  --purple-fg: #6e4470;
  --shadow-card: 0 1px 3px rgba(46, 36, 48, 0.05), 0 10px 30px rgba(46, 36, 48, 0.06);
  --shadow-pop: 0 16px 44px rgba(46, 36, 48, 0.16);
  --sidebar-fg: #dcc8d0;
  --sidebar-fg-strong: #fdf4f6;
  --sidebar-fg-muted: #ab93a0;
  --sidebar-caption: #9f8794;
  --nav-fg: #cfb8c2;
  --sidebar-badge-bg: #4f4054;
  --sidebar-badge-fg: #f6e4e9;
}

:root[data-scheme="dark"][data-concept="rose"] {
  --sidebar: #1b1419;
  --sidebar-hover: #271d24;
  --sidebar-active: #362a32;
  --accent: #e0a3b4;
  --accent-2: #b0a2d8;
  --bg: #140f12;
  --card: #1c151a;
  --border: #2c2228;
  --text: #f7eef1;
  --muted: #a68b93;
  --red-bg: #391b22;
  --red-fg: #ea98a8;
  --amber-bg: #362915;
  --amber-fg: #ddb471;
  --green-bg: #1c2f20;
  --green-fg: #8ac390;
  --blue-bg: #1d2130;
  --blue-fg: #9fabd6;
  --purple-bg: #2c1e2f;
  --purple-fg: #cfa6d4;
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.26), 0 10px 30px rgba(0, 0, 0, 0.36);
  --shadow-pop: 0 18px 50px rgba(0, 0, 0, 0.6);
  --sidebar-fg: #c6b2bb;
  --sidebar-fg-strong: #ffffff;
  --sidebar-fg-muted: #94808a;
  --sidebar-caption: #87737e;
  --nav-fg: #b89faa;
  --sidebar-badge-bg: #362a32;
  --sidebar-badge-fg: #f2dfe5;
}

/* ---------- Theme picker ---------- */
.theme-picker-hint {
  margin: 0 0 12px;
  color: var(--muted);
  font-size: var(--fs-body-s);
  line-height: var(--lh-relaxed);
}

.theme-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}

.theme-option {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 10px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-ctl);
  background: var(--card);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}

.theme-option:hover {
  border-color: var(--accent);
}

.theme-option.active {
  border-color: var(--accent);
  box-shadow: var(--ring);
}

.theme-option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Three blocks standing in for sidebar, accent and surface, so the choice reads at a glance. */
.theme-swatch {
  display: flex;
  height: 40px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: var(--border-w) solid var(--border);
}

.theme-swatch i {
  display: block;
  height: 100%;
}

.theme-swatch i:nth-child(1) {
  width: 28%;
}

.theme-swatch i:nth-child(2) {
  width: 34%;
}

.theme-swatch i:nth-child(3) {
  flex: 1;
}

.theme-option-label {
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
}

.theme-option-hint {
  color: var(--muted);
  font-size: var(--fs-meta);
  line-height: var(--lh-dense-2);
}

/* ---------- Inline help (the "i" beside a confusing field) ----------

   A tooltip that only answers on hover is invisible on a phone, which is where this app is most
   often opened. So the two cases are separated on purpose: `:hover` is applied only inside
   `@media (hover: hover)`, and tapping the dot toggles `.is-open` everywhere. Without that split,
   a tap on a touch device leaves the element in a stuck :hover state and the bubble never closes.

   Everything is a token, so the bubble reads correctly in all ten concepts without a per-theme
   rule. The glyph is a CSS `::before` rather than an icon: an inline <i> would need the Lucide
   script to have run, and this must never be the reason a tooltip fails to appear. */
.info-tip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  /* --tip-shift is set by positionTip() to keep the bubble on screen when its dot sits near an
     edge. The default has to live HERE, on the element script.js writes to — declaring it inside
     .info-tip__bubble would shadow the inherited inline value and the nudge would silently do
     nothing, which is exactly the bug this comment is here to stop. */
  --tip-shift: 0px;
  /* Negative margins let the dot sit inside a label row without opening the spacing up. */
  margin: 0 -2px;
  padding: 0;
  border: var(--border-w) solid transparent;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  cursor: help;
  vertical-align: middle;
  /* Without this the bubble is painted behind the card it belongs to. */
  z-index: 30;
}

.info-tip::before {
  content: "i";
  font-family: var(--font-display);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  font-style: italic;
  line-height: 1;
  /* A real circle rather than a filled dot, so it reads as "information" and not as a control. */
  width: 15px;
  height: 15px;
  display: grid;
  place-items: center;
  border: var(--border-w) solid currentColor;
  border-radius: 50%;
}

.info-tip:hover,
.info-tip:focus-visible,
.info-tip.is-open {
  color: var(--accent);
  background: var(--blue-bg);
}

.info-tip:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}

.info-tip__bubble {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(calc(-50% + var(--tip-shift))) translateY(4px);
  width: max-content;
  /* A cap, so a long note wraps instead of running off the side of a 390px phone. */
  max-width: min(250px, calc(100vw - 32px));
  padding: 8px 10px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-body-s);
  font-style: normal;
  font-weight: var(--fw-normal);
  line-height: var(--lh-normal);
  text-align: left;
  white-space: normal;
  box-shadow: var(--shadow-pop);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: 31;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease), visibility var(--dur-2);
}

/* The tail. A rotated square rather than a CSS triangle, so it picks up the border and the
   rounded corners the bubble already has. */
.info-tip__bubble::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  /* The tail has to travel with the bubble. Left at 50% it would point at empty screen the moment
     the bubble is nudged inwards to stay on a narrow display. */
  margin: -5px 0 0 -4.5px;
  margin-left: calc(-4.5px + var(--tip-shift));
  width: 9px;
  height: 9px;
  border-right: var(--border-w) solid var(--border);
  border-bottom: var(--border-w) solid var(--border);
  background: var(--card);
  transform: rotate(45deg);
}

/* Opened by tap or by script: works on every device, hover or not. */
.info-tip.is-open .info-tip__bubble,
.info-tip:focus-visible .info-tip__bubble {
  opacity: 1;
  visibility: visible;
  transform: translateX(calc(-50% + var(--tip-shift))) translateY(0);
}

/* Keyboard and pointer devices only. Scoped to the media query on purpose — see the note above. */
@media (hover: hover) and (pointer: fine) {
  .info-tip:hover .info-tip__bubble {
    opacity: 1;
    visibility: visible;
    transform: translateX(calc(-50% + var(--tip-shift))) translateY(0);
  }
}

/* Clicked shut while the pointer is still resting on the dot. Without this, tapping the icon to
   close it does nothing on a desktop: `:hover` is still true, so the bubble stays visible and the
   control feels broken. Cleared as soon as the pointer leaves. */
.info-tip.is-dismissed .info-tip__bubble {
  opacity: 0;
  visibility: hidden;
}

/* Near the top of the screen there is no room above, so the bubble opens downward instead. The
   class is set by positionTip() rather than by guessing in CSS, because the dot moves as the page
   scrolls. */
.info-tip.below .info-tip__bubble {
  bottom: auto;
  top: calc(100% + 9px);
  transform: translateX(calc(-50% + var(--tip-shift))) translateY(-4px);
}

.info-tip.below .info-tip__bubble::after {
  top: auto;
  bottom: 100%;
  margin: -5px 0 0 -4.5px;
  border: 0;
  border-left: var(--border-w) solid var(--border);
  border-top: var(--border-w) solid var(--border);
}

.info-tip.below.is-open .info-tip__bubble,
.info-tip.below:focus-visible .info-tip__bubble {
  transform: translateX(calc(-50% + var(--tip-shift))) translateY(0);
}

@media (hover: hover) and (pointer: fine) {
  .info-tip.below:hover .info-tip__bubble {
    transform: translateX(calc(-50% + var(--tip-shift))) translateY(0);
  }
}

/* On a phone the bubble is a tap target's worth of reading, not a hover: give it room to breathe
   without letting it outgrow the sheet. */
@media (max-width: 480px) {
  .info-tip__bubble {
    max-width: min(220px, calc(100vw - 40px));
    font-size: var(--fs-meta);
  }
}

.theme-scheme-select {
  padding: 7px 10px;
  border-radius: var(--radius-ctl);
  border: var(--border-w) solid var(--border);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background-color: var(--bg);
  background-image: var(--app-gradient);
  color: var(--text);
  overflow-x: hidden;
  overscroll-behavior-x: none;
}

svg.lucide {
  display: block;
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

input,
select,
textarea {
  min-width: 0;
  max-width: 100%;
}

.app {
  display: flex;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}

/* Display type: brand, page title, card heading, dialog title and the big stat number. This is
   where a concept's typeface reads loudest, and it is a token — so Default keeps its original sans
   while Editorial or Aurora can put a serif or a geometric face here alone. */
.brand-name,
h1.page-title,
.card-head h3,
.modal h3,
.stat-num {
  font-family: var(--font-display);
}

/* Sidebar */
.sidebar {
  width: 230px;
  background-color: var(--sidebar);
  background-image: var(--sidebar-bg-image);
  color: var(--sidebar-fg);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  padding: 18px 12px;
  overflow-y: auto;
  transition: width var(--dur-4) var(--ease);
}

.sidebar.collapsed {
  width: 76px;
  padding-left: 10px;
  padding-right: 10px;
}

.brand {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px 18px;
}

/* Soft ambient glow behind the mark: depth without adding chrome. */
.brand::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 2px;
  width: 58px;
  height: 28px;
  border-radius: var(--radius-modal);
  background: radial-gradient(
    ellipse at 32% 50%,
    rgba(99, 102, 241, 0.42),
    rgba(124, 58, 237, 0.18) 58%,
    transparent 78%
  );
  filter: blur(5px);
  opacity: 0.85;
  pointer-events: none;
}

.brand-mark {
  width: 50px;
  height: 20px;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 6px rgba(79, 70, 229, 0.32));
  transition: transform var(--dur-4) var(--ease), filter var(--dur-4) var(--ease);
}

.brand:hover .brand-mark {
  transform: translateY(-1px);
  filter: drop-shadow(0 4px 10px rgba(124, 58, 237, 0.42));
}

.brand-name {
  font-weight: var(--fw-bold);
  font-size: var(--fs-title);
  letter-spacing: var(--tracking-wide);
  color: var(--sidebar-fg-strong);
  text-shadow: 0 1px 2px rgba(8, 12, 32, 0.35);
}

.sidebar-collapse {
  width: 24px;
  height: 24px;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border: var(--border-w) solid var(--sidebar-divider);
  border-radius: 999px;
  background: transparent;
  color: var(--sidebar-fg);
  cursor: pointer;
  transition: background var(--dur-3) var(--ease), color var(--dur-3) var(--ease), border-color var(--dur-3) var(--ease);
}

.sidebar-collapse svg {
  width: 15px;
  height: 15px;
  stroke-width: 2;
}

.sidebar-collapse:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* Collapsed rail: the mark stays centred and the same button expands it again. */
.sidebar.collapsed .brand {
  flex-wrap: wrap;
  justify-content: center;
  row-gap: 8px;
  padding-left: 0;
  padding-right: 0;
}

.sidebar.collapsed .brand::before {
  left: 50%;
  top: 2px;
  width: 50px;
  transform: translateX(-50%);
}

.sidebar.collapsed .brand-mark {
  width: 44px;
}

.sidebar.collapsed .brand-name {
  display: none;
}

.sidebar.collapsed .sidebar-collapse {
  margin-left: 0;
}

@media (prefers-reduced-motion: reduce) {

  .brand-mark,
  .sidebar-collapse {
    transition: none;
  }

  .brand:hover .brand-mark {
    transform: none;
  }
}

.nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
}

.sidebar-caption {
  padding: 0 10px 8px;
  color: var(--sidebar-caption);
  font-size: var(--fs-caption);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--pad-ctl-y) var(--pad-nav-x);
  cursor: pointer;
  font-size: var(--fs-lead);
  color: var(--nav-fg);
  justify-content: space-between;
  position: relative;
}

.nav-item .left {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Bare glyph only — no square/rounded container behind the icon. */
.nav-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: auto;
  height: auto;
  padding: 0;
  margin: 0;
  background: none !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  outline: none;
  opacity: 0.9;
}

.nav-icon svg {
  width: 18px;
  height: 18px;
  stroke-width: 1.8;
}

/* Collapsed rail: the accent bar centres under the icon. */
.sidebar.collapsed .nav-item::before {
  left: 50%;
  transform: translate(-50%, -50%);
}

.sidebar.collapsed .nav-item.active::before {
  height: 20px;
}

.sidebar.collapsed .nav-item {
  justify-content: center;
  padding-left: 9px;
  padding-right: 9px;
}

.sidebar.collapsed .nav-item .left {
  gap: 0;
}

.sidebar.collapsed .nav-label,
.sidebar.collapsed .nav-badge,
.sidebar.collapsed .capture-label {
  display: none;
}

/* No filled box behind a nav row: hover brightens the text and the active row
   is marked with a slim accent bar on the left edge, as in the reference. */
.nav-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 3px;
  height: 0;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
  transform: translateY(-50%);
  transition: height var(--dur-3) var(--ease);
}

.nav-item:hover .nav-label,
.nav-item:hover .nav-icon {
  color: var(--sidebar-fg-strong);
  opacity: 1;
}

.nav-item.active {
  color: var(--sidebar-fg-strong);
  font-weight: var(--fw-semibold);
}

.nav-item.active::before {
  height: 22px;
}

.nav-badge {
  background: var(--sidebar-badge-bg);
  color: var(--sidebar-badge-fg);
  font-size: var(--fs-label);
  padding: 1px 7px;
  border-radius: var(--radius-tag);
}

.nav-item.active .nav-badge {
  background: var(--accent);
}

.capture-btn {
  width: 100%;
  margin-top: 14px;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--radius-none);
  background: transparent;
  color: var(--sidebar-fg-strong);
  box-shadow: var(--shadow-none);
  font-family: inherit;
  font-size: var(--fs-lead);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  text-align: left;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
  transition: opacity var(--dur-3) var(--ease), transform var(--dur-3) var(--ease);
}

.capture-btn:hover {
  background: transparent;
  color: var(--sidebar-fg-strong);
  opacity: 0.82;
  transform: translateY(-1px);
}

.capture-btn:active {
  background: transparent;
  opacity: 1;
  transform: translateY(0);
}

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

/* The Capture control uses the same infinity mark and gradient as the brand. */
.capture-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  line-height: var(--lh-tight);
}

.capture-mark {
  width: 50px;
  height: 20px;
  overflow: visible;
  filter: drop-shadow(0 2px 6px rgba(79, 70, 229, 0.32));
}

.capture-label {
  color: var(--sidebar-fg-strong);
}

.sidebar-profile {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  margin-top: 12px;
  padding: 10px 8px 2px;
  border: 0;
  border-top: var(--border-w) solid var(--sidebar-rule);
  background: transparent;
  color: var(--sidebar-fg-strong);
  text-align: left;
  cursor: pointer;
}

.sidebar-profile-avatar {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  border-radius: var(--radius-row);
  background: var(--logo-gradient);
  color: var(--logo-fg);
  font-size: var(--fs-small);
  font-weight: var(--fw-heavy);
}

.sidebar-profile-copy {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 2px;
}

.sidebar-profile-copy strong,
.sidebar-profile-copy small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-profile-copy strong { font-size: var(--fs-small); }
.sidebar-profile-copy small { color: var(--sidebar-fg-muted); font-size: var(--fs-caption); }

.sidebar.collapsed .sidebar-caption,
.sidebar.collapsed .sidebar-profile-copy {
  display: none;
}

.sidebar.collapsed .sidebar-profile {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

.sidebar.collapsed .capture-btn {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

.sidebar.collapsed .capture-mark {
  width: 44px;
}

/* Main */
.main {
  width: 100%;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.topbar {
  position: relative;
  z-index: 20;
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 14px;
  min-width: 0;
  padding: 14px 24px;
  border-bottom: var(--border-w) solid var(--border);
  background: var(--card);
}

.topbar-menu-wrap {
  position: relative;
  flex-shrink: 0;
}

.topbar-actions {
  display: contents;
}

/* Topbar Capture uses the same brand mark, with no surrounding button box. */
.mobile-capture-btn {
  display: none;
  width: 50px;
  height: 20px;
  min-width: 50px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-none);
  background: transparent;
  box-shadow: var(--shadow-none);
  color: inherit;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  appearance: none;
  -webkit-appearance: none;
  transition: opacity var(--dur-3) var(--ease), transform var(--dur-3) var(--ease);
}

.mobile-capture-btn:hover {
  background: transparent;
  opacity: 0.82;
  transform: translateY(-1px);
}

.mobile-capture-btn:active {
  background: transparent;
  opacity: 1;
  transform: translateY(0);
}

.mobile-capture-btn:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}

.mobile-capture-btn .capture-mark {
  display: block;
  width: 50px;
  height: 20px;
  overflow: visible;
  filter: drop-shadow(0 2px 6px rgba(79, 70, 229, 0.32));
}

/* The Capture module logo in the top bar uses the same premium travelling-dash loader as the
   in-app brand mark: a light, seamless sweep that says "brand working" from a small touch target
   without the heaviness of the full screen overlay loop. */
.mobile-capture-btn .capture-mark .loader-run {
  animation: capture-mark-loader-run 1.4s linear infinite;
}

@keyframes capture-mark-loader-run {
  0% {
    stroke-dasharray: 0.06 0.94;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 0.45 0.55;
    stroke-dashoffset: -0.35;
  }
  100% {
    stroke-dasharray: 0.06 0.94;
    stroke-dashoffset: -1;
  }
}

.sidebar-backdrop {
  display: none;
}

/* The wrapper is the positioning context so the dropdown aligns to the field, not the topbar. */
.search-wrap {
  position: relative;
  flex: 1 1 220px;
  min-width: 0;
}

.search-box {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-ctl);
  padding: var(--pad-ctl-y) var(--pad-input-x);
  color: var(--muted);
  cursor: text;
}

.search-box:focus-within {
  border-color: var(--accent-2);
  box-shadow: var(--ring-lg);
}

.search-box input {
  border: none;
  background: transparent;
  outline: none;
  flex: 1;
  /* 16px stops iOS Safari zooming the viewport when the field gains focus. */
  font-size: var(--fs-input);
  min-width: 0;
  color: var(--text);
}

.search-box > svg,
.topbar .icon-btn > svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  stroke-width: 1.8;
}

/* Bare icon buttons: no bordered square, just the glyph on a soft hover. */
.icon-btn {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-ctl);
  border: 0;
  background: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--muted);
  font-size: var(--fs-input);
  position: relative;
  flex-shrink: 0;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.icon-btn:hover {
  background: var(--bg);
  color: var(--text);
}

/* The quick-jump button is useful on wider screens and tablets. Phones keep their topbar focused on
   navigation, search, and account controls; the launcher itself remains available by keyboard. */
.palette-btn {
  display: flex;
}

@media (max-width: 900px) {
  /* Two things compete for the search field here. The palette button took 42px, dropping the box to
     106px against the audit's 120px floor; the theme toggle was the control removed to compensate,
     wrongly — `prefers-color-scheme` only picks the initial scheme, so the header toggle is the only
     one-tap route back from a deliberate dark choice. Both are back, with the width reclaimed below
     from the topbar gap, the side padding and the capture mark: 128px at 390px, over the floor. */
  .search-box .kbd {
    display: none;
  }
}

@media (max-width: 600px) {
  .palette-btn {
    display: none;
  }
}

/* The unread dot needs its own contrast now the button has no white plate. */
.theme-dot {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: var(--ring-inset);
  display: none;
}

.avatar {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: 0 0 auto;
  font-family: inherit;
  font-weight: var(--fw-bold);
  font-size: var(--fs-body);
}

.content {
  width: 100%;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  padding: 24px;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.view {
  display: none;
  width: 100%;
  min-width: 0;
}

/* ---------- Motion ----------
   The app had 20 transitions in nearly 6,000 lines of CSS and one @keyframes, and the place it showed
   most was navigation: `.view` is `display: none` and `.view.active` is `display: block`, so every
   tap on a nav entry swapped one page for another instantly. Nothing about the layout was wrong —
   which is exactly why it never showed up in a responsive audit. A hard cut is a feel problem, and a
   feel problem cannot be measured by asking whether anything overflows.

   Everything here is on the existing tokens, so it inherits each concept's --motion-scale and
   --ease for free: the springy "casual" theme springs, "editorial" barely moves, and the calm theme
   stays calm. That is the reason to add motion as tokens rather than as durations.

   Two deliberate limits, because the failure modes are worse than the flatness being fixed:

   Nothing animates on load. A page that fades up while a person is reading it, once per visit, is
   decoration that costs them time; so the arrival animation only runs on a view the person actually
   navigated to, and only after the first paint.

   Nothing animates more than 8px or 200ms. A long travel is read as lag, and a long travel on a list
   of forty rows is read as forty separate waits. */

/* A view arriving. `animation` rather than `transition`, because display:none → block cannot
   transition: there is no "before" state to interpolate from.

   Played only for `.view.active.entering`, and `entering` is added by switchView when the person
   actually navigated — not by boot and not by a re-render. Tying it to `.active` alone would restart
   the fade every time a list re-rendered, so ticking one checkbox would make the whole page pulse. */
@keyframes view-arrive {
  from {
    opacity: 0;
    transform: translate3d(0, 6px, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

.view.active {
  display: block;
}

.view.active.entering {
  animation: view-arrive var(--dur-4) var(--ease) both;
}

/* Rows and controls acknowledge the finger. There were eight :active rules in the file and none on
   a row, so a tap on a task produced no evidence at all until the page came back — which reads as a
   dropped tap rather than a slow one. This is the cheapest possible confirmation: a hair of
   compression and the existing surface token. transform only, so it never triggers layout, and the
   row's own text is left alone because scaling text makes small type look blurry mid-press. */
.task-row:not(.swipeable) {
  transition: background var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.task-row:not(.swipeable):active {
  transform: scale(0.994);
  background: var(--bg);
}

/* Swipeable rows are excluded deliberately. Their transform is owned by the drag in progress, and a
   :active rule competing for the same property mid-gesture is how a row ends up stuck a few pixels
   sideways after it has been released. */

/* Checkboxes are the single most-tapped control in the app, and the smallest, so they get the same
   acknowledgement at the same cost. */
.checkbox {
  transition: transform var(--dur-1) var(--ease), background var(--dur-1) var(--ease),
    border-color var(--dur-1) var(--ease);
}

.checkbox:active {
  transform: scale(0.9);
}

/* Anything focusable gets a visible ring without the layout moving. outline rather than box-shadow,
   because outline does not change the element's size and a focus ring that pushes a row sideways
   makes the list reflow while somebody is arrowing through it. */
:focus-visible {
  outline: var(--ring-w) solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-ctl);
}

/* ---------- Motion, when the reader has asked for none ----------
   The old rule turned off two selectors by name, which meant every animation added after it was
   opted in by forgetting, not out. This zeroes the tokens themselves, so the whole app goes still and
   stays still as it grows — the override cannot fall behind the thing it is protecting.

   Specificity matters here and is easy to get wrong. Each concept sets --motion-scale on
   `:root[data-concept="…"]`, which is (0,2,0). A bare `:root` override is (0,1,0) and loses to it,
   so the reduced-motion rule would silently do nothing for every concept except the default. The
   selector below matches the concepts as well as the root, and its !important is deliberate: it is
   the one place in the stylesheet where a rule is allowed to insist, because losing this one is
   invisible and the consequence is a motion-sensitive reader being moved whether they asked to be or
   not. */
/* ---------- Confirm dialog ----------
   Narrower than a form dialog and without the close X crowding the title, because there is only ever
   one question here and the buttons are the content. The danger tint comes from the tokens rather
   than a fixed red, so it follows the theme instead of fighting it. */
.modal-confirm {
  width: min(400px, 100%);
}

.confirm-body {
  margin: 0;
  font-size: var(--fs-ui);
  line-height: 1.55;
  color: var(--muted);
}

/* The consequence is the part people skip, so it is allowed more weight than usual and is never
   truncated — a half-read consequence is the whole failure this dialog exists to prevent. */
.modal-confirm .confirm-body {
  color: var(--fg);
}

/* ---------- Empty views ----------
   The old treatment was four lines of CSS: muted grey, body size, centred, some padding. Applied to
   about thirty places. A line of grey text in the middle of a white page is indistinguishable from a
   render that failed, and an empty view is the very first thing a new person sees on every screen —
   so this is the app's opening line, spent on the least deliberate state in the product.

   Centred with generous air, so the eye lands on the sentence rather than hunting for it. The mark
   sits above at low opacity: it identifies the state as *Everything's* and not an error, without
   competing with the sentence for attention. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  /* Air rather than a border or a card. A bordered box says "this is a panel"; an empty list is not a
     panel, it is the absence of one, and drawing a frame around nothing invents something. */
  padding: 44px 20px 52px;
  max-width: 380px;
  margin: 0 auto;
}

.empty-mark {
  width: 54px;
  height: 32px;
  stroke: var(--accent);
  opacity: 0.28;
  flex: none;
  margin-bottom: 2px;
}

.empty-state-title {
  margin: 0;
  font-size: var(--fs-ui);
  font-weight: var(--fw-bold);
  color: var(--fg);
  text-wrap: balance;
}

.empty-state-body {
  margin: 0;
  font-size: var(--fs-small, 13px);
  line-height: 1.5;
  color: var(--muted);
  text-wrap: pretty;
}

/* The one next step. Given room to be found, and given the primary ring — it is the only thing on
   the screen that does anything, and dressing it as secondary would be telling the person to ignore
   the only action available. */
.empty-action {
  margin-top: 6px;
  /* A whole-row target on a phone. A small centred button is the easiest thing on the screen to miss
     with a thumb. */
  min-height: 40px;
  padding-inline: 18px;
}

/* "Nothing is stuck. That is a good week." — a good outcome is not an absence and should not be
   dressed as one. Same shape, so the screen does not jump, but the mark takes the success colour,
   because nothing is wrong here. */
.empty-state--good .empty-mark {
  stroke: var(--green-fg, var(--accent));
  opacity: 0.45;
}

/* Inside a card or a dropdown, where 44px of air would be absurd. */
.card .empty-state,
.search-dropdown .empty-state {
  padding: 20px 8px;
}

.card .empty-mark,
.search-dropdown .empty-mark {
  width: 40px;
  height: 24px;
}

/* ---------- The boot splash ----------

   The gap between the browser's cached icon and the running app. Before this element the platform is
   drawing a static icon it saved earlier; after it, the app is up. Putting the brand mark in that gap
   — on the same path, with the same travelling dash as the sidebar logo and the in-app loader — means
   the brand is the one thing on screen through the whole load, instead of a static box that is
   replaced a moment later by a bare mark of a different shape.

   Shown by a class rather than by default, so the inline `display:none` in the markup is what
   actually governs the first paint. A splash that appears only once a stylesheet has loaded and then
   replaced again is worse than no splash at all. */

.boot-splash {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  /* Column, so the name sits under the mark rather than beside it. The mark and the word together
     are the identity; stacked, they are a lockup, and a lockup read left-to-right puts the word
     first, which is not what the app is showing the person yet. */
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* The app's own background tokens, so the splash is the same colour the app is about to become.
     A white flash on a dark theme is its own brand failure. */
  background: var(--bg, #f5f6fb);
  /* One knob for the lockup, because the mark and the word are sized as a pair. min() against the
     viewport rather than a fixed px: the splash is the first thing seen on the widest range of
     screens in the app, and a fixed size is either a speck on a phone or a poster on a tablet. */
  --splash-mark-size: min(52vw, 210px);
  /* The overlay is not a control. Its status label is announced, while keyboard focus stays where
     the person left it. */
  pointer-events: none;
}

.boot-splash[aria-hidden="false"] {
  pointer-events: auto;
  cursor: wait;
}

/* 84px was a mark with about 50px of actual ink on it, because the curve fills only 24 of the
   viewBox's 40 units — barely bigger than the sidebar logo, on the one screen a person stares at
   while waiting. The box has to be large for the logo to read as large. */
.boot-splash-mark {
  width: var(--splash-mark-size);
  height: calc(var(--splash-mark-size) * 0.6);
  overflow: visible;
}

/* The word under the mark. Scaled off the mark rather than tracking the theme's type scale: the
   splash is the one screen that has to look right before any of the app's own styles have settled,
   and a token that has not loaded yet would leave this invisible. The fallback in each clamp() arm
   is what makes it safe either way. */
.boot-splash-name {
  /* Tracked off the mark rather than fixed, so the lockup holds together from a small phone to a
     tablet: a 15px word under a 210px mark reads as a caption rather than as the name of the app. */
  margin-top: calc(var(--splash-mark-size) * 0.075);
  font-size: clamp(15px, 4.4vw, 21px);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text, #1c1f2b);
}

/* The track behind the travelling segment, matching the in-app loader so the two are the same mark
   doing the same thing. Without it a dash appearing out of nowhere reads as a glitch rather than as
   something moving along a known path. */
.boot-splash .loader-track {
  stroke: var(--accent);
  opacity: 0.2;
}

/* The gradient is the splash's own (defined in its svg in index.html), not the sidebar's #logoGrad:
   the sidebar is parsed after the splash is already showing, so borrowing its id left this stroke
   pointing at nothing — which renders as no stroke at all, and a loader you cannot see is a splash
   that looks hung. */
.boot-splash .loader-run {
  stroke: url(#bootSplashGrad);
  stroke-dasharray: 0.18 0.82;
  animation: brand-loader-run 0.9s linear infinite;
}

/* ---------- The brand loader ----------
   There was no loading indicator anywhere in the app. The one wait a person actually sees — the
   Ask overlay saying "Thinking…" — was a line of text with nothing moving on it, which is the
   hardest kind of wait: it is indistinguishable from a hung app, because text never proves that
   anything is still happening.

   This is the sidebar mark doing the waiting, so the app is recognisable even in a screenshot taken
   at the moment it is thinking. It is the brand rather than a generic ring because a ring says
   "loading" and nothing more, while this says "Everything is working on it".

   The stroke is normalised with pathLength="1", so the dash maths below is exact and does not
   depend on measuring this particular curve. That is why the numbers read as fractions: .16 of the
   way round is .16 of the way round, whatever the real path length turns out to be. */

.brand-loader {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  vertical-align: middle;
  color: var(--muted);
}

.brand-loader svg {
  width: var(--loader-size, 22px);
  height: calc(var(--loader-size, 22px) * 0.6);
  display: block;
  overflow: visible;
  flex: none;
}

/* The track: the whole loop, held quietly. Without it the travelling segment reads as a stray dash
   appearing out of nowhere rather than as something moving along a known path. */
.brand-loader .loader-track {
  stroke: var(--accent);
  opacity: 0.2;
}

/* The segment that travels. Linear and looping, so the period is constant — an ease on the offset
   would make it hesitate at the join and draw the eye to the seam. The "splash" is the swell, not the
   speed: the visible segment grows as it moves and retracts as it comes home, so the mark reads as
   being poured along the path rather than a rigid dash spinning round it. Both dasharray and offset
   are fractions of pathLength=1, so the swell is exact at any size and loops seamlessly (0% and 100%
   match, and a full -1 offset is a full loop). */
.brand-loader .loader-run {
  stroke: url(#logoGrad);
  stroke-dasharray: 0.08 0.92;
  animation: brand-loader-run 1.4s linear infinite;
}

@keyframes brand-loader-run {
  0% {
    stroke-dasharray: 0.08 0.92;
    stroke-dashoffset: 0;
  }
  50% {
    /* Widest, a little past halfway round, so the swell and the travel peak together. */
    stroke-dasharray: 0.6 0.4;
    stroke-dashoffset: -0.35;
  }
  100% {
    /* One full path length, because pathLength is 1 — back to the seed dash, ready to pour again. */
    stroke-dasharray: 0.08 0.92;
    stroke-dashoffset: -1;
  }
}

.brand-loader-label {
  font-size: var(--fs-caption, 12px);
  line-height: 1;
  white-space: nowrap;
}

/* Sizes. Small sits inline beside a status line; large replaces the content of a panel that has
   nothing else in it yet. */
.brand-loader--sm {
  --loader-size: 18px;
}

/* Medium. The Ask overlay is a panel holding nothing but a wait, so the default 22px read as a
   smudge beside the words and 56px would take the room over from the thing being waited for. 34px is
   the size that is clearly the brand mark and still leaves "Thinking…" the louder of the two. */
.brand-loader--md {
  --loader-size: 34px;
}

.brand-loader--lg {
  --loader-size: 56px;
}

/* Centred block, for a panel that is empty because something is genuinely in flight. */
.brand-loader-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 28px 12px;
}

@media (prefers-reduced-motion: reduce) {
  :root,
  :root[data-concept] {
    --motion-scale: 0 !important;
  }

  /* Anything that animates for its own sake rather than through a token stops outright. */
  .view.active.entering {
    animation: none;
  }

  /* A transform left behind by a rule that is no longer animating would otherwise park an element
     mid-travel with nothing left to move it home. */
  .task-row:not(.swipeable):active,
  .checkbox:active {
    transform: none;
  }

  /* Scrolling is motion too, and the one most often left on. */
  html {
    scroll-behavior: auto !important;
  }

  /* A loader that stops moving has to stop looking like a spinner and start looking like a status,
     or it becomes a still ring nobody can tell from a stuck app — the exact problem it was built to
     solve. Half the loop is drawn solid instead, which is static and still unmistakably "working". */
  .brand-loader .loader-run {
    animation: none;
    stroke-dasharray: 0.5 0.5;
    stroke-dashoffset: 0;
  }

  /* The boot splash carries the same rule for the same reason: a still mark that nobody can tell from
     a hung page is worse than no mark. Half the loop solid still reads as "working". */
  .boot-splash .loader-run {
    animation: none;
    stroke-dasharray: 0.5 0.5;
    stroke-dashoffset: 0;
  }

  .mobile-capture-btn .capture-mark .loader-run {
    animation: none;
    stroke-dasharray: 0.5 0.5;
    stroke-dashoffset: 0;
  }
}

.logout-illustration {
  width: min(240px, 70%);
  margin: 0 auto 16px;
}

.logout-illustration svg {
  display: block;
  width: 100%;
  height: auto;
}

.logout-button-icon {
  width: 17px;
  height: 17px;
  vertical-align: -4px;
  margin-right: 5px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

h1.page-title {
  font-size: var(--fs-display);
  margin: 0 0 2px;
}

.page-sub {
  color: var(--muted);
  font-size: var(--fs-ui);
  margin: 0 0 18px;
}

.stat-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 20px;
}

.stat-card {
  background: var(--card);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-card);
  padding: var(--pad-stat-y) var(--pad-ctl-x);
  display: flex;
  align-items: center;
  gap: var(--gap-row);
  min-width: 0;
}

.stat-num {
  font-size: var(--fs-h1);
  font-weight: var(--fw-bold);
}

.stat-label {
  font-size: var(--fs-body-s);
  color: var(--muted);
}

/* Dashboard page icons are glyph-only: the tinted square tile is dropped.
   !important is required because the renderers set `background` inline
   (index.html stat cards, and kindColor() for .recent-dot). */
.stat-icon,
.recent-dot {
  background: transparent !important;
  box-shadow: var(--shadow-none);
}

.stat-icon {
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-input);
  flex-shrink: 0;
}

.stat-icon svg {
  width: 19px;
  height: 19px;
  stroke-width: 1.8;
  vertical-align: middle;
}

.recent-dot svg {
  width: 15px;
  height: 15px;
  stroke-width: 1.9;
}

.type-chip svg,
#voiceRecordBtn svg,
#visibilityRow svg {
  width: 16px;
  height: 16px;
  stroke-width: 1.8;
  vertical-align: middle;
}

.type-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.grid-2 {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 16px;
  align-items: start;
}

.card {
  background: var(--card);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  backdrop-filter: var(--card-backdrop);
  -webkit-backdrop-filter: var(--card-backdrop);
  padding: var(--pad-card);
  margin-bottom: 16px;
}

.card-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.card-head h3 {
  margin: 0;
  font-size: var(--fs-title);
}

.today-agenda {
  max-height: 180px;
  overflow-y: auto;
  padding-right: 4px;
  scrollbar-width: thin;
}

/* Today & Tomorrow render as plain rows with no headers, as before; days beyond
   Tomorrow get one exact-date header each, so no relative word ever appears here. */
.agenda-group + .agenda-group {
  margin-top: 10px;
}

.agenda-group--plain .task-row:first-child {
  margin-top: 0;
}

.agenda-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
}

.agenda-group-label {
  font-size: var(--fs-body-s);
  font-weight: var(--fw-semibold);
  color: var(--muted);
}

.agenda-group-count {
  font-size: var(--fs-meta);
  font-weight: var(--fw-semibold);
  color: var(--muted);
  background: var(--bg);
  border-radius: var(--radius-sm);
  padding: 1px 7px;
  white-space: nowrap;
}

.agenda-more {
  color: var(--accent);
  font-size: var(--fs-body-s);
  font-weight: var(--fw-semibold);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
}

/* Agenda rows stay compact: the priority is one small emoji dot instead of a
   word badge, because the card is narrow on a phone. The title carries the
   name for screen readers; the colour is a convenience, never the only channel. */
.agenda-priority {
  font-size: var(--fs-lead);
  line-height: 1;
  flex-shrink: 0;
}

.link-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  min-width: 0;
  min-height: 40px;
  padding: 6px 8px;
  color: var(--accent);
  font-size: var(--fs-body);
  cursor: pointer;
  background: none;
  border: none;
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  text-align: center;
  white-space: normal;
}

.task-row {
  display: flex;
  align-items: center;
  gap: var(--gap-row);
  padding: var(--pad-row-y) var(--pad-row-x);
  border-radius: var(--radius-ctl);
  cursor: pointer;
  border: var(--border-w) solid transparent;
}

.task-row:hover {
  background: var(--bg);
}

.task-row.done .task-title {
  text-decoration: line-through;
  color: var(--muted);
}

.checkbox {
  width: 18px;
  height: 18px;
  padding: 0;
  border-radius: var(--radius-xs);
  border: var(--border-w-strong) solid var(--border);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: transparent;
}

.checkbox.checked {
  background: var(--green-fg);
  border-color: var(--green-fg);
  color: #fff;
  font-size: var(--fs-small);
}

.task-meta {
  flex: 1;
  min-width: 0;
}

.task-title {
  font-size: var(--fs-lead);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
}

.task-sub {
  font-size: var(--fs-body-s);
  color: var(--muted);
  line-height: var(--lh-normal);
}

.task-time {
  font-size: var(--fs-body-s);
  color: var(--muted);
  white-space: nowrap;
  line-height: var(--lh-normal);
}

/* Icons placed inside a line of text (Today & upcoming rows and every task
   list) must scale with the text and sit on its optical centre. Without this
   they keep Lucide's 24px default and sit on the baseline, which is what made
   the text and icons look misaligned. */
.task-title svg,
.task-sub svg,
.task-time svg,
.task-meta > span svg {
  width: 1em;
  height: 1em;
  vertical-align: -0.14em;
  stroke-width: 2;
  flex-shrink: 0;
}

/* The tick inside the 18px checkbox must fit the box. */
.checkbox svg {
  width: 12px;
  height: 12px;
  stroke-width: 3;
}

.badge {
  font-size: var(--fs-meta);
  font-weight: var(--fw-semibold);
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}

.badge.high {
  background: var(--red-bg);
  color: var(--red-fg);
}

.badge.medium {
  background: var(--amber-bg);
  color: var(--amber-fg);
}

.badge.low {
  background: var(--green-bg);
  color: var(--green-fg);
}

.badge.work,
.badge.event {
  background: var(--blue-bg);
  color: var(--blue-fg);
}

.badge.waiting {
  background: var(--purple-bg);
  color: var(--purple-fg);
}

.badge.task {
  background: var(--blue-bg);
  color: var(--blue-fg);
}

.capture-panel {
  background: var(--card);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-card);
  padding: 16px;
  margin-bottom: 16px;
}

.capture-input {
  width: 100%;
  border: var(--border-w) solid var(--border);
  background: var(--bg);
  border-radius: var(--radius-ctl);
  padding: 10px 12px;
  font-size: var(--fs-ui);
  color: var(--text);
  margin-bottom: 10px;
}

.recent-item {
  display: flex;
  gap: 10px;
  padding: 9px 4px;
  border-bottom: var(--border-w) solid var(--border);
  cursor: pointer;
}

.recent-item:last-child {
  border-bottom: none;
}

.recent-dot {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-lead);
}

.recent-text {
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
}

.recent-tag {
  font-size: var(--fs-meta);
  color: var(--muted);
}

.recent-time {
  font-size: var(--fs-label);
  color: var(--muted);
  margin-left: auto;
  white-space: nowrap;
}

.insight-item {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  align-items: start;
  gap: 10px;
  padding: 9px 0;
  border-bottom: var(--border-w) solid var(--border);
}

.insight-item > div {
  min-width: 0;
}

.insight-item > .link-btn {
  margin-left: 0;
  align-self: start;
}

.insight-item > .insight-chev {
  justify-self: end;
}

/* The Today strip is a set of shortcuts, not just text. A card that names a view is a button: it
   gets the pointer, a hover lift, and a trailing chevron, so "tap me and I go somewhere" is obvious
   without reading the copy. Cards with no target stay calm text with no affordance. */
.insight-item--action {
  cursor: pointer;
  border-radius: var(--radius-row);
  transition: background 120ms ease;
}

.insight-item--action:hover {
  background: var(--sidebar-hover);
}

.insight-item--action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.insight-next {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 3px;
  font-size: var(--fs-body-s);
  color: var(--muted);
}

.insight-next svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.insight-chev {
  margin-left: auto;
  align-self: center;
  color: var(--muted);
  display: inline-flex;
}

.insight-chev svg {
  width: 16px;
  height: 16px;
}

.insight-card {
  padding: 4px 0;
  border-bottom: var(--border-w) solid var(--border);
}

.insight-card:last-child {
  border-bottom: none;
}

.insight-actions {
  display: flex;
  gap: 8px;
  align-items: stretch;
  flex-wrap: wrap;
  min-width: 0;
  margin: 8px 0 10px 30px;
}

.insight-actions > .btn,
.insight-actions > .link-btn {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
}

.insight-items {
  margin: 0 0 10px 32px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.insight-title {
  font-size: var(--fs-ui);
  font-weight: var(--fw-semibold);
}

.insight-sub {
  font-size: var(--fs-body-s);
  color: var(--muted);
}

.hero-card {
  background: var(--brand-gradient);
  color: #fff;
  border-radius: var(--radius-card);
  padding: 18px;
  margin-bottom: 16px;
  font-size: var(--fs-lead);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-relaxed);
  min-height: 90px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.hero-card #heroQuoteText {
  width: 100%;
  animation: quote-arrive 420ms ease both;
}

.quote-actions {
  width: 100%;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}

.quote-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: var(--border-w) solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  padding: 6px 11px;
  border-radius: var(--radius-md);
  font-size: var(--fs-small);
  cursor: pointer;
}

.quote-action-icon {
  display: inline-grid;
  place-items: center;
  width: 15px;
  min-width: 0;
  line-height: var(--lh-tight);
  overflow: visible;
}

.quote-action-icon>svg {
  width: 15px;
  height: 15px;
  max-width: 100%;
}

.quote-action:hover {
  background: rgba(255, 255, 255, 0.28);
}

@keyframes quote-arrive {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: translateY(0); }
}

.dashboard-movable,
#heroQuoteCard {
  cursor: grab;
}

.dashboard-movable:active,
#heroQuoteCard:active {
  cursor: grabbing;
}

/* The shared reorder engine's states, named generically rather than after the dashboard, because
   the same class now marks a dragged task row and a dragged dashboard card. The old per-dashboard
   drag state was removed along with the duplicated implementation it belonged to. */
.reorderable {
  cursor: grab;
  touch-action: pan-y;
}

.reorderable:active {
  cursor: grabbing;
}

/* Suppresses text selection and the iOS long-press callout for the duration of a drag. Without
   these, a press-and-hold on a phone selects the row's text instead of picking it up. */
.reorder-active,
.reorder-active * {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.reorder-dragging {
  opacity: 0.55;
  outline: 2px dashed var(--accent);
  outline-offset: 2px;
}

.task-row.selected {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}

/* Swipe to act on a row.

   The row's own children are moved into .swipe-row-body and the actions sit underneath, so the
   transform slides the content off and the buttons are revealed rather than animated in. Without
   that structure the actions would simply be next in the flow and always visible. */
.task-row.swipeable {
  position: relative;
  overflow: hidden;
  /* Vertical scrolling must keep working, so the browser is told it owns the y axis and the swipe
     handler claims only x. Getting this wrong makes a long list unscrollable. */
  touch-action: pan-y;
  /* The row stops being a layout box and becomes a positioned, clipping frame. Its own layout is
     the strip's job, and on a phone the row's layout is a grid keyed on *direct* children
     (`.task-row > .checkbox`) — which the strip has just nested one level deeper, so every one of
     those rules stopped matching the moment a row became swipeable. Keeping the layout on the row
     and only translating the strip was the arrangement that broke it. */
  display: block;
  padding: 0;
  /* The row's own border goes with its padding: the strip carries an identical one, and two stacked
     borders left the strip 2px narrower than the window it slides behind — a bare seam at the edge. */
  border: 0;
  /* The row is the surface the revealed strip is cut out of, so it carries the card colour. Left
     transparent, a swiped row showed a grey hole the width of the buttons where its own text used
     to be. The strip above it does the rounding, via this row's `overflow: hidden`. */
  background: var(--card);
}

/* Only the *frame* is a block. Height still comes from the strip, so a row that wraps to three
   lines on a phone is as tall as its own content and the revealed buttons stretch to match. */
.task-row.swipeable > .swipe-row-body {
  /* Full row width, so the travel distance and the reveal agree at every density. Without it
     `min-height: 100%` resolved against a content-height parent, and the buttons alone set the
     row's height — revealing buttons taller than the strip covering them. */
  width: 100%;
}

.task-row.swipeable:hover {
  background: var(--bg);
}

.task-row .swipe-row-body {
  /* The row's own layout, so a swipeable row is laid out exactly like a row that is not one. */
  display: flex;
  align-items: center;
  gap: var(--gap-row);
  padding: var(--pad-row-y) var(--pad-row-x);
  border: var(--border-w) solid transparent;
  background: var(--card);
  position: relative;
  z-index: 1;
  /* Fills the row. As a bare flex or grid item it was only as wide as its own text, and an opaque
     strip that does not reach the edges is not a cover: every closed row showed a slice of its own
     buttons beside the text. */
  min-width: 0;
  min-height: 100%;
}

/* The strip is opaque, so it also has to carry the row's own state colours. Without these, hover
   and selection stopped working on exactly the list people swipe most. */
.task-row.swipeable:hover .swipe-row-body {
  background: var(--bg);
}

.task-row.swipeable.selected .swipe-row-body {
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
  /* The row's own selection bar is an inset shadow, and the strip now covers the row, so the bar
     has to be drawn on the strip to stay visible. */
  box-shadow: inset 3px 0 0 var(--accent);
}

/* The revealed actions, on top of the row's content — which does not move.

   The buttons are revealed *over* the row, not exposed by sliding it away: transforming the content
   strip pushed the title and badges 168px out of the clipping window, so the opened row showed only
   buttons. The body therefore moves only mid-gesture, and the committed state leaves it at 0. */
.task-row .swipe-actions {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: stretch;
  /* Above the opaque strip, or it paints over the buttons the moment the row opens. */
  z-index: 2;
  /* Exactly as wide as the slide, or a strip of bare row shows between the text and the first
     button. The slide is SWIPE_MAX_WIDTH in JS because it must be known mid-gesture. */
  width: 168px;
  /* Invisible until the row is open, so a press on a closed row cannot land on a hidden button. */
  pointer-events: none;
}

.task-row.swiped-open .swipe-actions {
  pointer-events: auto;
}

/* The buttons ease in because nothing else animates now that the content is pinned — without this
   they pop into existence and the gesture stops reading as a gesture. Matches the body's 180ms. */
.task-row .swipe-actions {
  opacity: 0;
  transform: translateX(16px);
  transition: opacity 180ms var(--ease, ease), transform 180ms var(--ease, ease);
}

.task-row.swiping .swipe-actions {
  transition: none;
}

.task-row.swiped-open .swipe-actions {
  opacity: 1;
  transform: translateX(0);
}

.swipe-action {
  border: 0;
  padding: 0 18px;
  font: inherit;
  font-size: var(--fs-body-s, 12.5px);
  font-weight: 600;
  color: #fff;
  cursor: pointer;
  /* Two actions split the revealed width evenly. Left to their own padding they came out narrower
     than the distance the row slides, which left a strip of bare row showing beside them. */
  flex: 1 1 0;
  min-width: 0;
}

.swipe-done {
  background: var(--green-fg, #2e9e79);
}

.swipe-archive {
  background: var(--accent, #4361ee);
}

.swipe-action:active {
  filter: brightness(0.92);
}

/* No transition while the finger is down, so the row tracks it exactly; the transition is for the
   snap open or shut, which is what should feel sprung rather than glued to the finger.

   The transition is on the body, not the row, and that is the whole difference between a reveal
   and a translation. Transforming the row moved the buttons along with it, so the buttons ended up
   stranded 168px from the edge they are supposed to be pinned to. */
.task-row.swiping .swipe-row-body {
  transition: none;
}

.task-row.swipeable:not(.swiping) .swipe-row-body {
  transition: transform 180ms var(--ease, ease);
}

.command-row {
  scroll-margin: 8px;
}

/* Inbox */
.tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.tab {
  padding: 7px 13px;
  border-radius: var(--radius-row);
  font-size: var(--fs-body);
  cursor: pointer;
  border: var(--border-w) solid var(--border);
  color: var(--muted);
  background: var(--card);
}

.tab.active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

.inbox-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Task detail panel (slide-over) */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  display: none;
  z-index: 40;
}

.overlay.open {
  display: block;
}

.panel {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  height: 100dvh;
  max-height: 100dvh;
  width: 420px;
  max-width: 92vw;
  background: var(--card);
  z-index: 50;
  box-shadow: var(--shadow-drawer);
  transform: translateX(100%);
  transition: transform .2s var(--ease);
  display: flex;
  flex-direction: column;
}

.panel.open {
  transform: translateX(0);
}

.panel-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-shrink: 0;
  padding: 16px 20px;
  border-bottom: var(--border-w) solid var(--border);
}

.panel-body {
  padding: 20px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
  min-height: 0;
}

.close-x {
  cursor: pointer;
  color: var(--muted);
  background: none;
  border: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  border-radius: var(--radius-sm);
}

.close-x:hover {
  color: var(--text);
  background: var(--bg);
}

.close-x>svg {
  width: 18px;
  height: 18px;
}

.field-row {
  display: flex;
  justify-content: space-between;
  padding: 9px 0;
  border-bottom: var(--border-w) solid var(--border);
  font-size: var(--fs-ui);
}

.field-label {
  color: var(--muted);
}

.panel-actions {
  display: flex;
  flex-shrink: 0;
  gap: 8px;
  padding: 16px 20px;
  border-top: var(--border-w) solid var(--border);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  min-width: 0;
  min-height: 40px;
  padding: var(--pad-ctl-y) var(--pad-ctl-x);
  border-radius: var(--radius-ctl);
  font-size: var(--fs-ui);
  font-weight: var(--fw-semibold);
  font-family: inherit;
  line-height: 1.25;
  text-align: center;
  white-space: normal;
  cursor: pointer;
  border: var(--border-w) solid var(--border);
  background: var(--card);
  color: var(--text);
  gap: 7px;
}

.btn-sm {
  min-height: 36px;
  padding: 7px 10px;
  font-size: var(--fs-body-s);
}

/* Icons inside buttons keep the same optical size as the label. */
.btn>svg,
.btn>[data-lucide] {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.btn[disabled] {
  opacity: 0.6;
  cursor: default;
  pointer-events: none;
}

.btn.primary {
  background: var(--green-fg);
  border-color: var(--green-fg);
  color: #fff;
}

.btn.danger {
  background: var(--card);
  color: var(--red-fg);
  border-color: var(--red-bg);
}

.btn.ghost {
  background: transparent;
  color: var(--accent);
  border-color: transparent;
}

.btn.ghost:hover {
  background: var(--blue-bg);
  color: var(--blue-fg);
}

.btn.tiny {
  min-height: 28px;
  padding: 5px 8px;
  font-size: var(--fs-meta);
}


/* Capture modal */
.modal-overlay {
  position: fixed;
  inset: 0;
  padding: 24px;
  background: rgba(0, 0, 0, 0.35);
  display: none;
  align-items: center;
  justify-content: center;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  z-index: 90;
}

.modal-overlay.open {
  display: flex;
}

.modal {
  width: min(440px, 100%);
  max-width: 100%;
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 48px);
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-gutter: stable;
  background: var(--card);
  backdrop-filter: var(--card-backdrop);
  -webkit-backdrop-filter: var(--card-backdrop);
  border-radius: var(--radius-modal);
  padding: var(--pad-modal);
}

.modal h3 {
  margin: 0 0 14px;
}

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
  margin-bottom: 14px;
}

.modal-head h3 {
  margin: 0;
}

.modal-close {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  padding: 0;
}

.type-row {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.type-chip {
  padding: 7px 13px;
  border-radius: var(--radius-row);
  border: var(--border-w) solid var(--border);
  font-size: var(--fs-body);
  cursor: pointer;
  color: var(--muted);
}

.type-chip.active {
  background: var(--blue-bg);
  color: var(--blue-fg);
  border-color: var(--blue-fg);
}

.modal textarea {
  width: 100%;
  min-height: 80px;
  max-height: min(32dvh, 220px);
  border: var(--border-w) solid var(--border);
  background: var(--bg);
  border-radius: var(--radius-ctl);
  padding: 10px 12px;
  font-size: var(--fs-lead);
  color: var(--text);
  resize: vertical;
  font-family: inherit;
}

.capture-hint-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 8px;
}

.capture-smart-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  color: var(--muted);
  font-size: var(--fs-body-s);
  cursor: pointer;
}

.capture-smart-toggle input {
  accent-color: var(--accent);
}

.capture-smart-toggle svg {
  width: 14px;
  height: 14px;
  color: var(--accent);
}

.capture-clear-suggestions {
  border: 0;
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-small);
  font-weight: var(--fw-semibold);
}

.capture-clear-suggestions:disabled {
  opacity: 0.4;
  cursor: default;
}

.capture-hint {
  min-height: 18px;
  margin: 6px 0 0;
  color: var(--accent);
  font-size: var(--fs-small);
  line-height: var(--lh-base);
}

.capture-hint svg {
  width: 1em;
  height: 1em;
  vertical-align: -0.14em;
}

/* Missing-value question. Reads as an assistant asking something — not as an error, and not as
   a form the person has to complete. It sits under the suggestions and never blocks Save. */
.capture-question {
  margin-top: 10px;
  padding: 11px 12px;
  border: var(--border-w) solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-ctl);
  background: var(--bg);
}

.capture-question[hidden] {
  display: none;
}

.capture-question-text {
  margin: 0 0 9px;
  font-size: var(--fs-body);
  line-height: var(--lh-base);
  color: var(--text);
}

.capture-question-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.capture-question-actions .btn {
  padding: 7px 11px;
  font-size: var(--fs-body-s);
}

.capture-question-skip {
  padding: 7px 4px;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: var(--fs-body-s);
  cursor: pointer;
  text-decoration: underline;
}

.capture-question-skip:hover {
  color: var(--text);
}

/* A sync that did not run. Sits above every view rather than inside one, and stays plain: the
   point is that it is unmissable, not that it is alarming. */
.sync-problem {
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-sm, 8px);
  background: var(--bg);
}

.sync-problem-title {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 6px;
  font-size: var(--fs-body);
  font-weight: 600;
  color: var(--text);
}

.sync-problem-title svg {
  width: 17px;
  height: 17px;
  flex: 0 0 auto;
  color: var(--accent);
}

.sync-problem-body {
  margin: 0 0 10px;
  font-size: var(--fs-body-s);
  line-height: var(--lh-base);
  color: var(--muted);
}

/* ---------- Conversation engine ----------
   A slot question is not a panel of its own. The answer goes back into the capture box as words —
   "remind me" becomes "remind me tomorrow" — and the next question drops in right underneath, so
   one growing sentence is all there is to look at. A second card beside the box made the sheet
   feel like two forms fighting for the same small space. */

/* It therefore loses the card treatment entirely: no border, no fill, no side padding. It reads as
   the next line of what you are already writing. */
.capture-question.slot {
  margin-top: 8px;
  padding: 0;
  border: 0;
  background: none;
}

.capture-question.slot .capture-question-text {
  margin: 0 0 7px;
  font-size: var(--fs-body-s);
  color: var(--muted);
}

.capture-question.slot .capture-question-skip {
  padding: 6px 2px;
}

.capture-question-reply {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
}

/* The common answers. One tap for the ordinary case, which is why the field is folded away by
   default rather than sitting open under the question. */
.capture-question-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  flex: 1 1 100%;
}

.capture-chip {
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: none;
  color: var(--text);
  font-size: var(--fs-body-s);
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.capture-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.capture-chip.other {
  color: var(--muted);
  border-style: dashed;
}

.capture-chip.other.open {
  border-style: solid;
  border-color: var(--accent);
  color: var(--accent);
}

.capture-answer-open {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  flex: 1 1 100%;
}

.capture-question-input {
  flex: 1 1 130px;
  min-width: 0;
  padding: 8px 10px;
  /* 16px is the floor that stops iOS zooming the page the moment the field takes focus. */
  font-size: 16px;
  font-family: inherit;
  color: var(--text);
  background: var(--bg-input, var(--bg));
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 8px);
}

.capture-question-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.capture-question-send {
  padding: 8px 13px;
  font-size: var(--fs-body-s);
}

.capture-question-mic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* A 40px target, matching the other icon controls in the sheet, so it is reachable one-handed. */
  min-width: 40px;
  min-height: 40px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 8px);
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.capture-question-mic svg {
  width: 17px;
  height: 17px;
}

.capture-question-mic:hover {
  color: var(--text);
}

.capture-question-mic.listening {
  color: var(--accent);
  border-color: var(--accent);
}

.capture-question-status {
  flex: 1 0 100%;
  margin: 0;
  font-size: var(--fs-body-s);
  color: var(--muted);
}

/* One sentence, several things. Reads as a short list of what else will be saved, not as a
   second form to complete: each row is editable, and every row can be removed. */
.capture-plan {
  margin-top: 10px;
  padding: 11px 12px;
  border: var(--border-w) solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-ctl);
  background: var(--bg);
}

.capture-plan[hidden] {
  display: none;
}

/* Confirmation for an auto-created capture. Sits above the content, clear of the mobile tab
   bar, and never covers the thing it is talking about. */
.capture-undo {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 90;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(440px, calc(100vw - 24px));
  padding: 10px 12px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-tag);
  background: var(--card);
  box-shadow: var(--shadow-lg);
  color: var(--text);
  font-size: var(--fs-body);
}

.capture-undo[hidden] {
  display: none;
}

.capture-undo > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.capture-undo > svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  color: var(--green-fg);
}

.capture-undo-btn {
  flex: 0 0 auto;
  min-height: 32px;
  padding: 6px 12px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body-s);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

.capture-undo-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.capture-plan-ask,
.capture-plan-head,
.capture-plan-agenda {
  margin: 0 0 8px;
  font-size: var(--fs-body-s);
  line-height: var(--lh-base);
  color: var(--text);
}

.capture-plan-ask {
  color: var(--muted);
}

.capture-plan-head {
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

.capture-plan-agenda {
  color: var(--muted);
}

.capture-plan-head svg,
.capture-plan-ask svg,
.capture-plan-agenda svg,
.capture-plan-flag svg {
  width: 1em;
  height: 1em;
  vertical-align: -0.14em;
}

.capture-plan-row {
  padding: 8px 0;
  border-top: var(--border-w) solid var(--border);
}

.capture-plan-row:first-of-type {
  border-top: 0;
}

.capture-plan-row-top,
.capture-plan-row-bottom {
  display: flex;
  align-items: center;
  gap: 7px;
}

.capture-plan-row-bottom {
  margin-top: 6px;
}

.capture-plan-title {
  flex: 1;
  min-width: 0;
  padding: 7px 9px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body);
}

.capture-plan-title:focus,
.capture-plan-select:focus,
.capture-plan-due:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.capture-plan-select,
.capture-plan-due {
  min-height: 34px;
  padding: 6px 8px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body-s);
}

.capture-plan-select {
  flex: 0 0 auto;
}

.capture-plan-due {
  flex: 1;
  min-width: 0;
}

.capture-plan-remove {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
}

.capture-plan-remove:hover {
  color: var(--text);
}

.capture-plan-remove svg {
  width: 15px;
  height: 15px;
}

.capture-plan-flag {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: var(--lh-normal);
  overflow-wrap: anywhere;
}

.capture-duplicate-warning {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 10px;
  padding: 11px;
  border: var(--border-w) solid var(--warning-border);
  border-radius: var(--radius-ctl);
  background: var(--amber-bg);
  color: var(--amber-fg);
}

.capture-duplicate-warning[hidden] {
  display: none;
}

.capture-duplicate-warning > svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  margin-top: 1px;
}

.capture-duplicate-copy {
  min-width: 0;
  flex: 1;
}

.capture-duplicate-copy strong {
  display: block;
  font-size: var(--fs-body-s);
}

.capture-duplicate-copy p {
  margin: 3px 0 0;
  color: var(--text);
  font-size: var(--fs-small);
  line-height: var(--lh-normal);
  overflow-wrap: anywhere;
}

.capture-duplicate-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 9px;
}

.capture-duplicate-actions .btn {
  min-height: 34px;
  padding: 7px 10px;
  font-size: var(--fs-small);
}

.voice-dictation-status {
  width: 100%;
  min-height: 17px;
  margin: 2px 0 0;
  color: var(--muted);
  font-size: var(--fs-small);
}

/* Dictation language picker. The chips carry each language in its own script (தமிழ், हिन्दी,
   తెలుగు…) so the option is readable by the person who speaks it, without a translation. */
.voice-lang-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  margin-top: 4px;
}

#voiceRecordBtn {
  width: 100%;
}

/* One-click voice capture carries its live state on the element so the sheet always says what
   the microphone is doing: red while it is open, amber while a tapped Stop is waiting for the
   last words to land. The label still says what a tap will do; this says what is happening. */
#voiceRecordBtn[data-state="recording"] {
  border-color: var(--red-fg);
  color: var(--red-fg);
}

#voiceRecordBtn[data-state="stopping"] {
  border-color: var(--amber-fg);
  color: var(--amber-fg);
}

.voice-lang-label {
  font-size: var(--fs-small);
  color: var(--muted);
}

#voiceLangRow {
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
}

#voiceLangRow .type-chip {
  padding: 7px 10px;
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
}

.modal-actions {
  display: flex;
  flex-shrink: 0;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}

/* Search / Ask panel */
.ask-overlay {
  position: fixed;
  inset: 0;
  padding: max(12px, env(safe-area-inset-top)) 12px 12px;
  background: rgba(0, 0, 0, 0.3);
  display: none;
  align-items: flex-start;
  justify-content: center;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  z-index: 85;
}

.ask-overlay.open {
  display: flex;
}

.tab-vert {
  padding: 9px 12px;
  border-radius: var(--radius-row);
  font-size: var(--fs-ui);
  font-family: inherit;
  cursor: pointer;
  color: var(--muted);
  margin-bottom: 2px;
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
}

/* Icon + label stay optically centred in every tab / button. */
.tab-vert>svg,
.tab-vert>[data-lucide] {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.tab-vert>span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tab-vert:hover {
  background: var(--bg);
}

.tab-vert.active {
  background: var(--blue-bg);
  color: var(--blue-fg);
  font-weight: var(--fw-semibold);
}

.tab-vert:focus-visible,
.btn:focus-visible,
.settings-tabs button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Settings: three columns on desktop, a single column with a scrollable tab
   strip on phones. minmax(0,1fr) keeps the middle column from overflowing. */
.settings-grid {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr) 260px;
  gap: 16px;
  align-items: start;
}

.settings-tabs {
  padding: 8px;
}

.settings-aside {
  min-width: 0;
}

.save-hint {
  font-size: var(--fs-meta);
  font-weight: var(--fw-semibold);
  color: var(--green-fg, var(--accent));
  white-space: nowrap;
}

@media (max-width: 1180px) {
  .settings-grid {
    grid-template-columns: 180px minmax(0, 1fr);
  }
  .settings-aside {
    grid-column: 1 / -1;
  }
}

@media (max-width: 900px) {
  .settings-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .settings-tabs {
    display: flex;
    flex-wrap: nowrap;
    gap: 6px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .settings-tabs::-webkit-scrollbar {
    display: none;
  }
  .tab-vert {
    margin-bottom: 0;
    white-space: nowrap;
  }
  .settings-aside {
    grid-column: auto;
  }
}



@media (max-width:900px) {
  .topbar {
    padding: 10px 14px;
    gap: 8px;
  }

  .mobile-capture-btn {
    display: inline-flex;
  }

  .content {
    padding: 18px 14px;
  }

  .search-box {
    min-width: 0;
    padding: 8px 10px;
  }

  .search-box input {
    min-width: 0;
  }

  .search-box .kbd {
    display: none;
  }

  #view-settings>div {
    grid-template-columns: 1fr !important;
  }

  #view-settings>div>div:last-child {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
  }

  #view-settings>div>div:last-child .hero-card {
    margin-bottom: 0;
  }

  .card,
  .capture-panel {
    min-width: 0;
  }
}

.ask-box {
  background: var(--card);
  border-radius: var(--radius-lg);
  width: 600px;
  max-width: 92vw;
  max-height: 75vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow-pop);
}

/* The command palette reuses .ask-overlay but not .ask-box, and for a long time it had a class of
   its own with no rule under it anywhere in this file. A div with no styles is transparent and
   shrink-to-fit, so the palette opened as a 222px-wide hole in the page: the dashboard, the
   capture bar and the stat cards all showed through it, and the input was only as wide as its own
   default size, which cut the placeholder off mid-word.

   So this is not a nicety. An unstyled panel is a panel you can see the app through, and a feature
   that was made reachable by touch on a phone, where it was least usable. */
.ask-panel {
  background: var(--card);
  border-radius: var(--radius-lg);
  /* A width, not a max-width: an unstyled panel is shrink-to-fit, and a cap on its own leaves it at
     whatever the input's default size happens to be — which is how this panel ended up 222px wide
     on a 390px screen. 34rem is the 560px this was capped at before, in rem so it follows the
     user's text size the way the rest of the sizing does. */
  width: min(34rem, 100%);
  max-height: min(75vh, calc(100dvh - 24px));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow-pop);
}

.ask-panel > input {
  flex: 0 0 auto;
  width: 100%;
  min-width: 0;
  border: 0;
  border-bottom: var(--border-w) solid var(--border);
  outline: none;
  background: transparent;
  padding: 15px 18px;
  font-size: var(--fs-input);
  color: var(--text);
}

/* The one scrolling region in the panel. It has to be allowed to shrink below its content, or the
   flex column pushes the panel past max-height and the hint strip falls off the bottom. */
#commandList {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 6px 8px;
}

.command-hint {
  flex: 0 0 auto;
  display: flex;
  gap: 14px;
  padding: 10px 18px;
  font-size: 12px;
  color: var(--muted);
  border-top: 1px solid var(--border);
}

.ask-input-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 18px;
  border-bottom: var(--border-w) solid var(--border);
}

.ask-input-row input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-size: var(--fs-input);
  color: var(--text);
}

.ask-input-row>svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  color: var(--muted);
}

.ask-results {
  padding: 16px 18px;
  overflow-y: auto;
}

.ask-answer {
  background: var(--blue-bg);
  border-radius: var(--radius-tag);
  padding: 14px;
  margin-bottom: 14px;
  font-size: var(--fs-ui);
  line-height: var(--lh-loose);
  color: var(--text);
  /* Models answer in paragraphs and bullet lists. Without this the newlines collapse and the
     whole answer renders as one run-on line. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Sources list, shared by the overlay and the header search dropdown. */
.ask-sources {
  margin-top: 10px;
  padding-top: 10px;
  border-top: var(--border-w) solid var(--border);
}

.ask-sources-label {
  font-size: var(--fs-meta);
  color: var(--muted);
  font-weight: var(--fw-semibold);
  margin-bottom: 6px;
}

.ask-source {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 0;
  cursor: pointer;
  font-size: var(--fs-body-s);
}

.ask-source-title {
  color: var(--accent);
}

.ask-hint {
  margin-top: 6px;
  font-size: var(--fs-meta);
  color: var(--muted);
}

/* Header search dropdown — results stream under the field instead of opening a new panel.
   Centred on the field and allowed to grow a little past it, but always capped so there is
   breathing room on both sides of the screen rather than running edge to edge. */
.search-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  min-width: 260px;
  max-width: min(calc(100% + 40px), calc(100vw - 24px));
  max-height: min(60vh, 460px);
  max-height: min(60dvh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: var(--card);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-modal);
  padding: 6px;
  z-index: 70;
}

.search-dropdown[hidden] {
  display: none;
}

.search-dropdown .empty {
  margin: 10px 8px;
}

.search-hit {
  padding: 9px 10px;
  border-radius: var(--radius-row);
  cursor: pointer;
  font-size: var(--fs-ui);
}

.search-hit:hover,
.search-hit:active {
  background: var(--bg);
}

.search-hit-sub {
  color: var(--muted);
  font-size: var(--fs-body-s);
}

/* The AI answer sits below the hits, separated so it reads as a distinct answer. */
.search-ask:not(:empty) {
  margin: 6px 4px 2px;
  padding-top: 8px;
  border-top: var(--border-w) solid var(--border);
}

.search-ask .ask-answer {
  margin-bottom: 6px;
}

.ask-result-item {
  padding: 10px 8px;
  border-radius: var(--radius-row);
  cursor: pointer;
  font-size: var(--fs-ui);
}

.ask-result-item:hover {
  background: var(--bg);
}

.empty {
  color: var(--muted);
  font-size: var(--fs-ui);
  padding: 20px 4px;
  text-align: center;
}

/* Calendar */
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
  background: var(--border);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-tag);
  overflow: hidden;
}

.cal-day-head {
  background: var(--card);
  padding: 8px;
  text-align: center;
  font-size: var(--fs-small);
  color: var(--muted);
  font-weight: var(--fw-semibold);
}

.cal-cell {
  background: var(--card);
  min-height: 90px;
  padding: 6px;
  font-size: var(--fs-small);
  position: relative;
  min-width: 0;
}

/* Fixed-size number box for every cell: the "today" pill must not push that
   cell's events down relative to the rest of the row. */
.cal-cell .num {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-small);
  color: var(--muted);
  margin-bottom: 4px;
  flex-shrink: 0;
}

.cal-cell.today .num {
  color: #fff;
  background: var(--accent);
  font-weight: var(--fw-semibold);
}

/* Month event peek. Sized in `rem` rather than `px` because the whole problem it solves is that a
   fixed-pixel card stops fitting once the reader has enlarged their text - the same reason the rest
   of the type on this page is not measured in pixels either. Max-width is what keeps it a small
   module rather than a dialog that fills a phone. */
.month-peek {
  position: fixed;
  z-index: 300;
  width: max-content;
  max-width: min(20rem, calc(100vw - 16px));
  background: var(--card);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-tag);
  box-shadow: 0 10px 30px rgb(0 0 0 / 0.28);
  padding: 0.6rem 0.7rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.month-peek-title {
  font-weight: var(--fw-semibold);
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.month-peek-when,
.month-peek-meta {
  font-size: var(--fs-small);
  color: var(--muted);
}

.month-peek-actions {
  display: flex;
  gap: 6px;
  margin-top: 0.25rem;
}

@media (max-width: 480px) {
  .cal-event {
    /* The title is unreadable at this width however it is styled, and a tap now reveals the whole
       one. Truncating it cleanly makes the cell legible as a count of what is on rather than a
       half-word that looks like a rendering fault. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.cal-event {
  font-size: var(--fs-micro);
  padding: 2px 5px;
  border-radius: var(--radius-xs);
  margin-bottom: 2px;
  background: var(--blue-bg);
  color: var(--blue-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  max-width: 100%;
}

.cal-week-wrap {
  display: flex;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-tag);
  overflow: hidden;
  background: var(--card);
}

.cal-time-col {
  width: 56px;
  flex-shrink: 0;
  border-right: var(--border-w) solid var(--border);
}

.cal-week-head-spacer {
  height: 52px;
  border-bottom: var(--border-w) solid var(--border);
}

.cal-hour-label {
  height: 50px;
  font-size: var(--fs-label);
  color: var(--muted);
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding: 1px 6px 0 0;
  border-bottom: var(--border-w) solid var(--border);
}

.cal-week-days {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.cal-week-day-col {
  border-right: var(--border-w) solid var(--border);
  position: relative;
  min-width: 0;
}

.cal-week-day-col:last-child {
  border-right: none;
}

.cal-week-day-head {
  height: 52px;
  border-bottom: var(--border-w) solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-small);
  color: var(--muted);
}

.cal-week-day-head .num {
  font-size: var(--fs-title);
  font-weight: var(--fw-semibold);
  color: var(--text);
  margin-top: 2px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.cal-week-day-head.today .num {
  background: var(--accent);
  color: #fff;
}

.cal-week-day-body {
  position: relative;
}

.cal-hour-row {
  height: 50px;
  border-bottom: var(--border-w) solid var(--border);
}

.cal-week-event {
  position: absolute;
  left: 4px;
  right: auto;
  border-radius: var(--radius-sm);
  font-size: var(--fs-label);
  padding: 3px 6px;
  overflow: hidden;
  cursor: pointer;
  line-height: var(--lh-dense);
  white-space: nowrap;
  text-overflow: ellipsis;
  min-height: 18px;
}

.cal-week-event:hover {
  filter: brightness(1.06);
  z-index: 2;
}

/* Upcoming annual dates on Today. The row is a whole clickable target that opens the person, so it
   carries the same 44px minimum as everything else tappable here. */
.upcoming-date-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 4px;
  min-height: 44px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.upcoming-date-row:hover {
  background: var(--sidebar-hover);
}

.upcoming-date-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--blue-bg);
  color: var(--blue-fg);
}

.upcoming-date-name {
  font-size: var(--fs-label);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

.upcoming-date-when {
  font-size: var(--fs-small);
  color: var(--muted);
}

/* Rescheduling affordances. The touch target is the whole block, not a handle, because on a phone a
   12px grip inside a 46px event is below the minimum comfortable size. */
.cal-drag-event {
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  cursor: grab;
}

.cal-drag-event:active {
  cursor: grabbing;
}

/* The source block stays in place, dimmed, rather than disappearing — the gap it leaves shows the
   person that the calendar did not lose the event, only that it is being moved. */
.cal-dragging {
  opacity: 0.35;
}

/* A fixed copy that follows the pointer. position:fixed so it does not shift as the page scrolls
   under a drag that started near the edge. */
.cal-drag-ghost {
  position: fixed;
  z-index: 999;
  pointer-events: none;
  opacity: 0.9;
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.2));
  border-radius: var(--radius-sm);
  transform: rotate(-1.5deg);
}

/* The row or cell under the pointer. Applied to the day body, not the individual hour row, so the
   whole column lights up and the drop target matches the highlight exactly. */
.cal-drop-day {
  transition: background 0.12s ease;
}

.cal-drop-active {
  background: var(--accent-soft, var(--blue-bg)) !important;
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* One line of instruction, shown only while a drag is in progress so it cannot be mistaken for
   chrome. */
.cal-drop-hint {
  font-size: var(--fs-small);
  color: var(--muted);
  text-align: center;
  padding: 6px 0 8px;
}

/* The day view is the week grid with one column, so the column rule gives the single day room to
   breathe instead of hugging the left edge. */
.cal-day-days {
  grid-template-columns: 1fr;
}

.cal-day-wrap .cal-week-event {
  /* With one lane available an event should use the full width; the week view's percentage
     arithmetic is reused unchanged, so this is only a readability adjustment. */
  left: 6px;
}

/* ---------- Documents ---------- */
/* The expiry chip is the reason a person scans the list rather than reading it, so it is the one
   part of the row allowed to shout. Colour is carried by the theme tokens rather than fixed hexes,
   so it stays legible in all of them — an expiry badge that fails contrast in dark mode is worse
   than no badge, because the one date that mattered becomes the one you cannot read. */
.doc-chip {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: var(--fs-small);
  font-weight: 600;
  white-space: nowrap;
  background: var(--bg);
  color: var(--muted);
}

/* Expired is the only state that has already gone wrong, so it is the only red. */
.doc-chip.doc-expired {
  background: var(--red-bg);
  color: var(--red-fg);
}

/* Inside a week: the last moment it can still be dealt with. */
.doc-chip.doc-week {
  background: var(--amber-bg);
  color: var(--amber-fg);
}

/* Inside the lead time: worth knowing about, not worth interrupting anyone for. */
.doc-chip.doc-month {
  background: var(--blue-bg);
  color: var(--blue-fg);
}

.doc-chip.doc-later {
  background: var(--green-bg);
  color: var(--green-fg);
}

/* A receipt. Neutral on purpose — it has nothing to be alarmed about, and colouring it would make
   every quiet document in the list look like a problem. */
.doc-chip.doc-none {
  background: var(--bg);
  color: var(--muted);
}

/* ---------- Money ---------- */
/* The amount is the reason the row is read, so it is set apart from the title and never wrapped onto
   its own line — a total that breaks across two lines is a total that has to be added up by eye. */
.money-amount {
  flex-shrink: 0;
  margin-left: auto;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
}

.money-cat-row {
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
}

.money-cat-row:last-child {
  border-bottom: none;
}

.money-cat-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: var(--fs-body-s);
  color: var(--text);
  margin-bottom: 6px;
}

.money-cat-head span:last-child {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.money-cat-bar {
  height: 7px;
  border-radius: 999px;
  background: var(--bg);
  overflow: hidden;
}

.money-cat-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent, var(--blue-fg));
}

/* The "/mo" is a unit, not a number, so it is set lighter and smaller — the eye should read the
   figure first and the qualification second. */
.money-per {
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--muted);
  margin-left: 1px;
}

/* The two directions of a debt get a label each, because "owed to you" and "you owe" are different
   facts and a single net number would quietly hide one of them. */
.money-owed-group + .money-owed-group {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.money-owed-head {
  font-size: var(--fs-small);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 0 0 2px;
}

/* On a phone the chip would squeeze the title into two words, so it drops to its own line under
   the row instead. The label is kept — it is the information, not the decoration.
   520px, not 560px: that exact width is the old capture-sheet width the UI suite asserts is gone,
   and reusing it here would make that check fail on a rule that has nothing to do with it. */
@media (max-width:520px) {
  .task-row:has(.doc-chip) {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
  }
  .task-row .doc-chip {
    align-self: flex-start;
  }
}

@media (max-width:900px) {
  .sidebar-backdrop.visible {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 60;
    background: rgba(10, 15, 35, 0.28);
  }

  .sidebar {
    position: fixed;
    left: -240px;
    top: 0;
    bottom: 0;
    z-index: 70;
    transition: left .2s;
  }

  .sidebar.collapsed {
    width: 230px;
    padding-left: 12px;
    padding-right: 12px;
  }

  .sidebar.collapsed .brand {
    flex-wrap: nowrap;
    justify-content: flex-start;
    padding-left: 8px;
    padding-right: 8px;
  }

  .sidebar.collapsed .brand::before {
    left: 4px;
    top: 2px;
    width: 58px;
    transform: none;
  }

  .sidebar.collapsed .brand-mark {
    width: 50px;
  }

  .sidebar.collapsed .brand-name,
  .sidebar.collapsed .sidebar-collapse,
  .sidebar.collapsed .nav-label,
  .sidebar.collapsed .nav-badge,
  .sidebar.collapsed .capture-label,
  .sidebar.collapsed .sidebar-caption,
  .sidebar.collapsed .sidebar-profile-copy {
    display: revert;
  }

  .sidebar.collapsed .nav-item {
    justify-content: space-between;
    padding-left: 10px;
    padding-right: 10px;
  }

  .sidebar.collapsed .nav-item .left {
    gap: 10px;
  }

  .sidebar.collapsed .sidebar-profile {
    justify-content: flex-start;
    padding-left: 8px;
    padding-right: 8px;
  }

  .sidebar.open {
    left: 0;
  }

  .stat-row {
    grid-template-columns: repeat(2, 1fr);
  }

  .grid-2 {
    grid-template-columns: 1fr;
  }

  .cal-cell {
    min-height: 60px;
  }
}

@media (max-width:900px) {
  .cal-week-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .cal-week-days {
    min-width: 560px;
  }

  .cal-time-col {
    position: sticky;
    left: 0;
    background: var(--card);
    z-index: 2;
  }

  #authScreen>div {
    width: min(340px, calc(100vw - 28px)) !important;
    padding: 22px !important;
  }

  .modal,
  .panel {
    max-width: 100vw;
  }

  .panel {
    width: min(420px, 100vw);
  }
}

@media (max-width:480px) {
  .logout-card {
    margin: 20px auto !important;
    padding: 30px 20px !important;
  }

  .logout-card .page-sub {
    line-height: var(--lh-base);
  }

  .logout-card .btn {
    min-height: 44px;
    width: 100%;
  }

  .logout-illustration {
    width: min(220px, 78vw);
  }

  .topbar .icon-btn {
    width: 34px;
    height: 34px;
  }

  .topbar .avatar {
    width: 30px;
    height: 30px;
  }

  h1.page-title {
    font-size: var(--fs-h1);
  }

  .stat-row {
    gap: 8px;
  }

  .stat-card {
    padding: 11px 10px;
    gap: 8px;
  }

  .stat-label {
    font-size: var(--fs-meta);
  }

  /* The settings aside wraps under the main column on medium screens. */
  #settingsTab-account .card>div[style*="grid-template-columns"] {
    grid-template-columns: 1fr !important;
  }

  .modal>div[style*="grid-template-columns"] {
    grid-template-columns: 1fr !important;
  }

  .modal-actions {
    justify-content: stretch;
  }

  .modal-actions .btn {
    flex: 1;
  }

  .field-row {
    align-items: flex-start;
    gap: 10px;
  }

  .field-row>*:last-child {
    min-width: 0;
    text-align: right;
  }

  .task-row {
    flex-wrap: wrap;
    order: initial;
  }

  /* The swipe strip nests the row's children one level deeper, so the row's own direct-child rules
     have to be restated for the strip. Without this, a swipeable row on a phone loses the wrap
     every non-swipeable row has, which is how the badges ended up underneath the revealed buttons. */
  .swipe-row-body {
    flex-wrap: wrap;
  }

  .task-meta {
    flex-basis: 100%;
    order: initial;
  }

  .swipe-row-body .task-meta {
    flex-basis: 100%;
  }

  .task-time,
  .task-row>.badge {
    order: initial;
    margin-top: 4px;
  }

  .swipe-row-body .task-time,
  .swipe-row-body>.badge {
    order: initial;
    margin-top: 4px;
  }
}

/* Keyboard shortcut hints */
.kbd {
  font-size: var(--fs-label);
  font-weight: var(--fw-semibold);
  color: var(--muted);
  background: var(--bg);
  border: var(--border-w) solid var(--border);
  border-bottom-width: 2px;
  border-radius: var(--radius-xs);
  padding: 2px 6px;
  white-space: nowrap;
  flex-shrink: 0;
}

.search-box:hover .kbd {
  color: var(--text);
}

.shortcut-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: var(--border-w) solid var(--border);
  font-size: var(--fs-ui);
}

.shortcut-row:last-child {
  border-bottom: none;
}

/* Reports: 14-day activity chart */
.chart-legend {
  display: flex;
  gap: 14px;
  font-size: var(--fs-small);
  color: var(--muted);
  margin-bottom: 10px;
}

.chart-legend i {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  margin-right: 5px;
}

.chart-wrap {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 140px;
}

.chart-col {
  flex: 1;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 4px;
}

.chart-bars {
  flex: 1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 2px;
  min-height: 0;
}

.chart-bar {
  width: 8px;
  max-width: 45%;
  min-height: 2px;
  border-radius: 3px 3px 0 0;
  transition: height .25s var(--ease);
}

.chart-label {
  font-size: var(--fs-caption);
  color: var(--muted);
  text-align: center;
  white-space: nowrap;
}

.reports-toolbar {
  display: grid;
  grid-template-columns: minmax(200px, 2fr) repeat(3, minmax(130px, 1fr));
  gap: 12px;
  align-items: end;
}

.report-filter {
  display: grid;
  gap: 5px;
  min-width: 0;
  color: var(--muted);
  font-size: var(--fs-small);
  font-weight: 600;
}

.report-filter .capture-input {
  width: 100%;
  margin: 0;
  color: var(--text);
}

.report-actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.report-actions .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.report-actions .btn i,
.report-state > i {
  width: 16px;
  height: 16px;
}

.report-actions .btn:disabled {
  cursor: not-allowed;
  opacity: .55;
}

.report-source {
  grid-column: 1 / -1;
  margin: -4px 0 0;
  color: var(--muted);
  font-size: var(--fs-caption);
}

.report-state {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  padding: 14px 16px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-card);
  background: var(--card);
  color: var(--muted);
}

.report-state[hidden],
#reportResults[hidden] {
  display: none;
}

.report-state.is-error {
  border-color: var(--red-fg);
  color: var(--red-fg);
}

.report-state-spinner {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: report-spin .8s linear infinite;
}

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

.report-card-hint {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.report-type-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  width: 100%;
  gap: 7px 12px;
  padding: 10px 8px;
  border: 0;
  border-bottom: var(--border-w) solid var(--border);
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}

.report-type-row:last-child {
  border-bottom: 0;
}

.report-type-row:hover,
.report-type-row.is-selected {
  border-radius: var(--radius-row);
  background: var(--bg);
}

.report-type-row:focus-visible,
.report-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.report-type-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: var(--fs-ui);
}

.report-type-label i,
.report-item-icon i {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}

.report-type-count {
  color: var(--muted);
  font-size: var(--fs-small);
  font-variant-numeric: tabular-nums;
}

.report-type-track {
  grid-column: 1 / -1;
  height: 6px;
  overflow: hidden;
  border-radius: 99px;
  background: var(--bg);
}

.report-type-track > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  transition: width .2s var(--ease);
}

.report-item {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto auto;
  align-items: center;
  width: 100%;
  gap: 10px;
  padding: 12px 4px;
  border: 0;
  border-bottom: var(--border-w) solid var(--border);
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}

.report-item:last-child {
  border-bottom: 0;
}

.report-item:hover {
  background: var(--bg);
}

.report-item-icon {
  display: grid;
  place-items: center;
  color: var(--accent);
}

.report-item-main {
  display: grid;
  min-width: 0;
  gap: 3px;
}

.report-item-title,
.report-item-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.report-item-title {
  font-size: var(--fs-ui);
  font-weight: 600;
}

.report-item-meta,
.report-item-date {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.report-item-status {
  padding: 4px 8px;
  border-radius: 99px;
  background: var(--bg);
  color: var(--muted);
  font-size: var(--fs-caption);
  white-space: nowrap;
}

.report-item-status.is-complete {
  background: var(--green-bg);
  color: var(--green-fg);
}

#reportPreview .empty-state {
  margin: 10px 0 0;
}

#reportPreviewCount {
  white-space: nowrap;
}

@media (max-width: 700px) {
  .reports-toolbar {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .report-search,
  .report-actions,
  .report-source {
    grid-column: 1 / -1;
  }
}

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

  .report-filter,
  .report-search,
  .report-actions,
  .report-source {
    grid-column: 1;
  }

  .report-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .report-actions .btn {
    width: 100%;
    min-width: 0;
    padding-right: 8px;
    padding-left: 8px;
  }

  .report-actions .btn:first-child {
    grid-column: 1 / -1;
  }

  .report-item {
    grid-template-columns: 24px minmax(0, 1fr) auto;
    gap: 8px;
  }

  .report-item-date {
    grid-column: 2;
  }

  .report-item-status {
    grid-column: 3;
    grid-row: 1 / span 2;
  }
}

/* Item panel: the next-step suggestion.

   Token-driven only, so it stays legible across all ten themes without a single per-theme rule —
   the same approach the inline help took. The accent stripe is what marks this as the app talking,
   not as part of the item's own data. */
.next-action {
  margin: 0 20px 14px;
  padding: 12px 14px 13px;
  border: var(--border-w) solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 12px;
  background: var(--card);
}

.next-action[hidden] {
  display: none;
}

.next-action-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.next-action-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--accent);
}

.next-action-label svg {
  width: 14px;
  height: 14px;
}

/* The dismiss control is a real button, not a bare cross: it carries its own label, because
   "stop suggesting this" is the whole promise and an unlabelled cross does not convey it. */
.next-action-skip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: background .15s var(--ease), color .15s var(--ease);
}

.next-action-skip:hover,
.next-action-skip:focus-visible {
  background: var(--bg);
  color: var(--text);
}

.next-action-skip svg {
  width: 15px;
  height: 15px;
}

.next-action-why {
  margin: 6px 0 0;
  font-size: var(--fs-body-s);
  line-height: 1.45;
  color: var(--text);
}

.next-action-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.next-action-acts:empty {
  display: none;
}

.next-action-acts .btn {
  padding: 7px 12px;
  font-size: var(--fs-body-s);
}

@media (max-width: 480px) {
  .next-action {
    margin: 0 16px 12px;
  }

  /* A 30px cross is a miss on a phone, so the touch target grows while the glyph stays the same. */
  .next-action-skip {
    width: 44px;
    height: 44px;
    margin: -7px -7px -7px 0;
  }
}

/* Item panel: quick reschedule */
.panel-snooze {
  padding: 12px 20px;
  border-top: var(--border-w) solid var(--border);
}

.panel-snooze .snooze-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.panel-snooze .btn {
  padding: 7px 12px;
  font-size: var(--fs-body-s);
}

@media (max-width:480px) {
  .chart-wrap {
    height: 110px;
    gap: 4px;
  }

  .chart-label {
    font-size: var(--fs-tiny);
  }
}

body.overlay-open {
  overflow: hidden;
}

body.overlay-open .content {
  overflow: hidden;
}

/* ---------- Mobile-first shell and module layout ---------- */
.calendar-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 14px;
}

.calendar-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.calendar-month-scroll {
  width: 100%;
  min-width: 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.ask-box {
  position: relative;
  width: min(600px, 100%);
  max-width: 100%;
  max-height: min(75vh, calc(100dvh - 24px));
}

.ask-box > .close-x {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
}

.ask-input-row {
  padding-right: 52px;
}

.ask-results {
  min-height: 0;
  flex: 1 1 auto;
}

#authScreen {
  overflow-y: auto;
  padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
}

#authScreen > div {
  max-height: calc(100vh - 24px);
  max-height: calc(100dvh - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

@media (max-width: 900px) {
  .topbar {
    gap: 6px;
    min-height: 56px;
    padding: max(10px, env(safe-area-inset-top)) max(14px, env(safe-area-inset-right)) 10px max(14px, env(safe-area-inset-left));
  }

  /* 40px rather than the desktop 36px: a measured 34x38 target was below the comfortable
     minimum for a thumb, and these sit shoulder to shoulder in a crowded top bar. */
  .topbar .icon-btn {
    width: 40px;
    height: 40px;
  }

  .topbar .mobile-capture-btn {
    width: 44px;
    height: 40px;
    min-width: 44px;
  }

  .topbar .mobile-capture-btn .capture-mark {
    width: 44px;
    height: 26px;
  }

  .search-wrap {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* The input is real and focusable now, so it must keep its width on mobile. */
  .search-box input {
    width: 100%;
    min-width: 0;
    font-size: var(--fs-input);
  }

  .search-box {
    padding: 9px 12px;
  }

  /* Stays anchored under the field; the topbar's stacking context keeps it above content. */
  .search-dropdown {
    max-height: 52vh;
    max-height: 52dvh;
  }

  #notifPanel,
  #avatarMenu {
    right: 0;
    max-width: calc(100vw - 16px);
  }

  .sidebar {
    left: 0;
    width: min(280px, calc(100vw - 48px));
    max-width: calc(100vw - 48px);
    height: 100vh;
    height: 100dvh;
    padding-top: max(18px, env(safe-area-inset-top));
    padding-bottom: max(18px, env(safe-area-inset-bottom));
    transform: translateX(-105%);
    transition: transform var(--dur-5) var(--ease);
    overscroll-behavior: contain;
  }

  .sidebar.open {
    left: 0;
    transform: translateX(0);
  }

  .sidebar.collapsed {
    width: min(280px, calc(100vw - 48px));
    max-width: calc(100vw - 48px);
  }

  .sidebar-backdrop.visible {
    z-index: 80;
  }

  .sidebar {
    z-index: 90;
  }

  .content {
    padding: 18px max(14px, env(safe-area-inset-right)) calc(28px + env(safe-area-inset-bottom)) max(14px, env(safe-area-inset-left));
    scrollbar-gutter: auto;
  }

  .view > *,
  .card,
  .grid-2 > *,
  .settings-grid > *,
  .settings-panel,
  .settings-panel > *,
  .capture-panel {
    min-width: 0;
  }

  h1.page-title,
  .page-sub,
  .card-head h3,
  .task-title,
  .task-sub,
  .recent-text,
  .recent-tag,
  .insight-title,
  .insight-sub,
  .empty {
    overflow-wrap: anywhere;
  }

  .card {
    padding: 16px;
  }

  .card-head {
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 8px;
  }

  .card-head h3,
  .card-head > div {
    min-width: 0;
  }

  .stat-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .stat-card,
  .stat-card > div:last-child {
    min-width: 0;
  }

  .grid-2 {
    grid-template-columns: minmax(0, 1fr);
  }

  .task-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    column-gap: 10px;
    row-gap: 4px;
    order: initial;
  }

  /* On a phone the row is a grid keyed on its *direct* children, and the swipe strip sits between
     the two. So the strip becomes the grid and takes the same template, and every direct-child
     rule below is restated for the strip's children. Without this, a swipeable row on a phone had
     no grid at all: the checkbox, title and badges collapsed into one flex line and the revealed
     buttons were painted straight over them. */
  /* Matched to the base rule's specificity, `.task-row .swipe-row-body`. A bare `.swipe-row-body`
     here loses regardless of order, so this phone grid never applied and swiped rows kept the
     desktop flex layout, badges collapsing under the buttons. Restating a rule is not enough — it
     has to match the specificity of the rule it replaces. */
  .task-row .swipe-row-body {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    column-gap: 10px;
    row-gap: 4px;
  }

  .task-row > .checkbox,
  .task-row > .avatar,
  .swipe-row-body > .checkbox,
  .swipe-row-body > .avatar {
    grid-column: 1;
    grid-row: 1;
  }

  .task-row > .task-meta {
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
  }

  .swipe-row-body > .task-meta {
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
  }

  .task-row > .task-meta:only-child {
    grid-column: 1 / -1;
  }

  .swipe-row-body > .task-meta:only-child {
    grid-column: 1 / -1;
  }

  .task-row > .task-time,
  .task-row > .badge,
  .task-row > .btn,
  .swipe-row-body > .task-time,
  .swipe-row-body > .badge,
  .swipe-row-body > .btn {
    grid-column: 2;
    justify-self: start;
    margin-top: 0;
    max-width: 100%;
  }

  .task-row > .btn:last-child {
    grid-column: 1 / -1;
    justify-self: end;
  }

  .swipe-row-body > .btn:last-child {
    grid-column: 1 / -1;
    justify-self: end;
  }

  .task-time {
    white-space: normal;
  }

  .task-meta audio,
  .task-meta img,
  .task-meta video {
    max-width: 100%;
  }

  .task-meta audio {
    width: 100%;
  }

  .recent-item,
  .insight-item {
    min-width: 0;
  }

  .recent-item > div:nth-child(2),
  .insight-item > div:last-child {
    min-width: 0;
  }

  .recent-time {
    white-space: normal;
    text-align: right;
  }

  .quote-actions {
    flex-wrap: wrap;
  }

  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
    scrollbar-width: none;
  }

  .tabs::-webkit-scrollbar {
    display: none;
  }

  .tab {
    flex: 0 0 auto;
    white-space: nowrap;
  }

  .settings-grid {
    grid-template-columns: minmax(0, 1fr) !important;
    gap: 14px;
  }

  .settings-aside {
    display: grid;
    grid-column: auto !important;
    grid-template-columns: minmax(0, 1fr) !important;
    gap: 14px;
  }
}

/* ---------- Onboarding demo deck ---------- */
.demo-overlay {
  position: fixed;
  inset: 0;
  padding:
    max(12px, env(safe-area-inset-top))
    max(12px, env(safe-area-inset-right))
    max(12px, env(safe-area-inset-bottom))
    max(12px, env(safe-area-inset-left));
  background: rgba(0, 0, 0, 0.45);
  display: none;
  align-items: flex-end;
  justify-content: center;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  z-index: 110;
}

/* Backdrop fills the overlay so clicks never reach the page behind the tour. */
.demo-backdrop {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.demo-overlay.open {
  display: flex;
}

/* Lock the body so the page behind cannot scroll or be clicked while the tour is open. */
body.demo-open {
  overflow: hidden;
}

@media (min-width: 640px) {
  .demo-overlay {
    align-items: center;
  }
}

.demo-card {
  position: relative;
  z-index: 2;
  width: min(560px, 100%);
  max-height: calc(100vh - 48px);
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-modal);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);
  padding: var(--pad-modal);
}

.demo-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.demo-title {
  font-size: var(--fs-title);
  font-weight: var(--fw-bold);
  color: var(--text);
}

.demo-status {
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
  color: var(--muted);
  background: var(--border);
  padding: 4px 10px;
  border-radius: 999px;
}

.demo-close {
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  padding: 8px;
  border-radius: var(--radius-ctl);
  display: flex;
}

.demo-close:hover {
  color: var(--text);
  background: var(--border);
}

.demo-slides {
  position: relative;
  min-height: 0;
}

.demo-slide {
  display: none;
  flex-direction: column;
  gap: 12px;
  padding: 8px 4px 20px;
  min-height: 0;
}

.demo-slide.active {
  display: flex;
}

.demo-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-ctl);
  background: var(--blue-bg);
  color: var(--blue-fg);
  flex: 0 0 auto;
}

.demo-slide h2 {
  margin: 0;
  font-size: var(--fs-title);
  font-weight: var(--fw-bold);
  color: var(--text);
}

.demo-eyebrow {
  margin: 0;
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
  color: var(--accent);
}

.demo-list {
  margin: 0;
  padding-left: 18px;
  color: var(--text);
  line-height: 1.55;
  font-size: var(--fs-body-s);
}

.demo-hint {
  margin: 0;
  font-size: var(--fs-meta);
  color: var(--muted);
  font-style: italic;
}

.demo-foot {
  margin-top: 16px;
}

.demo-dots {
  display: flex;
  gap: 8px;
  justify-content: center;
  margin-bottom: 14px;
}

.demo-dots-track {
  display: flex;
}

.demo-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border-radius: 999px;
  border: none;
  background: var(--border);
  cursor: pointer;
}

.demo-dot.active {
  background: var(--accent);
  width: 22px;
}

.demo-ctrls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

.demo-ctrls-sep {
  display: none;
}

.demo-ctrls-label {
  font-size: var(--fs-ui);
  font-weight: var(--fw-medium);
  color: var(--muted);
  min-width: 0;
  text-align: center;
}

.demo-bottom {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) repeat(2, minmax(0, 1fr));
  align-items: center;
  gap: 10px;
}

.demo-bottom-sep {
  display: none;
}

.demo-foot .btn {
  width: 100%;
  min-height: 42px;
  min-width: 0;
  padding: 8px 10px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-ctl);
  background: var(--card);
  color: var(--text);
}

.demo-foot .btn:hover {
  background: var(--bg);
  border-color: var(--accent);
}

.demo-foot .btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 480px) {
  .demo-ctrls {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 10px;
  }

  .demo-ctrls-label {
    display: none;
  }

  .demo-bottom {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
  }
}

/* ---------- Support tickets ---------- */
#view-support .cards {
  width: min(100%, 900px);
  margin: 0 auto;
}

#view-support > .cards > .card {
  padding: clamp(18px, 3vw, 32px);
}

#view-support .support-hero {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 20px;
  margin-bottom: 24px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-card);
  background: var(--bg);
}

#view-support .support-hero-icon {
  display: grid;
  place-items: center;
  flex: 0 0 52px;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-ctl);
  background: var(--blue-bg);
  color: var(--blue-fg);
}

#view-support .support-hero-icon svg {
  width: 25px;
  height: 25px;
  stroke-width: 1.8;
}

#view-support .support-hero-copy {
  min-width: 0;
}

#view-support .support-eyebrow {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: var(--fs-meta);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

#view-support .support-hero .page-sub {
  margin: 4px 0 0;
  line-height: var(--lh-base);
}

#view-support .form-card {
  padding: 20px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-card);
  background: var(--bg);
}

#view-support .card-title {
  margin: 0 0 14px;
  font-size: var(--fs-title);
  font-weight: var(--fw-semibold);
}

#view-support .divider {
  margin: 24px 0;
}

#view-support #ticketEmpty {
  margin-bottom: 12px;
}

#view-support #ticketList {
  display: grid;
  gap: 10px;
}

@media (max-width: 600px) {
  #view-support > .cards > .card {
    padding: 16px;
  }

  #view-support .support-hero {
    align-items: flex-start;
    gap: 12px;
    padding: 16px;
    margin-bottom: 18px;
  }

  #view-support .support-hero-icon {
    flex-basis: 44px;
    width: 44px;
    height: 44px;
  }

  #view-support .support-hero-icon svg {
    width: 22px;
    height: 22px;
  }

  #view-support .form-card {
    padding: 16px;
  }

  #view-support .divider {
    margin: 20px 0;
  }
}

.badge.normal {
  background: var(--border);
  color: var(--muted);
}

.badge.urgent {
  background: var(--red-fg);
  color: #fff;
}

.badge.open {
  background: var(--amber-bg);
  color: var(--amber-fg);
}

.badge.in_progress {
  background: var(--blue-bg);
  color: var(--blue-fg);
}

.badge.resolved {
  background: var(--green-bg);
  color: var(--green-fg);
}

.ticket {
  border: 1px solid var(--border);
  border-radius: var(--radius-modal);
  padding: 12px 14px;
  background: var(--card);
}

.ticket-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.ticket-title {
  font-weight: var(--fw-semibold);
  color: var(--text);
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

.ticket-actions {
  flex: 0 0 auto;
}

.ticket-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-meta);
  color: var(--muted);
  margin-top: 8px;
}

.ticket-meta time {
  font-variant-numeric: tabular-nums;
}

.ticket-updates {
  margin: 10px 0 0;
  padding-left: 18px;
  color: var(--muted);
  line-height: 1.55;
}

.ticket-update {
  display: flex;
  gap: 8px;
  align-items: baseline;
  flex-wrap: wrap;
}

.ticket-update-time {
  font-variant-numeric: tabular-nums;
  color: var(--border);
  font-size: var(--fs-meta);
}

.ticket-empty-note,
.ticket-updated {
  font-size: var(--fs-meta);
  color: var(--muted);
}

.ticket-update-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 10px;
}

.ticket-update-form textarea {
  flex: 1 1 100%;
  min-width: 0;
}

.ticket-update-form .btn {
  flex: 0 0 auto;
}

#ticketForm .field-row {
  display: grid;
  grid-template-columns: minmax(110px, 150px) minmax(0, 1fr);
  gap: 12px;
  align-items: center;
}

#ticketForm .field-row > :last-child {
  min-width: 0;
  text-align: left;
}

#ticketForm .field-row .capture-input {
  width: 100%;
}

#ticketSubmit {
  margin-top: 8px;
}

@media (max-width: 600px) {
  #ticketForm .field-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
  }

  #ticketSubmit {
    width: 100%;
  }
}

@media (max-width: 900px) {

  .settings-aside .hero-card {
    margin-bottom: 0;
  }

  .settings-tabs {
    width: 100%;
    min-width: 0;
    flex-wrap: nowrap;
  }

  .settings-tabs .tab-vert {
    flex: 0 0 auto;
    width: auto;
  }

  .profile-fields {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  .profile-summary {
    align-items: flex-start;
  }

  .settings-panel .card-head .btn,
  .settings-panel .field-row .btn {
    max-width: 100%;
  }

  .field-row {
    gap: 12px;
    flex-wrap: wrap;
  }

  .field-row > * {
    min-width: 0;
  }

  .field-row > .btn {
    margin-left: auto;
  }

  .shortcut-row {
    align-items: flex-start;
    flex-wrap: wrap;
  }

  .panel-snooze {
    flex-shrink: 0;
  }

  .recent-time {
    flex-shrink: 0;
    max-width: 30%;
  }

  .card-head > div {
    flex-wrap: wrap;
    justify-content: flex-end;
  }

  .card-head > div .btn {
    white-space: normal;
  }

  .task-time,
  .task-row > .badge {
    max-width: 100%;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .profile-summary > span {
    min-width: 0;
  }


  .calendar-toolbar,
  .calendar-controls {
    width: 100%;
  }

  .calendar-controls {
    justify-content: space-between;
  }

  .calendar-controls .tabs {
    margin-left: auto !important;
  }

  /* All seven date columns fit the screen, rather than being pushed off the right edge.

     This had min-width: 560px, which on a 375px phone left 37% of the grid past the edge. It was
     scrollable, so no overflow check ever complained, and it looked deliberate on a laptop. But
     nothing on a phone says there is more to the right: the week runs off the edge with no hint, and
     the only way to find Saturday is a horizontal swipe nobody thinks to try, across a grid whose
     natural gesture is vertical. Measured at seven widths, this was the one place the layout needed
     the person to know something the screen did not tell them.

     Seven columns in ~350px is about 50px each, which fits the date number comfortably. Losing
     breathing room is a far smaller cost than a date column that does not exist until you guess. */
  .cal-grid {
    width: 100%;
    min-width: 0;
  }

  .cal-grid,
  .cal-week-wrap {
    overscroll-behavior-x: contain;
  }

  .cal-week-wrap {
    width: 100%;
    max-width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .cal-week-days {
    min-width: 560px;
  }

  .cal-time-col {
    position: sticky;
    left: 0;
    background: var(--card);
    z-index: 2;
  }

  .modal-overlay {
    align-items: flex-end;
    padding: 12px max(10px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
  }

  .modal-overlay,
  .ask-overlay {
    padding-top: max(12px, env(safe-area-inset-top));
  }

  .modal {
    /* Narrower than the old 560px so the sheet does not span a 393px-wide phone, and capped
       against both the viewport height and the on-screen keyboard. */
    width: 100%;
    max-width: 420px;
    max-height: min(78vh, 620px);
    max-height: min(78dvh, 620px);
    margin: 0 auto;
    border-radius: var(--radius-sheet) var(--radius-sheet) var(--radius-tag) var(--radius-tag);
    padding: 18px 16px;
    padding-bottom: calc(18px + env(safe-area-inset-bottom));
  }

  .modal-capture {
    max-height: min(78vh, 620px);
    max-height: min(78dvh, 620px);
  }

  .modal-capture textarea {
    max-height: min(26dvh, 180px);
  }

  .modal > div[style*="grid-template-columns"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  .modal .type-row {
    gap: 6px;
  }

  #typeRow {
    /* One scrolling line instead of a two-column grid. With twelve kinds a grid was six rows — a
       third of the sheet — and every extra kind the app grows made it worse. The chips are a
       picker, not something to read, so a swipe is the right gesture and the height is now the same
       whatever the list contains. It also keeps the item count visible on a capture that produces
       more than one thing, which a taller picker pushed below the fold. */
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* Room for the last chip to sit clear of the edge, and no visible bar — it is a scroller the
       finger already knows about, and a 6px grey strip on a sheet is just clutter. */
    padding-bottom: 2px;
    margin-bottom: 12px;
    scrollbar-width: none;
  }

  #typeRow::-webkit-scrollbar {
    display: none;
  }

  .modal .type-chip {
    min-height: 38px;
    flex: 0 0 auto;
    scroll-snap-align: start;
  }

  #typeRow .type-chip {
    min-width: 0;
  }

  .modal textarea {
    min-height: 88px;
  }

  #voiceCaptureUI,
  #imageCaptureUI,
  #fileCaptureUI,
  #linkCaptureUI {
    min-width: 0;
  }

  #voiceCaptureUI {
    width: 100%;
  }

  #voiceDictationStatus {
    flex-basis: 100%;
  }

  #voiceTimer {
    display: inline-block;
    margin-left: 0 !important;
    margin-top: 6px;
  }

  #imageFileInput,
  #genericFileInput {
    max-width: 100%;
  }

  .modal-actions {
    gap: 8px;
  }

  .modal-actions .btn {
    min-width: 0;
  }

  .ask-overlay {
    align-items: flex-start;
    padding-top: max(12px, env(safe-area-inset-top));
  }

  .ask-box {
    width: 100%;
    max-height: calc(100vh - 24px);
    max-height: calc(100dvh - 24px);
    border-radius: var(--radius-lg);
  }

  .ask-results {
    max-height: calc(100vh - 100px);
    max-height: calc(100dvh - 100px);
  }

  .panel {
    width: 100%;
    max-width: 100%;
    height: 100vh;
    height: 100dvh;
    border-radius: var(--radius-none);
  }

  .panel-head {
    padding-top: max(16px, env(safe-area-inset-top));
  }

  .panel-body {
    padding: 18px 16px;
  }

  .panel-snooze {
    padding: 12px 16px;
  }

  .panel-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  }

  .panel-actions .btn {
    min-width: 0;
    padding-left: 8px;
    padding-right: 8px;
  }

  .panel-actions .btn:last-child {
    grid-column: 1 / -1;
  }

  .cal-cell {
    min-height: 64px;
    padding: 4px;
    font-size: var(--fs-label);
  }

  .cal-day-head {
    padding: 6px 4px;
    font-size: var(--fs-caption);
  }

  .cal-event {
    padding: 2px 3px;
    font-size: var(--fs-tiny);
  }

  .chart-wrap {
    gap: 3px;
  }

  .chart-bar {
    width: 6px;
  }
}

@media (max-width: 480px) {
  .topbar {
    padding-left: max(8px, env(safe-area-inset-left));
    padding-right: max(8px, env(safe-area-inset-right));
  }

  /* 40x40 rather than 34x38, which a 390px audit measured as too small to tap reliably.
     The 2px gain comes out of the topbar gap, so the search field keeps its width. */
  .topbar .icon-btn {
    width: 40px;
    height: 40px;
  }

  /* The capture mark is the one topbar element that is a logo rather than a control, so it is the
     only thing here that can shrink without costing a tap target or hiding a feature. 44x18 from
     50x20, and the button tracks it, so the SVG cannot overflow the hit area. */
  .mobile-capture-btn {
    width: 44px;
    height: 18px;
    min-width: 44px;
  }

  .mobile-capture-btn .capture-mark {
    width: 44px;
    height: 18px;
  }

  .topbar .avatar {
    width: 30px;
    height: 30px;
  }

  .content {
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
  }

  .card {
    padding: 14px;
  }

  .stat-row {
    gap: 8px;
  }

  .stat-card {
    padding: 10px 8px;
    gap: 6px;
  }

  .stat-icon {
    width: 22px;
    height: 22px;
  }

  .stat-num {
    font-size: var(--fs-stat);
  }

  .stat-label {
    font-size: var(--fs-label);
  }

  .modal-overlay {
    /* More side inset so the sheet reads as a card rather than a full-bleed panel. */
    padding-left: max(14px, env(safe-area-inset-left));
    padding-right: max(14px, env(safe-area-inset-right));
  }

  .modal {
    width: 100%;
    max-width: 400px;
    scrollbar-gutter: auto;
    padding-left: 14px;
    padding-right: 14px;
  }

  .modal-actions {
    justify-content: stretch;
  }

  .modal-actions .btn {
    flex: 1 1 0;
  }

  .panel-actions {
    grid-template-columns: minmax(0, 1fr);
  }

  .panel-actions .btn:last-child {
    grid-column: auto;
  }

  .panel-snooze .btn {
    flex: 1 1 calc(50% - 8px);
  }
}

/* ---------- Phase 2 task workflow ---------- */
.task-only[hidden] {
  display: none !important;
}

.task-status-badge.planned {
  background: var(--blue-bg);
  color: var(--blue-fg);
}

.task-status-badge.today {
  background: var(--accent);
  color: #fff;
}

.task-status-badge.in-progress {
  background: var(--purple-bg);
  color: var(--purple-fg);
}

.task-status-badge.waiting {
  background: var(--amber-bg);
  color: var(--amber-fg);
}

.task-status-badge.someday {
  background: var(--bg);
  color: var(--muted);
  border: var(--border-w) solid var(--border);
}

.task-status-badge.completed {
  background: var(--green-bg);
  color: var(--green-fg);
}

.task-row.archived {
  opacity: 0.78;
}

.task-row.archived .task-title {
  color: var(--muted);
}

.badge.archived {
  background: var(--bg);
  color: var(--muted);
  border: var(--border-w) solid var(--border);
}

.checklist-toggle:disabled,
.checklist-remove:disabled,
.task-select:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

.task-progress {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  color: var(--muted);
  font-size: var(--fs-meta);
}

.task-progress svg {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
}

.task-select {
  width: min(190px, 100%);
  padding: 8px 10px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-row);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-body);
}

.task-select:focus-visible,
.checklist-toggle:focus-visible,
.checklist-remove:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.task-workflow {
  margin-top: 18px;
  padding-top: 16px;
  border-top: var(--border-w) solid var(--border);
}

.task-workflow-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.task-workflow-head h3 {
  margin: 0;
  font-size: var(--fs-body);
  color: var(--muted);
}

.task-workflow-head > span {
  color: var(--muted);
  font-size: var(--fs-small);
  font-weight: var(--fw-semibold);
}

.checklist-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.checklist-empty {
  margin: 0 0 10px;
  padding: 10px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-row);
  font-size: var(--fs-small);
}

.checklist-row {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) 28px;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 7px 8px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-row);
  background: var(--bg);
}

.checklist-row.done .checklist-text {
  color: var(--muted);
  text-decoration: line-through;
}

.checklist-toggle,
.checklist-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.checklist-toggle {
  width: 20px;
  height: 20px;
  border: var(--border-w-strong) solid var(--border);
  border-radius: var(--radius-xs);
}

.checklist-toggle.checked {
  border-color: var(--green-fg);
  background: var(--green-fg);
  color: #fff;
}

.checklist-toggle svg,
.checklist-remove svg {
  width: 13px;
  height: 13px;
}

.checklist-toggle svg {
  stroke-width: 3;
}

.checklist-text {
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--fs-body);
  line-height: var(--lh-normal);
}

.checklist-remove {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
}

.checklist-remove:hover {
  background: var(--red-bg);
  color: var(--red-fg);
}

.checklist-add {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  margin-top: 10px;
}

.checklist-add .capture-input {
  margin-bottom: 0;
}

.checklist-add .btn {
  min-height: 40px;
}

.panel-actions {
  flex-wrap: wrap;
}

.panel-actions .btn {
  flex: 1 1 auto;
  min-width: 0;
}

@media (max-width: 900px) {
  .panel-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .panel-actions .btn {
    width: 100%;
  }

  .checklist-add {
    grid-template-columns: minmax(0, 1fr);
  }
}

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

  .panel-actions .btn:last-child {
    grid-column: auto;
  }

  .task-select {
    width: 100%;
  }
}

/* Capture plan, tightened for phones. A plan makes the capture sheet taller than one screen, so
   every row loses a little height to keep the review close to the fold. */
@media (max-width: 480px) {
  .capture-plan {
    margin-top: 8px;
    padding: 9px 10px;
  }

  .capture-plan-ask,
  .capture-plan-head,
  .capture-plan-agenda {
    margin-bottom: 6px;
    font-size: var(--fs-small);
  }

  .capture-plan-row {
    padding: 6px 0;
  }

  .capture-plan-row-bottom {
    margin-top: 4px;
  }

  .capture-plan-title {
    padding: 6px 8px;
    font-size: var(--fs-body-s);
  }

  .capture-plan-select,
  .capture-plan-due {
    min-height: 30px;
    padding: 4px 6px;
    font-size: var(--fs-small);
  }

  .capture-plan-remove {
    width: 28px;
    height: 28px;
  }

  .capture-plan-flag {
    margin-top: 4px;
    font-size: var(--fs-meta);
  }

  /* Clear of the bottom tab bar. */
  .capture-undo {
    bottom: 76px;
    font-size: var(--fs-body-s);
  }
}

@media (max-width: 335px) {
  .topbar {
    gap: 8px;
    padding-left: max(6px, env(safe-area-inset-left));
    padding-right: max(6px, env(safe-area-inset-right));
  }

  .search-box {
    padding: 9px 8px;
  }
}

/* ---------- Tablet: the width that had no owner ----------

   The breakpoints were 1180 / 900 / 480 / 381 / 335, and every one of them was either a phone or a
   laptop. That left 640px-900px with nothing: 768px — an iPad in portrait, a Surface, a Galaxy Tab —
   got the entire phone layout. Measured before this section existed:

       768px  stat-row=2 cols   grid-2=1 col   settings=1 col   content padding 18px/14px
       900px  stat-row=2 cols   grid-2=1 col   settings=1 col   content padding 18px/14px
      1024px  stat-row=4 cols   grid-2=2 cols  settings=2 cols  content padding 24px

   So the whole tier was phone-dense, and 1024px was the first width to breathe. It is not that the
   layout broke — nothing overflowed at any width, which is why no test had caught it. It is that a
   tablet was being served a phone's worth of information density on a screen twice the size: four
   stat cards stacked two-by-two, and a two-column dashboard collapsed to a single column.

   Settings is deliberately left alone. It already carries an '!important' single column at 900px,
   beating the inline grid on `#view-settings>div`, and undoing that would mean a second `!important`
   to cancel the first. At 640px+ its tab strip scrolls horizontally and the panel is already two
   columns, so it reads fine. The density that actually hurt was the stat row and the dashboard.

   The fix is a range rather than a new breakpoint, because the phone tier at 900px and the laptop tier
   above it are both already correct at their own widths. 640px is where a two-column split starts to
   earn its keep; below that a phone is right, and above 900px the desktop rules already apply.

   Deliberately still off-canonvas: the sidebar stays a drawer. 640px leaves ~410px of content beside a
   230px rail, and a rail that narrow is worse than a drawer, so tablets get the same menu a phone gets
   and a much wider canvas once it is open. Reusing the existing `.sidebar` and `.stat-row` rules is
   the whole point — nothing here is a second implementation, it is the same components told to use
   more of the room they have. */
@media (min-width: 640px) and (max-width: 900px) {
  /* Enough padding to read as a tablet without the desktop's 24px, which at 640px would eat a
     fifth of the width. */
  .content {
    padding: 22px 20px calc(32px + env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
  }

  .topbar {
    padding-left: max(20px, env(safe-area-inset-left));
    padding-right: max(20px, env(safe-area-inset-right));
  }

  .card {
    padding: 18px;
  }

  /* Four across, not two. 640px / 4 = 160px per card, which holds a stat number and its label
     without truncating either. */
  .stat-row {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
  }

  /* The dashboard's 2fr/1fr split back. The aside column lands at ~230px, enough for its chips. */
  .grid-2 {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  }


  /* A calendar cell is a touch target. 60px was a phone's worth of room for a finger that is
     driving a 768px screen with a stylus or a mouse. */
  .cal-cell {
    min-height: 78px;
  }

  /* Type scales with the canvas rather than staying at phone size on a tablet. The existing
     `--type-scale` token is the lever, so this moves every step of the ramp together and the
     concept overrides keep working. */
  :root {
    --type-scale: 1.06;
  }
}

/* Above 1180px the content grows without limit, and a 2560px monitor leaves text lines far too long
   to read comfortably. Capping the measure is the one place a max-width belongs; the sidebar stays
   pinned to the left edge so the window still reads as a workspace rather than a centred page. */
@media (min-width: 1600px) {
  .content > .view {
    max-width: 1400px;
    margin: 0 auto;
  }
}

@media (max-width: 600px) {
  .topbar {
    display: grid;
    grid-template-columns: 40px 44px minmax(0, 1fr);
    grid-template-areas:
      "menu brand actions";
    gap: 8px;
    align-items: center;
  }

  .topbar .search-wrap {
    display: none;
  }

  .topbar.is-search-open {
    grid-template-areas:
      "menu brand actions"
      "search search search";
  }

  .topbar.is-search-open .search-wrap {
    display: block;
    grid-area: search;
    width: 100%;
  }

  .topbar #hamburger {
    grid-area: menu;
  }

  .topbar .mobile-capture-btn {
    grid-area: brand;
  }

  .topbar .mobile-capture-btn {
    width: 44px;
    height: 40px;
    min-width: 44px;
  }

  .topbar .mobile-capture-btn .capture-mark {
    width: 44px;
    height: 26px;
  }

  .topbar-actions {
    grid-area: actions;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    width: 100%;
    min-width: 0;
  }

  .topbar-actions .mobile-search-toggle {
    display: flex;
  }

  .topbar-actions > .topbar-menu-wrap {
    display: grid;
    flex: 0 0 40px;
    width: 40px;
    height: 40px;
    place-items: center;
  }

  .topbar-actions .avatar {
    width: 40px;
    height: 40px;
  }

}

.mobile-search-toggle {
  display: none;
}
