/* Opt-in surfaces: no padding, radius, type or motion defaults. Existing cards are unchanged.
   A local shadow variable lets hover preserve selected/drop rings without duplicating shadows. */
.surface[data-surface] {
  --surface-depth: 0 0 transparent;
  --surface-ring: 0 0 transparent;
  background: transparent; border: 1px solid transparent;
  box-shadow: var(--surface-ring), var(--surface-depth);
}
.surface[data-surface="outlined"], .surface[data-surface="elevated"] { background: var(--panel); border-color: var(--line); }
.surface[data-surface="elevated"] { --surface-depth: var(--shadow-card); }
.surface[data-surface]:is([aria-selected="true"], .is-selected, .is-drop-target) { --surface-ring: inset 0 0 0 2px var(--accent); }
.surface[data-hover-elevation] { transition: box-shadow var(--hover-dur) var(--ease); }
.surface[data-lift="true"] { transition: transform var(--lift-dur) var(--ease), box-shadow var(--lift-dur) var(--ease); }
@media (hover: hover) and (pointer: fine) {
  .surface[data-surface][data-hover-elevation="quiet"]:hover { --surface-depth: var(--shadow-card-quiet); }
  .surface[data-surface][data-hover-elevation="raised"]:hover { --surface-depth: var(--shadow-card-hover); }
  .surface[data-lift="true"]:hover { transform: var(--lift); }
}
@media (prefers-reduced-motion: reduce) {
  .surface[data-hover-elevation], .surface[data-lift="true"] { transition: none; }
  .surface[data-lift="true"]:hover { transform: none; }
}
/* Blend only neutral note chrome. Semantic chips, pressed controls and photo overlays keep their treatments. */
.surface[data-controls="blend"] .chip.static:not(.on, .bad, .ok, .out) { background: transparent; color: inherit; }
.surface[data-controls="blend"] .icon-btn:not(.solid, .glass, .ico-toggle, .is-on, .pressed, :disabled, [aria-pressed="true"]):hover {
  background: var(--control-hover); color: inherit;
}
.surface[data-controls="blend"] .icon-btn:is(.is-on, .pressed, [aria-pressed="true"]):not(.solid, .glass, .ico-toggle):hover {
  background: var(--accent-soft); color: var(--accent-text);
}
/* Settings composition: spacing belongs to sections, not collapsing margins on unrelated children.
   Panels choose their own .surface variant. Rows have no implicit separators or elevation. */
.settings-stack { display: grid; align-content: start; gap: var(--space-6); }
.settings-section { display: grid; min-width: 0; gap: var(--space-2); }
.settings-section[hidden] { display: none; }
.settings-heading { margin: 0 var(--space-1); font-size: var(--text-2xs); font-weight: 600;
  color: var(--muted); text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.settings-stack .settings-panel { margin: 0; min-width: 0; border-radius: var(--radius-md); }
.settings-stack .settings-help, .settings-stack .settings-status { margin: 0 var(--space-1); font-size: var(--text-sm); color: var(--muted); line-height: 1.45; }
.settings-section > :is(.settings-status, .settings-help):empty { display: none; }
.settings-rows { display: grid; gap: var(--space-1); padding: var(--space-2); }
.settings-row { display: flex; min-height: calc(var(--icon) + var(--space-3) * 3); align-items: center; gap: var(--space-3); min-width: 0; width: 100%;
  padding: var(--space-3); border: 0; border-radius: var(--radius-sm); background: transparent; text-align: left; color: inherit; }
.settings-row > .grow { min-width: 0; flex: 1; }
.settings-row-action { flex: 0 0 calc(var(--icon) + var(--space-3)); }
.settings-row > small { color: var(--muted); font-size: var(--text-md); }
.settings-rows > .settings-add { justify-self: start; margin: var(--space-1) var(--space-3); }

/* The strike-through: one look everywhere (done to-dos and list items, old prices, sold-out chips). A 1.3px line in
   the text's color with round tips, .07em below the middle of the capitals. The browser's line-through can't have round
   tips, so it's drawn as a background: two dots for the tips and a bar between them, repeated on every line of
   wrapped text (box-decoration-break: clone). Put it on inline text (a <span>), not a block or flex item, which
   would get one line at its top. .completed-text is the same thing under the name done text uses.
   Text only: never redraw or change the shared input.fill checkbox. */
.strike, .completed-text {
  --strike-top: calc(var(--strike-cap) + var(--strike-drop) - var(--strike-width) / 2);
  text-decoration: none;
  background:
    radial-gradient(closest-side, currentColor 92%, transparent) left var(--strike-top) / var(--strike-width) var(--strike-width) no-repeat,
    radial-gradient(closest-side, currentColor 92%, transparent) right var(--strike-top) / var(--strike-width) var(--strike-width) no-repeat,
    linear-gradient(currentColor, currentColor) center var(--strike-top) / calc(100% - var(--strike-width)) var(--strike-width) no-repeat;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
/* A text field can't draw on its own text, so a done field gets a see-through copy of its text laid over it that
   draws only the line: <div class="strike-field"><textarea></textarea><div class="strike-copy" aria-hidden="true">
   <span class="strike">same text</span></div></div>. Keep the copy's text in step with the field's (on input).
   The copy takes the field's font, padding, and wrapping; its letters are invisible. */
.strike-field { position: relative; display: grid; }
.strike-field > * { grid-area: 1 / 1; }
.strike-field > .strike-copy { pointer-events: none; white-space: pre-wrap; overflow-wrap: anywhere; -webkit-text-fill-color: transparent;
  font: inherit; color: inherit; border: 0 solid transparent; user-select: none; }

/* =====================================================================
   Shared components: the page base, top bar, buttons, dialogs, form fields, toasts, the Appearance controls
   (theme picker, color picker, sliders) and scroll bars. Built only from tokens.css.
   Apps add their own layout and anything specific to them in their own stylesheet, loaded after this one.
   ===================================================================== */

/* ---------- page ---------- */
* { box-sizing: border-box; }
html, body { margin: 0; }
/* The glow is drawn once, over at least the whole window. Left to repeat, it started over wherever a short page
   (or one still loading) ended, which drew hard horizontal lines across the background. */
html { min-height: 100%; }
body {
  background:
    radial-gradient(1200px 600px at 10% -10%, var(--glow-1), transparent 60%),
    radial-gradient(900px 500px at 100% 0%, var(--glow-2), transparent 60%),
    var(--bg);
  background-repeat: no-repeat;
  color: var(--text);
  font: var(--text-base)/1.45 var(--font);
}
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--accent-text); text-decoration: none; }
/* a plain text link (or .link): accent, underlined on hover. Links styled as buttons, chips, icons, or menu rows have a class and keep their own look */
a:not([class]):hover, a:not([class]):focus-visible, a.link:hover, a.link:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
.link { color: var(--accent-text); text-decoration: none; cursor: pointer; }
button.link { border: 0; background: none; padding: 0; font: inherit; }
svg { fill: currentColor; }
[hidden] { display: none !important; }
.spacer { flex: 1; }

/* ---------- top bar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 20; min-height: var(--topbar-h);
  display: flex; align-items: center; gap: var(--topbar-gap);
  padding: var(--topbar-pad-y) var(--topbar-pad-x);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
/* the glass top bar: liquid glass (liquid-glass.js, see .glass below) stretched edge to edge, square like every top
   bar: what scrolls under it shows through, bent at its bottom edge, with light along that edge instead of a line. */
.topbar.glass {
  --glass-drop-c: var(--bg); --glass-drop-a: 80%;
  background: none; border-bottom-color: transparent;
  -webkit-backdrop-filter: blur(1.8px) saturate(1.12); backdrop-filter: blur(1.8px) saturate(1.12);   /* until the lens is drawn */
  box-shadow: 0 8px 24px -12px color-mix(in srgb, var(--glass-drop-c) var(--glass-drop-a), transparent);
}
:root[data-light] .topbar.glass { --glass-drop-a: 53%; }
.brand { display: flex; align-items: center; gap: 10px; margin-right: var(--space-2); }
.brand-mark {
  display: grid; place-items: center; width: var(--brand-size); height: var(--brand-size); border-radius: var(--radius-sm); flex: none;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 70%, white), var(--accent)); color: var(--accent-ink);
  font-size: var(--text-lg);
  box-shadow: 0 6px 20px color-mix(in srgb, var(--accent) 25%, transparent);
}
.brand-mark .mark-ico, .app-mark .mark-ico { width: 100%; height: 100%; }   /* icoMark() fits the icon to 55% of the square itself */
.brand-name { font-family: var(--serif); font-size: var(--title); letter-spacing: .2px; }
/* top bar icons: round, with a see-through highlight on hover (the same as icons in an .icon-group), so it suits a frosted
   or glass bar and whatever shows through it */
.topbar .icon-btn { border-radius: var(--radius-pill); transition: background var(--hover-dur) var(--ease), color var(--hover-dur); }
.topbar .icon-btn:hover { background: var(--control-hover); }
.topbar > .tb-search { display: none; }   /* the phone's search button (select.js topbarPhone); .topbar > beats .icon-btn's display */
/* On a phone the top bar is the same in every app, whatever it holds (select.js topbarPhone does the search button):
   row 1  brand · search button · main action (compact) · ☰
   row 2  the page tabs (.seg, .viewnav), scrolling sideways when they don't fit
   row 3  the search box, only while it's open (.search-open) */
@media (max-width: 760px) {
  .topbar { flex-wrap: wrap; gap: 10px var(--space-2); }
  .topbar::after { content: ""; order: 4; flex-basis: 100%; }           /* ends row 1 */
  .topbar > .brand { order: 0; min-width: 0; margin-right: auto; }
  .topbar > .brand .brand-btn { min-width: 0; }   /* only the name is cut off: clipping the brand would cut the mark's glow into a box */
  .topbar .brand-name { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .topbar > .tb-search { order: 1; display: grid; }
  .topbar > .search[hidden] + .tb-search { display: none; }
  .topbar > .btn.primary { order: 2; height: var(--size-6); padding: 0 12px; font-size: var(--text-md); }
  .topbar > .pop-wrap:not(.brand) { order: 3; }
  .topbar > .seg, .topbar > .viewnav { order: 5; min-width: 0; max-width: 100%; overflow-x: auto; scrollbar-width: none; }
  .topbar > .seg { flex: 1 1 auto; }
  .topbar > .seg::-webkit-scrollbar, .topbar > .viewnav::-webkit-scrollbar { display: none; }
  .topbar > .seg button { flex: 1 0 0; min-width: max-content; padding: 6px 12px; }   /* equal widths; a long label keeps its own */
  .topbar > .viewnav { flex: none; margin: 0; }
  .topbar > .search { order: 6; flex: 1 1 100%; max-width: none; margin: 0; }
  .topbar:not(.search-open) > .search { display: none; }
  .topbar > .spacer { display: none; }                                   /* desktop-only pushers */
}
/* ☰ first, ⋯ last (DESIGN.md): `.topbar.menu-first`, for an app whose ☰ opens the side drawer. Markup: #menuBtn,
   .brand, .spacer, .search, the main action, then .pop-wrap > ⋯ (the app menu). On a phone it stays one row:
   ☰ · the search box, filling the bar (a page with no search shows its name there instead) · main action · ⋯.
   No search button: the box is always there (select.js topbarPhone leaves these bars alone). */
.topbar.menu-first > :is(#menuBtn, .pop-wrap) { flex: none; }
@media (max-width: 760px) {
  .topbar.menu-first { flex-wrap: nowrap; gap: var(--space-1); }
  .topbar.menu-first::after { content: none; }
  .topbar.menu-first > #menuBtn { order: -1; }
  .topbar.menu-first > .brand { flex: 1; margin-right: 0; }
  .topbar.menu-first > .brand .brand-mark { display: none; }              /* ☰ leads the bar; the page's name is enough */
  .topbar.menu-first:has(> .search:not([hidden])) > .brand { display: none; }
  .topbar.menu-first > .search { order: 0; display: block; flex: 1 1 auto; min-width: 0; }
  .topbar.menu-first > .tb-search { display: none; }
}

/* ---------- segmented control (Movies | TV, Places | Flights) ---------- */
.seg { display: inline-flex; box-sizing: border-box; height: var(--bar-h); background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 3px; }
.seg button { border: 0; background: none; height: calc(var(--bar-h) - 8px); padding: 0 16px; border-radius: var(--radius-pill); cursor: pointer; color: var(--muted); font-weight: 500; white-space: nowrap; }
.seg button.active { background: var(--panel-2); color: var(--text); box-shadow: inset 0 0 0 1px var(--line); }
.seg.small { --bar-h: var(--size-5); }
.seg.small button { padding: 0 12px; font-size: var(--text-sm); }
/* stacked: the choices one above the other, each as wide as the widest (AM over PM beside a time's wheels).
   The highlight slides up and down the same way (select.js places it by the button's top and height). */
.seg.vertical { display: inline-flex; flex-direction: column; align-items: stretch; height: auto; border-radius: var(--radius-lg); }
.seg.vertical button { border-radius: var(--radius-md); text-align: center; }
.seg.vertical > .seg-thumb { border-radius: var(--radius-md); }
/* the highlight slides: select.js puts one .seg-thumb in every .seg and moves it to the .active button (no app code) */
.seg { position: relative; }
.seg-thumb { position: absolute; top: 0; left: 0; border-radius: var(--radius-pill); background: var(--panel-2); box-shadow: inset 0 0 0 1px var(--line);
  pointer-events: none; transition: transform var(--motion-slide) var(--ease), width var(--motion-slide) var(--ease), height var(--motion-slide) var(--ease), opacity var(--hover-dur); }
.seg-thumb.instant { transition: none; }
.seg.has-thumb > button { position: relative; transition: color var(--motion-slide) var(--ease); }
.seg.has-thumb > button.active { background: none; box-shadow: none; }

/* ---------- switch: on / off with no words (select.js flips it on click and sends a "toggle" event, detail.on).
   <button class="switch" role="switch" aria-checked="false" aria-label="…"></button>  · .small · :disabled
   The knob slides across and, while held, stretches a little toward where it will go. ---------- */
.switch { --w: 44px; --h: 26px; position: relative; flex: none; width: var(--w); height: var(--h); padding: 0; border: 0; border-radius: var(--radius-pill);
  cursor: pointer; background: color-mix(in srgb, var(--text) 18%, transparent); transition: background var(--motion-slide) var(--ease); }
.switch::after { content: ""; position: absolute; top: 3px; left: 3px; width: calc(var(--h) - 6px); height: calc(var(--h) - 6px); border-radius: var(--radius-pill);
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transition: transform var(--motion-slide) var(--ease), width var(--motion-slide) var(--ease); }
.switch[aria-checked="true"] { background: var(--accent); }
.switch[aria-checked="true"]::after { transform: translateX(calc(var(--w) - var(--h))); }
.switch:active::after { width: calc(var(--h) + 2px); }
.switch[aria-checked="true"]:active::after { transform: translateX(calc(var(--w) - var(--h) - 8px)); }
.switch:disabled { opacity: .4; cursor: default; }
.switch:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; }
.switch.small { --w: 36px; --h: 22px; }
/* a setting with its switch: the words on the left, the switch on the right. It's a <label>, so clicking the words flips
   the switch too: <label class="switch-row"><span>Shade hills</span><button class="switch" role="switch" aria-checked="false"></button></label> */
.switch-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; cursor: pointer; font-size: var(--text-md); font-weight: 400; color: var(--text); }
.switch-row > span { min-width: 0; }
.stack > .seg { align-self: flex-start; }

/* ---------- tab strip: small underlined tabs inside a card or preview page (tabs.js) ---------- */
.tabstrip { display: flex; gap: 18px; box-shadow: inset 0 -1px 0 var(--line); overflow-x: auto; scrollbar-width: none; }   /* a shadow, not a border: the tabs' 2px bar sits over it without being clipped by the scrolling */
.tabstrip::-webkit-scrollbar { display: none; }
.tabstrip button { flex: none; border: 0; background: none; padding: 8px 0 6px; cursor: pointer; color: var(--muted);
  font-size: var(--text-sm); font-weight: 500; white-space: nowrap; display: inline-flex; align-items: center; gap: 6px;
  border-bottom: 2px solid transparent; }
.tabstrip button:hover { color: var(--text); }
.tabstrip button.on { color: var(--text); border-bottom-color: var(--accent); }
.tabstrip button:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; border-radius: var(--radius-xs); }
.tabstrip button .ui-ico { width: 14px; height: 14px; }
.tabstrip button small { color: var(--muted); font-size: var(--text-2xs); font-weight: 500; }
.tab-pane[hidden] { display: none; }
.tab-empty { color: var(--muted); font-size: var(--text-sm); padding: 6px 0; }

/* ---------- search box in the top bar ---------- */
.search { flex: 1; max-width: 420px; margin-left: auto; position: relative; }
.search > .search-ico { position: absolute; left: 12px; top: 50%; display: grid; transform: translateY(-50%); color: var(--faint); pointer-events: none; }
.search > .search-ico .ui-ico { width: var(--icon-sm); height: var(--icon-sm); }
.search input {
  width: 100%; box-sizing: border-box; height: var(--bar-h); padding: 0 12px 0 36px; border-radius: var(--radius-pill);
  background: var(--panel); border: 1px solid var(--line); outline: none;
}
.search input:focus { border-color: var(--accent-line); }
.search input::-webkit-search-cancel-button { display: none; }   /* one clear button everywhere: the phone's ✕ (and Esc) */

/* ---------- buttons ---------- */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); box-sizing: border-box; height: var(--size-7);
  border: 1px solid var(--line); background: var(--panel); padding: 0 16px; border-radius: var(--radius-sm); cursor: pointer; font-weight: 600; white-space: nowrap; color: var(--text); }
a.btn { display: inline-flex; align-items: center; text-decoration: none; }
.btn:hover { background: var(--panel-2); }
.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
.btn.primary:hover { filter: brightness(1.08); }
.btn.primary:disabled, .btn:disabled { opacity: .4; cursor: default; }
.btn.ghost { background: transparent; }
.btn.danger { color: var(--bad-text); background: transparent; }
.btn.danger-solid { background: var(--bad); color: #fff; border-color: transparent; }
.btn.danger-solid:not(.glass):hover { filter: brightness(1.08); background: var(--bad); }   /* glass has its own hover */
.btn.small { height: var(--size-5); padding: 0 10px; font-size: var(--text-sm); border-radius: var(--radius-sm); font-weight: 500; }
/* an icon beside a button's text: centered on the box like the text (its span is a box, not a line of text, which
   set it 1.4px high), at the size for the button's height (beside text: 16 in 40, 14 in 32) */
:is(.btn, .tool) > [data-ico] { display: grid; flex: none; }
:is(.btn, .tool) :is(.ui-ico, .ui-ico.inline) { width: var(--icon-sm); height: var(--icon-sm); flex: none; vertical-align: 0; }
.btn.small :is(.ui-ico, .ui-ico.inline) { width: var(--icon-xs); height: var(--icon-xs); }
.icon-btn { border: 0; background: none; cursor: pointer; color: var(--muted); padding: 6px; border-radius: var(--radius-sm); display: grid; place-items: center; }
/* one hover highlight for every control that's only an icon (icon buttons, a group's icons, the top bar's, the
   stepper's, the drag handle, page numbers): --control-hover (the text color at 10%) behind it, the icon in the text color.
   Inside a pill (.icon-group, .stepper) the highlight is 2px in from the pill's edge: its size minus 4. */
.icon-btn:hover { color: var(--text); background: var(--control-hover); }
.icon-btn svg, .icon-btn .ui-ico { width: var(--icon); height: var(--icon); }

/* ---------- backdrops: two looks for a control's surface. .solid is opaque; .glass is liquid glass (a lens that bends
   what's behind it at the rim), for controls that float over pictures or scrolling content.
   They go on an icon button (a 40px disc, .square for a rounded square), on an .icon-group (several icons in one
   capsule), and on a .btn (Save, Cancel…: the material changes, the shape stays). .primary and .danger-solid glass is
   tinted with their own color. ---------- */
/* a drag handle (a row you can reorder): the grip-vertical icon in a round press target, faint until hovered; grab
   cursor. <span class="drag-handle" aria-hidden="true">ico("grip-vertical")</span> */
.drag-handle { flex: none; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; color: var(--faint); cursor: grab; transition: background var(--hover-dur), color var(--hover-dur); }
.drag-handle .ui-ico, .drag-handle svg { width: 16px; height: 16px; }
@media (hover: hover) { .drag-handle:hover { color: var(--text); background: var(--control-hover); } }
.drag-handle:active { cursor: grabbing; color: var(--text); background: color-mix(in srgb, currentColor 16%, transparent); }
.icon-btn.solid, .icon-btn.glass {
  width: 40px; height: 40px; padding: 0; border-radius: var(--radius-pill); color: var(--text);
}
.icon-btn.square, .icon-group.square { border-radius: var(--radius-md); }
/* Circles are drawn 10% wider than their slot (--circle-grow in tokens.css): a circle looks smaller than a square of the
   same width. --slot is the size it takes in the layout (what a square would be); the negative margin gives the extra
   back, so rows, grids, and absolute positions don't move and the center stays put. The icon or number inside keeps
   its size. Circles inside a pill (a .stepper's buttons) and tiny dots and handles are left alone. */
.icon-btn.solid:not(.square), .icon-btn.glass:not(.square), .topbar .icon-btn:not(.square, .solid, .glass, .icon-group > *), .drag-handle, .score-badge {
  flex: none; width: calc(var(--slot) * var(--circle-grow)); height: calc(var(--slot) * var(--circle-grow));
  margin: calc(var(--slot) * (1 - var(--circle-grow)) / 2); padding: 0; box-sizing: border-box;
}
.icon-btn.solid, .icon-btn.glass { --slot: 40px; }
.topbar .icon-btn { --slot: calc(var(--icon) + 12px); }   /* the 6px padding round its icon */
.drag-handle { --slot: 28px; }
.score-badge { --slot: 40px; } .score-badge.small { --slot: 32px; }
.icon-btn.solid, .icon-btn.glass, .btn.glass, .fab, .icon-group > .icon-btn {
  transition: background var(--hover-dur) var(--ease), transform var(--hover-dur) var(--ease), color var(--hover-dur), filter var(--hover-dur);
}
.icon-btn.solid, .icon-group.solid { background: var(--panel-2); box-shadow: inset 0 0 0 1px var(--line); }
/* .glass is liquid glass, drawn by liquid-glass.js (icons.js loads it on every page): a lens that bends what's behind
   it at the rim. Until the lens is drawn (the first frame), the same blur, tint, and shadow without the bend. */
.icon-btn.glass, .btn.glass, .fab.glass, .icon-group.glass {
  border-color: transparent; background: none;
  -webkit-backdrop-filter: blur(1.8px) saturate(1.12); backdrop-filter: blur(1.8px) saturate(1.12);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .1), 0 0 0 .5px rgba(0, 0, 0, .05);
}
/* hover: the same see-through highlight as an icon in an .icon-group, fading in over the backdrop (behind the icon or
   label). Every glass piece, tinted ones (Save, Delete, the floating plus) too. The plus keeps its own position. */
:is(.icon-btn.solid, .icon-btn.glass, .btn.glass) { position: relative; }
:is(.icon-btn.solid, .icon-btn.glass, .btn.glass, .fab.glass) { isolation: isolate; }
:is(.icon-btn.solid, .icon-btn.glass, .btn.glass, .fab.glass)::after {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  background: color-mix(in srgb, var(--text) 10%, transparent); opacity: 0; transition: opacity var(--hover-dur) var(--ease);
}
:is(.icon-btn.solid, .icon-btn.glass, .btn.glass, .fab.glass):hover::after { opacity: 1; }
/* an option: data-glass-lift on a glass piece, or on anything round it (a toolbar, a page, <body>), lifts the glass
   --glass-lift (2px) as it's hovered, over the same time as the highlight. It moves by translate, so the press still
   sinks it (icons.js's press is a transform). */
:is(.icon-btn, .btn, .fab, .icon-group).glass:is([data-glass-lift], [data-glass-lift] *) {
  transition: background var(--hover-dur) var(--ease), transform var(--hover-dur) var(--ease), translate var(--hover-dur) var(--ease),
    color var(--hover-dur), filter var(--hover-dur);
}
@media (hover: hover) and (pointer: fine) {
  :is(.icon-btn, .btn, .fab, .icon-group).glass:is([data-glass-lift], [data-glass-lift] *):hover { translate: var(--glass-lift); }
}
@media (prefers-reduced-motion: reduce) {
  :is(.icon-btn, .btn, .fab, .icon-group).glass:is([data-glass-lift], [data-glass-lift] *):hover { translate: none; }
}
/* data-glass-lift="only": the lift alone, with no highlight (an app that wants the glass to stay clear on hover) */
:is(.icon-btn.glass, .btn.glass, .fab.glass):is([data-glass-lift="only"], [data-glass-lift="only"] *)::after { content: none; }
/* tinted glass (.primary, .danger-solid, the floating plus): its color (--glass) laid on through the lens. */
:is(:root, :root[data-light]) :is(.btn.primary, .fab).glass { --glass: var(--accent); color: var(--accent-ink); }
:is(:root, :root[data-light]) .btn.danger-solid.glass { --glass: var(--bad); color: #fff; }
:is(.btn.primary, .btn.danger-solid, .fab).glass:not(.lq-glass) { background: color-mix(in srgb, var(--glass) 48%, transparent); }
:is(.btn.primary, .btn.danger-solid, .fab).glass:hover { filter: none; }
/* the lens drawn (liquid-glass.js): it bends what's behind at the rim, splits it into a faint rainbow there, and blurs
   it smoothly. The script puts the lens (.lq-copybox) and one fixed light from the top left (.lq-light) under the
   piece's content, so the piece itself paints only its shadow. */
:is(:root, :root[data-light]) :is(.icon-btn, .btn, .fab, .icon-group, .topbar).glass.lq-glass:not(.lq-fallback) {
  --lq-z: -1; isolation: isolate;
  background: none; -webkit-backdrop-filter: none; backdrop-filter: none;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .1), 0 0 0 .5px rgba(0, 0, 0, .05);
}
.lq-light {
  position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  box-shadow: inset 1px 1px 1px -.5px rgba(255, 255, 255, .38), inset -1px -1px 1px -.5px rgba(255, 255, 255, .14);
  background: radial-gradient(130% 85% at 26% 6%, rgba(255, 255, 255, .2), rgba(255, 255, 255, .05) 42%, transparent 62%);
}
/* the top bar: edge to edge, so no glint on its face; light along its bottom edge, and its own soft shadow below */
:is(:root, :root[data-light]) .topbar.glass.lq-glass:not(.lq-fallback) {
  box-shadow: 0 8px 24px -12px color-mix(in srgb, var(--glass-drop-c) var(--glass-drop-a), transparent);
}
.topbar > .lq-light { background: none; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), inset 0 -1px 0 rgba(255, 255, 255, .22); }
/* ---------- scroll edges (scrollEdges() in scrollbars.js): iOS 26's scroll edge effect. Where a list scrolls under
   floating controls or off its box's top, it blurs more and more toward the edge (a light blur over the whole band, a
   stronger one over the half nearest the edge) and fades into the page's color (--edge-c, --bg: white in light themes,
   black in dark). Shown only while there's more of the list beyond that edge; fades in and out. ---------- */
.scroll-edge {
  --edge-c: var(--bg);
  position: absolute; left: 0; right: 0; z-index: 10; height: var(--edge-h, 64px); pointer-events: none;
  opacity: 0; transition: opacity var(--hover-dur) var(--ease);
  background: linear-gradient(var(--edge-dir), color-mix(in srgb, var(--edge-c) 86%, transparent),
    color-mix(in srgb, var(--edge-c) 50%, transparent) 45%, transparent);
}
.scroll-edge.on { opacity: 1; }
.scroll-edge.top { top: 0; --edge-dir: to bottom; }
.scroll-edge.bottom { bottom: 0; --edge-dir: to top; }
.scroll-edge::before, .scroll-edge::after { content: ""; position: absolute; inset: 0; }
.scroll-edge::before {
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  -webkit-mask-image: linear-gradient(var(--edge-dir), currentColor 40%, transparent); mask-image: linear-gradient(var(--edge-dir), currentColor 40%, transparent);
}
.scroll-edge::after {
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  -webkit-mask-image: linear-gradient(var(--edge-dir), currentColor, transparent 55%); mask-image: linear-gradient(var(--edge-dir), currentColor, transparent 55%);
}
@media (prefers-reduced-transparency: reduce) { .scroll-edge::before, .scroll-edge::after { display: none; } }

/* ---------- the floating plus (.fab, like Google Keep's): a page's add action, floating over its content at the
   bottom right. A --fab-size rounded square (--radius-lg) of accent-tinted glass (.glass; without it, solid accent)
   with a 26px plus: <button class="fab glass" aria-label="New note">ico("plus")</button>. It sits --fab-gap in from
   the corner, clear of the home bar; it's fixed to the window, or an app can set it in a column (position: absolute,
   so it stays in that pane and above a phone's keyboard). A list it floats over keeps --fab-room clear at its end, so
   the last row can scroll out from under it. ---------- */
.fab {
  position: fixed; z-index: 20; right: calc(var(--fab-gap) + env(safe-area-inset-right)); bottom: calc(var(--fab-gap) + env(safe-area-inset-bottom));
  width: var(--fab-size); height: var(--fab-size); padding: 0; display: grid; place-items: center;
  border: 0; border-radius: var(--radius-lg); cursor: pointer; color: var(--accent-ink);
}
.fab:not(.glass) { background: var(--accent); box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--accent) 60%, transparent); }
.fab:not(.glass):hover { filter: brightness(1.08); }
.fab[hidden] { display: none; }
.fab .ui-ico { width: var(--icon-lg); height: var(--icon-lg); }
.fab:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 3px; }
/* several icons in one capsule (a bar of related actions); each is a plain .icon-btn inside it */
.icon-group { display: inline-flex; align-items: center; box-sizing: border-box; height: var(--size-7); padding: 2px; border-radius: var(--radius-pill); }
.icon-group > .icon-btn { width: var(--size-6); height: var(--size-6); padding: 0; border-radius: var(--radius-pill); color: var(--text); }
.icon-group > .icon-btn:is(.is-on, .pressed) { background: none; }   /* on is the accent icon alone: the capsule is its backdrop */
.icon-group > .icon-btn:hover { background: var(--control-hover); }
:is(.icon-btn.solid, .icon-btn.glass, .icon-group > .icon-btn):is(.is-on, .pressed) { color: var(--accent-text); }

/* ---------- toggle icons (icons.js iconToggle / data-ico-toggle): on is the filled icon in accent, nothing behind it
   (on a solid or glass backdrop, the backdrop stays); turning on, the icon pops: a quick grow past full size and back ---------- */
.icon-btn.ico-toggle.is-on:not(.solid, .glass) { background: none; border-color: transparent; }
.icon-btn.ico-toggle.is-on:not(.solid, .glass):hover { background: color-mix(in srgb, var(--text) 10%, transparent); }   /* keeps its accent icon, so not currentColor */
.ico-pop > svg { animation: ico-pop var(--pop-dur) var(--ease); }
@keyframes ico-pop { 40% { transform: scale(var(--pop-scale)); } 70% { transform: scale(.94); } }
/* turning off: the outline settles in from a little smaller and faded, so off reads as a step too */
.ico-unpop > svg { animation: ico-unpop calc(var(--pop-dur) * .75) var(--ease); }
@keyframes ico-unpop { from { transform: scale(.7); opacity: .4; } }

/* ---------- press physics (icons.js): every .btn and .icon-btn, plus anything with .ripple (a tile, a card); .no-ripple
   leaves one out. It behaves like a soft surface:
   pressing: it sinks (--dent: --press-scale on buttons, a little more for icons in a group, --ripple-dent on tiles) and
     tips toward your finger (--press-tilt, from --tx / --ty: where you pressed, -.5 to .5), while a soft glow of the
     text color swells under your finger for as long as you hold;
   letting go: the glow rushes out to the far edges and fades, a thin wave ring runs out ahead of it, and the surface
     springs back past its size (--press-spring) and settles. Speeds: --ripple-dur, strength: --ripple-strength.
   The glow and the wave are drawn in ::before (nothing is added inside the button), sized by the registered properties
   below so they can animate. A control that already has its own transform (.ripple-flat, set by icons.js) gets the
   glow only, so it doesn't jump. ---------- */
@property --ink-r { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --ink-a { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --wave-r { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --wave-a { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --wave-w { syntax: "<length>"; inherits: false; initial-value: 2px; }
.btn, .icon-btn { --dent: var(--press-scale); }
.icon-group > .icon-btn { --dent: calc(var(--press-scale) - .06); }
.ripple { position: relative; isolation: isolate; }
.ripple-host { isolation: isolate; --tx: 0; --ty: 0;
  --press-tf: perspective(600px) rotateX(calc(var(--ty) * var(--press-tilt) * -1)) rotateY(calc(var(--tx) * var(--press-tilt))) scale(var(--dent, var(--ripple-dent))); }
.ripple-host.ripple-static { position: relative; }   /* only while it moves, so the glow has a box to fill */
.ripple-host::before {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  --ink-a: var(--ripple-strength);
  background:
    radial-gradient(circle at var(--rx) var(--ry), transparent calc(var(--wave-r) - var(--wave-w)),
      color-mix(in srgb, currentColor calc(var(--wave-a) * 100%), transparent) calc(var(--wave-r) - var(--wave-w) / 2), transparent var(--wave-r)),
    radial-gradient(circle at var(--rx) var(--ry), color-mix(in srgb, currentColor calc(var(--ink-a) * 100%), transparent),
      color-mix(in srgb, currentColor calc(var(--ink-a) * 60%), transparent) calc(var(--ink-r) * .7), transparent var(--ink-r));
}
.ripple-down::before { --ink-r: calc(var(--rmax) * .32); transition: --ink-r 1.2s cubic-bezier(.05,.7,.2,1); }
.ripple-up::before { --ink-r: var(--rmax); --ink-a: 0;
  transition: --ink-r var(--ripple-dur) cubic-bezier(.2,.8,.2,1), --ink-a var(--ripple-dur) cubic-bezier(.6,0,.8,.6);
  animation: ripple-wave calc(var(--ripple-dur) * 1.3) cubic-bezier(.15,.75,.3,1) forwards; }
@keyframes ripple-wave {
  0% { --wave-r: calc(var(--rmax) * .12); --wave-a: 0; --wave-w: 4px; }
  12% { --wave-a: calc(var(--ripple-strength) * 2.4); }
  100% { --wave-r: var(--rmax); --wave-a: 0; --wave-w: .5px; }
}
.ripple-down:not(.ripple-flat) { animation: ripple-sink .18s cubic-bezier(.3,.7,.4,1) forwards; }
@keyframes ripple-sink { to { transform: var(--press-tf); } }
.ripple-up:not(.ripple-flat) { animation: ripple-spring calc(var(--ripple-dur) * 1.1) linear; }
@keyframes ripple-spring {
  0% { transform: var(--press-tf); animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  40% { transform: perspective(600px) scale(var(--press-spring)); animation-timing-function: ease-in-out; }
  70% { transform: perspective(600px) scale(calc(1 - (var(--press-spring) - 1) * .35)); animation-timing-function: ease-out; }
  100% { transform: perspective(600px) scale(1); }
}
@media (prefers-reduced-motion: reduce) { :is(.ico-pop, .ico-unpop) > svg { animation: none; } }

/* ---------- pressed / selected: one look for every on-off control (toggles, Yes/No, toolbar buttons) ---------- */
.pressed, .is-on { color: var(--accent-text); border-color: var(--accent-line); background: var(--accent-soft); }

/* ---------- chips: small pills for tags, filters, and choices ---------- */
.chip {
  display: inline-flex; align-items: center; gap: 4px; box-sizing: border-box; height: var(--size-4); border: 1px solid var(--line); background: var(--panel); color: var(--muted);
  border-radius: var(--radius-pill); padding: 0 11px; font: 500 var(--text-sm) var(--font); cursor: pointer; white-space: nowrap;
  max-width: 100%; min-width: 0; overflow: hidden;   /* never wider than the space it's in (its .chip-t ends in "…") */
}
.chip:not(.static):hover { color: var(--text); border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.chip.on { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent-text); border-color: var(--accent-line); }
.chip small { color: var(--muted); font-size: 1em; margin-left: 2px; }
/* not available (sold out), still clickable: plain gray and faded, crossed out by the strike (.strike: 1.3px, round tips; close
   to the letters' own stroke, so it crosses the word instead of covering it), .07em below the middle of the capitals.
   The line hangs on .chip-t, which is trimmed to the capitals, so 50% of it is their middle. A chip whose text isn't in
   a .chip-t falls back to the browser's own line-through at the same weight. */
.chip.out { color: var(--faint); background: none; opacity: .6; text-decoration: line-through var(--strike-width); }
.chip.out:has(> .chip-t) { text-decoration: none; }
.chip.out > .chip-t { position: relative; }
.chip.out > .chip-t::after {
  content: ""; position: absolute; left: 0; right: 0; top: calc(50% + var(--strike-drop)); height: var(--strike-width); transform: translateY(-50%);
  background: currentColor; border-radius: 99px; pointer-events: none;
}
.chip.out:hover { opacity: 1; }
.chip.out.on { color: var(--accent-text); opacity: 1; }
.chip.on small { color: inherit; opacity: .7; }
/* A chip's text in .chip-t (chipHTML() in select.js draws it): ends in "…" when there's no room, and is centered on
   its capital letters, not its line box (Safari sets the line box higher than Chrome, which left icons looking low).
   The padding gives accents and descenders back the room the trim takes, so the chip keeps its height. */
.chip-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; text-box: trim-both cap alphabetic; padding-block: .25em; }
.chip > .ui-ico, .chip > [data-ico] > .ui-ico { width: var(--icon-xs); height: var(--icon-xs); flex: none; }
.chip > [data-ico] { display: grid; flex: none; }
.chip-x { position: relative; flex: none; display: grid; place-items: center; width: 1.25em; height: 1.25em; margin: -.1em -6px -.1em 0; border: 0; background: none; border-radius: var(--radius-pill); color: var(--muted); cursor: pointer; }
.chip-x:hover { color: var(--bad-text); }
/* its tap area reaches 24px (WCAG 2.2 target size) without growing the ✕ or the chip */
.chip-x::after { content: ""; position: absolute; inset: -4px; }
.chip-x .ui-ico { width: .9em; height: .9em; }
/* a chip that only shows something (a reminder, a label, a status): not clickable, no hover. Warnings and alerts: */
.chip.static { cursor: default; }
.chip.bad { color: var(--bad-text); border-color: color-mix(in srgb, var(--bad) 45%, transparent); background: color-mix(in srgb, var(--bad) 10%, transparent); }
.chip.ok { color: var(--ok-text); border-color: color-mix(in srgb, var(--ok) 45%, transparent); background: color-mix(in srgb, var(--ok) 10%, transparent); }
/* a row of chips that picks one (Places' years, Shopping's categories) */
.pills { display: flex; gap: 6px; flex-wrap: wrap; }
.pills.scroll { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.pills.scroll::-webkit-scrollbar { display: none; }
/* tags (select.js tagEditor): a .tag-chip is also a .chip. Yours are dashed with ✕; "+ add tag" is a dashed box that
   widens while you type. .tags is the row. Entertainment draws these too (its app.css has the same rules). */
.tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.tag-chip.mine { padding: 0 4px 0 0; border-style: dashed; cursor: default; }
.tag-chip.mine > button, .tag-chip.mine > .tag-name { border: 0; background: none; color: inherit; font: inherit; padding: 4px 4px 4px 11px; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tag-chip.mine > button { cursor: pointer; }
.tag-chip.mine .x { padding: 4px 6px; font-size: var(--text-2xs); color: var(--muted); cursor: pointer; }
.tag-chip.mine .x:hover { color: var(--bad-text); }
.tag-add { box-sizing: border-box; height: var(--size-4); border: 1px dashed var(--line); background: none; border-radius: var(--radius-pill); padding: 0 11px; font: 500 var(--text-sm) var(--font); width: 110px; outline: none; color: var(--text); }
.tag-add::placeholder { color: color-mix(in srgb, var(--faint) 55%, transparent); }
.tag-add:focus { border-color: var(--accent); border-style: solid; width: 160px; }
.tag-combo .cs-pop { min-width: 200px; }

/* ---------- small-caps label above a group (menu titles, tile headings, list counts) ---------- */
.eyebrow { font-size: var(--text-2xs); font-weight: 600; letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--muted); }

/* ---------- empty states ---------- */
.empty { text-align: center; color: var(--muted); padding: 60px 16px; }
.empty .big, .empty > b { display: block; font-family: var(--serif); font-size: var(--title); font-weight: 600; color: var(--text); margin-bottom: 6px; }
.empty .btn { margin-top: 12px; }

/* ---------- busy spinner on an icon button (↻ while it works) ---------- */
.icon-btn.busy .ui-ico, .icon-btn.busy > svg { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- icons (design/icons.js) ---------- */
.ui-ico { width: var(--icon); height: var(--icon); display: block; }
.chev.up .ui-ico { transform: rotate(180deg); }   /* an open section: its chevron points up */
.ui-ico.inline { display: inline-block; width: 1em; height: 1em; vertical-align: -.14em; flex: none; }   /* in a line of text, at its size */

/* ---------- dialogs ---------- */
.modal {
  border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 0; width: min(var(--dialog-w), calc(100vw - 32px));
  max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); overflow-y: auto; overscroll-behavior: contain;   /* dvh: a phone's address bar doesn't hide the foot */
  background: var(--bg-2); color: var(--text); box-shadow: var(--shadow);
}
.modal::backdrop { background: rgba(4,5,8,.52); backdrop-filter: blur(4px); }
.modal[open] { animation: pop .22s var(--ease); }
@keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.98); } }
dialog.modal:focus, dialog.modal:focus-visible { outline: none; }
/* Focus rings only for the keyboard: after a tap or click (html.by-pointer, theme-boot.js) a ring that code's focus would
   draw stays hidden; Tab or an arrow key brings rings back. A ring drawn some other way (a box-shadow) is
   written `html:not(.by-pointer) .x:focus-visible` for the same reason. */
html.by-pointer :focus-visible { outline: none !important; }
.modal.small { width: min(var(--dialog-sm), calc(100vw - 32px)); }   /* sizes: --dialog-sm / -w / -lg in tokens.css */
.modal.wide { width: min(var(--dialog-lg), calc(100vw - 32px)); }
body:has(dialog[open]) { overflow: hidden; }   /* freeze the page behind a dialog */
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: 18px 22px 0; }
.modal-head h2 { font-family: var(--serif); margin: 0; font-size: var(--title); }
.modal-body { padding: 18px 22px; display: flex; flex-direction: column; gap: var(--space-4); }
.modal-foot { display: flex; justify-content: flex-end; align-items: center; gap: 10px; padding: 0 22px 20px; }
.modal-foot .spacer { flex: 1; }
/* a long dialog you scroll (a recipe, a title): Cancel / Save float in reach at the bottom right, with no bar behind
   them: the page scrolls under the buttons, which carry their own fill and shadow. The empty foot still takes its
   room at the end, so nothing is left under the buttons once you've scrolled to the bottom. */
.modal-foot.sticky { position: sticky; bottom: 0; z-index: 3; padding-top: 14px; padding-bottom: 16px; pointer-events: none; }
.modal-foot.sticky > * { pointer-events: auto; }
.modal-foot.sticky .btn { box-shadow: var(--shadow-card); }
.modal-foot.sticky .btn:not(.primary):not(.danger) { background: var(--bg-2); }
.modal.confirm { width: min(var(--dialog-sm), calc(100vw - 32px)); }
.modal.confirm .modal-body { padding: 24px 24px 8px; gap: var(--space-2); }
.modal.confirm h2 { font-family: var(--serif); font-size: var(--text-lg); margin: 0; line-height: 1.25; }
.modal.confirm p { margin: 0; color: var(--muted); line-height: 1.5; font-size: var(--text-md); white-space: pre-line; }
.modal.confirm .modal-foot { padding: 16px 24px 22px; }
/* on a phone: the page's 16px edge inside dialogs too, and the buttons wrap rather than run off the side */
@media (max-width: 760px) {
  .modal-head { padding: 16px 16px 0; }
  .modal-body { padding: 16px; }
  .modal-foot { padding: 0 16px 16px; flex-wrap: wrap; }
  .modal-foot.sticky { padding-top: 12px; padding-bottom: 12px; }
}

/* ---------- form fields ---------- */
/* A phone zooms the whole page into any text box under 16px when you tap it (iPhone), so on touch screens every
   box is at least 16px. 16px is the browser's own limit, not a design size. */
@media (hover: none) and (pointer: coarse) {
  input:not([type=checkbox], [type=radio], [type=range]), textarea, select { font-size: max(16px, var(--text-base)); }
}
.field { width: 100%; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 10px 12px; outline: none;
  line-height: calc(var(--text-base) * 1.45); }   /* a fixed line, so a text area's rows are the same on every screen */
.field:not(textarea) { box-sizing: border-box; height: var(--size-7); padding-block: 0; }   /* one line (a text box, a drop-down box): 40, like a button */
/* A time (type="time", the one browser picker allowed) looks like a text box: an iPhone otherwise draws its own, taller,
   text centered, with a width of its own that ignores its column. Tapping it still opens the system's time wheel. */
input.field[type="time"] { -webkit-appearance: none; appearance: none; display: block; min-width: 0; text-align: left; }
input.field[type="time"]::-webkit-date-and-time-value { margin: 0; text-align: left; }
.field:focus { border-color: var(--accent-line); }
/* The three text boxes, one look (every one is a .field):
     Text box         <input class="field">                 one line
     Note             <textarea class="field">              three lines, fixed size; scrolls inside once there's more
     Expandable note  <textarea class="field grow">         three lines to start; drag the corner taller, never shorter
   --lines sets how many lines a note starts at (default 3): style="--lines:9" for a recipe's ingredients.
   22px = the field's padding (10 + 10) and border (1 + 1). */
textarea.field { --lines: 3; display: block; line-height: 1.5; height: calc(var(--lines) * 1.5em + 22px); resize: none; overflow-y: auto; }
textarea.field.grow { resize: vertical; min-height: calc(var(--lines) * 1.5em + 22px); }
/* placeholders are hint text, never mistaken for something typed: --faint blended halfway into the box behind it, so it
   is lighter than any text on every theme (the browser's own gray is darker, and differs per browser) */
::placeholder { color: color-mix(in srgb, var(--faint) 55%, transparent); opacity: 1; }
textarea.field:placeholder-shown { overflow: hidden; }   /* the browser measures the placeholder a line too tall */
select { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 10px; outline: none; min-width: 0; }
select:focus { border-color: var(--accent-line); }
select.field { padding: 10px 12px; }
/* Checkboxes: a see-through box with a line border on every theme; ticked = accent border and an accent tick, still see-through.
   Sizes at 1×: square 16px, circle 17.6px (10% wider: --circle-grow, since a circle looks smaller than a square of the
   same width), tick 11px in both. --check-scale grows box and tick together; the standard steps are .lg (1.25×) and
   .xl (1.5×). Other round ✓ buttons (Music's "seen") use the same --check-* sizes so they match. */
:root { --check-square: 16px; --check-circle: calc(var(--check-square) * var(--circle-grow)); --check-tick: 11px; }
input[type="checkbox"] { --check-scale: 1; --check-size: var(--check-square);
  appearance: none; -webkit-appearance: none; flex: none; width: calc(var(--check-size) * var(--check-scale)); height: calc(var(--check-size) * var(--check-scale)); margin: 0; cursor: pointer;
  background: transparent; border: 1.5px solid color-mix(in srgb, var(--text) 40%, transparent); border-radius: var(--radius-xs);
  display: inline-grid; place-content: center; transition: border-color var(--hover-dur); }
input[type="checkbox"]:hover { border-color: var(--accent); }
input[type="checkbox"]:checked { border-color: var(--accent); }
/* the tick: an accent stroke with rounded ends and a rounded corner, cut from the shape below (so it takes any theme's accent) */
input[type="checkbox"]::after { content: ""; width: calc(var(--check-tick) * var(--check-scale)); height: calc(var(--check-tick) * var(--check-scale)); background: var(--accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3 4.9 8.6 9.4 3.6' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3 4.9 8.6 9.4 3.6' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  transform: scale(0); transition: transform .12s; }
input[type="checkbox"]:checked::after { transform: scale(1); }
/* four kinds, chosen by class on the checkbox (or on its .check label):
   square see-through (the default) · .fill square filled: ticked = accent fill, tick in --accent-ink
   .round circle see-through (marking a thing done or seen) · .round.fill circle filled */
/* sizes: 1× by default; .lg 1.25× (Music's "seen" ✓), .xl 1.5×, on the checkbox or its .check label */
:is(input.round, .round > input)[type="checkbox"] { --check-size: var(--check-circle); border-radius: 50%; }
:is(input.lg, .lg > input)[type="checkbox"] { --check-scale: 1.25; }
:is(input.xl, .xl > input)[type="checkbox"] { --check-scale: 1.5; }
:is(input.fill, .fill > input)[type="checkbox"]:checked { background: var(--accent); }
:is(input.fill, .fill > input)[type="checkbox"]::after { background: var(--accent-ink); }
/* .select: a "choose this" checkbox (picking cards, as Keep's). Unticked it already shows its tick, muted, so it reads as
   "select" rather than an empty box; ticked it's the usual one. Use it with .round.fill. */
:is(input.select, .select > input)[type="checkbox"]:not(:checked)::after { transform: scale(1); background: color-mix(in srgb, var(--text) 45%, transparent); }
:is(input.select, .select > input)[type="checkbox"]:not(:checked):hover::after { background: var(--accent); }
input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; }
.check { display: flex; align-items: center; gap: 8px; font-weight: 400; color: var(--text); cursor: pointer; font-size: var(--text-md); }
.check input { margin: 0; }
.error { color: var(--bad-text); margin: 0; font-size: var(--text-md); }
.grid2, .grid3 { display: grid; gap: 14px; }       /* fields side by side in a dialog */
.grid2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }   /* minmax(0, …): a field can't push past its column */
.grid3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 760px) { .grid3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.stack { display: flex; flex-direction: column; gap: var(--space-2); font-size: var(--text-sm); color: var(--muted); font-weight: 500; }
/* Form rhythm: .stack is the existing label/control pair; a field group adds preview/help. */
.form-stack { display: grid; gap: var(--space-4); min-width: 0; }
.field-group { display: grid; gap: var(--space-2); min-width: 0; }
:is(.form-stack, .field-group) > .hint { margin: 0; line-height: 1.45; }
:is(.form-stack, .field-group) > .hint:empty { display: none; }
.field-group .stack { margin: 0; min-width: 0; }

.hint { font-weight: 400; color: var(--muted); font-size: var(--text-sm); }
.hint.warn { color: var(--ok-text); }   /* what Save will also do, said before it ("starts the price history over") */

/* ---------- toast ---------- */
.toast {
  position: fixed; bottom: 24px; left: 50%; transform: translate(-50%, 20px); opacity: 0; pointer-events: none;
  background: var(--panel-2); border: 1px solid var(--line); padding: 10px 18px; border-radius: var(--radius-md); box-shadow: var(--shadow);
  transition: .25s; z-index: 100;
}
/* As a popover (dialogs.js puts it in the top layer, over open dialogs): undo the browser's popover box */
.toast[popover] { inset: auto auto 24px 50%; margin: 0; color: var(--text); overflow: visible; }
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* Undo on a toast is the undo icon alone (an .icon-btn with aria-label "Undo", in the accent); other actions are text links */
.toast .icon-btn { color: var(--accent-text); padding: 8px; margin: -8px -10px -8px 0; border-radius: var(--radius-pill); }
.toast .icon-btn:hover { color: var(--accent-text); background: var(--accent-soft); }

/* ---------- Appearance: theme picker ---------- */
.themes { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; align-items: stretch; }
.theme-opt { border: 2px solid var(--line); border-radius: var(--radius-md); padding: 0; background: none; cursor: pointer; overflow: hidden; text-align: left; }
.theme-opt.on { border-color: var(--accent); }
.theme-opt .sw { height: 46px; display: flex; gap: 5px; align-items: flex-end; padding: 8px; }
.theme-opt .sw i { width: 16px; border-radius: var(--radius-xs); }
.theme-opt .sw i:nth-child(1) { height: 70%; } .theme-opt .sw i:nth-child(2) { height: 45%; } .theme-opt .sw i:nth-child(3) { height: 85%; }
.theme-opt .sw b { margin-left: auto; width: 18px; height: 18px; border-radius: 50%; }
.theme-opt .nm { display: flex; align-items: center; justify-content: space-between; gap: 4px; padding: 6px 6px 7px 10px; font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.theme-opt .nm > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.theme-opt .mini { border: 0; background: none; color: var(--muted); cursor: pointer; padding: 3px; border-radius: var(--radius-xs); display: grid; place-items: center; flex: none; }
.theme-opt .mini:hover { color: var(--accent-text); background: var(--panel-2); }
.theme-opt .mini svg, .theme-opt .mini .ui-ico { width: 14px; height: 14px; }
.theme-add {
  border: 2px dashed var(--line); border-radius: var(--radius-md); background: none; color: var(--muted); cursor: pointer;
  display: grid; place-items: center; min-height: 82px; transition: color var(--hover-dur), border-color var(--hover-dur), background var(--hover-dur);
}
.theme-add svg { width: 30px; height: 30px; }
.theme-add:hover { color: var(--accent-text); border-color: var(--accent); background: var(--accent-soft); }
.theme-edit-toggle { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--muted); font-weight: 500; cursor: pointer; }
.theme-edit-toggle input { accent-color: var(--accent); width: 15px; height: 15px; margin: 0; }
#teName:disabled { opacity: .7; }

/* theme editor: the color list and the picker are the same height */
.theme-editor { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--panel); padding: 14px; display: flex; flex-direction: column; gap: 14px; color: var(--text); font-weight: 400; }
.te-head { display: flex; gap: var(--space-2); align-items: center; }
.te-head .field { flex: 1; font-weight: 600; background: var(--bg-2); }
.te-body { display: grid; grid-template-columns: 1fr 240px; gap: var(--space-4); align-items: stretch; }
.te-fields { display: flex; flex-direction: column; gap: 6px; }
.te-field { flex: 1; display: flex; align-items: center; gap: 10px; border: 1px solid transparent; background: none; color: var(--text); padding: 10px 12px; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-md); text-align: left; }
.te-field:hover { background: var(--panel-2); }
.te-field.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, transparent); }
.te-field code { margin-left: auto; font: 500 var(--text-sm) var(--mono); color: var(--muted); }
.te-swatch { width: 26px; height: 26px; border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px rgba(128,128,128,.35); flex: none; }

/* color picker */
.picker { display: flex; flex-direction: column; gap: 9px; min-height: 0; }
.pk-sv { position: relative; flex: 1 1 0; min-height: 84px; border-radius: var(--radius-sm); cursor: crosshair; touch-action: none; box-shadow: inset 0 0 0 1px rgba(128,128,128,.3); }
.pk-dot { position: absolute; width: 16px; height: 16px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.45), 0 2px 6px rgba(0,0,0,.4); transform: translate(-50%, -50%); pointer-events: none; }
.pk-hue { -webkit-appearance: none; appearance: none; width: 100%; height: 12px; border-radius: var(--radius-pill); outline: none; margin: 0;
  background: linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); }
.pk-hue::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 2px solid var(--bg); box-shadow: 0 1px 5px rgba(0,0,0,.5); cursor: grab; }
.pk-hue::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: #fff; border: 2px solid var(--bg); cursor: grab; }
.pk-row { display: flex; gap: var(--space-2); align-items: center; }
.pk-preview { width: 38px; height: 38px; border-radius: var(--radius-sm); flex: none; box-shadow: inset 0 0 0 1px rgba(128,128,128,.35); }
.pk-hex { font: 500 var(--text-sm) var(--mono); background: var(--bg-2); padding: 6px 10px; text-align: center; }
.pk-swatches { display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px; }
.pk-swatches button { height: 20px; border: 0; border-radius: var(--radius-xs); cursor: pointer; box-shadow: inset 0 0 0 1px rgba(128,128,128,.35); padding: 0; }
.pk-swatches button:hover { transform: scale(1.12); }
@media (max-width: 620px) {
  .themes { grid-template-columns: repeat(2, 1fr); }
  .te-body { grid-template-columns: 1fr; }
  .pk-sv { flex: none; height: 130px; }
}

/* ---------- sliders (Appearance): a filled track, a round handle, a tick at the default (--t, 0–1) ---------- */
.width-row { display: flex; align-items: center; gap: 14px; }
.width-row .hint { min-width: 44px; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text-2); }
.slide { position: relative; display: inline-flex; flex: 0 1 300px; padding: 4px 0 12px; }
.slide input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 18px; margin: 0; background: none; cursor: pointer; outline: none; accent-color: var(--accent); }
.slide input[type=range]::-webkit-slider-runnable-track { height: 6px; border-radius: var(--radius-pill);
  background: linear-gradient(to right, var(--accent) var(--p, 50%), color-mix(in srgb, var(--text) 16%, transparent) var(--p, 50%)); }
.slide input[type=range]::-moz-range-track { height: 6px; border-radius: var(--radius-pill); background: color-mix(in srgb, var(--text) 16%, transparent); }
.slide input[type=range]::-moz-range-progress { height: 6px; border-radius: var(--radius-pill); background: var(--accent); }
.slide input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; margin-top: -6px; border-radius: 50%;
  background: #fff; border: 2px solid var(--accent); box-shadow: 0 1px 4px rgba(0,0,0,.3); transition: transform .12s; }
.slide input[type=range]::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: #fff; border: 2px solid var(--accent); box-shadow: 0 1px 4px rgba(0,0,0,.3); }
.slide input[type=range]:hover::-webkit-slider-thumb { transform: scale(1.1); }
.slide input[type=range]:active::-webkit-slider-thumb { transform: scale(1.18); cursor: grabbing; }
html:not(.by-pointer) .slide input[type=range]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 30%, transparent); }
.slide::after { content: ""; position: absolute; bottom: 2px; width: 2px; height: 6px; border-radius: var(--radius-pill); background: var(--muted);
  left: calc(9px + (100% - 18px) * var(--t, 0) - 1px); }
/* the rating slider (every 1–10 rating, in every app): the whole track is the rating colors, deep red → yellow → green →
   the theme's accent, the same stops as scoreColor(), so the color under the handle is the color of that score.
   A white handle ringed in the page color. Not rated yet = the slider faded (.unset on it or its row). */
.slide.rate { --grade: linear-gradient(90deg, #A31010 0%, #C22525 19%, #EB4D38 38%, #F2ED46 56%, #3FD951 74%, var(--accent) 100%); }
.slide.rate input[type=range]::-webkit-slider-runnable-track { background: var(--grade); }
.slide.rate input[type=range]::-moz-range-track { background: var(--grade); }
.slide.rate input[type=range]::-moz-range-progress { background: transparent; }
.slide.rate input[type=range]::-webkit-slider-thumb { width: 20px; height: 20px; margin-top: -7px; background: #fff; border: 3px solid var(--bg);
  box-shadow: 0 2px 8px rgba(0,0,0,.5); cursor: grab; }
.slide.rate input[type=range]::-moz-range-thumb { width: 14px; height: 14px; background: #fff; border: 3px solid var(--bg); box-shadow: 0 2px 8px rgba(0,0,0,.5); cursor: grab; }
html:not(.by-pointer) .slide.rate input[type=range]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 35%, transparent), 0 2px 8px rgba(0,0,0,.5); }
.slide.rate.unset, .unset > .slide.rate { opacity: .3; transition: opacity var(--hover-dur); }
.slide.rate.unset:hover, .unset > .slide.rate:hover { opacity: .6; }

/* ---------- drop-down menus (the ☰ menu and other pop-overs) ---------- */
.pop-wrap { position: relative; }
.pop {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 40; min-width: 230px;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: var(--space-2);
}
.pop[hidden] { display: none; }
/* opening: a quick drop from its button (placePop in select.js adds .pop-anim as a menu opens; a list redrawn while
   open, .pop-still, doesn't replay it). It grows from the corner at its button: above or below, left or right. */
.pop.pop-anim { animation: menu-in var(--menu-dur) var(--ease); transform-origin: top right; }
.pop.pop-anim.to-right { transform-origin: top left; }
.pop.pop-anim.up { animation-name: menu-in-up; transform-origin: bottom right; }
.pop.pop-anim.up.to-right { transform-origin: bottom left; }
.pop.pop-anim.pop-still { animation: none; }
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px) scale(.96); } }
@keyframes menu-in-up { from { opacity: 0; transform: translateY(4px) scale(.96); } }
/* where it opens (placePop in select.js picks these as it opens): above its button, or reaching left or right from it */
.pop.up { top: auto; bottom: calc(100% + 8px); }
.pop.to-left { left: auto; right: 0; }
.pop.to-right { left: 0; right: auto; }
.pop-title { font-size: var(--text-2xs); font-weight: 600; letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--muted); padding: 8px 10px 6px; }   /* in line with the rows' 10px */
.pop > button, .pop > a { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; border: 0; background: none; padding: 9px 10px; border-radius: var(--radius-sm); cursor: pointer; color: var(--text); font-size: var(--text-md); text-decoration: none; overflow-wrap: anywhere; }
.pop > button:hover:not(:disabled), .pop > a:hover { background: var(--panel-2); }
.pop > button:disabled { opacity: .4; cursor: default; }
.pop > button.on { color: var(--accent-text); font-weight: 600; }
.pop > button .check, .pop > button small { flex: none; margin-left: auto; color: var(--muted); font-size: var(--text-sm); white-space: nowrap; }
/* Long names wrap onto more lines (never run past the menu's edge); a count stays on the right, on the first line's
   baseline, and never wraps. A type-ahead suggestion's detail (bindCombo: a place's "Larimer County, United States")
   stays on the name's line when both fit, and otherwise drops under the name, still on the right. */
.pop > button:has(> small.detail) { flex-wrap: wrap; row-gap: 2px; }
.pop > button > small.detail { flex: 0 1 auto; white-space: normal; text-align: right; }
.pop > a small { margin-left: auto; color: var(--muted); font-size: var(--text-sm); }
.pop > button.on .check { color: var(--accent-text); }
.pop > button.danger, .pop > button.danger .mi { color: var(--bad-text); }
.pop hr { border: 0; border-top: 1px solid var(--line); margin: 6px 4px; }
.pop .mi { width: 18px; display: grid; place-items: center; color: var(--muted); }
.pop .mi .ui-ico { width: 17px; height: 17px; }
/* the ☰ menu: only as wide as its contents */
.pop.menu { min-width: 0; width: max-content; max-width: min(420px, calc(100vw - 32px)); }
.pop.menu > button, .pop.menu > a { padding-right: 18px; }
.pop-check { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer; font-size: var(--text-md); overflow-wrap: anywhere; }
.pop-check:hover { background: var(--panel-2); }
/* a row with a count: the name and its smaller count share a baseline (centered, the count would sit about a pixel high);
   the icon, checkbox, and chevron stay centered */
.pop > button:has(> small), .pop > a:has(> small), .pop-check:has(> small) { align-items: baseline; }
:is(.pop > button, .pop > a, .pop-check):has(> small) > :is(.mi, input, svg, .chev, .check) { align-self: center; }

/* the toolbar row above a list or grid: drop-down boxes and Filter on the left, Sort (.toolbar-right) pushed right.
   Apps add only their own padding or margins. On a phone it all runs left to right and wraps; nothing is pushed
   to the far side on a line of its own. */
.toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.toolbar-right { display: flex; gap: 10px; align-items: center; margin-left: auto; flex-wrap: wrap; }
.toolbar .pop { z-index: 15; }
@media (max-width: 760px) {
  .toolbar, .toolbar-right { gap: var(--space-2); }
  .toolbar-right { margin-left: 0; }
  .toolbar .tool { padding: 0 10px; gap: 6px; }   /* three controls fit one 343px line */
}

/* toolbar controls above a grid: Filter / Sort buttons and drop-down boxes (Entertainment's Genre, Shopping's Category).
   A .tool opens a .pop; .on = something chosen; .dot = how many filters are on. */
.tool {
  display: inline-flex; align-items: center; gap: 7px; box-sizing: border-box; height: var(--tool-h); padding: 0 13px; border-radius: var(--radius-sm); cursor: pointer;
  background: var(--panel); border: 1px solid var(--line); color: var(--muted); font-weight: 500; font-size: var(--text-md); outline: none;
}
.tool:hover { color: var(--text); }
.tool svg { width: var(--icon-sm); height: var(--icon-sm); }
.tool.on { color: var(--accent-text); border-color: var(--accent-line); background: var(--accent-soft); }
select.tool { padding-right: 8px; max-width: 220px; }
.tool .dot, .pop-section .dot { background: var(--accent); color: var(--accent-ink); font-size: var(--text-2xs); font-weight: 700; min-width: 17px; height: 17px; border-radius: var(--radius-pill); display: grid; place-items: center;
  width: auto; box-sizing: border-box; padding: 0 4px; font-variant-numeric: tabular-nums; }   /* a circle for one digit, a pill that grows for two (width: auto, whatever an app's own .dot says) */
/* the themed drop-down box: a .cs-btn that opens a .cs-pop list (customSelect / bindCombo in design/select.js) */
.cs-btn { display: inline-flex; align-items: center; justify-content: space-between; gap: 8px; cursor: pointer; font: inherit; text-align: left; }
.cs-btn.field { width: 100%; color: var(--text); font-weight: 400; line-height: calc(var(--text-base) * 1.45); }
.cs-btn.tool { font-size: var(--text-md); font-weight: 500; max-width: 240px; }
/* in a line of text (<select class="word">): reads as the word itself, in the accent, with a small chevron; no box */
.cs:has(> .cs-btn.word) { display: inline; }
.cs-btn.word { display: inline-flex; gap: 2px; padding: 0 3px; margin: 0 -3px; border: 0; border-radius: var(--radius-sm); background: none;
  color: var(--accent-text); font-size: inherit; font-weight: inherit; line-height: inherit; vertical-align: baseline; outline: none; }
.cs-btn.word:hover, .cs-btn.word:focus-visible { background: var(--accent-soft); }
.cs-btn.word .chev-ico { width: 12px; height: 12px; opacity: 1; }
.cs:has(> .cs-btn.word) > .cs-pop { min-width: 140px; }
.cs-btn > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cs-btn .chev-ico { width: 14px; height: 14px; flex: none; opacity: .7; }
/* as wide as its longest row, at least the box, at most 420px: past that a row wraps */
.cs-pop { left: 0; right: auto; width: max-content; min-width: 100%; max-width: min(420px, calc(100vw - 32px)); max-height: 280px; overflow-y: auto; }
.cs-pop > button { font-weight: 400; }
.cs-pop > button .check { display: none; }    /* a drop-down box marks its choice by highlighting it, never with a tick */
/* a drop-down box where several can be chosen (multiSelect): checkbox rows, the chosen ones in the accent; "Add your own…" at the foot */
.ms .cs-btn .ms-none { color: var(--muted); }
.ms .pop-check.on { color: var(--accent-text); font-weight: 600; }
.ms .ms-new { margin-top: 4px; width: 100%; padding: 8px 10px; font-size: var(--text-md); }
/* the wrapper customSelect() puts after the hidden <select>, and the type-ahead combo box (bindCombo, or an app's own suggestions) */
select[hidden] { display: none !important; }
.cs { position: relative; display: inline-block; }
.cs.block { display: block; }
.combo { position: relative; }
.combo .field { width: 100%; }
/* type-ahead suggestions line up with their field (at least 260px, so a narrow field on a phone still reads); long ones wrap */
.combo > .cs-pop { width: 100%; min-width: min(260px, calc(100vw - 32px)); }
/* the Clear filters row at the foot of a Filter menu */
.pop > button.pop-clear { justify-content: center; color: var(--muted); border-top: 1px solid var(--line); border-radius: 0 0 var(--radius-sm) var(--radius-sm); margin-top: 4px; font-size: var(--text-sm); }
/* the Filter menu (filterMenu in select.js): scrolls when long; choices as rows (counts on the right) or as chips that wrap */
.pop.filter-pop { width: 270px; max-height: max(180px, min(560px, calc(100dvh - 220px))); overflow-y: auto; }
.fm-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 8px 8px; }
.fm-empty { padding: 4px 10px 8px; color: var(--muted); font-size: var(--text-sm); }
.pop-check small { margin-left: auto; color: var(--muted); font-size: var(--text-sm); }
/* a collapsible group inside a pop-over (the Filter menu's sections); its chevron points up while open */
.pop > button.pop-section { font-size: var(--text-2xs); font-weight: 600; letter-spacing: var(--tracking-caps); text-transform: uppercase; color: var(--muted); padding: 9px 8px; }
.pop > button.pop-section .chev { margin-left: auto; font-size: 10px; color: var(--faint); }
.pop > button.pop-section .dot { margin-left: 2px; padding: 0 5px; letter-spacing: 0; }
.pop::-webkit-scrollbar { width: 10px; }
.pop::-webkit-scrollbar-thumb { background: var(--panel-2); border-radius: var(--radius-pill); border: 3px solid var(--bg-2); }

/* =====================================================================
   Scroll bars
   The page, dialogs, and scrolling lists use the floating bar from design/scrollbars.js: a slim bar over the right
   edge with nothing behind it, a ▲ / ▼ at each end, thicker while hovered or dragged. Anywhere else the browser's
   own bar is styled to match (slim, rounded, no arrow buttons).
   ===================================================================== */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--text) 30%, transparent) transparent; }
}
*::-webkit-scrollbar { width: 14px; height: 14px; }
*::-webkit-scrollbar-track { background: transparent; margin: 6px 0; }
*::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--text) 30%, transparent); border-radius: var(--radius-pill); border: 4px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover, *::-webkit-scrollbar-thumb:active { background: color-mix(in srgb, var(--text) 50%, transparent); border: 3px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
*::-webkit-scrollbar-corner { background: transparent; }

/* where the floating bar is used, the browser's own bar is hidden */
html, dialog.modal, .has-osb { scrollbar-width: none; }
html::-webkit-scrollbar, dialog.modal::-webkit-scrollbar, .has-osb::-webkit-scrollbar { display: none; width: 0; height: 0; }
/* a phone or tablet: no scroll bars anywhere (scrollbars.js draws none there either); you swipe */
@media (hover: none) and (pointer: coarse) {
  * { scrollbar-width: none; }
  *::-webkit-scrollbar { display: none; width: 0; height: 0; }
  .osb, .osb-rail { display: none !important; }
}

/* ---------- edge fades: the same on every page and in every box ----------
   Page: on pages that scroll (pageScrollbar() sets body.page-fade), a faint blur along the bottom of the window,
   with room under the content so the last row can scroll clear of it.
   Box: any box with a floating bar (except dialogs) fades its last few pixels into its own color while there's
   more below (it takes no space, and goes once you reach the end). No blur inside boxes: it ghosts the text. scrollbars.js supplies the box's padding and color. */
body.page-fade { padding-bottom: calc(var(--page-fade) + 10px); }
body.page-fade::after { content: ""; position: fixed; left: 0; right: 0; bottom: 0; height: var(--page-fade); pointer-events: none; z-index: 12;
  -webkit-backdrop-filter: blur(0.9px); backdrop-filter: blur(0.9px);
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 75%); mask-image: linear-gradient(to bottom, transparent, #000 75%); }
.has-osb.osb-scrolls:not(dialog)::after { content: ""; display: block; flex: none; grid-column: 1 / -1; align-self: stretch;
  min-width: calc(100% + var(--osb-pl, 0px) + var(--osb-pr, 0px));      /* a whole row in a box that lays out in rows */
  position: sticky; bottom: calc(-1 * var(--osb-pb, 0px)); height: var(--box-fade); pointer-events: none;
  margin: calc(-1 * var(--box-fade)) calc(-1 * var(--osb-pr, 0px)) calc(-1 * var(--osb-pb, 0px)) calc(-1 * var(--osb-pl, 0px));
  background: linear-gradient(to bottom, transparent, var(--osb-fade, var(--bg-2))); }

/* the floating bar: a zero-height rail pinned to the top of the scrolling box, with the bar hanging from it.
   Its hover zone is a fixed 16px strip; only the visible bar inside it changes size. */
/* Layer 11: above anything inside a box (content tops out at 5), but under every menu (.pop 40, toolbar menus 15),
   the top bar (20) and the page fade (12) — a bar from a box behind an open menu must never show through it. */
.osb-rail { position: sticky; top: 0; height: 0; z-index: 11; pointer-events: none; list-style: none; }
.osb { position: absolute; right: 0; width: 16px; z-index: 11; cursor: grab; touch-action: none; pointer-events: auto; }
.osb.dragging { cursor: grabbing; }
.osb[hidden] { display: none; }
.osb.page-osb { position: fixed; right: 0; z-index: 30; }
/* a note's bar (textarea): placed by scrollbars.js over the textarea's right edge, inside its border */
.osb.osb-ta { right: auto; z-index: 5; }
.osb::after { content: ""; position: absolute; top: 0; bottom: 0; right: 5px; width: 6px; border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--text) 30%, transparent); transition: width var(--hover-dur), right var(--hover-dur), background var(--hover-dur); }
.osb:hover::after, .osb.dragging::after { width: 8px; right: 4px; background: color-mix(in srgb, var(--text) 50%, transparent); }
.osb.osb-arrow { height: 14px; cursor: pointer; }
.osb.osb-arrow::after, .osb.osb-arrow:hover::after {
  top: 3px; bottom: auto; right: 4px; width: 0; height: 0; border-radius: 0; background: none;
  border-left: 4px solid transparent; border-right: 4px solid transparent; transition: border-color var(--hover-dur); }
.osb.osb-arrow.up::after { border-bottom: 6px solid color-mix(in srgb, var(--text) 35%, transparent); border-top: 0; }
.osb.osb-arrow.down::after { border-top: 6px solid color-mix(in srgb, var(--text) 35%, transparent); border-bottom: 0; top: 4px; }
.osb.osb-arrow.up:hover::after { border-bottom-color: color-mix(in srgb, var(--text) 65%, transparent); }
.osb.osb-arrow.down:hover::after { border-top-color: color-mix(in srgb, var(--text) 65%, transparent); }
.osb.osb-arrow:active::after { transform: scale(.85); }
/* the sideways version (sidewaysScrollbars): the same bar lying along the bottom of a box, ◀ / ▶ at its ends */
.osb.osb-x { right: auto; width: auto; height: 16px; }
.osb.osb-x::after { top: auto; bottom: 5px; left: 0; right: 0; width: auto; height: 6px; transition: height var(--hover-dur), bottom var(--hover-dur), background var(--hover-dur); }
.osb.osb-x:hover::after, .osb.osb-x.dragging::after { width: auto; right: 0; height: 8px; bottom: 4px; }
.osb.osb-arrow.osb-x { width: 14px; height: 16px; }
.osb.osb-arrow.osb-x::after, .osb.osb-arrow.osb-x:hover::after {
  top: 4px; bottom: auto; left: 4px; right: auto; width: 0; height: 0; border-radius: 0; background: none;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent; border-left: 0; border-right: 0; }
/* .osb-x in these (and the full border shorthand on hover) so they outrank the reset above, which would zero the arrow on hover */
.osb.osb-arrow.osb-x.left::after { border-right: 6px solid color-mix(in srgb, var(--text) 35%, transparent); }
.osb.osb-arrow.osb-x.right::after { border-left: 6px solid color-mix(in srgb, var(--text) 35%, transparent); left: 5px; }
.osb.osb-arrow.osb-x.left:hover::after { border-right: 6px solid color-mix(in srgb, var(--text) 65%, transparent); }
.osb.osb-arrow.osb-x.right:hover::after { border-left: 6px solid color-mix(in srgb, var(--text) 65%, transparent); }

/* ---------- the month calendar (monthCalendar in calendar.js), the same as Entertainment's ----------
   A .cal box floats above the row that opens it (give that row .cal-anchor). Days are round; today has an accent ring until a date is picked,
   only the picked day is filled with the accent; days already logged have a small accent dot under the number (so they
   never look picked); future days are faded and can't be picked. */
.cal-anchor { position: relative; }
.cal { width: 268px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 12px; color: var(--text); font-weight: 400;
  position: absolute; z-index: 45; bottom: calc(100% + 8px); left: 0; box-shadow: var(--shadow); }
.cal[hidden] { display: none; }
.cal.down { bottom: auto; top: calc(100% + 8px); }
.cal-head { display: flex; align-items: center; gap: 2px; margin-bottom: 8px; }
.cal-head b { flex: 1; text-align: center; font-size: var(--text-md); font-weight: 600; }
.cal-head button { width: 28px; height: 28px; border: 0; border-radius: var(--radius-sm); background: none; color: var(--muted); cursor: pointer; font-size: var(--text-base); line-height: 1; }
.cal-head button { display: grid; place-items: center; padding: 0; }   /* the shared chevrons, centered */
.cal-head button .ui-ico { width: 18px; height: 18px; }
.cal-head button:hover:not(:disabled) { background: var(--panel-2); color: var(--text); }
.cal-head button:disabled { opacity: .25; cursor: default; }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-dow span { text-align: center; font-size: var(--text-2xs); font-weight: 600; color: var(--muted); padding: 4px 0; }
.cal-grid button { aspect-ratio: 1; border: 0; border-radius: 50%; background: none; color: var(--text); cursor: pointer; font-size: var(--text-sm); font-variant-numeric: tabular-nums; transition: background var(--hover-dur) var(--ease), box-shadow var(--hover-dur) var(--ease); }
.cal-grid button:hover:not(:disabled, .picked, .inrange, .range-start, .range-end) { background: var(--control-hover); }
.cal-grid button:disabled { color: var(--faint); opacity: .4; cursor: default; }
/* days already logged: the number in accent (ink-colored once picked, by the rule below) */
.cal-grid button.marked { color: var(--accent-text); font-weight: 600; }
.cal-grid button.picked { background: var(--accent); color: var(--accent-ink); font-weight: 700; box-shadow: none; }
.cal-grid button.picked:hover:not(:disabled, .inrange, .range-start, .range-end) { background: var(--accent); color: var(--accent-ink); box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent-ink) 24%, transparent); }
/* today: a 4px dot under the number, always (both pickers, picked or not); ink-colored on an accent fill */
.cal-grid button.today { position: relative; }
.cal-today { position: absolute; z-index: 1; left: calc(50% - 2px); bottom: 12%; width: 4px; height: 4px; border-radius: 50%; background: var(--accent); pointer-events: none; }
.cal-grid button.picked:not(.range-end) .cal-today { background: var(--accent-ink); }
span.cal-anchor { display: inline-block; }   /* wrapped round just the Pick a date button */
/* a money box: the $ stays in front of the amount as you type */
.money-field { position: relative; display: inline-flex; align-items: center; }
.money-field::before { content: "$"; position: absolute; left: 11px; color: var(--muted); pointer-events: none; }
.money-field .field { padding-left: 23px; }
/* the date it's set to, shown as a pill beside the buttons that change it */
.date-chip { display: inline-flex; align-items: center; gap: 6px; box-sizing: border-box; height: var(--size-4); background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 0 10px; font-size: var(--text-sm); font-variant-numeric: tabular-nums; max-width: 100%; min-width: 0; overflow: hidden; }
.date-chip.empty { color: var(--muted); border-style: dashed; }
/* a chip you can act on: the date is a button (opens or edits it), and a round ✕ removes it, 2px in from the pill's end */
.date-chip:has(> .chip-main) { padding: 0 2px 0 0; gap: 0; }
.date-chip .chip-main { align-self: stretch; display: inline-flex; align-items: center; border: 0; background: none; color: var(--text); font: inherit; cursor: pointer; padding: 0 6px 0 10px; border-radius: var(--radius-pill); min-width: 0; white-space: nowrap; }
.date-chip .chip-main:hover { color: var(--accent-text); }
.date-chip .chip-main em { font-style: normal; color: var(--muted); white-space: pre; }
.date-chip .chip-main.static { cursor: default; }
.date-chip .chip-main.static:hover { color: var(--text); }
.date-chip > button:not(.chip-main) { flex: none; display: grid; place-items: center; width: calc(var(--size-4) - 6px); height: calc(var(--size-4) - 6px); padding: 0; border: 0; border-radius: 50%; background: none; color: var(--muted); cursor: pointer; }
.date-chip > button:not(.chip-main):hover { background: var(--control-hover); color: var(--bad-text); }
.date-chip > button:not(.chip-main) .ui-ico { width: var(--icon-xs); height: var(--icon-xs); vertical-align: 0; }
/* a form's date: the chip, Today and Pick a date ▾ (in its span.cal-anchor) side by side, as tall as a .field */
.date-row { display: flex; gap: var(--space-2); align-items: center; min-height: 40px; }
/* the range calendar (monthCalendar with range: true): "Start date – End date" under the month fills in as you pick */
.cal-range { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--line); font-size: var(--text-sm); color: var(--faint); white-space: nowrap; }
.cal-range i { font-style: normal; }
.cal-range .set { color: var(--text); font-weight: 600; }
.cal-range .next { color: var(--accent-text); font-weight: 600; }
/* the range is a slim band behind the days; the start is a solid circle, the end (or the day you point at) a ring */
.cal-grid button.inrange, .cal-grid button.inrange.range-start, .cal-grid button.inrange.range-end {
  background: none; box-shadow: none; border-radius: 50%; position: relative; isolation: isolate; color: var(--text); }
.cal-grid button.inrange:hover:not(:disabled) { background: none; }
.cal-grid button.inrange::before { content: ""; position: absolute; z-index: -2; left: -1px; right: -1px; background: color-mix(in srgb, var(--accent) 24%, var(--bg-2)); top: 13%; bottom: 13%; }
.cal-grid button.inrange.range-start::before { left: 50%; }
.cal-grid button.inrange.range-end::before { right: 50%; }
.cal-grid button.inrange.range-start::after, .cal-grid button.inrange.range-end::after { content: ""; position: absolute; z-index: -1; inset: 1px; border-radius: 50%;
  border: 1.5px solid color-mix(in srgb, var(--text) 45%, transparent); background: var(--bg-2); }
.cal-grid button.inrange.picked { background: none; color: var(--accent-ink); }
.cal-grid button.inrange.picked::after, .cal-grid button.picked.range-start::after, .cal-grid button.picked.range-end::after { border: 0; inset: 0; background: var(--accent); }
.cal-grid button.picked.range-end { color: var(--text); font-weight: 600; }
.cal-grid button.picked.range-end::after, .cal-grid button.inrange.picked.range-end::after {
  inset: 1px; background: var(--bg-2); border: 1.5px solid color-mix(in srgb, var(--text) 45%, transparent); }
/* Range endpoints keep their filled-start / outlined-end distinction while responding to hover. */
.cal-grid button.inrange.picked:not(.range-end):hover:not(:disabled)::after { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--accent-ink) 24%, transparent); }
.cal-grid button.range-end:hover:not(:disabled)::after { background: var(--panel-2); }
/* changing a range: both ends are listed under the month; the one being moved gets a ring */
.cal-grid button.moving { box-shadow: 0 0 0 2px var(--bg-2), 0 0 0 4px var(--accent); }
.cal-range.editing { gap: 6px; }
.cal-range.editing button[data-end] { border: 1px solid transparent; background: none; font: inherit; font-size: var(--text-sm); padding: 3px 7px; border-radius: var(--radius-sm); cursor: pointer; }
.cal-range.editing button[data-end]:hover { border-color: var(--line); }
.cal-range.editing button.next { border-color: var(--accent); }
.cal-range.editing [data-done] { margin-left: auto; padding: 4px 11px; }

/* ---------- app switcher (select.js appSwitcher): clicking the brand drops down the other apps, in a fixed
   order that wraps around, while the rest of the page (top bar included) blurs behind. No arrow: the brand is the control. ---------- */
.brand-btn {
  display: flex; align-items: center; gap: 10px; border: 0; background: none; color: inherit; font: inherit; cursor: pointer;
  padding: 4px 10px 4px 4px; margin: -4px -10px -4px -4px; border-radius: var(--radius-md); transition: background var(--hover-dur) var(--ease);
}
.brand-btn:hover { background: var(--panel); }
.app-scrim {
  position: fixed; inset: 0; z-index: 50; pointer-events: none; opacity: 0; transition: opacity .2s var(--ease);
  background: color-mix(in srgb, var(--bg) 45%, transparent); backdrop-filter: blur(10px);
}
body:has(.app-pop:not([hidden], .sg-static)) .app-scrim { opacity: 1; pointer-events: auto; }
/* lift the bar over the blur, and drop its own fill, blur, and bottom rule so the page's blur runs straight up under
   the brand: a filled bar over a dark page (Places' map) reads as a line. The lit brand is the only marker… */
/* the selection bar: while things are chosen it takes the top bar's place (as Keep's and Gmail's), with ✕ to clear,
   the count, and what you can do with them on the right. A .select-bar inside the .topbar; [hidden] when nothing is
   chosen. It's the same banner: the bar's own background, blur, and bottom line stay (none added), and the bar's other
   contents are hidden while it shows. */
.topbar > .select-bar {
  position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; gap: var(--space-1);
  padding: inherit; background: none;
  animation: select-bar-in var(--hover-dur) var(--ease);
}
.topbar > .select-bar[hidden] { display: none; }
.topbar:has(> .select-bar:not([hidden])) > :not(.select-bar) { visibility: hidden; }
.select-bar .select-count { flex: 1; min-width: 0; margin: 0 0 0 var(--space-2); font: 600 var(--text-lg) var(--font); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@keyframes select-bar-in { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .topbar > .select-bar { animation: none; } }
body:has(.app-pop:not([hidden], .sg-static)) .topbar { z-index: 60; background: transparent; backdrop-filter: none; border-bottom-color: transparent; box-shadow: none; }
body:has(.app-pop:not([hidden], .sg-static)) .topbar > :not(.brand) { filter: blur(4px); opacity: .45; pointer-events: none; transition: filter .2s, opacity .2s; }   /* …but blur all of it except the brand */
/* no panel: the apps sit straight on the blur, each logo in a column under the brand's own logo
   (rows use the brand button's 4px inset, so the hover patch lines up with the brand's too) */
.pop.app-pop {
  left: -4px; right: auto; top: calc(100% + 14px); min-width: 280px; padding: 0; display: flex; flex-direction: column; gap: 6px;
  background: none; border: 0; box-shadow: none; backdrop-filter: none; overflow: visible;
  animation: app-drop .22s var(--ease) both; transform-origin: top left;
}
.pop.app-pop > a { gap: 14px; padding: 4px 20px 4px 4px; border-radius: var(--radius-md); animation: app-drop .25s var(--ease) both; animation-delay: calc(var(--i, 0) * 40ms); }
.pop.app-pop > a:hover { background: var(--panel); }
.pop.app-pop a, .pop.app-pop a:hover { text-decoration: none; }   /* the lit row is the highlight; an app's own link rules never underline it */
.app-mark {
  display: grid; place-items: center; width: var(--brand-size); height: var(--brand-size); flex: none; border-radius: var(--radius-sm);
  background: var(--panel-2); color: var(--text-2); transition: background var(--hover-dur), color var(--hover-dur);
}
/* --app-accent / --app-accent-ink: that app's own accent, set by appSwitcher(). Until it's known (an app not opened
   since), the mark only brightens: borrowing this page's accent would show the wrong color */
.app-pop > a:hover .app-mark { background: var(--panel-2); color: var(--text); }
.app-pop > a[style*="--app-accent"]:hover .app-mark { background: linear-gradient(135deg, color-mix(in srgb, var(--app-accent) 70%, white), var(--app-accent)); color: var(--app-accent-ink); }
.app-text { display: flex; flex-direction: column; gap: 1px; line-height: 1.25; font-weight: 600; }
.app-pop .app-text small { margin: 0; color: var(--muted); font-size: var(--text-sm); font-weight: 400; }
.app-pop > a.off { color: var(--muted); }
.app-pop > a.off .app-mark { opacity: .55; }
.app-pop > a .app-off { margin-left: auto; padding-left: 12px; }
@keyframes app-drop { from { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) { .pop.app-pop, .app-pop > a { animation: none; } }
/* Reduce Motion (iPhone and Mac settings): dialogs, menus and toasts appear without sliding or popping */
@media (prefers-reduced-motion: reduce) { .modal[open], .pop, .toast { animation: none !important; transition-duration: 0s !important; } }

/* ---------- rating badge (score.js scoreColor): a 1–10 rating in a ring of its color, on a card or in a row.
   Set --c with scoreColor(v). On a picture it sits on --scrim; .flat is for plain backgrounds. ---------- */
.score-badge {   /* always a circle: a fixed size (40px slot, drawn 44 by --circle-grow; .small 32, where a smaller one belongs, like a list row), so 9.1 and 10.0 match */
  flex: none; border-radius: 50%; letter-spacing: -.02em; white-space: nowrap;
  display: inline-grid; place-items: center; font-weight: 700; font-size: var(--text-base);
  background: var(--scrim); backdrop-filter: blur(6px);
  border: 2px solid var(--c, var(--muted)); color: var(--c, var(--text));
}
.score-badge.flat { background: none; backdrop-filter: none; }
.score-badge.small { font-size: var(--text-sm); letter-spacing: -.04em; }
.score-ink { color: var(--c, var(--muted)); font-weight: 700; }      /* a rating as plain text in a line */
/* light themes: darken rating text on a light background so yellow stays readable (as Entertainment does);
   rings, and badges on a picture (on --scrim), keep the true color */
:is(.score-badge.flat, .rate-val, .score-ink) { color: color-mix(in srgb, var(--c, var(--muted)) var(--score-keep, 100%), var(--score-toward, #fff)); }   /* readable on every theme (themes.js: scoreInk) */
/* a rating you set: the badge, a 1–10 slider, and Clear once rated (score.js rateInput) */
/* laid out like a row of Entertainment's scorecard: slider, the score in its color, a small ✕; the numbers 1–10 under
   the slider, each centered where the handle sits at that score (the handle is 20px, so 10px in from either end) */
.rate-row { display: grid; grid-template-columns: minmax(0, 1fr) 38px 22px; grid-template-areas: "slide val clear" "ticks . ."; column-gap: 14px; row-gap: 2px; align-items: center; }
.rate-row .slide { grid-area: slide; width: 100%; padding: 0; }
.rate-row .slide::after { display: none; }                           /* no default-value line: that belongs to the plain slider, not a rating */
.rate-val { grid-area: val; text-align: right; font-weight: 700; font-size: var(--text-lg); color: var(--c, var(--muted)); font-variant-numeric: tabular-nums; }
.rate-clear { grid-area: clear; display: grid; place-items: center; padding: 0; border: 0; background: none; color: var(--muted); cursor: pointer; font-size: var(--text-sm); }
.rate-clear:hover { color: var(--text); }
.rate-row.unset .rate-clear { visibility: hidden; }
.rate-ticks { grid-area: ticks; position: relative; height: 1.4em; font-size: var(--text-2xs); color: var(--muted); font-variant-numeric: tabular-nums; }
.rate-ticks span { position: absolute; top: 0; left: calc(10px + (100% - 20px) * var(--i)); transform: translateX(-50%); }

/* ---------- side navigation (sidenav.js): tall sections down the left of a page, opening into sub-sections.
   The page is .page-with-side: the .sidenav column, then the page's own content (toolbar, grid…). The chosen
   section has the pressed look; a section with nothing in it is dimmed. Under 760px it becomes a row of pills. ---------- */
.page-with-side { display: grid; grid-template-columns: var(--sidenav-w) minmax(0, 1fr); gap: var(--space-6); padding-left: var(--page-pad-x); align-items: start; }
.sidenav {
  position: sticky; top: var(--topbar-h); max-height: calc(100vh - var(--topbar-h)); overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px; padding: var(--space-5) 0 var(--space-6);
}   /* give it floatingScrollbars("#yourNav") for the shared bar when it's taller than the window */
.sidenav-title { padding: 0 14px 10px; }
.sidenav hr { border: 0; border-top: 1px solid var(--line); margin: 8px 14px; }
/* a row: the item, then the chevron in its own 20px column outside it (empty when the section doesn't open), so every
   count, and every sub-section's count, lines up in one column */
.sidenav { --sn-chev-w: 20px; }
.sn-row { display: flex; align-items: center; gap: 2px; }   /* sub-sections stop 5px short of the chevron column: this gap, plus the item's 1px border and its 2px more padding */
.sn-item {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 14px; text-align: left; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius-sm); background: none; color: var(--text-2);
  font: 500 var(--text-base) var(--font); transition: background var(--hover-dur), color var(--hover-dur);
}
.sn-item:hover { background: var(--panel); color: var(--text); }
.sn-item .ui-ico { width: var(--icon); height: var(--icon); flex: none; color: var(--muted); }
.sn-item .sn-label { flex: 1; min-width: 0; }
.sn-item small, .sn-sub small { margin-left: auto; color: var(--muted); font-size: var(--text-sm); font-weight: 500; font-variant-numeric: tabular-nums; }
.sn-item.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text); font-weight: 600; }
.sn-item.on .ui-ico, .sn-item.on small { color: var(--accent-text); }
.sn-item.none { color: var(--muted); }
.sn-item.none .ui-ico { opacity: .6; }
.sn-chev { display: grid; place-items: center; flex: none; width: var(--sn-chev-w); height: var(--sn-chev-w); color: var(--muted); transition: transform var(--motion-slide) var(--ease); }
.sn-chev[data-key] { cursor: pointer; }
.sn-chev .ui-ico { width: 14px; height: 14px; color: inherit; }
.sn-chev:not(.open) { transform: rotate(-90deg); }
.sn-sub { display: flex; flex-direction: column; gap: 1px; margin: 2px calc(var(--sn-chev-w) + 5px) 8px 24px; padding-left: 12px; border-left: 1px solid var(--line); }
.sn-sub button {
  display: flex; align-items: center; gap: 8px; padding: 8px 12px; text-align: left; cursor: pointer; border: 0;
  border-radius: var(--radius-sm); background: none; color: var(--muted); font: 500 var(--text-md) var(--font);
}
.sn-sub button:hover { background: var(--panel); color: var(--text); }
.sn-sub button.on { background: var(--accent-soft); color: var(--accent-text); font-weight: 600; }
.sn-sub button.on small { color: var(--accent-text); }

/* ---------- the side drawer (drawer.js: navDrawer): what ☰ can open instead of the ☰ menu, like Keep's ---------- */
dialog.drawer {
  position: fixed; inset: 0 auto 0 0; margin: 0; padding: 0; border: 0;
  width: min(var(--drawer-w), calc(100vw - 56px)); height: 100%; max-width: none; max-height: none;
  background: var(--bg-2); color: var(--text); box-shadow: var(--shadow);
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0; overflow: hidden; overscroll-behavior: contain;
}
dialog.drawer[open] { display: flex; flex-direction: column; animation: drawer-in var(--drawer-dur) var(--ease); }
dialog.drawer::backdrop { background: rgba(4,5,8,.52); backdrop-filter: blur(4px); animation: drawer-dim var(--drawer-dur) ease-out; }
dialog.drawer.closing { animation: drawer-out calc(var(--drawer-dur) * .7) cubic-bezier(.4,0,1,1) forwards; pointer-events: none; }
dialog.drawer.closing::backdrop { animation: drawer-undim calc(var(--drawer-dur) * .7) ease-in forwards; }
@keyframes drawer-in { from { transform: translateX(-100%); } }
@keyframes drawer-out { to { transform: translateX(-100%); } }
@keyframes drawer-dim { from { opacity: 0; } }
@keyframes drawer-undim { to { opacity: 0; } }
.drawer:focus, .drawer:focus-visible { outline: none; }
.drawer-head { display: flex; align-items: center; gap: var(--space-2); padding: max(var(--space-4), env(safe-area-inset-top)) var(--space-3) var(--space-3) var(--space-5); }
.drawer-head .brand { flex: 1; min-width: 0; margin: 0; }
.drawer-head .brand-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.drawer-head .grow { flex: 1; }
.drawer-body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 2px; padding: var(--space-1) var(--space-3) var(--space-4); }
.drawer-body hr, .drawer-foot hr { border: 0; border-top: 1px solid var(--line); margin: 6px 12px; }
/* a page: its row (goes there), then the chevron in its own column outside it (empty when the page has no sections), so
   every row, and every section under it, ends at the same edge and every count lines up in one column (as sidenav's) */
.drawer, .nav-dock { --dr-chev-w: 36px; }
.dr-page { display: flex; align-items: center; gap: 2px; }
.dr-go, .dr-item, .dr-act {
  display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; text-align: left; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius-sm); background: none; color: var(--text-2);
  font: 500 var(--text-base) var(--font); padding: 10px 12px; transition: background var(--hover-dur), color var(--hover-dur);
}
.dr-go { height: var(--size-7); padding-block: 0; }   /* a page's row: 40, on the size scale (its padding made it 38) */
@media (hover: hover) { .dr-go:hover, .dr-item:hover, .dr-act:hover { background: var(--panel); color: var(--text); } }   /* no hover left stuck on a tapped row */
:is(.dr-go, .dr-item, .dr-act) .ui-ico { width: var(--icon); height: var(--icon); flex: none; color: var(--muted); }
.dr-label { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
:is(.dr-go, .dr-item, .dr-act) small { margin-left: auto; flex: none; color: var(--muted); font-size: var(--text-sm); font-weight: 500; font-variant-numeric: tabular-nums; }
.dr-page.here .dr-go { color: var(--text); font-weight: 600; }
/* the chosen row: the pressed look, as in the side navigation */
:is(.dr-go, .dr-item).on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text); font-weight: 600; }
:is(.dr-go, .dr-item).on :is(.ui-ico, small) { color: var(--accent-text); }
.dr-item.none { color: var(--muted); }
.dr-item.none .ui-ico { opacity: .6; }
/* the chevron is an icon button of its own (.icon-btn: its hover highlight and press feedback): the column's width and
   its row's height, so its highlight is a rectangle beside the row's, both on the size scale (36 × 40) */
.dr-toggle { flex: none; display: grid; place-items: center; width: var(--dr-chev-w); height: var(--size-7); padding: 0; transition: background var(--hover-dur), color var(--hover-dur); }
span.dr-toggle { height: 0; }
/* its hover is its row's (the --panel fill a drawer row lights with), not an icon button's --control-hover: the chevron
   belongs to the row beside it, so the two light the same way */
@media (hover: hover) { .icon-btn.dr-toggle:hover { background: var(--panel); color: var(--text); } }
@media (hover: none) { .icon-btn.dr-toggle:hover { background: none; color: var(--muted); } }   /* no square left behind after a tap */
/* its icon goes by the rectangle's shorter side, its width: 20 in 36, 16 in 28 (docked with a fine pointer) */
.dr-toggle .ui-ico { width: var(--icon); height: var(--icon); transition: transform var(--motion-slide) var(--ease); }
.dr-toggle:not(.open) .ui-ico { transform: rotate(-90deg); }
/* a page's sections, under it */
.dr-items, .dr-sub { display: flex; flex-direction: column; gap: 1px; margin: 2px calc(var(--dr-chev-w) + 2px) 8px 22px; padding-left: 10px; border-left: 1px solid var(--line); }
.dr-items .dr-item { font-size: var(--text-md); padding: 8px 12px; gap: 10px; }
.dr-items .dr-item .ui-ico { width: 18px; height: 18px; }
.dr-sub { margin: 0 0 4px 18px; }   /* inside .dr-items, which already stops short of the chevrons */
.dr-sub .dr-item { color: var(--muted); }
/* the ☰ menu's own rows, at the foot */
.drawer-foot { display: flex; flex-direction: column; gap: 1px; border-top: 1px solid var(--line); padding: var(--space-2) var(--space-3) max(var(--space-3), env(safe-area-inset-bottom)); }
.drawer-foot[hidden] { display: none; }
.dr-act { font-size: var(--text-md); padding: 9px 12px; gap: 10px; }
.dr-act .mi { width: 18px; display: grid; place-items: center; color: var(--muted); }
.dr-act .mi .ui-ico { width: 17px; height: 17px; }
/* docked (navDrawer's dock): the drawer as the page's left column on a computer or tablet, where ☰ is hidden. Its
   head is left out (the top bar already has the brand); a pointer gets a narrower chevron column. */
.nav-dock { flex: none; width: var(--sidenav-w); min-height: 0; display: flex; flex-direction: column; background: none; }
.nav-dock[hidden] { display: none; }
.nav-dock > .drawer-head { display: none; }
.nav-dock > .drawer-body { padding: var(--space-4) var(--space-3) var(--space-6) calc(var(--page-pad-x) - 12px + env(safe-area-inset-left)); }
@media (pointer: fine) { .nav-dock { --dr-chev-w: 28px; } .nav-dock .dr-toggle .ui-ico { width: var(--icon-sm); height: var(--icon-sm); } }
@media (prefers-reduced-motion: reduce) { dialog.drawer, dialog.drawer::backdrop, .dr-toggle .ui-ico { animation: none !important; transition: none !important; } }
@media (max-width: 760px) {
  .page-with-side { grid-template-columns: minmax(0, 1fr); gap: 0; padding-left: 0; }
  .sidenav { position: static; max-height: none; flex-direction: row; align-items: center; overflow-x: auto; scrollbar-width: none; gap: 6px; padding: var(--space-3) var(--page-pad-x) 0; }
  .sidenav-title, .sidenav hr, .sn-chev { display: none; }
  .sn-row { display: contents; }   /* the pills sit straight in the sideways row */
  .sidenav > .osb-rail { display: none; }   /* the up-and-down bar's rail: this row scrolls sideways, and its margins pulled the pills 10px left */
  .sn-item, .sn-sub button { width: auto; flex: none; padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--radius-pill); background: var(--panel); font-size: var(--text-sm); }
  .sn-item .ui-ico { width: 16px; height: 16px; }
  .sn-item small, .sn-sub small { margin-left: 4px; }
  .sn-sub { flex-direction: row; gap: 6px; margin: 0; padding: 0; border: 0; }
}

/* ---------- section picker (sidenav.js sectionPick): choose several sections, in an even grid with their icons.
   A chosen section with sub-sections gets a row of sub-section chips underneath (.sp-subs). ---------- */
.section-pick { display: flex; flex-direction: column; gap: var(--space-2); }
.sp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(200px, calc(50% - 3px)), 1fr)); gap: 6px; }   /* 4 across in a wide dialog, 2 on a phone */
.sp-item {
  display: flex; align-items: center; gap: 10px; min-width: 0; padding: 8px 12px; text-align: left; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel); color: var(--muted);
  font: 500 var(--text-md) var(--font); transition: border-color var(--hover-dur), color var(--hover-dur);
}
.sp-item span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-item .ui-ico { width: var(--icon); height: var(--icon); flex: none; }
.sp-item small { color: inherit; opacity: .7; font-size: var(--text-sm); font-variant-numeric: tabular-nums; }   /* sub-sections chosen */
.sp-item:hover { color: var(--text); border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.sp-item.on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text); font-weight: 600; }
.sp-subs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 10px 12px;
  border: 1px solid var(--line); border-left: 2px solid var(--accent-line); border-radius: var(--radius-sm); background: var(--bg);
}
.sp-subs-label { margin-right: 6px; color: var(--muted); font-size: var(--text-sm); font-weight: 600; }
.sp-subs .chip:not(.on) { background: none; }
@media (max-width: 760px) {
  .sp-item { gap: 8px; padding: 8px 10px; font-size: var(--text-sm); }
  .sp-item span { white-space: normal; line-height: 1.25; }   /* two lines on a phone, never cut off */
  .sp-item .ui-ico { width: 16px; height: 16px; }
}

/* ---------- pager (pager.js pagerHTML): page numbers under a long list. Plain numbers, no boxes; the current page is
   accent, 600, and underlined; the shared chevron icons either side, faded at either end. ---------- */
.pager { display: flex; justify-content: center; align-items: center; gap: 4px; margin: 30px 0 10px; flex-wrap: wrap; }
.pager[hidden] { display: none; }
.pager button { min-width: 32px; height: 32px; padding: 0 6px; border: 0; border-radius: var(--radius-sm); background: none; color: var(--muted);
  font: inherit; font-size: var(--text-md); font-weight: 500; cursor: pointer; font-variant-numeric: tabular-nums; }
.pager button:hover:not(:disabled):not(.on) { color: var(--text); background: var(--control-hover); }
.pager button.on { color: var(--accent-text); font-weight: 600; border-radius: 0; box-shadow: inset 0 -2px 0 var(--accent); cursor: default; }
.pager .pg-arrow { display: grid; place-items: center; color: var(--text-2); }
.pager .pg-arrow .ui-ico { width: 20px; height: 20px; }
.pager button:disabled { opacity: .3; cursor: default; }
.pager .gap { color: var(--faint); padding: 0 2px; }

/* ---------- stepper: a number with − and + either side (Recipes: how many people are eating). Markup:
   <span class="stepper"><button data-step="-1">ico("minus-circle")</button><output>4 people</output><button data-step="1">ico("plus-circle")</button></span>
   The app reads data-step; a button is :disabled at the ends of the range. ---------- */
.stepper { display: inline-flex; align-items: center; gap: 2px; box-sizing: border-box; height: var(--size-5); padding: 1px; border: 1px solid var(--line); border-radius: var(--radius-pill); background: var(--panel); }
.stepper button { display: grid; place-items: center; width: var(--size-4); height: var(--size-4); padding: 0; border: 0; border-radius: var(--radius-pill); background: none; color: var(--muted); cursor: pointer; }
.stepper button:hover:not(:disabled) { color: var(--text); background: var(--control-hover); }
.stepper button:disabled { opacity: .4; cursor: default; }
.stepper .ui-ico { width: var(--icon); height: var(--icon); }
.stepper output { min-width: 5.5em; padding: 0 4px; text-align: center; font-size: var(--text-sm); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- stat tiles: a row of headline numbers above a page (Finances: net worth; Music: shows seen).
   <div class="tiles"><div class="tile"><h4>Net worth</h4><div class="big">$12,400 <small>today</small></div><div class="sub">…</div></div></div>
   Entertainment's Stats page (.bento .tile in its app.css) is drawn the same way. ---------- */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-3); }
.tile { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 16px 18px; min-width: 0; }
.tile h4 { margin: 0 0 10px; font-size: var(--text-2xs); font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.tile .big { font-size: var(--number); font-weight: 700; letter-spacing: -.03em; line-height: 1; font-variant-numeric: tabular-nums; }
.tile .big small { font-size: var(--text-base); color: var(--muted); font-weight: 500; letter-spacing: 0; }
.tile .sub { color: var(--muted); font-size: var(--text-sm); margin-top: 6px; }
.tile .sub .up { color: var(--good-text); } .tile .sub .down { color: var(--bad-text); }
@media (max-width: 760px) {   /* two to a row on a phone, with smaller numbers */
  .tiles { grid-template-columns: 1fr 1fr; gap: var(--space-2); }
  .tile { padding: 12px 14px; }
  .tile .big { font-size: var(--title-lg); }
}

/* ---------- bar rows: a label, a bar, a value (spending by category, budgets, top artists).
   <div class="hbar"><span>Groceries</span><span class="track"><span class="fill" style="width:62%"></span></span><span>$412</span></div>
   .over turns the fill --bad (over budget); .warn --ok (close to it). ---------- */
.hbar { display: grid; grid-template-columns: minmax(0, 150px) 1fr minmax(70px, auto); align-items: center; gap: 10px; font-size: var(--text-sm); margin-bottom: 8px; }
.hbar > span:first-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hbar .track { height: 10px; background: var(--panel-2); border-radius: var(--radius-pill); overflow: hidden; }
.hbar .fill { display: block; height: 100%; max-width: 100%; border-radius: var(--radius-pill); background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 55%, var(--panel)), var(--accent)); }
.hbar .fill.warn { background: var(--ok); } .hbar .fill.over { background: var(--bad); }
.hbar > span:last-child { text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- stacked bar: one bar split into parts, each in its own color (Places: a city's places by category).
   <span class="stackbar" style="--w:80%"><i style="--c:#e4572e;flex-grow:12"></i><i style="--c:#2e86de;flex-grow:5"></i></span>
   --w is the bar's length (its total against the biggest in the set); each part's flex-grow is its count. ---------- */
.stackbar { display: flex; gap: 2px; width: var(--w, 100%); min-width: 6px; height: 6px; border-radius: var(--radius-pill); overflow: hidden; }
.stackbar i { flex: 1 1 0; min-width: 2px; background: var(--c, var(--accent)); }

/* ---------- pictures (images.js): a flyer, a poster, a photo ----------
   .img-thumb  a picture you can click to see full size (data-lightbox): it fills its box (cover), radius-xs, two-hairline
               border like a poster; hover rings it in accent (it sits inside cards that lift themselves). Size it where it's used.
   .img-pick   the picker: the picture box on the left (dashed and empty until there's a picture, which then shows whole),
               a link field with Get, Upload and Remove on the right. Under 560px the box goes on top.
   .lightbox   the picture full size over a dimmed page, no frame; click anywhere or Esc to close. */
.img-thumb { display: block; padding: 0; border: 1px solid var(--line); border-radius: var(--radius-xs); overflow: hidden; background: var(--panel-2);
  cursor: zoom-in; flex: none; box-shadow: 0 0 0 1px color-mix(in srgb, var(--bg) 60%, transparent); transition: border-color var(--hover-dur), box-shadow var(--hover-dur); }
.img-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.img-thumb:hover, html:not(.by-pointer) .img-thumb:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); outline: none; }
.img-pick { display: grid; grid-template-columns: 140px minmax(0, 1fr); gap: var(--space-4); align-items: start; }
.img-pick-box { height: 180px; border: 2px dashed var(--line); border-radius: var(--radius-sm); display: grid; place-items: center; overflow: hidden;
  color: var(--faint); cursor: pointer; outline: none; transition: border-color var(--hover-dur), background var(--hover-dur), color var(--hover-dur); }
.img-pick-box:hover, .img-pick-box:focus-visible, .img-pick-box.over { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-text); }
.img-pick.has .img-pick-box { border-style: solid; border-width: 1px; background: var(--panel-2); cursor: default; }
.img-pick.has .img-pick-empty { display: none; }
.img-pick-box img { width: 100%; height: 100%; object-fit: contain; }
.img-pick-empty { display: grid; justify-items: center; gap: var(--space-2); padding: var(--space-3); text-align: center; font-size: var(--text-sm); line-height: 1.35; }
.img-pick-ico svg, .img-pick-ico .ui-ico { width: 28px; height: 28px; }
.img-pick-side { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.img-pick-link { display: flex; gap: var(--space-2); }
.img-pick-link .field { flex: 1; min-width: 0; }
.img-pick-btns { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
@media (max-width: 560px) { .img-pick { grid-template-columns: 1fr; } .img-pick-box { height: 220px; } }
.modal.lightbox { width: fit-content; max-width: calc(100vw - 48px); max-height: calc(100vh - 48px); padding: 0; border: 0; background: none; box-shadow: none; overflow: visible; }
.modal.lightbox img { display: block; max-width: calc(100vw - 48px); max-height: calc(100vh - 48px); border-radius: var(--radius-sm); box-shadow: var(--shadow); cursor: zoom-out; }
.modal.lightbox .icon-btn { position: fixed; top: 16px; right: 16px; background: var(--scrim); color: #fff; }
.modal.lightbox::backdrop { background: rgba(4,5,8,.8); backdrop-filter: blur(6px); }
