:root {
  color-scheme: light dark;
  --bg: #f6f7fb;
  --card: #ffffff;
  --ink: #14161c;
  --muted: #6b7280;
  --line: #e3e6ee;
  --accent: #2f6bff;
  --danger: #d1434b;
  --ok: #1f9d55;
  /* A calendar day says what happened to it in four colours and no others: paid, missed, still ahead,
     waiting on a grown-up. They are hues rather than fills so a chip can be a wash of them. */
  --cal-done: #1f9d55;
  --cal-missed: #d1434b;
  --cal-soon: #a9740a;
  --cal-wait: #2f6bff;
  --radius: 18px;
  --pad: max(16px, env(safe-area-inset-left));
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #12141a;
    --card: #1b1f27;
    --ink: #f2f4f8;
    --muted: #98a1b3;
    --line: #2a2f3a;
    --accent: #5b8cff;
    /* The light hues are mixed into a card the colour of a dark room, so they lift here. The same
       swatch is too quiet to read a whole month by otherwise. */
    --cal-done: #56d190;
    --cal-missed: #ff8087;
    --cal-soon: #e9b949;
    --cal-wait: #8fb0ff;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  /* A standalone install must fill the screen exactly; 100vh overshoots on iPad and leaves a gap
     under the home indicator. */
  min-height: 100dvh;
  background: var(--bg);
  color: var(--ink);
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;
}

body {
  padding: 0;
}

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

/* iOS zooms the page when a focused control is under 16px — the surest tell that a page is a
   website pretending to be an app. */
input,
select,
textarea {
  font-size: 16px;
}

button {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  cursor: pointer;
}

.screen-center {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  color: var(--muted);
}

.shell {
  max-width: 860px;
  margin: 0 auto;
  padding: 0 var(--pad) calc(32px + env(safe-area-inset-bottom));
}

.bar {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: calc(12px + env(safe-area-inset-top)) 0 12px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
}

.bar h1 {
  font-size: clamp(22px, 4vw, 30px);
  margin: 0;
  color: var(--kid, inherit);
}

.bar .balance {
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 20px;
}

.tabs {
  display: flex;
  gap: 4px;
  margin-left: auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px;
}

/* Ten pixels rather than fourteen: five tabs have to live on one row at iPad portrait width. */
.tabs button {
  border: 0;
  background: transparent;
  border-radius: 999px;
  padding: 8px 10px;
  color: var(--muted);
  min-height: 40px;
}

.tabs button.on {
  background: var(--accent);
  color: #fff;
}

.quiet {
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 12px;
  padding: 10px 14px;
  min-height: 44px;
  color: var(--ink);
}

.quiet:active {
  transform: scale(0.97);
}

.quiet.danger {
  color: var(--danger);
}

.primary {
  border: 0;
  background: var(--accent);
  color: #fff;
  border-radius: 12px;
  padding: 12px 18px;
  min-height: 48px;
  font-weight: 600;
}

.primary:active {
  transform: scale(0.98);
}

.primary:disabled,
.quiet:disabled {
  opacity: 0.45;
}

.kid-strip {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding: 4px 0 12px;
  scrollbar-width: none;
}

.chip {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 999px;
  padding: 10px 16px;
  min-height: 46px;
  white-space: nowrap;
}

.chip.on {
  border-color: var(--kid, var(--accent));
  box-shadow: inset 0 0 0 1px var(--kid, var(--accent));
}

.chip b {
  color: var(--kid, inherit);
}

.chip span {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.stack {
  display: grid;
  gap: 16px;
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 4px 16px 16px;
}

.card > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 0 6px;
}

.card h2 {
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: 0;
}

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

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 0;
  border-top: 1px solid var(--line);
}

.row.big {
  padding: 18px 0;
}

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

.row-main b {
  display: block;
  font-size: 17px;
  overflow-wrap: anywhere;
}

.row-main small {
  color: var(--muted);
}

.row .value {
  font-variant-numeric: tabular-nums;
  font-weight: 650;
}

.row .value.minus {
  color: var(--danger);
}

.row[data-status='approved'] .value {
  color: var(--ok);
}

/* A refusal is a job to do again, not one that has finished, so nothing strikes the title through.
   The small print says a grown-up sent it back, in the same voice the overdue line uses. */
.row[data-status='rejected'] .row-main small {
  color: var(--danger);
  font-weight: 600;
}

/* A standby job whose stretch is up. The title stays as calm as every other row — only the small
   print says the job is past the length someone asked for, so nothing on the board shouts. */
.row[data-state='overdue'] .row-main small {
  color: var(--danger);
  font-weight: 600;
}

.dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--kid, var(--muted));
  flex: none;
}

.empty {
  padding: 16px 0 4px;
  color: var(--muted);
}

.done {
  min-width: 116px;
  font-size: 17px;
}

.error {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
  border-radius: 12px;
  padding: 12px 14px;
}

.hint {
  color: var(--muted);
  text-align: center;
}

.field {
  display: block;
  margin: 12px 0;
}

.field span {
  display: block;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 6px;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px;
  min-height: 50px;
}

/* A description runs to more than one line and the keyboard must not be able to stretch it over the
   Done button it sits above. */
.field textarea {
  resize: vertical;
}

/* A control that will not answer still has to look like it will not answer. */
.field select:disabled {
  color: var(--muted);
}

.segmented {
  display: inline-flex;
  gap: 4px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px;
}

.segmented button {
  border: 0;
  background: transparent;
  border-radius: 999px;
  padding: 10px 20px;
  min-height: 44px;
  color: var(--muted);
}

.segmented button.on {
  background: var(--accent);
  color: #fff;
}

.login {
  max-width: 560px;
  margin: 0 auto;
  padding: calc(40px + env(safe-area-inset-top)) var(--pad) calc(32px + env(safe-area-inset-bottom));
  display: grid;
  gap: 22px;
}

.login-head {
  display: grid;
  justify-items: center;
  gap: 16px;
}

.login-head h1 {
  margin: 0;
  font-size: clamp(26px, 6vw, 38px);
  letter-spacing: -0.02em;
}

.name-row {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  padding: 0;
  margin: 0 0 8px;
}

.avatar {
  border: 2px solid var(--line);
  background: var(--card);
  border-radius: 20px;
  padding: 16px 22px;
  min-height: 64px;
  font-size: 19px;
  font-weight: 650;
  color: var(--kid, inherit);
}

.avatar.on {
  border-color: var(--kid, var(--accent));
  box-shadow: 0 6px 20px color-mix(in srgb, var(--kid, var(--accent)) 30%, transparent);
}

.pin-dots {
  display: flex;
  gap: 16px;
  justify-content: center;
  margin: 6px 0 2px;
}

.pin-dots .dot {
  width: 16px;
  height: 16px;
  background: transparent;
  border: 2px solid var(--muted);
}

.pin-dots .dot.full {
  background: var(--ink);
  border-color: var(--ink);
}

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  max-width: 380px;
  margin: 0 auto;
  width: 100%;
}

.key {
  aspect-ratio: 1.5;
  min-height: 62px;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: var(--card);
  font-size: 26px;
  font-variant-numeric: tabular-nums;
}

.key:active {
  background: var(--accent);
  color: #fff;
  transform: scale(0.97);
}

.key:disabled {
  opacity: 0.4;
}

.parent-signin {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
}

.parent-signin .primary {
  width: 100%;
}

.kid-signin {
  display: grid;
  gap: 18px;
}

.sheet-scrim {
  position: fixed;
  inset: 0;
  background: rgba(8, 10, 16, 0.45);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
  z-index: 10;
}

.sheet {
  background: var(--card);
  width: min(620px, 100%);
  border-radius: 22px 22px 0 0;
  padding: 22px var(--pad) calc(24px + env(safe-area-inset-bottom));
  animation: rise 0.22s ease-out;
  display: flex;
  flex-direction: column;
  max-height: calc(100dvh - 12px);
}

/* Nothing dismisses a sheet by being tapped, so a sheet has to be tall-but-bounded instead: the list
   scrolls inside it and the Close button stays where the thumb already knows it is. */
.sheet-scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  min-height: 0;
}

.sheet h3 {
  margin: 0 0 10px;
  font-size: 20px;
}

.sheet-actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
  margin-top: 18px;
}

@keyframes rise {
  from {
    transform: translateY(18px);
    opacity: 0.6;
  }
}

.day-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin: 10px 0;
}

.day {
  border: 1px solid var(--line);
  background: var(--bg);
  border-radius: 12px;
  padding: 10px 12px;
  min-height: 44px;
}

.day.on {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}

.swatches {
  display: flex;
  gap: 10px;
  margin: 12px 0;
}

.swatch {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 3px solid transparent;
  background: var(--kid);
}

.swatch.on {
  border-color: var(--ink);
}

/* Two boards, one grid: seven columns because a week is the shape the eye already has. A cell is a
   day, a chip is one chore on it, and a chip's colour comes from the single rule in client/calendar.
   A month cell keeps the 44px floor every other tap target in the app keeps, which is what six rows
   of an open day are for. */
.cal-nav {
  display: flex;
  align-items: center;
  gap: 6px;
}

.cal-nav span {
  min-width: 9.5em;
  text-align: center;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cal-head,
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
}

.cal-head {
  padding: 2px 0 6px;
}

.cal-head b {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.cal-day,
.cal-cell {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 7px 6px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg);
  min-width: 0;
  /* A day number, three chores and what follows them. */
  min-height: 98px;
}

.cal-day > b,
.cal-day > small {
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.cal-day > b {
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* The number is the top strip of the cell and the whole width of it, so the day opens from wherever
   the thumb happened to land in that row. */
.cal-day-num {
  width: 100%;
  border: 0;
  background: none;
  padding: 0 2px;
  text-align: right;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.cal-day[data-today='yes'],
.cal-cell[data-today='yes'] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* The tail of the month before and the head of the one after: true, and not what this page is about. */
.cal-cell[data-out='yes'] {
  opacity: 0.45;
}

.cal-day-entries {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 4px;
  min-width: 0;
}

.cal-chip {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  min-width: 0;
  min-height: 20px;
  padding: 2px 6px;
  border: 0;
  border-radius: 7px;
  font-size: 11px;
  font-weight: 600;
  text-align: left;
  overflow: hidden;
  background: color-mix(in srgb, var(--tone, var(--muted)) 16%, transparent);
  color: var(--tone, var(--muted));
}

/* A flex item refuses to shrink below its words unless it is told it may, which is the difference
   between a chip that ellipsizes inside its day and one that walks over into the next. */
.cal-chip > span {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.cal-chip .dot {
  width: 7px;
  height: 7px;
}

.cal-chip[data-tone='done'] {
  --tone: var(--cal-done);
}

.cal-chip[data-tone='missed'] {
  --tone: var(--cal-missed);
}

/* The days ahead are a plan, not a fact, and a month is mostly made of them. So this one tone is an
   outline instead of a fill: the days that have actually happened are the ones that stand out. */
.cal-chip[data-tone='soon'] {
  --tone: var(--cal-soon);
  background: none;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tone) 50%, transparent);
  color: color-mix(in srgb, var(--tone) 72%, var(--ink));
}

.cal-chip[data-tone='wait'] {
  --tone: var(--cal-wait);
}

.cal-more,
.cal-money {
  border: 0;
  background: none;
  padding: 0 6px;
  text-align: left;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The day's corrections added up. It is a chip like the chores are, because it opens the same day. */
.cal-money {
  min-height: 20px;
  padding: 2px 6px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--muted) 16%, transparent);
  color: var(--ink);
  font-weight: 650;
}

/* The grid's four colours again, said quietly: a day sheet lists a month's worth of rows at once and
   needs the same "what happened" at a glance. Only the small print says it, as everywhere else here. */
.row[data-tone='done'] .row-main small {
  color: var(--cal-done);
}

.row[data-tone='wait'] .row-main small {
  color: var(--cal-wait);
}

.row[data-tone='missed'] .row-main small {
  color: var(--cal-missed);
  font-weight: 600;
}

/* A day sheet's entries open on tap, so the whole row is the button and the row's own furniture has to
   come off the button rather than off the list item. */
.cal-open {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
}

@media (max-width: 560px) {
  .bar {
    gap: 8px;
  }
  .tabs {
    order: 3;
    width: 100%;
    justify-content: space-between;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}

/* The living-room screen. No control on it can be tapped, so the sizes that stop iOS zooming on an
   iPad do not apply here and everything is free to scale with the wall it hangs on — and it shows one
   month at a time and scrolls nothing, because the bottom row of a month nobody can scroll is a week
   nobody sees. So the grid is fitted to the screen rather than allowed to grow past it. */
.kiosk {
  display: flex;
  flex-direction: column;
  gap: clamp(8px, 1.2vh, 20px);
  padding: clamp(12px, 2vw, 40px);
  height: 100vh;
  overflow: hidden;
}

.kiosk-bar {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: clamp(10px, 2vw, 40px);
  flex: none;
}

.kiosk-bar h1 {
  margin: 0;
  font-size: clamp(24px, 3.2vw, 60px);
}

.kiosk-legend {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(10px, 1.6vw, 32px);
  font-size: clamp(14px, 1.5vw, 28px);
  font-weight: 600;
}

.kiosk-kid {
  display: flex;
  align-items: center;
  gap: 0.4em;
  color: var(--kid, inherit);
}

.kiosk-kid::before {
  content: '';
  width: 0.7em;
  height: 0.7em;
  border-radius: 50%;
  background: var(--kid);
}

.kiosk-updated {
  margin-left: auto;
  font-size: clamp(12px, 1.1vw, 20px);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* A frozen screen looks exactly like a quiet month, so the only difference is this saying so. */
.kiosk-updated[data-stale='yes'] {
  color: var(--danger);
  font-weight: 700;
}

.kiosk-head {
  flex: none;
}

.kiosk-head b {
  font-size: clamp(11px, 1.2vw, 22px);
}

/* Six rows of one sixth of what is left, so the six rows a month can need are always six rows and
   the grid never grows past the screen. */
.kiosk-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  grid-template-rows: repeat(6, minmax(0, 1fr));
  gap: clamp(6px, 0.8vw, 16px);
  flex: 1;
  min-height: 0;
}

.kiosk-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: clamp(4px, 0.5vw, 10px);
  border: 1px solid var(--line);
  border-radius: clamp(8px, 1vw, 18px);
  background: var(--card);
  min-width: 0;
  overflow: hidden;
}

.kiosk-cell[data-out='yes'] {
  background: transparent;
  opacity: 0.45;
}

.kiosk-cell[data-today='yes'] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.kiosk-num {
  font-size: clamp(11px, min(0.9vw, 1.5vh), 18px);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* A day with more jobs than a cell can hold scrolls inside its own cell. Ugly, but it is the only
   answer that does not quietly drop a job from a screen nobody can tap. */
.kiosk-cell ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Colour is spent on the kid, so the state a job is in is drawn as a mark instead. A job still ahead
   wears no mark: where it sits in the month already says that, and a mark on every line makes the
   three that mean something harder to pick out. The size is read against the row it has to live in
   rather than the width of the screen — five jobs and a day number is what a day here holds, and a
   line cut in half reads as nothing at all. */
.kiosk-cell li {
  color: var(--kid, inherit);
  font-size: clamp(11px, min(0.8vw, 1.4vh), 15px);
  font-weight: 600;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.kiosk-cell li::before {
  margin-right: 0.35em;
}

.kiosk-cell li[data-tone='done']::before {
  content: '✓';
}

.kiosk-cell li[data-tone='done'] {
  opacity: 0.55;
}

.kiosk-cell li[data-tone='wait']::before {
  content: '●';
}

.kiosk-cell li[data-tone='missed']::before {
  content: '!';
  color: var(--cal-missed);
}

.kiosk-cell li[data-tone='soon'] {
  font-weight: 500;
  opacity: 0.85;
}

/* The family board. A message is one line where the writer's name keeps their colour, and the line
   wraps rather than clipping, because a sentence is not a title. The height is capped so a long
   conversation cannot push the composer off a card at the bottom of the kid's page. */
.messages {
  max-height: min(46vh, 420px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

.messages li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}

/* The one line that says nobody has spoken yet needs no rule above it. */
.messages li.empty {
  border-top: 0;
}

.messages .who {
  color: var(--kid, inherit);
  font-weight: 700;
}

.messages .said {
  /* Grows into the line and gives way on it, so the name and the time stay on the same row until
     they genuinely cannot. */
  flex: 1 1 16ch;
  min-width: 0;
  font-size: 16px;
  overflow-wrap: anywhere;
}

.messages .when {
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* The delete is a mark, not a button: a thing this reversible by hand should not weigh as much as
   the message next to it. Tapping it turns the row into the question. */
.messages .wipe {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  padding: 4px 8px;
  min-width: 44px;
  min-height: 44px;
}

.messages .sure {
  display: flex;
  gap: 8px;
}

.messages .sure button {
  padding: 8px 12px;
  min-height: 40px;
}

.say {
  display: flex;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.say input {
  flex: 1;
  min-width: 0;
}
