/*
 * Mobile first. This is used mostly one-handed on a phone, so the defaults are
 * phone defaults and the wider layout is the special case.
 *
 * Nothing here is generated or minified: it is meant to be read and edited
 * directly on the server if need be.
 */

:root {
  --bg: #f3f3f5;
  --surface: #ffffff;
  --surface-2: #eceef1;
  --border: #dcdee3;
  --text: #16191f;
  --text-dim: #5b6472;
  --accent: #1d4ed8;
  --accent-text: #ffffff;
  --danger: #b42318;
  --overdue: #b42318;
  --due-soon: #b25e09;
  --due-week: #8a6d00;
  --due-later: #17734a;

  /* Filled-pill variants of the same bands. Backgrounds are light tints and the
     text is darkened until it clears 4.5:1 against them. */
  --overdue-fg: #a82820;
  --soon-fg:    #94500f;
  --week-fg:    #6f5c08;
  --later-fg:   #1a6b46;

  /* Row tints, with a matching border for each so a row reads as one object
     rather than a grey box with a stain in it. Held at a consistent lightness
     across the four hues, and far enough from white to look deliberate — the
     previous values were 2% off white and just looked grubby. */
  --overdue-row: #fdecec; --overdue-edge: #f3d4d4;
  --soon-row:    #fdf1e7; --soon-edge:    #f2ddc6;
  --week-row:    #fdfaee; --week-edge:    #ece5c8;
  --later-row:   #eaf7ee; --later-edge:   #cee5d6;

  /* Tag palette. Backgrounds are tinted lightly and text is darkened until it
     clears 4.5:1 against them, so a coloured tag stays readable rather than
     becoming decoration. */
  --tag-0-bg: #dbeafe; --tag-0-fg: #1e40af;
  --tag-1-bg: #dcfce7; --tag-1-fg: #166534;
  --tag-2-bg: #fef3c7; --tag-2-fg: #854d0e;
  --tag-3-bg: #fae8ff; --tag-3-fg: #86198f;
  --tag-4-bg: #ffe4e6; --tag-4-fg: #9f1239;
  --tag-5-bg: #cffafe; --tag-5-fg: #155e75;
  --tag-6-bg: #ede9fe; --tag-6-fg: #5b21b6;
  --tag-7-bg: #ffedd5; --tag-7-fg: #9a3412;
  --ok: #15803d;

  --radius: 10px;
  --tap: 44px;                 /* Apple's minimum comfortable touch target */
  --gap: 12px;

  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14161a;
    --surface: #1c1f26;
    --surface-2: #262a33;
    --border: #333945;
    --text: #e8eaed;
    --text-dim: #9aa3b2;
    --accent: #6d94ff;
    --accent-text: #0b0d10;
    --danger: #ff6b5e;
    --overdue: #ff7a6d;
    --due-soon: #f0a952;
    --due-week: #d8c25a;
    --due-later: #63c48c;

    --overdue-fg: #ff9086;
    --soon-fg:    #f0b36c;
    --week-fg:    #dcc76d;
    --later-fg:   #74cd99;

    --overdue-row: #2b1a1a; --overdue-edge: #3f2626;
    --soon-row:    #2a2118; --soon-edge:    #3d3122;
    --week-row:    #201f18; --week-edge:    #322e20;
    --later-row:   #17251d; --later-edge:   #22372a;

    /* Dark theme: the same hues, inverted — muted backgrounds with bright text,
       which is what stays legible on a dark surface. */
    --tag-0-bg: #1e3a8a55; --tag-0-fg: #93c5fd;
    --tag-1-bg: #14532d55; --tag-1-fg: #86efac;
    --tag-2-bg: #78350f55; --tag-2-fg: #fcd34d;
    --tag-3-bg: #70163755; --tag-3-fg: #f5b7f0;
    --tag-4-bg: #88113755; --tag-4-fg: #fda4af;
    --tag-5-bg: #164e6355; --tag-5-fg: #67e8f9;
    --tag-6-bg: #4c1d9555; --tag-6-fg: #c4b5fd;
    --tag-7-bg: #7c2d1255; --tag-7-fg: #fdba74;
    --ok: #58c07a;
  }
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Keeps a pull-to-refresh gesture from bouncing the whole page on iOS. */
  overscroll-behavior-y: contain;
}

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

.noscript, .boot {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--text-dim);
}

/* ---------------------------------------------------------------- layout */

.app {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.main {
  flex: 1;
  padding: var(--gap);
  padding-bottom: calc(var(--tap) + var(--safe-bottom) + 2rem);
  max-width: 780px;
  width: 100%;
  margin: 0 auto;
}

/* ------------------------------------------------------------------ head */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: calc(var(--safe-top) + 8px) var(--gap) 0;
}

.topbar__row {
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: 780px;
  margin: 0 auto;
  min-height: var(--tap);
}

.topbar__title {
  font-size: 1.05rem;
  font-weight: 650;
  margin: 0;
  margin-right: auto;
}

.count {
  color: var(--text-dim);
  font-size: .85rem;
  font-variant-numeric: tabular-nums;
}

/* Segmented context switcher, one button per configured context.

   It scrolls rather than pushes. The buttons are flex: 1 with the default
   min-width: auto, so none of them can shrink below its own label — add one
   context too many and the row grows past the screen and takes the whole page's
   horizontal scroll with it. Five labels need 316px and a 320px phone has 296.
   Containing the overflow here costs nothing at 360px and up, where the row
   still fits exactly, and it stops the next context added to the config being a
   layout bug. The scrollbar is hidden, as it is on .filters below. */
.segmented {
  display: flex;
  gap: 2px;
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 3px;
  max-width: 780px;
  margin: 8px auto 0;
  overflow-x: auto;
  scrollbar-width: none;
}

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

.segmented button {
  flex: 1;
  min-height: 34px;
  border: 0;
  border-radius: calc(var(--radius) - 3px);
  background: transparent;
  color: var(--text-dim);
  font-size: .9rem;
  font-weight: 550;
  cursor: pointer;
}

.segmented button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
}

.filters {
  display: flex;
  gap: 8px;
  align-items: center;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 8px 0;
  max-width: 780px;
  margin: 0 auto;
}

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

/* Search and "add what I just typed", joined into one control: they act on the
   same text, so they should look like one thing rather than two. */
.searchbar {
  flex: 1 1 auto;
  min-width: 10rem;
  display: flex;
  align-items: stretch;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  overflow: hidden;
}

.searchbar:focus-within {
  border-color: var(--accent);
}

.search {
  flex: 1 1 auto;
  min-width: 4rem;
  min-height: 36px;
  padding: 0 10px;
  border: 0;
  background: transparent;
  color: var(--text);
}

/* The ring moves to the wrapper above, so the control keeps a visible focus
   indicator without drawing one inside its own border. */
.search:focus { outline: none; }

.searchbar__add {
  flex: 0 0 auto;
  width: 38px;
  border: 0;
  border-left: 1px solid var(--border);
  background: var(--surface);
  color: var(--accent);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.searchbar__add:hover {
  background: var(--accent);
  color: var(--accent-text);
}

.chip {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-dim);
  font-size: .85rem;
  white-space: nowrap;
  cursor: pointer;
}

.chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
  font-weight: 550;
}

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

.group {
  margin: .9rem 0 .3rem;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.group--overdue { color: var(--overdue); }
.group--priority { color: var(--accent); }

/* The heading doubles as a fold control. */
.group__toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  padding: 4px 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: left;
  cursor: pointer;
}

.group__caret {
  display: inline-block;
  width: .7em;
  font-size: .9em;
  opacity: .8;
}

.list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }

/* Compact by design: this list is read far more often than it is edited, and
   fitting more of it on a phone screen matters more than breathing room.
   The left border carries the due-date colour — see .task--due-* below. */
.task {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 6px;
  line-height: 1.35;
  font-size: .95rem;
}

.task--waiting { opacity: .62; }

/* The visible circle is drawn by ::before at 22px while the button itself stays
   32px, so the row gets shorter without the tap target shrinking with it —
   the thing being tapped is still comfortably bigger than the ink. */
.task__check {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border: 0;
  background: transparent;
  cursor: pointer;
  position: relative;
  padding: 0;
}

.task__check::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: 2px solid var(--border);
  border-radius: 50%;
}

.task__check:hover::before { border-color: var(--ok); }

.task__check::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 7px;
  height: 12px;
  border: solid var(--ok);
  border-width: 0 2.5px 2.5px 0;
  transform: rotate(45deg) translate(-1px, -1px);
  opacity: 0;
}

.task__check:hover::after { opacity: .45; }

/* A completed task has to look completed at a glance, and clicking the tick
   again undoes it — completing the wrong row on a phone is easy. */
.task__check--done::before {
  background: var(--ok);
  border-color: var(--ok);
}

.task__check--done::after {
  border-color: #fff;
  opacity: 1;
}

.task__check--done:hover::after { opacity: 1; }

/* The wash tracks urgency and fades out, rather than colouring everything.
   Tinting every row makes the list a stack of pastel bands where nothing stands
   out — and on a real list most tasks are weeks away, so most of the screen
   ends up coloured for no reason. Colour should mean "look at this".

   The date text stays colour-coded in all four bands, so no information is
   lost; only the background stops shouting about things that are months off. */
.task--due-overdue { background: var(--overdue-row); border-color: var(--overdue-edge); }
.task--due-soon    { background: var(--soon-row);    border-color: var(--soon-edge); }
.task--due-week    { background: var(--week-row);    border-color: var(--week-edge); }

.task--done .task__title {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  color: var(--text-dim);
}

.task--done { opacity: .75; }

/* The tappable body of the row opens the editor. */
.task__body {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
}

.task__title {
  /* Inline, not block, so the metadata flows on the same line after it. A task
     with a short description and a couple of tags is then a single line — which
     is most of them, and it roughly halves the height of the list.
     Still wraps rather than truncating: on a phone the text is the whole point
     of the row, and an ellipsis in the middle of a task is useless. */
  overflow-wrap: anywhere;
}

.task__meta {
  display: inline;
  font-size: .75rem;
  color: var(--text-dim);
}

/* Each piece of metadata is its own inline box so it never breaks internally,
   while the run of them still wraps as ordinary text. */
.task__meta > * {
  display: inline-block;
  margin-left: 4px;
  vertical-align: baseline;
}

/* Tags and the date, pinned to the right edge — the two things read down the
   list rather than across a row. */
.task__rail {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

/* Tags wrap among themselves rather than pushing the date around, so however
   many a task carries, the date stays where the eye expects it. */
.task__tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 3px;
  min-width: 0;
}

/* A fixed slot, held even when empty: this is what turns the dates into a
   column instead of a ragged edge. Wide enough for the longest label the
   formatter produces ("tomorrow", "12 Aug 2027"). */
/* Also the defer control, so it has to be a real tap target: 30px tall against
   a 38px row, which is the same ink-to-target ratio the checkbox uses. */
.task__day {
  flex: 0 0 auto;
  min-width: 4.6rem;
  min-height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  white-space: nowrap;
  padding: 0 5px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;

  /* Spelled out rather than left to .due, because the completed variant is a
     <span> with no .due class and the two must not differ in size. */
  font-family: inherit;
  font-size: .75rem;
  font-weight: 650;
  letter-spacing: .01em;
}

.task__day:hover { background: var(--surface-2); }

/* No due date: still opens the picker, but a blank cell would not say so. */
.task__day--empty { color: var(--text-dim); }

/* A completed date is not a control. */
span.task__day { cursor: default; color: var(--text-dim); }

/* On a phone the rail would otherwise take as much width as its content wants,
   and a task with two tags leaves the description a ~90px column that wraps
   three times. Capped, the tags stack inside the rail instead of squeezing the
   text — the rail is the part it costs nothing to make taller. */
@media (max-width: 30rem) {
  .task__rail { max-width: 40%; gap: 4px; }
  .task__day { min-width: 3.5rem; }
  .task .tag { padding: 0 4px; font-size: .68rem; }
}

/* Urgency bands, matching the scheme Tracks used: overdue red, today and
   tomorrow orange, the rest of the week yellow, further out green.

   Coloured text, NOT a filled pill. The row behind it already carries the same
   hue, so a pill on top was a second helping of the same colour — two tints of
   one hue sitting on each other, which is what made the list look muddy. One
   colour carrier per row; the text just needs to be dark enough to read on it. */
.due {
  font-weight: 650;
  font-size: .75rem;
  letter-spacing: .01em;
}

.due--overdue { color: var(--overdue-fg); }
.due--soon    { color: var(--soon-fg); }
.due--week    { color: var(--week-fg); }
.due--later   { color: var(--later-fg); }

.tag {
  border: 0;
  font: inherit;
  background: var(--surface-2);
  color: var(--text-dim);
  border-radius: 4px;
  padding: 0 5px;
  font-size: .7rem;
  font-weight: 550;
  cursor: pointer;
  /* Tertiary in the row's hierarchy: description first, then the date, then
     these. Without holding them back, every row is three competing colours. */
  opacity: .9;
}

.tag:hover { opacity: 1; }

/* Tag colours are assigned by hashing the name, so a tag looks the same
   everywhere and a new one is coloured without any configuration. They are
   classes rather than inline styles because the CSP is style-src 'self' with no
   unsafe-inline, which blocks style attributes outright. */
.tag--c0 { background: var(--tag-0-bg); color: var(--tag-0-fg); }
.tag--c1 { background: var(--tag-1-bg); color: var(--tag-1-fg); }
.tag--c2 { background: var(--tag-2-bg); color: var(--tag-2-fg); }
.tag--c3 { background: var(--tag-3-bg); color: var(--tag-3-fg); }
.tag--c4 { background: var(--tag-4-bg); color: var(--tag-4-fg); }
.tag--c5 { background: var(--tag-5-bg); color: var(--tag-5-fg); }
.tag--c6 { background: var(--tag-6-bg); color: var(--tag-6-fg); }
.tag--c7 { background: var(--tag-7-bg); color: var(--tag-7-fg); }

/* Quick-tag toggles.
   Deliberately NOT reusing .chip: the filter chips paint themselves accent blue
   when pressed, which would make every selected tag the same colour and throw
   away the thing that makes them scannable. Selection is shown by going from
   faded to full with a ring, so each tag keeps its own colour throughout. */
.quick-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.qtag {
  min-height: 36px;
  padding: 0 12px;
  border: 2px solid transparent;
  border-radius: 999px;
  font-size: .85rem;
  white-space: nowrap;
  cursor: pointer;
  opacity: .5;
  filter: saturate(.6);
}

.qtag[aria-pressed="true"] {
  opacity: 1;
  filter: none;
  font-weight: 650;
  border-color: currentColor;
}

.field--spaced { margin-top: 1rem; }

.checkline {
  display: flex;
  gap: 8px;
  align-items: center;
}

.pill {
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0 7px;
  font-size: .72rem;
}

.empty {
  text-align: center;
  color: var(--text-dim);
  padding: 3rem 1rem;
}

/* ------------------------------------------------------------------- fab */

.fab {
  position: fixed;
  right: max(var(--gap), env(safe-area-inset-right, 0px));
  bottom: calc(var(--gap) + var(--safe-bottom));
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 0;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 1.7rem;
  line-height: 1;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .28);
  cursor: pointer;
  z-index: 30;
}

/* ----------------------------------------------------------------- sheet */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .45);
  z-index: 40;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.sheet {
  background: var(--surface);
  width: 100%;
  max-width: 640px;
  max-height: 92dvh;
  overflow-y: auto;
  border-radius: 16px 16px 0 0;
  padding: var(--gap);
  padding-bottom: calc(var(--gap) + var(--safe-bottom));
}

@media (min-width: 700px) {
  .sheet-backdrop { align-items: center; }
  .sheet { border-radius: 16px; }
}

.sheet__head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: var(--gap);

  /* Sticky, so the primary action stays reachable however long the form gets —
     the edit sheet grows with every annotation. */
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
  padding: var(--gap) 0 8px;
  margin-top: calc(var(--gap) * -1);
}

.sheet__title { margin: 0; font-size: 1.05rem; margin-right: auto; }

/* On a narrow screen the title yields before the buttons do. */
.sheet__head .btn--primary { flex: 0 0 auto; }

/* ----------------------------------------------------------------- forms */

.field { margin-bottom: var(--gap); }

.field > label {
  display: block;
  font-size: .8rem;
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 4px;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}

.field textarea { min-height: 5rem; resize: vertical; }

.field__row { display: flex; gap: 8px; }
.field__row > * { flex: 1; min-width: 0; }

.hint { font-size: .78rem; color: var(--text-dim); margin: 4px 0 0; }

.btn {
  min-height: var(--tap);
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  font-weight: 550;
  cursor: pointer;
}

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

.btn--danger { color: var(--danger); }
.btn--ghost { background: transparent; border-color: transparent; }
.btn:disabled { opacity: .5; cursor: not-allowed; }

.btn-row { display: flex; gap: 8px; margin-top: var(--gap); }
.btn-row--end { justify-content: flex-end; }

.quick { display: flex; flex-wrap: wrap; gap: 8px; }
.quick .btn { flex: 1 1 auto; min-width: 6rem; }

/* ----------------------------------------------------------- annotations */

.annotations { list-style: none; margin: 0 0 8px; padding: 0; display: grid; gap: 6px; }

.annotation {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  background: var(--surface-2);
  border-radius: 8px;
  padding: 8px;
  font-size: .9rem;
}

.annotation__text {
  flex: 1;
  /* Annotations legitimately contain newlines and account numbers. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.annotation time { display: block; font-size: .72rem; color: var(--text-dim); margin-top: 2px; }

/* ----------------------------------------------------------------- login */

.login {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--gap);
}

.login form {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.5rem;
  width: 100%;
  max-width: 22rem;
}

.login h1 { margin: 0 0 1.25rem; font-size: 1.2rem; }

/* --------------------------------------------------------------- notices */

.toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--gap) + var(--safe-bottom) + 64px);
  background: var(--text);
  color: var(--bg);
  padding: 10px 16px;
  border-radius: 999px;
  font-size: .9rem;
  z-index: 60;
  max-width: calc(100vw - 2rem);
  box-shadow: 0 3px 12px rgba(0, 0, 0, .3);
}

.toast--error { background: var(--danger); color: #fff; }

/* The way back from a tick that landed on the wrong row. Inverted against the
   toast so it reads as the thing to press, and given a full tap target — it is
   offered for a few seconds only, and missing it means going hunting in the
   Done view instead. */
.toast__undo {
  margin-left: 12px;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
  padding: 4px 6px;
  min-height: 28px;
}

.toast__undo:disabled { opacity: .5; text-decoration: none; }

/* Swallows the stray second tap for a moment after a dialog closes, so it
   cannot fall through onto the tick column underneath. Above the list and the
   sheet backdrop, below the toast, so Undo stays reachable. */
.shield {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: transparent;
}

/* A write is in flight. The sheet stays open until the server answers, and
   without this nothing on screen distinguishes that from a tap that missed. */
.btn.is-busy {
  opacity: .6;
  cursor: progress;
}

.banner {
  background: var(--danger);
  color: #fff;
  padding: 8px var(--gap);
  font-size: .88rem;
  text-align: center;
}

.error { color: var(--danger); font-size: .88rem; margin: 0 0 var(--gap); }

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

/* Respect a user who has asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
