/*
  Get a grip
  --------
  A quiet, paper-like academic calendar. The palette is warm and muted so a
  month of colour-coded work still reads as calm: sage page ground, off-white
  paper, a slate app bar, and four task colours (terracotta, slate blue,
  garden green, plum) that stay distinguishable in both themes.

  Type is two faces doing two jobs.

  EB Garamond, a book face, appears only where the app signs its own name: the
  wordmark, the week or month on the calendar, and the title of a dialog. It is
  a serif because the thing being referenced is a school exercise book, not
  because a serif looks expensive, and it is a reading face rather than a
  display one for the same reason. It replaced Fraunces, which is a fine
  typeface and the single most over-reached-for display serif there is; using
  it was a decision nobody had made on purpose.

  Geist carries every piece of interface text and every figure. It was chosen
  over Inter, which is the default sans of everything and which this app had
  only because it was the obvious pick: Geist has the same legibility at small
  sizes, better lining figures for a page that is mostly numbers, and it is
  about forty per cent smaller over the wire.

  Accessibility notes that the rest of this file assumes:
  - Colour is never the only signal. Every task type also carries a glyph
    (star, circle, diamond, triangle) and a spelled-out label.
  - All text and UI colours meet WCAG 2.1 AA contrast in both themes;
    see tools/check-contrast.mjs for the check.
  - Focus is always visible, with a ring re-coloured per surface.
  - Everything is sized in rem and reflows to a single column, so the page
    survives 200% zoom and narrow screens without horizontal scrolling.
*/

/* ---------- Fonts ---------- */

@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/garamond-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/garamond-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Alex Brush";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/alexbrush-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Alex Brush";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/alexbrush-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/geist-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/geist-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------- Tokens ---------- */

:root {
  color-scheme: light;

  /*
    A desk and the paper on it.

    These were three tints of the same cream seven per cent apart, which is
    why the page read flat: everything was the same material and nothing was
    on top of anything. The ground is deeper and less yellow now, so the paper
    lifts off it and the app has a figure and a ground instead of a wash.
  */
  --ground: #D8CFC4;
  --surface: #FDFBF7;
  --surface-2: #F4EEE5;
  --surface-3: #E8DFD2;
  --now: #C8880A;
  --ink: #2A1E1F;
  --ink-2: #5A4B49;
  --line: #DDD3C3;
  --line-strong: #8B7C6B;

  --signature: #7A4A2E;

  /* The money half's own green, mixed down far enough to sit on paper. It
     paints that half's door on the chooser and appears nowhere else. */
  --money-door: #4C7A1E;

  --accent: #A81D33;
  --accent-hover: #85162A;
  --accent-ink: #FFFFFF;
  --accent-soft: #F8E4E5;

  --danger: #7C1D1D;

  --focus: #123E63;
  --focus-halo: #FFFFFF;

  /* One hue per subject. Never the only signal: the name is always written
     out, and the type keeps its own glyph on top. */
  --s-economics: #0D6470;
  --s-economics-soft: #DCEEF0;
  --s-mathematics: #3A3E92;
  --s-mathematics-soft: #E1E2F4;
  --s-english: #7A2F66;
  --s-english-soft: #F1E1ED;
  --s-polish: #8A5A0B;
  --s-polish-soft: #F6E9D3;
  --s-history: #3E5871;
  --s-history-soft: #E2E9F1;
  --s-ess: #2B6B45;
  --s-ess-soft: #DEEEE4;
  --s-none: #6A5C58;
  --s-none-soft: #EBE4E0;
  --subject-ink: #FFFFFF;

  --done: #5A625F;
  --done-ink: #FFFFFF;
  --done-soft: #E6E2DC;

  --today: #FBEFD9;
  --today-line: #A81D33;

  /* Tinted to the ground rather than black, and deep enough to read as height. */
  --shadow: 0 1px 2px rgba(58, 42, 34, .06), 0 10px 28px -12px rgba(58, 42, 34, .22);

  /*
    One corner scale, and a rule for which is which.

    There were eight radii in this file: 2, 3, 4, 6, 8, 10, a pill and a
    circle, assigned by whatever looked right in the moment. Four now, with a
    job each. A mark is 4, a control is 8, a surface is 14, and a thing that is
    a pill by nature is a pill. Nothing picks its own.
  */
  --r-mark: 4px;     /* chips, swatches, bar fills, the small coloured things */
  --radius: 8px;     /* buttons, inputs, rows, cards: anything you act on */
  --radius-lg: 14px; /* sheets: the main panel, dialogs, the chooser tiles */
  --r-pill: 999px;   /* the live bar, segmented controls, anything already round */

  --font-display: "EB Garamond", Georgia, "Times New Roman", serif;
  --font-body: "Geist", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --font-signature: "Alex Brush", "Segoe Script", "Snell Roundhand", cursive;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --ground: #171213;
    --surface: #201A1A;
    --surface-2: #292222;
    --surface-3: #332B2B;
    --now: #F0B429;
    --ink: #F1E8E2;
    --ink-2: #B6A9A5;
    --line: #3D3333;
    --line-strong: #857672;

    --signature: #E0B48C;

    --money-door: #A8D44A;

    --accent: #F08098;
    --accent-hover: #F79BAE;
    --accent-ink: #2A0710;
    --accent-soft: #3A1D22;

    --danger: #F0A0A0;

    --focus: #9FCBEC;
    --focus-halo: #000000;

    --s-economics: #6FC4CE;
    --s-economics-soft: #1E3336;
    --s-mathematics: #9CA4EE;
    --s-mathematics-soft: #24263C;
    --s-english: #DE9CCB;
    --s-english-soft: #382634;
    --s-polish: #E0B36A;
    --s-polish-soft: #37301F;
    --s-history: #9FBBDA;
    --s-history-soft: #242F3A;
    --s-ess: #8CCCA4;
    --s-ess-soft: #21332A;
    --s-none: #A79792;
    --s-none-soft: #2E2725;
    --subject-ink: #1A1214;

    --done: #99A29F;
    --done-ink: #171C1B;
    --done-soft: #2C2626;

    --today: #3A2A1C;
    --today-line: #F08098;

    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 6px 18px rgba(0, 0, 0, .35);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --ground: #171213;
  --surface: #201A1A;
  --surface-2: #292222;
  --surface-3: #332B2B;
  --now: #F0B429;
  --ink: #F1E8E2;
  --ink-2: #B6A9A5;
  --line: #3D3333;
  --line-strong: #857672;

  --signature: #E0B48C;

  --money-door: #A8D44A;

  --accent: #F08098;
  --accent-hover: #F79BAE;
  --accent-ink: #2A0710;
  --accent-soft: #3A1D22;

  --danger: #F0A0A0;

  --focus: #9FCBEC;
  --focus-halo: #000000;

  --s-economics: #6FC4CE;
  --s-economics-soft: #1E3336;
  --s-mathematics: #9CA4EE;
  --s-mathematics-soft: #24263C;
  --s-english: #DE9CCB;
  --s-english-soft: #382634;
  --s-polish: #E0B36A;
  --s-polish-soft: #37301F;
  --s-history: #9FBBDA;
  --s-history-soft: #242F3A;
  --s-ess: #8CCCA4;
  --s-ess-soft: #21332A;
  --s-none: #A79792;
  --s-none-soft: #2E2725;
  --subject-ink: #1A1214;

  --done: #99A29F;
  --done-ink: #171C1B;
  --done-soft: #2C2626;

  --today: #3A2A1C;
  --today-line: #F08098;

  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 6px 18px rgba(0, 0, 0, .35);
}

/* ---------- Base ---------- */

*, *::before, *::after { box-sizing: border-box; }

/* Several components set an explicit display, which would otherwise win
   against the user-agent rule for [hidden]. */
[hidden] { display: none !important; }

/*
  The header is a full-bleed slab of cherry. Left mid-scroll it shows as a
  stripe under the status bar, so the page is given two snap points -- the top
  of the header and the top of the content below it -- and settles on one or
  the other rather than between them. Proximity, not mandatory: a deliberate
  scroll past them is never caught, only a small one near a point is tidied up.
*/
html {
  -webkit-text-size-adjust: 100%;
  scroll-snap-type: y proximity;
  scroll-behavior: smooth;
}

.app-bar,
.update-bar,
.layout { scroll-snap-align: start; }

/*
  The end of the document has to be a snap point of its own. Without it, a
  short page whose last start-point sits within snapping distance of the
  bottom is pulled back every time, and the footer can never be reached.
*/
.site-footer { scroll-snap-align: end; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.55;
  color: var(--ink);
  background: var(--ground);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { line-height: 1.25; margin: 0; text-wrap: balance; }

button, input, select, textarea { font: inherit; color: inherit; }

/*
  This is an app that lives on a touchscreen. Without touch-action a tap on
  any control waits 300ms to find out whether a second one is coming, which is
  felt as the whole interface being slightly behind you; and the default grey
  flash on iOS is not a colour anything else here uses.
*/
button, a, label, summary, .tile, [role="button"] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

abbr[title] { text-decoration: none; }

kbd {
  font-family: var(--font-body);
  font-size: .82em;
  font-weight: 600;
  padding: .1em .45em;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: var(--r-mark);
  background: var(--surface);
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  box-shadow: 0 0 0 6px var(--focus-halo);
  border-radius: var(--r-mark);
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: .75rem;
  top: -4rem;
  z-index: 50;
  padding: .7rem 1.1rem;
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  border: 2px solid var(--ink);
  border-radius: var(--radius);
  transition: top .15s ease;
}

.skip-link:focus { top: .75rem; }

/* The date under the period heading: a caption, in the page's own voice
   rather than in the uppercase tracked one every dashboard label wears. */
.period-today {
  margin: .15rem 0 0;
  font-size: .88rem;
  color: var(--ink-2);
}

/* ---------- App bar ---------- */

/*
  The bar used to be a band of cherry across the top. The welcome carries the
  brand now, so this is furniture: the page's own paper, a hairline to part it
  from the grid, and colour saved for the one thing worth pressing.
*/
.app-bar {
  background: var(--surface);
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  /* Installed on an iPad the page runs under the status bar, so the bar pays
     for its own clearance rather than letting the wordmark sit beneath it. */
  padding-top: env(safe-area-inset-top, 0px);
}

.app-bar-inner {
  max-width: 82rem;
  margin: 0 auto;
  padding: 1rem 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.25rem;
}

.wordmark {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-right: auto;
  min-width: 0;
  font-family: var(--font-display);
  font-weight: 620;
  font-size: 1.35rem;
  letter-spacing: 0;
}

.wordmark-mark {
  width: 1.45rem;
  height: 1.45rem;
  flex-shrink: 0;
  color: var(--accent);
}

/* On a narrow phone the mark alone identifies the app and the controls need
   the room more than the name does. */
@media (max-width: 26rem) {
  .wordmark-text { display: none; }
}

/*
  A script face carries far less height per point than the text around it, so
  the signature is set larger than the wordmark to sit level with it, and
  nudged along the baseline the way a hand-signed name overlaps what it signs.
*/
.signature {
  font-family: var(--font-signature);
  font-weight: 400;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--signature);
  white-space: nowrap;
  transform: translateY(.1em) rotate(-2deg);
  transform-origin: left baseline;
}

.footer-signature {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: .45rem;
  margin: 0 0 .35rem;
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 620;
  color: var(--ink);
}

.view-switch { display: flex; }

.app-bar .segmented { --seg-bg: var(--surface-2); }

/* ---------- Segmented control ---------- */

.segmented {
  display: inline-flex;
  /* Four views no longer fit on one line at the narrowest widths. */
  flex-wrap: wrap;
  padding: 3px;
  gap: 3px;
  border: 0;
  margin: 0;
  border-radius: var(--radius);
  background: var(--seg-bg, var(--surface-3));
}

.segmented-block { display: flex; width: 100%; }
.segmented-block label { flex: 1; text-align: center; }

.segmented input { position: absolute; opacity: 0; pointer-events: none; }

.segmented label {
  padding: .45rem .95rem;
  border-radius: var(--r-mark);
  font-size: .92rem;
  font-weight: 500;
  color: var(--ink-2);
  cursor: pointer;
  white-space: nowrap;
}

.segmented label:hover { color: var(--ink); }
.app-bar .segmented label:hover { color: var(--ink); }

.segmented input:checked + label {
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
}

.segmented input:focus-visible + label {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* ---------- Buttons ---------- */

/*
  Every button gives under the finger. The app had hover states and focus
  rings and nothing at all for the press itself, which on a touchscreen is the
  only one of the three that exists.
*/
.btn:active:not(:disabled), .tile:active, .link-btn:active { transform: translateY(1px); }
.btn { transition: background .14s ease, border-color .14s ease, transform .08s ease; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  min-height: 2.75rem;
  padding: .5rem 1rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: transparent;
  font-size: .95rem;
  font-weight: 600;
  cursor: pointer;
}

.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-hover); }

.btn-quiet {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--line-strong);
}
.btn-quiet:hover { background: var(--surface-2); }

.btn-icon {
  min-width: 2.75rem;
  padding: .5rem;
  background: var(--surface);
  border-color: var(--line-strong);
  font-size: 1.05rem;
}
.btn-icon:hover { background: var(--surface-2); }

.btn-danger {
  background: transparent;
  color: var(--danger);
  border-color: currentColor;
}
.btn-danger:hover { background: var(--accent-soft); }

.btn-add {
  background: var(--accent);
  color: var(--accent-ink);
}
.btn-add:hover { background: var(--accent-hover); }

.btn-plus { font-size: 1.15em; line-height: 1; }

/* With the band gone, this is the only cherry left up here, which is the
   point: the one thing worth pressing is the one thing with colour. */
.app-bar-add { flex-shrink: 0; }

/* ---------- Update bar ---------- */

.update-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .75rem;
  max-width: 82rem;
  margin: 0 auto;
  padding: .7rem 1.25rem;
  background: var(--accent-soft);
  border-bottom: 1px solid var(--line);
  font-size: .9rem;
}

.update-text {
  margin: 0;
  flex: 1 1 14rem;
  font-weight: 600;
}

/* ---------- Layout ---------- */

/*
  Two asides bracket <main> in the source: "Upcoming" before it, the settings
  after it. On a phone that is exactly the order a reader wants -- what is due
  next, the calendar, then the controls -- and on a wide screen the grid areas
  stack both asides into one left-hand column. Reading order and visual order
  therefore agree at every width.
*/
.layout {
  max-width: 82rem;
  margin: 0 auto;
  /* Room to breathe under the header: the cherry is a strong edge and the
     calendar sat straight up against it. */
  padding: 2.5rem calc(1.25rem + env(safe-area-inset-right, 0px)) 2rem
           calc(1.25rem + env(safe-area-inset-left, 0px));
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  /*
    Two rows, not three with main spanning: a spanning main is only as tall as
    the rows it spans, which are sized by the sidebar beside it, so a tall
    calendar spilled straight over whatever sat underneath.
  */
  /*
    The rail spans both rows rather than splitting across them. Split, its two
    short panels sat at the top and the bottom of a column six hundred pixels
    tall, with the hole between them reading as a missing card.
  */
  grid-template-areas:
    "rail main"
    "rail organiser"
    "settings settings";
  gap: 1.5rem;
  align-items: start;
}

/*
  Not sticky, though the ground below it is tempting. The rail's grid area is
  the first two rows, but a sticky box in a grid area is not clamped to it the
  way the spec reads: stuck at the top of the viewport it travelled 129px down
  into the settings panel below, which is `position: relative` and later in
  the document, so settings painted over it and swallowed the clicks meant for
  the filters. A column that ends is a column that ends.
*/
.sidebar-rail { grid-area: rail; }
.main { grid-area: main; }

/*
  Settings used to be six panels of prose stacked in the left column, where
  they took up two thirds of the page height and competed with the week for
  attention. They are read once and then never again, so they fold away under
  everything else, and open three across rather than one down.
*/
.sidebar-settings { grid-area: settings; }

.settings-grid {
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  align-items: start;
}

.panel-fold { position: relative; padding: 0; }
.panel-fold > .fold-summary { padding: .85rem 1rem; }
.panel-fold[open] > .fold-summary { border-bottom: 1px solid var(--line); }
.panel-fold > .fold-body { padding: 1.1rem 1rem 1.2rem; }

/*
  Explicit, because `display: grid` on the body is an author rule and it beats
  whatever the browser uses to hide a shut <details>. Measured: the settings
  body still reported 944 pixels of layout with the fold shut, and the box
  inside it still had a client rect, so everything in there was still a
  tab stop on a page that showed none of it.
*/
.card-fold:not([open]) > .fold-body,
.panel-fold:not([open]) > .fold-body { display: none; }
.panel-fold[open] > .fold-summary::after { transform: translateY(.55rem) rotate(225deg); }
.settings-grid > .panel { background: var(--surface); }

/* The organiser wears .main for the box treatment, so its own placement has to
   come after .main or it inherits main's cell and lands on top of it. */
.organiser-panel { grid-area: organiser; }

.sidebar { display: grid; gap: 1rem; align-content: start; }

.main {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 1.25rem;
}

.main:focus { outline: none; }

.panel {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1rem;
}

.panel-title {
  font-size: 1rem;
  font-weight: 650;
  letter-spacing: -.011em;
  margin-bottom: .35rem;
}

.panel-hint {
  margin: 0 0 .75rem;
  font-size: .85rem;
  color: var(--ink-2);
}

.panel-rule {
  border: 0;
  border-top: 1px solid var(--line);
  margin: .75rem 0;
}

.sync-status {
  margin: 0 0 .75rem;
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-2);
}

.sync-status.is-stale { color: var(--accent); }

.sync-status:empty { display: none; }

.panel-actions { display: flex; flex-wrap: wrap; gap: .5rem; }
.panel-actions .btn { flex: 1; font-size: .88rem; }

/* ---------- The sync panel's own small form ---------- */

.field-label {
  display: block;
  font-size: .85rem;
  font-weight: 600;
  margin-bottom: .35rem;
}

.input {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  padding: .55rem .65rem;
  min-height: 2.75rem;
  font: inherit;
  font-size: .9rem;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

.input::placeholder { color: var(--ink-2); opacity: 1; }

/* The calendar address is long and there is nowhere to wrap it, so it scrolls
   rather than pushing the sidebar wider. */
#cloud-feed {
  font-size: .78rem;
  text-overflow: ellipsis;
}

.cloud-setup .field-hint,
.cloud-live .field-hint { margin: .35rem 0 .75rem; }

/* ---------- Checkboxes ---------- */

.filters { border: 0; margin: 0; padding: 0; display: grid; gap: .15rem; }

.check {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .4rem .25rem;
  min-height: 2.25rem;
  font-size: .95rem;
  cursor: pointer;
  border-radius: var(--r-mark);
}

.check:hover { background: var(--surface-3); }

.check input[type="checkbox"] {
  width: 1.15rem;
  height: 1.15rem;
  accent-color: var(--accent);
  flex-shrink: 0;
  cursor: pointer;
}

.check-done { margin-top: .25rem; }

/* ---------- Type glyphs ---------- */

/*
  Colour now says which subject; the glyph says whether it is a test or
  homework. Keeping those on separate channels means each survives on its own,
  so a glyph simply takes the colour of whatever it sits on.
*/
.glyph { flex-shrink: 0; font-size: .8em; line-height: 1; }

.glyph-test::before { content: "\2605"; }
.glyph-homework::before { content: "\25CF"; }
.glyph-project::before { content: "\25C6"; }
.glyph-other::before { content: "\25B2"; }
.glyph-done::before { content: "\2713"; }
.glyph-session::before { content: "\270E"; }

/* ---------- Upcoming list ---------- */

.up-group-title {
  font-size: .74rem;
  font-weight: 700;
  color: var(--ink-2);
  margin: .85rem 0 .4rem;
}

.up-group-title.is-overdue { color: var(--danger); }

.up-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

.up-item { display: block; }

.up-btn {
  width: 100%;
  text-align: left;
  display: grid;
  gap: .2rem;
  padding: .6rem .7rem .6rem .85rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--subject-color, var(--line-strong));
  border-radius: var(--radius);
  cursor: pointer;
}

.up-btn:hover { background: var(--surface-3); }

.up-title { font-weight: 600; font-size: .93rem; line-height: 1.35; }
.up-meta { font-size: .8rem; color: var(--ink-2); }
.up-when { font-weight: 600; }
.up-btn.is-overdue .up-when { color: var(--danger); }

.empty {
  margin: 0;
  padding: .85rem;
  font-size: .88rem;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
}

.empty-main { text-align: center; padding: 2.5rem 1.25rem; font-size: .95rem; }
.empty-main p { margin: 0 0 1rem; }

/* ---------- Period bar ---------- */

.period-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: .75rem 1rem;
  margin-bottom: 1.1rem;
}

.period-headings { min-width: 0; }

.period-title {
  font-family: var(--font-display);
  font-weight: 620;
  font-size: clamp(1.75rem, 1.2rem + 1.9vw, 2.5rem);
  letter-spacing: -.005em;
}

.period-nav { display: flex; gap: .5rem; flex-shrink: 0; }

.grid-hint {
  margin: 0 0 .6rem;
  font-size: .82rem;
  color: var(--ink-2);
}

/* ---------- Calendar ---------- */

.calendar {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  border: 1px solid var(--line);
}

.calendar th {
  padding: .5rem .4rem;
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.calendar td {
  border: 1px solid var(--line);
  padding: 0;
  height: 7rem;
  vertical-align: top;
  background: var(--surface);
}

.calendar td.is-weekend { background: var(--surface-2); }
.calendar td.is-outside { background: var(--surface-3); }
.calendar td.is-today { background: var(--today); box-shadow: inset 0 0 0 2px var(--today-line); }

.day {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  width: 100%;
  height: 100%;
  min-height: 7rem;
  padding: .35rem .35rem .45rem;
  text-align: left;
  background: none;
  border: 0;
  border-radius: 0;
  cursor: pointer;
}

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

.day-num {
  font-size: .95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  align-self: flex-start;
  min-width: 1.6rem;
  padding: .1rem .25rem;
  border-radius: var(--r-mark);
}

.is-outside .day-num { color: var(--ink-2); font-weight: 400; }
.is-today .day-num { background: var(--today-line); color: var(--surface); }

.day-chips { display: grid; gap: 2px; align-content: start; }

.chip {
  display: flex;
  align-items: center;
  gap: .25rem;
  padding: .1rem .3rem;
  border-radius: var(--r-mark);
  font-size: .74rem;
  font-weight: 500;
  line-height: 1.35;
  background: var(--chip-bg);
  color: var(--chip-ink);
  overflow: hidden;
}

.chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip-glyph { font-size: .78em; flex-shrink: 0; }

.chip.is-done { background: var(--done-soft); color: var(--ink-2); }
.chip.is-done .chip-text { text-decoration: line-through; }

.chip-more {
  font-size: .72rem;
  font-weight: 600;
  color: var(--ink-2);
  padding-left: .3rem;
}

.chip { --chip-bg: var(--subject-color); --chip-ink: var(--subject-ink); }

/* Planned work, not work that is due: outlined rather than filled, so a busy
   day still reads at a glance as "two things due, one thing to work on". */
.chip-session {
  --chip-bg: transparent;
  --chip-ink: var(--subject-color, var(--ink-2));
  border: 1px dashed var(--subject-color, var(--line-strong));
  font-weight: 600;
}

/* ---------- Timetable ---------- */

/* Five weekdays plus a period gutter will not shrink usefully, so the table
   keeps its width and scrolls inside its own box rather than squeezing. */
.timetable-scroll {
  position: relative;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/*
  Where the day has got to. It is drawn over the grid rather than inside a cell,
  so it can sit part-way through a lesson instead of snapping to a row, and it
  is decorative: the time itself is on the reader's own clock, and announcing a
  moving line would be noise.
*/
.now-line {
  position: absolute;
  height: 2px;
  background: var(--now);
  border-radius: var(--r-mark);
  pointer-events: none;
  z-index: 2;
}

/* A cap on the leading edge, so the line reads as a marker rather than as a
   rule belonging to the table. */
.now-line::before {
  content: "";
  position: absolute;
  left: -4px;
  top: -3px;
  width: 8px;
  height: 8px;
  background: var(--now);
  border-radius: 50%;
}

/*
  The week, drawn as a week rather than as a spreadsheet.

  Every cell used to carry a box: a hairline on all four sides, which turns
  thirty-five lessons into thirty-five little rectangles and makes the grid
  louder than the work in it. The rules that stay are the ones that do a job:
  a line under each row of periods, because the eye travels along a row, and
  one line between days. The outer box is gone, the cells sit on the paper,
  and the only strong vertical in the whole table is today.
*/
.timetable {
  width: 100%;
  min-width: 44rem;
  table-layout: fixed;
  border-collapse: collapse;
}

.timetable th {
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line-strong);
  padding: .3rem .55rem .5rem;
  text-align: left;
}

.tt-corner { width: 4.25rem; }

.tt-day { }

.tt-day-name {
  display: block;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.tt-day-date {
  display: block;
  font-size: .85rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.timetable th.is-today {
  background: var(--today);
  border-bottom-color: var(--today-line);
  box-shadow: inset 0 -2px 0 var(--today-line);
  border-radius: var(--r-mark) var(--r-mark) 0 0;
}

.timetable th.is-today .tt-day-name { color: var(--accent); }

.tt-period {
  vertical-align: top;
  font-variant-numeric: tabular-nums;
}

.tt-period-num {
  display: block;
  font-size: 1rem;
  font-weight: 700;
}

.tt-period-time {
  display: block;
  font-size: .76rem;
  color: var(--ink-2);
}

.timetable td {
  border: 0;
  border-bottom: 1px solid var(--line);
  /* Two pixels, so a double period reads as two lessons and not as one tall
     block with a line drawn through it. */
  padding: 2px;
  height: 3.9rem;
  vertical-align: top;
  background: var(--surface);
}

/* One rule between the period column and the week, and that is the lot. */
.timetable td:first-child, .timetable th:first-child { border-right: 1px solid var(--line); }
.timetable tr:last-child td { border-bottom: 0; }

/* Today keeps its tint on the lessons themselves, not only in the gaps. */
.timetable td.is-today { background: var(--today); }
.timetable td.is-today .tt-lesson { background: var(--today); }
.timetable td.is-today .tt-lesson:hover { background: var(--surface-3); }

/* A free period is the absence of a lesson, so it is drawn as absence. */
.tt-free { background: none; }
.timetable td.tt-free.is-today { background: var(--today); }

.tt-lesson {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  width: 100%;
  height: 100%;
  min-height: 3.9rem;
  padding: .35rem .5rem;
  text-align: left;
  border: 0;
  /* The grid itself stays neutral; colour belongs to the work due on it. */
  border-left: 3px solid var(--s-none);
  border-radius: 0 var(--r-mark) var(--r-mark) 0;
  background: var(--surface-2);
  cursor: pointer;
  transition: background .14s ease;
}

/*
  The strip down the side of a lesson is the subject's own colour, which it was
  always meant to be and never was: it sat on the neutral line colour, three
  pixels of decoration on every tile. Now the week can be read by colour at a
  glance, and never only by colour, because the subject is written on it too.
*/
.tt-lesson[data-subject="economics"] { border-left-color: var(--s-economics); }
.tt-lesson[data-subject="mathematics"] { border-left-color: var(--s-mathematics); }
.tt-lesson[data-subject="english"] { border-left-color: var(--s-english); }
.tt-lesson[data-subject="polish"] { border-left-color: var(--s-polish); }
.tt-lesson[data-subject="history"] { border-left-color: var(--s-history); }
.tt-lesson[data-subject="ess"] { border-left-color: var(--s-ess); }

.tt-lesson:hover { background: var(--surface-3); }

.tt-name {
  font-size: .8rem;
  font-weight: 600;
  line-height: 1.25;
}

.tt-room {
  font-size: .72rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.tt-chips {
  display: grid;
  gap: 2px;
  margin-top: .15rem;
}

/* Work due on a day with no lesson to sit against. */
.tt-extra-row th,
.tt-extra-row td { border-top: 2px solid var(--line-strong); }

.tt-extra-label {
  font-size: .8rem;
  color: var(--ink-2);
}

.tt-extra { background: var(--surface-2); }

.tt-extra-btn {
  border-left-color: var(--line-strong);
  justify-content: center;
}

/* ---------- Legend ---------- */

.legend-title {
  font-size: .76rem;
  font-weight: 700;
  color: var(--ink-2);
  margin: 1.1rem 0 .4rem;
}

.legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem 1.1rem;
  font-size: .85rem;
  color: var(--ink-2);
}

.legend li { display: flex; align-items: center; gap: .4rem; }

.legend-subjects { margin-bottom: .5rem; }

.swatch {
  width: .85rem;
  height: .85rem;
  border-radius: var(--r-mark);
  background: var(--subject-color);
  flex-shrink: 0;
}

/* ---------- Agenda ---------- */

.agenda-group { margin-bottom: 1.5rem; }

.agenda-date {
  font-size: 1.02rem;
  font-weight: 600;
  padding-bottom: .4rem;
  margin-bottom: .6rem;
  border-bottom: 2px solid var(--line);
}

.agenda-date .agenda-rel { font-family: var(--font-body); font-size: .8rem; font-weight: 500; color: var(--ink-2); }
.agenda-date.is-today { border-bottom-color: var(--today-line); }
.agenda-date.is-today .agenda-rel { color: var(--accent); font-weight: 600; }

.task-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

.task-row {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .6rem .75rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--subject-color, var(--line-strong));
  border-radius: var(--radius);
}

.task-check {
  width: 1.25rem;
  height: 1.25rem;
  margin-top: .25rem;
  accent-color: var(--accent);
  flex-shrink: 0;
  cursor: pointer;
}

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

.task-open {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-weight: 600;
  font-size: .98rem;
  line-height: 1.35;
}

.task-open:hover { text-decoration: underline; }

.task-row.is-done .task-open { text-decoration: line-through; color: var(--ink-2); }

.task-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem .6rem;
  margin-top: .2rem;
  font-size: .82rem;
  color: var(--ink-2);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .05rem .45rem;
  border-radius: var(--r-pill);
  font-size: .74rem;
  font-weight: 600;
  background: var(--subject-soft, var(--surface-3));
  color: var(--ink);
  border: 1px solid var(--subject-color, var(--line-strong));
}

.type-tag { display: inline-flex; align-items: center; gap: .3rem; }

.task-notes { margin: .35rem 0 0; font-size: .86rem; color: var(--ink-2); white-space: pre-wrap; }

/* ---------- Study organiser ---------- */

.organiser-actions { display: flex; flex-wrap: wrap; gap: .5rem; }

.organiser-title {
  font-size: 1.2rem;
  font-weight: 650;
  letter-spacing: -.015em;
}

.organiser-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: .75rem 1rem;
  margin-bottom: 1rem;
}

.organiser-title {
  font-size: 1.25rem;
  font-weight: 600;
}

.organiser-hint {
  margin: .2rem 0 0;
  font-size: .85rem;
  color: var(--ink-2);
  max-width: 44ch;
}

.cw-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
}

.cw-card {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  padding: 1rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-top: 3px solid var(--subject-color, var(--line-strong));
  border-radius: var(--radius-lg);
}

.cw-card-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem .75rem; }

.cw-next {
  margin: 0;
  padding: .5rem .65rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: .88rem;
}

.cw-next-label {
  display: block;
  font-size: .72rem;
  font-weight: 700;
  color: var(--ink-2);
  margin-bottom: .1rem;
}

.cw-next-title { font-weight: 600; }

.cw-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }

.cw-step {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  padding: .3rem .25rem;
  border-radius: var(--r-mark);
  font-size: .88rem;
}

.cw-step:hover { background: var(--surface-3); }

.cw-step input {
  width: 1.1rem;
  height: 1.1rem;
  accent-color: var(--accent);
  flex-shrink: 0;
  cursor: pointer;
}

.cw-step-title { flex: 1; min-width: 0; }
.cw-step.is-done .cw-step-title { text-decoration: line-through; color: var(--ink-2); }
.cw-step-due { font-size: .8rem; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.cw-step-due.is-close { color: var(--accent); font-weight: 600; }
.cw-step-due.is-late { color: var(--danger); font-weight: 600; }

.cw-card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--line);
}

.cw-count { font-size: .8rem; color: var(--ink-2); }

/* The step editor inside the dialog. */
.step-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 9.5rem auto;
  gap: .4rem;
  align-items: center;
  margin-bottom: .4rem;
}

.step-row input[type="text"],
.step-row input[type="date"] {
  width: 100%;
  min-width: 0;
  padding: .45rem .55rem;
  min-height: 2.5rem;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

/* Hours, the button that offers to work them out, and what it said. */
.step-hours {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: 0 .1rem .15rem;
}

.step-hours-label {
  font-size: .8rem;
  color: var(--ink-2);
  flex-shrink: 0;
}

.step-hours-field {
  width: 5.5rem;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

.step-estimate-note {
  flex-basis: 100%;
  margin: 0;
  font-size: .8rem;
  line-height: 1.5;
  color: var(--ink-2);
}

.step-estimate-note:empty { display: none; }

.cw-next-effort {
  font-size: .78rem;
  font-weight: 600;
  color: var(--ink-2);
}

@media (max-width: 40rem) {
  .step-row { grid-template-columns: minmax(0, 1fr) auto; }
  .step-row input[type="date"] { grid-column: 1; }
}

.cw-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .9rem;
  padding: .7rem .85rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--subject-color, var(--line-strong));
  border-radius: var(--radius);
}

.cw-main { flex: 1 1 16rem; min-width: 0; }

.cw-open {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-weight: 600;
  font-size: 1rem;
  line-height: 1.3;
  cursor: pointer;
  /* A dotted rule says "there is more behind this" without shouting, and keeps
     the heading reading as a heading. */
  text-decoration: underline dotted;
  text-decoration-color: var(--line-strong);
  text-underline-offset: .25em;
}

.cw-open:hover { text-decoration: underline solid; }

/*
  The card is the target people actually aim at, so it behaves like one: the
  whole thing lifts under the pointer and settles when pressed. Touch has no
  hover, which is what the pressed state is for.
*/
.cw-card {
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
}

.cw-card:hover { border-color: var(--line-strong); }

.cw-card:active { background: var(--surface-3, var(--surface)); }

/* Its own controls are not the card, and must not look like it. */
.cw-card input,
.cw-card select,
.cw-card label,
.cw-card button { cursor: auto; }

.cw-card .cw-open,
.cw-card button { cursor: pointer; }

/* Keyboard focus lands on the title button, so the card shows the same
   attention the pointer gets. */
.cw-card:focus-within { border-color: var(--line-strong); }

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

.cw-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem .6rem;
  margin-top: .25rem;
  font-size: .82rem;
  color: var(--ink-2);
}

.cw-due { font-weight: 600; }
.cw-due.is-close { color: var(--accent); }
.cw-due.is-late { color: var(--danger); }

.cw-stage-field { display: flex; align-items: center; gap: .4rem; flex-shrink: 0; }

.cw-stage-field select {
  padding: .35rem .5rem;
  min-height: 2.25rem;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  font-size: .85rem;
}

.cw-item.is-submitted { opacity: .72; }
.cw-item.is-submitted .cw-open { text-decoration: line-through; }

.cw-notes { margin: .35rem 0 0; font-size: .85rem; color: var(--ink-2); white-space: pre-wrap; }

/* ---------- Dialogs ---------- */

.dialog {
  width: min(34rem, calc(100vw - 2rem));
  max-height: calc(100vh - 3rem);
  /* dvh tracks the browser chrome as it hides and shows; the insets keep a
     full-height dialog clear of the status bar and home indicator when the
     app is running installed. */
  max-height: calc(100dvh - 2rem - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  /*
    Every dialog here opens with a sticky head that pays for its own top
    inset, so the box itself does not. One that ever opens without a head
    would have its first line flush against the edge, which is the guard
    below rather than a rule anybody has to remember.
  */
  padding: 1.25rem 1.25rem 1.25rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  /* Two parts: a tight contact shadow where it meets the page and a soft lift
     above it. One forty-pixel bloom reads as fog rather than as height. */
  box-shadow: 0 2px 6px rgba(0, 0, 0, .16), 0 18px 36px -14px rgba(0, 0, 0, .42);
}

.dialog:has(.dialog-head) { padding-top: 0; }

.dialog::backdrop { background: rgba(20, 28, 26, .55); }

.dialog-head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  /* Spans the dialog's side padding so nothing shows through beside it. */
  margin: 0 -1.25rem;
  padding: 1.25rem 1.25rem .75rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.dialog-title {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 620;
}

/*
  Two gaps do the grouping here, and the ratio between them is what makes a
  question read as belonging to its answers: a question sits close to its own
  controls, and further from the next question. Keep the outer roughly double
  the inner.
*/
.dialog-form {
  --form-gap: 1.35rem;
  display: grid;
  gap: var(--form-gap);
}

/* The revealed follow-up questions live in a plain wrapper, so they need the
   form's rhythm applying to them directly or they stack with no gap at all. */
#detail-steps {
  display: grid;
  gap: var(--form-gap);
}

.field { display: grid; gap: .5rem; border: 0; margin: 0; padding: 0; min-width: 0; }

.field > label,
.field > legend {
  font-size: .88rem;
  font-weight: 600;
  line-height: 1.35;
  padding: 0;
}

/*
  A legend is rendered outside its fieldset's formatting context, so the grid
  gap that spaces every label from its control never reached it -- which left
  the questions sitting tighter than the plain labels. It has to carry the
  spacing itself.
*/
.field > legend { margin-bottom: .5rem; }

/* A hint belongs to the label above it, not to the control below. */
.field > .field-hint { margin-top: -.15rem; }

.req { color: var(--danger); }
.optional { font-weight: 400; color: var(--ink-2); }

.field input[type="text"],
.field input[type="date"],
.field input[type="time"],
.field textarea {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  padding: .6rem .7rem;
  min-height: 2.75rem;
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

/* Safari sizes and centres these natively, which overflowed the dialog and
   left the value floating in the middle of the field. */
.field input[type="date"],
.field input[type="time"] {
  -webkit-appearance: none;
  appearance: none;
  text-align: left;
}

.field input[type="date"]::-webkit-date-and-time-value,
.field input[type="time"]::-webkit-date-and-time-value {
  text-align: left;
  margin: 0;
}

.field input[type="date"]::-webkit-calendar-picker-indicator,
.field input[type="time"]::-webkit-calendar-picker-indicator {
  margin-inline-start: auto;
}

.field textarea { resize: vertical; line-height: 1.5; }

.field input::placeholder,
.field textarea::placeholder { color: var(--ink-2); opacity: 1; }

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--form-gap); }

.field-hint {
  margin: 0;
  font-size: .82rem;
  color: var(--ink-2);
}

/* Each answered question reveals the next, so give the revealed ones a rule
   that shows they belong to the step above. */
.detail-step {
  padding-left: .85rem;
  border-left: 2px solid var(--line);
}

.chapter-tools {
  display: flex;
  gap: .4rem;
  margin-bottom: .15rem;
}

.btn-tiny {
  min-height: 2rem;
  padding: .2rem .7rem;
  font-size: .8rem;
  font-weight: 500;
}

.chapter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.6rem, 1fr));
  gap: .3rem;
}

.chapter-grid input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.chapter-grid label {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.5rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  font-size: .9rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.chapter-grid label:hover { background: var(--surface-3); }

.chapter-grid input:checked + label {
  background: var(--ink);
  color: var(--surface);
  border-color: var(--ink);
  font-weight: 600;
}

.chapter-grid input:focus-visible + label {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.field-error {
  margin: 0;
  font-size: .84rem;
  font-weight: 600;
  color: var(--danger);
  min-height: 0;
}

.field-error:empty { display: none; }

input[aria-invalid="true"] { border-color: var(--danger); border-width: 2px; }

.type-choice { display: flex; flex-wrap: wrap; gap: .5rem; }

.type-choice input { position: absolute; opacity: 0; pointer-events: none; }

.type-choice label {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: 2.5rem;
  padding: .4rem .8rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  font-size: .9rem;
  cursor: pointer;
  background: var(--surface-2);
}

.type-choice label:hover { background: var(--surface-3); }

.type-choice input:checked + label {
  background: var(--ink);
  color: var(--surface);
  border-color: var(--ink);
  font-weight: 600;
}

.type-choice input:checked + label .glyph::before { color: inherit; }

.type-choice input:focus-visible + label {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.dialog-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: .5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}

.dialog-actions-spacer { flex: 1; }

.dialog-close { min-width: 2.5rem; min-height: 2.5rem; font-size: 1.3rem; }

.day-dialog-meta { margin: 0 0 1rem; color: var(--ink-2); font-size: .9rem; }

/* ---------- Footer ---------- */

/*
  One line where there is room for one. The signature sat centred with the
  version stranded on the far left below it, two separate footers sharing a
  footer; they are a row now, with a rule above to end the page on something
  deliberate.
*/
.site-footer {
  max-width: 82rem;
  margin: 0 auto;
  padding: 1.1rem 1.25rem calc(2.5rem + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .35rem 1.5rem;
  border-top: 1px solid var(--line);
  font-size: .85rem;
  color: var(--ink-2);
}

.site-footer .footer-signature { margin-right: auto; }
.site-footer .school-only { flex-basis: 100%; order: 3; }

.site-footer p { margin: 0; }

.link-btn {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.link-btn:hover { color: var(--accent); }

.version {
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
  opacity: .8;
}

/* ---------- Responsive ---------- */

/*
  An iPad in portrait is 1024px wide, and the week has to fit in it. The table
  scrolls sideways rather than breaking, which is correct but useless: a week
  view whose Friday is off the edge is not a week view. Below a desktop width
  the period column narrows and the table is allowed to be smaller, so all five
  days are on screen at once.
*/
@media (max-width: 75rem) {
  .timetable { min-width: 38.5rem; }
  .tt-corner { width: 3.5rem; }
  /* A room code and a period time are things you read off the grid in a
     corridor, not smallprint: they hold at 11.5px rather than 10.9. */
  .tt-period-time { font-size: .72rem; }
  .tt-room { font-size: .72rem; }
}

@media (max-width: 62rem) {
  .layout {
    grid-template-columns: minmax(0, 1fr);
    /*
      The week first. On a phone the old order put the filters above the
      thing they filter, so the first screenful of a calendar was two
      checkboxes and an empty "nothing scheduled yet".
    */
    grid-template-areas: "main" "upcoming" "organiser" "filters" "settings";
    grid-template-columns: minmax(0, 1fr);

  }
  /* One column: the rail stops being a column and its panels place
     themselves, so the week comes before the things that filter it. */
  .sidebar-rail { display: contents; }
  .panel-upcoming { grid-area: upcoming; }
  .panel-filters { grid-area: filters; }
  .calendar td { height: 5.5rem; }
  .day { min-height: 5.5rem; }
}

@media (max-width: 40rem) {
  .timetable { min-width: 38rem; }
  .tt-corner { width: 3.4rem; }
  .timetable td, .tt-lesson { height: 3.4rem; min-height: 3.4rem; }
  .tt-name { font-size: .74rem; }
  .tt-chips .chip { padding: .05rem .2rem; font-size: 0; justify-content: center; min-height: 1rem; }
  .tt-chips .chip-text { display: none; }
  .tt-chips .chip-glyph { font-size: .68rem; }
  /*
    One row, not four.

    The bar used to wrap onto a line each for the wordmark, Switch, the view
    switch and Add task, which is 230 pixels of chrome before a phone shows a
    single lesson. Everything shortens instead: the mark without the wordmark,
    the arrow without "Switch", and a plus without "Add task". The accessible
    names stay on the buttons, so nothing is lost to a screen reader.
  */
  .app-bar-inner {
    padding: .6rem .75rem;
    flex-wrap: nowrap;
    gap: .4rem;
    align-items: center;
  }
  .wordmark-group { margin-right: auto; gap: 0; min-width: 0; }
  .wordmark { font-size: 0; gap: 0; }
  .wordmark-text { display: none; }
  .wordmark-mark { width: 1.6rem; height: 1.6rem; }
  /* The bar's signature only. The one in the footer is the one that signs. */
  .app-bar .signature { display: none; }
  .btn-back { padding-inline: .55rem; font-size: 0; gap: 0; }
  .btn-back [aria-hidden] { font-size: 1rem; }
  .view-switch .segmented label { padding: .3rem .5rem; font-size: .78rem; }
  #add-task-top { padding-inline: .7rem; font-size: 0; gap: 0; }
  #add-task-top .btn-plus { font-size: 1.2rem; line-height: 1; }
  .layout {
    /* Keeps the side insets a narrow screen still needs in landscape. */
    padding: 1.75rem calc(.75rem + env(safe-area-inset-right, 0px)) 1.5rem
             calc(.75rem + env(safe-area-inset-left, 0px));
    gap: 1rem;
  }
  .main { padding: .9rem; }
  .calendar th { font-size: .74rem; letter-spacing: .04em; text-align: center; }
  .calendar td { height: 4.25rem; }
  .day { min-height: 4.25rem; padding: .2rem; gap: .15rem; }
  .day-num { font-size: .85rem; }
  .chip { padding: .1rem .2rem; font-size: 0; gap: 0; justify-content: center; min-height: 1.1rem; }
  .chip-text { display: none; }
  .chip-glyph { font-size: .72rem; }
  .chip-more { font-size: .72rem; }
  .field-row { grid-template-columns: minmax(0, 1fr); }
  .dialog { padding: 0 1rem 1rem; }
  .dialog-head { margin: 0 -1rem; padding: 1rem 1rem .7rem; }
}

/* ---------- Preferences ---------- */

@media (prefers-reduced-motion: reduce) {
  /* Snapping moves the page on its own, which is exactly what this asks us
     not to do. */
  html { scroll-snap-type: none; }

  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

@media (prefers-contrast: more) {
  :root { --line: var(--line-strong); }
  .calendar td, .calendar th, .panel, .main, .up-btn, .task-row { border-color: var(--ink); }
  .chip { outline: 1px solid var(--ink); }
}

@media print {
  .app-bar, .sidebar, .period-nav, .grid-hint, .site-footer, .skip-link { display: none !important; }
  body { background: #fff; }
  .layout { display: block; padding: 0; }
  .main { border: 0; box-shadow: none; padding: 0; }
  .calendar td { height: auto; min-height: 4.5rem; }
  .chip { border: 1px solid #000; }
}

/* ---------- The welcome ---------- */

/*
  A brand moment, then the app. Three seconds, once per launch, with nothing to
  press: the segments scramble into a grip, the name arrives, and the whole
  thing lifts away.

  It is decorative from end to end -- aria-hidden, and pointer-events none, so a
  screen reader is handed the app underneath immediately and a stray tap during
  it reaches what it was aimed at rather than being swallowed.

  The ground is a warm near-black rather than pure black. Against it the cherry
  reads as cherry instead of turning fluorescent, and it is the same ink the
  dark theme is built on, so the welcome dissolves into the app rather than
  cutting to a different room.
*/

.welcome {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: #141011;
  pointer-events: none;
  animation: welcome-leave .55s cubic-bezier(.4, 0, .2, 1) 2.35s both;
}

/* While it is up, the page behind must not scroll under it. */
body.is-booting { overflow: hidden; }

.welcome-inner {
  display: grid;
  justify-items: center;
  gap: .35rem;
  padding: 2rem;
}

.welcome-logo {
  width: clamp(5.5rem, 22vmin, 8.5rem);
  height: clamp(5.5rem, 22vmin, 8.5rem);
}

.welcome-ring { stroke: #D4374F; }
.welcome-dot { fill: #F7DFC9; }

/*
  Each segment swings into place around the ring's own centre, from a different
  angle and a different distance out, so they arrive as three separate things
  finding each other rather than as one object fading up.
*/
.welcome-seg {
  transform-box: view-box;
  transform-origin: 32px 32px;
  animation: welcome-grip .85s cubic-bezier(.22, 1.2, .36, 1) both;
}

.welcome-seg-a { animation-delay: .05s; --from: -150deg; }
.welcome-seg-b { animation-delay: .17s; --from: 120deg; }
.welcome-seg-c { animation-delay: .29s; --from: -85deg; }

@keyframes welcome-grip {
  from { opacity: 0; transform: rotate(var(--from)) scale(1.45); }
  to   { opacity: 1; transform: rotate(0deg) scale(1); }
}

/* The centre is what the grip closes on, so it lands last and settles. */
.welcome-dot {
  transform-box: view-box;
  transform-origin: 32px 32px;
  animation: welcome-catch .5s cubic-bezier(.34, 1.56, .64, 1) .62s both;
}

@keyframes welcome-catch {
  from { opacity: 0; transform: scale(0); }
  to   { opacity: 1; transform: scale(1); }
}

.welcome-name {
  margin: 1.1rem 0 0;
  font-family: var(--font-display);
  font-weight: 620;
  font-size: clamp(2.2rem, 8vmin, 3.4rem);
  letter-spacing: 0;
  color: #FCFBF8;
  animation: welcome-rise .6s cubic-bezier(.2, .8, .3, 1) 1s both;
}

.welcome-signature {
  margin: 0;
  font-family: var(--font-signature);
  font-size: clamp(1.4rem, 5vmin, 2rem);
  line-height: 1;
  color: #C98A63;
  transform-origin: center;
  animation: welcome-rise .6s cubic-bezier(.2, .8, .3, 1) 1.25s both;
}

@keyframes welcome-rise {
  from { opacity: 0; transform: translateY(.6rem); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes welcome-leave {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.06); visibility: hidden; }
}

/*
  Asked for stillness, the mark is simply there: no scramble, no travel, and
  the whole thing is over in well under half the time.
*/
@media (prefers-reduced-motion: reduce) {
  .welcome { animation: welcome-leave-quiet .4s linear 1.1s both; }

  .welcome-seg,
  .welcome-dot,
  .welcome-name,
  .welcome-signature {
    animation: welcome-appear .3s linear both;
    transform: none;
  }

  .welcome-name { animation-delay: .2s; }
  .welcome-signature { animation-delay: .3s; }

  @keyframes welcome-appear {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  @keyframes welcome-leave-quiet {
    from { opacity: 1; }
    to   { opacity: 0; visibility: hidden; }
  }
}

/* ---------- Choosing a half ---------- */

/*
  The choice takes the screen it is on. At 60vh it sat in a band across the
  middle of a 1366-pixel iPad with the footer stranded in the dead space below
  it, which reads as a page that failed to load rather than as a question.
*/
.chooser {
  min-height: calc(100dvh - 4.5rem);
  display: grid;
  place-items: center;
  padding: 2rem 1.25rem 3rem;
}

.chooser-inner {
  width: 100%;
  max-width: 44rem;
  text-align: center;
}

.chooser-title {
  font-family: var(--font-display);
  font-size: clamp(1.65rem, 4.6vw, 2.25rem);
  font-weight: 620;
  letter-spacing: -.005em;
  margin-bottom: 1.6rem;
}

.chooser-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1rem;
}

.tile {
  display: grid;
  justify-items: start;
  gap: .4rem;
  padding: 1.5rem 1.35rem;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  /*
    The door is painted on its frame, not on a bar across the lintel. A
    four-pixel edge on one side of a fourteen-pixel radius fights the corner
    it runs into; the same colour at a hairline all the way round says the
    same thing and sits on the shape instead of across it.
  */
  border: 1px solid color-mix(in oklab, var(--door, var(--accent)) 30%, var(--line));
  border-radius: var(--radius-lg);
  cursor: pointer;
  transition:
    border-color .18s ease,
    transform .18s cubic-bezier(.22, .9, .3, 1),
    box-shadow .18s ease;
}

/*
  What the half would tell you if you opened it. A door that says nothing has
  to be opened to find out whether it needed opening.
*/
.tile-live {
  margin-top: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--line);
  width: 100%;
  font-size: .85rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.tile-live:empty { display: none; }

/*
  Each door is painted the colour of the room behind it: cherry for the
  schoolwork half, the money half's own lime for the other. The two halves do
  genuinely look different inside, and a front door that says which is which
  is more use than two identical ones.
*/
.tile[data-area="money"] { --door: var(--money-door); }
.tile-icon { color: var(--door, var(--accent)); }

.tile:hover {
  border-color: color-mix(in oklab, var(--door, var(--accent)) 70%, var(--line));
  transform: translateY(-3px);
  box-shadow: 0 14px 30px -18px rgba(0, 0, 0, .45);
}

.tile:hover .tile-icon { transform: scale(1.06) rotate(-2deg); }

/* Pressed, it gives: the only part of this that is felt rather than seen. */
.tile:active {
  transform: translateY(0) scale(.985);
  transition-duration: .06s;
}

/*
  The two of them arrive rather than appear. A short rise, the second a beat
  behind the first, so the eye is led across them in the order they are meant
  to be read. `backwards` keeps them at the start of the animation during the
  delay, which is the difference between a stagger and a flicker.

  They used to start from nothing and overshoot on the way in. Starting from
  nothing means the first thing the app shows, on a cold open or a slow
  device, is an empty page: a screenshot taken a third of a second in caught
  one tile at thirteen per cent and the other at zero. They start from
  something now, and the curve is an exponential ease-out, which is how a
  thing that has weight comes to rest.
*/
.chooser-title { animation: chooser-rise .34s cubic-bezier(.16, 1, .3, 1) backwards; }
.chooser-tiles .tile {
  animation: tile-arrive .34s cubic-bezier(.16, 1, .3, 1) backwards;
}

.chooser-tiles .tile:nth-child(1) { animation-delay: .07s; }
.chooser-tiles .tile:nth-child(2) { animation-delay: .15s; }

@keyframes chooser-rise {
  from { opacity: .4; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

@keyframes tile-arrive {
  from { opacity: .35; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

/*
  The curtain that stands in front of the switch.

  It takes its colour from the half that is arriving -- the class that makes
  the money half dark is already on the body by the time this paints -- so the
  dark half arrives out of its own dark rather than through a flash of paper.
*/
.area-curtain {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  place-items: center;
  background: var(--ground);
  /*
    No fill mode. "both" keeps the last keyframe applying for ever, and an
    animation's value beats a transition's: the curtain faded in, then sat at
    opacity 1 while the class that was supposed to dissolve it did nothing at
    all. It was only ever removed, never seen to go.
  */
  animation: curtain-in .16s ease-out;
  transition:
    opacity .46s cubic-bezier(.4, 0, .25, 1),
    transform .46s cubic-bezier(.22, .61, .36, 1);
}

/*
  Leaving, in two parts. The ring and the word go first -- that is what "done"
  looks like -- and the curtain follows a beat later, receding towards the
  reader as it dissolves so the half behind it is arriving rather than being
  uncovered.
*/
.area-curtain .curtain-inner {
  transition:
    opacity .16s ease-in,
    transform .22s cubic-bezier(.4, 0, .6, 1);
}

.area-curtain.is-settling .curtain-inner {
  opacity: 0;
  transform: scale(.92);
}

.area-curtain.is-lifting {
  opacity: 0;
  transform: scale(1.06);
  pointer-events: none;
}

@keyframes curtain-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.curtain-inner { display: grid; justify-items: center; gap: 1rem; }

.curtain-ring { width: 3.4rem; height: 3.4rem; }
.curtain-arcs { stroke: var(--accent); transform-origin: 50% 50%; animation: curtain-turn 1.1s linear infinite; }
.curtain-dot { fill: var(--ink-2); animation: curtain-pulse 1.1s ease-in-out infinite; }

@keyframes curtain-turn {
  to { transform: rotate(360deg); }
}

@keyframes curtain-pulse {
  0%, 100% { opacity: .35; transform: scale(.9); transform-origin: 50% 50%; }
  50% { opacity: 1; transform: scale(1.05); transform-origin: 50% 50%; }
}

.curtain-what {
  margin: 0;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-2);
  animation: chooser-rise .3s .1s ease-out backwards;
}

/*
  And the half comes forward to meet it.

  No fade: the page is whole and already behind the curtain, and fading it in
  under a curtain that is itself fading leaves both of them half there at
  once, which is the washed-out moment this was trying to avoid. It only
  settles -- a little nearer, a little straighter -- while the curtain goes.
*/
.area-arriving {
  animation: area-arrive .52s cubic-bezier(.19, .78, .28, 1) both;
}

@keyframes area-arrive {
  from { transform: translateY(7px) scale(.991); }
  to { transform: none; }
}

/*
  Asked to stay still, everything here is a change of state and nothing else:
  no rise, no stagger, no ghost (the script does not make one), no arrival.
*/
@media (prefers-reduced-motion: reduce) {
  .tile { transition: none; }
  .tile:hover, .tile:active { transform: none; box-shadow: none; }
  .tile:hover .tile-icon { transform: none; }
  .chooser-title, .chooser-tiles .tile, .area-arriving { animation: none; }
  /* No curtain is made at all when motion is turned down; this is belt and
     braces for a browser that reports the preference late. */
  .area-curtain { display: none; }
}

.tile-icon {
  width: 2rem;
  height: 2rem;
  color: var(--accent);
  margin-bottom: .35rem;
  transition: transform .18s cubic-bezier(.22, .9, .3, 1);
}

.tile-name {
  font-size: 1.2rem;
  font-weight: 680;
  letter-spacing: -.02em;
}

.tile-note {
  font-size: .88rem;
  color: var(--ink-2);
}

.btn-back { flex-shrink: 0; }

/* ---------- Money ---------- */

.area-money {
  max-width: 62rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 2rem;
}

.tx-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}

/* The row is a list item holding one button; the button does the laying out,
   so the item itself gets out of the way. */
.tx {
  display: block;
  padding: .55rem .1rem;
  border-top: 1px solid var(--line);
}

.tx:first-child { border-top: 0; }

.tx-date {
  font-size: .8rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.tx-what { min-width: 0; }

.tx-title {
  display: block;
  font-size: .92rem;
  overflow-wrap: anywhere;
}

.tx-note {
  display: block;
  font-size: .78rem;
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

/* Money read in a hurry is read down the right-hand edge, so the figures line
   up on their decimal point and money in is the one that stands out. */
.tx-amount {
  font-variant-numeric: tabular-nums;
  font-size: .92rem;
  font-weight: 600;
  white-space: nowrap;
}

.tx.is-in .tx-amount { color: var(--s-ess); }

/* ---------- Money: rules and categories ---------- */

.money-rules {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .82rem;
  line-height: 1.55;
  min-height: 12rem;
  margin-bottom: .75rem;
  /* Rules are one per line and a wrapped line reads as two rules. */
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

.tally {
  list-style: none;
  margin: .75rem 0 0;
  padding: 0;
  display: grid;
  gap: .1rem;
}

.tally-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 2.5rem auto;
  align-items: baseline;
  gap: .6rem;
  padding: .3rem .1rem;
  border-top: 1px solid var(--line);
  font-size: .85rem;
}

.tally-name { text-transform: capitalize; overflow-wrap: anywhere; }

.tally-count {
  font-size: .78rem;
  color: var(--ink-2);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.tally-sum {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}

/* The pile nothing has claimed is the one worth a rule, so it says so. */
.tally-row.is-loose .tally-name { color: var(--accent); font-weight: 600; }

.tx-date { grid-area: date; }
.tx-what { grid-area: what; }
.tx-amount { grid-area: amount; }

.tx-cat {
  grid-area: cat;
  justify-self: start;
  margin-top: .2rem;
  padding: .1rem .45rem;
  font-size: .72rem;
  text-transform: capitalize;
  color: var(--ink-2);
  background: var(--surface-3);
  border-radius: var(--r-pill);
}

.tx-cat.is-loose {
  color: var(--accent);
  background: var(--accent-soft);
}

/* ---------- Money: the month's report ---------- */

.report-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  margin-bottom: .5rem;
}

.report-nav { display: flex; align-items: center; gap: .4rem; }

.report-month {
  font-weight: 600;
  font-size: .9rem;
  min-width: 8.5rem;
  text-align: center;
}

.report-total {
  margin: 0 0 .9rem;
  font-size: 1.05rem;
}

.report-total strong {
  font-size: 1.6rem;
  font-weight: 650;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

.report-against {
  display: block;
  font-size: .85rem;
  color: var(--ink-2);
}

.report-sub {
  margin: 1.25rem 0 .35rem;
  font-size: .9rem;
  font-weight: 600;
  color: var(--ink-2);
}

.report-list { list-style: none; margin: 0; padding: 0; }

.report-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: .1rem .75rem;
  padding: .5rem .1rem;
  border-top: 1px solid var(--line);
}

.report-name { text-transform: capitalize; overflow-wrap: anywhere; font-size: .92rem; }

.report-now {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
  font-size: .92rem;
}

.report-was {
  grid-column: 1 / -1;
  font-size: .78rem;
  color: var(--ink-2);
}

.report-budget { grid-column: 1 / -1; display: grid; gap: .2rem; margin-top: .15rem; }

.report-bar {
  height: 4px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  overflow: hidden;
}

.report-bar-fill {
  display: block;
  height: 100%;
  background: var(--s-ess);
  border-radius: var(--r-pill);
}

.report-limit { font-size: .76rem; color: var(--ink-2); }

/* Close to the line, and past it. Colour is never the only signal: the words
   beside the bar say which it is. */
.report-row.is-close .report-bar-fill { background: var(--now); }
.report-row.is-close .report-limit { color: var(--ink); font-weight: 600; }

.report-row.is-over .report-bar-fill { background: var(--danger); }
.report-row.is-over .report-limit { color: var(--danger); font-weight: 600; }

/* ---------- The money dashboard ---------- */

/*
  A different kind of page from the calendar on purpose: no paper, no serif, no
  colour-per-subject. Cards on hairlines, numbers set large in tabular figures,
  and one hue that gets darker as the figure gets bigger.

  The ramp below is the whole colour scheme. Five steps of one hue, light to
  dark, because the thing being encoded is magnitude -- five different hues
  would say "different" where this says "more". Both sets were checked against
  the surface they sit on for contrast, for a chroma floor, and for separation
  under the three common kinds of colour blindness; the dark steps are chosen
  for the dark surface rather than flipped from the light ones.

  Every bar carries its own number, so the colour is never load-bearing.
*/
/*
  The money half runs on its own surface, and it is dark in both themes.

  That is a decision rather than an oversight: a ledger is read in one glance
  at the top of the hour and at the end of the day, the figures want to glow
  rather than sit on paper, and the calendar's warm paper is the wrong ground
  for it. Every token the shared components use is redefined here, so the
  cards, bars, tables and notices re-skin without a line of their markup
  changing.

  Contrast was measured, not guessed: body text 15.2:1 on the card, secondary
  7.9:1, the lime 13.8:1, and black on the lime 15.0:1. The ramp is one hue,
  five steps, checked against this surface -- more is brighter, because on a
  dark ground that is what "more" looks like.
*/
body.on-money {
  color-scheme: dark;

  --ground: #0A120E;
  --surface: #121C17;
  --surface-2: #18251E;
  --surface-3: #1E2F26;
  --ink: #E8F2EA;
  --ink-2: #9FB3A6;
  --line: #243730;
  /* The dashed line the daily columns are read against is data, not chrome,
     so it clears 3:1 rather than the 2.6:1 it first had. */
  --line-strong: #56786A;

  --accent: #C9F45C;
  --accent-hover: #D8FF7A;
  --accent-ink: #0A120E;
  --accent-soft: #1C2A1A;

  --danger: #FF8A80;
  --now: #F2C94C;
  --s-ess: #7FE0A0;

  --focus: #C9F45C;
  --focus-halo: #0A120E;

  --seq-1: #2C5D3F;
  --seq-2: #3C7F52;
  --seq-3: #4FA468;
  --seq-4: #6BC983;
  --seq-5: #8EEBA0;

  --card-line: var(--line);
  /*
    A hairline and a shadow, not a hairline and a halo and a shadow. The ring
    was a zero-offset lime glow sitting on top of the border, so every card
    wore two edges and a forty-pixel bloom; eight of them down a page is a
    default nobody chose. The shadow has an offset and a shorter blur now, so
    it reads as height rather than as fog.
  */
  --glow: 0 1px 2px rgba(0, 0, 0, .35), 0 10px 24px -16px rgba(0, 0, 0, .8);

  /* The class goes on the body, not on the half, so the bar at the top and
     the footer at the bottom come with it. A page that is dark in the middle
     and warm at both ends reads as two apps stacked. */
  background: var(--ground);
  color: var(--ink);
}

body.on-money .app-bar { box-shadow: none; }
body.on-money .wordmark-mark { color: var(--accent); }

.seq-1 { background: var(--seq-1); }
.seq-2 { background: var(--seq-2); }
.seq-3 { background: var(--seq-3); }
.seq-4 { background: var(--seq-4); }
.seq-5 { background: var(--seq-5); }

/* ---------- Tabs ---------- */

.money-tabs {
  display: flex;
  gap: .25rem;
  margin: 0 0 .85rem;
  border-bottom: 1px solid var(--line);
}

.money-tab {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: .55rem .2rem;
  margin-right: 1.1rem;
  font: inherit;
  font-size: .95rem;
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
}

.money-tab.is-on {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

.money-tab:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-mark);
}

/* ---------- Cards ---------- */

/* align-items, not just align-content: a card should be as tall as what is in
   it, rather than as tall as whatever sits beside it. */
.money-page { display: grid; gap: 1rem; align-content: start; align-items: start; }

/* One column until there is room for two; the columns themselves always
   stack, so a card is never held up waiting for the one beside it. */
.dash-split { display: grid; gap: 1rem; align-content: start; }
.dash-col { display: grid; gap: 1rem; align-content: start; }

.card {
  background: var(--surface);
  border: 1px solid var(--card-line);
  border-radius: var(--radius-lg);
  padding: .95rem 1.05rem 1.1rem;
}

.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .6rem;
  margin-bottom: .55rem;
}

/*
  These were uppercase wide-tracked micro-labels, on every card, nine of them
  down the page. One of those is a label; nine is a rhythm, and the rhythm was
  the loudest thing on a page whose job is to show numbers. They are headings
  now: sentence case, the weight doing the work the letter-spacing was doing.
*/
.card-title {
  margin: 0;
  font-size: .98rem;
  font-weight: 650;
  letter-spacing: -.01em;
  color: var(--ink);
}

.card .panel-hint { margin-top: 0; }

/* The folds under the dashboard: everything that is not a question you open
   the app to ask. */
.card-fold { padding: 0; }

.fold-summary {
  display: grid;
  gap: .15rem;
  padding: .8rem 1.05rem;
  cursor: pointer;
  list-style: none;
}

.fold-summary::-webkit-details-marker { display: none; }

.fold-summary::after {
  content: "";
  position: absolute;
  right: 1.1rem;
  width: .5rem;
  height: .5rem;
  border-right: 2px solid var(--ink-2);
  border-bottom: 2px solid var(--ink-2);
  transform: translateY(.35rem) rotate(45deg);
}

.card-fold { position: relative; }
.card-fold[open] > .fold-summary::after { transform: translateY(.55rem) rotate(225deg); }

.fold-summary:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

.fold-title { font-weight: 650; font-size: .95rem; padding-right: 1.5rem; }
.fold-note { font-size: .8rem; color: var(--ink-2); padding-right: 1.5rem; }

.fold-body { padding: 0 1.05rem 1.1rem; display: grid; gap: 1.35rem; }
.fold-body > .subpanel { display: grid; gap: .45rem; }
.fold-body .panel-title { margin: 0; }

/* ---------- The balance ---------- */

.kpi-label {
  margin: 0;
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-2);
}

/*
  The headline figure. A single current number is not a chart -- it is a
  number, set large enough that it is the first thing read, in tabular figures
  so it does not reflow as it changes.
*/
/*
  Sized under the lead on purpose. What is in the account and what is left to
  spend today are different questions, and only one of them is asked every
  time the app is opened. Two figures of the same size asked both at once and
  answered neither.
*/
.kpi-figure {
  margin: .1rem 0 .1rem;
  font-size: clamp(1.7rem, 4.5vw, 2.1rem);
  font-weight: 680;
  line-height: 1.1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.kpi-figure.is-negative { color: var(--danger); }
/* Nothing saved is not a loss, so it is not in the colour a loss wears. */
.saved-figure.is-waiting { color: var(--ink-2); }
.kpi-figure.is-missing { color: var(--ink-2); }

.kpi-move {
  margin: 0 0 .2rem;
  font-size: .9rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.kpi-note { margin: .1rem 0 0; font-size: .82rem; color: var(--ink-2); }

.kpi-strip, .rate-strip {
  list-style: none;
  margin: .9rem 0 0;
  padding: .8rem 0 0;
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
  gap: .7rem 1rem;
}

.kpi-strip li, .rate-strip li { display: grid; gap: .1rem; }

.strip-label { font-size: .76rem; color: var(--ink-2); }

.strip-value {
  font-size: 1.08rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.strip-value.is-negative { color: var(--danger); }

/* The two that answer the card's question, a step above the three that
   qualify the answer. */
.rate-strip li.is-lead .strip-value { font-size: 1.32rem; letter-spacing: -.015em; }
.rate-strip li.is-lead .strip-label { color: var(--ink); font-weight: 600; }

/* ---------- Category bars ---------- */

.chart-caption { margin: 0 0 .7rem; font-size: .82rem; color: var(--ink-2); }
.chart-caption.is-stale { color: var(--ink); font-weight: 600; }

.bars { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }

.bar-open {
  appearance: none;
  width: 100%;
  background: none;
  border: 0;
  padding: .2rem 0;
  font: inherit;
  text-align: left;
  color: inherit;
  cursor: pointer;
  display: grid;
  gap: .22rem;
}

.bar-open:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-mark); }

.bar-head { display: flex; justify-content: space-between; gap: .6rem; align-items: baseline; }
.bar-name { font-weight: 600; font-size: .95rem; text-transform: capitalize; }
.bar-value { font-variant-numeric: tabular-nums; font-weight: 650; }

/* Thin marks, square at the baseline and rounded at the data end. */
.bar-track {
  display: block;
  height: 10px;
  background: var(--surface-3);
  border-radius: var(--r-mark);
  overflow: hidden;
}

.bar-fill {
  display: block;
  height: 100%;
  border-radius: 0 var(--r-mark) var(--r-mark) 0;
}

.bar-note { font-size: .76rem; color: var(--ink-2); }
.bar-note.is-close, .bar-note.is-over { color: var(--ink); font-weight: 600; }

/* Past a budget the bar leaves the ramp, because that is a state and not a
   magnitude. The words beside it say so too. */
.bar-fill.is-over { background: var(--danger); }
.bar-fill.is-close { background: var(--now); }

.bar-detail {
  list-style: none;
  margin: .3rem 0 .5rem;
  padding: .45rem 0 .1rem .9rem;
  border-left: 2px solid var(--line);
  display: grid;
  gap: .3rem;
}

.tx-more {
  font-size: .78rem;
  color: var(--ink-2);
  padding: .3rem 0 0;
}

/* ---------- The verdict ---------- */

/*
  A state, so it carries a mark and a word as well as a colour -- the colour is
  the last of the three to be read and the only one that can fail.
*/
.verdict {
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  padding: .7rem .8rem;
  /*
    The state used to be a four-pixel bar down the left edge. It is a tinted
    edge and a tinted ground now: the same hue, around the whole block rather
    than stacked on one side of it, which is a quieter way to say the same
    thing and does not read as a tab somebody forgot to attach to anything.
  */
  border: 1px solid var(--state-line, var(--line));
  border-radius: var(--radius);
  background: var(--state-wash, var(--surface-2));
  margin: 0 0 .9rem;
}

.verdict-mark {
  font-weight: 700;
  font-size: 1.05rem;
  line-height: 1.3;
}

.verdict-words { display: grid; gap: .15rem; }
.verdict-label { font-size: .98rem; }
.verdict-why { font-size: .84rem; color: var(--ink-2); }

.verdict.is-sustainable {
  --state-line: color-mix(in oklab, var(--s-ess) 40%, var(--line));
  --state-wash: color-mix(in oklab, var(--s-ess) 8%, var(--surface-2));
}
.verdict.is-sustainable .verdict-mark { color: var(--s-ess); }
.verdict.is-tight {
  --state-line: color-mix(in oklab, var(--now) 40%, var(--line));
  --state-wash: color-mix(in oklab, var(--now) 8%, var(--surface-2));
}
.verdict.is-tight .verdict-mark { color: var(--now); }
.verdict.is-overspending {
  --state-line: color-mix(in oklab, var(--danger) 42%, var(--line));
  --state-wash: color-mix(in oklab, var(--danger) 9%, var(--surface-2));
}
.verdict.is-overspending .verdict-mark { color: var(--danger); }
.verdict.is-unclear { --state-line: var(--line-strong); }
.verdict.is-unclear .verdict-mark { color: var(--ink-2); }

/* ---------- Daily rate ---------- */

.rate-chart {
  position: relative;
  height: 9rem;
  margin: 1rem 0 .3rem;
  border-bottom: 1px solid var(--line-strong);
}

.rate-days {
  list-style: none;
  margin: 0;
  padding: 0;
  height: 100%;
  display: flex;
  align-items: flex-end;
  gap: 2px;
}

.rate-day { flex: 1 1 0; height: 100%; display: flex; align-items: flex-end; min-width: 0; }
.rate-day.is-weekend { background: linear-gradient(var(--surface-2), var(--surface-2)); }

.rate-col {
  display: block;
  width: 100%;
  border-radius: var(--r-mark) var(--r-mark) 0 0;
}

/* The line is the point of the chart: a column is only tall against something.
   Its label sits under the plot rather than over it, where it would land on
   whichever column happened to be tall that week. */
.rate-line {
  position: absolute;
  left: 0;
  right: 0;
  border-top: 2px dashed var(--line-strong);
  pointer-events: none;
}

.rate-key { display: flex; align-items: center; gap: .4rem; margin-top: .45rem; }

.rate-key-mark {
  flex: 0 0 auto;
  width: 1.4rem;
  border-top: 2px dashed var(--line-strong);
}

.weeks {
  list-style: none;
  margin: .9rem 0 0;
  padding: 0;
  display: grid;
  gap: .35rem;
}

.week {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: .7rem;
  align-items: baseline;
  font-size: .85rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid var(--line);
}

.week:last-child { border-bottom: 0; padding-bottom: 0; }
.week-when { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.week-sum { font-weight: 650; font-variant-numeric: tabular-nums; }
.week-rate { color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* ---------- The same thing as a table ---------- */

.as-table { margin-top: .9rem; font-size: .85rem; }
.as-table summary { cursor: pointer; color: var(--ink-2); }
.as-table summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.plain-table { width: 100%; border-collapse: collapse; margin-top: .6rem; }

.plain-table th, .plain-table td {
  text-align: left;
  padding: .35rem .5rem .35rem 0;
  border-bottom: 1px solid var(--line);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

.plain-table thead th {
  font-size: .74rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.plain-table tbody th { font-weight: 600; }

/* ---------- The analytics sector ---------- */

.insight-headline {
  margin: .2rem 0 .8rem;
  font-size: 1.25rem;
  font-weight: 650;
  line-height: 1.35;
  letter-spacing: -.01em;
}

.insight-prose { margin: .55rem 0; font-size: .92rem; line-height: 1.6; }

.insight-notes {
  list-style: none;
  margin: .9rem 0 0;
  padding: 0;
  display: grid;
  gap: .5rem;
}

.insight-notes li {
  font-size: .88rem;
  line-height: 1.5;
  padding-left: .8rem;
  border-left: 2px solid var(--accent);
}

.insight-sub {
  margin: 1.1rem 0 .4rem;
  font-size: .78rem;
  font-weight: 700;
  color: var(--ink-2);
}

.watch-list { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; font-size: .88rem; }

.sync-status.is-busy { color: var(--ink); font-weight: 600; }

.plan-save { margin: .7rem 0; font-size: .95rem; }
.plan-table { margin-top: .8rem; }
.plan-cat { text-transform: capitalize; }
.plan-limit { font-weight: 650; }
.plan-why { color: var(--ink-2); font-size: .82rem; }
.plan-year { margin: .9rem 0 .2rem; font-size: .9rem; font-weight: 600; }

/* ---------- Wide enough for two ---------- */

@media (min-width: 60rem) {
  /*
    Two columns, each flowing on its own.

    This was a two-column grid with the wide cards spanning both tracks, which
    makes every pair share a row: the shorter card of the pair stops where it
    stops and the row stays as tall as the taller one, so the difference is
    left behind as a hole. Measured on a 1024 screen the holes came to 790 of
    2720 pixels -- most of a screen of nothing, in the middle of the page.

    A column that flows has no such rows. The wide cards stay wide, and the
    four that pair off are two stacks: what happened on the left, what is
    being put by on the right.
  */
  .dash-split { grid-template-columns: 1fr 1fr; align-items: start; }
  .fold-body { grid-template-columns: 1fr 1fr; }
  .fold-body > .subpanel:first-child { grid-column: 1 / -1; }
  #money-more .fold-body, #money-history .fold-body { grid-template-columns: 1fr; }
}

/* ---------- Saying something where it can be seen ---------- */

/*
  The message that explains why nothing happened cannot live in a region only
  a screen reader reads, nor behind a fold. This sits above everything on the
  money page and stays until it is dismissed.
*/
.notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .5rem 1rem;
  margin: 0 0 .75rem;
  padding: .75rem .9rem;
  border: 1px solid var(--state-line, var(--line));
  border-radius: var(--radius);
  background: var(--state-wash, var(--surface-2));
}

.notice-text { margin: 0; font-size: .9rem; line-height: 1.5; max-width: 62ch; }
.notice-acts { display: flex; align-items: center; gap: .9rem; margin-left: auto; }

.notice.is-good {
  --state-line: color-mix(in oklab, var(--s-ess) 40%, var(--line));
  --state-wash: color-mix(in oklab, var(--s-ess) 8%, var(--surface-2));
}
.notice.is-warn {
  --state-line: color-mix(in oklab, var(--now) 40%, var(--line));
  --state-wash: color-mix(in oklab, var(--now) 8%, var(--surface-2));
}
.notice.is-plain { --state-line: var(--line-strong); }

/* The connection, on the card rather than behind a fold. */
.kpi-bank {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .4rem;
  margin: .45rem 0 0;
  /*
    The dot hangs beside the sentence rather than queueing with it. As a flex
    item it was the first thing on a wrapping row, so on a phone the sentence
    took the whole width and left the dot stranded on a line of its own,
    reading as a bullet for nothing.
  */
  position: relative;
  padding-left: .9rem;
  font-size: .82rem;
  color: var(--ink-2);
}

.kpi-dot {
  position: absolute;
  left: 0;
  top: .5em;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
}

/* Never the only signal: the sentence beside it says the same thing. */
.kpi-dot.is-on { background: var(--s-ess); }
.kpi-dot.is-off { background: var(--line-strong); }
.kpi-dot.is-stale { background: var(--now); }

/* The plan, on the card the month is measured on. */
.kpi-plan {
  margin: .5rem 0 0;
  font-size: .85rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ---------- The fintech surface ---------- */

/*
  Softer corners, a hairline that catches the light, and cards that sit on the
  ground rather than on paper. Everything here is a change of surface only --
  no component's markup knows about it.
*/
.area-money .card {
  background: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--card-line);
  border-radius: var(--radius-lg);
  box-shadow: var(--glow);
}

.area-money .btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: transparent;
  border-radius: var(--r-pill);
  font-weight: 650;
}

.area-money .btn-primary:hover { background: var(--accent-hover); }

.area-money .btn-quiet {
  border-radius: var(--r-pill);
  border-color: var(--line-strong);
  background: var(--surface-2);
  color: var(--ink);
}

.area-money .btn-quiet:hover { background: var(--surface-3); }
.area-money .link-btn { color: var(--accent); }
.area-money .input { background: var(--surface-2); color: var(--ink); border-color: var(--line); }
.area-money .money-tab.is-on { border-bottom-color: var(--accent); }
.area-money .kpi-figure { letter-spacing: -.035em; }
.area-money .bar-track { background: var(--surface-3); }
.area-money .notice { background: var(--surface-2); border-radius: var(--radius); }

/* ---------- The greeting ---------- */

.greeting {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem .8rem;
  margin: 0 0 .9rem;
}

.greeting-hello {
  margin: 0;
  font-size: 1.35rem;
  font-weight: 650;
  letter-spacing: -.02em;
  color: var(--ink);
}

/*
  The name was lime, which is this half's action colour: the colour of the
  button you press and the figure that has gone wrong. Spending it on a word
  that is neither leaves it meaning nothing in the two places it has to. The
  greeting recedes instead and the name holds full strength, so the hierarchy
  is made of value rather than of hue.
*/
.greeting-hello { color: var(--ink-2); }
.greeting-hello .greeting-name { color: var(--ink); }

/*
  The lead.

  Every figure in this half is a quantity read at a glance, so they are all
  lining figures of one width: a column of amounts that shifts sideways as the
  digits change is a column you have to read twice.
*/
.today-lead {
  margin: 0 0 1rem;
  display: grid;
  gap: .3rem;
  justify-items: start;
}

.lead-label {
  margin: 0;
  font-size: .8rem;
  font-weight: 650;
  color: var(--ink-2);
}

.lead-figure {
  margin: 0;
  text-wrap: balance;
  font-size: clamp(2.6rem, 9vw, 4rem);
  font-weight: 680;
  line-height: 1;
  letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.lead-figure.is-close { color: var(--now); }
.lead-figure.is-over { color: var(--accent); }

/*
  A track with nothing in it should look like a track. At full width in a
  colour two steps up from the card it read as a rule under the figure --
  a line the eye takes for a divider rather than a gauge standing at zero.
  It is narrower, shorter, and barely above its ground now, so the fill is
  the thing you see and the empty part is the thing you do not.
*/
.lead-meter {
  width: min(100%, 18rem);
  height: .3rem;
  margin: .45rem 0 .25rem;
  border-radius: var(--r-pill);
  background: color-mix(in oklab, var(--ink-2) 16%, transparent);
  overflow: hidden;
}

.lead-meter-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--s-ess);
  transition: width .5s cubic-bezier(.22, .61, .36, 1);
}

.lead-meter.is-close .lead-meter-fill { background: var(--now); }
.lead-meter.is-over .lead-meter-fill { background: var(--accent); }

.lead-note {
  margin: 0;
  font-size: .86rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  max-width: 60ch;
}

/* Why the figure above reads what it reads. Set off by a rule so it is read
   as an aside rather than as more of the same sentence. */
.lead-aside {
  margin: .55rem 0 0;
  padding-top: .55rem;
  border-top: 1px solid var(--line);
  font-size: .82rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  max-width: 60ch;
}

/* One rule for every amount on the half, rather than per component. */
.lead-figure, .kpi-figure, .strip-value, .bar-value, .week-sum, .week-rate,
.saved-figure, .tx-amount, .rate-strip .strip-value, .plain-table td,
.today-figure, .week-line, .money-figure {
  font-variant-numeric: tabular-nums;
}

.greeting-when {
  margin: 0;
  font-size: .82rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* ---------- The budget map ---------- */

.map-wrap { margin: .2rem 0 0; }
.budget-map { display: block; width: 100%; height: auto; overflow: hidden; }

.map-node { transition: opacity .15s ease; }
/* The ribbons carry the eye from the plan to each budget. A hairline of the
   surface between them keeps two adjacent flows from reading as one. */
.map-flow {
  opacity: .38;
  stroke: var(--surface);
  stroke-width: 1.5;
}

.map-label {
  fill: var(--ink);
  font-family: var(--font-body);
  font-size: 12.5px;
  font-weight: 600;
}

.map-sub {
  fill: var(--ink-2);
  font-family: var(--font-body);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.map-sub.is-over { fill: var(--danger); }
.map-sub.is-close { fill: var(--now); }
.map-sub.is-moved { fill: var(--accent); }

.map-source { fill: var(--accent); }
.map-source-ink { fill: var(--accent-ink); font-weight: 700; font-size: 12px; }
.map-free { fill: none; stroke: var(--line-strong); stroke-width: 1; stroke-dasharray: 2 3; }

.map-legend {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem 1.1rem;
  margin: .8rem 0 0;
  font-size: .76rem;
  color: var(--ink-2);
}

.map-key { display: inline-flex; align-items: center; gap: .35rem; }
.map-key-mark { width: .8rem; height: .55rem; border-radius: var(--r-mark); background: var(--seq-3); }
.map-key-mark.is-spent { background: var(--seq-4); }
.map-key-mark.is-budget { background: var(--seq-4); opacity: .3; }
.map-key-mark.is-free { background: none; border: 1px dashed var(--line-strong); }

/* ---------- What the AI moved ---------- */

.moves {
  list-style: none;
  margin: .7rem 0 0;
  padding: 0;
  display: grid;
  gap: .35rem;
}

.move {
  display: grid;
  grid-template-columns: minmax(5rem, auto) auto auto 1fr;
  align-items: baseline;
  gap: .2rem .55rem;
  font-size: .85rem;
  font-variant-numeric: tabular-nums;
}

.move-from, .move-to { white-space: nowrap; }
.move-why { grid-column: 1 / -1; }

@media (min-width: 42rem) {
  .move-why { grid-column: auto; }
}

.move-cat { text-transform: capitalize; font-weight: 600; }
.move-from { color: var(--ink-2); }
.move-to { color: var(--accent); font-weight: 650; }
.move-why { color: var(--ink-2); font-size: .8rem; }

/* ---------- The brief, and what the spending says ---------- */

.greeting-brief {
  margin: 0;
  flex-basis: 100%;
  font-size: .95rem;
  line-height: 1.5;
  color: var(--ink);
}

.card-reading { border-top: 1px solid color-mix(in oklab, var(--accent) 45%, var(--card-line)); }

/*
  Four pieces, each with its own heading, because the four questions are the
  reason anybody opened the app. On a wide screen they pair up; on a phone
  they stack in the order they are wanted.
*/
.reading-grid {
  display: grid;
  gap: .9rem;
  margin-top: .9rem;
}

@media (min-width: 48rem) {
  .reading-grid { grid-template-columns: 1fr 1fr; gap: 1rem 1.6rem; }
}

.reading-part { display: grid; gap: .2rem; align-content: start; }

.reading-title {
  margin: 0;
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.reading-part.is-good .reading-title { color: var(--s-ess); }
.reading-part.is-bad .reading-title { color: var(--danger); }
.reading-part.is-cut .reading-title { color: var(--now); }
.reading-part.is-change .reading-title { color: var(--accent); }

.reading-text { margin: 0; font-size: .92rem; line-height: 1.55; }

/* ---------- Money from outside the plan ---------- */

@media (min-width: 34rem) {
}

/* ---------- The week, and the one quotation ---------- */

.week-line {
  margin: 0 0 .9rem;
  font-size: .88rem;
  line-height: 1.5;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.week-line strong { color: var(--ink); font-weight: 650; }
.week-line.is-over strong { color: var(--now); }

/*
  Quiet, and at the foot of the card about having money. A quotation that
  shouts is a poster; this one is a note to self.
*/
.kpi-quote {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem .6rem;
  margin: .9rem 0 0;
  padding: .1rem 0 .1rem .7rem;
  /* A neutral rule, not the action colour. Lime is what you press and what has
     gone wrong; a quotation is neither, and the italic already says it is one. */
  border-left: 1px solid var(--line-strong);
  font-size: .82rem;
  font-style: italic;
  color: var(--ink-2);
}

.kpi-quote-who {
  font-style: normal;
  font-size: .76rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  /* Was --line-strong, which is a 3:1 colour for rules and chart baselines.
     This is twelve-pixel text and has to clear 4.5:1 like any other. */
  color: var(--ink-2);
}

/* ---------- A transaction, and its details ---------- */

/*
  Every row in every list of transactions is a button now. It has to look like
  the row it replaced -- the lists are long and a page of buttons is a page of
  noise -- so the affordance is the hover, the focus ring and the cursor,
  rather than a border on each of two hundred rows.
*/
.tx-open {
  appearance: none;
  width: 100%;
  min-width: 0;
  background: none;
  border: 0;
  padding: .45rem .4rem;
  margin: -.45rem -.4rem;
  border-radius: var(--radius);
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  display: grid;
  grid-template-areas: "date what amount" "date cat amount";
  grid-template-columns: 3.2rem minmax(0, 1fr) auto;
  gap: 0 .7rem;
  align-items: baseline;
}

.tx-open:hover { background: var(--surface-2); }
.tx-open:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.tx-open .tx-date { grid-area: date; }
.tx-open .tx-what { grid-area: what; }
.tx-open .tx-amount { grid-area: amount; }
.tx-open .tx-cat { grid-area: cat; justify-self: start; }
.tx-open .tx-amount.is-in { color: var(--s-ess); }

/* Out of the month: still there, visibly set aside. */

.bar-detail.tx-list { padding-left: .6rem; }

.dialog-tx .dialog-form { display: grid; gap: .2rem; }

/* The money half's dialog is the money half's: sans, not the calendar's serif. */
body.on-money .dialog-tx .dialog-title { font-family: var(--font-body); font-weight: 650; }

.tx-figure {
  margin: .4rem 0 .8rem;
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

.tx-figure.is-in { color: var(--s-ess); }

.tx-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .3rem .9rem;
  margin: 0 0 1.1rem;
  font-size: .88rem;
}

.tx-facts dt { color: var(--ink-2); }
.tx-facts dd { margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }

.dialog-tx .field-hint { margin: .3rem 0 .8rem; }
.dialog-tx select.input { width: 100%; }

/* ---------- What the months have kept ---------- */

.card-saved { border-top: 1px solid color-mix(in oklab, var(--s-ess) 45%, var(--card-line)); }

.saved-figure {
  margin: .15rem 0 .1rem;
  font-size: clamp(1.9rem, 6vw, 2.4rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -.025em;
  font-variant-numeric: tabular-nums;
  color: var(--s-ess);
}

.saved-figure.is-negative { color: var(--danger); }

.saved-months {
  list-style: none;
  margin: .9rem 0 0;
  padding: 0;
  display: grid;
  gap: .4rem;
}

.saved-month {
  display: grid;
  grid-template-columns: 2.4rem 1fr auto;
  gap: .6rem;
  align-items: center;
  font-size: .82rem;
  font-variant-numeric: tabular-nums;
}

.saved-when { color: var(--ink-2); }
.saved-sum { font-weight: 600; }
.saved-month.is-now .saved-when { color: var(--accent); font-weight: 650; }
.saved-month.is-negative .saved-sum { color: var(--danger); }

.saved-track {
  height: 8px;
  background: var(--surface-3);
  border-radius: var(--r-mark);
  overflow: hidden;
}

.saved-fill {
  display: block;
  height: 100%;
  border-radius: 0 var(--r-mark) var(--r-mark) 0;
  background: var(--s-ess);
}

.saved-month.is-negative .saved-fill { background: var(--danger); }

/* ---------- Today ---------- */

.today-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .2rem .6rem;
  margin: 0 0 .5rem;
  font-size: .88rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.today-label {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.today-figure { color: var(--ink); font-size: 1rem; font-weight: 650; }
.today-line.is-close .today-figure { color: var(--now); }
.today-line.is-over .today-figure { color: var(--danger); }
.today-note { flex-basis: 100%; font-size: .8rem; }

/* ---------- The week, read back ---------- */

.card-debrief { border-top: 1px solid color-mix(in oklab, var(--accent) 45%, var(--card-line)); }

/* ---------- Being offered the evening, and reporting it back ---------- */

.session-actions { flex-wrap: wrap; gap: .75rem; }

.later-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-right: auto;
  flex-wrap: wrap;
}

.later-row .field-label { margin: 0; }
.later-row .input { width: 7.5rem; }

/* Three boxes and a button, on one line where there is room for one. The
   labels sit over their boxes rather than beside them: "On / At / For" read
   as a sentence across the row, which "At 19:00 For 1 hour" does not. */
.plan-row {
  display: flex;
  align-items: flex-end;
  gap: .6rem;
  margin-right: auto;
  flex-wrap: wrap;
}

.plan-field { display: grid; gap: .2rem; }
.plan-field .field-label { margin: 0; }
.plan-field .input { width: auto; min-width: 7rem; }

.start-planned {
  margin: .5rem 0 0;
  padding-top: .5rem;
  border-top: 1px solid var(--line);
  font-size: .85rem;
  color: var(--ink-2);
}

.start-when {
  margin: 0;
  font-size: .85rem;
  font-weight: 650;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.start-what {
  margin: .1rem 0 .2rem;
  font-size: 1.3rem;
  font-weight: 600;
}

.start-step { margin: 0; font-size: .95rem; }
.start-why, .start-due { margin: .35rem 0 0; font-size: .88rem; color: var(--ink-2); }

.checkin-lead { margin: 0 0 .8rem; font-size: .95rem; }
.checkin-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }

.checkin-row {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .55rem .7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.checkin-what { display: grid; gap: .1rem; min-width: 0; }
.checkin-title { font-size: .92rem; font-weight: 600; }
.checkin-plan { font-size: .78rem; color: var(--ink-2); }
.checkin-minutes {
  margin-left: auto;
  width: 5rem;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/*
  Money that was due and did not turn up. Not an error and not a warning
  colour: a fact about the month that belongs next to the plan it belongs to.
*/
.kpi-late {
  display: block;
  margin-top: .15rem;
  font-weight: 600;
  color: var(--now);
}

/* ---------- The ones worth asking about ---------- */

/*
  A question, so it looks like one: the accent down the edge the way a notice
  wears it, and three answers of equal weight. None of them is the recommended
  one, because the app does not know which is true and guessing is the thing
  this card exists to stop.
*/

/*
  One card on this page is the one you came to look at. Seven cards of
  identical material is a list of seven things of equal weight, which is not
  what the page means: the balance is the object, the rest is what it is made
  of. A single step of surface says so without a border or a shadow shouting
  about it.
*/
.card-kpi {
  background: var(--surface-2);
  border-color: var(--line);
}

.ask-who {
  font-size: .9rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ask-acts { display: flex; flex-wrap: wrap; gap: .4rem; }

/*
  The guard's line, which is on the card whether or not it has anything to
  complain about. A guard that only appears when it is unhappy is one you
  cannot check, and checking it is what earns it the benefit of the doubt the
  one time it does speak up.
*/
.guard-line {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  margin: .55rem 0 0;
  font-size: .86rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.guard-mark {
  width: .5rem;
  height: .5rem;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--s-ess);
}

.guard-line.is-watch { color: var(--ink); }
.guard-line.is-watch .guard-mark { background: var(--now); }
.guard-line.is-over { color: var(--ink); font-weight: 600; }
.guard-line.is-over .guard-mark { background: var(--accent); }

/* Every zloty of the month, and which side of the line it sits on. */
.ledger { list-style: none; margin: .5rem 0 .9rem; padding: 0; display: grid; gap: .1rem; }

.ledger-row {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .25rem 0;
  border-bottom: 1px solid var(--card-line);
  font-size: .85rem;
}

.ledger-row:last-child { border-bottom: 0; }
.ledger-what { min-width: 0; }
.ledger-sum { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
.ledger-how { width: 6.5rem; text-align: right; font-size: .76rem; color: var(--ink-2); }
.ledger-row.is-apart .ledger-what, .ledger-row.is-apart .ledger-sum { color: var(--ink-2); }

/* ---------- The plan, argued ---------- */

/*
  Not a card among cards. This is a piece of writing about the thing it sits
  under, so it is set as writing: a rule above it, the argument numbered, and
  the two asides it ends on marked off rather than boxed.
*/
.sense {
  margin: .25rem 0 1.1rem;
  padding: 1rem 0 0;
  border-top: 2px solid var(--line-strong);
  display: grid;
  gap: .9rem;
  animation: sense-in .28s cubic-bezier(.19, .78, .28, 1);
}

/* Answering the plan back. Quieter than the argument above it: this is a
   box you type in, not a thing that has something to say. */
.revise {
  margin: .25rem 0 1.1rem;
  padding: .9rem 0 0;
  border-top: 1px solid var(--line);
  display: grid;
  gap: .4rem;
}

.revise-text {
  width: 100%;
  resize: vertical;
  min-height: 3.2rem;
  font: inherit;
  font-size: .95rem;
  line-height: 1.45;
}

.revise-actions { display: flex; justify-content: flex-end; }

/*
  The answer, and the lid for it. The first line of the report and the button
  that puts the whole thing away share a row, so the way out is beside the
  thing you are trying to get out of rather than at the end of it.
*/
.revise-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: .6rem;
}

.revise-top > :first-child { min-width: 0; flex: 1 1 auto; }

.revise-hide {
  flex: 0 0 auto;
  min-width: 2rem;
  min-height: 2rem;
  margin-top: -.15rem;
  /* Not a dialog close. A border here makes it read as one, and this puts a
     paragraph away rather than shutting a window. */
  border-color: transparent;
  background: none;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink-2);
}

.revise-hide:hover { color: var(--ink); background: var(--surface-3); }

.revise-label {
  margin: .6rem 0 0;
  font-size: .78rem;
  font-weight: 650;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.revise-changes, .revise-warnings {
  margin: .3rem 0 0;
  padding-left: 1.1rem;
  display: grid;
  gap: .3rem;
  font-size: .9rem;
}

.revise-warnings { color: var(--ink-2); }
.revise-note { margin: .5rem 0 0; font-size: .9rem; }
.revise-asked { margin: .5rem 0 0; font-size: .85rem; color: var(--ink-2); font-style: italic; }
.revise-waiting { margin: .5rem 0 0; font-size: .9rem; color: var(--ink-2); }

.revise-refused {
  margin: .5rem 0 0;
  padding: .55rem .7rem;
  border-radius: var(--radius);
  background: var(--surface-2);
  font-size: .88rem;
}

.sense-fix { margin-top: .55rem; }

@keyframes sense-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}

.sense-head { display: flex; align-items: flex-start; gap: 1rem; }

.sense-verdict {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 650;
  letter-spacing: -.015em;
  line-height: 1.35;
  text-wrap: balance;
}

.sense-head .sense-close { margin-left: auto; flex-shrink: 0; }

.sense-points {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: sense;
  display: grid;
  gap: .8rem;
}

.sense-point {
  counter-increment: sense;
  display: grid;
  grid-template-columns: 1.4rem minmax(0, 1fr);
  gap: 0 .6rem;
}

/* The number is the argument's order, which is its strength. */
.sense-point::before {
  content: counter(sense);
  grid-row: 1 / span 2;
  font-size: .82rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  padding-top: .1rem;
}

.sense-claim { margin: 0; font-weight: 650; font-size: .96rem; }
.sense-because { margin: .1rem 0 0; font-size: .9rem; color: var(--ink-2); line-height: 1.55; max-width: 64ch; }

.sense-aside {
  padding-left: .85rem;
  border-left: 2px solid var(--line);
}

.sense-aside.is-weakest { border-left-color: var(--now); }

.sense-aside-label {
  margin: 0;
  font-size: .78rem;
  font-weight: 650;
  color: var(--ink-2);
}

.sense-aside-text { margin: .1rem 0 0; font-size: .9rem; line-height: 1.55; max-width: 64ch; }

.sense-waiting, .sense-error { margin: 0; font-size: .92rem; color: var(--ink-2); }
.sense-when { margin: 0; font-size: .78rem; color: var(--ink-2); }

@media (prefers-reduced-motion: reduce) {
  .sense { animation: none; }
}

/*
  Passing an evening. Last in the row, quietest in the row, and honest about
  itself: the label carries its own warning rather than hiding behind a
  confirmation nobody reads. It is not styled as a danger, because it is not
  one. It is a choice with a cost, and the cost is written on it.
*/
.btn-pass {
  /* Last in the row on purpose: the eye reaches it after both answers that
     actually move the work along. */
  order: 3;
  padding-inline: .6rem;
  color: var(--ink-2);
  font-weight: 600;
}

.btn-pass:hover { color: var(--ink); background: var(--surface-2); }

/*
  Quiet like btn-pass, but not last.

  It borrowed btn-pass's looks and got its `order: 3` with them, which is
  right for passing an evening -- the eye reaches it after both answers that
  move the work along -- and wrong here: it put taking a goal off the list
  past "Done", on the right, where the button you press to finish lives. A
  destructive action does not sit where the confirming one is expected.
*/
.btn-remove {
  padding-inline: .6rem;
  color: var(--ink-2);
  font-weight: 600;
}

.btn-remove:hover { color: var(--danger); background: var(--surface-2); }

.pass-note { font-weight: 500; opacity: .75; }

/* A sitting that has been pushed before says so, before it is pushed again. */
.start-passed {
  margin: .35rem 0 0;
  font-size: .86rem;
  font-weight: 600;
  color: var(--now);
}

@media (max-width: 34rem) {
  /* Narrow: the three answers stack, and the discouraged one stays last. */
  .session-actions { flex-direction: column; align-items: stretch; }
  .session-actions .later-row { margin-right: 0; }
  .session-actions .plan-row { margin-right: 0; }
  .plan-field { flex: 1 1 7rem; }
  .plan-row .btn { width: 100%; }
  .btn-pass { order: 4; }
}

/* ---------- Asking the bank now ---------- */

.kpi-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: .1rem;
}

.refresh-btn {
  margin-left: auto;
  min-height: 2.25rem;
  padding: .3rem .7rem;
  gap: .4rem;
  color: var(--ink-2);
}

.refresh-btn:hover:not(:disabled) { color: var(--ink); background: var(--surface-3); }
.refresh-btn:disabled { opacity: .7; cursor: default; }

.refresh-mark { width: 1rem; height: 1rem; flex-shrink: 0; }

/* It turns while it is asking, and only while it is asking. */
.refresh-btn.is-turning .refresh-mark { animation: refresh-turn 1.1s linear infinite; }

@keyframes refresh-turn {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .refresh-btn.is-turning .refresh-mark { animation: none; }
}

/* ---------- The emergency budget ---------- */

/*
  On the savings card, not the daily one. It is money that came in and was not
  spent, which is what saving is; putting it next to today's figure would read
  as permission, and the whole point of it is that it is not.
*/
.saved-fund {
  margin: .9rem 0 0;
  padding-top: .8rem;
  border-top: 1px solid var(--line);
}

.saved-fund-label {
  margin: 0;
  font-size: .78rem;
  font-weight: 650;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.saved-fund-figure {
  margin: .15rem 0 .3rem;
  font-size: 1.5rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.saved-fund-note {
  margin: 0;
  font-size: .84rem;
  color: var(--ink-2);
  max-width: 54ch;
}

/* ---------- Saving up for something ---------- */

.wish-form {
  display: grid;
  grid-template-columns: 1fr 7rem 9rem auto;
  gap: .5rem;
  margin: .8rem 0 0;
}

.wish-list { list-style: none; margin: .9rem 0 0; padding: 0; display: grid; gap: .9rem; }

/* The row is one target, so it is one button. */
/* The rule belongs to the row rather than to the button, so a goal with a
   link keeps one line under both of them instead of one between them. */
.wish-row { display: block; border-bottom: 1px solid var(--line); }
.wish-list li:last-child { border-bottom: 0; }

.wish-open {
  display: grid;
  gap: .35rem;
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-radius: var(--radius);
  padding: .55rem .5rem .8rem;
  margin: 0 -.5rem;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.wish-row:has(.wish-go) .wish-open { padding-bottom: .4rem; }
.wish-open:hover { background: var(--surface-2); }
.wish-open:active { background: var(--surface-3); }
.wish-open:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/*
  Going to the shop, under the goal rather than inside it. Quiet, because it
  is the second thing you would want from this row and the first is the
  dialog; named after the site, because a link that does not say where it
  leads is one you have to follow to find out.
*/
.wish-go {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  margin: 0 0 .7rem;
  padding: .2rem .35rem;
  margin-left: -.35rem;
  border-radius: var(--r-mark);
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-2);
  text-decoration-color: color-mix(in oklab, currentColor 40%, transparent);
  text-underline-offset: .18em;
}

.wish-go:hover { color: var(--accent); background: var(--surface-2); }
.wish-go:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.wish-go-mark { font-size: .9em; line-height: 1; }

/* The same thing in the dialog, where it is the whole point of the row above
   it and is allowed to look like a button. */
.wish-link-row {
  display: flex;
  gap: .4rem;
  align-items: center;
  margin: .2rem 0 .5rem;
}

.wish-link-row .input { flex: 1 1 auto; min-width: 0; }
.wish-link-row .btn { flex: 0 0 auto; }

.wish-visit {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin: 0 0 .9rem;
  text-decoration: none;
}

.wish-head { display: flex; align-items: baseline; gap: .6rem; }
.wish-name { font-weight: 600; margin-right: auto; }
/* A price that breaks after the comma, leaving "zl" alone on the next line,
   is not a price anybody can read at a glance. */
.wish-price { font-variant-numeric: tabular-nums; font-weight: 650; white-space: nowrap; }

.wish-meter {
  display: block;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  overflow: hidden;
}

.wish-meter-fill {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--s-ess);
}

.wish-open.is-late .wish-meter-fill { background: var(--accent); }
.wish-open.is-bought .wish-meter-fill { opacity: .55; }
.wish-open.is-bought .wish-name { text-decoration: line-through; text-decoration-thickness: 1px; }

.wish-note { display: block; margin: 0; font-size: .84rem; color: var(--ink-2); font-variant-numeric: tabular-nums; }

.wish-sum {
  margin: .9rem 0 0;
  padding-top: .8rem;
  border-top: 1px solid var(--line);
  font-size: .86rem;
  color: var(--ink-2);
  max-width: 60ch;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 560px) {
  .wish-form { grid-template-columns: 1fr 1fr; }
  .wish-form #wish-name { grid-column: 1 / -1; }
  .wish-form .btn { grid-column: 1 / -1; }
}

/* ---------- The two halves of what has been saved ---------- */

.saved-split {
  list-style: none;
  margin: .6rem 0 0;
  padding: 0;
  display: grid;
  gap: .3rem;
}

.saved-split li {
  display: flex;
  align-items: baseline;
  gap: .7rem;
  font-size: .88rem;
}

.saved-split-what { color: var(--ink-2); margin-right: auto; }
.saved-split-sum { font-variant-numeric: tabular-nums; font-weight: 650; }

.saved-when {
  margin: .55rem 0 0;
  font-size: .8rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* ---------- Picking the payment that bought a goal ---------- */

.pick-list {
  list-style: none;
  margin: .4rem 0 1rem;
  padding: 0;
  display: grid;
  gap: .1rem;
  max-height: 20rem;
  overflow-y: auto;
}

.pick-row {
  display: grid;
  grid-template-columns: 3rem 1fr auto auto;
  gap: .3rem .8rem;
  align-items: baseline;
  padding: .45rem .1rem;
  border-bottom: 1px solid var(--line);
  font-size: .88rem;
}

.pick-list .pick-row:last-child { border-bottom: 0; }
.pick-when { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.pick-what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-sum { font-variant-numeric: tabular-nums; font-weight: 650; white-space: nowrap; }

/* Searching comes before the list it searches, and sits on the list rather
   than in the form below it. */
.pick-find { width: 100%; margin: .2rem 0 .1rem; }

/*
  Adding one by hand is the way out when searching finds nothing, so it is
  underneath the search and folded away until it is wanted. Open by default
  it would read as the first thing to try, which it is not.
*/
.pick-hand { margin: .2rem 0 1rem; }

.pick-hand > summary {
  cursor: pointer;
  padding: .4rem 0;
  font-size: .88rem;
  font-weight: 600;
  color: var(--ink-2);
}

.pick-hand > summary:hover { color: var(--ink); }
.pick-hand[open] > summary { color: var(--ink); }

.hand-form {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: .45rem;
  margin: .3rem 0 .5rem;
}

.hand-form .input { min-width: 0; }
.hand-form .btn { grid-column: 1 / -1; justify-self: start; }

@media (max-width: 30rem) {
  .hand-form { grid-template-columns: minmax(0, 1fr); }
}

/* Said quietly beside the name, because it qualifies the row rather than
   labelling it. */
.tx-typed {
  display: inline-block;
  margin-left: .4rem;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink-2);
}

/* ---------- The foot of the transaction list ---------- */

/*
  Both the thing that loads the next page when it is scrolled to and the
  button that does it when the observer cannot, so it has to look like a
  button rather than a spinner.
*/
.tx-more {
  display: block;
  width: 100%;
  margin-top: .8rem;
}

.tx-end {
  margin: .9rem 0 0;
  text-align: center;
  font-size: .82rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
