/* See the Bills: the grid.
   Geometry comes from grid.js as four numbers on #app: --pitch (one line plus one
   cell), --cell, --gap and --hh (the date header's height). Each tile is its line
   first, then its cell, so the left and top edges always show a line. Everything below is
   positioned from those, so a zoom changes four numbers and the browser does the
   rest. No element is moved one by one. */

/* Colors and shadows come from tokens.css, by role. No raw values below. */

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--page); color: var(--text); }
body { font-family: var(--font); overflow: hidden; -webkit-text-size-adjust: 100%; }
button { font: inherit; color: inherit; }

#app {
  position: fixed;
  top: 0; bottom: 0; left: 0; right: 0;
  margin: 0 auto;
  width: 100%;
  max-width: var(--preview, none);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--page);
}

/* ---- Top bar: says where the center is. Absorbs leftover height. ---- */
/* White, and sized in whole grid rows, so it and the date row read as one header.
   Leftover height goes below the grid. */
#top {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: env(safe-area-inset-top) 16px 0;
  background: var(--surface);
}
/* Centered over the lens, between the menu and Today. Only the sentence itself sits
   above the scrim, not the whole bar: the hourglass and Today still dim like
   everything else, and the sentence reads a little more clearly, not fully undimmed. */
#where {
  position: relative;
  z-index: 21;
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
  font-size: var(--whisper-size);
  line-height: 1.3;
  width: var(--whisper-w, 60%);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 6px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
#where strong { text-align: right; overflow: hidden; text-overflow: ellipsis; }
#where i { font-style: normal; color: var(--text-2); }
#where span { text-align: left; overflow: hidden; text-overflow: ellipsis; }
#where strong { font-weight: 650; }
#where span { color: var(--text-2); }
#where .count { font-style: normal; font-weight: 300; color: var(--text-2); }
#devread { display: flex; align-items: center; gap: 10px; font: 12px/1 ui-monospace, Menlo, monospace; color: var(--text-2); }
#devread[hidden] { display: none; }
#size { border: 1px solid var(--hairline); background: var(--surface); border-radius: 4px; padding: 6px 8px; font: inherit; }

/* ---- Stage: exactly the grid, centered. ---- */
#stage {
  flex: none;
  position: relative;
  margin: 0 auto;
}
#scroller {
  position: absolute;
  inset: 0;
  overflow: auto;
  scroll-snap-type: both mandatory;
  scroll-padding-top: var(--hh);
  overscroll-behavior: contain;
  touch-action: pan-x pan-y;
  scrollbar-width: none;
  background: var(--page);
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}
#scroller::-webkit-scrollbar { display: none; }
/* Pull down at the top of the grid to reload: a circle slides down under the
   dates, turns Bondi once you've pulled far enough, and letting go reloads. Same
   circle, icon, colors and shadow as the zoom button, so the two feel like a set.
   Its starting spot is centered on the first row's square; a full pull lands it
   centered on the second row's, then 1px lower (trying it by eye on the phone,
   where the grid stretches a little under a full pull). */
#pull {
  position: absolute; left: 50%; top: calc(var(--hh) + var(--gap) + var(--cell) / 2 - 27px); z-index: 6;
  width: 56px; height: 56px; margin-left: -28px; border-radius: 50%;
  display: grid; place-items: center; pointer-events: none;
  background: var(--button); color: var(--button-ink);
  box-shadow: var(--shadow-float);
  opacity: 0; transform: translateY(-24px);
}
#pull svg { width: 25px; height: 25px; }
#pull.ready { color: var(--c-bondi); }
#pull.settle { transition: transform 0.2s ease, opacity 0.2s ease; }
#pull.go svg { animation: pull-spin 0.9s linear infinite; }
/* Dissolving out: fades and eases a little smaller, relaxed, not snapped away. */
#pull.gone { opacity: 0 !important; scale: 0.85; transition: opacity 0.35s ease, scale 0.35s ease; }
@keyframes pull-spin { to { transform: rotate(360deg); } }
#scroller.dragging { scroll-snap-type: none; cursor: grabbing; }
/* Under the bills, the grid carries on as dots: 2px squares where the lines would
   cross. Lines mean bills; dots mean room. Vertical dot-colored lines, then the
   page color over everything but a 2px band at the top of each tile. */
#sheet {
  position: relative;
  background-color: var(--page);
  background-image:
    linear-gradient(to bottom, transparent var(--gap), var(--page) 0),
    linear-gradient(to right, var(--dot) var(--gap), var(--page) 0);
  background-size: var(--pitch) var(--pitch);
  background-position: 0 var(--hh);
}

/* ---- Date header: sticky top, inside the same scroller, so it can't lag. ---- */
#dates {
  -webkit-touch-callout: none;
  position: sticky;
  top: 0;
  z-index: 3;
  height: var(--hh);
  width: 100%;
  background: var(--surface);
  transition: box-shadow 0.15s ease;
}
/* Flat up top: no shadow under the date row, even with the grid scrolled under it. */
.d {
  position: absolute;
  /* Date cells start 2px down, the same white edge every grid cell has above it,
     so today's raised tile and the lens show a white cap at the top the way the
     closing line shows at the bottom. */
  top: var(--gap);
  left: calc(var(--c) * var(--pitch) + var(--gap));
  width: var(--cell);
  height: calc(var(--hh) - var(--gap));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
}
.d b { font-weight: 600; font-size: clamp(12px, calc(var(--cell) * 0.34), 20px); font-variant-numeric: tabular-nums; }
.d i { font-style: normal; font-weight: 600; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; line-height: 1; }
/* A month name is a reference, not a date: regular weight beside the bold numbers. */
.d.first i { font-weight: 400; }
/* At 100% the 1 sits on the same line as every other date (the month name used to
   push it 5.5px down, crowding the Bondi dots under it). The month floats in the
   space above the number, its capitals centered between the tile's top and the top of
   the number: a quarter of the way down, less a quarter of the number's height.
   Today's tile works the same way, TODAY floating where the month name would. */
.z0 .d.first i, .z0 .d.today i {
  position: absolute; left: 0; right: 0; text-align: center;
  top: calc(25% - clamp(2px, var(--cell) * 0.0575, 3.4px) - 5px);
}
.d.first { background: var(--month); color: var(--text-on-surface-strong); }
.d.payday i { color: var(--text); }
.d.first.payday i, .d.today.payday i { color: inherit; }

/* ---- Bill labels: sticky left, one screen wide, chips slide between sides. ---- */
#labels {
  position: sticky;
  left: 0;
  z-index: 2;
  width: var(--vw);
  height: calc(100% - var(--hh));
  pointer-events: none;
}
.lrow {
  position: absolute;
  left: var(--gap);
  top: calc(var(--r) * var(--pitch) + var(--gap));
  height: var(--cell);
  display: flex;
  align-items: center;
}
/* Holding a bill's name with dates hidden: it lifts off the board (a touch bigger,
   the zoom button's floating shadow) and follows the finger; the others slide
   aside to show where it will land. */
.lrow.lifted { z-index: 4; }
.lrow.lifted .chip { scale: 1.06; box-shadow: var(--shadow-float); transition: scale 0.15s ease, box-shadow 0.15s ease; }
.lrow.shifting { transition: transform 0.15s ease; }
.chip {
  position: relative;
  max-width: var(--chipmax);
  margin-left: 8px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: min(34px, calc(var(--cell) - 4px));
  /* Each end matches its own top and bottom: the date pill sits 8.5px from the top
     and bottom, the name 7.5px. */
  padding: 0 7.5px 0 8.5px;
  border-radius: 8px;
  background: var(--chip);
  color: var(--chip-ink);
  font-size: clamp(11px, calc(var(--cell) * 0.28), 16px);
  font-weight: 600;
  white-space: nowrap;
  transform: translateX(0);
  transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
  will-change: transform;
}
#labels.right .chip { transform: translateX(calc(var(--vw) - var(--w) - 16px)); }
#labels.still .chip { transition: none; }
/* On dark rows (collections, lawsuit) the chip inverts so it reads. */
.chip.inv { background: var(--chip-inv); color: var(--chip-inv-ink); }
.chip.inv .dot { box-shadow: none; }
.dot { width: 0.5em; height: 0.5em; border-radius: 50%; background: var(--status-ok); flex: none; }
.dot.late { background: var(--status-late); }
.dot.overdue { background: var(--status-overdue); }
.dot.collections { background: var(--status-collections); box-shadow: 0 0 0 1.5px var(--c-white); }
.dot.lawsuit { background: var(--status-lawsuit); box-shadow: 0 0 0 1.5px var(--c-white); }
/* In the hourglass view the dot grows into a numbered circle, same status color. */
.dot.num {
  width: auto;
  min-width: 1.5em;
  height: 1.5em;
  padding: 0 0.3em;
  border-radius: 0.75em;
  display: grid;
  place-items: center;
  font-size: 0.72em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--text-on-state);
}
.dot.num.collections, .dot.num.lawsuit { color: var(--text-on-dark); box-shadow: none; }
.dot.soon { background: var(--status-due-soon); }
.dot.early { background: var(--status-early); }
/* Names truncate so a chip at rest never reaches the lens. */
.nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Optical centering: the name sat a pixel low in its chip, so it rides up one; with
   the dotted "needs" underline it hugged the bottom, so it rides up two. */
.chip .nm { position: relative; top: -1px; }
.chip.todo .nm { top: -2px; }
/* The bubble sits on the chip's corner and adds no width. Black with a white
   number; the dot beside the name carries the status. */
.bub {
  position: absolute;
  top: -0.55em;
  right: -0.55em;
  min-width: 1.5em;
  height: 1.5em;
  padding: 0 0.35em;
  border-radius: 0.75em;
  display: grid;
  place-items: center;
  font-style: normal;
  font-weight: 700;
  font-size: max(11px, 0.72em);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.bub.late, .bub.early { background: var(--bubble); color: var(--bubble-ink); }

/* ---- Cells: one background draws every line; only marks are elements. ---- */
#cells {
  position: absolute;
  left: 0; right: 0;
  top: var(--hh);
  height: var(--rows-h);
  z-index: 1;
  background-color: var(--grid);
  background-image:
    linear-gradient(to right, var(--grid-line) var(--gap), transparent 0),
    linear-gradient(to bottom, var(--grid-line) var(--gap), transparent 0);
  background-size: var(--pitch) var(--pitch);
}
.sx, .sy { position: absolute; width: 1px; height: 1px; pointer-events: none; }
/* Chrome only snaps to a marker it can see, so column markers run the full height
   and row markers the full width. */
.sx { left: calc(var(--c) * var(--pitch)); top: 0; height: 100%; scroll-snap-align: none start; }
.sy { top: calc(var(--r) * var(--pitch)); left: 0; width: 100%; scroll-snap-align: start none; }

.run {
  position: absolute;
  left: calc(var(--c) * var(--pitch) + var(--gap));
  top: calc(var(--r) * var(--pitch) + var(--gap));
  width: calc(var(--len) * var(--pitch) - var(--gap));
  height: var(--cell);
  /* Tint each cell but leave the gaps clear, so the grid lines run through. */
  background: linear-gradient(to right, var(--state-late) calc(var(--pitch) - var(--gap)), transparent 0) 0 0 / var(--pitch) 100%;
}
/* Past 30 days, late goes rosy brown. Then the color drains out: gray for
   collections, charcoal for a lawsuit. */
.run.over { background-image: linear-gradient(to right, var(--state-overdue) calc(var(--pitch) - var(--gap)), transparent 0); }
.run.coll { background-image: linear-gradient(to right, var(--state-collections) calc(var(--pitch) - var(--gap)), transparent 0); }
.run.suit { background-image: linear-gradient(to right, var(--state-lawsuit) calc(var(--pitch) - var(--gap)), transparent 0); }
.m {
  position: absolute;
  left: calc(var(--c) * var(--pitch) + var(--gap));
  top: calc(var(--r) * var(--pitch) + var(--gap));
  width: var(--cell);
  height: var(--cell);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  overflow: hidden;
}
.m svg { width: calc(var(--cell) * 0.36); height: calc(var(--cell) * 0.36); flex: none; }
.m b { font-weight: 600; font-size: 13px; line-height: 1; white-space: nowrap; }
/* Numbers are set in Satoshi: the dates across the top, the counts in the squares,
   the badges, the due day or order in each dot, and the key's sample numbers. The
   date row is the one exception to "Satoshi for numbers only": its month names and
   TODAY are Satoshi too, so the row reads as one piece instead of two fonts. */
.d b, .d i, .m b, .bub, .dot.num, .sw b { font-family: var(--font-num); }

/* Loudness follows urgency: late is loud, due soon is medium, paid fades. */
.m { color: var(--icon); }
.m b { color: var(--text-on-state); }
.k-paid { background: var(--state-paid); }
/* Paid part: Bondi on top, Emerald on the bottom, half full, so it doesn't read as paid in full. */
.k-partial { background: var(--state-partial); }
/* The check sits on the Bondi half: near-black #121212 (trying it, 2026-09-28), an
   exception to never running dark on Bondi, to see if it balances the Emerald half. */
.k-partial svg { color: #121212; }
.k-early { background: var(--state-early); color: var(--state-early-ink); }
.k-early b { color: var(--state-early-ink); }
.k-missed { background: var(--state-missed); }
.k-count b { font-size: 16px; font-weight: 700; }
.k-poop { background: var(--state-overdue); }
/* Coming up, on the plain blue: a white number of days, 16px bold, over "days".
   Bold text this size counts as large, so white's 3.75:1 on bondi passes. */
.k-due, .k-soon { color: var(--text-on-grid); }
.k-due b, .k-soon b { color: var(--text-on-grid); font-size: 16px; font-weight: 700; }
/* A promise is its own thing: a solid white square with a midnight handshake. */
.k-promise { background: var(--state-promise); color: var(--state-promise-ink); }
.k-promise svg, .k-collections svg, .k-lawsuit svg { width: calc(var(--cell) * 0.52); height: calc(var(--cell) * 0.52); }
/* Collections and lawsuit: white icon on dark gray, and white words on their runs. */
.k-collections { background: var(--state-collections); color: var(--text-on-dark); }
.k-lawsuit { background: var(--state-lawsuit); color: var(--text-on-dark); }
.k-count.coll b, .k-count.suit b, .k-count.coll small, .k-count.suit small { color: var(--text-on-dark); }
.k-count small { color: var(--text-on-state); }
.m small { font-size: 13px; font-weight: 600; line-height: 1; }

/* ---- Semantic zoom: each level draws less, not the same thing smaller. ---- */
/* Two weeks: icons only; words leave the narrow date cells; payday's number is green. */
.z1 .m b, .z2 .m b, .z1 .m small, .z2 .m small { display: none; }
/* At 50% a due date keeps its number, since it has no icon to fall back on. */
.z1 .k-due b, .z1 .k-soon b { display: block; font-size: 13px; }
.z1 .k-count.coll::before, .z1 .k-count.suit::before { background: var(--text-on-dark); }
.z1 .k-count::before {
  content: ""; width: 30%; height: 30%; border-radius: 50%; background: var(--text-on-state);
}
.z1 .d.today i, .z2 .d.today i,
.z1 .d.payday:not(.first) i, .z2 .d.payday:not(.first) i { display: none; }
/* At 50%, the marker row (today's bar, payday's ring, or here's Bondi bar) can land
   on the 1st too, and OCT was sitting dead center in that same row, right where any
   of those draw. The word moves to straddle the line between the two rows instead,
   center of the word on the divider, clear of whatever's marking that row. Breaks the
   stay-in-your-row rule on purpose, the same way the 25% view already does. */
/* At 50%, the chip's own top/bottom gap around the label is 4px; left/right was
   stuck at 100%'s 11px, wider than the chip needs and squeezing the name inside it. */
.z1 .chip { padding: 0 4px; }
.z1 .d.first i {
  position: absolute; left: 0; right: 0; top: var(--pitch); transform: translateY(-50%);
  height: auto; margin: 0; display: block; text-align: center;
  font-size: 9px; letter-spacing: 0.02em;
}
/* Payday: the date is circled, like on a paper calendar. The number stays ink. */
.d.payday b { position: relative; }
.d.payday:not(.today) b::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: min(1.9em, calc(var(--cell) + 2px)); aspect-ratio: 1;
  transform: translate(-50%, -50%); border-radius: 50%;
  box-shadow: inset 0 0 0 3px var(--payday-ring);
}
/* 3px at 100%, 2px at 50%, 1px at 25%, thinning as the ring gets smaller. */
.z1 .d.payday:not(.today) b::before { box-shadow: inset 0 0 0 2px var(--payday-ring); }
/* Grown 2px wider (a pixel out on each side), spilling past its square on purpose. */
.z2 .d.payday:not(.today) b::before { box-shadow: inset 0 0 0 1px var(--payday-ring); width: 1.7em; }
/* The 25% ring is wider than its tiny column; it can spill into the white around it. */
/* Paydays sit in front of the dates beside them (and today's raised tile), so a
   ring that spills past its column never gets cut off. */
.d.payday { overflow: visible; z-index: 2; }
/* Paydays keep their number even in the 25% view. */
.z2 .d.payday b { visibility: visible; }
/* Zoomed out, the date row is two grid squares tall: the number in the top square,
   the marker (today's bar, payday's bar, the month) in the square below. */
.z1 .d, .z2 .d { justify-content: flex-start; gap: 0; }
.z1 .d b, .z2 .d b { order: 1; height: var(--cell); display: flex; align-items: center; justify-content: center; }
.z1 .d i, .z2 .d i { order: 2; height: var(--cell); margin-top: var(--gap); display: flex; align-items: center; }
/* At 50% the bubble rides inside the chip, after the name, and the chip grows to fit
   both, so it never sits over the end of the name. */
.z1 .bub {
  position: static; flex: none; font-size: 11px;
  /* 3px of chip around it on the top, right and bottom alike. */
  height: calc(min(34px, var(--cell) - 4px) - 6px); min-width: calc(min(34px, var(--cell) - 4px) - 6px); border-radius: 999px;
}
.z1 .chip:has(.bub) { padding-right: 3px; }
/* Inside a dark chip a black bubble went muddy: it turns white with a Midnight green
   number. On the white chips (collections, lawsuit) it stays black. */
.z1 .chip:not(.inv) .bub.late, .z1 .chip:not(.inv) .bub.early { background: var(--c-white); color: var(--c-midnight); }
/* The bubble sits on the chip's inner corner, the one facing the middle of the
   screen, so it can't run off the edge when the labels are on the right. */
#labels.right .bub { right: auto; left: -0.55em; }
.z1 #labels.right .bub { order: -1; }

/* Gauge: colors only. No icons, no words. Mondays and the 1st keep numbers;
   the month becomes a divider line with its name. Chips drop to their dot. */
.z2 .m svg { display: none; }
.z2 .d:not(.a):not(.today) b { visibility: hidden; }
/* A Monday's number that would crash into a month's name or line moves to a day
   with room (see untangle in the board code). */
.z2 #dates .d.moved-off b { visibility: hidden; }
.z2 #dates .d.moved-to b { visibility: visible; }
.z2 .d.first { background: transparent; color: var(--text); box-shadow: calc(-1 * var(--gap)) 0 0 var(--month); }  /* sits on the grid line */
/* Corrected 2026-09-26, a third time: the number and the marker row both stayed
   contested no matter where OCT sat between them. Simplest fix wins: the month
   name replaces the day number outright, in the number's own slot. Once you see
   a month name at this zoom you know it's the 1st, so there's nothing the digit
   was telling you that OCT doesn't already say. Left-aligned, since the word is
   wider than the cell and has nothing but its own month's empty cells to run
   into on the right; centering would spill left across the divider instead. */
.z2 .d.first b { display: none; }
/* Corrected again, same day: OCT was reading smaller than the numbers around it
   and sitting flush on the divider. Same size and weight as a date number, and a
   few px clear of the line, the way a typed space would sit next to it. Its left
   edge lines up with the first future dot's (14% into the square), so the word
   and the dots below it share one left edge. */
.z2 .d.first i {
  order: 1; position: static; align-self: flex-start; margin: 3px 0 0 calc(var(--cell) * 0.14); height: var(--cell); line-height: normal;
  display: flex; align-items: center; justify-content: flex-start;
  font-size: clamp(12px, calc(var(--cell) * 0.34), 20px); font-weight: 400;
  color: var(--text); white-space: nowrap;
}
.z2 .nm, .z2 .bub { display: none; }
.z2 .chip { padding: 0 4px; gap: 0; margin-left: 4px; height: calc(var(--cell) - 2px); }

/* ---- The add row: a plus under every date. ---- */
.addrow {
  position: absolute;
  left: 0; right: 0;
  top: calc(var(--r) * var(--pitch) + var(--gap));
  height: var(--cell);
  background: linear-gradient(to right, transparent var(--gap), var(--add-row) 0) 0 0 / var(--pitch) 100%;
}
.add {
  position: absolute;
  left: calc(var(--c) * var(--pitch) + var(--gap) + var(--cell) / 2);
  top: calc(var(--r) * var(--pitch) + var(--gap) + var(--cell) / 2);
  width: 0; height: 0;
}
.add::before, .add::after {
  content: ""; position: absolute; background: var(--add-mark); border-radius: 1px;
  width: clamp(8px, calc(var(--cell) * 0.3), 16px); height: 2px;
  transform: translate(-50%, -50%);
}
.add::after { transform: translate(-50%, -50%) rotate(90deg); }

/* ---- The lens: fixed in the center. A raised column, shadow down both sides, no
   colored frame, the white lines untouched. It closes on the add row's base. ---- */
#lens {
  position: absolute;
  top: var(--gap);
  z-index: 5;
  pointer-events: none;
  border-radius: var(--gap) var(--gap) 0 0;
  /* Shadow down both sides and across the top; it starts 2px below the top of the
     date row, so a white edge shows above it the way the white line shows below. */
  box-shadow: -5px 0 9px -4px var(--shadow-lens), 5px 0 9px -4px var(--shadow-lens), 0 -6px 9px -4px var(--shadow-lens);
}
#lens.closed { border-radius: var(--gap); box-shadow: -5px 0 9px -4px var(--shadow-lens), 5px 0 9px -4px var(--shadow-lens), 0 -6px 9px -4px var(--shadow-lens), 0 6px 9px -4px var(--shadow-lens); }

/* Today in the date row: flat, marked by the word and the emerald bar. The raised
   column below is the lift. */
.d.today {
  z-index: 1;
  background: var(--today-tile);
  color: var(--text);
}
/* The raised column's shadow wraps its top too: a soft shadow falls into the flat
   date row, exactly the column's width. */
.today-cap {
  position: absolute; pointer-events: none;
  left: calc(var(--c) * var(--pitch));
  top: var(--hh);
  width: calc(var(--pitch) + var(--gap));
  height: 12px;
  box-shadow: 0 -6px 9px -4px var(--shadow-lens);
}
.lens-center .today-cap { display: none; }
.d.today::after {
  content: ""; position: absolute; left: 28%; right: 28%; bottom: 5px;
  height: 4px; border-radius: 2px; background: var(--today-bar);
}
.z1 .d.today::after, .z2 .d.today::after { top: calc(var(--pitch) + var(--cell) / 2 - 2px); bottom: auto; }

/* ---- Header controls, each one a block of grid cells: the timer on the left, today on the right.
   Today is always there: the way back sits next to where you are. ---- */
.topbtn {
  position: absolute;
  z-index: 12;
  border: 0;
  border-radius: var(--gap);
  background: transparent;
  color: var(--button-ink);
  display: grid;
  place-items: center;
  cursor: pointer;
}
.topbtn svg { width: 25px; height: 25px; }
.topbtn:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* ---- Bottom corner: zoom, alone, on the side opposite the labels. grid.js sets
   its place: 16px from the side and the bottom. ---- */
#corner {
  position: absolute;
  z-index: 10;
  bottom: calc(16px + env(safe-area-inset-bottom));
  display: flex;
}
#corner.moves { transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1); }
#corner .round {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 0;
  background: var(--button);
  color: var(--button-ink);
  box-shadow: var(--shadow-float);
  display: grid;
  place-items: center;
  cursor: pointer;
}
#corner .round svg { width: 25px; height: 25px; }
#corner .round:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

.sheet[hidden], #devtools[hidden], .sheet button[hidden] { display: none; }
/* ---- The menu: a card in the middle of the screen, over the dimmed board. ---- */
#menu {
  position: absolute;
  z-index: 20;
  /* Top-anchored, not centered: flush against the bottom of the whisper bar (the
     one thing up there that can carry a message), covering the date row entirely,
     not sitting below it (this isn't about picking a date). Grows down to 16px
     above the bottom (or the gesture area) if it needs to. That space, top or
     bottom, is the dimmed board, tappable to close same as anywhere else. */
  left: 50%; top: var(--top-h, 56px); transform: translateX(-50%);
  /* 16px from the screen's edges on a phone; no wider than 480px on a big screen. */
  width: min(480px, calc(100% - 32px));
  max-height: calc(100% - var(--top-h, 56px) - 16px - env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--shadow-sheet);
  overflow: hidden;
}
/* A face wipes in flat from the right, over the one before; the card's height follows. */
#menu .face { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; background: var(--surface); }
#menu .face.lift { position: absolute; top: 0; left: 0; right: 0; max-height: 100%; }
#menu.wiping { transition: height 0.25s ease-out; }
#menu.wiping .face { transition: transform 0.25s ease-out; }
#menu .face[hidden] { display: none; }
.menu-head { flex: none; display: flex; align-items: center; gap: 8px; padding: 8px 16px 0 6px; }
.menu-head h2 { margin: 0; font-size: 18px; font-weight: 700; color: var(--text); }
.sheet .menu-head button { min-height: 44px; width: 44px; padding: 0; border: 0; box-shadow: none; justify-content: center; }
.menu-head svg { width: 22px; height: 22px; color: var(--menu-icon); }
.menu-body {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* A white fade at the bottom while there's more to scroll to. It sits in the space
   under the last item, so at the very end it covers nothing. */
.menu-body::after {
  content: ""; position: sticky; bottom: -24px; flex: none;
  height: 24px; margin: -8px -16px -24px;
  background: linear-gradient(to bottom, transparent, var(--surface));
  pointer-events: none;
}
.menu-body hr { width: 100%; margin: 2px 0; border: 0; border-top: 1px solid var(--hairline); }
/* Two buttons with no divider between them (the front menu's top two) still need 16px, not the container's 8px. */
.menu-body .act + .act { margin-top: 8px; }
#devtools { display: flex; flex-direction: column; gap: 8px; padding-top: 8px; border-top: 1px solid var(--hairline); }
/* White buttons are solid: no outline, the same soft shadow as the whisper's answer
   buttons. Fields you type in keep their thin line; they aren't buttons. */
.sheet button {
  min-height: 48px;
  padding: 0 16px;
  border: 0;
  box-shadow: var(--shadow-button);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: 16px;
  text-align: left;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* Each line: the words on the left, its one icon on the right. The whole line turns
   the card. */
.sheet button.act { justify-content: flex-start; gap: 12px; }
.sheet .act svg { flex: none; width: 20px; height: 20px; margin-left: auto; color: var(--menu-icon); }
.sheet .act span { margin-left: auto; font-size: 13px; color: var(--text-2); }
.sheet .act span + svg { margin-left: 0; }
/* The doing lines lead. */
#finish-open, #addbills-open, #menu-next { font-weight: 600; }
/* Where you stand: the snapshot is plain text, no tap; the h3 above it already
   reads (it's the first thing in the menu, .face h3 handles its spacing). */
#menu-snapshot { margin: 0; font-size: 15px; color: var(--text-2); }
.menu-body .snapshot + hr { margin-top: 16px; }
/* Settings, opened in place. */
#settings { padding: 0; }
#settings h3 { margin: 4px 0 6px; font-size: 14px; font-weight: 700; color: var(--text); }
#settings p { margin: 0 0 12px; font-size: 14px; line-height: 1.4; color: var(--text-2); }
/* Reset is a temporary tool, not an error: a 5/5 dashed line in the text color, no
   red and no shadow. The dash is drawn so its length can be set; a plain dashed
   border can't be. */
.sheet button.danger {
  width: 100%; justify-content: center; font-weight: 600; box-shadow: none;
  background: var(--surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' rx='8' ry='8' fill='none' stroke='%23073B4C' stroke-width='4' stroke-dasharray='5 5'/%3E%3C/svg%3E");
}
.menu-foot { flex: none; padding: 12px 16px; border-top: 1px solid var(--hairline); display: flex; gap: 12px; justify-content: flex-end; }
#menu[hidden] { display: none; }
.menu-foot button { min-height: 44px; padding: 0 16px; border: 0; box-shadow: none; border-radius: 8px; background: var(--pressed); color: var(--pressed-ink); font-weight: 600; justify-content: center; }
.menu-foot button:disabled { opacity: 0.4; }
.menu-foot button.quiet { background: transparent; color: var(--text); }
/* What used to be drawers, now on the back of the card. */
#menu .face h3 { margin: 16px 0 8px; font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-2); }
#menu .face ul { list-style: none; margin: 0; padding: 0; }
#menu .face li { display: flex; align-items: center; gap: 12px; min-height: 44px; font-size: 16px; line-height: 1.35; }
#menu .face .how li { display: block; padding: 6px 0; min-height: 0; }
#menu .face .lede { margin: 0 0 4px; font-size: 16px; color: var(--text-2); }
/* The chip wall's chips are shaped like the bill names on the board: 8px corners,
   midnight and flat when chosen, white with a soft shadow when not. 16px apart. */
#menu .face .picks button, #menu .face .named button { border-radius: 8px; padding: 0 14px; min-height: 44px; font-size: 16px; font-weight: 600; display: inline-flex; gap: 6px; }
#menu .face .picks .pick.on, #menu .face .named .pick.on { background: var(--chip); color: var(--chip-ink); border-color: var(--chip); }
#menu .face .picks, #menu .face .named { gap: 16px; }
#menu .face .named:not(:empty) { margin-top: 16px; }
/* How it works: the key is a little column of the board, squares 2px apart with no
   keyline (only the white promise square keeps one, or it would vanish), each line
   centered on its square, 16px away. Instructions sit 16px apart. */
#menu .face .key { display: grid; gap: 2px; }
#menu .face .key li { gap: 16px; min-height: 44px; }
#menu .face .key .sw { width: 44px; height: 44px; box-shadow: none; }
#menu .face .key .sw.k-promise, #menu .face .key .sw.k-here { box-shadow: inset 0 0 0 1px var(--hairline); }

/* The dot key: a real chip at its 100% height (34px, 8px corners), midnight like the
   real thing, with the plain 8px dot (the same size a chip's own dot shrinks to when
   dates are hidden) centered inside it. Collections and lawsuit chips invert to white
   on the board, so their swatch does too, with a keyline (a white square needs one, or
   it vanishes on the white card). */
#menu .face .dotkey { gap: 2px; }
#menu .face .dotkey .dot-sw {
  width: 34px; height: 34px; border-radius: 8px; box-shadow: none;
  background: var(--chip); display: grid; place-items: center;
}
#menu .face .dotkey .dot-sw::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--status-ok); }
#menu .face .dotkey .dot-sw.soon::before { background: var(--status-due-soon); }
#menu .face .dotkey .dot-sw.late::before { background: var(--status-late); }
#menu .face .dotkey .dot-sw.overdue::before { background: var(--status-overdue); }
#menu .face .dotkey .dot-sw.early::before { background: var(--status-early); }
#menu .face .dotkey .dot-sw.collections, #menu .face .dotkey .dot-sw.lawsuit { background: var(--chip-inv); box-shadow: inset 0 0 0 1px var(--hairline); }
#menu .face .dotkey .dot-sw.collections::before { background: var(--status-collections); }
#menu .face .dotkey .dot-sw.lawsuit::before { background: var(--status-lawsuit); }
#menu .face .dotkey .dot-sw.unset::before { background: var(--c-stone); }
#menu .face .how { display: grid; gap: 16px; }
#menu .face .how li { padding: 0; line-height: 1.4; }

/* ---- The menu chip: Bondi, with three dots, over the plus row. ---- */
.chip.menu-chip { background: var(--menu-chip); color: var(--menu-chip-ink); padding: 0 14px; }
.chip.menu-chip svg { width: 22px; height: 22px; }
.chip.menu-chip:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
/* At 25% the wrench doesn't survive at 14px (it clipped, 14px wide but stuck at 22px
   tall); drops to the same plain dot every other chip uses there. */
.z2 .chip.menu-chip svg { display: none; }
.z2 .chip.menu-chip::after { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--c-white); }
/* Out of the way while a new bill's name is typed into the plus row. */
.adding .menu-chip { visibility: hidden; }
/* While you type a new bill's name, the zoom button steps aside so it can't sit on
   top of the name (it lives on the side opposite the names). */
.adding #corner { visibility: hidden; }

/* ---- The message board: two-sided tiles. Rotate only. ---- */
.flip {
  position: absolute;
  left: 0;
  z-index: 6;
  overflow: hidden;
  background: var(--grid-line);
}
.flip > i {
  position: absolute;
  width: var(--cell);
  height: var(--cell);
  transform-style: preserve-3d;
  animation: flip 2.6s ease-in-out both;
  animation-delay: var(--d);
}
.flip > i { font-style: normal; }
.flip .face, .flip .back {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
/* A front matches its cell exactly: its tint, then its mark's color on top. */
.flip .face, .flip .back { background: var(--grid); }
.flip .face.tint-late { background: var(--state-late); }
.flip .face.tint-over { background: var(--state-overdue); }
.flip .face.tint-coll, .flip .face.k-collections { background: var(--state-collections); }
.flip .face.tint-suit, .flip .face.k-lawsuit { background: var(--state-lawsuit); }
.flip .face.tint-add { background: var(--add-row); }
.flip .face.k-paid { background: var(--state-paid); }
.flip .face.k-partial { background: var(--state-partial); }
.flip .face.k-early { background: var(--state-early); }
.flip .face.k-missed { background: var(--state-missed); }
.flip .face.k-poop { background: var(--state-overdue); }
.flip .face.k-promise { background: var(--state-promise); }
.flip .m.face { left: 0; top: 0; width: 100%; height: 100%; }
.flip .back { transform: rotateY(180deg); }
.flip .back.on { background: var(--message); }
@keyframes flip {
  0% { transform: perspective(500px) rotateY(0); }
  15%, 85% { transform: perspective(500px) rotateY(180deg); }
  100% { transform: perspective(500px) rotateY(360deg); }
}
/* The lens, today's tint and the labels step aside while a message plays. */
#lens, #labels { transition: opacity 0.2s ease; }
.flipping #lens, .flipping #labels { opacity: 0; }
/* After a drag the bill names stay up, above the turning tiles. */
.flipping.keep-labels #labels { opacity: 1; z-index: 7; }
@keyframes fade {
  0%, 100% { opacity: 0; }
  15%, 85% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .flip > i { animation: none; }
  .flip .back { transform: none; animation: fade 2.6s ease-in-out both; animation-delay: var(--d); }
  .chip { transition: none; }
}

/* ---- Marking: one cell turns over. Rotate only. ---- */
.flip1 {
  position: absolute;
  z-index: 3;
  left: calc(var(--c) * var(--pitch) + var(--gap));
  top: calc(var(--r) * var(--pitch) + var(--gap));
  width: var(--cell);
  height: var(--cell);
  font-style: normal;
  pointer-events: none;
}
.flip1 > .face, .flip1 > .back {
  position: absolute;
  inset: 0;
}
.flip1 .m.face, .flip1 .back > .face { position: absolute; inset: 0; width: 100%; height: 100%; left: 0; top: 0; }
/* A wipe: the new state sweeps in from one edge, inside the square only. */
.flip1 > .back { animation: 0.3s ease-out both; }
.flip1.from-right > .back { animation-name: wipe-right; }
.flip1.from-left > .back { animation-name: wipe-left; }
.flip1.from-top > .back { animation-name: wipe-top; }
.flip1.from-bottom > .back { animation-name: wipe-bottom; }
@keyframes wipe-right { from { clip-path: inset(0 0 0 100%); } to { clip-path: inset(0); } }
@keyframes wipe-left { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
@keyframes wipe-top { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0); } }
@keyframes wipe-bottom { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0); } }
@media (prefers-reduced-motion: reduce) { .flip1 { display: none; } }

/* The tile's faces keep the cell's own color on both sides: the mark's color, the
   late run's tint, or plain blue only when the cell really is empty. */
.flip1 .face:not([class*="k-"]):not([class*="tint-"]) { background: var(--grid); }
.flip1 .tint-late:not(.m), .flip1 .k-count.tint-late { background: var(--state-late); }
.flip1 .tint-over:not(.m), .flip1 .k-count.tint-over { background: var(--state-overdue); }
.flip1 .tint-coll:not(.m), .flip1 .k-count.tint-coll { background: var(--state-collections); }
.flip1 .tint-suit:not(.m), .flip1 .k-count.tint-suit { background: var(--state-lawsuit); }

/* ---- The whisper's messages center normally; the pinned dot is only for dates. ---- */
#where.msg { display: block; text-align: center; overflow: hidden; text-overflow: ellipsis; }
/* An Undo beside the message, for as long as the message shows. */
#where .undo { margin-left: 12px; min-height: 36px; padding: 0 14px; border: 0; border-radius: 8px; font: inherit; font-weight: 600; vertical-align: middle; cursor: pointer; }
@keyframes undo-bar { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) { .undo-bar { animation-timing-function: steps(9); } }

/* ---- Adding a bill: the name is typed into a chip in the add row. ---- */
.addchip {
  position: absolute;
  z-index: 4;
  top: calc(var(--r) * var(--pitch) + var(--gap) + (var(--cell) - min(34px, calc(var(--cell) - 4px))) / 2);
  margin-left: 0;
  max-width: none;
  transition: none;
}
.addchip input {
  min-width: 6ch;
  border: 0;
  outline: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  caret-color: var(--c-white);
}

/* ---- A bill's card: the same centered card as the menu, dead center over the
   dimmed board. Swipe the header down to dismiss, like tapping the dimmed board. ---- */
.drawer {
  position: absolute;
  z-index: 20;
  /* Same top-anchored rule as the menu: see #menu for why. */
  left: 50%; top: var(--top-h, 56px); transform: translateX(-50%);
  width: min(480px, calc(100% - 32px));
  max-height: calc(100% - var(--top-h, 56px) - 16px - env(safe-area-inset-bottom));
  display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--shadow-sheet);
  color: var(--text);
}
/* The bill's card is off-white, the board's own page color, so its white fields and
   buttons read first. One rhythm inside: 6px from a label to its field, 16px
   between everything else, nothing above the first row. */
#bill { background: var(--page); }
#bill .callcard { background: var(--surface); }
#bill-body > :first-child, #bill-body > .pair:first-child > .field { margin-top: 0; }
#bill h3 { margin: 16px 0 0; }
.drawer[hidden] { display: none; }
.drawer header {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 16px 16px 8px;
}
.drawer header .dot { width: 12px; height: 12px; flex: none; }
.drawer h2 { margin: 0; font-size: 20px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The bill's name, and under it its status in the grid's words, both lined up with
   the name, not the dot. The name gets the whole line and gives way with "…"; the
   header's own padding keeps both 16px or more from the card's right edge. The dot
   stays centered on the name's line. */
#bill header { align-items: flex-start; }
#bill header .dot { margin-top: 8px; }
#bill-title { min-width: 0; }
#bill-title .bt-name { display: block; line-height: 28px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#bill-title .bt-status { display: block; font-size: 14px; line-height: 18px; font-weight: 400; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#bill-title .bt-status:empty { display: none; }
/* Short fields share a line on thirds, 16px apart, the same thirds as Caught up /
   Behind / Not sure, so every short field's edge lines up with a button's edge:
   Due, Days behind and What I owe take a third; Paid on and All of it take two. */
.drawer .pair { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; align-items: end; }
.drawer .pair.due-name > :first-child, .drawer .pair.days-stage > :last-child, .drawer .pair.owe-phone > :last-child,
.drawer .pair.pay-when > :first-child, .drawer .pair.pay-go > :first-child { grid-column: span 2; }
/* Repeats and Month split the row in half. Month is always there, grayed out and
   locked unless the bill repeats yearly, so the row never has a hole in it. */
.drawer .pair.how-often { grid-template-columns: 1fr 1fr; }
.drawer .field select:disabled { opacity: 0.45; }
/* A field with its square button: call beside the phone number, a rocket beside
   the website. Always there; grayed out and inert until there's something to call
   or open, the same as Month until a bill repeats annually. */
.drawer .withbtn { display: flex; gap: 16px; margin-top: 6px; }
.drawer .field .withbtn input { margin-top: 0; flex: 1; min-width: 0; }
.drawer .sqbtn {
  flex: none; width: 48px; height: 48px; border-radius: 8px; display: grid; place-items: center;
  background: var(--surface); color: var(--text); box-shadow: var(--shadow-button);
}
.drawer .sqbtn[aria-disabled="true"] { opacity: 0.45; box-shadow: none; pointer-events: none; }
.drawer .sqbtn svg { width: 22px; height: 22px; }
/* Switching to yearly after this year's date: one question, two answers on the
   same thirds as everything else (Paid, Not yet, then an empty third). */
#f-yearly-ask[hidden] { display: none; }
/* The three things the board needs (name, due day, where it stands) carry an
   asterisk in the text color; anything without one is extra. */
.drawer .req { color: var(--text); margin-left: 2px; }
#f-yearly-ask .ask-q { margin: 16px 0 6px; }
.drawer .pair.yearly-answer { align-items: stretch; }
.drawer .yearly-answer button {
  min-height: 48px; border: 0; border-radius: 8px; box-shadow: var(--shadow-button);
  background: var(--surface); color: var(--text); font-size: 16px; font-weight: 600;
}
.drawer .yearly-answer button.on { background: var(--pressed); color: var(--pressed-ink); box-shadow: none; }
/* Labels stay on one line; "Days behind" is exactly as wide as its box on a phone,
   and on a narrower one it may run a few px into the 16px gap, never into the next
   label. */
.drawer .pair > .field { min-width: 0; white-space: nowrap; }
/* At a third, the due day's box still fits "31st" beside the usual arrow. */
.drawer h3 { margin: 20px 0 8px; font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-2); }
.drawer ul { list-style: none; margin: 0; padding: 0; }
.drawer li { display: flex; align-items: center; gap: 12px; min-height: 44px; font-size: 16px; line-height: 1.35; }
.drawer .how li { display: block; padding: 6px 0; min-height: 0; }
.sw {
  flex: none; width: 40px; height: 40px;
  display: grid; place-items: center;
  box-shadow: inset 0 0 0 2px var(--grid-line), 0 0 0 1px var(--hairline);
  color: var(--icon);
}
.sw svg { width: 20px; height: 20px; }
.sw b { font-size: 16px; font-weight: 700; }
.sw.k-good { background: var(--grid); }
.sw.k-due { background: var(--grid); color: var(--text-on-grid); }
.sw.k-paid { background: var(--state-paid); }
.sw.k-partial { background: var(--state-partial); }
.sw.k-early { background: var(--state-early); color: var(--state-early-ink); }
.sw.k-promise { background: var(--state-promise); color: var(--state-promise-ink); }
.sw.k-missed { background: var(--state-missed); }
.sw.k-late { background: var(--state-late); color: var(--text-on-state); }
.sw.k-over { background: var(--state-overdue); color: var(--text-on-state); }
.sw.k-coll { background: var(--state-collections); color: var(--text-on-dark); }
.sw.k-suit { background: var(--state-lawsuit); color: var(--text-on-dark); }

/* ---- The chip wall. ---- */
.drawer .lede { margin: 0 0 4px; font-size: 16px; color: var(--text-2); }
.picks, .named { display: flex; flex-wrap: wrap; gap: 16px; }
.named:not(:empty) { margin-top: 8px; }
.pick {
  min-height: 44px; padding: 0 14px; border-radius: 8px;
  border: 0; box-shadow: var(--shadow-button); background: var(--surface); color: var(--text);
  font-size: 16px; display: inline-flex; align-items: center; gap: 6px;
}
/* Chosen: filled and flat, like a pressed key. */
.pick.on { background: var(--chip); color: var(--chip-ink); border-color: var(--chip); box-shadow: none; }
.pick em { font-style: normal; font-weight: 700; }
/* Same field as everywhere else: thin gray line, 8px corners, 16px above it. */
.nameit input {
  width: 100%; min-height: 44px; margin: 16px 0 0; padding: 0 14px;
  border: 1px solid var(--hairline); border-radius: 8px; background: var(--surface);
  font: 400 16px var(--font); color: var(--text);
}
/* A new bill with no due day yet: a question mark in its dot, and a ring while asked. */
.dot.num.unset { background: var(--c-stone); }
.lrow.asking .chip { box-shadow: 0 0 0 3px var(--c-white), 0 0 0 5px var(--chip); }
/* A bill that just arrived wears the same ring for a moment, then lets it go. */
.lrow .chip { transition: box-shadow 0.4s ease; }
.lrow.arrived .chip { box-shadow: 0 0 0 3px var(--c-white), 0 0 0 5px var(--chip); }

/* The bar that asks for a due day, over the top of the grid. */
#setday {
  position: absolute; z-index: 12; left: 50%; transform: translateX(-50%);
  top: calc(var(--top-h, 60px) + var(--hh, 56px) + 8px);
  display: flex; align-items: center; gap: 12px;
  padding: 6px 6px 6px 16px; border-radius: 26px;
  background: var(--chip); color: var(--chip-ink); box-shadow: var(--shadow-sheet);
  font-size: 15px; font-weight: 600; white-space: nowrap; max-width: calc(100% - 24px);
}
#setday[hidden] { display: none; }
#setday span { overflow: hidden; text-overflow: ellipsis; }
#setday button {
  min-height: 40px; padding: 0 14px; border: 0; border-radius: 20px;
  background: var(--c-white); color: var(--text); font-size: 15px; font-weight: 600;
}

/* ---- The dimmed board behind an open drawer. ---- */
#scrim { position: absolute; inset: 0; z-index: 19; background: rgba(var(--c-shade), 0.35); }
#scrim[hidden] { display: none; }

/* ---- Bills missing something critical: a dotted underline under the name. ---- */
.chip { pointer-events: auto; cursor: pointer; }
.chip.todo .nm { text-decoration: underline dotted var(--c-bondi); text-decoration-thickness: 2px; text-underline-offset: 3px; }

/* ---- A bill's card and the finish list. ---- */
/* What a bill still needs is an invitation, not a warning: Bondi, never Sunglow
   (Sunglow means missed). A pale Bondi note so Midnight green text passes AA. */
.drawer .needs { margin: 4px 0 12px; padding: 10px 12px; border-radius: 8px; background: color-mix(in srgb, var(--c-bondi) 24%, #fff); color: var(--c-midnight); font-weight: 600; }
.drawer .needs { transition: opacity 0.25s ease; }
.drawer .needs.gone { opacity: 0; }
.drawer .field { display: block; margin: 16px 0 0; font-size: 14px; font-weight: 600; color: var(--text-2); border: 0; padding: 0; }
.drawer .field legend { padding: 0; margin-bottom: 6px; }
.drawer .field input, .drawer .field select {
  display: block; width: 100%; min-height: 48px; margin-top: 6px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: 8px; background: var(--surface);
  color: var(--text); font: 400 16px var(--font);
}
/* Notes: the same field, growing with what you write until it's as tall as it is
   wide, then scrolling inside. */
.drawer .field textarea {
  display: block; width: 100%; margin-top: 6px; padding: 12px;
  border: 1px solid var(--hairline); border-radius: 8px; background: var(--surface);
  color: var(--text); font: 400 16px/1.4 var(--font); resize: none; overflow-y: hidden;
}
/* A payment: when and how much, then Part of it (white) and All of it (midnight,
   with the paid check), the same pair the assistant asks with. */
.drawer .pair.pay-go { margin-top: 16px; }
.drawer .pay-go button {
  min-height: 48px; border: 0; border-radius: 8px; font-size: 16px; font-weight: 600;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--surface); color: var(--text); box-shadow: var(--shadow-button);
}
.drawer .pay-go button.go { background: var(--pressed); color: var(--pressed-ink); box-shadow: none; }
.drawer .pay-go button.go svg { width: 18px; height: 18px; color: var(--state-paid); }
/* Just tapped: the button turns into a white Undo for 9 seconds. */
.drawer .pay-go button.undoing { background: var(--surface); color: var(--text); box-shadow: var(--shadow-button); }
/* Stop tracking: a plain white button at the very end. Settings can undo it, so it
   doesn't need to look dangerous. */
.drawer button.stop {
  display: block; width: 100%; min-height: 48px; margin-top: 16px; border: 0; border-radius: 8px;
  background: var(--surface); color: var(--text); font-size: 16px; font-weight: 600; box-shadow: var(--shadow-button);
}
/* Counting down: the same button reads Undo, with a Bondi bar along its bottom
   emptying over the 9 seconds. */
.drawer button.stop.counting { position: relative; overflow: hidden; }
.drawer button.stop .undo-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; background: var(--c-bondi); transform-origin: left; animation: undo-bar 9s linear both; }
/* Under it, where to find it again, with the wrench drawn in so it matches the
   button on the board. */
.drawer .stop-note { margin: 8px 0 0; font-size: 14px; line-height: 1.4; color: var(--text-2); text-align: center; }
.drawer .stop-note svg { width: 16px; height: 16px; vertical-align: -3px; color: var(--text); }
/* Settings, Not tracking: each bill's name, and Delete beside it. */
#stopped-list { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 16px; }
#menu .face #stopped-list li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 16px; min-height: 0; }
#stopped-list b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 16px; color: var(--text); }
#stopped-list button { justify-content: center; }
.seg { display: flex; gap: 16px; }
.seg button {
  flex: 1; min-height: 48px; border: 0; box-shadow: var(--shadow-button); border-radius: 8px;
  background: var(--surface); color: var(--text); font-size: 16px; font-weight: 600;
}
.seg button.on { background: var(--pressed); color: var(--pressed-ink); border-color: var(--pressed); box-shadow: none; }
.callcard { margin-top: 12px; padding: 12px 14px; border-radius: 8px; background: var(--page); color: var(--text); font-weight: 400; font-size: 16px; }
.callcard[hidden], #f-behind[hidden] { display: none; }
.callcard p { margin: 0 0 8px; }
.callcard ol { margin: 0 0 8px; padding-left: 20px; }
.paylink { display: block; margin-top: 16px; min-height: 48px; line-height: 48px; text-align: center; border-radius: 8px; background: var(--state-paid); color: var(--text-on-state); font-weight: 700; text-decoration: none; }
.bar { height: 12px; border-radius: 6px; background: var(--page); overflow: hidden; margin: 8px 0 12px; box-shadow: inset 0 0 0 1px var(--hairline); }
.bar i { display: block; height: 100%; background: var(--state-paid); }
.tasks { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.tasks button {
  width: 100%; min-height: 56px; padding: 8px 14px; text-align: left;
  border: 0; box-shadow: var(--shadow-button); border-radius: 8px; background: var(--surface); color: var(--text);
  display: flex; flex-direction: column; gap: 2px; font-size: 16px;
}
.tasks button span { font-size: 14px; color: var(--text-2); }
#finish-open span { font-size: 13px; }

/* Dropdowns get a Phosphor caret, 20px, 16px in from the edge, in the text color. */
.drawer .sel { position: relative; display: block; margin-top: 6px; }
.drawer .sel select { appearance: none; -webkit-appearance: none; padding-right: 52px; margin-top: 0; }
/* The date field wears the same caret. The phone's own little arrow is made
   invisible and stretched over the whole field, so a tap anywhere still opens the
   date picker. */
.drawer .sel input[type="date"] { position: relative; appearance: none; -webkit-appearance: none; padding-right: 52px; margin-top: 0; }
.drawer .sel input[type="date"]::-webkit-calendar-picker-indicator { position: absolute; inset: 0; width: auto; height: auto; opacity: 0; cursor: pointer; }
/* Numbers you type get no up and down arrows. */
.drawer input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.drawer input[type="number"]::-webkit-inner-spin-button, .drawer input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.drawer .sel::after {
  content: ""; position: absolute; right: 16px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; pointer-events: none; background: var(--text);
  -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22M213.66%2C101.66l-80%2C80a8%2C8%2C0%2C0%2C1-11.32%2C0l-80-80A8%2C8%2C0%2C0%2C1%2C53.66%2C90.34L128%2C164.69l74.34-74.35a8%2C8%2C0%2C0%2C1%2C11.32%2C11.32Z%22/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%20256%20256%22%3E%3Cpath%20d%3D%22M213.66%2C101.66l-80%2C80a8%2C8%2C0%2C0%2C1-11.32%2C0l-80-80A8%2C8%2C0%2C0%2C1%2C53.66%2C90.34L128%2C164.69l74.34-74.35a8%2C8%2C0%2C0%2C1%2C11.32%2C11.32Z%22/%3E%3C/svg%3E") center / contain no-repeat;
}

/* Fields still needed: a Bondi border, matching the note. */
.drawer .field.need input, .drawer .field.need select, .drawer .field.need .seg button:not(.on) {
  border: 2px solid var(--c-bondi);
}
/* Where it stands, once chosen, takes its board color, the same as the date card. */
.drawer .seg button.on[data-stand="ok"] { background: var(--c-emerald); color: var(--c-midnight); border-color: var(--c-emerald); }
.drawer .seg button.on[data-stand="behind"] { background: var(--c-pink); color: var(--c-deep); border-color: var(--c-pink); }
.drawer .seg button.on[data-stand="unsure"] { background: var(--c-sunglow); color: var(--c-midnight); border-color: var(--c-sunglow); }

/* The sheen, off by default: it made the same state look like two colors. ?sheen
   brings it back. */
.sheen #lens { background: var(--lens-sheen); }


/* ---- Today's column, raised. It scrolls with the grid. ---- */
.today-lift {
  position: absolute; z-index: 2; pointer-events: none;
  left: calc(var(--c) * var(--pitch));
  top: 0; bottom: 0;
  width: calc(var(--pitch) + var(--gap));
  box-shadow: -5px 0 9px -4px var(--shadow-lens), 5px 0 9px -4px var(--shadow-lens), 0 6px 9px -4px var(--shadow-lens);
}
/* The fixed center frame is off by default; ?lens=center brings it back. */
#lens { display: none; }
.lens-center #lens { display: block; }
.lens-center .today-lift { display: none; }

/* ---- The date the whisper is naming: a Bondi bar, like today's green one. ---- */
#here {
  position: absolute; top: var(--gap); height: calc(var(--hh) - var(--gap));
  left: calc(var(--c) * var(--pitch) + var(--gap)); width: var(--cell);
  pointer-events: none;
}
#here[hidden] { display: none; }
#here::after {
  content: ""; position: absolute; left: 28%; right: 28%; bottom: 5px;
  height: 4px; border-radius: 2px; background: var(--here-bar);
}
.z1 #here::after, .z2 #here::after { top: calc(var(--pitch) + var(--cell) / 2 - 2px); bottom: auto; }
/* Ahead of today: at 100%, the bar breaks into 4 dots, not a solid line (3 read as
   an ellipsis, 5 leave almost no gap at this thickness). The wrapper is sized in
   calc() off --cell, not percentage padding (percentage padding measures against
   the containing block's width, the whole scrollable date row, not the cell it
   looks like it should be measuring against; calc() against --cell sidesteps that).
   The dots inside space themselves evenly by flex, not hand-measured. */
.hrow { display: none; }
#here.future::after { display: none; }
#here.future .hrow {
  display: flex; align-items: flex-end; justify-content: space-between;
  position: absolute; left: calc(var(--cell) * 0.28); width: calc(var(--cell) * 0.44);
  bottom: 5px; height: 4px;
}
#here.future .hrow .hdot { flex: none; width: 4px; height: 4px; border-radius: 50%; background: var(--here-bar); }
/* Corrected 2026-09-26, three times now. An outline read wrong on a real phone;
   dots throughout instead. 4 shrinking dots touched at small sizes (read solid
   again); 2 dots spread wide read as too far apart; 3 skinny dots read muddy and
   thinner than the bar. Settled: the dots never shrink below the bar's own 4px
   thickness (they're supposed to be its cousins, not a fading copy); only the
   count and span drop as the cell shrinks. 2 dots at both 50% and 25%, same 4px as
   100%'s dots and the solid bar. */
.z1 #here.future .hrow, .z2 #here.future .hrow {
  align-items: center; height: 4px;
  top: calc(var(--pitch) + var(--cell) / 2 - 2px); bottom: auto;
}
.z1 #here.future .hrow .hdot, .z2 #here.future .hrow .hdot { width: 4px; height: 4px; }
.z1 #here.future .hrow .hdot:nth-child(n + 3), .z2 #here.future .hrow .hdot:nth-child(n + 3) { display: none; }
/* 25%'s span widens a bit (was the same 44% as 100%/50%) so two 4px dots have a
   real gap instead of nearly touching in that tiny a box. */
.z2 #here.future .hrow { left: 14%; width: 72%; }

/* At 50% and 25%, today's raised column also gets the sheen, and travels with it. */
.z1 .today-lift, .z2 .today-lift { background: var(--lens-sheen); }

/* ---- 25% view: the big picture. ---- */
/* Date numbers sit 3px lower so payday's ring clears the top edge. */
.z2 .d b { margin-top: 3px; }
/* Chips are just their colored status dot on a midnight square that fills the
   row's first cell exactly, so no grid line runs through it. */
.z2 .chip {
  margin-left: 0; padding: 0; width: var(--cell); height: var(--cell); max-width: none;
  border-radius: 2px; justify-content: center;
}
.z2 .dot.num { font-size: 0; min-width: 0; width: 8px; height: 8px; padding: 0; border-radius: 50%; }
/* When the labels switch to the right, the tiles snap onto the last full column. */
.z2 #labels.right .chip { transform: translateX(calc(var(--vw) - var(--pitch) - var(--gap))); }
/* Every square with a mark shows a small dot in its message color, so the texture of
   what's happening reads at a glance: white on blue for due, Bondi on white for a
   promise, dark on green for paid. */
.z2 .m::after { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.z2 .k-count::before { display: none; }
/* At 50% the icons take about two thirds of the square (17px on a phone), up from
   13px, which read muddy. */
.z1 .m svg { width: calc(var(--cell) * 0.68); height: calc(var(--cell) * 0.68); }

/* Outlined icons all draw at the same size, so their bold lines are the same weight. */

/* On a payday there's no Bondi bar: when the whisper names it, its circle turns Bondi. */
.d.payday.here:not(.today) b::before { --payday-ring: var(--here-ring); }
/* Scrolling through today, its bar turns Bondi; emerald again once the grid stops. */
.d.today::after { transition: background-color 0.3s ease; }
.d.today.passing::after { background: var(--here-bar); }
/* Today's bar turns Bondi while the Bondi bar is on it, emerald again when it moves on. */
.d.today.here::after { background: var(--here-bar); }

/* The bill drawer's headline is the bill's own chip. */
#bill-title { display: block; min-height: 44px; padding: 0; box-sizing: border-box; }
/* The name and its status line sit as far below the card's top edge as the first
   field sits below them, measured by eye (the label's short, the space beside it
   is open): 8px of header padding below the status line. */
#bill header { padding-bottom: 8px; }

/* Key swatches for payday and the Bondi bar. */
.sw.k-payday { background: var(--surface); color: var(--text); }
.sw.k-payday b { font-size: 15px; width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%; box-shadow: inset 0 0 0 3px var(--payday-ring); }
.sw.k-here { background: var(--surface); position: relative; }
.sw.k-here::after { content: ""; position: absolute; left: 50%; top: 50%; width: 20px; height: 4px; transform: translate(-50%, -50%); border-radius: 2px; background: var(--here-bar); }
/* Same swatch, the future's dots instead of the bar: same 4px thickness, same
   spot, so the two read as the same mark in two states, not two different marks.
   Both sit centered in the square, level with the words beside them (2026-09-28). */
.sw.k-here-future { background: var(--surface); position: relative; }
.sw.k-here-future::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 20px; height: 4px; transform: translate(-50%, -50%);
  background: radial-gradient(circle at 2px 2px, var(--here-bar) 2px, transparent 2.5px) 0 0 / 8px 4px repeat-x;
}

/* ---- The assistant: the whisper asks, across the whole top bar. ---- */
/* The hourglass and Today fade, and the row fills the bar, 16px in from each side,
   where the whisper's words sit. */
.topbtn { transition: opacity 0.2s; }
.assist .topbtn { opacity: 0; pointer-events: none; }
.assist #where { width: 100%; }
/* One row: Skip, the field, All of it. Everything on the date's size and baseline. */
#where.row { display: flex; align-items: baseline; gap: 16px; overflow: visible; }
#where.row input {
  flex: 1; min-width: 0; height: 44px; padding: 0 12px;
  border: 1px solid var(--hairline); border-radius: 8px; background: var(--surface);
  color: var(--text); font: 400 var(--whisper-size) var(--font);
  user-select: text; -webkit-user-select: text;
}
#where.row input::placeholder { color: var(--text-2); }
/* The answers wait for the question to finish typing, holding their space. */
#where.row button {
  flex: none; min-height: 44px; padding: 0 12px; border: 0; border-radius: 8px;
  font: 600 var(--whisper-size) var(--font);
  opacity: 0; pointer-events: none; transition: opacity 0.2s;
}
#where.row.ready button { opacity: 1; pointer-events: auto; }
/* Light or dark: white with a shadow is the secondary answer, midnight the main one. */
#where .soft { background: var(--button); color: var(--button-ink); box-shadow: var(--shadow-button); }
#where .go { background: var(--pressed); color: var(--pressed-ink); }
/* ---- Hiding due dates in the chips: the dot stays (color still says the status),
   the number inside it goes. Toggled by a long press on a chip, remembered on this
   device. ---- */
/* Hidden due dates shrink to plain dots, but the hourglass's order numbers always
   show: showing what to pay first is the whole point of that view. */
.nodates .dot.num:not(.rank) { font-size: 0; min-width: 0; width: 8px; height: 8px; padding: 0; border-radius: 50%; }
/* Partly known: a hollow white ring, no fill, so it can't pass for a status. 1px,
   taken from the inside so the dot keeps its size (Bryan, 2026-09-29). */
.dot.num.open { background: transparent; color: var(--c-white); box-shadow: inset 0 0 0 1px var(--c-white); }
/* While it asks, the header sits above the date row, so the buttons' shadows show
   whole. */
.ask #top { position: relative; z-index: 4; }
/* The 1st's midnight month block settles flat while it asks, its name left as text,
   dissolving down and back up. */
.d.first { transition: background-color 0.3s ease, color 0.3s ease; }
.ask .d.first { background: transparent; color: var(--text); }
/* On phones the question row sits 16px from the top, running a few pixels over the
   date row. */
@media (max-width: 1023px) {
  .ask #top {
    align-items: flex-start;
    padding-top: calc(env(safe-area-inset-top) + 16px);
  }
}
/* The square being asked about: a 3px midnight ring outside its white line. */
.ring {
  position: absolute; z-index: 3; pointer-events: none;
  left: calc(var(--c) * var(--pitch) - 3px);
  top: calc(var(--r) * var(--pitch) - 3px);
  width: calc(var(--pitch) + var(--gap) + 6px);
  height: calc(var(--pitch) + var(--gap) + 6px);
  box-sizing: border-box;
  border: 3px solid var(--ask-ring);
}
/* All of it carries the grid's paid check, in emerald, sitting on the text's line. */
#where .go svg { width: 1em; height: 1em; margin-right: 6px; vertical-align: -0.125em; color: var(--state-paid); }

/* ---- Add bills, in the dot zone (onboarding, 2026-09-28) ---- */
/* The chips float over the dots under the plus row on soft shadows. On an empty board
   the panel is see-through, so the real dots show; when bills fill the screen it
   covers the board's lower part with dots of its own. */
/* The wall of bills is its own card again (2026-09-29): over the dimmed board, 16px in
   from every edge, Mist with white chips. Not laid over the grid. */
#pickzone {
  position: absolute; left: 16px; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 20;
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: 12px; background: var(--page); box-shadow: var(--shadow-sheet);
}
#pickzone[hidden] { display: none; }
#pickzone.over-rows { box-shadow: var(--shadow-header); }
#pickzone .pz-scroll {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: 8px 16px 24px;
}
/* Everything sits in whole rows of the grid: a line of text takes one row, centered
   in its square; each line of chips is centered in its row, 44px tall. */
#pickzone .lede, #pickzone h3, #try-sample {
  height: var(--pitch); margin: 0; padding-top: var(--gap); display: flex; align-items: center;
}
#pickzone .lede { font-size: 16px; color: var(--text); }
#pickzone h3 { font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-2); }
/* Select all sits at the right end of its group's label, on the same baseline. */
#pickzone h3 { justify-content: space-between; }
#pickzone h3 .all {
  height: 44px; padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; letter-spacing: 0.02em; text-transform: none; font-weight: 600; color: var(--text);
  text-decoration: underline; text-underline-offset: 3px;
}
#pickzone .pick { -webkit-touch-callout: none; user-select: none; }
#pickzone .picks, #pickzone .named:not(:empty), #pickzone .nameit {
  display: flex; flex-wrap: wrap; column-gap: 16px; margin: 0;
  row-gap: max(8px, calc(var(--pitch) - 44px));
  padding: max(0px, calc(var(--gap) + (var(--cell) - 44px) / 2)) 0 max(0px, calc((var(--cell) - 44px) / 2));
}
#pickzone .nameit input { margin: 0; height: 44px; min-height: 44px; }
#pickzone .pick { box-shadow: var(--shadow-raised); font-weight: 600; height: 44px; min-height: 44px; }
#pickzone .pick.on { box-shadow: none; }
#try-sample { padding-left: 0; padding-right: 0; border: 0; background: none; font: inherit; font-size: 15px; color: var(--text-2); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
#try-sample[hidden] { display: none; }
#pickzone .pz-foot {
  flex: none; display: grid; grid-template-columns: 3fr 7fr; gap: 16px; padding: 16px;
  /* Solid: the fade showed the board through the top of the bar. */
  background: var(--page);
}
#pickzone .pz-foot button { min-height: 48px; padding: 0 16px; white-space: nowrap; border: 0; border-radius: 8px; font: inherit; font-size: 16px; font-weight: 600; cursor: pointer; }
#pickzone .pz-foot .quiet { background: var(--surface); color: var(--text); box-shadow: var(--shadow-button); }
#wall-add { background: var(--pressed); color: var(--pressed-ink); }
#wall-add:disabled { opacity: 0.4; cursor: default; }
/* The zoom button steps aside while bills are being picked. */
.picking #corner { visibility: hidden; }
.corner-out #corner { opacity: 0; transition: opacity 0.25s ease; pointer-events: none; }

/* ---- Due dates, from the board (onboarding, 2026-09-28) ---- */
/* A bill with no due date: its row's squares go blank (Mist, the page color) instead of
   Bondi, since Bondi says "you're fine" and we don't know that yet. The white 2px grid
   lines stay, so it's still a row of squares wherever it sits. */
.blankrow {
  position: absolute; left: 0; right: 0; z-index: 3;
  top: calc(var(--r) * var(--pitch)); height: var(--pitch);
  background:
    linear-gradient(to bottom, var(--c-white) var(--gap), transparent 0),
    linear-gradient(to right, var(--c-white) var(--gap), var(--blank-row) 0) 0 0 / var(--pitch) 100%;
}
/* No shadow under today on a blank row: on Mist it read as a dirty stripe splitting
   the screen, louder than a date we just don't know yet. (Bryan, 2026-09-28) */

/* On pale Mist the full-strength lift under today read harsh; the Mist plus row sits
   over it and carries its own lift at half strength, a slight raise. */
.addrow { z-index: 3; }
.add { z-index: 4; }
/* The screen runs down today's column on every row at one strength: the blank Mist
   rows and the plus row cover the board's own, so they draw it themselves. */
.blankrow::after, .addrow::after {
  content: ""; position: absolute; top: 0; bottom: 0;
  left: calc(var(--t) * var(--pitch)); width: calc(var(--pitch) + var(--gap));
  box-shadow: -5px 0 9px -4px var(--shadow-lens), 5px 0 9px -4px var(--shadow-lens);
}
/* The plus chip: the same height and corners as the name chip, right after it, and
   always on its right, even when the names sit on the right. */
.datechip {
  flex: none; margin-left: 8px; padding: 0; border: 0; border-radius: 8px;
  width: min(34px, calc(var(--cell) - 4px)); height: min(34px, calc(var(--cell) - 4px));
  display: grid; place-items: center; background: var(--chip); color: var(--chip-ink);
  pointer-events: auto; cursor: pointer;
  transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.datechip svg { width: 55%; height: 55%; }
#labels.right .lrow.has-plus .chip, #labels.right .lrow.has-plus .datechip {
  transform: translateX(calc(var(--vw) - var(--w) - min(34px, calc(var(--cell) - 4px)) - 8px - 16px));
}
.z2 .datechip { display: none; }

/* The date card: one question inside the grid, 16px from each side, just under the
   date row. Midnight green ground, 1 to 31 as 44px Bondi circles, today ringed; the
   tapped number turns Emerald. Then "Caught up on it?" in the board's colors. */
#dimmer { position: absolute; inset: 0; z-index: 19; background: rgba(var(--c-shade), 0.35); }
#dimmer[hidden] { display: none; }
#daycard {
  position: absolute; z-index: 20; left: 16px; right: 16px; top: calc(var(--hh) + 16px);
  max-height: calc(100% - var(--hh) - 32px); overflow-y: auto;
  padding: 24px 16px 16px; border-radius: 12px;
  background: var(--c-midnight); color: var(--c-white); box-shadow: var(--shadow-sheet);
}
#daycard[hidden], #daycard [hidden] { display: none; }
#daycard .dc-q { margin: 0 0 24px; text-align: center; font-size: 18px; line-height: 1.3; }
#daycard .dc-q b { font-weight: 700; }
#daycard .dc-nums { display: grid; grid-template-columns: repeat(5, 44px); justify-content: space-between; row-gap: 16px; padding: 0 8px; }
#daycard .dcn {
  width: 44px; height: 44px; padding: 0; border: 0; border-radius: 50%;
  background: var(--c-bondi); color: var(--c-white);
  font-family: var(--font-num); font-weight: 700; font-size: 18px; cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
#daycard .dcn.today { box-shadow: 0 0 0 3px var(--c-midnight), 0 0 0 5px var(--c-white); }
#daycard .dcn.on { background: var(--c-emerald); color: var(--c-midnight); }
/* Where it stands: three across, the same three as the bill card. Unpicked they're a
   quiet tint; picked, they fill with their board color. */
#daycard .dc-q2 { margin: 24px 0 12px; font-size: 16px; }
#daycard .dc-three { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
#daycard .dc-three button {
  min-height: 52px; padding: 0 4px; border: 0; border-radius: 8px; font: inherit; font-size: 15px; font-weight: 700;
  cursor: pointer; background: rgba(255, 255, 255, 0.12); color: var(--c-white);
}
#daycard .dc-ok.on { background: var(--c-emerald); color: var(--c-midnight); }
#daycard .dc-behind.on { background: var(--c-pink); color: var(--c-deep); }
#daycard .dc-maybe.on { background: var(--c-sunglow); color: var(--c-midnight); }
#daycard button.dc-go {
  display: block; width: 100%; margin-top: 16px; min-height: 52px; border: 0; border-radius: 8px;
  font: inherit; font-size: 17px; font-weight: 700; cursor: pointer; background: var(--c-white); color: var(--c-midnight);
}

/* The first run: no plus row and no wrench until the first bills land; then they fade in. */
.firstrun .addrow, .firstrun .add, .firstrun .menu-chip { visibility: hidden; }
.tools-in .addrow, .tools-in .add, .tools-in .menu-chip { animation: tools-in 0.6s ease both; }
@keyframes tools-in { from { opacity: 0; } to { opacity: 1; } }

/* ---- Parking (onboarding): the empty rows are there at once, like painted spots,
   and each name drives into its row from the side the names sit on, one after
   another; the chips fade out of the dot zone as they land. ---- */
#pickzone.leaving { opacity: 0; transition: opacity 0.3s ease; }
.lrow.parking { animation: park-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) var(--p) both; }
#labels.right .lrow.parking { animation-name: park-in-right; }
@keyframes park-in { from { transform: translateX(calc(-100% - 2 * var(--pitch))); } }
@keyframes park-in-right { from { transform: translateX(calc(100% + 2 * var(--pitch))); } }
@media (prefers-reduced-motion: reduce) {
  .lrow.parking { animation: none; }
  #pickzone.leaving { transition: none; }
}

/* ---- The wrench: a scoreboard, then the tools (2026-09-28) ---- */
/* Tiles in the board's colors: a big number over its word, square like the board. */
#menu .score { display: flex; gap: 16px; margin-bottom: 8px; }
#menu .score .tile {
  flex: 1 1 0; min-width: 0; aspect-ratio: 1; max-width: 50%; border: 0; border-radius: 8px; box-shadow: none; padding: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; text-align: center;
}
#menu .score .tile b { font-family: var(--font-num); font-size: 36px; font-weight: 700; line-height: 1; }
#menu .score .tile span { font-size: 13px; font-weight: 600; line-height: 1.1; }
#menu .score .t-ok { background: var(--c-emerald); color: var(--c-midnight); }
#menu .score .t-behind { background: var(--c-pink); color: var(--c-deep); }
#menu .score .t-trouble { background: var(--c-graphite); color: var(--c-white); }
/* Needs info matches the bill card's note: pale Bondi, Midnight green words (small white
   words on solid Bondi fail AA). */
#menu .score .t-info { background: color-mix(in srgb, var(--c-bondi) 24%, #fff); color: var(--c-midnight); cursor: pointer; }
/* The tools: four across, an icon over its word. */
#menu .tools { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 0; }
/* Sort is off the wrench for now: with no context it read as nothing happening. */
#menu .tools #sort-open { display: none; }
#menu .tools .tool {
  min-height: 72px; padding: 8px 4px; flex-direction: column; justify-content: center; align-items: center; gap: 6px;
  text-align: center; font-size: 13px; font-weight: 600; line-height: 1.15;
}
#menu .tools .tool svg { width: 24px; height: 24px; color: var(--menu-icon); }

/* Switching to onboarding: the tiles turn over onto Mist, then the chips fade in. */
.flip .face.mist { background: var(--blank-row); }
/* Only the chips and words fade in; the grid behind them is there at once, so the
   empty board underneath never shows through. */
#pickzone.arriving { animation: pz-rise 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) both; }
@keyframes pz-rise { from { opacity: 0; transform: translateY(24px); } }
#pickzone.chips-out { opacity: 0; transform: translateY(24px); transition: opacity 0.3s ease, transform 0.3s ease; }
.flip.to-mist { background: var(--c-white); }
.flip.to-mist .back { background: var(--blank-row); }
@keyframes pz-arrive { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { #pickzone.arriving { animation: none; } }

/* The bill card's X: its corner sits 16px from the card's top and right edges, inside a
   44px tap target. */
#bill header { position: relative; }
#bill .x {
  position: absolute; top: 6px; right: 6px; width: 44px; height: 44px; padding: 0; border: 0;
  background: none; box-shadow: none; display: grid; place-items: center; color: var(--text); cursor: pointer;
}
#bill .x svg { width: 24px; height: 24px; }
