/* Mobile-first, shared across index/confirm/dashboard/weekly. Touch targets
   are kept >= 44px per Apple/Google's minimum recommended hit area - this is
   a phone-first capture app (see ARCHITECTURE.md Part 3).

   Design tokens below are extracted from design/idea-inbox-ui (the "Blank
   Canvas v7" mockup approved as the visual reference - see MIGRATION_PLAN.md
   Section 4). Variable *names* were kept from the pre-existing sheet so every
   selector below and on every page keeps working unchanged; only *values*
   were updated to match the mockup, plus new tokens for things the mockup's
   monochrome palette doesn't define but kept features (delete button, warn
   banners, drawer nav, popout sheet, chips) need. */

:root {
  /* Surfaces & lines */
  --bg: #ececec;
  --surface: #ffffff;
  --soft: #f7f7f7; /* search/filter fills, hover rows */
  --hover: #ededed; /* icon/button hover */
  --border: #e3e3e3; /* hairline dividers */
  --border-strong: #d2d2d2; /* composer/chip borders */

  /* Text */
  --text: #202123;
  --text-dim: #777777;
  --text-faint: #9a9a9a;

  /* Primary action / active - the mockup is monochrome (black), no color accent */
  --accent: #111111;
  --accent-text: #ffffff;

  /* Semantic tokens the mockup doesn't define, added for kept functionality */
  --danger: #b3401f;
  --danger-text: #ffffff;
  /* Archive sits next to --danger in the swipe row - an amber, clearly not the
     red at a glance. */
  --archive: #f59e0b;
  --archive-text: #ffffff;
  --warn-bg: #fdf3e3;
  --warn-border: #e8c979;
  --warn-text: #664d03;

  /* Radii */
  --radius-sm: 10px; /* menu items, popout-close */
  --radius: 12px; /* inputs, icons, buttons */
  --radius-md: 14px; /* cards, menus */
  --radius-lg: 22px; /* sheet, popout */
  --radius-pill: 999px; /* pills, chips, toast */

  /* Shadows */
  --shadow-sm: 0 3px 14px rgba(0, 0, 0, 0.07);
  --shadow-menu: 0 10px 30px rgba(0, 0, 0, 0.12);
  --shadow-lg: 0 18px 50px rgba(0, 0, 0, 0.18);

  /* Type */
  --font: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --tracking-tight: -0.03em;

  --gap: 12px; /* kept: general spacing unit used throughout */
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

/* Belt-and-braces against sideways drift on iOS; the actual cause was the
   off-canvas .drawer (see there). Set on the *root*, deliberately not on body:
   overflow on the root propagates to the viewport and the root itself then
   computes to `visible`, so no new scroll container appears and
   `position: sticky` (the app header, the confirm footer) keeps sticking to
   the viewport. The same declaration on `body` is what would break sticky, by
   making body itself the scrollport. `hidden` rather than `clip` because
   WebKit doesn't reliably propagate `clip` from the root to the viewport. */
html {
  overflow-x: hidden;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.4;
  padding-bottom: 32px;
}

main {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--gap);
}

/* ---- App header / drawer nav (mockup's .top + slide-out .drawer) ----
   The 4 pages are plain static HTML with no server templating, so the
   drawer/scrim markup is injected once by app.js::renderChrome() from each
   page's existing <header><nav> links, rather than duplicated 4 times. The
   original inline nav links stay in the DOM (real <a href>, so no JS routing
   dependency) but are visually hidden in favor of the injected drawer. */

header.app-header {
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
  height: 62px;
  padding: 8px var(--gap);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
}

header.app-header h1 {
  grid-column: 2;
  font-size: 15px;
  font-weight: 600;
  text-align: center;
  margin: 0;
}

/* Original inline nav is kept in the DOM (renderChrome reads its <a> tags)
   but hidden - the drawer is the real, visible navigation now. */
header.app-header nav {
  display: none;
}

.icon {
  width: 42px;
  height: 42px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  display: grid;
  place-items: center;
  color: var(--text);
  padding: 0;
  cursor: pointer;
}

.icon:hover {
  background: var(--hover);
}

.hamb {
  width: 20px;
  height: 15px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.hamb span {
  height: 1.7px;
  background: currentColor;
  border-radius: 9px;
}

.scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.18);
  z-index: 20;
  display: none;
}

.scrim.open {
  display: block;
}

.drawer {
  position: fixed;
  inset: 0 auto 0 0;
  width: 82%;
  max-width: 300px;
  background: var(--soft);
  border-right: 1px solid var(--border);
  padding: 14px 12px;
  transform: translateX(-103%);
  /* Hidden (not just translated away) while closed. Parked off-canvas it sits
     ~300px left of the viewport, and iOS WebKit lets you pan to leftward
     overflow that desktop browsers clamp away - that was the sideways wobble,
     on every page. visibility also takes it out of the tab order and the
     screen-reader tree while closed. Delayed by the slide duration on close so
     the animation still plays; applied instantly on open. */
  visibility: hidden;
  transition: transform 0.22s, visibility 0s 0.22s;
  box-shadow: 10px 0 28px rgba(0, 0, 0, 0.08);
  z-index: 21;
  /* Column layout so .nav-footer's margin-top:auto can pin Settings to the
     bottom. Width and slide behaviour above are unchanged. */
  display: flex;
  flex-direction: column;
}

.drawer.open {
  transform: translateX(0);
  visibility: visible;
  transition: transform 0.22s, visibility 0s;
}

.drawer-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1px 2px 14px 8px;
}

.drawer-head strong {
  font-size: 14px;
}

.drawer nav.nav {
  display: grid;
  gap: 3px;
}

/* Settings lives in its own nav pushed to the bottom of the column, separated
   by a hairline. It keeps the .nav class, so every row style above applies
   unchanged - only its position in the drawer differs. */
.drawer nav.nav-footer {
  margin-top: auto;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}

/* Sign out is a <button> (an action, no URL), every other row an <a>. Both are
   listed on the row rules so they render identically; the button then only needs
   the global button chrome reset away, below. */
.drawer nav.nav a,
.drawer nav.nav button {
  display: flex;
  align-items: center;
  gap: 16px;
  /* Row height comes from min-height rather than vertical padding, so every
     row is exactly the same height and the active row's background can't end
     up taller than its neighbours. Horizontal padding is unchanged. */
  min-height: 58px;
  border-radius: 11px;
  padding: 0 10px;
  text-decoration: none;
  color: var(--text);
  font-size: 18px;
  font-weight: 500;
  line-height: 1.2;
}

/* The row rule above already wins on font-size, weight, padding, min-height and
   radius (it is the more specific selector), so this only has to undo the border
   and background the global `button` rule paints on. */
.drawer nav.nav button {
  width: 100%;
  border: none;
  background: none;
  text-align: left;
}

.drawer nav.nav a:hover,
.drawer nav.nav a.active,
.drawer nav.nav button:hover {
  background: var(--hover);
  font-weight: 600;
}

/* Mockup nav icons (.nav-mark family), sized/stroked like the reference. */
.drawer nav.nav .nav-mark {
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
  display: grid;
  place-items: center;
  color: var(--text);
}

.drawer nav.nav .nav-mark svg {
  /* Fills the 22px .nav-mark box above, so every icon renders at the same
     size and optical weight regardless of its artwork. */
  width: 22px;
  height: 22px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

h2 {
  font-size: 23px;
  letter-spacing: var(--tracking-tight);
  margin: 20px 0 8px;
}

label {
  display: block;
  font-size: 14px;
  color: var(--text-dim);
  margin: 14px 0 4px;
}

input,
select,
textarea {
  width: 100%;
  /* width:100% alone doesn't stop iOS from rendering date/datetime-local
     controls at their wider intrinsic size, which overflows the container. */
  max-width: 100%;
  font-size: 16px; /* prevents iOS auto-zoom on focus */
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--soft);
  color: var(--text);
  min-height: 44px;
}

textarea {
  min-height: 120px;
  resize: vertical;
}

button {
  font-size: 16px;
  font-weight: 600;
  min-height: 48px;
  min-width: 48px;
  padding: 12px 20px;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}

button.danger {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--danger-text);
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.row {
  display: flex;
  gap: var(--gap);
  align-items: center;
  flex-wrap: wrap;
}

.row > * {
  flex: 1;
  /* Flex items default to min-width:auto, so they refuse to shrink below their
     content's intrinsic width. iOS Safari gives date/datetime-local inputs a
     wide intrinsic minimum, so a two-input .row would push past the sheet's
     width instead of shrinking - which is what made the edit sheet scrollable
     sideways. min-width:0 lets them actually shrink. */
  min-width: 0;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--gap);
  margin-bottom: var(--gap);
}

.card h3 {
  margin: 0 0 4px;
  font-size: 14px;
  font-weight: 600;
}

.card ul {
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
}

.card li {
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.card li:first-child {
  border-top: none;
  padding-top: 0;
}

.card li:last-child {
  padding-bottom: 0;
}

.card li strong {
  display: block;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.35;
}

.card li .meta {
  color: var(--text-dim);
  font-size: 12px;
  margin-top: 3px;
}

/* Baskets header ("Weekly Picks" + generated-at subline + Regenerate). The
   title runs a step above the app's normal h2 so the page opens like a weekly
   summary rather than another list. */
.week-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--gap);
  padding: 6px 0 16px;
}

.week-head-text {
  min-width: 0; /* lets the title wrap instead of pushing Regenerate off-screen */
}

.week-head h2 {
  margin: 0 0 5px;
  font-size: 26px;
  line-height: 1.15;
}

.week-generated {
  margin: 0;
  color: var(--text-dim);
  font-size: 12px;
}

.regenerate {
  flex: 0 0 auto;
  min-height: 40px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  padding: 9px 14px;
  font-size: 13px;
  font-weight: 600;
}

.regenerate:hover {
  background: var(--soft);
}

.pill {
  display: inline-block;
  font-size: 12px;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  background: var(--soft);
  border: 1px solid var(--border);
  color: var(--text-dim);
  margin-inline-end: 6px;
}

.proposal {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: var(--radius-md);
  padding: var(--gap);
  margin: 10px 0;
}

.proposal .actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

.proposal .actions button {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  font-size: 12px;
  font-weight: 600;
}

/* Type/category combobox (app.js::renderProposalField). The drop-down is
   position:absolute so it never adds to layout width - important on the confirm
   page, whose only scroll is vertical (no sideways wobble). */
.combobox {
  position: relative;
  max-width: 100%;
}

.combobox-list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 20;
  margin: 0;
  padding: 4px;
  list-style: none;
  max-height: 240px;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* The list is meant to be scrolled with a thumb: allow vertical panning and
     momentum scrolling explicitly, so a long category list can be browsed. */
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-menu);
}

.combobox-list li {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* Only on real pointing devices: on touch, :hover sticks after a tap and would
   light up whatever row your thumb is dragging over, making a scroll look like
   a selection. */
@media (hover: hover) {
  .combobox-list li:hover {
    background: var(--soft);
  }
}

.combobox-list li:active {
  background: var(--soft);
}

/* Capture page hero (mockup's .home h2/p intro) */
.hero {
  text-align: center;
  padding: 28px 8px 8px;
}

.hero h2 {
  margin: 0 0 10px;
  font-size: 26px;
  letter-spacing: var(--tracking-tight);
}

.hero p {
  /* No cap: the 320px one wrapped the subhead onto two lines. The hero's own
     padding still bounds it (~353px on a 393px phone, enough for the sentence
     at 14px), and it stays centered because the text is shorter than the box.
     Deliberately not `white-space: nowrap` - that would overflow narrow screens
     and bring back the sideways scrolling fixed in c80af98. */
  max-width: none;
  margin: 0 auto;
  color: var(--text-dim);
  font-size: 14px;
  line-height: 1.55;
}

/* Bottom-anchored compact composer bar (mockup's .composer-wrap/.composer):
   a fixed bar at the bottom of the viewport - textarea on top, then a row
   with the mic + language on the left and Capture on the right. */
.capture-main {
  padding-bottom: 150px; /* room so the hero never hides behind the fixed bar */
}

.composer-wrap {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 8;
  max-width: 640px;
  margin: 0 auto;
  padding: 12px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
}

.composer-status {
  text-align: center;
  min-height: 0;
  margin: 0 0 6px;
}

.composer {
  padding: 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.composer textarea {
  width: 100%;
  height: 52px;
  min-height: 44px;
  max-height: 140px;
  resize: none;
  border: 0;
  outline: 0;
  background: transparent;
  padding: 6px 7px;
}

.composer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.composer-bottom .left,
.composer-bottom .right {
  display: flex;
  align-items: center;
  gap: 8px;
}

.capture-btn {
  min-width: 0;
  min-height: 42px;
  padding: 10px 18px;
  font-size: 14px;
}

/* Mic as the mockup's dark circular button with white waveform bars. */
.mic-button {
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  min-width: 42px;
  min-height: 42px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  display: grid;
  place-items: center;
}

.mic-button .wave {
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
}

.mic-button .wave i {
  display: block;
  width: 2px;
  background: #fff;
  border-radius: 99px;
  transition: height 0.15s ease;
}

.mic-button .wave i:nth-child(1) { height: 7px; }
.mic-button .wave i:nth-child(2) { height: 15px; }
.mic-button .wave i:nth-child(3) { height: 19px; }
.mic-button .wave i:nth-child(4) { height: 11px; }

/* Recording: red so the state is unmistakable, plus the mockup's bar shuffle. */
.mic-button.recording {
  background: var(--danger);
}

.mic-button.recording .wave i:nth-child(1) { height: 16px; }
.mic-button.recording .wave i:nth-child(2) { height: 8px; }
.mic-button.recording .wave i:nth-child(3) { height: 14px; }
.mic-button.recording .wave i:nth-child(4) { height: 19px; }

.status-text {
  color: var(--text-dim);
  font-size: 14px;
  min-height: 20px;
}

.status-text.error {
  color: var(--danger);
}

/* Capture-page progress animation (app.js::startStatusCycle). Slightly larger
   and in full body text rather than the muted grey the rest of .status-text
   uses - this one is the focus of the screen while you wait, not a footnote.
   Scoped to these two spans so error messages keep the muted/red treatment. */
.status-word,
.status-dots {
  /* `ui-rounded` resolves to SF Pro Rounded on iOS/macOS - a friendlier face
     than the app's Inter stack, and native, so there's no web-font download or
     flash of unstyled text for a message that's only on screen a few seconds.
     The named fallbacks cover platforms without it. */
  font-family: ui-rounded, "SF Pro Rounded", "Chalkboard SE", "Comic Sans MS", var(--font);
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
}

/* The dots get a fixed-width slot because .composer-status is centered -
   letting them grow from "." to "..." would shove the word sideways on every
   tick. In em, so it tracks the font-size above. */
.status-dots {
  display: inline-block;
  width: 1.5em;
  text-align: left;
}

/* The word progression alone still conveys the stage changes, so drop the
   per-400ms dot churn and just show them settled. */
@media (prefers-reduced-motion: reduce) {
  .status-dots {
    visibility: hidden;
  }

  .status-dots::before {
    content: "...";
    visibility: visible;
  }
}

/* Dashboard header (mockup's .dashboard-head) */
.dashboard-head {
  padding: 4px 0 14px;
}

.dashboard-head .status-text {
  margin: 0;
}

.flash {
  margin-top: 6px !important;
  padding: 8px 12px;
  border-radius: var(--radius);
  background: var(--soft);
  border: 1px solid var(--border);
}

/* ---- Dashboard: Blank Canvas white-list theme (mockup's dashboard page).
   Scoped to .dashboard-view (added to <body> by app.js::initDashboard) so the
   other pages keep the gray-card look until they're migrated too. The page
   sits on white, with hairline-separated list rows instead of floating cards. */
body.dashboard-view,
body.weekly-view {
  background: var(--surface);
}

body.dashboard-view main,
body.weekly-view main {
  background: var(--surface);
}

.dashboard-sub {
  margin: 0 0 4px;
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.5;
}

/* Three filter chips. flex-wrap (no overflow-x scroll) so Category / Importance
   / Status all fit on a phone screen. */
.dashboard-toolbar {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}

.filter-menu-wrap {
  position: relative;
}

/* Interval preset chips (confirm screen + edit sheet). Same chip styling as
   the dashboard toolbar - .active marks the selected cadence. */
.chip-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.filter-chip {
  min-height: 40px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  border-radius: var(--radius-pill);
  padding: 9px 14px;
  font-size: 12px;
  font-weight: 600;
}

.filter-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}

/* Category is the default grouping mode: filled when no filter is active. */
.filter-chip.category-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  cursor: default;
}

.filter-menu {
  position: absolute;
  z-index: 12;
  top: 46px;
  left: 0;
  min-width: 180px;
  display: none;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: var(--shadow-menu);
}

.filter-menu.open {
  display: block;
}

/* The "All" sort chip is the rightmost one, so its menu right-aligns to the
   chip instead of left-aligning (which would overflow the screen edge). */
#sortMenu {
  left: auto;
  right: 0;
}

.filter-menu button {
  width: 100%;
  min-height: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 10px;
  text-align: start;
  font-size: 12px;
  font-weight: 500;
}

.filter-menu button:hover,
.filter-menu button.selected {
  background: var(--soft);
}

/* Dashboard category rows: name on the left, idea count on the right, drilling
   into a popout. The Baskets page used to share these rules, which is exactly
   what made the two pages look alike - its rows now have their own styling
   further down (.basket-row) and this block is the dashboard's alone. */
.category-list {
  border-top: 1px solid var(--border);
}

.category-button {
  width: 100%;
  min-height: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: var(--surface);
  padding: 16px 2px;
  text-align: start;
}

.category-button:hover {
  background: var(--soft);
}

.category-button strong {
  font-size: 14px;
  font-weight: 600;
}

.category-button span {
  font-size: 11px;
  color: var(--text-dim);
}

/* ---- Baskets page: one featured card over a lighter descriptive list ----
   The split is the whole point: the dashboard is a uniform run of rows, so
   giving this page a single focal point plus a quieter secondary list is what
   makes it read as a weekly summary instead of another database view. Both
   are still plain buttons opening the same focus popout. */
.basket-featured {
  display: block;
  width: 100%;
  min-height: 0;
  padding: 20px 18px 18px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  font-family: inherit; /* the base button rule leaves the UA font in place */
  text-align: start;
}

.basket-featured-title {
  display: block;
  font-size: 23px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--tracking-tight);
}

.basket-featured-desc {
  display: block;
  margin-top: 8px;
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
}

/* Grid, not flex, so the three boxes stay equal width at 390px no matter how
   long the timing word runs. */
.basket-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 16px;
}

.basket-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 0;
  padding: 11px 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--soft);
}

.basket-stat b {
  font-size: 17px;
  font-weight: 600;
  line-height: 1.15;
}

.basket-stat span {
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 400;
  line-height: 1.2;
  text-align: center;
}

.basket-section-label {
  margin: 26px 0 0;
  color: var(--text-faint);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.basket-list {
  margin-top: 10px;
  border-top: 1px solid var(--border);
}

/* Name + descriptor subtitle + chevron - descriptive, where the dashboard's
   equivalent row is name + count. */
.basket-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 0;
  padding: 15px 2px;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  background: var(--surface);
  font-family: inherit;
  text-align: start;
}

.basket-row-text {
  flex: 1;
  min-width: 0; /* lets a long subtitle wrap rather than widen the row */
}

.basket-row strong {
  display: block;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
}

.basket-row-sub {
  display: block;
  margin-top: 3px;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
}

.basket-chevron {
  flex: 0 0 auto;
  color: var(--text-faint);
  font-size: 20px;
  line-height: 1;
}

@media (hover: hover) {
  .basket-featured:hover,
  .basket-row:hover {
    background: var(--soft);
  }
}

/* Hairline idea rows, shared by the flat filtered list and the category popout.

   The hairlines live on .swipe-row, not on .idea/.list, because .idea slides
   sideways when swiped and its separators must stay put. Same lines in the same
   places as before (n rows -> n+1 rules): ::before on the first row replaces
   what used to be `.list { border-top }`, ::after replaces
   `.idea { border-bottom }`. Both fade out as the row becomes a card. */
.swipe-row {
  position: relative;
  /* Clips the card's left overhang so a swiped row can't spill into the
     popout's padding or reintroduce horizontal overflow (see .sheet below). */
  overflow: hidden;
  /* First line of defence for vertical scrolling; app.js's explicit axis lock
     is the authority, because this alone isn't reliable on iOS Safari.
     pinch-zoom is kept so the row doesn't block accessibility zoom. */
  touch-action: pan-y pinch-zoom;
  /* A long press on a row otherwise starts an iOS text selection, and the
     selection gesture cancels the pointer mid-swipe. These rows are tap
     targets, not prose, so selection is given up for a reliable drag. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  --swipe-p: 0; /* 0..1 drag progress, written by app.js during the gesture */
}

.swipe-row::before,
.swipe-row::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--border);
  opacity: calc(1 - var(--swipe-p));
  pointer-events: none;
  z-index: 2;
}

.swipe-row::before {
  top: 0;
  /* Only the first row draws a top line - the rest use the row above's ::after. */
  display: none;
}

.list > .swipe-row:first-child::before {
  display: block;
}

.swipe-row::after {
  bottom: 0;
}

.idea {
  padding: 15px 2px 14px;
  cursor: pointer;
  position: relative;
  z-index: 1;
  /* Identity transform at rest: no radius, no ring, no background, so the
     resting row is pixel-identical to the pre-swipe design. */
  transform: translate3d(0, 0, 0);
  border-radius: 0;
  /* No grey flash under the finger when a swipe begins. */
  -webkit-tap-highlight-color: transparent;
}

/* Hover only where there's a real pointer. On a touchscreen :hover latches on
   first touch and stays until you tap elsewhere, which during a swipe reads as
   the row flashing grey/white/grey. */
@media (hover: hover) {
  .idea:hover {
    background: var(--soft);
  }
}

/* --- While the finger is down: the row becomes a card, progressively --- */

.swipe-row.swipe-dragging .idea,
.swipe-row.swipe-snapping .idea,
.swipe-row.swipe-open .idea {
  background: var(--surface);
  /* Inset rather than an outer shadow: the wrapper clips, and an outer shadow
     would be sliced off at the row's edges. */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, calc(var(--swipe-p) * 0.09));
}

.swipe-row.swipe-dragging .idea {
  border-radius: calc(var(--swipe-p) * 15px);
  /* No transition while dragging - the transform must track the finger exactly. */
  transition: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.swipe-row.swipe-dragging .idea * {
  pointer-events: none;
  -webkit-user-drag: none;
}

/* Applied only on release, so the spring easing never fights the drag. */
.swipe-row.swipe-snapping .idea {
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1),
    border-radius 0.28s cubic-bezier(0.32, 0.72, 0, 1),
    box-shadow 0.28s cubic-bezier(0.32, 0.72, 0, 1);
}

/* Resting-open and resting-closed radii come from the class, not from
   --swipe-p: an unregistered custom property is a token substitution and not
   every engine transitions dependent properties off it smoothly. */
.swipe-row.swipe-snapping .idea {
  border-radius: 0;
}

.swipe-row.swipe-open .idea {
  border-radius: 15px;
}

/* --- The two revealed actions, anchored behind the card's right edge --- */

.swipe-actions {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  display: flex;
  /* Hidden at rest so nothing is exposed (and nothing is focusable) until a
     gesture starts; app.js also manages tabindex. */
  visibility: hidden;
  z-index: 0;
}

.swipe-row.swipe-dragging .swipe-actions,
.swipe-row.swipe-snapping .swipe-actions,
.swipe-row.swipe-open .swipe-actions {
  visibility: visible;
}

.swipe-action {
  width: 64px; /* SWIPE_ACTION_WIDTH in app.js - keep the two in step */
  border: 0;
  padding: 0;
  display: grid;
  place-items: center;
  cursor: pointer;
  font: inherit;
  /* Every tile is rounded on all four corners so each reads as its own box
     rather than a slab of a single strip - same radius as the card. They sit
     flush against each other, so the curves meet at two small notches. */
  border-radius: 15px;
  /* Deliberately no opacity ramp off --swipe-p: the tiles are at full strength
     from the first pixel of the drag, so what you're uncovering is legible
     immediately rather than resolving as you pull. */
}

.swipe-action.edit {
  /* --border-strong rather than --hover: the lighter grey read as almost the
     same as the white card next to it. */
  background: var(--border-strong);
  color: var(--text);
}

.swipe-action.archive {
  background: var(--archive);
  color: var(--archive-text);
}

.swipe-action.delete {
  background: var(--danger);
  color: var(--danger-text);
}

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

.swipe-icon {
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
}

/* Same stroke-only icon family as the drawer nav marks. */
.swipe-icon svg {
  width: 22px;
  height: 22px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (prefers-reduced-motion: reduce) {
  .swipe-row.swipe-snapping .idea {
    transition: none;
  }
}

.idea strong {
  display: block;
  font-size: 14px;
  margin-bottom: 4px;
  line-height: 1.35;
}

.idea .meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
  color: var(--text-dim);
  font-size: 11px;
}

.idea .meta span + span::before {
  content: "·";
  margin-inline-end: 8px;
  color: var(--border-strong);
}

.idea .unconfirmed-flag {
  color: var(--danger);
  font-weight: 600;
  font-size: 12px;
  margin-bottom: 4px;
}

/* Focus popout: a category's ideas, opened over the dashboard. position:fixed
   (no phone-frame ancestor in production) and centered with the same max-width
   as the edit sheet. z-index below .sheet-backdrop (20) so tapping an idea in
   here opens the edit sheet above it. */
.focus-scrim {
  position: fixed;
  z-index: 13;
  inset: 0;
  background: rgba(255, 255, 255, 0.42);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}

.focus-scrim[hidden] {
  display: none;
}

.focus-popout {
  position: fixed;
  z-index: 14;
  left: 16px;
  right: 16px;
  top: 24px;
  bottom: 24px;
  max-width: 608px; /* 640 sheet width minus the 16px side insets */
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

.focus-popout[hidden] {
  display: none;
}

.popout-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 13px 11px;
  border-bottom: 1px solid var(--border);
}

.popout-title {
  flex: 1;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: normal;
}

.popout-body {
  padding: 8px 16px 20px;
  /* Same reasoning as .sheet below - `overflow: auto` is both axes. */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
}

.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(255, 255, 255, 0.5);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 20;
}

.sheet-backdrop[hidden] {
  display: none;
}

.sheet {
  background: var(--surface);
  width: 100%;
  max-width: 640px;
  max-height: 88vh;
  overflow-y: auto;
  /* Must be explicit. Per spec, overflow-x:visible next to a non-visible
     overflow-y computes to `auto` - so the sheet was silently horizontally
     scrollable, and on iOS the horizontal drift of a thumb swipe dragged it
     back and forth (the reported wobble). Vertical scrolling only. */
  overflow-x: hidden;
  /* Don't chain scroll to the page behind the sheet once it hits an end. */
  overscroll-behavior: contain;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-lg);
  padding: 0 var(--gap) 28px;
}

/* Popout-style head (mockup's .popout-head): title + close button in one
   row, replacing the old bottom-row Close button (app.js reads #close-sheet
   by id only, so relocating it in the DOM needed no JS change). */
.sheet-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 calc(var(--gap) * -1);
  padding: 13px var(--gap) 11px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--surface);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

.sheet-head #sheet-title {
  flex: 1;
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: normal;
}

.popout-close {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  min-width: 36px;
  min-height: 36px;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: var(--soft);
  font-size: 18px;
  line-height: 1;
}

.sheet .row {
  margin-top: 16px;
}

.empty-state {
  text-align: center;
  color: var(--text-dim);
  padding: 40px 20px;
}

/* ---- Confirm page (MIGRATION_PLAN.md Section 6 - net-new design, the
   mockup itself has no confirm page). Sections borrow the mockup's
   Settings-page grouping pattern (.settings-section: an h3 label over
   hairline-separated rows) as the "calm, grouped form" metaphor. Every
   element ID app.js reads (title/summary/idea-type-field/category-field/
   importance/target_date/reminder*/language/status) is unchanged - this is
   layout/CSS only, Tier A per the plan's fidelity tiers. */

.confirm-main {
  padding-bottom: 8px;
}

.hero-compact {
  padding: 16px 4px 4px;
  text-align: left;
}

.hero-compact h2 {
  font-size: 21px;
  margin: 0 0 4px;
}

.hero-compact p {
  margin: 0;
  max-width: none;
  text-align: left;
}

.confirm-section {
  padding: 14px 0 18px;
  border-bottom: 1px solid var(--border);
}

.confirm-section:last-of-type {
  border-bottom: none;
}

.confirm-section h3 {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.confirm-section label:first-of-type {
  margin-top: 10px;
}

/* Sticky action bar, echoing the mockup's persistent composer position
   without adopting the composer itself (locked decision - see
   MIGRATION_PLAN.md). Sits in normal document flow via `sticky`, not
   `fixed`, so it never overlaps the drawer/scrim's stacking context. */
.confirm-footer {
  position: sticky;
  bottom: 0;
  margin: 8px calc(var(--gap) * -1) 0;
  padding: 10px var(--gap) calc(var(--gap) + env(safe-area-inset-bottom, 0px));
  background: var(--bg);
  border-top: 1px solid var(--border);
}

.confirm-footer .status-text {
  margin: 0 0 8px;
}

.confirm-footer button {
  width: 100%;
}

/* Delete sits below Confirm as a low-emphasis text button - destructive, but
   never competing with the primary action for a thumb. */
.danger-link {
  margin-top: 8px;
  background: none;
  border: none;
  color: var(--danger);
  font-weight: 600;
  min-height: 40px;
}

/* ---- Placeholder pages (explore.html, settings.html) ----
   Centred in what's left of the viewport below the 62px sticky header. `dvh`
   second so iOS uses the dynamic viewport (avoiding the toolbar-collapse jump)
   while older engines keep the vh value. */
.placeholder-main {
  min-height: calc(100vh - 62px);
  min-height: calc(100dvh - 62px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.placeholder-main h2 {
  margin: 0;
  font-size: 26px;
  letter-spacing: var(--tracking-tight);
}

.placeholder-label {
  margin: 18px 0 6px;
  color: var(--text-dim);
  font-size: 14px;
}

/* Narrow measure so the description stays readable rather than running the
   full width of the phone. */
.placeholder-note {
  max-width: 340px;
  margin: 0 auto;
  color: var(--text);
  font-size: 14px;
  line-height: 1.6;
}

.placeholder-note p {
  margin: 0 0 10px;
}

.placeholder-note p:last-child {
  margin-bottom: 0;
}

/* Transient message for actions taken outside any panel - currently only a
   failed swipe-delete, whose error has no #sheet-status to land in. Above the
   sheet backdrop (20) so it is never buried. */
.toast {
  position: fixed;
  left: 50%;
  bottom: 26px;
  z-index: 40;
  transform: translate(-50%, 12px);
  max-width: min(88vw, 420px);
  padding: 11px 18px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-text);
  font-size: 13px;
  line-height: 1.35;
  text-align: center;
  box-shadow: var(--shadow-menu);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}

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

/* ---- Calendar view (the dashboard's Calendar chip) ----
   Monochrome, using the same tokens as the rest of the page - the reference
   mockup rendered in blue, but this app's language is --accent black. */

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

.calendar-head strong {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
}

/* Reuses .icon for the 42px hit area; only the glyph size differs. */
.calendar-nav {
  font-size: 22px;
  line-height: 1;
  border: 0;
  min-width: 42px;
}

.calendar-weekdays,
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
}

.calendar-weekdays {
  margin-bottom: 4px;
}

.calendar-weekdays span {
  text-align: center;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dim);
}

.calendar-day {
  /* Square cells keep the grid on one screen and stay well past the 44px
     minimum touch target on a phone. */
  aspect-ratio: 1;
  min-height: 0;
  min-width: 0;
  padding: 5px 4px 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 3px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 13px;
  font-weight: 500;
}

.calendar-date {
  line-height: 1.1;
}

/* Days spilling in from the neighbouring months stay visible but recede. */
.calendar-day.is-outside {
  opacity: 0.38;
}

/* Today is outlined, not filled - filling is reserved for the selection. */
.calendar-day.is-today {
  border-color: var(--accent);
}

.calendar-day.is-selected {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}

.calendar-dots {
  display: flex;
  align-items: center;
  gap: 3px;
  height: 7px;
}

.calendar-dots i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text);
}

/* The overflow count shown when a day has more reminders than dots. */
.calendar-dots em {
  font-size: 9px;
  font-style: normal;
  color: var(--text-dim);
}

.calendar-day.is-selected .calendar-dots i {
  background: var(--accent-text);
}

.calendar-day.is-selected .calendar-dots em {
  color: var(--accent-text);
}

/* Selected-day agenda, directly below the grid. */
.calendar-agenda {
  margin-top: 16px;
  padding: 14px 14px 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.calendar-agenda-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: 10px;
}

.calendar-agenda-head strong {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
}

.calendar-agenda-head span {
  font-size: 12px;
  color: var(--text-dim);
}

.calendar-empty {
  margin: 0;
  padding: 8px 0 16px;
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.5;
}

/* The optional "you can move a future reminder here" second line. */
.calendar-empty span {
  display: block;
  margin-top: 4px;
  color: var(--text-faint);
  font-size: 12px;
}

/* The agenda's leading time. Idea rows elsewhere don't set this. */
.idea .idea-time {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 2px;
}
